1 module served.commands.code_lens;
2 
3 import served.commands.code_actions;
4 
5 import served.extension;
6 import served.types;
7 import served.utils.fibermanager;
8 
9 import workspaced.api;
10 import workspaced.coms;
11 
12 import core.time : minutes, msecs;
13 
14 import std.algorithm : startsWith;
15 import std.conv : to;
16 import std.datetime.stopwatch : StopWatch;
17 import std.datetime.systime : Clock, SysTime;
18 import std.regex : matchAll;
19 
20 @protocolMethod("textDocument/codeLens")
21 CodeLens[] provideCodeLens(CodeLensParams params)
22 {
23 	auto document = documents[params.textDocument.uri];
24 	string file = document.uri.uriToFile;
25 	if (document.languageId != "d")
26 		return [];
27 	CodeLens[] ret;
28 	if (workspace(params.textDocument.uri).config.d.enableDMDImportTiming)
29 	{
30 		size_t lastIndex = size_t.max;
31 		Position lastPosition;
32 
33 		foreach (match; document.rawText.matchAll(importRegex))
34 		{
35 			size_t index = match.pre.length;
36 			auto pos = document.movePositionBytes(lastPosition, lastIndex, index);
37 			lastIndex = index;
38 			lastPosition = pos;
39 
40 			ret ~= CodeLens(TextRange(pos), Optional!Command.init,
41 					JsonValue([
42 						"type": JsonValue("importcompilecheck"),
43 						"code": JsonValue(match.hit.idup),
44 						"module": JsonValue(match[1].idup),
45 						"file": JsonValue(file)
46 					]).opt);
47 		}
48 	}
49 	return ret;
50 }
51 
52 @protocolMethod("codeLens/resolve")
53 CodeLens resolveCodeLens(CodeLens lens)
54 {
55 	if (lens.data.isNone ||
56 		lens.data.deref.kind != JsonValue.Kind.object)
57 		throw new Exception("Invalid Lens Object");
58 
59 	auto lensData = lens.data.deref.get!(StringMap!JsonValue);
60 	auto type = lensData.get("type", JsonValue(""))
61 		.match!((string s) => s, _ => "");
62 	switch (type)
63 	{
64 	case "importcompilecheck":
65 		try
66 		{
67 			auto code = lensData.get("code", JsonValue(null))
68 				.match!((string s) => s, _ => "");
69 			if (!code.length)
70 				throw new Exception("No valid code provided");
71 
72 			auto module_ = lensData.get("module", JsonValue(null))
73 				.match!((string s) => s, _ => "");
74 			if (!module_.length)
75 				throw new Exception("No valid module provided");
76 
77 			auto file = lensData.get("file", JsonValue(null))
78 				.match!((string s) => s, _ => "");
79 			if (!file.length)
80 				throw new Exception("No valid file provided");
81 
82 			int decMs = getImportCompilationTime(code, module_, file);
83 			lens.command = Command((decMs < 10
84 					? "no noticable effect" : "~" ~ decMs.to!string ~ "ms") ~ " for importing this");
85 			return lens;
86 		}
87 		catch (Exception)
88 		{
89 			lens.command = Command.init;
90 			return lens;
91 		}
92 	default:
93 		throw new Exception("Unknown lens type");
94 	}
95 }
96 
97 bool importCompilationTimeRunning;
98 int getImportCompilationTime(string code, string module_, string file)
99 {
100 	import std.math : round;
101 
102 	static struct CompileCache
103 	{
104 		SysTime at;
105 		string code;
106 		int ret;
107 	}
108 
109 	static CompileCache[] cache;
110 
111 	auto now = Clock.currTime;
112 
113 	foreach_reverse (i, exist; cache)
114 	{
115 		if (exist.code != code)
116 			continue;
117 		if (now - exist.at < (exist.ret >= 500 ? 20.minutes : exist.ret >= 30 ? 5.minutes
118 				: 2.minutes) || module_.startsWith("std."))
119 			return exist.ret;
120 		else
121 		{
122 			cache[i] = cache[$ - 1];
123 			cache.length--;
124 		}
125 	}
126 
127 	while (importCompilationTimeRunning)
128 		Fiber.yield();
129 	importCompilationTimeRunning = true;
130 	scope (exit)
131 		importCompilationTimeRunning = false;
132 	// run blocking so we don't compute multiple in parallel
133 	auto ret = backend.best!DMDComponent(file).measureSync(code, null, 20, 500);
134 	if (!ret.success)
135 		throw new Exception("Compilation failed");
136 	auto msecs = cast(int) round(ret.duration.total!"msecs" / 5.0) * 5;
137 	cache ~= CompileCache(now, code, msecs);
138 	StopWatch sw;
139 	sw.start();
140 	while (sw.peek < 100.msecs) // pass through requests for 100ms
141 		Fiber.yield();
142 	return msecs;
143 }