예제 #1
0
        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));
        }
예제 #4
0
        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);
            }
        }
예제 #7
0
        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
            });
        }
예제 #8
0
        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
            });
        }
예제 #9
0
        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);
        }
예제 #12
0
        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);
        }