From 3c654ae6bd2569a1c61b9f6bcf7b278678cae5ab Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Wed, 27 Apr 2022 09:19:02 -0500 Subject: [PATCH] filter out hints that don't have meaningful names, or that match their user-provided input texts --- src/FsAutoComplete.Core/InlayHints.fs | 86 +++++++++++++++++++----- src/FsAutoComplete/FsAutoComplete.Lsp.fs | 8 ++- src/FsAutoComplete/LspHelpers.fs | 6 +- 3 files changed, 77 insertions(+), 23 deletions(-) diff --git a/src/FsAutoComplete.Core/InlayHints.fs b/src/FsAutoComplete.Core/InlayHints.fs index 8448a0816..39f868958 100644 --- a/src/FsAutoComplete.Core/InlayHints.fs +++ b/src/FsAutoComplete.Core/InlayHints.fs @@ -18,6 +18,7 @@ type HintKind = type Hint = { Text: string + InsertText: string option Pos: Position Kind: HintKind } @@ -90,14 +91,59 @@ type FSharp.Compiler.CodeAnalysis.FSharpParseFileResults with let result = SyntaxTraversal.Traverse(pos, x.ParseTree, visitor) result.IsSome -let getFirstPositionAfterParen (str: string) startPos = +let private getFirstPositionAfterParen (str: string) startPos = match str with | null -> -1 | str when startPos > str.Length -> -1 | str -> str.IndexOf('(') + 1 +let private maxHintLength = 30 + +let inline private truncated (s: string) = + if s.Length > maxHintLength then + s.Substring(0, maxHintLength) + "..." + else + s + +let private isNotWellKnownName = + let names = Set.ofList [ + "mapping" + "format" + "value" + "x" + ] + + fun (p: FSharpParameter) -> + match p.Name with + | None -> true + | Some n -> not (Set.contains n names) + +let inline hasName (p: FSharpParameter) = + not (String.IsNullOrEmpty p.DisplayName) + && p.DisplayName <> "````" + +let inline isMeaningfulName (p: FSharpParameter) = + p.DisplayName.Length > 2 + +let inline doesNotMatchArgumentText (parameterName: string) (userArgumentText: string) = + parameterName <> userArgumentText + && not (userArgumentText.StartsWith parameterName) + +/// +/// We filter out parameters that generate lots of noise in hints. +/// * parameter has a name +/// * parameter is one of a set of 'known' names that clutter (like printfn formats) +/// * parameter has length > 2 +/// * parameter does not match (or is an extension of) the user-entered text +/// +let shouldCreateHint (p: FSharpParameter) (matchingArgumentText: string) = + hasName p + && isNotWellKnownName p + && isMeaningfulName p + && doesNotMatchArgumentText p.DisplayName matchingArgumentText + let provideHints (text: NamedText, p: ParseAndCheckResults, range: Range) : Async = - async { + asyncResult { let parseFileResults, checkFileResults = p.GetParseResults, p.GetCheckResults let! cancellationToken = Async.CancellationToken @@ -125,19 +171,18 @@ let provideHints (text: NamedText, p: ParseAndCheckResults, range: Range) : Asyn && not funcOrValue.IsConstructorThisValue && not (PrettyNaming.IsOperatorDisplayName funcOrValue.DisplayName) - - for symbolUse in symbolUses do match symbolUse.Symbol with | :? FSharpMemberOrFunctionOrValue as funcOrValue when isValidForTypeHint funcOrValue symbolUse -> - let layout = - ": " - + funcOrValue.ReturnParameter.Type.Format symbolUse.DisplayContext + + let layout = $": {truncated(funcOrValue.ReturnParameter.Type.Format symbolUse.DisplayContext)}" + let insertText = $": {funcOrValue.ReturnParameter.Type.Format symbolUse.DisplayContext}" let hint = { Text = layout + InsertText = Some insertText Pos = symbolUse.Range.End Kind = Type } @@ -163,14 +208,16 @@ let provideHints (text: NamedText, p: ParseAndCheckResults, range: Range) : Asyn else for idx = 0 to minLength - 1 do let appliedArgRange = appliedArgRanges.[idx] - let definitionArgName = definitionArgs.[idx].DisplayName + let! appliedArgText = text[appliedArgRange] + let definitionArg = definitionArgs.[idx] + let definitionArgName = definitionArg.DisplayName if - not (String.IsNullOrWhiteSpace(definitionArgName)) - && definitionArgName <> "````" + shouldCreateHint definitionArg appliedArgText then let hint = - { Text = definitionArgName + " =" + { Text = $"{truncated definitionArgName} =" + InsertText = None Pos = appliedArgRange.Start Kind = Parameter } @@ -201,9 +248,11 @@ let provideHints (text: NamedText, p: ParseAndCheckResults, range: Range) : Asyn |> Array.ofSeq // TODO: need ArgumentLocations to be surfaced for idx = 0 to parameters.Length - 1 do - // let paramLocationInfo = tupledParamInfos. .ArgumentLocations.[idx] - // let paramName = parameters.[idx].DisplayName - // if not paramLocationInfo.IsNamedArgument && not (String.IsNullOrWhiteSpace(paramName)) then + // let paramLocationInfo = tupledParamInfos.ArgumentLocations.[idx] + let param = parameters.[idx] + let paramName = param.DisplayName + + // if shouldCreateHint param && paramLocationInfo.IsNamedArgument then // let hint = { Text = paramName + " ="; Pos = paramLocationInfo.ArgumentRange.Start; Kind = Parameter } // parameterHints.Add(hint) () @@ -219,11 +268,13 @@ let provideHints (text: NamedText, p: ParseAndCheckResults, range: Range) : Asyn for idx = 0 to appliedArgRanges.Length - 1 do let appliedArgRange = appliedArgRanges.[idx] - let definitionArgName = definitionArgs.[idx].DisplayName + let! appliedArgText = text[appliedArgRange] + let definitionArg = definitionArgs.[idx] - if not (String.IsNullOrWhiteSpace(definitionArgName)) then + if shouldCreateHint definitionArg appliedArgText then let hint = - { Text = definitionArgName + " =" + { Text = $"{truncated definitionArg.DisplayName} =" + InsertText = None Pos = appliedArgRange.Start Kind = Parameter } @@ -235,3 +286,4 @@ let provideHints (text: NamedText, p: ParseAndCheckResults, range: Range) : Asyn return typeHints.AddRange(parameterHints).ToArray() } + |> AsyncResult.foldResult id (fun _ -> [||]) diff --git a/src/FsAutoComplete/FsAutoComplete.Lsp.fs b/src/FsAutoComplete/FsAutoComplete.Lsp.fs index 3c34b6a0d..b7cc982bb 100644 --- a/src/FsAutoComplete/FsAutoComplete.Lsp.fs +++ b/src/FsAutoComplete/FsAutoComplete.Lsp.fs @@ -49,6 +49,7 @@ type InlayHintKind = Type | Parameter type LSPInlayHint = { Text : string + InsertText: string option Pos : Types.Position Kind : InlayHintKind } @@ -844,9 +845,9 @@ type FSharpLspServer(backgroundServiceEnabled: bool, state: State, lspClient: FS let getAbstractClassStubReplacements () = abstractClassStubReplacements () codeFixes <- - [| - Run.ifEnabled - (fun _ -> config.UnusedOpensAnalyzer) + [| + Run.ifEnabled + (fun _ -> config.UnusedOpensAnalyzer) (RemoveUnusedOpens.fix getFileLines) Run.ifEnabled (fun _ -> config.ResolveNamespaces) @@ -2706,6 +2707,7 @@ type FSharpLspServer(backgroundServiceEnabled: bool, state: State, lspClient: FS hints |> Array.map (fun h -> { Text = h.Text + InsertText = h.InsertText Pos = fcsPosToLsp h.Pos Kind = mapHintKind h.Kind }) diff --git a/src/FsAutoComplete/LspHelpers.fs b/src/FsAutoComplete/LspHelpers.fs index 8baeffa6c..6bd85e02c 100644 --- a/src/FsAutoComplete/LspHelpers.fs +++ b/src/FsAutoComplete/LspHelpers.fs @@ -523,9 +523,9 @@ type PlainNotification= { Content: string } /// Notification when a `TextDocument` is completely analyzed: /// F# Compiler checked file & all Analyzers (like `UnusedOpensAnalyzer`) are done. -/// +/// /// Used to signal all Diagnostics for this `TextDocument` are collected and sent. -/// -> For tests to get all Diagnostics of `TextDocument` +/// -> For tests to get all Diagnostics of `TextDocument` type DocumentAnalyzedNotification = { TextDocument: VersionedTextDocumentIdentifier } @@ -841,7 +841,7 @@ let encodeSemanticHighlightRanges (rangesAndHighlights: (struct(Ionide.LanguageS match rangesAndHighlights.Length with | 0 -> None - /// only 1 entry, so compute the line from the 0 position + // only 1 entry, so compute the line from the 0 position | 1 -> Some ( computeLine fileStart rangesAndHighlights.[0]