1 /**
2  * This file is part of DCD, a development tool for the D programming language.
3  * Copyright (C) 2014 Brian Schott
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 module server.autocomplete;
20 
21 import std.algorithm;
22 import std.experimental.allocator;
23 import std.experimental.logger;
24 import std.array;
25 import std.conv;
26 import std.experimental.logger;
27 import std.file;
28 import std.path;
29 import std.range;
30 import std.stdio;
31 import std.string;
32 import std.typecons;
33 import std.uni;
34 
35 import dparse.ast;
36 import dparse.lexer;
37 import dparse.parser;
38 
39 import dsymbol.conversion;
40 import dsymbol.modulecache;
41 import dsymbol.string_interning;
42 import dsymbol.symbol;
43 import dsymbol.scope_;
44 import dsymbol.builtin.names;
45 import dsymbol.builtin.symbols;
46 
47 import common.constants;
48 import common.messages;
49 
50 /**
51  * Gets documentation for the symbol at the cursor
52  * Params:
53  *     request = the autocompletion request
54  * Returns:
55  *     the autocompletion response
56  */
57 public AutocompleteResponse getDoc(const AutocompleteRequest request,
58 	ref ModuleCache moduleCache)
59 {
60 //	trace("Getting doc comments");
61 	AutocompleteResponse response;
62 	auto allocator = scoped!(ASTAllocator)();
63 	auto cache = StringCache(StringCache.defaultBucketCount);
64 	SymbolStuff stuff = getSymbolsForCompletion(request, CompletionType.ddoc,
65 		allocator, cache, moduleCache);
66 	if (stuff.symbols.length == 0)
67 		warning("Could not find symbol");
68 	else
69 	{
70 		struct Escaper(O)
71 		{
72 			this(O* or)
73 			{
74 				this.outputRange = or;
75 			}
76 
77 			void put(string s)
78 			{
79 				foreach (c; s)
80 					put(c);
81 			}
82 
83 			void put(char c)
84 			{
85 				switch (c)
86 				{
87 				case '\n':
88 					outputRange.put('\\');
89 					outputRange.put('n');
90 					break;
91 				default:
92 					outputRange.put(c);
93 					break;
94 				}
95 			}
96 
97 			O* outputRange;
98 		}
99 
100 		auto app = appender!(char[])();
101 		auto e = Escaper!(typeof(app))(&app);
102 		foreach (symbol; stuff.symbols.filter!(a => !a.doc.empty))
103 		{
104 			app.clear();
105 			foreach(c; symbol.doc)
106 				e.put(c);
107 			response.docComments ~= cast(string) app.data;
108 		}
109 	}
110 	return response;
111 }
112 
113 /**
114  * Finds the declaration of the symbol at the cursor position.
115  * Params:
116  *     request = the autocompletion request
117  * Returns:
118  *     the autocompletion response
119  */
120 public AutocompleteResponse findDeclaration(const AutocompleteRequest request,
121 	ref ModuleCache moduleCache)
122 {
123 	AutocompleteResponse response;
124 	auto allocator = scoped!(ASTAllocator)();
125 	auto cache = StringCache(StringCache.defaultBucketCount);
126 	SymbolStuff stuff = getSymbolsForCompletion(request,
127 		CompletionType.location, allocator, cache, moduleCache);
128 	scope(exit) stuff.destroy();
129 	if (stuff.symbols.length > 0)
130 	{
131 		response.symbolLocation = stuff.symbols[0].location;
132 		response.symbolFilePath = stuff.symbols[0].symbolFile.idup;
133 	}
134 	else
135 		warning("Could not find symbol declaration");
136 	return response;
137 }
138 
139 /**
140  * Handles autocompletion
141  * Params:
142  *     request = the autocompletion request
143  * Returns:
144  *     the autocompletion response
145  */
146 public AutocompleteResponse complete(const AutocompleteRequest request,
147 	ref ModuleCache moduleCache)
148 {
149 	const(Token)[] tokenArray;
150 	auto stringCache = StringCache(StringCache.defaultBucketCount);
151 	auto beforeTokens = getTokensBeforeCursor(request.sourceCode,
152 		request.cursorPosition, stringCache, tokenArray);
153 	if (beforeTokens.length >= 2)
154 	{
155 		if (beforeTokens[$ - 1] == tok!"(" || beforeTokens[$ - 1] == tok!"[")
156 		{
157 			return parenCompletion(beforeTokens, tokenArray, request.cursorPosition,
158 				moduleCache);
159 		}
160 		else if (beforeTokens[$ - 1] == tok!",")
161 		{
162 			immutable size_t end = goBackToOpenParen(beforeTokens);
163 			if (end != size_t.max)
164 				return parenCompletion(beforeTokens[0 .. end], tokenArray,
165 					request.cursorPosition, moduleCache);
166 		}
167 		else
168 		{
169 			ImportKind kind = determineImportKind(beforeTokens);
170 			if (kind == ImportKind.neither)
171 				return dotCompletion(beforeTokens, tokenArray, request.cursorPosition,
172 					moduleCache);
173 			else
174 				return importCompletion(beforeTokens, kind, moduleCache);
175 		}
176 	}
177 	return dotCompletion(beforeTokens, tokenArray, request.cursorPosition, moduleCache);
178 }
179 
180 /**
181  *
182  */
183 public AutocompleteResponse symbolSearch(const AutocompleteRequest request,
184 	ref ModuleCache moduleCache)
185 {
186 	import containers.ttree : TTree;
187 
188 	LexerConfig config;
189 	config.fileName = "";
190 	auto cache = StringCache(StringCache.defaultBucketCount);
191 	const(Token)[] tokenArray = getTokensForParser(cast(ubyte[]) request.sourceCode,
192 		config, &cache);
193 	auto allocator = scoped!(ASTAllocator)();
194 	ScopeSymbolPair pair = generateAutocompleteTrees(tokenArray, allocator,
195 		request.cursorPosition, moduleCache);
196 	scope(exit) pair.destroy();
197 
198 	static struct SearchResults
199 	{
200 		void put(const(DSymbol)* symbol)
201 		{
202 			tree.insert(SearchResult(symbol));
203 		}
204 
205 		static struct SearchResult
206 		{
207 			const(DSymbol)* symbol;
208 
209 			int opCmp(ref const SearchResult other) const pure nothrow
210 			{
211 				if (other.symbol.symbolFile < symbol.symbolFile)
212 					return -1;
213 				if (other.symbol.symbolFile > symbol.symbolFile)
214 					return 1;
215 				if (other.symbol.location < symbol.location)
216 					return -1;
217 				return other.symbol.location > symbol.location;
218 			}
219 		}
220 
221 		TTree!(SearchResult) tree;
222 	}
223 
224 	SearchResults results;
225 	foreach (symbol; pair.scope_.symbols)
226 		symbol.getAllPartsNamed(request.searchName, results);
227 	foreach (s; moduleCache.getAllSymbols())
228 		s.symbol.getAllPartsNamed(request.searchName, results);
229 
230 	AutocompleteResponse response;
231 	foreach (result; results.tree[])
232 	{
233 		response.locations ~= result.symbol.location;
234 		response.completionKinds ~= result.symbol.kind;
235 		response.completions ~= result.symbol.symbolFile;
236 	}
237 
238 	return response;
239 }
240 
241 /******************************************************************************/
242 private:
243 
244 enum ImportKind : ubyte
245 {
246 	selective,
247 	normal,
248 	neither
249 }
250 
251 /**
252  * Handles dot completion for identifiers and types.
253  * Params:
254  *     beforeTokens = the tokens before the cursor
255  *     tokenArray = all tokens in the file
256  *     cursorPosition = the cursor position in bytes
257  * Returns:
258  *     the autocompletion response
259  */
260 AutocompleteResponse dotCompletion(T)(T beforeTokens, const(Token)[] tokenArray,
261 	size_t cursorPosition, ref ModuleCache moduleCache)
262 {
263 	AutocompleteResponse response;
264 
265 	// Partial symbol name appearing after the dot character and before the
266 	// cursor.
267 	string partial;
268 
269 	// Type of the token before the dot, or identifier if the cursor was at
270 	// an identifier.
271 	IdType significantTokenType;
272 
273 	if (beforeTokens.length >= 1 && beforeTokens[$ - 1] == tok!"identifier")
274 	{
275 		// Set partial to the slice of the identifier between the beginning
276 		// of the identifier and the cursor. This improves the completion
277 		// responses when the cursor is in the middle of an identifier instead
278 		// of at the end
279 		auto t = beforeTokens[$ - 1];
280 		if (cursorPosition - t.index >= 0 && cursorPosition - t.index <= t.text.length)
281 			partial = t.text[0 .. cursorPosition - t.index];
282 		significantTokenType = tok!"identifier";
283 		beforeTokens = beforeTokens[0 .. $ - 1];
284 	}
285 	else if (beforeTokens.length >= 2 && beforeTokens[$ - 1] ==  tok!".")
286 		significantTokenType = beforeTokens[$ - 2].type;
287 	else
288 		return response;
289 
290 	switch (significantTokenType)
291 	{
292 	case tok!"stringLiteral":
293 	case tok!"wstringLiteral":
294 	case tok!"dstringLiteral":
295 		foreach (symbol; arraySymbols)
296 		{
297 			response.completionKinds ~= symbol.kind;
298 			response.completions ~= symbol.name.dup;
299 		}
300 		response.completionType = CompletionType.identifiers;
301 		break;
302 	case tok!"int":
303 	case tok!"uint":
304 	case tok!"long":
305 	case tok!"ulong":
306 	case tok!"char":
307 	case tok!"wchar":
308 	case tok!"dchar":
309 	case tok!"bool":
310 	case tok!"byte":
311 	case tok!"ubyte":
312 	case tok!"short":
313 	case tok!"ushort":
314 	case tok!"cent":
315 	case tok!"ucent":
316 	case tok!"float":
317 	case tok!"ifloat":
318 	case tok!"cfloat":
319 	case tok!"idouble":
320 	case tok!"cdouble":
321 	case tok!"double":
322 	case tok!"real":
323 	case tok!"ireal":
324 	case tok!"creal":
325 	case tok!"identifier":
326 	case tok!")":
327 	case tok!"]":
328 	case tok!"this":
329 	case tok!"super":
330 		auto allocator = scoped!(ASTAllocator)();
331 		ScopeSymbolPair pair = generateAutocompleteTrees(tokenArray, allocator,
332 			cursorPosition, moduleCache);
333 		scope(exit) pair.destroy();
334 		response.setCompletions(pair.scope_, getExpression(beforeTokens),
335 			cursorPosition, CompletionType.identifiers, false, partial);
336 		break;
337 	case tok!"(":
338 	case tok!"{":
339 	case tok!"[":
340 	case tok!";":
341 	case tok!":":
342 		break;
343 	default:
344 		break;
345 	}
346 	return response;
347 }
348 
349 /**
350  * Params:
351  *     sourceCode = the source code of the file being edited
352  *     cursorPosition = the cursor position in bytes
353  * Returns:
354  *     a sorted range of tokens before the cursor position
355  */
356 auto getTokensBeforeCursor(const(ubyte[]) sourceCode, size_t cursorPosition,
357 	ref StringCache cache, out const(Token)[] tokenArray)
358 {
359 	LexerConfig config;
360 	config.fileName = "";
361 	tokenArray = getTokensForParser(cast(ubyte[]) sourceCode, config, &cache);
362 	auto sortedTokens = assumeSorted(tokenArray);
363 	return sortedTokens.lowerBound(cast(size_t) cursorPosition);
364 }
365 
366 /**
367  * Params:
368  *     request = the autocompletion request
369  *     type = type the autocompletion type
370  * Returns:
371  *     all symbols that should be considered for the autocomplete list based on
372  *     the request's source code, cursor position, and completion type.
373  */
374 SymbolStuff getSymbolsForCompletion(const AutocompleteRequest request,
375 	const CompletionType type, IAllocator allocator, ref StringCache cache,
376 	ref ModuleCache moduleCache)
377 {
378 	const(Token)[] tokenArray;
379 	auto beforeTokens = getTokensBeforeCursor(request.sourceCode,
380 		request.cursorPosition, cache, tokenArray);
381 	ScopeSymbolPair pair = generateAutocompleteTrees(tokenArray, allocator,
382 		request.cursorPosition, moduleCache);
383 	auto expression = getExpression(beforeTokens);
384 	return SymbolStuff(getSymbolsByTokenChain(pair.scope_, expression,
385 		request.cursorPosition, type), pair.symbol, pair.scope_);
386 }
387 
388 struct SymbolStuff
389 {
390 	void destroy()
391 	{
392 		typeid(DSymbol).destroy(symbol);
393 		typeid(Scope).destroy(scope_);
394 	}
395 
396 	DSymbol*[] symbols;
397 	DSymbol* symbol;
398 	Scope* scope_;
399 }
400 
401 /**
402  * Handles paren completion for function calls and some keywords
403  * Params:
404  *     beforeTokens = the tokens before the cursor
405  *     tokenArray = all tokens in the file
406  *     cursorPosition = the cursor position in bytes
407  * Returns:
408  *     the autocompletion response
409  */
410 AutocompleteResponse parenCompletion(T)(T beforeTokens,
411 	const(Token)[] tokenArray, size_t cursorPosition, ref ModuleCache moduleCache)
412 {
413 	AutocompleteResponse response;
414 	immutable(string)[] completions;
415 	switch (beforeTokens[$ - 2].type)
416 	{
417 	case tok!"__traits":
418 		completions = traits;
419 		goto fillResponse;
420 	case tok!"scope":
421 		completions = scopes;
422 		goto fillResponse;
423 	case tok!"version":
424 		completions = predefinedVersions;
425 		goto fillResponse;
426 	case tok!"extern":
427 		completions = linkages;
428 		goto fillResponse;
429 	case tok!"pragma":
430 		completions = pragmas;
431 	fillResponse:
432 		response.completionType = CompletionType.identifiers;
433 		foreach (completion; completions)
434 		{
435 			response.completions ~= completion;
436 			response.completionKinds ~= CompletionKind.keyword;
437 		}
438 		break;
439 	case tok!"characterLiteral":
440 	case tok!"doubleLiteral":
441 	case tok!"dstringLiteral":
442 	case tok!"floatLiteral":
443 	case tok!"identifier":
444 	case tok!"idoubleLiteral":
445 	case tok!"ifloatLiteral":
446 	case tok!"intLiteral":
447 	case tok!"irealLiteral":
448 	case tok!"longLiteral":
449 	case tok!"realLiteral":
450 	case tok!"stringLiteral":
451 	case tok!"uintLiteral":
452 	case tok!"ulongLiteral":
453 	case tok!"wstringLiteral":
454 	case tok!"this":
455 	case tok!"super":
456 	case tok!")":
457 	case tok!"]":
458 		auto allocator = scoped!(ASTAllocator)();
459 		ScopeSymbolPair pair = generateAutocompleteTrees(tokenArray, allocator,
460 			cursorPosition, moduleCache);
461 		scope(exit) pair.destroy();
462 		auto expression = getExpression(beforeTokens[0 .. $ - 1]);
463 		response.setCompletions(pair.scope_, expression,
464 			cursorPosition, CompletionType.calltips, beforeTokens[$ - 1] == tok!"[");
465 		break;
466 	default:
467 		break;
468 	}
469 	return response;
470 }
471 
472 /**
473  * Determines if an import is selective, whole-module, or neither.
474  */
475 ImportKind determineImportKind(T)(T tokens)
476 {
477 	assert (tokens.length > 1);
478 	size_t i = tokens.length - 1;
479 	if (!(tokens[i] == tok!":" || tokens[i] == tok!"," || tokens[i] == tok!"."
480 			|| tokens[i] == tok!"identifier"))
481 		return ImportKind.neither;
482 	bool foundColon = false;
483 	while (true) switch (tokens[i].type)
484 	{
485 	case tok!":":
486 		foundColon = true;
487 		goto case;
488 	case tok!"identifier":
489 	case tok!"=":
490 	case tok!".":
491 	case tok!",":
492 		if (i == 0)
493 			return ImportKind.neither;
494 		else
495 			i--;
496 		break;
497 	case tok!"import":
498 		return foundColon ? ImportKind.selective : ImportKind.normal;
499 	default:
500 		return ImportKind.neither;
501 	}
502 	return ImportKind.neither;
503 }
504 
505 unittest
506 {
507 	import std.stdio : writeln;
508 
509 	Token[] t = [
510 		Token(tok!"import"), Token(tok!"identifier"), Token(tok!"."),
511 		Token(tok!"identifier"), Token(tok!":"), Token(tok!"identifier"), Token(tok!",")
512 	];
513 	assert(determineImportKind(t) == ImportKind.selective);
514 	Token[] t2;
515 	t2 ~= Token(tok!"else");
516 	t2 ~= Token(tok!":");
517 	assert(determineImportKind(t2) == ImportKind.neither);
518 	writeln("Unittest for determineImportKind() passed");
519 }
520 
521 /**
522  * Provides autocomplete for selective imports, e.g.:
523  * ---
524  * import std.algorithm: balancedParens;
525  * ---
526  */
527 AutocompleteResponse importCompletion(T)(T beforeTokens, ImportKind kind,
528 	ref ModuleCache moduleCache)
529 in
530 {
531 	assert (beforeTokens.length >= 2);
532 }
533 body
534 {
535 	AutocompleteResponse response;
536 	if (beforeTokens.length <= 2)
537 		return response;
538 
539 	size_t i = beforeTokens.length - 1;
540 
541 	if (kind == ImportKind.normal)
542 	{
543 
544 		while (beforeTokens[i].type != tok!"," && beforeTokens[i].type != tok!"import"
545 				&& beforeTokens[i].type != tok!"=" )
546 			i--;
547 		setImportCompletions(beforeTokens[i .. $], response, moduleCache);
548 		return response;
549 	}
550 
551 	loop: while (true) switch (beforeTokens[i].type)
552 	{
553 	case tok!"identifier":
554 	case tok!"=":
555 	case tok!",":
556 	case tok!".":
557 		i--;
558 		break;
559 	case tok!":":
560 		i--;
561 		while (beforeTokens[i].type == tok!"identifier" || beforeTokens[i].type == tok!".")
562 			i--;
563 		break loop;
564 	default:
565 		break loop;
566 	}
567 
568 	size_t j = i;
569 	loop2: while (j <= beforeTokens.length) switch (beforeTokens[j].type)
570 	{
571 	case tok!":": break loop2;
572 	default: j++; break;
573 	}
574 
575 	if (i >= j)
576 	{
577 		warning("Malformed import statement");
578 		return response;
579 	}
580 
581 	immutable string path = beforeTokens[i + 1 .. j]
582 		.filter!(token => token.type == tok!"identifier")
583 		.map!(token => cast() token.text)
584 		.joiner(dirSeparator)
585 		.text();
586 
587 	string resolvedLocation = moduleCache.resolveImportLocation(path);
588 	if (resolvedLocation is null)
589 	{
590 		warning("Could not resolve location of ", path);
591 		return response;
592 	}
593 	auto symbols = moduleCache.getModuleSymbol(internString(resolvedLocation));
594 
595 	import containers.hashset : HashSet;
596 	HashSet!string h;
597 
598 	void addSymbolToResponses(const(DSymbol)* sy)
599 	{
600 		auto a = DSymbol(sy.name);
601 		if (!builtinSymbols.contains(&a) && sy.name !is null && !h.contains(sy.name)
602 				&& sy.name != CONSTRUCTOR_SYMBOL_NAME)
603 		{
604 			response.completionKinds ~= sy.kind;
605 			response.completions ~= sy.name;
606 			h.insert(sy.name);
607 		}
608 	}
609 
610 	foreach (s; symbols.opSlice())
611 	{
612 		if (s.kind == CompletionKind.importSymbol && s.type !is null)
613 			foreach (sy; s.type.opSlice())
614 				addSymbolToResponses(sy);
615 		else
616 			addSymbolToResponses(s);
617 	}
618 	response.completionType = CompletionType.identifiers;
619 	return response;
620 }
621 
622 /**
623  * Populates the response with completion information for an import statement
624  * Params:
625  *     tokens = the tokens after the "import" keyword and before the cursor
626  *     response = the response that should be populated
627  */
628 void setImportCompletions(T)(T tokens, ref AutocompleteResponse response,
629 	ref ModuleCache cache)
630 {
631 	response.completionType = CompletionType.identifiers;
632 	string partial = null;
633 	if (tokens[$ - 1].type == tok!"identifier")
634 	{
635 		partial = tokens[$ - 1].text;
636 		tokens = tokens[0 .. $ - 1];
637 	}
638 	auto moduleParts = tokens.filter!(a => a.type == tok!"identifier").map!("a.text").array();
639 	string path = buildPath(moduleParts);
640 
641 	bool found = false;
642 
643 	foreach (importDirectory; cache.getImportPaths())
644 	{
645 		string p = buildPath(importDirectory, path);
646 		if (!exists(p))
647 			continue;
648 
649 		found = true;
650 
651 		foreach (string name; dirEntries(p, SpanMode.shallow))
652 		{
653 			import std.path: baseName;
654 			if (name.baseName.startsWith(".#"))
655 				continue;
656 
657 			auto n = name.baseName(".d").baseName(".di");
658 			if (isFile(name) && (name.endsWith(".d") || name.endsWith(".di"))
659 				&& (partial is null || n.startsWith(partial)))
660 			{
661 				response.completions ~= n;
662 				response.completionKinds ~= CompletionKind.moduleName;
663 			}
664 			else if (isDir(name))
665 			{
666 				if (n[0] != '.' && (partial is null || n.startsWith(partial)))
667 				{
668 					response.completions ~= n;
669 					response.completionKinds ~=
670 						exists(buildPath(name, "package.d")) || exists(buildPath(name, "package.di"))
671 						? CompletionKind.moduleName : CompletionKind.packageName;
672 				}
673 			}
674 		}
675 	}
676 	if (!found)
677 		warning("Could not find ", moduleParts);
678 }
679 
680 static void skip(alias O, alias C, T)(T t, ref size_t i)
681 {
682 	int depth = 1;
683 	while (i < t.length) switch (t[i].type)
684 	{
685 	case O:
686 		i++;
687 		depth++;
688 		break;
689 	case C:
690 		i++;
691 		depth--;
692 		if (depth <= 0)
693 			return;
694 		break;
695 	default:
696 		i++;
697 		break;
698 	}
699 }
700 
701 bool isSliceExpression(T)(T tokens, size_t index)
702 {
703 	while (index < tokens.length) switch (tokens[index].type)
704 	{
705 	case tok!"[":
706 		skip!(tok!"[", tok!"]")(tokens, index);
707 		break;
708 	case tok!"(":
709 		skip!(tok!"(", tok!")")(tokens, index);
710 		break;
711 	case tok!"]":
712 	case tok!"}":
713 		return false;
714 	case tok!"..":
715 		return true;
716 	default:
717 		index++;
718 		break;
719 	}
720 	return false;
721 }
722 
723 /**
724  *
725  */
726 DSymbol*[] getSymbolsByTokenChain(T)(Scope* completionScope,
727 	T tokens, size_t cursorPosition, CompletionType completionType)
728 {
729 	// Find the symbol corresponding to the beginning of the chain
730 	DSymbol*[] symbols;
731 	if (tokens.length == 0)
732 		return [];
733 	if (tokens[0] == tok!"." && tokens.length > 1)
734 	{
735 		tokens = tokens[1 .. $];
736 		symbols = completionScope.getSymbolsAtGlobalScope(stringToken(tokens[0]));
737 	}
738 	else
739 		symbols = completionScope.getSymbolsByNameAndCursor(stringToken(tokens[0]), cursorPosition);
740 
741 	if (symbols.length == 0)
742 	{
743 		warning("Could not find declaration of ", stringToken(tokens[0]),
744 			" from position ", cursorPosition);
745 		return [];
746 	}
747 
748 	// If the `symbols` array contains functions, and one of them returns
749 	// void and the others do not, this is a property function. For the
750 	// purposes of chaining auto-complete we want to ignore the one that
751 	// returns void.
752 	void filterProperties() @nogc @safe
753 	{
754 		if (symbols.length == 0)
755 			return;
756 		if (symbols[0].kind == CompletionKind.functionName
757 			|| symbols[0].qualifier == SymbolQualifier.func)
758 		{
759 			int voidRets = 0;
760 			int nonVoidRets = 0;
761 			size_t firstNonVoidIndex = size_t.max;
762 			foreach (i, sym; symbols)
763 			{
764 				if (sym.type is null)
765 					return;
766 				if (sym.type.name.ptr == getBuiltinTypeName(tok!"void").ptr)
767 					voidRets++;
768 				else
769 				{
770 					nonVoidRets++;
771 					firstNonVoidIndex = min(firstNonVoidIndex, i);
772 				}
773 			}
774 			if (voidRets > 0 && nonVoidRets > 0)
775 				symbols = symbols[firstNonVoidIndex .. $];
776 		}
777 	}
778 
779 	filterProperties();
780 
781 	if (shouldSwapWithType(completionType, symbols[0].kind, 0, tokens.length - 1))
782 	{
783 		symbols = symbols[0].type is null ? [] : [symbols[0].type];
784 		if (symbols.length == 0)
785 			return [];
786 	}
787 
788 	loop: for (size_t i = 1; i < tokens.length; i++)
789 	{
790 		IdType open;
791 		IdType close;
792 		void skip()
793 		{
794 			i++;
795 			for (int depth = 1; depth > 0 && i < tokens.length; i++)
796 			{
797 				if (tokens[i].type == open)
798 					depth++;
799 				else if (tokens[i].type == close)
800 				{
801 					depth--;
802 					if (depth == 0) break;
803 				}
804 			}
805 		}
806 		switch (tokens[i].type)
807 		{
808 		case tok!"int":
809 		case tok!"uint":
810 		case tok!"long":
811 		case tok!"ulong":
812 		case tok!"char":
813 		case tok!"wchar":
814 		case tok!"dchar":
815 		case tok!"bool":
816 		case tok!"byte":
817 		case tok!"ubyte":
818 		case tok!"short":
819 		case tok!"ushort":
820 		case tok!"cent":
821 		case tok!"ucent":
822 		case tok!"float":
823 		case tok!"ifloat":
824 		case tok!"cfloat":
825 		case tok!"idouble":
826 		case tok!"cdouble":
827 		case tok!"double":
828 		case tok!"real":
829 		case tok!"ireal":
830 		case tok!"creal":
831 		case tok!"this":
832 		case tok!"super":
833 			symbols = symbols[0].getPartsByName(internString(str(tokens[i].type)));
834 			if (symbols.length == 0)
835 				break loop;
836 			break;
837 		case tok!"identifier":
838 			//trace(symbols[0].qualifier, " ", symbols[0].kind);
839 			filterProperties();
840 
841 			// Use type instead of the symbol itself for certain symbol kinds
842 			while (symbols[0].qualifier == SymbolQualifier.func
843 				|| symbols[0].kind == CompletionKind.functionName
844 				|| (symbols[0].kind == CompletionKind.moduleName
845 					&& symbols[0].type !is null && symbols[0].type.kind == CompletionKind.importSymbol)
846 				|| symbols[0].kind == CompletionKind.importSymbol
847 				|| symbols[0].kind == CompletionKind.aliasName)
848 			{
849 				symbols = symbols[0].type is null ? [] : [symbols[0].type];
850 				if (symbols.length == 0)
851 					break loop;
852 			}
853 
854 			//trace("looking for ", tokens[i].text, " in ", symbols[0].name);
855 			symbols = symbols[0].getPartsByName(internString(tokens[i].text));
856 			//trace("symbols: ", symbols.map!(a => a.name));
857 			filterProperties();
858 			if (symbols.length == 0)
859 			{
860 				//trace("Couldn't find it.");
861 				break loop;
862 			}
863 			if (shouldSwapWithType(completionType, symbols[0].kind, i, tokens.length - 1))
864 			{
865 				symbols = symbols[0].type is null ? [] : [symbols[0].type];
866 				if (symbols.length == 0)
867 					break loop;
868 			}
869 			if ((symbols[0].kind == CompletionKind.aliasName
870 				|| symbols[0].kind == CompletionKind.moduleName)
871 				&& (completionType == CompletionType.identifiers
872 				|| i + 1 < tokens.length))
873 			{
874 				symbols = symbols[0].type is null ? [] : [symbols[0].type];
875 			}
876 			if (symbols.length == 0)
877 				break loop;
878 			break;
879 		case tok!"(":
880 			open = tok!"(";
881 			close = tok!")";
882 			skip();
883 			break;
884 		case tok!"[":
885 			open = tok!"[";
886 			close = tok!"]";
887 			if (symbols[0].qualifier == SymbolQualifier.array)
888 			{
889 				skip();
890 				if (!isSliceExpression(tokens, i))
891 				{
892 					symbols = symbols[0].type is null ? [] : [symbols[0].type];
893 					if (symbols.length == 0)
894 						break loop;
895 				}
896 			}
897 			else if (symbols[0].qualifier == SymbolQualifier.assocArray)
898 			{
899 				symbols = symbols[0].type is null ? [] : [symbols[0].type];
900 				skip();
901 			}
902 			else
903 			{
904 				skip();
905 				DSymbol*[] overloads;
906 				if (isSliceExpression(tokens, i))
907 					overloads = symbols[0].getPartsByName(internString("opSlice"));
908 				else
909 					overloads = symbols[0].getPartsByName(internString("opIndex"));
910 				if (overloads.length > 0)
911 				{
912 					symbols = overloads[0].type is null ? [] : [overloads[0].type];
913 				}
914 				else
915 					return [];
916 			}
917 			break;
918 		case tok!".":
919 			break;
920 		default:
921 			break loop;
922 		}
923 	}
924 	return symbols;
925 }
926 
927 /**
928  *
929  */
930 void setCompletions(T)(ref AutocompleteResponse response,
931 	Scope* completionScope, T tokens, size_t cursorPosition,
932 	CompletionType completionType, bool isBracket = false, string partial = null)
933 {
934 	static void addSymToResponse(const(DSymbol)* s, ref AutocompleteResponse r, string p,
935 		size_t[] circularGuard = [])
936 	{
937 		if (circularGuard.canFind(cast(size_t) s))
938 			return;
939 		foreach (sym; s.opSlice())
940 		{
941 			if (sym.name !is null && sym.name.length > 0 && sym.kind != CompletionKind.importSymbol
942 				&& (p is null ? true : sym.name.toUpper().startsWith(p.toUpper()))
943 				&& !r.completions.canFind(sym.name)
944 				&& sym.name[0] != '*')
945 			{
946 				r.completionKinds ~= sym.kind;
947 				r.completions ~= sym.name.dup;
948 			}
949 			if (sym.kind == CompletionKind.importSymbol && !sym.skipOver && sym.type !is null)
950 				addSymToResponse(sym.type, r, p, circularGuard ~ (cast(size_t) s));
951 		}
952 	}
953 
954 	// Handle the simple case where we get all symbols in scope and filter it
955 	// based on the currently entered text.
956 	if (partial !is null && tokens.length == 0)
957 	{
958 		auto currentSymbols = completionScope.getSymbolsInCursorScope(cursorPosition);
959 		foreach (s; currentSymbols.filter!(a => isPublicCompletionKind(a.kind)
960 				&& a.name.toUpper().startsWith(partial.toUpper())))
961 		{
962 			response.completionKinds ~= s.kind;
963 			response.completions ~= s.name.dup;
964 		}
965 		response.completionType = CompletionType.identifiers;
966 		return;
967 	}
968 
969 	if (tokens.length == 0)
970 		return;
971 
972 	DSymbol*[] symbols = getSymbolsByTokenChain(completionScope, tokens,
973 		cursorPosition, completionType);
974 
975 	if (symbols.length == 0)
976 		return;
977 
978 	if (completionType == CompletionType.identifiers)
979 	{
980 		while (symbols[0].qualifier == SymbolQualifier.func
981 			|| symbols[0].kind == CompletionKind.functionName
982 			|| symbols[0].kind == CompletionKind.importSymbol
983 			|| symbols[0].kind == CompletionKind.aliasName)
984 		{
985 			symbols = symbols[0].type is null ? [] : [symbols[0].type];
986 			if (symbols.length == 0)
987 				return;
988 		}
989 		addSymToResponse(symbols[0], response, partial);
990 		response.completionType = CompletionType.identifiers;
991 	}
992 	else if (completionType == CompletionType.calltips)
993 	{
994 		//trace("Showing call tips for ", symbols[0].name, " of kind ", symbols[0].kind);
995 		if (symbols[0].kind != CompletionKind.functionName
996 			&& symbols[0].callTip is null)
997 		{
998 			if (symbols[0].kind == CompletionKind.aliasName)
999 			{
1000 				if (symbols[0].type is null)
1001 					return;
1002 				symbols = [symbols[0].type];
1003 			}
1004 			if (symbols[0].kind == CompletionKind.variableName)
1005 			{
1006 				auto dumb = symbols[0].type;
1007 				if (dumb !is null)
1008 				{
1009 					if (dumb.kind == CompletionKind.functionName)
1010 					{
1011 						symbols = [dumb];
1012 						goto setCallTips;
1013 					}
1014 					if (isBracket)
1015 					{
1016 						auto index = dumb.getPartsByName(internString("opIndex"));
1017 						if (index.length > 0)
1018 						{
1019 							symbols = index;
1020 							goto setCallTips;
1021 						}
1022 					}
1023 					auto call = dumb.getPartsByName(internString("opCall"));
1024 					if (call.length > 0)
1025 					{
1026 						symbols = call;
1027 						goto setCallTips;
1028 					}
1029 				}
1030 			}
1031 			if (symbols[0].kind == CompletionKind.structName
1032 				|| symbols[0].kind == CompletionKind.className)
1033 			{
1034 				auto constructor = symbols[0].getPartsByName(CONSTRUCTOR_SYMBOL_NAME);
1035 				if (constructor.length == 0)
1036 				{
1037 					// Build a call tip out of the struct fields
1038 					if (symbols[0].kind == CompletionKind.structName)
1039 					{
1040 						response.completionType = CompletionType.calltips;
1041 						response.completions = [generateStructConstructorCalltip(symbols[0])];
1042 						return;
1043 					}
1044 				}
1045 				else
1046 				{
1047 					symbols = constructor;
1048 					goto setCallTips;
1049 				}
1050 			}
1051 		}
1052 	setCallTips:
1053 		response.completionType = CompletionType.calltips;
1054 		foreach (symbol; symbols)
1055 		{
1056 			if (symbol.kind != CompletionKind.aliasName && symbol.callTip !is null)
1057 				response.completions ~= symbol.callTip;
1058 		}
1059 	}
1060 }
1061 
1062 string generateStructConstructorCalltip(const DSymbol* symbol)
1063 in
1064 {
1065 	assert(symbol.kind == CompletionKind.structName);
1066 }
1067 body
1068 {
1069 	string generatedStructConstructorCalltip = "this(";
1070 	const(DSymbol)*[] fields = symbol.opSlice().filter!(
1071 		a => a.kind == CompletionKind.variableName).map!(a => cast(const(DSymbol)*) a.ptr).array();
1072 	fields.sort!((a, b) => a.location < b.location);
1073 	foreach (i, field; fields)
1074 	{
1075 		if (field.kind != CompletionKind.variableName)
1076 			continue;
1077 		i++;
1078 		if (field.type !is null)
1079 		{
1080 			generatedStructConstructorCalltip ~= field.type.name;
1081 			generatedStructConstructorCalltip ~= " ";
1082 		}
1083 		generatedStructConstructorCalltip ~= field.name;
1084 		if (i < fields.length)
1085 			generatedStructConstructorCalltip ~= ", ";
1086 	}
1087 	generatedStructConstructorCalltip ~= ")";
1088 	return generatedStructConstructorCalltip;
1089 }
1090 
1091 private enum TYPE_IDENT_AND_LITERAL_CASES = q{
1092 	case tok!"int":
1093 	case tok!"uint":
1094 	case tok!"long":
1095 	case tok!"ulong":
1096 	case tok!"char":
1097 	case tok!"wchar":
1098 	case tok!"dchar":
1099 	case tok!"bool":
1100 	case tok!"byte":
1101 	case tok!"ubyte":
1102 	case tok!"short":
1103 	case tok!"ushort":
1104 	case tok!"cent":
1105 	case tok!"ucent":
1106 	case tok!"float":
1107 	case tok!"ifloat":
1108 	case tok!"cfloat":
1109 	case tok!"idouble":
1110 	case tok!"cdouble":
1111 	case tok!"double":
1112 	case tok!"real":
1113 	case tok!"ireal":
1114 	case tok!"creal":
1115 	case tok!"this":
1116 	case tok!"super":
1117 	case tok!"identifier":
1118 	case tok!"stringLiteral":
1119 	case tok!"wstringLiteral":
1120 	case tok!"dstringLiteral":
1121 };
1122 
1123 
1124 /**
1125  *
1126  */
1127 T getExpression(T)(T beforeTokens)
1128 {
1129 	enum EXPRESSION_LOOP_BREAK = q{
1130 		if (i + 1 < beforeTokens.length) switch (beforeTokens[i + 1].type)
1131 		{
1132 		mixin (TYPE_IDENT_AND_LITERAL_CASES);
1133 			i++;
1134 			break expressionLoop;
1135 		default:
1136 			break;
1137 		}
1138 	};
1139 
1140 	if (beforeTokens.length == 0)
1141 		return beforeTokens[0 .. 0];
1142 	size_t i = beforeTokens.length - 1;
1143 	size_t sliceEnd = beforeTokens.length;
1144 	IdType open;
1145 	IdType close;
1146 	uint skipCount = 0;
1147 
1148 	expressionLoop: while (true)
1149 	{
1150 		switch (beforeTokens[i].type)
1151 		{
1152 		case tok!"import":
1153 			break expressionLoop;
1154 		mixin (TYPE_IDENT_AND_LITERAL_CASES);
1155 			mixin (EXPRESSION_LOOP_BREAK);
1156 			if (i > 1 && beforeTokens[i - 1] == tok!"!"
1157 				&& beforeTokens[i - 2] == tok!"identifier")
1158 			{
1159 				sliceEnd -= 2;
1160 				i--;
1161 			}
1162 			break;
1163 		case tok!".":
1164 			break;
1165 		case tok!")":
1166 			open = tok!")";
1167 			close = tok!"(";
1168 			goto skip;
1169 		case tok!"]":
1170 			open = tok!"]";
1171 			close = tok!"[";
1172 		skip:
1173 			mixin (EXPRESSION_LOOP_BREAK);
1174 			immutable bookmark = i;
1175 			int depth = 1;
1176 			do
1177 			{
1178 				if (depth == 0 || i == 0)
1179 					break;
1180 				else
1181 					i--;
1182 				if (beforeTokens[i].type == open)
1183 					depth++;
1184 				else if (beforeTokens[i].type == close)
1185 					depth--;
1186 			} while (true);
1187 
1188 			skipCount++;
1189 
1190 			// check the current token after skipping parens to the left.
1191 			// if it's a loop keyword, pretend we never skipped the parens.
1192 			if (i > 0) switch (beforeTokens[i - 1].type)
1193 			{
1194 				case tok!"scope":
1195 				case tok!"if":
1196 				case tok!"while":
1197 				case tok!"for":
1198 				case tok!"foreach":
1199 				case tok!"foreach_reverse":
1200 				case tok!"do":
1201 				case tok!"cast":
1202 				case tok!"catch":
1203 					i = bookmark + 1;
1204 					break expressionLoop;
1205 				case tok!"!":
1206 					if (skipCount == 1)
1207 					{
1208 						sliceEnd = i - 1;
1209 						i -= 2;
1210 					}
1211 					break expressionLoop;
1212 				default:
1213 					break;
1214 			}
1215 			break;
1216 		default:
1217 			i++;
1218 			break expressionLoop;
1219 		}
1220 		if (i == 0)
1221 			break;
1222 		else
1223 			i--;
1224 	}
1225 	return beforeTokens[i .. sliceEnd];
1226 }
1227 
1228 size_t goBackToOpenParen(T)(T beforeTokens)
1229 in
1230 {
1231 	assert (beforeTokens.length > 0);
1232 }
1233 body
1234 {
1235 	size_t i = beforeTokens.length - 1;
1236 	IdType open;
1237 	IdType close;
1238 	while (true) switch (beforeTokens[i].type)
1239 	{
1240 	case tok!",":
1241 	case tok!".":
1242 	case tok!"*":
1243 	case tok!"&":
1244 	case tok!"doubleLiteral":
1245 	case tok!"floatLiteral":
1246 	case tok!"idoubleLiteral":
1247     case tok!"ifloatLiteral":
1248 	case tok!"intLiteral":
1249 	case tok!"longLiteral":
1250 	case tok!"realLiteral":
1251     case tok!"irealLiteral":
1252 	case tok!"uintLiteral":
1253 	case tok!"ulongLiteral":
1254 	case tok!"characterLiteral":
1255 	mixin(TYPE_IDENT_AND_LITERAL_CASES);
1256 		if (i == 0)
1257 			return size_t.max;
1258 		else
1259 			i--;
1260 		break;
1261 	case tok!"(":
1262 	case tok!"[":
1263 		return i + 1;
1264 	case tok!")":
1265 		open = tok!")";
1266 		close = tok!"(";
1267 		goto skip;
1268 	case tok!"}":
1269 		open = tok!"}";
1270 		close = tok!"{";
1271 		goto skip;
1272 	case tok!"]":
1273 		open = tok!"]";
1274 		close = tok!"[";
1275 	skip:
1276 		if (i == 0)
1277 			return size_t.max;
1278 		else
1279 			i--;
1280 		int depth = 1;
1281 		do
1282 		{
1283 			if (depth == 0 || i == 0)
1284 				break;
1285 			else
1286 				i--;
1287 			if (beforeTokens[i].type == open)
1288 				depth++;
1289 			else if (beforeTokens[i].type == close)
1290 				depth--;
1291 		} while (true);
1292 		break;
1293 	default:
1294 		return size_t.max;
1295 	}
1296 	return size_t.max;
1297 }
1298 
1299 /**
1300  * Params:
1301  *     completionType = the completion type being requested
1302  *     kind = the kind of the current item in the completion chain
1303  *     current = the index of the current item in the symbol chain
1304  *     max = the number of items in the symbol chain
1305  * Returns:
1306  *     true if the symbol should be swapped with its type field
1307  */
1308 bool shouldSwapWithType(CompletionType completionType, CompletionKind kind,
1309 	size_t current, size_t max) pure nothrow @safe
1310 {
1311 	// packages never have types, so always return false
1312 	if (kind == CompletionKind.packageName
1313 		|| kind == CompletionKind.className
1314 		|| kind == CompletionKind.structName
1315 		|| kind == CompletionKind.interfaceName
1316 		|| kind == CompletionKind.enumName
1317 		|| kind == CompletionKind.unionName)
1318 	{
1319 		return false;
1320 	}
1321 	// Swap out every part of a chain with its type except the last part
1322 	if (current < max)
1323 		return true;
1324 	// Only swap out types for these kinds
1325 	immutable bool isInteresting =
1326 		kind == CompletionKind.variableName
1327 		|| kind == CompletionKind.memberVariableName
1328 		|| kind == CompletionKind.importSymbol
1329 		|| kind == CompletionKind.aliasName
1330 		|| kind == CompletionKind.enumMember
1331 		|| kind == CompletionKind.functionName;
1332 	return isInteresting && (completionType == CompletionType.identifiers
1333 		|| (completionType == completionType.calltips && kind == CompletionKind.variableName)) ;
1334 }
1335 
1336 istring stringToken()(auto ref const Token a)
1337 {
1338 	return internString(a.text is null ? str(a.type) : a.text);
1339 }