1 module served.utils.diet;
2 
3 import dietc.complete;
4 import dietc.lexer;
5 import dietc.parser;
6 
7 import vscode = served.lsp.protocol;
8 
9 import ext = served.extension;
10 import served.types : documents;
11 
12 import std.algorithm;
13 import std.experimental.logger;
14 import std.path;
15 import std.string;
16 
17 DietComplete[string] dietFileCache;
18 
19 DietComplete updateDietFile(string file, string content)
20 {
21 	if (auto existing = file in dietFileCache)
22 	{
23 		existing.reparse(content);
24 		return *existing;
25 	}
26 	else
27 	{
28 		DietInput input;
29 		input.file = file;
30 		input.code = content;
31 		auto ret = new DietComplete(input, DietComplete.defaultFileProvider(file.dirName));
32 		dietFileCache[file] = ret;
33 		return ret;
34 	}
35 }
36 
37 vscode.CompletionItemKind mapToCompletionItemKind(CompletionType type)
38 {
39 	final switch (type)
40 	{
41 	case CompletionType.none:
42 		return vscode.CompletionItemKind.text;
43 	case CompletionType.tag:
44 		return vscode.CompletionItemKind.keyword;
45 	case CompletionType.attribute:
46 		return vscode.CompletionItemKind.property;
47 	case CompletionType.value:
48 		return vscode.CompletionItemKind.constant;
49 	case CompletionType.reference:
50 		return vscode.CompletionItemKind.reference;
51 	case CompletionType.cssName:
52 		return vscode.CompletionItemKind.property;
53 	case CompletionType.cssValue:
54 		return vscode.CompletionItemKind.value;
55 	case CompletionType.d:
56 		return vscode.CompletionItemKind.snippet;
57 	case CompletionType.meta:
58 		return vscode.CompletionItemKind.keyword;
59 	}
60 }
61 
62 void contextExtractD(DietComplete completion, size_t offset, out string code,
63 		out size_t dOffset, bool extractContext)
64 {
65 	string prefix;
66 	if (completion.parser.root.children.length > 0)
67 	{
68 		int i = 0;
69 		if (auto node = cast(TagNode) completion.parser.root.children[i])
70 		{
71 			if (node.name == "extends" && completion.parser.root.children.length > 1)
72 				i++;
73 		}
74 
75 		if (auto comment = cast(HiddenComment) completion.parser.root.children[i])
76 		{
77 			string startComment = comment.content.strip;
78 			info("Have context ", startComment);
79 			if (startComment.startsWith("context=") && extractContext)
80 			{
81 				auto context = startComment["context=".length .. $];
82 				auto end = context.indexOfAny(" ;,");
83 				if (end != -1)
84 					context = context[0 .. end];
85 
86 				context = context.strip;
87 				if (!context.endsWith(".d"))
88 					context ~= ".d";
89 
90 				auto currentFile = completion.parser.input.file.baseName;
91 
92 				foreach (doc; documents.documentStore)
93 				{
94 					if (doc.uri.endsWith(context))
95 					{
96 						auto content = doc.rawText;
97 						infof("Searching for diet file '%s' in context file '%s'", currentFile, doc.uri);
98 
99 						auto index = searchDietTemplateUsage(content, currentFile);
100 						if (index >= 0)
101 							prefix = content[0 .. index].idup;
102 						else
103 							info("Failed finding diet file, error ", index);
104 						break;
105 					}
106 				}
107 
108 				if (!prefix.length)
109 				{
110 					infof("Didn't find any valid context for diet file '%s' when searching for context '%s'",
111 							currentFile, context);
112 				}
113 			}
114 		}
115 	}
116 
117 	extractD(completion, offset, code, dOffset, prefix);
118 }
119 
120 ptrdiff_t searchDietTemplateUsage(scope const(char)[] code, scope const(char)[] dietFile)
121 {
122 	auto index = code.indexOf(dietFile);
123 	if (index == -1)
124 		return -2;
125 
126 	if (!code[index + dietFile.length .. $].startsWith("\"", "`"))
127 		return -3;
128 
129 	auto funcStart = code[0 .. index].lastIndexOfAny(";!(");
130 	if (funcStart == -1)
131 		return -4;
132 
133 	return code[0 .. funcStart].lastIndexOfAny(";\r\n");
134 }