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