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 		debug (SnippetScope)
66 		{
67 			import painlessjson : toJSON;
68 
69 			Snippet ret;
70 			ret.providerId = typeid(this).name;
71 			ret.id = "workspaced-snippet-debug";
72 			ret.title = "[DEBUG] Snippet";
73 			ret.shortcut = "__debug_snippet";
74 			ret.plain = ret.snippet = info.toJSON.toPrettyString;
75 			ret.unformatted = true;
76 			ret.resolved = true;
77 			res ~= ret;
78 		}
79 
80 		return typeof(return).fromResult(res.length ? res : null);
81 	}
82 
83 	Future!Snippet resolveSnippet(scope const WorkspaceD.Instance instance,
84 			scope const(char)[] file, scope const(char)[] code, int position,
85 			const SnippetInfo info, Snippet snippet)
86 	{
87 		return typeof(return).fromResult(snippet);
88 	}
89 
90 	Snippet ndForeach(int n, string name = null)
91 	{
92 		Snippet ret;
93 		ret.providerId = typeid(this).name;
94 		ret.id = "nd";
95 		ret.title = "foreach over " ~ n.to!string ~ " keys";
96 		if (name.length)
97 			ret.title ~= " (over " ~ name ~ ")";
98 		ret.shortcut = "foreach";
99 		ret.documentation = "Foreach over locally defined variable with " ~ n.to!string ~ " keys.";
100 		string keys;
101 		if (n == 2)
102 		{
103 			keys = "key, value";
104 		}
105 		else if (n <= 4)
106 		{
107 			foreach (i; 0 .. n - 1)
108 			{
109 				keys ~= cast(char)('i' + i) ~ ", ";
110 			}
111 			keys ~= "value";
112 		}
113 		else
114 		{
115 			foreach (i; 0 .. n - 1)
116 			{
117 				keys ~= "k" ~ (i + 1).to!string ~ ", ";
118 			}
119 			keys ~= "value";
120 		}
121 
122 		if (name.length)
123 		{
124 			ret.plain = "foreach (" ~ keys ~ "; " ~ name ~ ") {\n\t\n}";
125 			ret.snippet = "foreach (${1:" ~ keys ~ "}; ${2:" ~ name ~ "}) {\n\t$0\n}";
126 		}
127 		else
128 		{
129 			ret.plain = "foreach (" ~ keys ~ "; map) {\n\t\n}";
130 			ret.snippet = "foreach (${1:" ~ keys ~ "}; ${2:map}) {\n\t$0\n}";
131 		}
132 		ret.resolved = true;
133 		return ret;
134 	}
135 
136 	Snippet simpleForeach(string name = null, string type = null)
137 	{
138 		Snippet ret;
139 		ret.providerId = typeid(this).name;
140 		ret.id = "simple";
141 		ret.title = "foreach loop";
142 		if (name.length)
143 			ret.title ~= " (over " ~ name ~ ")";
144 		ret.shortcut = "foreach";
145 		ret.documentation = name.length
146 			? "Foreach over locally defined variable." : "Foreach over a variable or range.";
147 		string t = type.length ? type ~ " " : null;
148 		if (name.length)
149 		{
150 			ret.plain = "foreach (" ~ t ~ "key; " ~ name ~ ") {\n\t\n}";
151 			ret.snippet = "foreach (" ~ t ~ "${1:key}; ${2:" ~ name ~ "}) {\n\t$0\n}";
152 		}
153 		else
154 		{
155 			ret.plain = "foreach (" ~ t ~ "key; list) {\n\t\n}";
156 			ret.snippet = "foreach (" ~ t ~ "${1:key}; ${2:list}) {\n\t$0\n}";
157 		}
158 		ret.resolved = true;
159 		return ret;
160 	}
161 
162 	Snippet stringIterators(string name = null)
163 	{
164 		Snippet ret;
165 		ret.providerId = typeid(this).name;
166 		ret.id = "str";
167 		ret.title = "foreach loop";
168 		if (name.length)
169 			ret.title ~= " (unicode over " ~ name ~ ")";
170 		else
171 			ret.title ~= " (unicode)";
172 		ret.shortcut = "foreach_utf";
173 		ret.documentation = name.length
174 			? "Foreach over locally defined variable." : "Foreach over a variable or range.";
175 		if (name.length)
176 		{
177 			ret.plain = "foreach (char key; " ~ name ~ ") {\n\t\n}";
178 			ret.snippet = "foreach (${1|char,wchar,dchar|} ${2:key}; ${3:" ~ name ~ "}) {\n\t$0\n}";
179 		}
180 		else
181 		{
182 			ret.plain = "foreach (char key; str) {\n\t\n}";
183 			ret.snippet = "foreach (${1|char,wchar,dchar|} ${2:key}; ${3:str}) {\n\t$0\n}";
184 		}
185 		ret.resolved = true;
186 		return ret;
187 	}
188 }