1 module workspaced.com.dcdext;
2 
3 import dparse.ast;
4 import dparse.lexer;
5 import dparse.parser;
6 import dparse.rollback_allocator;
7 
8 import core.thread;
9 
10 import std.algorithm;
11 import std.array;
12 import std.ascii;
13 import std.conv;
14 import std.file;
15 import std.functional;
16 import std.json;
17 import std.meta;
18 import std.range;
19 import std.string;
20 
21 import workspaced.api;
22 import workspaced.com.dcd;
23 import workspaced.com.dfmt;
24 import workspaced.dparseext;
25 
26 import workspaced.visitors.classifier;
27 import workspaced.visitors.methodfinder;
28 
29 public import workspaced.visitors.methodfinder : InterfaceDetails, FieldDetails,
30 	MethodDetails, ArgumentInfo;
31 
32 @component("dcdext")
33 class DCDExtComponent : ComponentWrapper
34 {
35 	mixin DefaultComponentWrapper;
36 
37 	static immutable CodeRegionProtection[] mixableProtection = [
38 		CodeRegionProtection.public_ | CodeRegionProtection.default_,
39 		CodeRegionProtection.package_, CodeRegionProtection.packageIdentifier,
40 		CodeRegionProtection.protected_, CodeRegionProtection.private_
41 	];
42 
43 	/// Loads dcd extension methods. Call with `{"cmd": "load", "components": ["dcdext"]}`
44 	void load()
45 	{
46 		if (!refInstance)
47 			return;
48 
49 		config.stringBehavior = StringBehavior.source;
50 	}
51 
52 	/// Extracts calltips help information at a given position.
53 	/// The position must be within the arguments of the function and not
54 	/// outside the parentheses or inside some child call.
55 	///
56 	/// When generating the call parameters for a function definition, the position must be inside the normal parameters,
57 	/// otherwise the template arguments will be put as normal arguments.
58 	///
59 	/// Returns: the position of significant locations for parameter extraction.
60 	/// Params:
61 	///   code = code to analyze
62 	///   position = byte offset where to check for function arguments
63 	///   definition = true if this hints is a function definition (templates don't have an exclamation point '!')
64 	CalltipsSupport extractCallParameters(scope const(char)[] code, int position,
65 			bool definition = false)
66 	{
67 		auto tokens = getTokensForParser(cast(ubyte[]) code, config, &workspaced.stringCache);
68 		if (!tokens.length)
69 			return CalltipsSupport.init;
70 		// TODO: can probably use tokenIndexAtByteIndex here
71 		auto queuedToken = tokens.countUntil!(a => a.index >= position) - 1;
72 		if (queuedToken == -2)
73 			queuedToken = cast(ptrdiff_t) tokens.length - 1;
74 		else if (queuedToken == -1)
75 			return CalltipsSupport.init;
76 
77 		// TODO: refactor code to be more readable
78 		// all this code does is:
79 		// - go back all tokens until a starting ( is found. (with nested {} scope checks for delegates and () for calls)
80 		//   - abort if not found
81 		//   - set "isTemplate" if directly before the ( is a `!` token and an identifier
82 		// - if inTemplate is true:
83 		//   - go forward to starting ( of normal arguments -- this code has checks if startParen is `!`, which currently can't be the case but might be useful
84 		// - else not in template arguments, so
85 		//   - if before ( comes a ) we are definitely in a template, so track back until starting (
86 		//   - otherwise check if it's even a template (single argument: `!`, then a token, then `(`)
87 		// - determine function name & all parents (strips out index operators)
88 		// - split template & function arguments
89 		// - return all information
90 		// it's reasonably readable with the variable names and that pseudo explanation there pretty much directly maps to the code,
91 		// so it shouldn't be too hard of a problem, it's just a lot return values per step and taking in multiple returns from previous steps.
92 
93 		/// describes if the target position is inside template arguments rather than function arguments (only works for calls and not for definition)
94 		bool inTemplate;
95 		int activeParameter; // counted commas
96 		int depth, subDepth;
97 		/// contains opening parentheses location for arguments or exclamation point for templates.
98 		auto startParen = queuedToken;
99 		while (startParen >= 0)
100 		{
101 			const c = tokens[startParen];
102 			const p = startParen > 0 ? tokens[startParen - 1] : Token.init;
103 
104 			if (c.type == tok!"{")
105 			{
106 				if (subDepth == 0)
107 				{
108 					// we went too far, probably not inside a function (or we are in a delegate, where we don't want calltips)
109 					return CalltipsSupport.init;
110 				}
111 				else
112 					subDepth--;
113 			}
114 			else if (c.type == tok!"}")
115 			{
116 				subDepth++;
117 			}
118 			else if (subDepth == 0 && c.type == tok!";")
119 			{
120 				// this doesn't look like function arguments anymore
121 				return CalltipsSupport.init;
122 			}
123 			else if (depth == 0 && !definition && c.type == tok!"!" && p.type == tok!"identifier")
124 			{
125 				inTemplate = true;
126 				break;
127 			}
128 			else if (c.type == tok!")")
129 			{
130 				depth++;
131 			}
132 			else if (c.type == tok!"(")
133 			{
134 				if (depth == 0 && subDepth == 0)
135 				{
136 					if (startParen > 1 && p.type == tok!"!" && tokens[startParen - 2].type
137 							== tok!"identifier")
138 					{
139 						startParen--;
140 						inTemplate = true;
141 					}
142 					break;
143 				}
144 				else
145 					depth--;
146 			}
147 			else if (depth == 0 && subDepth == 0 && c.type == tok!",")
148 			{
149 				activeParameter++;
150 			}
151 			startParen--;
152 		}
153 
154 		if (startParen <= 0)
155 			return CalltipsSupport.init;
156 
157 		/// Token index where the opening template parentheses or exclamation point is. At first this is only set if !definition but later on this is resolved.
158 		auto templateOpen = inTemplate ? startParen : 0;
159 		/// Token index where the normal argument parentheses start or 0 if it doesn't exist for this call/definition
160 		auto functionOpen = inTemplate ? 0 : startParen;
161 
162 		bool hasTemplateParens = false;
163 
164 		if (inTemplate)
165 		{
166 			// go forwards to function arguments
167 			if (templateOpen + 2 < tokens.length)
168 			{
169 				if (tokens[templateOpen + 1].type == tok!"(")
170 				{
171 					hasTemplateParens = true;
172 					templateOpen++;
173 					functionOpen = findClosingParenForward(tokens, templateOpen,
174 							"in template function open finder");
175 					functionOpen++;
176 
177 					if (functionOpen >= tokens.length)
178 						functionOpen = 0;
179 				}
180 				else
181 				{
182 					// single template arg (can only be one token)
183 					// https://dlang.org/spec/grammar.html#TemplateSingleArgument
184 					if (tokens[templateOpen + 2] == tok!"(")
185 						functionOpen = templateOpen + 2;
186 				}
187 			}
188 			else
189 				return CalltipsSupport.init; // syntax error
190 		}
191 		else
192 		{
193 			// go backwards to template arguments
194 			if (functionOpen > 0 && tokens[functionOpen - 1].type == tok!")")
195 			{
196 				// multi template args
197 				depth = 0;
198 				subDepth = 0;
199 				templateOpen = functionOpen - 1;
200 				const minTokenIndex = definition ? 1 : 2;
201 				while (templateOpen >= minTokenIndex)
202 				{
203 					const c = tokens[templateOpen];
204 
205 					if (c == tok!")")
206 						depth++;
207 					else
208 					{
209 						if (depth == 1 && templateOpen > minTokenIndex && c.type == tok!"(")
210 						{
211 							if (definition
212 									? tokens[templateOpen - 1].type == tok!"identifier" : (tokens[templateOpen - 1].type == tok!"!"
213 										&& tokens[templateOpen - 2].type == tok!"identifier"))
214 								break;
215 						}
216 
217 						if (depth == 0)
218 						{
219 							templateOpen = 0;
220 							break;
221 						}
222 
223 						if (c == tok!"(")
224 							depth--;
225 					}
226 
227 					templateOpen--;
228 				}
229 
230 				if (templateOpen < minTokenIndex)
231 					templateOpen = 0;
232 				else
233 					hasTemplateParens = true;
234 			}
235 			else
236 			{
237 				// single template arg (can only be one token) or no template at all here
238 				if (functionOpen >= 3 && tokens[functionOpen - 2] == tok!"!"
239 						&& tokens[functionOpen - 3] == tok!"identifier")
240 				{
241 					templateOpen = functionOpen - 2;
242 				}
243 			}
244 		}
245 
246 		depth = 0;
247 		subDepth = 0;
248 		bool inFuncName = true;
249 		auto callStart = (templateOpen ? templateOpen : functionOpen) - 1;
250 		auto funcNameStart = callStart;
251 		while (callStart >= 0)
252 		{
253 			const c = tokens[callStart];
254 			const p = callStart > 0 ? tokens[callStart - 1] : Token.init;
255 
256 			if (c.type == tok!"]")
257 				depth++;
258 			else if (c.type == tok!"[")
259 			{
260 				if (depth == 0)
261 				{
262 					// this is some sort of `foo[(4` situation
263 					return CalltipsSupport.init;
264 				}
265 				depth--;
266 			}
267 			else if (c.type == tok!")")
268 				subDepth++;
269 			else if (c.type == tok!"(")
270 			{
271 				if (subDepth == 0)
272 				{
273 					// this is some sort of `foo((4` situation
274 					return CalltipsSupport.init;
275 				}
276 				subDepth--;
277 			}
278 			else if (depth == 0)
279 			{
280 
281 				if (c.type.isCalltipable)
282 				{
283 					if (c.type == tok!"identifier" && p.type == tok!"." && (callStart < 2
284 							|| !tokens[callStart - 2].type.among!(tok!";", tok!",",
285 							tok!"{", tok!"}", tok!"(")))
286 					{
287 						// member function, traverse further...
288 						if (inFuncName)
289 						{
290 							funcNameStart = callStart;
291 							inFuncName = false;
292 						}
293 						callStart--;
294 					}
295 					else
296 					{
297 						break;
298 					}
299 				}
300 				else
301 				{
302 					// this is some sort of `4(5` or `if(4` situtation
303 					return CalltipsSupport.init;
304 				}
305 			}
306 			// we ignore stuff inside brackets and parens such as `foo[4](5).bar[6](a`
307 			callStart--;
308 		}
309 
310 		if (inFuncName)
311 			funcNameStart = callStart;
312 
313 		ptrdiff_t templateClose;
314 		if (templateOpen)
315 		{
316 			if (hasTemplateParens)
317 			{
318 				if (functionOpen)
319 					templateClose = functionOpen - 1;
320 				else
321 					templateClose = findClosingParenForward(tokens, templateOpen,
322 							"in template close finder");
323 			}
324 			else
325 				templateClose = templateOpen + 2;
326 		}
327 		//dfmt on
328 		auto functionClose = functionOpen ? findClosingParenForward(tokens,
329 				functionOpen, "in function close finder") : 0;
330 
331 		CalltipsSupport.Argument[] templateArgs;
332 		if (templateOpen)
333 			templateArgs = splitArgs(tokens[templateOpen + 1 .. templateClose]);
334 
335 		CalltipsSupport.Argument[] functionArgs;
336 		if (functionOpen)
337 			functionArgs = splitArgs(tokens[functionOpen + 1 .. functionClose]);
338 
339 		return CalltipsSupport([
340 				tokens.tokenIndex(templateOpen),
341 				templateClose ? tokens.tokenEndIndex(templateClose) : 0
342 				], hasTemplateParens, templateArgs, [
343 				tokens.tokenIndex(functionOpen),
344 				functionClose ? tokens.tokenEndIndex(functionClose) : 0
345 				], functionArgs, funcNameStart != callStart, tokens.tokenIndex(funcNameStart),
346 				tokens.tokenIndex(callStart), inTemplate, activeParameter);
347 	}
348 
349 	/// Finds the immediate surrounding code block at a position or returns CodeBlockInfo.init for none/module block.
350 	/// See_Also: CodeBlockInfo
351 	CodeBlockInfo getCodeBlockRange(scope const(char)[] code, int position)
352 	{
353 		RollbackAllocator rba;
354 		auto tokens = getTokensForParser(cast(ubyte[]) code, config, &workspaced.stringCache);
355 		auto parsed = parseModule(tokens, "getCodeBlockRange_input.d", &rba);
356 		auto reader = new CodeBlockInfoFinder(position);
357 		reader.visit(parsed);
358 		return reader.block;
359 	}
360 
361 	/// Inserts a generic method after the corresponding block inside the scope where position is.
362 	/// If it can't find a good spot it will insert the code properly indented ata fitting location.
363 	// make public once usable
364 	private CodeReplacement[] insertCodeInContainer(string insert, scope const(char)[] code,
365 			int position, bool insertInLastBlock = true, bool insertAtEnd = true)
366 	{
367 		auto container = getCodeBlockRange(code, position);
368 
369 		scope const(char)[] codeBlock = code[container.innerRange[0] .. container.innerRange[1]];
370 
371 		RollbackAllocator rba;
372 		scope tokensInsert = getTokensForParser(cast(ubyte[]) insert, config,
373 				&workspaced.stringCache);
374 		scope parsedInsert = parseModule(tokensInsert, "insertCode_insert.d", &rba);
375 
376 		scope insertReader = new CodeDefinitionClassifier(insert);
377 		insertReader.visit(parsedInsert);
378 		scope insertRegions = insertReader.regions.sort!"a.type < b.type".uniq.array;
379 
380 		scope tokens = getTokensForParser(cast(ubyte[]) codeBlock, config, &workspaced.stringCache);
381 		scope parsed = parseModule(tokens, "insertCode_code.d", &rba);
382 
383 		scope reader = new CodeDefinitionClassifier(codeBlock);
384 		reader.visit(parsed);
385 		scope regions = reader.regions;
386 
387 		CodeReplacement[] ret;
388 
389 		foreach (CodeDefinitionClassifier.Region toInsert; insertRegions)
390 		{
391 			auto insertCode = insert[toInsert.region[0] .. toInsert.region[1]];
392 			scope existing = regions.enumerate.filter!(a => a.value.sameBlockAs(toInsert));
393 			if (existing.empty)
394 			{
395 				const checkProtection = CodeRegionProtection.init.reduce!"a | b"(
396 						mixableProtection.filter!(a => (a & toInsert.protection) != 0));
397 
398 				bool inIncompatible = false;
399 				bool lastFit = false;
400 				int fittingProtection = -1;
401 				int firstStickyProtection = -1;
402 				int regionAfterFitting = -1;
403 				foreach (i, stickyProtection; regions)
404 				{
405 					if (stickyProtection.affectsFollowing
406 							&& stickyProtection.protection != CodeRegionProtection.init)
407 					{
408 						if (firstStickyProtection == -1)
409 							firstStickyProtection = cast(int) i;
410 
411 						if ((stickyProtection.protection & checkProtection) != 0)
412 						{
413 							fittingProtection = cast(int) i;
414 							lastFit = true;
415 							if (!insertInLastBlock)
416 								break;
417 						}
418 						else
419 						{
420 							if (lastFit)
421 							{
422 								regionAfterFitting = cast(int) i;
423 								lastFit = false;
424 							}
425 							inIncompatible = true;
426 						}
427 					}
428 				}
429 				assert(firstStickyProtection != -1 || !inIncompatible);
430 				assert(regionAfterFitting != -1 || fittingProtection == -1 || !inIncompatible);
431 
432 				if (inIncompatible)
433 				{
434 					int insertRegion = fittingProtection == -1 ? firstStickyProtection : regionAfterFitting;
435 					insertCode = text(indent(insertCode, regions[insertRegion].minIndentation), "\n\n");
436 					auto len = cast(uint) insertCode.length;
437 
438 					toInsert.region[0] = regions[insertRegion].region[0];
439 					toInsert.region[1] = regions[insertRegion].region[0] + len;
440 					foreach (ref r; regions[insertRegion .. $])
441 					{
442 						r.region[0] += len;
443 						r.region[1] += len;
444 					}
445 				}
446 				else
447 				{
448 					auto lastRegion = regions.back;
449 					insertCode = indent(insertCode, lastRegion.minIndentation).idup;
450 					auto len = cast(uint) insertCode.length;
451 					toInsert.region[0] = lastRegion.region[1];
452 					toInsert.region[1] = lastRegion.region[1] + len;
453 				}
454 				regions ~= toInsert;
455 				ret ~= CodeReplacement([toInsert.region[0], toInsert.region[0]], insertCode);
456 			}
457 			else
458 			{
459 				auto target = insertInLastBlock ? existing.tail(1).front : existing.front;
460 
461 				insertCode = text("\n\n", indent(insertCode, regions[target.index].minIndentation));
462 				const codeLength = cast(int) insertCode.length;
463 
464 				if (insertAtEnd)
465 				{
466 					ret ~= CodeReplacement([
467 							target.value.region[1], target.value.region[1]
468 							], insertCode);
469 					toInsert.region[0] = target.value.region[1];
470 					toInsert.region[1] = target.value.region[1] + codeLength;
471 					regions[target.index].region[1] = toInsert.region[1];
472 					foreach (ref other; regions[target.index + 1 .. $])
473 					{
474 						other.region[0] += codeLength;
475 						other.region[1] += codeLength;
476 					}
477 				}
478 				else
479 				{
480 					ret ~= CodeReplacement([
481 							target.value.region[0], target.value.region[0]
482 							], insertCode);
483 					regions[target.index].region[1] += codeLength;
484 					foreach (ref other; regions[target.index + 1 .. $])
485 					{
486 						other.region[0] += codeLength;
487 						other.region[1] += codeLength;
488 					}
489 				}
490 			}
491 		}
492 
493 		return ret;
494 	}
495 
496 	/// Implements the interfaces or abstract classes of a specified class/interface.
497 	/// Helper function which returns all functions as one block for most primitive use.
498 	Future!string implement(scope const(char)[] code, int position,
499 			bool formatCode = true, string[] formatArgs = [])
500 	{
501 		auto ret = new typeof(return);
502 		gthreads.create({
503 			mixin(traceTask);
504 			try
505 			{
506 				auto impl = implementAllSync(code, position, formatCode, formatArgs);
507 
508 				auto buf = appender!string;
509 				string lastBaseClass;
510 				foreach (ref func; impl)
511 				{
512 					if (func.baseClass != lastBaseClass)
513 					{
514 						buf.put("// implement " ~ func.baseClass ~ "\n\n");
515 						lastBaseClass = func.baseClass;
516 					}
517 
518 					buf.put(func.code);
519 					buf.put("\n\n");
520 				}
521 				ret.finish(buf.data.length > 2 ? buf.data[0 .. $ - 2] : buf.data);
522 			}
523 			catch (Throwable t)
524 			{
525 				ret.error(t);
526 			}
527 		});
528 		return ret;
529 	}
530 
531 	/// Implements the interfaces or abstract classes of a specified class/interface.
532 	/// The async implementation is preferred when used in background tasks to prevent disruption
533 	/// of other services as a lot of code is parsed and processed multiple times for this function.
534 	/// Params:
535 	/// 	code = input file to parse and edit.
536 	/// 	position = position of the superclass or interface to implement after the colon in a class definition.
537 	/// 	formatCode = automatically calls dfmt on all function bodys when true.
538 	/// 	formatArgs = sets the formatter arguments to pass to dfmt if formatCode is true.
539 	/// 	snippetExtensions = if true, snippets according to the vscode documentation will be inserted in place of method content. See https://code.visualstudio.com/docs/editor/userdefinedsnippets#_creating-your-own-snippets
540 	/// Returns: a list of newly implemented methods
541 	Future!(ImplementedMethod[]) implementAll(scope const(char)[] code, int position,
542 			bool formatCode = true, string[] formatArgs = [], bool snippetExtensions = false)
543 	{
544 		mixin(
545 				gthreadsAsyncProxy!`implementAllSync(code, position, formatCode, formatArgs, snippetExtensions)`);
546 	}
547 
548 	/// ditto
549 	ImplementedMethod[] implementAllSync(scope const(char)[] code, int position,
550 			bool formatCode = true, string[] formatArgs = [], bool snippetExtensions = false)
551 	{
552 		auto tree = describeInterfaceRecursiveSync(code, position);
553 		auto availableVariables = tree.availableVariables;
554 
555 		string[] implementedMethods = tree.details
556 			.methods
557 			.filter!"!a.needsImplementation"
558 			.map!"a.identifier"
559 			.array;
560 
561 		int snippetIndex = 0;
562 		// maintains snippet ids and their value in an AA so they can be replaced after formatting
563 		string[string] snippetReplacements;
564 
565 		auto methods = appender!(ImplementedMethod[]);
566 		void processTree(ref InterfaceTree tree)
567 		{
568 			auto details = tree.details;
569 			if (details.methods.length)
570 			{
571 				foreach (fn; details.methods)
572 				{
573 					if (implementedMethods.canFind(fn.identifier))
574 						continue;
575 					if (!fn.needsImplementation)
576 					{
577 						implementedMethods ~= fn.identifier;
578 						continue;
579 					}
580 
581 					//dfmt off
582 					ImplementedMethod method = {
583 						baseClass: details.name,
584 						name: fn.name
585 					};
586 					//dfmt on
587 					auto buf = appender!string;
588 
589 					snippetIndex++;
590 					bool writtenSnippet;
591 					string snippetId;
592 					auto snippetBuf = appender!string;
593 
594 					void startSnippet(bool withDefault = true)
595 					{
596 						if (writtenSnippet || !snippetExtensions)
597 							return;
598 						snippetId = format!`/+++__WORKSPACED_SNIPPET__%s__+++/`(snippetIndex);
599 						buf.put(snippetId);
600 						swap(buf, snippetBuf);
601 						buf.put("${");
602 						buf.put(snippetIndex.to!string);
603 						if (withDefault)
604 							buf.put(":");
605 						writtenSnippet = true;
606 					}
607 
608 					void endSnippet()
609 					{
610 						if (!writtenSnippet || !snippetExtensions)
611 							return;
612 						buf.put("}");
613 
614 						swap(buf, snippetBuf);
615 						snippetReplacements[snippetId] = snippetBuf.data;
616 					}
617 
618 					if (details.needsOverride)
619 						buf.put("override ");
620 					buf.put(fn.signature[0 .. $ - 1]);
621 					buf.put(" {");
622 					if (fn.optionalImplementation)
623 					{
624 						buf.put("\n\t");
625 						startSnippet();
626 						buf.put("// TODO: optional implementation\n");
627 					}
628 
629 					string propertySearch;
630 					if (fn.signature.canFind("@property") && fn.arguments.length <= 1)
631 						propertySearch = fn.name;
632 					else if ((fn.name.startsWith("get") && fn.arguments.length == 0)
633 							|| (fn.name.startsWith("set") && fn.arguments.length == 1))
634 						propertySearch = fn.name[3 .. $];
635 
636 					string foundProperty;
637 					if (propertySearch)
638 					{
639 						// frontOrDefault
640 						const matching = availableVariables.find!(a => fieldNameMatches(a.name,
641 								propertySearch));
642 						if (!matching.empty)
643 							foundProperty = matching.front.name;
644 					}
645 
646 					if (foundProperty.length)
647 					{
648 						method.autoProperty = true;
649 						buf.put("\n\t");
650 						startSnippet();
651 						if (fn.returnType != "void")
652 						{
653 							method.getter = true;
654 							buf.put("return ");
655 						}
656 
657 						if (fn.name.startsWith("set") || fn.arguments.length == 1)
658 						{
659 							method.setter = true;
660 							buf.put(foundProperty ~ " = " ~ fn.arguments[0].name);
661 						}
662 						else
663 						{
664 							// neither getter nor setter, but we will just put the property here anyway
665 							buf.put(foundProperty);
666 						}
667 						buf.put(";");
668 						endSnippet();
669 						buf.put("\n");
670 					}
671 					else if (fn.hasBody)
672 					{
673 						method.callsSuper = true;
674 						buf.put("\n\t");
675 						startSnippet();
676 						if (fn.returnType != "void")
677 							buf.put("return ");
678 						buf.put("super." ~ fn.name);
679 						if (fn.arguments.length)
680 							buf.put("(" ~ format("%(%s, %)", fn.arguments)
681 									.translate(['\\': `\\`, '{': `\{`, '$': `\$`, '}': `\}`]) ~ ")");
682 						else if (fn.returnType == "void")
683 							buf.put("()"); // make functions that don't return add (), otherwise they might be attributes and don't need that
684 						buf.put(";");
685 						endSnippet();
686 						buf.put("\n");
687 					}
688 					else if (fn.returnType != "void")
689 					{
690 						method.debugImpl = true;
691 						buf.put("\n\t");
692 						if (snippetExtensions)
693 						{
694 							startSnippet(false);
695 							buf.put('|');
696 							// choice snippet
697 
698 							if (fn.returnType.endsWith("[]"))
699 								buf.put("return null; // TODO: implement");
700 							else
701 								buf.put("return " ~ fn.returnType.translate([
702 											'\\': `\\`,
703 											'{': `\{`,
704 											'$': `\$`,
705 											'}': `\}`,
706 											'|': `\|`,
707 											',': `\,`
708 										]) ~ ".init; // TODO: implement");
709 
710 							buf.put(',');
711 
712 							buf.put(`assert(false\, "Method ` ~ fn.name ~ ` not implemented");`);
713 
714 							buf.put('|');
715 							endSnippet();
716 						}
717 						else
718 						{
719 							if (fn.isNothrowOrNogc)
720 							{
721 								if (fn.returnType.endsWith("[]"))
722 									buf.put("return null; // TODO: implement");
723 								else
724 									buf.put("return " ~ fn.returnType.translate([
725 												'\\': `\\`,
726 												'{': `\{`,
727 												'$': `\$`,
728 												'}': `\}`
729 											]) ~ ".init; // TODO: implement");
730 							}
731 							else
732 								buf.put(`assert(false, "Method ` ~ fn.name ~ ` not implemented");`);
733 						}
734 						buf.put("\n");
735 					}
736 					else if (snippetExtensions)
737 					{
738 						buf.put("\n\t");
739 						startSnippet(false);
740 						endSnippet();
741 						buf.put("\n");
742 					}
743 
744 					buf.put("}");
745 
746 					method.code = buf.data;
747 					methods.put(method);
748 				}
749 			}
750 
751 			foreach (parent; tree.inherits)
752 				processTree(parent);
753 		}
754 
755 		processTree(tree);
756 
757 		if (formatCode && instance.has!DfmtComponent)
758 		{
759 			foreach (ref method; methods.data)
760 				method.code = instance.get!DfmtComponent.formatSync(method.code, formatArgs).strip;
761 		}
762 
763 		foreach (ref method; methods.data)
764 		{
765 			// TODO: replacing using aho-corasick would be far more efficient but there is nothing like that in phobos
766 			foreach (key, value; snippetReplacements)
767 			{
768 				method.code = method.code.replace(key, value);
769 			}
770 		}
771 
772 		return methods.data;
773 	}
774 
775 	/// Looks up a declaration of a type and then extracts information about it as class or interface.
776 	InterfaceDetails lookupInterface(scope const(char)[] code, int position)
777 	{
778 		auto data = get!DCDComponent.findDeclaration(code, position).getBlocking;
779 		string file = data.file;
780 		int newPosition = data.position;
781 
782 		if (!file.length || !newPosition)
783 			return InterfaceDetails.init;
784 
785 		auto newCode = code;
786 		if (file != "stdin")
787 			newCode = readText(file);
788 
789 		return getInterfaceDetails(file, newCode, newPosition);
790 	}
791 
792 	/// Extracts information about a given class or interface at the given position.
793 	InterfaceDetails getInterfaceDetails(string file, scope const(char)[] code, int position)
794 	{
795 		RollbackAllocator rba;
796 		auto tokens = getTokensForParser(cast(ubyte[]) code, config, &workspaced.stringCache);
797 		auto parsed = parseModule(tokens, file, &rba);
798 		auto reader = new InterfaceMethodFinder(code, position);
799 		reader.visit(parsed);
800 		return reader.details;
801 	}
802 
803 	Future!InterfaceTree describeInterfaceRecursive(scope const(char)[] code, int position)
804 	{
805 		mixin(gthreadsAsyncProxy!`describeInterfaceRecursiveSync(code, position)`);
806 	}
807 
808 	InterfaceTree describeInterfaceRecursiveSync(scope const(char)[] code, int position)
809 	{
810 		auto baseInterface = getInterfaceDetails("stdin", code, position);
811 
812 		InterfaceTree tree = InterfaceTree(baseInterface);
813 
814 		InterfaceTree* treeByName(InterfaceTree* tree, string name)
815 		{
816 			if (tree.details.name == name)
817 				return tree;
818 			foreach (ref parent; tree.inherits)
819 			{
820 				InterfaceTree* t = treeByName(&parent, name);
821 				if (t !is null)
822 					return t;
823 			}
824 			return null;
825 		}
826 
827 		void traverseTree(ref InterfaceTree sub)
828 		{
829 			foreach (i, parent; sub.details.parentPositions)
830 			{
831 				string parentName = sub.details.normalizedParents[i];
832 				if (treeByName(&tree, parentName) is null)
833 				{
834 					auto details = lookupInterface(sub.details.code, parent);
835 					details.name = parentName;
836 					sub.inherits ~= InterfaceTree(details);
837 				}
838 			}
839 			foreach (ref inherit; sub.inherits)
840 				traverseTree(inherit);
841 		}
842 
843 		traverseTree(tree);
844 
845 		return tree;
846 	}
847 
848 	Related[] highlightRelated(scope const(char)[] code, int position)
849 	{
850 		auto tokens = getTokensForParser(cast(ubyte[]) code, config, &workspaced.stringCache);
851 		if (!tokens.length)
852 			return null;
853 		auto token = tokens.tokenIndexAtByteIndex(position);
854 		if (token >= tokens.length || !tokens[token].isLikeIdentifier)
855 			return null;
856 
857 		Related[] ret;
858 
859 		switch (tokens[token].type)
860 		{
861 		case tok!"static":
862 			if (token + 1 < tokens.length)
863 			{
864 				if (tokens[token + 1].type == tok!"if")
865 				{
866 					token++;
867 					goto case tok!"if";
868 				}
869 				else if (tokens[token + 1].type == tok!"foreach" || tokens[token + 1].type == tok!"foreach_reverse")
870 				{
871 					token++;
872 					goto case tok!"for";
873 				}
874 			}
875 			goto default;
876 		case tok!"if":
877 		case tok!"else":
878 			// if lister
879 			auto finder = new IfFinder();
880 			finder.target = tokens[token].index;
881 			RollbackAllocator rba;
882 			auto parsed = parseModule(tokens, "stdin", &rba);
883 			finder.visit(parsed);
884 			foreach (ifToken; finder.foundIf)
885 				ret ~= Related(Related.Type.controlFlow, [ifToken.index, ifToken.index + ifToken.tokenText.length]);
886 			break;
887 		case tok!"for":
888 		case tok!"foreach":
889 		case tok!"foreach_reverse":
890 		case tok!"while":
891 		case tok!"do":
892 		case tok!"break":
893 		case tok!"continue":
894 			// loop and switch matcher
895 			// special case for switch
896 			auto finder = new BreakFinder();
897 			finder.target = tokens[token].index;
898 			finder.isBreak = tokens[token].type == tok!"break";
899 			finder.isLoop = !(tokens[token].type == tok!"break" || tokens[token].type == tok!"continue");
900 			if (token + 1 < tokens.length && tokens[token + 1].type == tok!"identifier")
901 				finder.label = tokens[token + 1].text;
902 			RollbackAllocator rba;
903 			auto parsed = parseModule(tokens, "stdin", &rba);
904 			finder.visit(parsed);
905 
906 			if (finder.isLoop && finder.foundBlock.length)
907 			{
908 				auto retFinder = new ReverseReturnFinder();
909 				retFinder.target = finder.target;
910 				retFinder.visit(parsed);
911 				finder.foundBlock ~= retFinder.returns;
912 				finder.foundBlock.sort!"a.index < b.index";
913 			}
914 
915 			foreach (blockToken; finder.foundBlock)
916 				ret ~= Related(Related.Type.controlFlow, [blockToken.index, blockToken.index + blockToken.tokenText.length]);
917 			break;
918 		case tok!"switch":
919 		case tok!"case":
920 		case tok!"default":
921 			// switch/case lister
922 			auto finder = new SwitchFinder();
923 			finder.target = tokens[token].index;
924 			RollbackAllocator rba;
925 			auto parsed = parseModule(tokens, "stdin", &rba);
926 			finder.visit(parsed);
927 			foreach (switchToken; finder.foundSwitch)
928 				ret ~= Related(Related.Type.controlFlow, [switchToken.index, switchToken.index + switchToken.tokenText.length]);
929 			break;
930 		case tok!"return":
931 			// return effect lister
932 			auto finder = new ReturnFinder();
933 			finder.target = tokens[token].index;
934 			RollbackAllocator rba;
935 			auto parsed = parseModule(tokens, "stdin", &rba);
936 			finder.visit(parsed);
937 			foreach (switchToken; finder.related)
938 				ret ~= Related(Related.Type.controlFlow, [switchToken.index, switchToken.index + switchToken.tokenText.length]);
939 			break;
940 		default:
941 			// exact token / string matcher
942 			auto currentText = tokens[token].tokenText;
943 			foreach (i, tok; tokens)
944 			{
945 				if (tok.type == tokens[token].type && tok.text == tokens[token].text)
946 					ret ~= Related(Related.Type.exactToken, [tok.index, tok.index + currentText.length]);
947 				else if (tok.type.isSomeString && tok.evaluateExpressionString == currentText)
948 					ret ~= Related(Related.Type.exactString, [tok.index, tok.index + tok.text.length]);
949 			}
950 			break;
951 		}
952 
953 		return ret;
954 	}
955 
956 	/// Formats DCD definitions (symbol declarations) in a readable format.
957 	/// For functions this formats each argument in a separate line.
958 	/// For other symbols the definition is returned as-is.
959 	string formatDefinitionBlock(string definition)
960 	{
961 		// DCD definition help contains calltips for functions, which always end
962 		// with )
963 		if (!definition.endsWith(")"))
964 			return definition;
965 
966 		auto tokens = getTokensForParser(cast(const(ubyte)[]) definition ~ ';',
967 			config, &workspaced.stringCache);
968 		if (!tokens.length)
969 			return definition;
970 
971 		RollbackAllocator rba;
972 		auto parser = new Parser();
973 		parser.fileName = "stdin";
974 		parser.tokens = tokens;
975 		parser.messageFunction = null;
976 		parser.messageDelegate = null;
977 		parser.allocator = &rba;
978 		const Declaration decl = parser.parseDeclaration(
979 			false, // strict
980 			true // must be declaration (for constructor)
981 		);
982 		if (!decl)
983 			return definition;
984 
985 		const FunctionDeclaration funcdecl = decl.functionDeclaration;
986 		const Constructor ctor = decl.constructor;
987 		if (!funcdecl && !ctor)
988 			return definition;
989 
990 		auto ret = appender!string();
991 		ret.reserve(definition.length);
992 
993 		if (funcdecl)
994 			ret.put(definition[0 .. funcdecl.name.index + funcdecl.name.text.length]);
995 		else if (ctor)
996 			ret.put("this");
997 
998 		const templateParameters = funcdecl ? funcdecl.templateParameters : ctor.templateParameters;
999 		if (templateParameters && templateParameters.templateParameterList)
1000 		{
1001 			const params = templateParameters.templateParameterList.items;
1002 			ret.put("(\n");
1003 			foreach (i, param; params)
1004 			{
1005 				assert(param.tokens.length, "no tokens for template parameter?!");
1006 				const start = param.tokens[0].index;
1007 				const end = param.tokens[$ - 1].index + tokenText(param.tokens[$ - 1]).length;
1008 				const hasNext = i + 1 < params.length;
1009 				ret.put("\t");
1010 				ret.put(definition[start .. end]);
1011 				if (hasNext)
1012 					ret.put(",");
1013 				ret.put("\n");
1014 			}
1015 			ret.put(")");
1016 		}
1017 
1018 		const parameters = funcdecl ? funcdecl.parameters : ctor.parameters;
1019 		if (parameters && (parameters.parameters.length || parameters.hasVarargs))
1020 		{
1021 			const params = parameters.parameters;
1022 			ret.put("(\n");
1023 			foreach (i, param; params)
1024 			{
1025 				assert(param.tokens.length, "no tokens for parameter?!");
1026 				const start = param.tokens[0].index;
1027 				const end = param.tokens[$ - 1].index + tokenText(param.tokens[$ - 1]).length;
1028 				const hasNext = parameters.hasVarargs || i + 1 < params.length;
1029 				ret.put("\t");
1030 				ret.put(definition[start .. end]);
1031 				if (hasNext)
1032 					ret.put(",");
1033 				ret.put("\n");
1034 			}
1035 			if (parameters.hasVarargs)
1036 				ret.put("\t...\n");
1037 			ret.put(")");
1038 		}
1039 		else
1040 		{
1041 			ret.put("()");
1042 		}
1043 
1044 		return ret.data;
1045 	}
1046 
1047 private:
1048 	LexerConfig config;
1049 }
1050 
1051 ///
1052 enum CodeRegionType : int
1053 {
1054 	/// null region (unset)
1055 	init,
1056 	/// Imports inside the block
1057 	imports = 1 << 0,
1058 	/// Aliases `alias foo this;`, `alias Type = Other;`
1059 	aliases = 1 << 1,
1060 	/// Nested classes/structs/unions/etc.
1061 	types = 1 << 2,
1062 	/// Raw variables `Type name;`
1063 	fields = 1 << 3,
1064 	/// Normal constructors `this(Args args)`
1065 	ctor = 1 << 4,
1066 	/// Copy constructors `this(this)`
1067 	copyctor = 1 << 5,
1068 	/// Destructors `~this()`
1069 	dtor = 1 << 6,
1070 	/// Properties (functions annotated with `@property`)
1071 	properties = 1 << 7,
1072 	/// Regular functions
1073 	methods = 1 << 8,
1074 }
1075 
1076 ///
1077 enum CodeRegionProtection : int
1078 {
1079 	/// null protection (unset)
1080 	init,
1081 	/// default (unmarked) protection
1082 	default_ = 1 << 0,
1083 	/// public protection
1084 	public_ = 1 << 1,
1085 	/// package (automatic) protection
1086 	package_ = 1 << 2,
1087 	/// package (manual package name) protection
1088 	packageIdentifier = 1 << 3,
1089 	/// protected protection
1090 	protected_ = 1 << 4,
1091 	/// private protection
1092 	private_ = 1 << 5,
1093 }
1094 
1095 ///
1096 enum CodeRegionStatic : int
1097 {
1098 	/// null static (unset)
1099 	init,
1100 	/// non-static code
1101 	instanced = 1 << 0,
1102 	/// static code
1103 	static_ = 1 << 1,
1104 }
1105 
1106 /// Represents a class/interface/struct/union/template with body.
1107 struct CodeBlockInfo
1108 {
1109 	///
1110 	enum Type : int
1111 	{
1112 		// keep the underlines in these values for range checking properly
1113 
1114 		///
1115 		class_,
1116 		///
1117 		interface_,
1118 		///
1119 		struct_,
1120 		///
1121 		union_,
1122 		///
1123 		template_,
1124 	}
1125 
1126 	static immutable string[] typePrefixes = [
1127 		"class ", "interface ", "struct ", "union ", "template "
1128 	];
1129 
1130 	///
1131 	Type type;
1132 	///
1133 	string name;
1134 	/// Outer range inside the code spanning curly braces and name but not type keyword.
1135 	uint[2] outerRange;
1136 	/// Inner range of body of the block touching, but not spanning curly braces.
1137 	uint[2] innerRange;
1138 
1139 	string prefix() @property
1140 	{
1141 		return typePrefixes[cast(int) type];
1142 	}
1143 }
1144 
1145 ///
1146 struct CalltipsSupport
1147 {
1148 	///
1149 	struct Argument
1150 	{
1151 		/// Ranges of type, name and value not including commas or parentheses, but being right next to them. For calls this is the only important and accurate value.
1152 		int[2] contentRange;
1153 		/// Range of just the type, or for templates also `alias`
1154 		int[2] typeRange;
1155 		/// Range of just the name
1156 		int[2] nameRange;
1157 		/// Range of just the default value
1158 		int[2] valueRange;
1159 		/// True if the type declaration is variadic (using ...), or without typeRange a completely variadic argument
1160 		bool variadic;
1161 
1162 		/// Creates Argument(range, range, range, 0)
1163 		static Argument templateType(int[2] range)
1164 		{
1165 			return Argument(range, range, range);
1166 		}
1167 
1168 		/// Creates Argument(range, 0, range, range)
1169 		static Argument templateValue(int[2] range)
1170 		{
1171 			return Argument(range, typeof(range).init, range, range);
1172 		}
1173 
1174 		/// Creates Argument(range, 0, 0, 0, true)
1175 		static Argument anyVariadic(int[2] range)
1176 		{
1177 			return Argument(range, typeof(range).init, typeof(range).init, typeof(range).init, true);
1178 		}
1179 	}
1180 
1181 	bool hasTemplate() @property
1182 	{
1183 		return hasTemplateParens || templateArgumentsRange != typeof(templateArgumentsRange).init;
1184 	}
1185 
1186 	/// Range starting before exclamation point until after closing bracket or before function opening bracket.
1187 	int[2] templateArgumentsRange;
1188 	///
1189 	bool hasTemplateParens;
1190 	///
1191 	Argument[] templateArgs;
1192 	/// Range starting before opening parentheses until after closing parentheses.
1193 	int[2] functionParensRange;
1194 	///
1195 	Argument[] functionArgs;
1196 	/// True if the function is UFCS or a member function of some object or namespace.
1197 	/// False if this is a global function call.
1198 	bool hasParent;
1199 	/// Start of the function itself.
1200 	int functionStart;
1201 	/// Start of the whole call going up all call parents. (`foo.bar.function` having `foo.bar` as parents)
1202 	int parentStart;
1203 	/// True if cursor is in template parameters
1204 	bool inTemplateParameters;
1205 	/// Number of the active parameter (where the cursor is) or -1 if in none
1206 	int activeParameter = -1;
1207 }
1208 
1209 /// Represents one method automatically implemented off a base interface.
1210 struct ImplementedMethod
1211 {
1212 	/// Contains the interface or class name from where this method is implemented.
1213 	string baseClass;
1214 	/// The name of the function being implemented.
1215 	string name;
1216 	/// True if an automatic implementation calling the base class has been made.
1217 	bool callsSuper;
1218 	/// True if a default implementation that should definitely be changed (assert or for nogc/nothrow simple init return) has been implemented.
1219 	bool debugImpl;
1220 	/// True if the method has been detected as property and implemented as such.
1221 	bool autoProperty;
1222 	/// True if the method is either a getter or a setter but not both. Is none for non-autoProperty methods but also when a getter has been detected but the method returns void.
1223 	bool getter, setter;
1224 	/// Actual code to insert for this class without class indentation but optionally already formatted.
1225 	string code;
1226 }
1227 
1228 /// Contains details about an interface or class and all extended or implemented interfaces/classes recursively.
1229 struct InterfaceTree
1230 {
1231 	/// Details of the template in question.
1232 	InterfaceDetails details;
1233 	/// All inherited classes in lexical order.
1234 	InterfaceTree[] inherits;
1235 
1236 	const(FieldDetails)[] availableVariables(bool onlyPublic = false) const
1237 	{
1238 		if (!inherits.length && !onlyPublic)
1239 			return details.fields;
1240 
1241 		// start with private, add all the public ones later in traverseTree
1242 		auto ret = appender!(typeof(return));
1243 		if (onlyPublic)
1244 			ret.put(details.fields.filter!(a => !a.isPrivate));
1245 		else
1246 			ret.put(details.fields);
1247 
1248 		foreach (sub; inherits)
1249 			ret.put(sub.availableVariables(true));
1250 
1251 		return ret.data;
1252 	}
1253 }
1254 
1255 /// Represents one selection for things related to the queried cursor position.
1256 struct Related
1257 {
1258 	///
1259 	enum Type
1260 	{
1261 		/// token is the same as the selected token (except for non-text tokens)
1262 		exactToken,
1263 		/// string content is exactly equal to identifier text
1264 		exactString,
1265 		/// token is related to control flow:
1266 		/// - all if/else keywords when checking any of them
1267 		/// - loop/switch keyword when checking a break/continue
1268 		controlFlow
1269 	}
1270 
1271 	/// The type of the related selection.
1272 	Type type;
1273 	/// Byte range [from-inclusive, to-exclusive] of the related selection.
1274 	size_t[2] range;
1275 }
1276 
1277 private:
1278 
1279 bool isCalltipable(IdType type)
1280 {
1281 	return type == tok!"identifier" || type == tok!"assert" || type == tok!"import"
1282 		|| type == tok!"mixin" || type == tok!"super" || type == tok!"this" || type == tok!"__traits";
1283 }
1284 
1285 int[2] tokenRange(const Token token)
1286 {
1287 	return [cast(int) token.index, cast(int)(token.index + token.tokenText.length)];
1288 }
1289 
1290 int tokenEnd(const Token token)
1291 {
1292 	return cast(int)(token.index + token.tokenText.length);
1293 }
1294 
1295 int tokenIndex(const(Token)[] tokens, ptrdiff_t i)
1296 {
1297 	if (i > 0 && i == tokens.length)
1298 		return cast(int)(tokens[$ - 1].index + tokens[$ - 1].tokenText.length);
1299 	return i >= 0 ? cast(int) tokens[i].index : 0;
1300 }
1301 
1302 int tokenEndIndex(const(Token)[] tokens, ptrdiff_t i)
1303 {
1304 	if (i > 0 && i == tokens.length)
1305 		return cast(int)(tokens[$ - 1].index + tokens[$ - 1].text.length);
1306 	return i >= 0 ? cast(int)(tokens[i].index + tokens[i].tokenText.length) : 0;
1307 }
1308 
1309 /// Returns the index of the closing parentheses in tokens starting at the opening parentheses which is must be at tokens[open].
1310 ptrdiff_t findClosingParenForward(const(Token)[] tokens, ptrdiff_t open, string what = null)
1311 in(tokens[open].type == tok!"(",
1312 		"Calling findClosingParenForward must be done on a ( token and not on a " ~ str(
1313 			tokens[open].type) ~ " token! " ~ what)
1314 {
1315 	if (open >= tokens.length || open < 0)
1316 		return open;
1317 
1318 	open++;
1319 
1320 	int depth = 1;
1321 	int subDepth = 0;
1322 	while (open < tokens.length)
1323 	{
1324 		const c = tokens[open];
1325 
1326 		if (c == tok!"(")
1327 			depth++;
1328 		else if (c == tok!"{")
1329 			subDepth++;
1330 		else if (c == tok!"}")
1331 		{
1332 			if (subDepth == 0)
1333 				break;
1334 			subDepth--;
1335 		}
1336 		else
1337 		{
1338 			if (c == tok!";" && subDepth == 0)
1339 				break;
1340 
1341 			if (c == tok!")")
1342 				depth--;
1343 
1344 			if (depth == 0)
1345 				break;
1346 		}
1347 
1348 		open++;
1349 	}
1350 	return open;
1351 }
1352 
1353 CalltipsSupport.Argument[] splitArgs(const(Token)[] tokens)
1354 {
1355 	auto ret = appender!(CalltipsSupport.Argument[]);
1356 	size_t start = 0;
1357 	size_t valueStart = 0;
1358 
1359 	int depth, subDepth;
1360 	const targetDepth = tokens.length > 0 && tokens[0].type == tok!"(" ? 1 : 0;
1361 	bool gotValue;
1362 
1363 	void putArg(size_t end)
1364 	{
1365 		if (start >= end || start >= tokens.length)
1366 			return;
1367 
1368 		CalltipsSupport.Argument arg;
1369 
1370 		auto typename = tokens[start .. end];
1371 		arg.contentRange = [cast(int) typename[0].index, typename[$ - 1].tokenEnd];
1372 		if (typename.length == 1)
1373 		{
1374 			auto t = typename[0];
1375 			if (t.type == tok!"identifier" || t.type.isBasicType)
1376 				arg = CalltipsSupport.Argument.templateType(t.tokenRange);
1377 			else if (t.type == tok!"...")
1378 				arg = CalltipsSupport.Argument.anyVariadic(t.tokenRange);
1379 			else
1380 				arg = CalltipsSupport.Argument.templateValue(t.tokenRange);
1381 		}
1382 		else
1383 		{
1384 			if (gotValue && valueStart > start && valueStart <= end)
1385 			{
1386 				typename = tokens[start .. valueStart];
1387 				auto val = tokens[valueStart .. end];
1388 				if (val.length)
1389 					arg.valueRange = [cast(int) val[0].index, val[$ - 1].tokenEnd];
1390 			}
1391 
1392 			else if (typename.length == 1)
1393 			{
1394 				auto t = typename[0];
1395 				if (t.type == tok!"identifier" || t.type.isBasicType)
1396 					arg.typeRange = arg.nameRange = t.tokenRange;
1397 				else
1398 					arg.typeRange = t.tokenRange;
1399 			}
1400 			else if (typename.length)
1401 			{
1402 				if (typename[$ - 1].type == tok!"identifier")
1403 				{
1404 					arg.nameRange = typename[$ - 1].tokenRange;
1405 					typename = typename[0 .. $ - 1];
1406 				}
1407 				else if (typename[$ - 1].type == tok!"...")
1408 				{
1409 					arg.variadic = true;
1410 					if (typename.length > 1 && typename[$ - 2].type == tok!"identifier")
1411 					{
1412 						arg.nameRange = typename[$ - 2].tokenRange;
1413 						typename = typename[0 .. $ - 2];
1414 					}
1415 					else
1416 						typename = typename[0 .. 0];
1417 				}
1418 
1419 				if (typename.length)
1420 					arg.typeRange = [cast(int) typename[0].index, typename[$ - 1].tokenEnd];
1421 			}
1422 		}
1423 
1424 		ret.put(arg);
1425 
1426 		gotValue = false;
1427 		start = end + 1;
1428 	}
1429 
1430 	foreach (i, token; tokens)
1431 	{
1432 		if (token.type == tok!"{")
1433 			subDepth++;
1434 		else if (token.type == tok!"}")
1435 		{
1436 			if (subDepth == 0)
1437 				break;
1438 			subDepth--;
1439 		}
1440 		else if (token.type == tok!"(" || token.type == tok!"[")
1441 			depth++;
1442 		else if (token.type == tok!")" || token.type == tok!"]")
1443 		{
1444 			if (depth <= targetDepth)
1445 				break;
1446 			depth--;
1447 		}
1448 
1449 		if (depth == targetDepth)
1450 		{
1451 			if (token.type == tok!",")
1452 				putArg(i);
1453 			else if (token.type == tok!":" || token.type == tok!"=")
1454 			{
1455 				if (!gotValue)
1456 				{
1457 					valueStart = i + 1;
1458 					gotValue = true;
1459 				}
1460 			}
1461 		}
1462 	}
1463 	putArg(tokens.length);
1464 
1465 	return ret.data;
1466 }
1467 
1468 auto indent(scope const(char)[] code, string indentation)
1469 {
1470 	return code.lineSplitter!(KeepTerminator.yes)
1471 		.map!(a => a.length ? indentation ~ a : a)
1472 		.join;
1473 }
1474 
1475 bool fieldNameMatches(string field, in char[] expected)
1476 {
1477 	import std.uni : sicmp;
1478 
1479 	if (field.startsWith("_"))
1480 		field = field[1 .. $];
1481 	else if (field.startsWith("m_"))
1482 		field = field[2 .. $];
1483 	else if (field.length >= 2 && field[0] == 'm' && field[1].isUpper)
1484 		field = field[1 .. $];
1485 
1486 	return field.sicmp(expected) == 0;
1487 }
1488 
1489 final class CodeBlockInfoFinder : ASTVisitor
1490 {
1491 	this(int targetPosition)
1492 	{
1493 		this.targetPosition = targetPosition;
1494 	}
1495 
1496 	override void visit(const ClassDeclaration dec)
1497 	{
1498 		visitContainer(dec.name, CodeBlockInfo.Type.class_, dec.structBody);
1499 	}
1500 
1501 	override void visit(const InterfaceDeclaration dec)
1502 	{
1503 		visitContainer(dec.name, CodeBlockInfo.Type.interface_, dec.structBody);
1504 	}
1505 
1506 	override void visit(const StructDeclaration dec)
1507 	{
1508 		visitContainer(dec.name, CodeBlockInfo.Type.struct_, dec.structBody);
1509 	}
1510 
1511 	override void visit(const UnionDeclaration dec)
1512 	{
1513 		visitContainer(dec.name, CodeBlockInfo.Type.union_, dec.structBody);
1514 	}
1515 
1516 	override void visit(const TemplateDeclaration dec)
1517 	{
1518 		if (cast(int) targetPosition >= cast(int) dec.name.index && targetPosition < dec.endLocation)
1519 		{
1520 			block = CodeBlockInfo.init;
1521 			block.type = CodeBlockInfo.Type.template_;
1522 			block.name = dec.name.text;
1523 			block.outerRange = [
1524 				cast(uint) dec.name.index, cast(uint) dec.endLocation + 1
1525 			];
1526 			block.innerRange = [
1527 				cast(uint) dec.startLocation + 1, cast(uint) dec.endLocation
1528 			];
1529 			dec.accept(this);
1530 		}
1531 	}
1532 
1533 	private void visitContainer(const Token name, CodeBlockInfo.Type type, const StructBody structBody)
1534 	{
1535 		if (!structBody)
1536 			return;
1537 		if (cast(int) targetPosition >= cast(int) name.index && targetPosition < structBody.endLocation)
1538 		{
1539 			block = CodeBlockInfo.init;
1540 			block.type = type;
1541 			block.name = name.text;
1542 			block.outerRange = [
1543 				cast(uint) name.index, cast(uint) structBody.endLocation + 1
1544 			];
1545 			block.innerRange = [
1546 				cast(uint) structBody.startLocation + 1, cast(uint) structBody.endLocation
1547 			];
1548 			structBody.accept(this);
1549 		}
1550 	}
1551 
1552 	alias visit = ASTVisitor.visit;
1553 
1554 	CodeBlockInfo block;
1555 	int targetPosition;
1556 }
1557 
1558 version (unittest) static immutable string SimpleClassTestCode = q{
1559 module foo;
1560 
1561 class FooBar
1562 {
1563 public:
1564 	int i; // default instanced fields
1565 	string s;
1566 	long l;
1567 
1568 	public this() // public instanced ctor
1569 	{
1570 		i = 4;
1571 	}
1572 
1573 protected:
1574 	int x; // protected instanced field
1575 
1576 private:
1577 	static const int foo() @nogc nothrow pure @system // private static methods
1578 	{
1579 		if (s == "a")
1580 		{
1581 			i = 5;
1582 		}
1583 	}
1584 
1585 	static void bar1() {}
1586 
1587 	void bar2() {} // private instanced methods
1588 	void bar3() {}
1589 
1590 	struct Something { string bar; }
1591 
1592 	FooBar.Something somefunc() { return Something.init; }
1593 	Something somefunc2() { return Something.init; }
1594 }}.replace("\r\n", "\n");
1595 
1596 unittest
1597 {
1598 	scope backend = new WorkspaceD();
1599 	auto workspace = makeTemporaryTestingWorkspace;
1600 	auto instance = backend.addInstance(workspace.directory);
1601 	backend.register!DCDExtComponent;
1602 	DCDExtComponent dcdext = instance.get!DCDExtComponent;
1603 
1604 	assert(dcdext.getCodeBlockRange(SimpleClassTestCode, 123) == CodeBlockInfo(CodeBlockInfo.Type.class_,
1605 			"FooBar", [20, SimpleClassTestCode.length], [
1606 				28, SimpleClassTestCode.length - 1
1607 			]));
1608 	assert(dcdext.getCodeBlockRange(SimpleClassTestCode, 19) == CodeBlockInfo.init);
1609 	assert(dcdext.getCodeBlockRange(SimpleClassTestCode, 20) != CodeBlockInfo.init);
1610 
1611 	auto replacements = dcdext.insertCodeInContainer("void foo()\n{\n\twriteln();\n}",
1612 			SimpleClassTestCode, 123);
1613 
1614 	// TODO: make insertCodeInContainer work properly?
1615 }
1616 
1617 unittest
1618 {
1619 	import std.conv;
1620 
1621 	scope backend = new WorkspaceD();
1622 	auto workspace = makeTemporaryTestingWorkspace;
1623 	auto instance = backend.addInstance(workspace.directory);
1624 	backend.register!DCDExtComponent;
1625 	DCDExtComponent dcdext = instance.get!DCDExtComponent;
1626 
1627 	auto extract = dcdext.extractCallParameters("int x; foo.bar(4, fgerg\n\nfoo(); int y;", 23);
1628 	assert(!extract.hasTemplate);
1629 	assert(extract.parentStart == 7);
1630 	assert(extract.functionStart == 11);
1631 	assert(extract.functionParensRange[0] == 14);
1632 	assert(extract.functionParensRange[1] <= 31);
1633 	assert(extract.functionArgs.length == 2);
1634 	assert(extract.functionArgs[0].contentRange == [15, 16]);
1635 	assert(extract.functionArgs[1].contentRange[0] == 18);
1636 	assert(extract.functionArgs[1].contentRange[1] <= 31);
1637 
1638 	extract = dcdext.extractCallParameters("int x; foo.bar(4, fgerg)\n\nfoo(); int y;", 23);
1639 	assert(!extract.hasTemplate);
1640 	assert(extract.parentStart == 7);
1641 	assert(extract.functionStart == 11);
1642 	assert(extract.functionParensRange == [14, 24]);
1643 	assert(extract.functionArgs.length == 2);
1644 	assert(extract.functionArgs[0].contentRange == [15, 16]);
1645 	assert(extract.functionArgs[1].contentRange == [18, 23]);
1646 
1647 	extract = dcdext.extractCallParameters("void foo()", 9, true);
1648 	assert(extract != CalltipsSupport.init);
1649 	extract = dcdext.extractCallParameters("void foo()", 10, true);
1650 	assert(extract == CalltipsSupport.init);
1651 
1652 	// caused segfault once, doesn't return anything important
1653 	extract = dcdext.extractCallParameters(`SomeType!(int,"int_")foo(T,Args...)(T a,T b,string[string] map,Other!"(" stuff1,SomeType!(double,")double")myType,Other!"(" stuff,Other!")")`,
1654 			140, true);
1655 	assert(extract == CalltipsSupport.init);
1656 
1657 	extract = dcdext.extractCallParameters(
1658 			`auto bar(int foo, Button, my.Callback cb, ..., int[] arr ...)`, 60, true);
1659 	assert(extract != CalltipsSupport.init);
1660 	assert(!extract.hasTemplate);
1661 	assert(!extract.inTemplateParameters);
1662 	assert(extract.activeParameter == 4);
1663 	assert(extract.functionStart == 5);
1664 	assert(extract.parentStart == 5);
1665 	assert(extract.functionParensRange == [8, 61]);
1666 	assert(extract.functionArgs.length == 5);
1667 	assert(extract.functionArgs[0].contentRange == [9, 16]);
1668 	assert(extract.functionArgs[0].typeRange == [9, 12]);
1669 	assert(extract.functionArgs[0].nameRange == [13, 16]);
1670 	assert(extract.functionArgs[1].contentRange == [18, 24]);
1671 	assert(extract.functionArgs[1].typeRange == [18, 24]);
1672 	assert(extract.functionArgs[1].nameRange == [18, 24]);
1673 	assert(extract.functionArgs[2].contentRange == [26, 40]);
1674 	assert(extract.functionArgs[2].typeRange == [26, 37]);
1675 	assert(extract.functionArgs[2].nameRange == [38, 40]);
1676 	assert(extract.functionArgs[3].contentRange == [42, 45]);
1677 	assert(extract.functionArgs[3].variadic);
1678 	assert(extract.functionArgs[4].contentRange == [47, 60]);
1679 	assert(extract.functionArgs[4].typeRange == [47, 52]);
1680 	assert(extract.functionArgs[4].nameRange == [53, 56]);
1681 	assert(extract.functionArgs[4].variadic);
1682 
1683 	extract = dcdext.extractCallParameters(q{SomeType!(int, "int_") foo(T, Args...)(T a, T b, string[string] map, Other!"(" stuff1, SomeType!(double, ")double") myType, Other!"(" stuff, Other!")")},
1684 			150, true);
1685 	assert(extract != CalltipsSupport.init);
1686 	assert(extract.hasTemplate);
1687 	assert(extract.templateArgumentsRange == [26, 38]);
1688 	assert(extract.templateArgs.length == 2);
1689 	assert(extract.templateArgs[0].contentRange == [27, 28]);
1690 	assert(extract.templateArgs[0].nameRange == [27, 28]);
1691 	assert(extract.templateArgs[1].contentRange == [30, 37]);
1692 	assert(extract.templateArgs[1].nameRange == [30, 34]);
1693 	assert(extract.functionStart == 23);
1694 	assert(extract.parentStart == 23);
1695 	assert(extract.functionParensRange == [38, 151]);
1696 	assert(extract.functionArgs.length == 7);
1697 	assert(extract.functionArgs[0].contentRange == [39, 42]);
1698 	assert(extract.functionArgs[0].typeRange == [39, 40]);
1699 	assert(extract.functionArgs[0].nameRange == [41, 42]);
1700 	assert(extract.functionArgs[1].contentRange == [44, 47]);
1701 	assert(extract.functionArgs[1].typeRange == [44, 45]);
1702 	assert(extract.functionArgs[1].nameRange == [46, 47]);
1703 	assert(extract.functionArgs[2].contentRange == [49, 67]);
1704 	assert(extract.functionArgs[2].typeRange == [49, 63]);
1705 	assert(extract.functionArgs[2].nameRange == [64, 67]);
1706 	assert(extract.functionArgs[3].contentRange == [69, 85]);
1707 	assert(extract.functionArgs[3].typeRange == [69, 78]);
1708 	assert(extract.functionArgs[3].nameRange == [79, 85]);
1709 	assert(extract.functionArgs[4].contentRange == [87, 122]);
1710 	assert(extract.functionArgs[4].typeRange == [87, 115]);
1711 	assert(extract.functionArgs[4].nameRange == [116, 122]);
1712 	assert(extract.functionArgs[5].contentRange == [124, 139]);
1713 	assert(extract.functionArgs[5].typeRange == [124, 133]);
1714 	assert(extract.functionArgs[5].nameRange == [134, 139]);
1715 	assert(extract.functionArgs[6].contentRange == [141, 150]);
1716 	assert(extract.functionArgs[6].typeRange == [141, 150]);
1717 
1718 	extract = dcdext.extractCallParameters(`some_garbage(code); before(this); funcCall(4`, 44);
1719 	assert(extract != CalltipsSupport.init);
1720 	assert(!extract.hasTemplate);
1721 	assert(extract.activeParameter == 0);
1722 	assert(extract.functionStart == 34);
1723 	assert(extract.parentStart == 34);
1724 	assert(extract.functionArgs.length == 1);
1725 	assert(extract.functionArgs[0].contentRange == [43, 44]);
1726 
1727 	extract = dcdext.extractCallParameters(`some_garbage(code); before(this); funcCall(4, f(4)`, 50);
1728 	assert(extract != CalltipsSupport.init);
1729 	assert(!extract.hasTemplate);
1730 	assert(extract.activeParameter == 1);
1731 	assert(extract.functionStart == 34);
1732 	assert(extract.parentStart == 34);
1733 	assert(extract.functionArgs.length == 2);
1734 	assert(extract.functionArgs[0].contentRange == [43, 44]);
1735 	assert(extract.functionArgs[1].contentRange == [46, 50]);
1736 
1737 	extract = dcdext.extractCallParameters(q{some_garbage(code); before(this); funcCall(4, ["a"], JSONValue(["b": JSONValue("c")]), recursive(func, call!s()), "texts )\"(too"},
1738 			129);
1739 	assert(extract != CalltipsSupport.init);
1740 	assert(!extract.hasTemplate);
1741 	assert(extract.functionStart == 34);
1742 	assert(extract.parentStart == 34);
1743 	assert(extract.functionArgs.length == 5);
1744 	assert(extract.functionArgs[0].contentRange == [43, 44]);
1745 	assert(extract.functionArgs[1].contentRange == [46, 51]);
1746 	assert(extract.functionArgs[2].contentRange == [53, 85]);
1747 	assert(extract.functionArgs[3].contentRange == [87, 112]);
1748 	assert(extract.functionArgs[4].contentRange == [114, 129]);
1749 
1750 	extract = dcdext.extractCallParameters(`void log(T t = T.x, A...)(A a) { call(Foo(["bar":"hello"])); } bool x() const @property { return false; } /// This is not code, but rather documentation`,
1751 			127);
1752 	assert(extract == CalltipsSupport.init);
1753 }
1754 
1755 unittest
1756 {
1757 	scope backend = new WorkspaceD();
1758 	auto workspace = makeTemporaryTestingWorkspace;
1759 	auto instance = backend.addInstance(workspace.directory);
1760 	backend.register!DCDExtComponent;
1761 	DCDExtComponent dcdext = instance.get!DCDExtComponent;
1762 
1763 	auto info = dcdext.describeInterfaceRecursiveSync(SimpleClassTestCode, 23);
1764 	assert(info.details.name == "FooBar");
1765 	assert(info.details.blockRange == [27, 554]);
1766 	assert(info.details.referencedTypes.length == 2);
1767 	assert(info.details.referencedTypes[0].name == "Something");
1768 	assert(info.details.referencedTypes[0].location == 455);
1769 	assert(info.details.referencedTypes[1].name == "string");
1770 	assert(info.details.referencedTypes[1].location == 74);
1771 
1772 	assert(info.details.fields.length == 4);
1773 	assert(info.details.fields[0].name == "i");
1774 	assert(info.details.fields[1].name == "s");
1775 	assert(info.details.fields[2].name == "l");
1776 	assert(info.details.fields[3].name == "x");
1777 
1778 	assert(info.details.types.length == 1);
1779 	assert(info.details.types[0].type == TypeDetails.Type.struct_);
1780 	assert(info.details.types[0].name == ["FooBar", "Something"]);
1781 	assert(info.details.types[0].nameLocation == 420);
1782 
1783 	assert(info.details.methods.length == 6);
1784 	assert(info.details.methods[0].name == "foo");
1785 	assert(
1786 			info.details.methods[0].signature
1787 			== "private static const int foo() @nogc nothrow pure @system;");
1788 	assert(info.details.methods[0].returnType == "int");
1789 	assert(info.details.methods[0].isNothrowOrNogc);
1790 	assert(info.details.methods[0].hasBody);
1791 	assert(!info.details.methods[0].needsImplementation);
1792 	assert(!info.details.methods[0].optionalImplementation);
1793 	assert(info.details.methods[0].definitionRange == [222, 286]);
1794 	assert(info.details.methods[0].blockRange == [286, 324]);
1795 
1796 	assert(info.details.methods[1].name == "bar1");
1797 	assert(info.details.methods[1].signature == "private static void bar1();");
1798 	assert(info.details.methods[1].returnType == "void");
1799 	assert(!info.details.methods[1].isNothrowOrNogc);
1800 	assert(info.details.methods[1].hasBody);
1801 	assert(!info.details.methods[1].needsImplementation);
1802 	assert(!info.details.methods[1].optionalImplementation);
1803 	assert(info.details.methods[1].definitionRange == [334, 346]);
1804 	assert(info.details.methods[1].blockRange == [346, 348]);
1805 
1806 	assert(info.details.methods[2].name == "bar2");
1807 	assert(info.details.methods[2].signature == "private void bar2();");
1808 	assert(info.details.methods[2].returnType == "void");
1809 	assert(!info.details.methods[2].isNothrowOrNogc);
1810 	assert(info.details.methods[2].hasBody);
1811 	assert(!info.details.methods[2].needsImplementation);
1812 	assert(!info.details.methods[2].optionalImplementation);
1813 	assert(info.details.methods[2].definitionRange == [351, 363]);
1814 	assert(info.details.methods[2].blockRange == [363, 365]);
1815 
1816 	assert(info.details.methods[3].name == "bar3");
1817 	assert(info.details.methods[3].signature == "private void bar3();");
1818 	assert(info.details.methods[3].returnType == "void");
1819 	assert(!info.details.methods[3].isNothrowOrNogc);
1820 	assert(info.details.methods[3].hasBody);
1821 	assert(!info.details.methods[3].needsImplementation);
1822 	assert(!info.details.methods[3].optionalImplementation);
1823 	assert(info.details.methods[3].definitionRange == [396, 408]);
1824 	assert(info.details.methods[3].blockRange == [408, 410]);
1825 
1826 	assert(info.details.methods[4].name == "somefunc");
1827 	assert(info.details.methods[4].signature == "private FooBar.Something somefunc();");
1828 	assert(info.details.methods[4].returnType == "FooBar.Something");
1829 	assert(!info.details.methods[4].isNothrowOrNogc);
1830 	assert(info.details.methods[4].hasBody);
1831 	assert(!info.details.methods[4].needsImplementation);
1832 	assert(!info.details.methods[4].optionalImplementation);
1833 	assert(info.details.methods[4].definitionRange == [448, 476]);
1834 	assert(info.details.methods[4].blockRange == [476, 502]);
1835 
1836 	// test normalization of types
1837 	assert(info.details.methods[5].name == "somefunc2");
1838 	assert(info.details.methods[5].signature == "private FooBar.Something somefunc2();",
1839 			info.details.methods[5].signature);
1840 	assert(info.details.methods[5].returnType == "FooBar.Something");
1841 	assert(!info.details.methods[5].isNothrowOrNogc);
1842 	assert(info.details.methods[5].hasBody);
1843 	assert(!info.details.methods[5].needsImplementation);
1844 	assert(!info.details.methods[5].optionalImplementation);
1845 	assert(info.details.methods[5].definitionRange == [504, 526]);
1846 	assert(info.details.methods[5].blockRange == [526, 552]);
1847 }
1848 
1849 unittest
1850 {
1851 	string testCode = q{package interface Foo0
1852 {
1853 	string stringMethod();
1854 	Tuple!(int, string, Array!bool)[][] advancedMethod(int a, int b, string c);
1855 	void normalMethod();
1856 	int attributeSuffixMethod() nothrow @property @nogc;
1857 	private
1858 	{
1859 		void middleprivate1();
1860 		void middleprivate2();
1861 	}
1862 	extern(C) @property @nogc ref immutable int attributePrefixMethod() const;
1863 	final void alreadyImplementedMethod() {}
1864 	deprecated("foo") void deprecatedMethod() {}
1865 	static void staticMethod() {}
1866 	protected void protectedMethod();
1867 private:
1868 	void barfoo();
1869 }}.replace("\r\n", "\n");
1870 
1871 	scope backend = new WorkspaceD();
1872 	auto workspace = makeTemporaryTestingWorkspace;
1873 	auto instance = backend.addInstance(workspace.directory);
1874 	backend.register!DCDExtComponent;
1875 	DCDExtComponent dcdext = instance.get!DCDExtComponent;
1876 
1877 	auto info = dcdext.describeInterfaceRecursiveSync(testCode, 20);
1878 	assert(info.details.name == "Foo0");
1879 	assert(info.details.blockRange == [23, 523]);
1880 	assert(info.details.referencedTypes.length == 3);
1881 	assert(info.details.referencedTypes[0].name == "Array");
1882 	assert(info.details.referencedTypes[0].location == 70);
1883 	assert(info.details.referencedTypes[1].name == "Tuple");
1884 	assert(info.details.referencedTypes[1].location == 50);
1885 	assert(info.details.referencedTypes[2].name == "string");
1886 	assert(info.details.referencedTypes[2].location == 26);
1887 
1888 	assert(info.details.fields.length == 0);
1889 
1890 	assert(info.details.methods[0 .. 4].all!"!a.hasBody");
1891 	assert(info.details.methods[0 .. 4].all!"a.needsImplementation");
1892 	assert(info.details.methods.all!"!a.optionalImplementation");
1893 
1894 	assert(info.details.methods.length == 12);
1895 	assert(info.details.methods[0].name == "stringMethod");
1896 	assert(info.details.methods[0].signature == "string stringMethod();");
1897 	assert(info.details.methods[0].returnType == "string");
1898 	assert(!info.details.methods[0].isNothrowOrNogc);
1899 
1900 	assert(info.details.methods[1].name == "advancedMethod");
1901 	assert(info.details.methods[1].signature
1902 			== "Tuple!(int, string, Array!bool)[][] advancedMethod(int a, int b, string c);");
1903 	assert(info.details.methods[1].returnType == "Tuple!(int, string, Array!bool)[][]");
1904 	assert(!info.details.methods[1].isNothrowOrNogc);
1905 
1906 	assert(info.details.methods[2].name == "normalMethod");
1907 	assert(info.details.methods[2].signature == "void normalMethod();");
1908 	assert(info.details.methods[2].returnType == "void");
1909 
1910 	assert(info.details.methods[3].name == "attributeSuffixMethod");
1911 	assert(info.details.methods[3].signature == "int attributeSuffixMethod() nothrow @property @nogc;");
1912 	assert(info.details.methods[3].returnType == "int");
1913 	assert(info.details.methods[3].isNothrowOrNogc);
1914 
1915 	assert(info.details.methods[4].name == "middleprivate1");
1916 	assert(info.details.methods[4].signature == "private void middleprivate1();");
1917 	assert(info.details.methods[4].returnType == "void");
1918 
1919 	assert(info.details.methods[5].name == "middleprivate2");
1920 
1921 	assert(info.details.methods[6].name == "attributePrefixMethod");
1922 	assert(info.details.methods[6].signature
1923 			== "extern (C) @property @nogc ref immutable int attributePrefixMethod() const;");
1924 	assert(info.details.methods[6].returnType == "int");
1925 	assert(info.details.methods[6].isNothrowOrNogc);
1926 
1927 	assert(info.details.methods[7].name == "alreadyImplementedMethod");
1928 	assert(info.details.methods[7].signature == "void alreadyImplementedMethod();");
1929 	assert(info.details.methods[7].returnType == "void");
1930 	assert(!info.details.methods[7].needsImplementation);
1931 	assert(info.details.methods[7].hasBody);
1932 
1933 	assert(info.details.methods[8].name == "deprecatedMethod");
1934 	assert(info.details.methods[8].signature == `deprecated("foo") void deprecatedMethod();`);
1935 	assert(info.details.methods[8].returnType == "void");
1936 	assert(info.details.methods[8].needsImplementation);
1937 	assert(info.details.methods[8].hasBody);
1938 
1939 	assert(info.details.methods[9].name == "staticMethod");
1940 	assert(info.details.methods[9].signature == `static void staticMethod();`);
1941 	assert(info.details.methods[9].returnType == "void");
1942 	assert(!info.details.methods[9].needsImplementation);
1943 	assert(info.details.methods[9].hasBody);
1944 
1945 	assert(info.details.methods[10].name == "protectedMethod");
1946 	assert(info.details.methods[10].signature == `protected void protectedMethod();`);
1947 	assert(info.details.methods[10].returnType == "void");
1948 	assert(info.details.methods[10].needsImplementation);
1949 	assert(!info.details.methods[10].hasBody);
1950 
1951 	assert(info.details.methods[11].name == "barfoo");
1952 	assert(info.details.methods[11].signature == `private void barfoo();`);
1953 	assert(info.details.methods[11].returnType == "void");
1954 	assert(!info.details.methods[11].needsImplementation);
1955 	assert(!info.details.methods[11].hasBody);
1956 }
1957 
1958 unittest
1959 {
1960 	string testCode = q{module hello;
1961 
1962 interface MyInterface
1963 {
1964 	void foo();
1965 }
1966 
1967 class ImplA : MyInterface
1968 {
1969 
1970 }
1971 
1972 class ImplB : MyInterface
1973 {
1974 	void foo() {}
1975 }
1976 }.replace("\r\n", "\n");
1977 
1978 	scope backend = new WorkspaceD();
1979 	auto workspace = makeTemporaryTestingWorkspace;
1980 	auto instance = backend.addInstance(workspace.directory);
1981 	backend.register!DCDExtComponent;
1982 	DCDExtComponent dcdext = instance.get!DCDExtComponent;
1983 
1984 	auto info = dcdext.getInterfaceDetails("stdin", testCode, 72);
1985 
1986 	assert(info.blockRange == [81, 85]);
1987 }
1988 
1989 unittest
1990 {
1991 	scope backend = new WorkspaceD();
1992 	auto workspace = makeTemporaryTestingWorkspace;
1993 	auto instance = backend.addInstance(workspace.directory);
1994 	backend.register!DCDExtComponent;
1995 	DCDExtComponent dcdext = instance.get!DCDExtComponent;
1996 
1997 	assert(dcdext.formatDefinitionBlock("Foo!(int, string) x") == "Foo!(int, string) x");
1998 	assert(dcdext.formatDefinitionBlock("void foo()") == "void foo()");
1999 	assert(dcdext.formatDefinitionBlock("void foo(string x)") == "void foo(\n\tstring x\n)");
2000 	assert(dcdext.formatDefinitionBlock("void foo(string x,)") == "void foo(\n\tstring x\n)");
2001 	assert(dcdext.formatDefinitionBlock("void foo(string x, int y)") == "void foo(\n\tstring x,\n\tint y\n)");
2002 	assert(dcdext.formatDefinitionBlock("void foo(string, int)") == "void foo(\n\tstring,\n\tint\n)");
2003 	assert(dcdext.formatDefinitionBlock("this(string, int)") == "this(\n\tstring,\n\tint\n)");
2004 	assert(dcdext.formatDefinitionBlock("auto foo(string, int)") == "auto foo(\n\tstring,\n\tint\n)");
2005 	assert(dcdext.formatDefinitionBlock("ComplexTemplate!(int, 'a', string, Nested!(Foo)) foo(string, int)")
2006 		== "ComplexTemplate!(int, 'a', string, Nested!(Foo)) foo(\n\tstring,\n\tint\n)");
2007 	assert(dcdext.formatDefinitionBlock("auto foo(T, V)(string, int)") == "auto foo(\n\tT,\n\tV\n)(\n\tstring,\n\tint\n)");
2008 	assert(dcdext.formatDefinitionBlock("auto foo(string, int f, ...)") == "auto foo(\n\tstring,\n\tint f,\n\t...\n)");
2009 }
2010 
2011 final class IfFinder : ASTVisitor
2012 {
2013 	Token[] currentIf, foundIf;
2014 
2015 	size_t target;
2016 
2017 	alias visit = ASTVisitor.visit;
2018 
2019 	static foreach (If; AliasSeq!(IfStatement, ConditionalStatement))
2020 	override void visit(const If ifStatement)
2021 	{
2022 		if (foundIf.length)
2023 			return;
2024 
2025 		auto lastIf = currentIf;
2026 		scope (exit)
2027 			currentIf = lastIf;
2028 
2029 		currentIf = [ifStatement.tokens[0]];
2030 
2031 		static auto thenStatement(const If v)
2032 		{
2033 			static if (is(If == IfStatement))
2034 				return v.thenStatement;
2035 			else
2036 				return v.trueStatement;
2037 		}
2038 
2039 		static auto elseStatement(const If v)
2040 		{
2041 			static if (is(If == IfStatement))
2042 				return v.elseStatement;
2043 			else
2044 				return v.falseStatement;
2045 		}
2046 
2047 		if (thenStatement(ifStatement))
2048 			thenStatement(ifStatement).accept(this);
2049 
2050 		const(BaseNode) elseStmt = elseStatement(ifStatement);
2051 		while (elseStmt)
2052 		{
2053 			auto elseToken = elseStmt.tokens.ptr - 1;
2054 
2055 			// possible from if declarations
2056 			if (elseToken.type == tok!"{" || elseToken.type == tok!":")
2057 				elseToken--;
2058 
2059 			if (elseToken.type == tok!"else")
2060 			{
2061 				if (!currentIf.length || currentIf[$ - 1] != *elseToken)
2062 					currentIf ~= *elseToken;
2063 			}
2064 
2065 			if (auto elseIf = cast(IfStatement) elseStmt)
2066 			{
2067 				currentIf ~= elseIf.tokens[0];
2068 				elseIf.accept(this);
2069 				cast()elseStmt = elseIf.elseStatement;
2070 			}
2071 			else if (auto elseStaticIf = cast(ConditionalStatement) elseStmt)
2072 			{
2073 				currentIf ~= elseStaticIf.tokens[0];
2074 				currentIf ~= elseStaticIf.tokens[1];
2075 				elseStaticIf.accept(this);
2076 				cast()elseStmt = elseStaticIf.falseStatement;
2077 			}
2078 			else if (auto declOrStatement = cast(DeclarationOrStatement) elseStmt)
2079 			{
2080 				if (declOrStatement.statement && declOrStatement.statement.statementNoCaseNoDefault)
2081 				{
2082 					if (declOrStatement.statement.statementNoCaseNoDefault.conditionalStatement)
2083 					{
2084 						cast()elseStmt = declOrStatement.statement.statementNoCaseNoDefault.conditionalStatement;
2085 					}
2086 					else if (declOrStatement.statement.statementNoCaseNoDefault.ifStatement)
2087 					{
2088 						cast()elseStmt = declOrStatement.statement.statementNoCaseNoDefault.ifStatement;
2089 					}
2090 					else
2091 					{
2092 						elseStmt.accept(this);
2093 						cast()elseStmt = null;
2094 					}
2095 				}
2096 				else if (declOrStatement.declaration && declOrStatement.declaration.conditionalDeclaration)
2097 				{
2098 					auto cond = declOrStatement.declaration.conditionalDeclaration;
2099 					if (cond.trueDeclarations.length)
2100 					{
2101 						auto ifSearch = cond.trueDeclarations[0].tokens.ptr;
2102 						while (!ifSearch.type.among!(tok!"if", tok!";", tok!"}", tok!"module"))
2103 							ifSearch--;
2104 
2105 						if (ifSearch.type == tok!"if")
2106 						{
2107 							if ((ifSearch - 1).type == tok!"static")
2108 								currentIf ~= *(ifSearch - 1);
2109 							currentIf ~= *ifSearch;
2110 						}
2111 					}
2112 
2113 					if (cond.hasElse && cond.falseDeclarations.length == 1)
2114 					{
2115 						elseStmt.accept(this);
2116 						cast()elseStmt = cast()cond.falseDeclarations[0];
2117 					}
2118 					else
2119 					{
2120 						elseStmt.accept(this);
2121 						cast()elseStmt = null;
2122 					}
2123 				}
2124 				else
2125 				{
2126 					elseStmt.accept(this);
2127 					cast()elseStmt = null;
2128 				}
2129 			}
2130 			else
2131 			{
2132 				elseStmt.accept(this);
2133 				cast()elseStmt = null;
2134 			}
2135 		}
2136 
2137 		saveIfMatching();
2138 	}
2139 
2140 	void saveIfMatching()
2141 	{
2142 		if (foundIf.length)
2143 			return;
2144 
2145 		foreach (v; currentIf)
2146 			if (v.index == target)
2147 			{
2148 				foundIf = currentIf;
2149 				return;
2150 			}
2151 	}
2152 }
2153 
2154 unittest
2155 {
2156 	scope backend = new WorkspaceD();
2157 	auto workspace = makeTemporaryTestingWorkspace;
2158 	auto instance = backend.addInstance(workspace.directory);
2159 	backend.register!DCDExtComponent;
2160 	DCDExtComponent dcdext = instance.get!DCDExtComponent;
2161 
2162 	assert(dcdext.highlightRelated(`void foo()
2163 {
2164 	if (true) {}
2165 	else static if (true) {}
2166 	else if (true) {}
2167 	else {}
2168 
2169 	if (true) {}
2170 	else static if (true) {}
2171 	else {}
2172 }`, 35) == [
2173 	Related(Related.Type.controlFlow, [14, 16]),
2174 	Related(Related.Type.controlFlow, [28, 32]),
2175 	Related(Related.Type.controlFlow, [33, 39]),
2176 	Related(Related.Type.controlFlow, [40, 42]),
2177 	Related(Related.Type.controlFlow, [54, 58]),
2178 	Related(Related.Type.controlFlow, [59, 61]),
2179 	Related(Related.Type.controlFlow, [73, 77]),
2180 ]);
2181 
2182 	assert(dcdext.highlightRelated(`void foo()
2183 {
2184 	if (true) {}
2185 	else static if (true) {}
2186 	else if (true) {}
2187 	else {}
2188 
2189 	if (true) {}
2190 	else static if (true) { int a; }
2191 	else { int b;}
2192 }`, 83) == [
2193 	Related(Related.Type.controlFlow, [83, 85]),
2194 	Related(Related.Type.controlFlow, [97, 101]),
2195 	Related(Related.Type.controlFlow, [102, 108]),
2196 	Related(Related.Type.controlFlow, [109, 111]),
2197 	Related(Related.Type.controlFlow, [131, 135]),
2198 ]);
2199 }
2200 
2201 final class SwitchFinder : ASTVisitor
2202 {
2203 	Token[] currentSwitch, foundSwitch;
2204 	const(Statement) currentStatement;
2205 
2206 	size_t target;
2207 
2208 	alias visit = ASTVisitor.visit;
2209 
2210 	override void visit(const SwitchStatement stmt)
2211 	{
2212 		if (foundSwitch.length)
2213 			return;
2214 
2215 		auto lastSwitch = currentSwitch;
2216 		scope (exit)
2217 			currentSwitch = lastSwitch;
2218 
2219 		currentSwitch = [stmt.tokens[0]];
2220 		stmt.accept(this);
2221 
2222 		saveIfMatching();
2223 	}
2224 
2225 	override void visit(const CaseRangeStatement stmt)
2226 	{
2227 		if (currentStatement)
2228 		{
2229 			auto curr = currentStatement.tokens[0];
2230 			if (curr.type == tok!"case")
2231 				currentSwitch ~= curr;
2232 		}
2233 		auto last = *(stmt.high.tokens.ptr - 1);
2234 		if (last.type == tok!"case")
2235 			currentSwitch ~= last;
2236 		stmt.accept(this);
2237 	}
2238 
2239 	override void visit(const CaseStatement stmt)
2240 	{
2241 		if (currentStatement)
2242 		{
2243 			auto curr = currentStatement.tokens[0];
2244 			if (curr.type == tok!"case")
2245 				currentSwitch ~= curr;
2246 		}
2247 		stmt.accept(this);
2248 	}
2249 
2250 	override void visit(const DefaultStatement stmt)
2251 	{
2252 		currentSwitch ~= stmt.tokens[0];
2253 		stmt.accept(this);
2254 	}
2255 
2256 	override void visit(const Statement stmt)
2257 	{
2258 		auto last = currentStatement;
2259 		scope (exit)
2260 			cast()currentStatement = cast()last;
2261 		cast()currentStatement = cast()stmt;
2262 		stmt.accept(this);
2263 	}
2264 
2265 	void saveIfMatching()
2266 	{
2267 		if (foundSwitch.length)
2268 			return;
2269 
2270 		foreach (v; currentSwitch)
2271 			if (v.index == target)
2272 			{
2273 				foundSwitch = currentSwitch;
2274 				return;
2275 			}
2276 	}
2277 }
2278 
2279 unittest
2280 {
2281 	scope backend = new WorkspaceD();
2282 	auto workspace = makeTemporaryTestingWorkspace;
2283 	auto instance = backend.addInstance(workspace.directory);
2284 	backend.register!DCDExtComponent;
2285 	DCDExtComponent dcdext = instance.get!DCDExtComponent;
2286 
2287 	assert(dcdext.highlightRelated(`void foo()
2288 {
2289 	switch (foo)
2290 	{
2291 		case 1: .. case 3:
2292 			break;
2293 		case 5:
2294 			switch (bar)
2295 			{
2296 			case 6:
2297 				break;
2298 			default:
2299 				break;
2300 			}
2301 			break;
2302 		default:
2303 			break;
2304 	}
2305 }`.normLF, 35) == [
2306 	Related(Related.Type.controlFlow, [14, 20]),
2307 	Related(Related.Type.controlFlow, [32, 36]),
2308 	Related(Related.Type.controlFlow, [43, 47]),
2309 	Related(Related.Type.controlFlow, [63, 67]),
2310 	Related(Related.Type.controlFlow, [154, 161]),
2311 ]);
2312 }
2313 
2314 final class BreakFinder : ASTVisitor
2315 {
2316 	Token[] currentBlock, foundBlock;
2317 	const(Statement) currentStatement;
2318 	bool inSwitch;
2319 
2320 	size_t target;
2321 	bool isBreak; // else continue if not loop
2322 	bool isLoop; // checking loop token (instead of break/continue)
2323 	string label;
2324 
2325 	alias visit = ASTVisitor.visit;
2326 
2327 	override void visit(const LabeledStatement stmt)
2328 	{
2329 		if (foundBlock.length)
2330 			return;
2331 
2332 		if (label.length && label == stmt.identifier.text)
2333 		{
2334 			foundBlock = [stmt.identifier];
2335 			return;
2336 		}
2337 
2338 		stmt.accept(this);
2339 	}
2340 
2341 	override void visit(const SwitchStatement stmt)
2342 	{
2343 		if (foundBlock.length)
2344 			return;
2345 
2346 		bool wasSwitch = inSwitch;
2347 		scope (exit)
2348 			inSwitch = wasSwitch;
2349 		inSwitch = true;
2350 
2351 		if (isBreak)
2352 		{
2353 			auto lastSwitch = currentBlock;
2354 			scope (exit)
2355 				currentBlock = lastSwitch;
2356 
2357 			currentBlock = [stmt.tokens[0]];
2358 			stmt.accept(this);
2359 
2360 			saveIfMatching();
2361 		}
2362 		else
2363 		{
2364 			stmt.accept(this);
2365 		}
2366 	}
2367 
2368 	static foreach (LoopT; AliasSeq!(ForeachStatement, StaticForeachDeclaration,
2369 		StaticForeachStatement, ForStatement, WhileStatement))
2370 	override void visit(const LoopT stmt)
2371 	{
2372 		if (foundBlock.length)
2373 			return;
2374 
2375 		auto lastSwitch = currentBlock;
2376 		scope (exit)
2377 			currentBlock = lastSwitch;
2378 
2379 		currentBlock = [stmt.tokens[0]];
2380 		stmt.accept(this);
2381 
2382 		saveIfMatching();
2383 	}
2384 
2385 	override void visit(const DoStatement stmt)
2386 	{
2387 		if (foundBlock.length)
2388 			return;
2389 
2390 		auto lastSwitch = currentBlock;
2391 		scope (exit)
2392 			currentBlock = lastSwitch;
2393 
2394 		currentBlock = [stmt.tokens[0]];
2395 		auto whileTok = *(stmt.expression.tokens.ptr - 2);
2396 		stmt.accept(this);
2397 		if (whileTok.type == tok!"while")
2398 			currentBlock ~= whileTok;
2399 
2400 		saveIfMatching();
2401 	}
2402 
2403 	static foreach (IgnoreT; AliasSeq!(FunctionBody, FunctionDeclaration, StructBody))
2404 	override void visit(const IgnoreT stmt)
2405 	{
2406 		if (foundBlock.length)
2407 			return;
2408 
2409 		auto lastSwitch = currentBlock;
2410 		scope (exit)
2411 			currentBlock = lastSwitch;
2412 
2413 		currentBlock = null;
2414 		stmt.accept(this);
2415 	}
2416 
2417 	override void visit(const CaseRangeStatement stmt)
2418 	{
2419 		if (isBreak)
2420 		{
2421 			if (currentStatement)
2422 			{
2423 				auto curr = currentStatement.tokens[0];
2424 				if (curr.type == tok!"case")
2425 					currentBlock ~= curr;
2426 			}
2427 			auto last = *(stmt.high.tokens.ptr - 1);
2428 			if (last.type == tok!"case")
2429 				currentBlock ~= last;
2430 		}
2431 		stmt.accept(this);
2432 	}
2433 
2434 	override void visit(const CaseStatement stmt)
2435 	{
2436 		if (currentStatement && isBreak)
2437 		{
2438 			auto curr = currentStatement.tokens[0];
2439 			if (curr.type == tok!"case")
2440 				currentBlock ~= curr;
2441 		}
2442 		stmt.accept(this);
2443 	}
2444 
2445 	override void visit(const DefaultStatement stmt)
2446 	{
2447 		if (isBreak)
2448 			currentBlock ~= stmt.tokens[0];
2449 		stmt.accept(this);
2450 	}
2451 
2452 	override void visit(const Statement stmt)
2453 	{
2454 		auto last = currentStatement;
2455 		scope (exit)
2456 			cast()currentStatement = cast()last;
2457 		cast()currentStatement = cast()stmt;
2458 		stmt.accept(this);
2459 	}
2460 
2461 	override void visit(const BreakStatement stmt)
2462 	{
2463 		if (stmt.tokens[0].index == target || isLoop)
2464 			if (isBreak)
2465 				currentBlock ~= stmt.tokens[0];
2466 		stmt.accept(this);
2467 	}
2468 
2469 	override void visit(const ContinueStatement stmt)
2470 	{
2471 		// break token:
2472 		//   continue in switch: ignore
2473 		//   continue outside switch: include
2474 		// other token:
2475 		//   continue in switch: include
2476 		//   continue outside switch: include
2477 		if (stmt.tokens[0].index == target || isLoop)
2478 			if (!(isBreak && inSwitch))
2479 				currentBlock ~= stmt.tokens[0];
2480 		stmt.accept(this);
2481 	}
2482 
2483 	void saveIfMatching()
2484 	{
2485 		if (foundBlock.length || label.length)
2486 			return;
2487 
2488 		foreach (v; currentBlock)
2489 			if (v.index == target)
2490 			{
2491 				foundBlock = currentBlock;
2492 				return;
2493 			}
2494 	}
2495 }
2496 
2497 class ReverseReturnFinder : ASTVisitor
2498 {
2499 	Token[] returns;
2500 	size_t target;
2501 	bool record;
2502 
2503 	alias visit = ASTVisitor.visit;
2504 
2505 	static foreach (DeclT; AliasSeq!(Declaration, Statement))
2506 	override void visit(const DeclT stmt)
2507 	{
2508 		if (returns.length && !record)
2509 			return;
2510 
2511 		bool matches = stmt.tokens.length && stmt.tokens[0].index == target;
2512 		if (matches)
2513 			record = true;
2514 		stmt.accept(this);
2515 		if (matches)
2516 			record = false;
2517 	}
2518 
2519 	override void visit(const ReturnStatement ret)
2520 	{
2521 		if (record)
2522 			returns ~= ret.tokens[0];
2523 		ret.accept(this);
2524 	}
2525 }
2526 
2527 unittest
2528 {
2529 	scope backend = new WorkspaceD();
2530 	auto workspace = makeTemporaryTestingWorkspace;
2531 	auto instance = backend.addInstance(workspace.directory);
2532 	backend.register!DCDExtComponent;
2533 	DCDExtComponent dcdext = instance.get!DCDExtComponent;
2534 
2535 	assert(dcdext.highlightRelated(`void foo()
2536 {
2537 	while (true)
2538 	{
2539 		foreach (a; b)
2540 		{
2541 			switch (a)
2542 			{
2543 			case 1:
2544 				break;
2545 			case 2:
2546 				continue;
2547 			default:
2548 				return;
2549 			}
2550 		}
2551 	}
2552 }`.normLF, 88) == [
2553 	Related(Related.Type.controlFlow, [54, 60]),
2554 	Related(Related.Type.controlFlow, [73, 77]),
2555 	Related(Related.Type.controlFlow, [85, 90]),
2556 	Related(Related.Type.controlFlow, [95, 99]),
2557 	Related(Related.Type.controlFlow, [120, 127]),
2558 ]);
2559 
2560 	assert(dcdext.highlightRelated(`void foo()
2561 {
2562 	while (true)
2563 	{
2564 		foreach (a; b)
2565 		{
2566 			switch (a)
2567 			{
2568 			case 1:
2569 				break;
2570 			case 2:
2571 				continue;
2572 			default:
2573 				return;
2574 			}
2575 		}
2576 	}
2577 }`.normLF, 111) == [
2578 	Related(Related.Type.controlFlow, [32, 39]),
2579 	Related(Related.Type.controlFlow, [107, 115]),
2580 ]);
2581 
2582 	assert(dcdext.highlightRelated(`void foo()
2583 {
2584 	while (true)
2585 	{
2586 		foreach (a; b)
2587 		{
2588 			switch (a)
2589 			{
2590 			case 1:
2591 				break;
2592 			case 2:
2593 				continue;
2594 			default:
2595 				return;
2596 			}
2597 		}
2598 	}
2599 }`.normLF, 15) == [
2600 	Related(Related.Type.controlFlow, [14, 19]),
2601 	Related(Related.Type.controlFlow, [133, 139]),
2602 ]);
2603 }
2604 
2605 class ReturnFinder : ASTVisitor
2606 {
2607 	Token[] returns;
2608 	Token[] currentScope;
2609 	bool inTargetBlock;
2610 	Token[] related;
2611 	size_t target;
2612 
2613 	alias visit = ASTVisitor.visit;
2614 
2615 	static foreach (DeclT; AliasSeq!(FunctionBody))
2616 	override void visit(const DeclT stmt)
2617 	{
2618 		if (inTargetBlock || related.length)
2619 			return;
2620 
2621 		auto lastScope = currentScope;
2622 		scope (exit)
2623 			currentScope = lastScope;
2624 		currentScope = null;
2625 
2626 		auto lastReturns = returns;
2627 		scope (exit)
2628 			returns = lastReturns;
2629 		returns = null;
2630 
2631 		stmt.accept(this);
2632 		if (inTargetBlock)
2633 		{
2634 			related ~= returns;
2635 
2636 			related.sort!"a.index < b.index";
2637 		}
2638 	}
2639 
2640 	static foreach (ScopeT; AliasSeq!(SwitchStatement, ForeachStatement,
2641 		StaticForeachDeclaration, StaticForeachStatement, ForStatement, WhileStatement))
2642 	override void visit(const ScopeT stmt)
2643 	{
2644 		auto lastScope = currentScope;
2645 		scope (exit)
2646 			currentScope = lastScope;
2647 		currentScope ~= stmt.tokens[0];
2648 
2649 		stmt.accept(this);
2650 	}
2651 
2652 	override void visit(const DoStatement stmt)
2653 	{
2654 		auto lastScope = currentScope;
2655 		scope (exit)
2656 			currentScope = lastScope;
2657 		currentScope ~= stmt.tokens[0];
2658 
2659 		auto whileTok = *(stmt.expression.tokens.ptr - 2);
2660 		if (whileTok.type == tok!"while")
2661 			currentScope ~= whileTok;
2662 
2663 		stmt.accept(this);
2664 	}
2665 
2666 	override void visit(const ReturnStatement ret)
2667 	{
2668 		returns ~= ret.tokens[0];
2669 		if (target == ret.tokens[0].index)
2670 		{
2671 			inTargetBlock = true;
2672 			related ~= currentScope;
2673 		}
2674 		ret.accept(this);
2675 	}
2676 }
2677 
2678 unittest
2679 {
2680 	scope backend = new WorkspaceD();
2681 	auto workspace = makeTemporaryTestingWorkspace;
2682 	auto instance = backend.addInstance(workspace.directory);
2683 	backend.register!DCDExtComponent;
2684 	DCDExtComponent dcdext = instance.get!DCDExtComponent;
2685 
2686 	assert(dcdext.highlightRelated(`void foo()
2687 {
2688 	foreach (a; b)
2689 		return;
2690 
2691 	void bar()
2692 	{
2693 		return;
2694 	}
2695 
2696 	bar();
2697 
2698 	return;
2699 }`.normLF, 33) == [
2700 	Related(Related.Type.controlFlow, [14, 21]),
2701 	Related(Related.Type.controlFlow, [31, 37]),
2702 	Related(Related.Type.controlFlow, [79, 85]),
2703 ]);
2704 }