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