1 module workspaced.com.snippets.smart;
2 
3 //debug = SnippetScope;
4 
5 import workspaced.api;
6 import workspaced.com.snippets;
7 
8 import std.algorithm;
9 import std.conv;
10 import std.string;
11 
12 class SmartSnippetProvider : SnippetProvider
13 {
14 	Future!(Snippet[]) provideSnippets(scope const WorkspaceD.Instance instance,
15 			scope const(char)[] file, scope const(char)[] code, int position, const SnippetInfo info)
16 	{
17 		Snippet[] res;
18 
19 		if (info.loopScope.supported)
20 		{
21 			if (info.loopScope.numItems > 1)
22 			{
23 				res ~= ndForeach(info.loopScope.numItems, info.loopScope.iterator);
24 				res ~= simpleForeach();
25 				res ~= stringIterators();
26 			}
27 			else if (info.loopScope.stringIterator)
28 			{
29 				res ~= simpleForeach();
30 				res ~= stringIterators(info.loopScope.iterator);
31 			}
32 			else
33 			{
34 				res ~= simpleForeach(info.loopScope.iterator, info.loopScope.type);
35 				res ~= stringIterators();
36 			}
37 		}
38 
39 		if (info.lastStatement.type == "IfStatement"
40 			&& !info.lastStatement.ifHasElse)
41 		{
42 			int ifIndex = info.contextTokenIndex == 0 ? position : info.contextTokenIndex;
43 			auto hasBraces = code[0 .. max(min(ifIndex, $), 0)].stripRight.endsWith("}");
44 			Snippet snp;
45 			snp.providerId = typeid(this).name;
46 			snp.id = "else";
47 			snp.title = "else";
48 			snp.shortcut = "else";
49 			snp.documentation = "else block";
50 			if (hasBraces)
51 			{
52 				snp.plain = "else {\n\t\n}";
53 				snp.snippet = "else {\n\t$0\n}";
54 			}
55 			else
56 			{
57 				snp.plain = "else\n\t";
58 				snp.snippet = "else\n\t$0";
59 			}
60 			snp.unformatted = true;
61 			snp.resolved = true;
62 			res ~= snp;
63 		}
64 
65 		if (info.lastStatement.type == "TryStatement")
66 		{
67 			int tryIndex = info.contextTokenIndex == 0 ? position : info.contextTokenIndex;
68 			auto hasBraces = code[0 .. max(min(tryIndex, $), 0)].stripRight.endsWith("}");
69 			Snippet catchSnippet;
70 			catchSnippet.providerId = typeid(this).name;
71 			catchSnippet.id = "catch";
72 			catchSnippet.title = "catch";
73 			catchSnippet.shortcut = "catch";
74 			catchSnippet.documentation = "catch block";
75 			if (hasBraces)
76 			{
77 				catchSnippet.plain = "catch (Exception e) {\n\t\n}";
78 				catchSnippet.snippet = "catch (${1:Exception e}) {\n\t$0\n}";
79 			}
80 			else
81 			{
82 				catchSnippet.plain = "catch (Exception e)\n\t";
83 				catchSnippet.snippet = "catch (${1:Exception e})\n\t$0";
84 			}
85 			catchSnippet.unformatted = true;
86 			catchSnippet.resolved = true;
87 			res ~= catchSnippet;
88 
89 			Snippet finallySnippet;
90 			finallySnippet.providerId = typeid(this).name;
91 			finallySnippet.id = "finally";
92 			finallySnippet.title = "finally";
93 			finallySnippet.shortcut = "finally";
94 			finallySnippet.documentation = "finally block";
95 			if (hasBraces)
96 			{
97 				finallySnippet.plain = "finally {\n\t\n}";
98 				finallySnippet.snippet = "finally {\n\t$0\n}";
99 			}
100 			else
101 			{
102 				finallySnippet.plain = "finally\n\t";
103 				finallySnippet.snippet = "finally\n\t$0";
104 			}
105 			finallySnippet.unformatted = true;
106 			finallySnippet.resolved = true;
107 			res ~= finallySnippet;
108 		}
109 
110 		debug (SnippetScope)
111 		{
112 			import served.lsp.jsonops : serializeJson;
113 
114 			Snippet ret;
115 			ret.providerId = typeid(this).name;
116 			ret.id = "workspaced-snippet-debug";
117 			ret.title = "[DEBUG] Snippet";
118 			ret.shortcut = "__debug_snippet";
119 			ret.plain = ret.snippet = info.serializeJson;
120 			ret.unformatted = true;
121 			ret.resolved = true;
122 			res ~= ret;
123 		}
124 
125 		return typeof(return).fromResult(res.length ? res : null);
126 	}
127 
128 	Future!Snippet resolveSnippet(scope const WorkspaceD.Instance instance,
129 			scope const(char)[] file, scope const(char)[] code, int position,
130 			const SnippetInfo info, Snippet snippet)
131 	{
132 		return typeof(return).fromResult(snippet);
133 	}
134 
135 	Snippet ndForeach(int n, string name = null)
136 	{
137 		Snippet ret;
138 		ret.providerId = typeid(this).name;
139 		ret.id = "nd";
140 		ret.title = "foreach over " ~ n.to!string ~ " keys";
141 		if (name.length)
142 			ret.title ~= " (over " ~ name ~ ")";
143 		ret.shortcut = "foreach";
144 		ret.documentation = "Foreach over locally defined variable with " ~ n.to!string ~ " keys.";
145 		string keys;
146 		if (n == 2)
147 		{
148 			keys = "key, value";
149 		}
150 		else if (n <= 4)
151 		{
152 			foreach (i; 0 .. n - 1)
153 			{
154 				keys ~= cast(char)('i' + i) ~ ", ";
155 			}
156 			keys ~= "value";
157 		}
158 		else
159 		{
160 			foreach (i; 0 .. n - 1)
161 			{
162 				keys ~= "k" ~ (i + 1).to!string ~ ", ";
163 			}
164 			keys ~= "value";
165 		}
166 
167 		if (name.length)
168 		{
169 			ret.plain = "foreach (" ~ keys ~ "; " ~ name ~ ") {\n\t\n}";
170 			ret.snippet = "foreach (${1:" ~ keys ~ "}; ${2:" ~ name ~ "}) {\n\t$0\n}";
171 		}
172 		else
173 		{
174 			ret.plain = "foreach (" ~ keys ~ "; map) {\n\t\n}";
175 			ret.snippet = "foreach (${1:" ~ keys ~ "}; ${2:map}) {\n\t$0\n}";
176 		}
177 		ret.resolved = true;
178 		return ret;
179 	}
180 
181 	Snippet simpleForeach(string name = null, string type = null)
182 	{
183 		Snippet ret;
184 		ret.providerId = typeid(this).name;
185 		ret.id = "simple";
186 		ret.title = "foreach loop";
187 		if (name.length)
188 			ret.title ~= " (over " ~ name ~ ")";
189 		ret.shortcut = "foreach";
190 		ret.documentation = name.length
191 			? "Foreach over locally defined variable." : "Foreach over a variable or range.";
192 		string t = type.length ? type ~ " " : null;
193 		if (name.length)
194 		{
195 			ret.plain = "foreach (" ~ t ~ "key; " ~ name ~ ") {\n\t\n}";
196 			ret.snippet = "foreach (" ~ t ~ "${1:key}; ${2:" ~ name ~ "}) {\n\t$0\n}";
197 		}
198 		else
199 		{
200 			ret.plain = "foreach (" ~ t ~ "key; list) {\n\t\n}";
201 			ret.snippet = "foreach (" ~ t ~ "${1:key}; ${2:list}) {\n\t$0\n}";
202 		}
203 		ret.resolved = true;
204 		return ret;
205 	}
206 
207 	Snippet stringIterators(string name = null)
208 	{
209 		Snippet ret;
210 		ret.providerId = typeid(this).name;
211 		ret.id = "str";
212 		ret.title = "foreach loop";
213 		if (name.length)
214 			ret.title ~= " (unicode over " ~ name ~ ")";
215 		else
216 			ret.title ~= " (unicode)";
217 		ret.shortcut = "foreach_utf";
218 		ret.documentation = name.length
219 			? "Foreach over locally defined variable." : "Foreach over a variable or range.";
220 		if (name.length)
221 		{
222 			ret.plain = "foreach (char key; " ~ name ~ ") {\n\t\n}";
223 			ret.snippet = "foreach (${1|char,wchar,dchar|} ${2:key}; ${3:" ~ name ~ "}) {\n\t$0\n}";
224 		}
225 		else
226 		{
227 			ret.plain = "foreach (char key; str) {\n\t\n}";
228 			ret.snippet = "foreach (${1|char,wchar,dchar|} ${2:key}; ${3:str}) {\n\t$0\n}";
229 		}
230 		ret.resolved = true;
231 		return ret;
232 	}
233 }