public override async Task <TextEdit[]> DocumentOnTypeFormatting(DocumentOnTypeFormattingParams @params, CancellationToken cancellationToken) { int targetLine; // One-indexed line number switch (@params.ch) { case "\n": targetLine = @params.position.line; break; case ";": targetLine = @params.position.line + 1; break; default: throw new ArgumentException("unexpected trigger character", nameof(@params.ch)); } var uri = @params.textDocument.uri; if (!(ProjectFiles.GetEntry(uri) is IDocument doc)) { return(Array.Empty <TextEdit>()); } var part = ProjectFiles.GetPart(uri); using (var reader = doc.ReadDocument(part, out _)) { var lineFormatter = new LineFormatter(reader, Analyzer.LanguageVersion); return(lineFormatter.FormatLine(targetLine)); } }
public override async Task <CompletionList> Completion(CompletionParams @params, CancellationToken cancellationToken) { var uri = @params.textDocument.uri; ProjectFiles.GetEntry(@params.textDocument, @params._version, out var entry, out var tree); TraceMessage($"Completions in {uri} at {@params.position}"); tree = GetParseTree(entry, uri, cancellationToken, out var version) ?? tree; var analysis = entry != null ? await entry.GetAnalysisAsync(50, cancellationToken) : null; if (analysis == null) { TraceMessage($"No analysis found for {uri}"); return(new CompletionList()); } var opts = GetOptions(@params.context); var ctxt = new CompletionAnalysis(analysis, tree, @params.position, opts, Settings.completion, _displayTextBuilder, Logger, () => entry.ReadDocument(ProjectFiles.GetPart(uri), out _)); var members = string.IsNullOrEmpty(@params._expr) ? ctxt.GetCompletions() : ctxt.GetCompletionsFromString(@params._expr); if (members == null) { TraceMessage($"No completions at {@params.position} in {uri}"); return(new CompletionList()); } if (!Settings.completion.showAdvancedMembers) { members = members.Where(m => !m.label.StartsWith("__")); } var filterKind = @params.context?._filterKind; if (filterKind.HasValue && filterKind != CompletionItemKind.None) { TraceMessage($"Only returning {filterKind.Value} items"); members = members.Where(m => m.kind == filterKind.Value); } var res = new CompletionList { items = members.ToArray(), _expr = ctxt.ParentExpression?.ToCodeString(tree, CodeFormattingOptions.Traditional), _commitByDefault = ctxt.ShouldCommitByDefault, _allowSnippet = ctxt.ShouldAllowSnippets }; res._applicableSpan = GetApplicableSpan(ctxt, @params, tree); LogMessage(MessageType.Info, $"Found {res.items.Length} completions for {uri} at {@params.position} after filtering"); await InvokeExtensionsAsync((ext, token) => (ext as ICompletionExtension)?.HandleCompletionAsync(uri, analysis, tree, @params.position, res, cancellationToken), cancellationToken); return(res); }
public override async Task <DocumentSymbol[]> HierarchicalDocumentSymbol(DocumentSymbolParams @params, CancellationToken cancellationToken) { await WaitForCompleteAnalysisAsync(cancellationToken); var opts = GetMemberOptions.ExcludeBuiltins | GetMemberOptions.DeclaredOnly; var entry = ProjectFiles.GetEntry(@params.textDocument.uri); var members = await GetModuleVariablesAsync(entry as ProjectEntry, opts, string.Empty, 50, cancellationToken); return(ToDocumentSymbols(members)); }
private Dictionary <Uri, List <Reference> > FilterPrivatePrefixed( Dictionary <Uri, List <Reference> > refs, string originalName, string privatePrefix, string newName, Uri documentReaderUri, IDocumentReader documentReader) { // Filter out references depending on the private prefix, if any. foreach (var kvp in refs) { var ent = ProjectFiles.GetEntry(kvp.Key); var ast = (ent as ProjectEntry).GetCurrentParse().Tree; var reader = kvp.Key == documentReaderUri ? documentReader : new DocumentReader(ent as IDocument, ProjectFiles.GetPart(kvp.Key)); if (ast == null || reader == null) { throw new InvalidOperationException(Resources.RenameVariable_NoInformationAvailableForVariable.FormatUI(originalName)); } var fullName = $"{privatePrefix}{originalName}"; for (var i = 0; i < kvp.Value.Count; i++) { var reference = kvp.Value[i]; Debug.Assert(reference.range.start.line == reference.range.end.line); var actualName = reader.ReadRange(reference.range, ast); // If name does not match exactly, so we might be renaming a prefixed name if (string.IsNullOrEmpty(privatePrefix)) { // Not a mangled case, if names don't match, do not rename. if (actualName != fullName) { kvp.Value.RemoveAt(i); i--; } continue; // All good, rename. } // If renaming from private name to private name, rename the non-prefixed portion if (actualName.StartsWith(privatePrefix) && newName.StartsWith("__")) { reference.range.start.character = reference.range.start.character + privatePrefix.Length; } } } return(refs); }
public override async Task <SymbolInformation[]> DocumentSymbol(DocumentSymbolParams @params, CancellationToken cancellationToken) { await WaitForCompleteAnalysisAsync(cancellationToken); var opts = GetMemberOptions.ExcludeBuiltins | GetMemberOptions.DeclaredOnly; var entry = ProjectFiles.GetEntry(@params.textDocument.uri); var members = await GetModuleVariablesAsync(entry as ProjectEntry, opts, string.Empty, 50, cancellationToken); return(members .GroupBy(mr => mr.Name) .Select(g => g.First()) .Select(ToSymbolInformation) .ToArray()); }
public override async Task <TextEdit[]> DocumentOnTypeFormatting(DocumentOnTypeFormattingParams @params, CancellationToken cancellationToken) { int targetLine; switch (@params.ch) { case "\n": targetLine = @params.position.line - 1; break; case ";": case ":": targetLine = @params.position.line; break; default: throw new ArgumentException("unexpected trigger character", nameof(@params.ch)); } var uri = @params.textDocument.uri; if (!(ProjectFiles.GetEntry(uri) is IDocument doc)) { return(Array.Empty <TextEdit>()); } var part = ProjectFiles.GetPart(uri); using (var reader = doc.ReadDocument(part, out _)) { if (@params.ch == ":") { return(await BlockFormatter.ProvideEdits(reader, @params.position, @params.options)); } var lineFormatter = new LineFormatter(reader, Analyzer.LanguageVersion); var edits = lineFormatter.FormatLine(targetLine); var unmatchedToken = lineFormatter.UnmatchedToken(targetLine); if (unmatchedToken != null) { var message = Resources.LineFormatter_UnmatchedToken.FormatInvariant(unmatchedToken.Value.token, unmatchedToken.Value.line + 1); LogMessage(MessageType.Warning, message); } return(edits); } }
public override async Task <SignatureHelp> SignatureHelp(TextDocumentPositionParams @params, CancellationToken token) { var uri = @params.textDocument.uri; TraceMessage($"Signatures in {uri} at {@params.position}"); var analysis = GetEntry(@params.textDocument) is ProjectEntry entry ? await entry.GetAnalysisAsync(waitingTimeout : 50, cancellationToken : token) : null; if (analysis == null) { TraceMessage($"No analysis found for {uri}"); return(new SignatureHelp()); } ProjectFiles.GetEntry(@params.textDocument, @params._version, out _, out var tree); IEnumerable <IOverloadResult> overloads; int activeSignature = -1, activeParameter = -1; if (!string.IsNullOrEmpty(@params._expr)) { TraceMessage($"Getting signatures for {@params._expr}"); overloads = analysis.GetSignatures(@params._expr, @params.position); } else { var finder = new ExpressionFinder(tree, new GetExpressionOptions { Calls = true }); var index = tree.LocationToIndex(@params.position); if (finder.GetExpression(@params.position) is CallExpression callExpr) { TraceMessage($"Getting signatures for {callExpr.ToCodeString(tree, CodeFormattingOptions.Traditional)}"); overloads = analysis.GetSignatures(callExpr.Target, @params.position); activeParameter = -1; if (callExpr.GetArgumentAtIndex(tree, index, out activeParameter) && activeParameter < 0) { // Returned 'true' and activeParameter == -1 means that we are after // the trailing comma, so assume partially typed expression such as 'pow(x, y, |) activeParameter = callExpr.Args.Count; } } else { TraceMessage($"No signatures found in {uri} at {@params.position}"); return(new SignatureHelp()); } } var sigs = overloads.Select(ToSignatureInformation).ToArray(); if (activeParameter >= 0 && activeSignature < 0) { // TODO: Better selection of active signature activeSignature = sigs .Select((s, i) => Tuple.Create(s, i)) .OrderBy(t => t.Item1.parameters.Length) .FirstOrDefault(t => t.Item1.parameters.Length > activeParameter) ?.Item2 ?? -1; } activeSignature = activeSignature >= 0 ? activeSignature : (sigs.Length > 0 ? 0 : -1); return(new SignatureHelp { signatures = sigs, activeSignature = activeSignature, activeParameter = activeParameter }); }
public override async Task <WorkspaceEdit> Rename(RenameParams @params, CancellationToken cancellationToken) { ProjectFiles.GetEntry(@params.textDocument, @params._version, out var entry, out var tree); if (entry == null || tree == null) { throw new InvalidOperationException(Resources.RenameVariable_UnableGetExpressionAnalysis); } var references = await FindReferences(new ReferencesParams { textDocument = new TextDocumentIdentifier { uri = @params.textDocument.uri }, position = @params.position, context = new ReferenceContext { includeDeclaration = true } }, cancellationToken); if (references.Any(x => x._isModule)) { throw new InvalidOperationException(Resources.RenameVariable_CannotRenameModuleName); } var definition = references.FirstOrDefault(r => r._kind == ReferenceKind.Definition); if (definition == null) { throw new InvalidOperationException(Resources.RenameVariable_CannotRename); } var definitionSpan = definition.range.ToLinearSpan(tree); var reader = new DocumentReader(entry as IDocument, ProjectFiles.GetPart(definition.uri)); var originalName = reader.Read(definitionSpan.Start, definitionSpan.Length); if (originalName == null) { throw new InvalidOperationException(Resources.RenameVariable_SelectSymbol); } if (!references.Any(r => r._kind == ReferenceKind.Definition || r._kind == ReferenceKind.Reference)) { throw new InvalidOperationException(Resources.RenameVariable_NoInformationAvailableForVariable.FormatUI(originalName)); } // See https://en.wikipedia.org/wiki/Name_mangling, Python section. var privatePrefix = entry.Analysis.GetPrivatePrefix(definition.range.start); if (!string.IsNullOrEmpty(privatePrefix) && !string.IsNullOrEmpty(originalName) && originalName.StartsWithOrdinal(privatePrefix)) { originalName = originalName.Substring(privatePrefix.Length + 1); } // Group by URI for more optimal document reading in FilterPrivatePrefixed var grouped = references .GroupBy(x => x.uri) .ToDictionary(g => g.Key, e => e.ToList()); var refs = FilterPrivatePrefixed(grouped, originalName, privatePrefix, @params.newName, @params.textDocument.uri, reader); // Convert to Dictionary<Uri, TextEdit[]> var changes = refs .ToDictionary( kvp => kvp.Key, kvp => kvp.Value.Select(t => new TextEdit { range = t.range, newText = @params.newName }).ToArray()); return(new WorkspaceEdit { changes = changes }); }
public override async Task <CompletionList> Completion(CompletionParams @params, CancellationToken cancellationToken) { var uri = @params.textDocument.uri; ProjectFiles.GetEntry(@params.textDocument, @params._version, out var entry, out var tree); TraceMessage($"Completions in {uri} at {@params.position}"); tree = GetParseTree(entry, uri, cancellationToken, out var version) ?? tree; var analysis = entry != null ? await entry.GetAnalysisAsync(50, cancellationToken) : null; if (analysis == null) { TraceMessage($"No analysis found for {uri}"); return(new CompletionList()); } var opts = GetOptions(@params.context); var ctxt = new CompletionAnalysis(analysis, tree, @params.position, opts, _displayTextBuilder, this, () => entry.ReadDocument(ProjectFiles.GetPart(uri), out _)); var members = ctxt.GetCompletionsFromString(@params._expr) ?? ctxt.GetCompletions(); if (members == null) { TraceMessage($"Do not trigger at {@params.position} in {uri}"); return(new CompletionList()); } if (!Settings.completion.showAdvancedMembers) { members = members.Where(m => !m.label.StartsWith("__")); } var filterKind = @params.context?._filterKind; if (filterKind.HasValue && filterKind != CompletionItemKind.None) { TraceMessage($"Only returning {filterKind.Value} items"); members = members.Where(m => m.kind == filterKind.Value); } var completions = members.ToArray(); if (Settings.completion.addBrackets && (!filterKind.HasValue || CanHaveBrackets(filterKind.Value))) { foreach (var completionItem in completions.Where(ci => CanHaveBrackets(ci.kind))) { completionItem.insertText += "($0)"; completionItem.insertTextFormat = InsertTextFormat.Snippet; } } bool CanHaveBrackets(CompletionItemKind kind) => kind == CompletionItemKind.Constructor || kind == CompletionItemKind.Function || kind == CompletionItemKind.Method; var res = new CompletionList { items = completions, _expr = ctxt.ParentExpression?.ToCodeString(tree, CodeFormattingOptions.Traditional), _commitByDefault = ctxt.ShouldCommitByDefault, _allowSnippet = ctxt.ShouldAllowSnippets }; SourceLocation trigger = @params.position; if (ctxt.ApplicableSpan.HasValue) { res._applicableSpan = ctxt.ApplicableSpan; } else if (ctxt.Node != null) { var span = ctxt.Node.GetSpan(tree); if (@params.context?.triggerKind == CompletionTriggerKind.TriggerCharacter) { if (span.End > trigger) { span = new SourceSpan(span.Start, trigger); } } if (span.End != span.Start) { res._applicableSpan = span; } } else if (@params.context?.triggerKind == CompletionTriggerKind.TriggerCharacter) { var ch = @params.context?.triggerCharacter.FirstOrDefault() ?? '\0'; res._applicableSpan = new SourceSpan( trigger.Line, Tokenizer.IsIdentifierStartChar(ch) ? Math.Max(1, trigger.Column - 1) : trigger.Column, trigger.Line, trigger.Column ); } LogMessage(MessageType.Info, $"Found {res.items.Length} completions for {uri} at {@params.position} after filtering"); if (HandleOldStyleCompletionExtension(analysis as ModuleAnalysis, tree, @params.position, res)) { return(res); } await InvokeExtensionsAsync((ext, token) => (ext as ICompletionExtension)?.HandleCompletionAsync(uri, analysis, tree, @params.position, res, cancellationToken), cancellationToken); return(res); }
public override async Task <Reference[]> FindReferences(ReferencesParams @params, CancellationToken cancellationToken) { await WaitForCompleteAnalysisAsync(cancellationToken); var uri = @params.textDocument.uri; ProjectFiles.GetEntry(@params.textDocument, @params._version, out var entry, out var tree); TraceMessage($"References in {uri} at {@params.position}"); var analysis = entry != null ? await entry.GetAnalysisAsync(50, cancellationToken) : null; if (analysis == null) { TraceMessage($"No analysis found for {uri}"); return(Array.Empty <Reference>()); } tree = GetParseTree(entry, uri, cancellationToken, out var version); var modRefs = GetModuleReferences(entry, tree, version, @params); IEnumerable <IAnalysisVariable> result; if (!string.IsNullOrEmpty(@params._expr)) { TraceMessage($"Getting references for {@params._expr}"); result = analysis.GetVariables(@params._expr, @params.position); } else { var finder = new ExpressionFinder(tree, GetExpressionOptions.FindDefinition); if (finder.GetExpression(@params.position) is Expression expr) { TraceMessage($"Getting references for {expr.ToCodeString(tree, CodeFormattingOptions.Traditional)}"); result = analysis.GetVariables(expr, @params.position); } else { TraceMessage($"No references found in {uri} at {@params.position}"); result = Enumerable.Empty <IAnalysisVariable>(); } } var filtered = result.Where(v => v.Type != VariableType.None); if (!(@params.context?.includeDeclaration ?? false)) { filtered = filtered.Where(v => v.Type != VariableType.Definition); } if (!(@params.context?._includeValues ?? false)) { filtered = filtered.Where(v => v.Type != VariableType.Value); } var res = filtered.Select(v => new Reference { uri = v.Location.DocumentUri, range = v.Location.Span, _kind = ToReferenceKind(v.Type), _version = version?.Version }) .Concat(modRefs) .GroupBy(r => r, ReferenceComparer.Instance) .Select(g => g.OrderByDescending(r => (SourceLocation)r.range.end).ThenBy(r => (int?)r._kind ?? int.MaxValue).First()) .ToArray(); return(res); }
public override async Task <Hover> Hover(TextDocumentPositionParams @params, CancellationToken cancellationToken) { var uri = @params.textDocument.uri; ProjectFiles.GetEntry(@params.textDocument, @params._version, out var entry, out var tree); TraceMessage($"Hover in {uri} at {@params.position}"); var analysis = entry != null ? await entry.GetAnalysisAsync(50, cancellationToken) : null; if (analysis == null) { TraceMessage($"No analysis found for {uri}"); return(EmptyHover); } tree = GetParseTree(entry, uri, cancellationToken, out var version) ?? tree; Expression expr; SourceSpan?exprSpan; var finder = new ExpressionFinder(tree, GetExpressionOptions.Hover); expr = finder.GetExpression(@params.position) as Expression; exprSpan = expr?.GetSpan(tree); if (expr == null) { TraceMessage($"No hover info found in {uri} at {@params.position}"); return(EmptyHover); } TraceMessage($"Getting hover for {expr.ToCodeString(tree, CodeFormattingOptions.Traditional)}"); var hover = await GetSelfHoverAsync(expr, analysis, tree, @params.position, cancellationToken); if (hover != null && hover != EmptyHover) { return(hover); } // First try values from expression. This works for the import statement most of the time. var values = analysis.GetValues(expr, @params.position, null).ToList(); if (values.Count == 0) { values = GetImportHover(entry, analysis, tree, @params.position, out hover).ToList(); if (hover != null) { return(hover); } } if (values.Count > 0) { string originalExpr; if (expr is ConstantExpression || expr is ErrorExpression) { originalExpr = null; } else { originalExpr = @params._expr?.Trim(); if (string.IsNullOrEmpty(originalExpr)) { originalExpr = expr.ToCodeString(tree, CodeFormattingOptions.Traditional); } } var names = values.Select(GetFullTypeName).Where(n => !string.IsNullOrEmpty(n)).Distinct().ToArray(); var res = new Hover { contents = GetMarkupContent( _displayTextBuilder.GetDocumentation(values, originalExpr), _clientCaps.textDocument?.hover?.contentFormat), range = exprSpan, _version = version?.Version, _typeNames = names }; return(res); } return(EmptyHover); }
public override async Task <Hover> Hover(TextDocumentPositionParams @params, CancellationToken cancellationToken) { var uri = @params.textDocument.uri; ProjectFiles.GetEntry(@params.textDocument, @params._version, out var entry, out var tree); TraceMessage($"Hover in {uri} at {@params.position}"); var analysis = entry != null ? await entry.GetAnalysisAsync(50, cancellationToken) : null; if (analysis == null) { TraceMessage($"No analysis found for {uri}"); return(EmptyHover); } tree = GetParseTree(entry, uri, cancellationToken, out var version) ?? tree; Expression expr; SourceSpan?exprSpan; var finder = new ExpressionFinder(tree, GetExpressionOptions.Hover); expr = finder.GetExpression(@params.position) as Expression; exprSpan = expr?.GetSpan(tree); if (expr == null) { TraceMessage($"No hover info found in {uri} at {@params.position}"); return(EmptyHover); } TraceMessage($"Getting hover for {expr.ToCodeString(tree, CodeFormattingOptions.Traditional)}"); // First try values from expression. This works for the import statement most of the time. var values = analysis.GetValues(expr, @params.position, null).ToList(); if (values.Count == 0) { // See if this is hover over import statement var index = tree.LocationToIndex(@params.position); var w = new ImportedModuleNameWalker(entry, index, tree); tree.Walk(w); if (w.ImportedType != null) { values = analysis.GetValues(w.ImportedType.Name, @params.position).ToList(); } else { var sb = new StringBuilder(); var span = SourceSpan.Invalid; foreach (var n in w.ImportedModules) { if (Analyzer.Modules.TryGetImportedModule(n.Name, out var modRef) && modRef.AnalysisModule != null) { if (sb.Length > 0) { sb.AppendLine(); sb.AppendLine(); } sb.Append(_displayTextBuilder.GetModuleDocumentation(modRef)); span = span.IsValid ? span.Union(n.SourceSpan) : n.SourceSpan; } } if (sb.Length > 0) { return(new Hover { contents = sb.ToString(), range = span }); } } } if (values.Count > 0) { string originalExpr; if (expr is ConstantExpression || expr is ErrorExpression) { originalExpr = null; } else { originalExpr = @params._expr?.Trim(); if (string.IsNullOrEmpty(originalExpr)) { originalExpr = expr.ToCodeString(tree, CodeFormattingOptions.Traditional); } } var names = values.Select(GetFullTypeName).Where(n => !string.IsNullOrEmpty(n)).Distinct().ToArray(); var res = new Hover { contents = GetMarkupContent( _displayTextBuilder.GetDocumentation(values, originalExpr), _clientCaps.textDocument?.hover?.contentFormat), range = exprSpan, _version = version?.Version, _typeNames = names }; return(res); } return(EmptyHover); }