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.range;
18 import std.string;
19 
20 import workspaced.api;
21 import workspaced.dparseext;
22 import workspaced.com.dcd;
23 
24 import workspaced.visitors.classifier;
25 import workspaced.visitors.methodfinder;
26 
27 @component("dcdext")
28 class DCDExtComponent : ComponentWrapper
29 {
30 	mixin DefaultComponentWrapper;
31 
32 	static immutable CodeRegionProtection[] mixableProtection = [
33 		CodeRegionProtection.public_ | CodeRegionProtection.default_,
34 		CodeRegionProtection.package_, CodeRegionProtection.packageIdentifier,
35 		CodeRegionProtection.protected_, CodeRegionProtection.private_
36 	];
37 
38 	/// Loads dcd extension methods. Call with `{"cmd": "load", "components": ["dcdext"]}`
39 	void load()
40 	{
41 		if (!refInstance)
42 			return;
43 
44 		config.stringBehavior = StringBehavior.source;
45 	}
46 
47 	/// Extracts calltips help information at a given position.
48 	/// The position must be within the arguments of the function and not
49 	/// outside the parentheses or inside some child call.
50 	/// When generating the call parameters for a function definition, the position must be inside the normal parameters,
51 	/// otherwise the template arguments will be put as normal arguments.
52 	/// Returns: the position of significant locations for parameter extraction.
53 	/// Params:
54 	///   code = code to analyze
55 	///   position = byte offset where to check for function arguments
56 	///   definition = true if this hints is a function definition (templates don't have an exclamation point '!')
57 	CalltipsSupport extractCallParameters(scope const(char)[] code, int position, bool definition = false)
58 	{
59 		auto tokens = getTokensForParser(cast(ubyte[]) code, config, &workspaced.stringCache);
60 		auto queuedToken = tokens.countUntil!(a => a.index >= position) - 1;
61 		if (queuedToken == -2)
62 			queuedToken = cast(ptrdiff_t) tokens.length - 1;
63 		else if (queuedToken == -1)
64 			return CalltipsSupport.init;
65 
66 		bool inTemplate;
67 		int depth, subDepth;
68 		// contains opening parentheses location for arguments or exclamation point for templates.
69 		auto startParen = queuedToken;
70 		while (startParen >= 0)
71 		{
72 			const c = tokens[startParen];
73 			const p = startParen > 0 ? tokens[startParen - 1] : Token.init;
74 
75 			if (c.type == tok!"{")
76 			{
77 				if (subDepth == 0)
78 				{
79 					// we went too far, probably not inside a function (or we are in a delegate, where we don't want calltips)
80 					return CalltipsSupport.init;
81 				}
82 				else
83 					subDepth--;
84 			}
85 			else if (c.type == tok!"}")
86 			{
87 				subDepth++;
88 			}
89 			else if (depth == 0 && !definition && c.type == tok!"!" && p.type == tok!"identifier")
90 			{
91 				inTemplate = true;
92 				break;
93 			}
94 			else if (c.type == tok!")")
95 			{
96 				depth++;
97 			}
98 			else if (c.type == tok!"(")
99 			{
100 				if (depth == 0 && subDepth == 0)
101 				{
102 					if (startParen > 1 && p.type == tok!"!" && tokens[startParen - 2].type
103 							== tok!"identifier")
104 					{
105 						startParen--;
106 						inTemplate = true;
107 					}
108 					break;
109 				}
110 				else
111 					depth--;
112 			}
113 			startParen--;
114 		}
115 
116 		if (startParen <= 0)
117 			return CalltipsSupport.init;
118 
119 		auto templateOpen = inTemplate ? startParen : 0;
120 		auto functionOpen = inTemplate ? 0 : startParen;
121 
122 		if (inTemplate)
123 		{
124 			// go forwards to function arguments
125 			if (templateOpen + 2 < tokens.length && tokens[templateOpen + 1].type != tok!"(")
126 			{
127 				// single template arg (can only be one token)
128 				// https://dlang.org/spec/grammar.html#TemplateSingleArgument
129 				if (tokens[templateOpen + 2] == tok!"(")
130 					functionOpen = templateOpen + 2;
131 			}
132 			else
133 			{
134 				functionOpen = findClosingParenForward(tokens, templateOpen + 2);
135 
136 				if (functionOpen >= tokens.length)
137 					functionOpen = 0;
138 			}
139 		}
140 		else
141 		{
142 			// go backwards to template arguments
143 			if (functionOpen > 0 && tokens[functionOpen - 1].type == tok!")")
144 			{
145 				// multi template args
146 				depth = 0;
147 				subDepth = 0;
148 				templateOpen = functionOpen - 1;
149 				while (templateOpen >= 1)
150 				{
151 					const c = tokens[templateOpen];
152 
153 					if (c == tok!")")
154 						depth++;
155 					else
156 					{
157 						if (depth == 0 && templateOpen > 2 && c == tok!"(" && (definition
158 								|| (tokens[templateOpen - 1].type == tok!"!"
159 								&& tokens[templateOpen - 2].type == tok!"identifier")))
160 							break;
161 						else if (depth == 0)
162 						{
163 							templateOpen = 0;
164 							break;
165 						}
166 
167 						if (c == tok!"(")
168 							depth--;
169 					}
170 
171 					templateOpen--;
172 				}
173 
174 				if (templateOpen <= 1)
175 					templateOpen = 0;
176 			}
177 			else
178 			{
179 				// single template arg (can only be one token)
180 				if (functionOpen <= 2)
181 					return CalltipsSupport.init;
182 
183 				if (tokens[functionOpen - 2] == tok!"!" && tokens[functionOpen - 3] == tok!"identifier")
184 				{
185 					templateOpen = functionOpen - 2;
186 				}
187 			}
188 		}
189 
190 		bool hasTemplateParens = templateOpen && templateOpen == functionOpen - 2;
191 
192 		depth = 0;
193 		subDepth = 0;
194 		bool inFuncName = true;
195 		auto callStart = (templateOpen ? templateOpen : functionOpen) - 1;
196 		auto funcNameStart = callStart;
197 		while (callStart >= 0)
198 		{
199 			const c = tokens[callStart];
200 			const p = callStart > 0 ? tokens[callStart - 1] : Token.init;
201 
202 			if (c.type == tok!"]")
203 				depth++;
204 			else if (c.type == tok!"[")
205 			{
206 				if (depth == 0)
207 				{
208 					// this is some sort of `foo[(4` situation
209 					return CalltipsSupport.init;
210 				}
211 				depth--;
212 			}
213 			else if (c.type == tok!")")
214 				subDepth++;
215 			else if (c.type == tok!"(")
216 			{
217 				if (subDepth == 0)
218 				{
219 					// this is some sort of `foo((4` situation
220 					return CalltipsSupport.init;
221 				}
222 				subDepth--;
223 			}
224 			else if (depth == 0)
225 			{
226 
227 				if (c.type.isCalltipable)
228 				{
229 					if (c.type == tok!"identifier" && p.type == tok!"." && (callStart < 2
230 							|| !tokens[callStart - 2].type.among!(tok!";", tok!",",
231 							tok!"{", tok!"}", tok!"(")))
232 					{
233 						// member function, traverse further...
234 						if (inFuncName)
235 						{
236 							funcNameStart = callStart;
237 							inFuncName = false;
238 						}
239 						callStart--;
240 					}
241 					else
242 					{
243 						break;
244 					}
245 				}
246 				else
247 				{
248 					// this is some sort of `4(5` or `if(4` situtation
249 					return CalltipsSupport.init;
250 				}
251 			}
252 			// we ignore stuff inside brackets and parens such as `foo[4](5).bar[6](a`
253 			callStart--;
254 		}
255 
256 		if (inFuncName)
257 			funcNameStart = callStart;
258 
259 		auto templateClose = templateOpen ? (hasTemplateParens ? (functionOpen
260 				? functionOpen - 1 : findClosingParenForward(tokens, templateOpen + 1)) : templateOpen + 2)
261 			: 0;
262 		auto functionClose = functionOpen ? findClosingParenForward(tokens, functionOpen) : 0;
263 
264 		CalltipsSupport.Argument[] templateArgs;
265 		if (templateOpen)
266 			templateArgs = splitArgs(tokens[templateOpen + 1 .. templateClose]);
267 
268 		CalltipsSupport.Argument[] functionArgs;
269 		if (functionOpen)
270 			functionArgs = splitArgs(tokens[functionOpen + 1 .. functionClose]);
271 
272 		return CalltipsSupport([
273 				tokens.tokenIndex(templateOpen), tokens.tokenIndex(templateClose)
274 				], hasTemplateParens, templateArgs, [
275 				tokens.tokenIndex(functionOpen), tokens.tokenIndex(functionClose)
276 				], functionArgs, funcNameStart != callStart,
277 				tokens.tokenIndex(funcNameStart), tokens.tokenIndex(callStart));
278 	}
279 
280 	/// Finds the immediate surrounding code block at a position or returns CodeBlockInfo.init for none/module block.
281 	/// See_Also: CodeBlockInfo
282 	CodeBlockInfo getCodeBlockRange(scope const(char)[] code, int position)
283 	{
284 		auto tokens = getTokensForParser(cast(ubyte[]) code, config, &workspaced.stringCache);
285 		auto parsed = parseModule(tokens, "getCodeBlockRange_input.d", &rba);
286 		auto reader = new CodeBlockInfoFinder(position);
287 		reader.visit(parsed);
288 		return reader.block;
289 	}
290 
291 	/// Inserts a generic method after the corresponding block inside the scope where position is.
292 	/// If it can't find a good spot it will insert the code properly indented ata fitting location.
293 	// make public once usable
294 	private CodeReplacement[] insertCodeInContainer(string insert, scope const(char)[] code,
295 			int position, bool insertInLastBlock = true, bool insertAtEnd = true)
296 	{
297 		auto container = getCodeBlockRange(code, position);
298 
299 		scope const(char)[] codeBlock = code[container.innerRange[0] .. container.innerRange[1]];
300 
301 		scope tokensInsert = getTokensForParser(cast(ubyte[]) insert, config,
302 				&workspaced.stringCache);
303 		scope parsedInsert = parseModule(tokensInsert, "insertCode_insert.d", &rba);
304 
305 		scope insertReader = new CodeDefinitionClassifier(insert);
306 		insertReader.visit(parsedInsert);
307 		scope insertRegions = insertReader.regions.sort!"a.type < b.type".uniq.array;
308 
309 		scope tokens = getTokensForParser(cast(ubyte[]) codeBlock, config, &workspaced.stringCache);
310 		scope parsed = parseModule(tokens, "insertCode_code.d", &rba);
311 
312 		scope reader = new CodeDefinitionClassifier(codeBlock);
313 		reader.visit(parsed);
314 		scope regions = reader.regions;
315 
316 		CodeReplacement[] ret;
317 
318 		foreach (CodeDefinitionClassifier.Region toInsert; insertRegions)
319 		{
320 			auto insertCode = insert[toInsert.region[0] .. toInsert.region[1]];
321 			scope existing = regions.enumerate.filter!(a => a.value.sameBlockAs(toInsert));
322 			if (existing.empty)
323 			{
324 				const checkProtection = CodeRegionProtection.init.reduce!"a | b"(
325 						mixableProtection.filter!(a => (a & toInsert.protection) != 0));
326 
327 				bool inIncompatible = false;
328 				bool lastFit = false;
329 				int fittingProtection = -1;
330 				int firstStickyProtection = -1;
331 				int regionAfterFitting = -1;
332 				foreach (i, stickyProtection; regions)
333 				{
334 					if (stickyProtection.affectsFollowing
335 							&& stickyProtection.protection != CodeRegionProtection.init)
336 					{
337 						if (firstStickyProtection == -1)
338 							firstStickyProtection = cast(int) i;
339 
340 						if ((stickyProtection.protection & checkProtection) != 0)
341 						{
342 							fittingProtection = cast(int) i;
343 							lastFit = true;
344 							if (!insertInLastBlock)
345 								break;
346 						}
347 						else
348 						{
349 							if (lastFit)
350 							{
351 								regionAfterFitting = cast(int) i;
352 								lastFit = false;
353 							}
354 							inIncompatible = true;
355 						}
356 					}
357 				}
358 				assert(firstStickyProtection != -1 || !inIncompatible);
359 				assert(regionAfterFitting != -1 || fittingProtection == -1 || !inIncompatible);
360 
361 				if (inIncompatible)
362 				{
363 					int insertRegion = fittingProtection == -1 ? firstStickyProtection : regionAfterFitting;
364 					insertCode = text(indent(insertCode, regions[insertRegion].minIndentation), "\n\n");
365 					auto len = cast(uint) insertCode.length;
366 
367 					toInsert.region[0] = regions[insertRegion].region[0];
368 					toInsert.region[1] = regions[insertRegion].region[0] + len;
369 					foreach (ref r; regions[insertRegion .. $])
370 					{
371 						r.region[0] += len;
372 						r.region[1] += len;
373 					}
374 				}
375 				else
376 				{
377 					auto lastRegion = regions.back;
378 					insertCode = indent(insertCode, lastRegion.minIndentation).idup;
379 					auto len = cast(uint) insertCode.length;
380 					toInsert.region[0] = lastRegion.region[1];
381 					toInsert.region[1] = lastRegion.region[1] + len;
382 				}
383 				regions ~= toInsert;
384 				ret ~= CodeReplacement([toInsert.region[0], toInsert.region[0]], insertCode);
385 			}
386 			else
387 			{
388 				auto target = insertInLastBlock ? existing.tail(1).front : existing.front;
389 
390 				insertCode = text("\n\n", indent(insertCode, regions[target.index].minIndentation));
391 				const codeLength = cast(int) insertCode.length;
392 
393 				if (insertAtEnd)
394 				{
395 					ret ~= CodeReplacement([
396 							target.value.region[1], target.value.region[1]
397 							], insertCode);
398 					toInsert.region[0] = target.value.region[1];
399 					toInsert.region[1] = target.value.region[1] + codeLength;
400 					regions[target.index].region[1] = toInsert.region[1];
401 					foreach (ref other; regions[target.index + 1 .. $])
402 					{
403 						other.region[0] += codeLength;
404 						other.region[1] += codeLength;
405 					}
406 				}
407 				else
408 				{
409 					ret ~= CodeReplacement([
410 							target.value.region[0], target.value.region[0]
411 							], insertCode);
412 					regions[target.index].region[1] += codeLength;
413 					foreach (ref other; regions[target.index + 1 .. $])
414 					{
415 						other.region[0] += codeLength;
416 						other.region[1] += codeLength;
417 					}
418 				}
419 			}
420 		}
421 
422 		return ret;
423 	}
424 
425 	/// Implements the interfaces or abstract classes of a specified class/interface.
426 	Future!string implement(scope const(char)[] code, int position)
427 	{
428 		auto ret = new Future!string;
429 		gthreads.create({
430 			mixin(traceTask);
431 			try
432 			{
433 				struct InterfaceTree
434 				{
435 					InterfaceDetails details;
436 					InterfaceTree[] inherits;
437 				}
438 
439 				auto baseInterface = getInterfaceDetails("stdin", code, position);
440 
441 				string[] implementedMethods = baseInterface.methods
442 					.filter!"!a.needsImplementation"
443 					.map!"a.identifier"
444 					.array;
445 
446 				// start with private, add all the public ones later in traverseTree
447 				FieldDetails[] availableVariables = baseInterface.fields.filter!"a.isPrivate".array;
448 				InterfaceTree tree = InterfaceTree(baseInterface);
449 
450 				InterfaceTree* treeByName(InterfaceTree* tree, string name)
451 				{
452 					if (tree.details.name == name)
453 						return tree;
454 					foreach (ref parent; tree.inherits)
455 					{
456 						InterfaceTree* t = treeByName(&parent, name);
457 						if (t !is null)
458 							return t;
459 					}
460 					return null;
461 				}
462 
463 				void traverseTree(ref InterfaceTree sub)
464 				{
465 					availableVariables ~= sub.details.fields.filter!"!a.isPrivate".array;
466 					foreach (i, parent; sub.details.parentPositions)
467 					{
468 						string parentName = sub.details.normalizedParents[i];
469 						if (treeByName(&tree, parentName) is null)
470 						{
471 							auto details = lookupInterface(sub.details.code, parent);
472 							sub.inherits ~= InterfaceTree(details);
473 						}
474 					}
475 					foreach (ref inherit; sub.inherits)
476 						traverseTree(inherit);
477 				}
478 
479 				traverseTree(tree);
480 
481 				string changes;
482 				void processTree(ref InterfaceTree tree)
483 				{
484 					auto details = tree.details;
485 					if (details.methods.length)
486 					{
487 						bool first = true;
488 						foreach (fn; details.methods)
489 						{
490 							if (implementedMethods.canFind(fn.identifier))
491 								continue;
492 							if (!fn.needsImplementation)
493 							{
494 								implementedMethods ~= fn.identifier;
495 								continue;
496 							}
497 							if (first)
498 							{
499 								changes ~= "// implement " ~ details.name ~ "\n\n";
500 								first = false;
501 							}
502 							if (details.needsOverride)
503 								changes ~= "override ";
504 							changes ~= fn.signature[0 .. $ - 1];
505 							changes ~= " {";
506 							if (fn.optionalImplementation)
507 							{
508 								changes ~= "\n\t// TODO: optional implementation\n";
509 							}
510 
511 							string propertySearch;
512 							if (fn.signature.canFind("@property") && fn.arguments.length <= 1)
513 								propertySearch = fn.name;
514 							else if ((fn.name.startsWith("get") && fn.arguments.length == 0)
515 								|| (fn.name.startsWith("set") && fn.arguments.length == 1))
516 								propertySearch = fn.name[3 .. $];
517 
518 							string foundProperty;
519 							if (propertySearch)
520 							{
521 								foreach (variable; availableVariables)
522 								{
523 									if (fieldNameMatches(variable.name, propertySearch))
524 									{
525 										foundProperty = variable.name;
526 										break;
527 									}
528 								}
529 							}
530 
531 							if (foundProperty.length)
532 							{
533 								changes ~= "\n\t";
534 								if (fn.returnType != "void")
535 									changes ~= "return ";
536 								if (fn.name.startsWith("set") || fn.arguments.length == 1)
537 									changes ~= foundProperty ~ " = " ~ fn.arguments[0].name;
538 								else
539 									changes ~= foundProperty;
540 								changes ~= ";\n";
541 							}
542 							else if (fn.hasBody)
543 							{
544 								changes ~= "\n\t";
545 								if (fn.returnType != "void")
546 									changes ~= "return ";
547 								changes ~= "super." ~ fn.name;
548 								if (fn.arguments.length)
549 									changes ~= "(" ~ format("%(%s, %)", fn.arguments) ~ ")";
550 								else if (fn.returnType == "void")
551 									changes ~= "()"; // make functions that don't return add (), otherwise they might be attributes and don't need that
552 								changes ~= ";\n";
553 							}
554 							else if (fn.returnType != "void")
555 							{
556 								changes ~= "\n\t";
557 								if (fn.isNothrowOrNogc)
558 								{
559 									if (fn.returnType.endsWith("[]"))
560 										changes ~= "return null; // TODO: implement";
561 									else
562 										changes ~= "return " ~ fn.returnType ~ ".init; // TODO: implement";
563 								}
564 								else
565 									changes ~= `assert(false, "Method ` ~ fn.name ~ ` not implemented");`;
566 								changes ~= "\n";
567 							}
568 							changes ~= "}\n\n";
569 						}
570 					}
571 
572 					foreach (parent; tree.inherits)
573 						processTree(parent);
574 				}
575 
576 				processTree(tree);
577 
578 				ret.finish(changes);
579 			}
580 			catch (Throwable t)
581 			{
582 				ret.error(t);
583 			}
584 		});
585 		return ret;
586 	}
587 
588 private:
589 	RollbackAllocator rba;
590 	LexerConfig config;
591 
592 	InterfaceDetails lookupInterface(scope const(char)[] code, int position)
593 	{
594 		auto data = get!DCDComponent.findDeclaration(code, position).getBlocking;
595 		string file = data.file;
596 		int newPosition = data.position;
597 
598 		if (!file.length)
599 			return InterfaceDetails.init;
600 
601 		auto newCode = code;
602 		if (file != "stdin")
603 			newCode = readText(file);
604 
605 		return getInterfaceDetails(file, newCode, newPosition);
606 	}
607 
608 	InterfaceDetails getInterfaceDetails(string file, scope const(char)[] code, int position)
609 	{
610 		auto tokens = getTokensForParser(cast(ubyte[]) code, config, &workspaced.stringCache);
611 		auto parsed = parseModule(tokens, file, &rba);
612 		auto reader = new InterfaceMethodFinder(code, position);
613 		reader.visit(parsed);
614 		return reader.details;
615 	}
616 }
617 
618 ///
619 enum CodeRegionType : int
620 {
621 	/// null region (unset)
622 	init,
623 	/// Imports inside the block
624 	imports = 1 << 0,
625 	/// Aliases `alias foo this;`, `alias Type = Other;`
626 	aliases = 1 << 1,
627 	/// Nested classes/structs/unions/etc.
628 	types = 1 << 2,
629 	/// Raw variables `Type name;`
630 	fields = 1 << 3,
631 	/// Normal constructors `this(Args args)`
632 	ctor = 1 << 4,
633 	/// Copy constructors `this(this)`
634 	copyctor = 1 << 5,
635 	/// Destructors `~this()`
636 	dtor = 1 << 6,
637 	/// Properties (functions annotated with `@property`)
638 	properties = 1 << 7,
639 	/// Regular functions
640 	methods = 1 << 8,
641 }
642 
643 ///
644 enum CodeRegionProtection : int
645 {
646 	/// null protection (unset)
647 	init,
648 	/// default (unmarked) protection
649 	default_ = 1 << 0,
650 	/// public protection
651 	public_ = 1 << 1,
652 	/// package (automatic) protection
653 	package_ = 1 << 2,
654 	/// package (manual package name) protection
655 	packageIdentifier = 1 << 3,
656 	/// protected protection
657 	protected_ = 1 << 4,
658 	/// private protection
659 	private_ = 1 << 5,
660 }
661 
662 ///
663 enum CodeRegionStatic : int
664 {
665 	/// null static (unset)
666 	init,
667 	/// non-static code
668 	instanced = 1 << 0,
669 	/// static code
670 	static_ = 1 << 1,
671 }
672 
673 /// Represents a class/interface/struct/union/template with body.
674 struct CodeBlockInfo
675 {
676 	///
677 	enum Type : int
678 	{
679 		// keep the underlines in these values for range checking properly
680 
681 		///
682 		class_,
683 		///
684 		interface_,
685 		///
686 		struct_,
687 		///
688 		union_,
689 		///
690 		template_,
691 	}
692 
693 	static immutable string[] typePrefixes = [
694 		"class ", "interface ", "struct ", "union ", "template "
695 	];
696 
697 	///
698 	Type type;
699 	///
700 	string name;
701 	/// Outer range inside the code spanning curly braces and name but not type keyword.
702 	uint[2] outerRange;
703 	/// Inner range of body of the block touching, but not spanning curly braces.
704 	uint[2] innerRange;
705 
706 	string prefix() @property
707 	{
708 		return typePrefixes[cast(int) type];
709 	}
710 }
711 
712 ///
713 struct CalltipsSupport
714 {
715 	///
716 	struct Argument
717 	{
718 		/// 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.
719 		int[2] contentRange;
720 		/// Range of just the type, or for templates also `alias`
721 		int[2] typeRange;
722 		/// Range of just the name
723 		int[2] nameRange;
724 		/// Range of just the default value
725 		int[2] valueRange;
726 
727 		/// Creates Argument(range, range, range, 0)
728 		static Argument templateType(int[2] range)
729 		{
730 			return Argument(range, range, range);
731 		}
732 
733 		/// Creates Argument(range, 0, range, range)
734 		static Argument templateValue(int[2] range)
735 		{
736 			return Argument(range, typeof(range).init, range, range);
737 		}
738 	}
739 
740 	bool hasTemplate() @property
741 	{
742 		return hasTemplateParens || templateArgumentsRange != typeof(templateArgumentsRange).init;
743 	}
744 
745 	/// Range starting inclusive at exclamation point until exclusive at closing bracket or function opening bracket.
746 	int[2] templateArgumentsRange;
747 	///
748 	bool hasTemplateParens;
749 	///
750 	Argument[] templateArgs;
751 	/// Range starting inclusive at opening parentheses until exclusive at closing parentheses.
752 	int[2] functionParensRange;
753 	///
754 	Argument[] functionArgs;
755 	/// True if the function is UFCS or a member function of some object or namespace.
756 	/// False if this is a global function call.
757 	bool hasParent;
758 	/// Start of the function itself.
759 	int functionStart;
760 	/// Start of the whole call going up all call parents. (`foo.bar.function` having `foo.bar` as parents)
761 	int parentStart;
762 }
763 
764 private:
765 
766 bool isCalltipable(IdType type)
767 {
768 	return type == tok!"identifier" || type == tok!"assert" || type == tok!"import"
769 		|| type == tok!"mixin" || type == tok!"super" || type == tok!"this" || type == tok!"__traits";
770 }
771 
772 int[2] tokenRange(const Token token)
773 {
774 	return [cast(int) token.index, cast(int)(token.index + token.text.length)];
775 }
776 
777 int tokenEnd(const Token token)
778 {
779 	return cast(int)(token.index + token.text.length);
780 }
781 
782 int tokenIndex(const(Token)[] tokens, ptrdiff_t i)
783 {
784 	if (i > 0 && i == tokens.length)
785 		return cast(int)(tokens[$ - 1].index + tokens[$ - 1].text.length);
786 	return i >= 0 ? cast(int) tokens[i].index : 0;
787 }
788 
789 int tokenEndIndex(const(Token)[] tokens, ptrdiff_t i)
790 {
791 	if (i > 0 && i == tokens.length)
792 		return cast(int)(tokens[$ - 1].index + tokens[$ - 1].text.length);
793 	return i >= 0 ? cast(int)(tokens[i].index + tokens[i].text.length) : 0;
794 }
795 
796 ptrdiff_t findClosingParenForward(const(Token)[] tokens, ptrdiff_t open)
797 in(tokens[open].type == tok!"(")
798 {
799 	if (open >= tokens.length || open < 0)
800 		return open;
801 
802 	open++;
803 
804 	int depth = 1;
805 	int subDepth = 0;
806 	while (open < tokens.length)
807 	{
808 		const c = tokens[open];
809 
810 		if (c == tok!"(")
811 			depth++;
812 		else if (c == tok!"{")
813 			subDepth++;
814 		else if (c == tok!"}")
815 		{
816 			if (subDepth == 0)
817 				break;
818 			subDepth--;
819 		}
820 		else
821 		{
822 			if (c == tok!";" && subDepth == 0)
823 				break;
824 
825 			if (c == tok!")")
826 				depth--;
827 
828 			if (depth == 0)
829 				break;
830 		}
831 
832 		open++;
833 	}
834 	return open;
835 }
836 
837 CalltipsSupport.Argument[] splitArgs(const(Token)[] tokens)
838 {
839 	auto ret = appender!(CalltipsSupport.Argument[]);
840 	size_t start = 0;
841 	size_t valueStart = 0;
842 
843 	int depth, subDepth;
844 	bool gotValue;
845 
846 	void putArg(size_t end)
847 	{
848 		if (start >= end || start >= tokens.length)
849 			return;
850 
851 		CalltipsSupport.Argument arg;
852 
853 		auto typename = tokens[start .. end];
854 		arg.contentRange = [cast(int) typename[0].index, typename[$ - 1].tokenEnd];
855 		if (typename.length == 1)
856 		{
857 			auto t = typename[0];
858 			if (t.type == tok!"identifier" || t.type.isBasicType)
859 				arg = CalltipsSupport.Argument.templateType(t.tokenRange);
860 			else
861 				arg = CalltipsSupport.Argument.templateValue(t.tokenRange);
862 		}
863 		else
864 		{
865 			if (gotValue && valueStart > start && valueStart <= end)
866 			{
867 				typename = tokens[start .. valueStart];
868 				auto val = tokens[valueStart .. end];
869 				if (val.length)
870 					arg.valueRange = [cast(int) val[0].index, val[$ - 1].tokenEnd];
871 			}
872 
873 			else if (typename.length == 1)
874 			{
875 				auto t = typename[0];
876 				if (t.type == tok!"identifier" || t.type.isBasicType)
877 					arg.typeRange = arg.nameRange = t.tokenRange;
878 				else
879 					arg.typeRange = t.tokenRange;
880 			}
881 			else if (typename.length)
882 			{
883 				if (typename[$ - 1].type == tok!"identifier")
884 				{
885 					arg.nameRange = typename[$ - 1].tokenRange;
886 					typename = typename[0 .. $ - 1];
887 				}
888 				arg.typeRange = [cast(int) typename[0].index, typename[$ - 1].tokenEnd];
889 			}
890 		}
891 
892 		ret.put(arg);
893 
894 		gotValue = false;
895 		start = end + 1;
896 	}
897 
898 	foreach (i, token; tokens)
899 	{
900 		if (token.type == tok!"{")
901 			subDepth++;
902 		else if (token.type == tok!"}")
903 		{
904 			if (subDepth == 0)
905 				break;
906 			subDepth--;
907 		}
908 		else if (token.type == tok!"(" || token.type == tok!"[")
909 			depth++;
910 		else if (token.type == tok!")" || token.type == tok!"]")
911 		{
912 			if (depth == 0)
913 				break;
914 			depth--;
915 		}
916 
917 		if (token.type == tok!",")
918 			putArg(i);
919 		else if (token.type == tok!":" || token.type == tok!"=")
920 		{
921 			if (!gotValue)
922 			{
923 				valueStart = i + 1;
924 				gotValue = true;
925 			}
926 		}
927 	}
928 	putArg(tokens.length);
929 
930 	return ret.data;
931 }
932 
933 auto indent(scope const(char)[] code, string indentation)
934 {
935 	return code.lineSplitter!(KeepTerminator.yes)
936 		.map!(a => a.length ? indentation ~ a : a)
937 		.join;
938 }
939 
940 bool fieldNameMatches(string field, in char[] expected)
941 {
942 	import std.uni : sicmp;
943 
944 	if (field.startsWith("_"))
945 		field = field[1 .. $];
946 	else if (field.startsWith("m_"))
947 		field = field[2 .. $];
948 	else if (field.length >= 2 && field[0] == 'm' && field[1].isUpper)
949 		field = field[1 .. $];
950 
951 	return field.sicmp(expected) == 0;
952 }
953 
954 final class CodeBlockInfoFinder : ASTVisitor
955 {
956 	this(int targetPosition)
957 	{
958 		this.targetPosition = targetPosition;
959 	}
960 
961 	override void visit(const ClassDeclaration dec)
962 	{
963 		visitContainer(dec.name, CodeBlockInfo.Type.class_, dec.structBody);
964 	}
965 
966 	override void visit(const InterfaceDeclaration dec)
967 	{
968 		visitContainer(dec.name, CodeBlockInfo.Type.interface_, dec.structBody);
969 	}
970 
971 	override void visit(const StructDeclaration dec)
972 	{
973 		visitContainer(dec.name, CodeBlockInfo.Type.struct_, dec.structBody);
974 	}
975 
976 	override void visit(const UnionDeclaration dec)
977 	{
978 		visitContainer(dec.name, CodeBlockInfo.Type.union_, dec.structBody);
979 	}
980 
981 	override void visit(const TemplateDeclaration dec)
982 	{
983 		if (cast(int) targetPosition >= cast(int) dec.name.index && targetPosition < dec.endLocation)
984 		{
985 			block = CodeBlockInfo.init;
986 			block.type = CodeBlockInfo.Type.template_;
987 			block.name = dec.name.text;
988 			block.outerRange = [
989 				cast(uint) dec.name.index, cast(uint) dec.endLocation + 1
990 			];
991 			block.innerRange = [
992 				cast(uint) dec.startLocation + 1, cast(uint) dec.endLocation
993 			];
994 			dec.accept(this);
995 		}
996 	}
997 
998 	private void visitContainer(const Token name, CodeBlockInfo.Type type, const StructBody structBody)
999 	{
1000 		if (!structBody)
1001 			return;
1002 		if (cast(int) targetPosition >= cast(int) name.index && targetPosition < structBody.endLocation)
1003 		{
1004 			block = CodeBlockInfo.init;
1005 			block.type = type;
1006 			block.name = name.text;
1007 			block.outerRange = [
1008 				cast(uint) name.index, cast(uint) structBody.endLocation + 1
1009 			];
1010 			block.innerRange = [
1011 				cast(uint) structBody.startLocation + 1, cast(uint) structBody.endLocation
1012 			];
1013 			structBody.accept(this);
1014 		}
1015 	}
1016 
1017 	alias visit = ASTVisitor.visit;
1018 
1019 	CodeBlockInfo block;
1020 	int targetPosition;
1021 }
1022 
1023 version (unittest) static immutable string SimpleClassTestCode = q{
1024 module foo;
1025 
1026 class FooBar
1027 {
1028 public:
1029 	int i; // default instanced fields
1030 	string s;
1031 	long l;
1032 
1033 	public this() // public instanced ctor
1034 	{
1035 		i = 4;
1036 	}
1037 
1038 protected:
1039 	int x; // protected instanced field
1040 
1041 private:
1042 	static const int foo() @nogc nothrow pure @system // private static methods
1043 	{
1044 		if (s == "a")
1045 		{
1046 			i = 5;
1047 		}
1048 	}
1049 
1050 	static void bar1() {}
1051 
1052 	void bar2() {} // private instanced methods
1053 	void bar3() {}
1054 }};
1055 
1056 unittest
1057 {
1058 	scope backend = new WorkspaceD();
1059 	auto workspace = makeTemporaryTestingWorkspace;
1060 	auto instance = backend.addInstance(workspace.directory);
1061 	backend.register!DCDExtComponent;
1062 	DCDExtComponent dcdext = instance.get!DCDExtComponent;
1063 
1064 	assert(dcdext.getCodeBlockRange(SimpleClassTestCode, 123) == CodeBlockInfo(CodeBlockInfo.Type.class_,
1065 			"FooBar", [20, SimpleClassTestCode.length], [
1066 				28, SimpleClassTestCode.length - 1
1067 			]));
1068 	assert(dcdext.getCodeBlockRange(SimpleClassTestCode, 19) == CodeBlockInfo.init);
1069 	assert(dcdext.getCodeBlockRange(SimpleClassTestCode, 20) != CodeBlockInfo.init);
1070 
1071 	auto replacements = dcdext.insertCodeInContainer("void foo()\n{\n\twriteln();\n}",
1072 			SimpleClassTestCode, 123);
1073 	import std.stdio;
1074 
1075 	stderr.writeln(replacements);
1076 }
1077 
1078 unittest
1079 {
1080 	import std.conv;
1081 
1082 	scope backend = new WorkspaceD();
1083 	auto workspace = makeTemporaryTestingWorkspace;
1084 	auto instance = backend.addInstance(workspace.directory);
1085 	backend.register!DCDExtComponent;
1086 	DCDExtComponent dcdext = instance.get!DCDExtComponent;
1087 
1088 	import std.stdio;
1089 
1090 	auto extract = dcdext.extractCallParameters("int x; foo.bar(4, fgerg\n\nfoo(); int y;", 23);
1091 	assert(!extract.hasTemplate);
1092 	assert(extract.parentStart == 7);
1093 	assert(extract.functionStart == 11);
1094 	assert(extract.functionParensRange[0] == 14);
1095 	assert(extract.functionParensRange[1] <= 31);
1096 	assert(extract.functionArgs.length == 2);
1097 	assert(extract.functionArgs[0].contentRange == [15, 16]);
1098 	assert(extract.functionArgs[1].contentRange[0] == 18);
1099 	assert(extract.functionArgs[1].contentRange[1] <= 31);
1100 
1101 	extract = dcdext.extractCallParameters("int x; foo.bar(4, fgerg)\n\nfoo(); int y;", 23);
1102 	assert(!extract.hasTemplate);
1103 	assert(extract.parentStart == 7);
1104 	assert(extract.functionStart == 11);
1105 	assert(extract.functionParensRange == [14, 23]);
1106 	assert(extract.functionArgs.length == 2);
1107 	assert(extract.functionArgs[0].contentRange == [15, 16]);
1108 	assert(extract.functionArgs[1].contentRange == [18, 23]);
1109 }