From c5ac6d490716d2fa5653e6748d24f6bab17be301 Mon Sep 17 00:00:00 2001 From: baronfel Date: Wed, 8 Sep 2021 11:28:16 -0500 Subject: [PATCH 1/4] initial stab at completion context --- global.json | 2 +- .../ParseAndCheckResults.fs | 133 +++++++++--------- src/FsAutoComplete.Core/UntypedAstUtils.fs | 29 ++++ 3 files changed, 100 insertions(+), 64 deletions(-) diff --git a/global.json b/global.json index b51cac80d..a709eaa81 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "5.0.301", + "version": "5.0.100", "rollForward": "major" } } diff --git a/src/FsAutoComplete.Core/ParseAndCheckResults.fs b/src/FsAutoComplete.Core/ParseAndCheckResults.fs index 4eeb62469..125eca945 100644 --- a/src/FsAutoComplete.Core/ParseAndCheckResults.fs +++ b/src/FsAutoComplete.Core/ParseAndCheckResults.fs @@ -7,6 +7,7 @@ open Utils open FSharp.Compiler.Text open FSharp.Compiler open FsAutoComplete.Logging +open FsAutoComplete.UntypedAstUtils open FSharp.UMX [] @@ -424,72 +425,78 @@ type ParseAndCheckResults | None -> ResultOrString.Error "No symbol information found" | Some hlp -> Ok hlp - member __.TryGetCompletions (pos: Pos) (lineStr: LineStr) filter (getAllSymbols : unit -> AssemblySymbol list) = async { - try - let longName = FSharp.Compiler.SourceCodeServices.QuickParse.GetPartialLongNameEx(lineStr, pos.Column - 1) - let residue = longName.PartialIdent - logger.info (Log.setMessage "TryGetCompletions - long name: {longName}" >> Log.addContextDestructured "longName" longName) - - let getAllSymbols() = - getAllSymbols() - |> List.filter (fun entity -> entity.FullName.Contains "." && not (PrettyNaming.IsOperatorName entity.Symbol.DisplayName)) - - let token = Lexer.getSymbol pos.Line (pos.Column - 1) lineStr SymbolLookupKind.Simple [||] - logger.info (Log.setMessage "TryGetCompletions - token: {token}" >> Log.addContextDestructured "token" token) - let isEmpty = longName.QualifyingIdents.IsEmpty && String.IsNullOrWhiteSpace longName.PartialIdent && longName.LastDotPos.IsNone - - match token with - | Some k when k.Kind = Other && not isEmpty -> return None - | Some k when k.Kind = Operator -> return None - | Some k when k.Kind = Keyword -> return None - | _ -> + member x.TryGetCompletions (pos: Pos) (lineStr: LineStr) filter (getAllSymbols : unit -> AssemblySymbol list) = async { + match x.GetParseResults.ParseTree with + | None -> return None + | Some parsedInput -> + match Completion.atPos (pos, parsedInput) with + | Completion.Context.StringLiteral -> return None + | Completion.Context.Unknown -> + try + let longName = FSharp.Compiler.SourceCodeServices.QuickParse.GetPartialLongNameEx(lineStr, pos.Column - 1) + let residue = longName.PartialIdent + logger.info (Log.setMessage "TryGetCompletions - long name: {longName}" >> Log.addContextDestructured "longName" longName) + + let getAllSymbols() = + getAllSymbols() + |> List.filter (fun entity -> entity.FullName.Contains "." && not (PrettyNaming.IsOperatorName entity.Symbol.DisplayName)) + + let token = Lexer.getSymbol pos.Line (pos.Column - 1) lineStr SymbolLookupKind.Simple [||] + logger.info (Log.setMessage "TryGetCompletions - token: {token}" >> Log.addContextDestructured "token" token) + let isEmpty = longName.QualifyingIdents.IsEmpty && String.IsNullOrWhiteSpace longName.PartialIdent && longName.LastDotPos.IsNone + + match token with + | Some k when k.Kind = Other && not isEmpty -> return None + | Some k when k.Kind = Operator -> return None + | Some k when k.Kind = Keyword -> return None + | _ -> - let results = checkResults.GetDeclarationListInfo(Some parseResults, pos.Line, lineStr, longName, getAllSymbols) - - let getKindPriority = function - | FSharpCompletionItemKind.CustomOperation -> -1 - | FSharpCompletionItemKind.Property -> 0 - | FSharpCompletionItemKind.Field -> 1 - | FSharpCompletionItemKind.Method (isExtension = false) -> 2 - | FSharpCompletionItemKind.Event -> 3 - | FSharpCompletionItemKind.Argument -> 4 - | FSharpCompletionItemKind.Other -> 5 - | FSharpCompletionItemKind.Method (isExtension = true) -> 6 - - let decls = - match filter with - | Some "StartsWith" -> - results.Items - |> Array.filter (fun d -> d.Name.StartsWith(residue, StringComparison.InvariantCultureIgnoreCase)) - | Some "Contains" -> - results.Items - |> Array.filter (fun d -> d.Name.IndexOf(residue, StringComparison.InvariantCultureIgnoreCase) >= 0) - | _ -> results.Items - - let sortedDecls = - decls - |> Array.sortWith (fun x y -> - let transformKind (item: FSharpDeclarationListItem) = - if item.Kind = FSharpCompletionItemKind.Field && item.Glyph = FSharpGlyph.Method then - FSharpCompletionItemKind.Method false - elif item.Kind = FSharpCompletionItemKind.Argument && item.Glyph = FSharpGlyph.Property then - FSharpCompletionItemKind.Property - else - item.Kind - - let mutable n = (not x.IsResolved).CompareTo(not y.IsResolved) - if n <> 0 then n else - n <- (getKindPriority <| transformKind x).CompareTo(getKindPriority <| transformKind y) + let results = checkResults.GetDeclarationListInfo(Some parseResults, pos.Line, lineStr, longName, getAllSymbols) + + let getKindPriority = function + | FSharpCompletionItemKind.CustomOperation -> -1 + | FSharpCompletionItemKind.Property -> 0 + | FSharpCompletionItemKind.Field -> 1 + | FSharpCompletionItemKind.Method (isExtension = false) -> 2 + | FSharpCompletionItemKind.Event -> 3 + | FSharpCompletionItemKind.Argument -> 4 + | FSharpCompletionItemKind.Other -> 5 + | FSharpCompletionItemKind.Method (isExtension = true) -> 6 + + let decls = + match filter with + | Some "StartsWith" -> + results.Items + |> Array.filter (fun d -> d.Name.StartsWith(residue, StringComparison.InvariantCultureIgnoreCase)) + | Some "Contains" -> + results.Items + |> Array.filter (fun d -> d.Name.IndexOf(residue, StringComparison.InvariantCultureIgnoreCase) >= 0) + | _ -> results.Items + + let sortedDecls = + decls + |> Array.sortWith (fun x y -> + let transformKind (item: FSharpDeclarationListItem) = + if item.Kind = FSharpCompletionItemKind.Field && item.Glyph = FSharpGlyph.Method then + FSharpCompletionItemKind.Method false + elif item.Kind = FSharpCompletionItemKind.Argument && item.Glyph = FSharpGlyph.Property then + FSharpCompletionItemKind.Property + else + item.Kind + + let mutable n = (not x.IsResolved).CompareTo(not y.IsResolved) if n <> 0 then n else - n <- (not x.IsOwnMember).CompareTo(not y.IsOwnMember) + n <- (getKindPriority <| transformKind x).CompareTo(getKindPriority <| transformKind y) if n <> 0 then n else - n <- StringComparer.OrdinalIgnoreCase.Compare(x.Name, y.Name) + n <- (not x.IsOwnMember).CompareTo(not y.IsOwnMember) if n <> 0 then n else - x.MinorPriority.CompareTo(y.MinorPriority)) + n <- StringComparer.OrdinalIgnoreCase.Compare(x.Name, y.Name) + if n <> 0 then n else + x.MinorPriority.CompareTo(y.MinorPriority)) - let shouldKeywords = sortedDecls.Length > 0 && not results.IsForType && not results.IsError && List.isEmpty longName.QualifyingIdents - return Some (sortedDecls, residue, shouldKeywords) - with :? TimeoutException -> return None + let shouldKeywords = sortedDecls.Length > 0 && not results.IsForType && not results.IsError && List.isEmpty longName.QualifyingIdents + return Some (sortedDecls, residue, shouldKeywords) + with :? TimeoutException -> return None } member __.GetAllEntities (publicOnly: bool) : AssemblySymbol list = @@ -526,6 +533,6 @@ type ParseAndCheckResults member __.GetAST = parseResults.ParseTree - member __.GetCheckResults = checkResults - member __.GetParseResults = parseResults + member __.GetCheckResults: FSharpCheckFileResults = checkResults + member __.GetParseResults: FSharpParseFileResults = parseResults member __.FileName: string = UMX.tag parseResults.FileName diff --git a/src/FsAutoComplete.Core/UntypedAstUtils.fs b/src/FsAutoComplete.Core/UntypedAstUtils.fs index 1208d4f7f..cd2333496 100644 --- a/src/FsAutoComplete.Core/UntypedAstUtils.fs +++ b/src/FsAutoComplete.Core/UntypedAstUtils.fs @@ -1882,3 +1882,32 @@ module Printf = | _ -> () //debug "%A" idents result.ToArray() + +module Completion = + + [] + type Context = + | StringLiteral + | Unknown + + let atPos (pos: Pos, ast: ParsedInput): Context = + let visitor = + { new SourceCodeServices.AstTraversal.AstVisitorBase() with + + member x.VisitExpr(path, traverseExpr, defaultTraverse, expr): Context option = + match expr with + | SynExpr.InterpolatedString (parts, m) -> + if Range.rangeContainsPos m pos + then + parts + |> List.tryPick ( + function | SynInterpolatedStringPart.String(s, m) when Range.rangeContainsPos m pos -> Some Context.StringLiteral + | SynInterpolatedStringPart.String _ -> None + | SynInterpolatedStringPart.FillExpr(e, _) when Range.rangeContainsPos e.Range pos -> traverseExpr e + | SynInterpolatedStringPart.FillExpr _ -> None + ) + else None + | _ -> None + } + FSharp.Compiler.SourceCodeServices.AstTraversal.Traverse(pos, ast, visitor) + |> Option.defaultValue Context.Unknown From 00a658e144987889d7276b4ca07c15558aa3fefb Mon Sep 17 00:00:00 2001 From: baronfel Date: Wed, 8 Sep 2021 11:44:14 -0500 Subject: [PATCH 2/4] tweak context-aware completions --- src/FsAutoComplete.Core/UntypedAstUtils.fs | 27 +++++++++++----------- src/FsAutoComplete/FsAutoComplete.Lsp.fs | 2 +- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/FsAutoComplete.Core/UntypedAstUtils.fs b/src/FsAutoComplete.Core/UntypedAstUtils.fs index cd2333496..1509236e1 100644 --- a/src/FsAutoComplete.Core/UntypedAstUtils.fs +++ b/src/FsAutoComplete.Core/UntypedAstUtils.fs @@ -1895,19 +1895,20 @@ module Completion = { new SourceCodeServices.AstTraversal.AstVisitorBase() with member x.VisitExpr(path, traverseExpr, defaultTraverse, expr): Context option = - match expr with - | SynExpr.InterpolatedString (parts, m) -> - if Range.rangeContainsPos m pos - then - parts - |> List.tryPick ( - function | SynInterpolatedStringPart.String(s, m) when Range.rangeContainsPos m pos -> Some Context.StringLiteral - | SynInterpolatedStringPart.String _ -> None - | SynInterpolatedStringPart.FillExpr(e, _) when Range.rangeContainsPos e.Range pos -> traverseExpr e - | SynInterpolatedStringPart.FillExpr _ -> None - ) - else None - | _ -> None + if Range.rangeContainsPos expr.Range pos + then + match expr with + | SynExpr.Const(SynConst.String _, _) -> Some Context.StringLiteral + | SynExpr.InterpolatedString (parts, _) -> + parts + |> List.tryPick ( + function | SynInterpolatedStringPart.String(s, m) when Range.rangeContainsPos m pos -> Some Context.StringLiteral + | SynInterpolatedStringPart.String _ -> None + | SynInterpolatedStringPart.FillExpr(e, _) when Range.rangeContainsPos e.Range pos -> defaultTraverse e // gotta dive into the expr to see if we're in a literal inside the expr + | SynInterpolatedStringPart.FillExpr _ -> None + ) + | _ -> defaultTraverse expr + else None } FSharp.Compiler.SourceCodeServices.AstTraversal.Traverse(pos, ast, visitor) |> Option.defaultValue Context.Unknown diff --git a/src/FsAutoComplete/FsAutoComplete.Lsp.fs b/src/FsAutoComplete/FsAutoComplete.Lsp.fs index ca5197a23..2d781f9af 100644 --- a/src/FsAutoComplete/FsAutoComplete.Lsp.fs +++ b/src/FsAutoComplete/FsAutoComplete.Lsp.fs @@ -872,7 +872,7 @@ type FSharpLspServer(backgroundServiceEnabled: bool, state: State, lspClient: FS return! success (Some completionList) | _ -> logger.info (Log.setMessage "TextDocumentCompletion - no completion results") - return! success (Some { IsIncomplete = true; Items = [||] }) + return! success (Some { IsIncomplete = false; Items = [||] }) } override __.CompletionItemResolve(ci: CompletionItem) = async { From ec3b49e3d3d223023b7ac273dbc4b1380de8bdd7 Mon Sep 17 00:00:00 2001 From: baronfel Date: Wed, 8 Sep 2021 12:16:19 -0500 Subject: [PATCH 3/4] also skip hover calculation as well --- .../ParseAndCheckResults.fs | 64 ++++++++++--------- src/FsAutoComplete/FsAutoComplete.Lsp.fs | 4 +- 2 files changed, 38 insertions(+), 30 deletions(-) diff --git a/src/FsAutoComplete.Core/ParseAndCheckResults.fs b/src/FsAutoComplete.Core/ParseAndCheckResults.fs index 125eca945..a56c7a93e 100644 --- a/src/FsAutoComplete.Core/ParseAndCheckResults.fs +++ b/src/FsAutoComplete.Core/ParseAndCheckResults.fs @@ -234,38 +234,44 @@ type ParseAndCheckResults | _ -> Ok(tip) - member __.TryGetToolTipEnhanced (pos: Pos) (lineStr: LineStr) = - match Lexer.findLongIdents(pos.Column, lineStr) with - | None -> Error "Cannot find ident for tooltip" - | Some(col,identIsland) -> - let identIsland = Array.toList identIsland - // TODO: Display other tooltip types, for example for strings or comments where appropriate - let tip = checkResults.GetToolTipText(pos.Line, col, lineStr, identIsland, FSharpTokenTag.Identifier) - let symbol = checkResults.GetSymbolUseAtLocation(pos.Line, col, lineStr, identIsland) - - match tip with - | FSharpToolTipText(elems) when elems |> List.forall ((=) FSharpToolTipElement.None) && symbol.IsNone -> - match identIsland with - | [ident] -> - match KeywordList.keywordTooltips.TryGetValue ident with - | true, tip -> - Ok (tip, ident, "", None) - | _ -> + member x.TryGetToolTipEnhanced (pos: Pos) (lineStr: LineStr) = + match x.GetParseResults.ParseTree with + | None -> Error "No parse tree" + | Some parsedInput -> + match Completion.atPos (pos, parsedInput) with + | Completion.Context.StringLiteral -> Ok None + | Completion.Context.Unknown -> + match Lexer.findLongIdents(pos.Column, lineStr) with + | None -> Error "Cannot find ident for tooltip" + | Some(col,identIsland) -> + let identIsland = Array.toList identIsland + // TODO: Display other tooltip types, for example for strings or comments where appropriate + let tip = checkResults.GetToolTipText(pos.Line, col, lineStr, identIsland, FSharpTokenTag.Identifier) + let symbol = checkResults.GetSymbolUseAtLocation(pos.Line, col, lineStr, identIsland) + + match tip with + | FSharpToolTipText(elems) when elems |> List.forall ((=) FSharpToolTipElement.None) && symbol.IsNone -> + match identIsland with + | [ident] -> + match KeywordList.keywordTooltips.TryGetValue ident with + | true, tip -> + Ok (Some (tip, ident, "", None)) + | _ -> + Error "No tooltip information" + | _ -> Error "No tooltip information" | _ -> - Error "No tooltip information" - | _ -> - match symbol with - | None -> - Error "No tooltip information" - | Some symbol -> + match symbol with + | None -> + Error "No tooltip information" + | Some symbol -> - match SignatureFormatter.getTooltipDetailsFromSymbolUse symbol with - | None -> - Error "No tooltip information" - | Some (signature, footer) -> - let typeDoc = getTypeIfConstructor symbol.Symbol |> Option.map (fun n -> n.XmlDocSig) - Ok (tip, signature, footer, typeDoc) + match SignatureFormatter.getTooltipDetailsFromSymbolUse symbol with + | None -> + Error "No tooltip information" + | Some (signature, footer) -> + let typeDoc = getTypeIfConstructor symbol.Symbol |> Option.map (fun n -> n.XmlDocSig) + Ok (Some (tip, signature, footer, typeDoc)) member __.TryGetFormattedDocumentation (pos: Pos) (lineStr: LineStr) = match Lexer.findLongIdents(pos.Column - 1, lineStr) with diff --git a/src/FsAutoComplete/FsAutoComplete.Lsp.fs b/src/FsAutoComplete/FsAutoComplete.Lsp.fs index 2d781f9af..412456f7a 100644 --- a/src/FsAutoComplete/FsAutoComplete.Lsp.fs +++ b/src/FsAutoComplete/FsAutoComplete.Lsp.fs @@ -950,7 +950,9 @@ type FSharpLspServer(backgroundServiceEnabled: bool, state: State, lspClient: FS | CoreResponse.InfoRes msg | CoreResponse.ErrorRes msg -> LspResult.internalError msg |> async.Return - | CoreResponse.Res(tip, signature, footer, typeDoc) -> + | CoreResponse.Res None -> + async.Return (success None) + | CoreResponse.Res(Some(tip, signature, footer, typeDoc)) -> let formatCommentStyle = if config.TooltipMode = "full" then TipFormatter.FormatCommentStyle.FullEnhanced From c4e5af8aec97990faca3a684ef6d1c924b71a6fa Mon Sep 17 00:00:00 2001 From: baronfel Date: Thu, 9 Sep 2021 12:38:37 -0500 Subject: [PATCH 4/4] revert accidental downgrade of global.json --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index a709eaa81..b51cac80d 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "5.0.100", + "version": "5.0.301", "rollForward": "major" } }