From 89317436a467d6441a9676156a6252f6aef95120 Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Sat, 12 Mar 2022 11:04:43 -0600 Subject: [PATCH 1/2] fix out-of-bounds checking for completions --- src/FsAutoComplete/FsAutoComplete.Lsp.fs | 16 +++---- .../CompletionTests.fs | 44 ++++++++++++++++++- .../TestCases/Completion/Script.fsx | 5 +++ 3 files changed, 54 insertions(+), 11 deletions(-) diff --git a/src/FsAutoComplete/FsAutoComplete.Lsp.fs b/src/FsAutoComplete/FsAutoComplete.Lsp.fs index 751d3ecee..65b975fbc 100644 --- a/src/FsAutoComplete/FsAutoComplete.Lsp.fs +++ b/src/FsAutoComplete/FsAutoComplete.Lsp.fs @@ -25,6 +25,7 @@ open CliWrap.Buffered open FSharp.Compiler.Tokenization open FSharp.Compiler.EditorServices open FSharp.Compiler.Symbols +open FSharp.UMX module FcsRange = FSharp.Compiler.Text.Range type FcsRange = FSharp.Compiler.Text.Range @@ -1041,18 +1042,15 @@ type FSharpLspServer(backgroundServiceEnabled: bool, state: State, lspClient: FS commands.TryGetFileCheckerOptionsWithLines file |> Result.mapError JsonRpc.Error.InternalErrorMessage - let line, col = p.Position.Line, p.Position.Character - let lineStr = lines.GetLineString line - - let word = - lineStr.Substring(0, min col lineStr.Length) - - do! ensureInBounds lines (line, col) + let lineSegmentLSPRange = + { Start = { p.Position with Character = 0}; End = p.Position } + let lineSegmentFCSRange = protocolRangeToRange (UMX.untag file) lineSegmentLSPRange + let! lineStr = lines.GetText lineSegmentFCSRange |> Result.mapError JsonRpc.Error.InternalErrorMessage if (lineStr.StartsWith "#" && (KeywordList.hashDirectives.Keys - |> Seq.exists (fun k -> k.StartsWith word) - || word.Contains "\n")) then + |> Seq.exists (fun k -> k.StartsWith lineStr) + || lineStr.Contains "\n")) then let completionList = { IsIncomplete = false Items = KeywordList.hashSymbolCompletionItems } diff --git a/test/FsAutoComplete.Tests.Lsp/CompletionTests.fs b/test/FsAutoComplete.Tests.Lsp/CompletionTests.fs index b6a7be8c0..b2bfe5422 100644 --- a/test/FsAutoComplete.Tests.Lsp/CompletionTests.fs +++ b/test/FsAutoComplete.Tests.Lsp/CompletionTests.fs @@ -41,6 +41,46 @@ let tests state = | Error e -> failtestf "Got an error while retrieving completions: %A" e }) + + testCaseAsync "completion at start of line" (async { + let! server, path = server + let completionParams : CompletionParams = + { + TextDocument = { Uri = Path.FilePathToUri path } + Position = { Line = 6; Character = 5 } // the '.' in 'List.' + Context = Some { triggerKind = CompletionTriggerKind.TriggerCharacter; triggerCharacter = Some '.' } + } + let! response = server.TextDocumentCompletion completionParams + match response with + | Ok (Some completions) -> + Expect.equal completions.Items.Length 106 "at time of writing the List module has 106 exposed members" + let firstItem = completions.Items.[0] + Expect.equal firstItem.Label "Empty" "first member should be List.Empty, since properties are preferred over functions" + | Ok None -> + failtest "Should have gotten some completion items" + | Error e -> + failtestf "Got an error while retrieving completions: %A" e + }) + + testCaseAsync "completion at end of line" (async { + let! server, path = server + let completionParams : CompletionParams = + { + TextDocument = { Uri = Path.FilePathToUri path } + Position = { Line = 8; Character = 16 } // the '.' in 'List.' + Context = Some { triggerKind = CompletionTriggerKind.TriggerCharacter; triggerCharacter = Some '.' } + } + let! response = server.TextDocumentCompletion completionParams + match response with + | Ok (Some completions) -> + Expect.equal completions.Items.Length 106 "at time of writing the List module has 106 exposed members" + let firstItem = completions.Items.[0] + Expect.equal firstItem.Label "Empty" "first member should be List.Empty, since properties are preferred over functions" + | Ok None -> + failtest "Should have gotten some completion items" + | Error e -> + failtestf "Got an error while retrieving completions: %A" e + }) ] ///Tests for getting autocomplete @@ -243,7 +283,7 @@ let autoOpenTests state = return (edit, ns, openPos) | Ok _ -> return failtest $"Quick fix on `{word}` doesn't contain open action" } - + let test (compareWithQuickFix: bool) (name: string option) (server: Async) (word: string, ns: string) (cursor: Position) (expectedOpen: Position) pending = let name = name |> Option.defaultWith (fun _ -> sprintf "completion on `Regex` at (%i, %i) should `open System.Text.RegularExpressions` at (%i, %i) (0-based)" (cursor.Line) (cursor.Character) (expectedOpen.Line) (expectedOpen.Character)) let runner = if pending then ptestCaseAsync else testCaseAsync @@ -369,7 +409,7 @@ let autoOpenTests state = do! server.Shutdown() }) ] - + let ptestScript name scriptName = testList name [ let scriptPath = Path.Combine(dirPath, scriptName) diff --git a/test/FsAutoComplete.Tests.Lsp/TestCases/Completion/Script.fsx b/test/FsAutoComplete.Tests.Lsp/TestCases/Completion/Script.fsx index ca288d4bd..a37d4d040 100644 --- a/test/FsAutoComplete.Tests.Lsp/TestCases/Completion/Script.fsx +++ b/test/FsAutoComplete.Tests.Lsp/TestCases/Completion/Script.fsx @@ -2,3 +2,8 @@ async { return 1 } |> Async. // completion at this `.` should not have a billion suggestions + + +List. + +let tail = List. From 3402fa732b48c93d66007b783f3b44bc930672a7 Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Sat, 12 Mar 2022 11:19:13 -0600 Subject: [PATCH 2/2] don't regress hash directive completions --- src/FsAutoComplete.Core/KeywordList.fs | 5 +++-- src/FsAutoComplete/FsAutoComplete.Lsp.fs | 5 +---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/FsAutoComplete.Core/KeywordList.fs b/src/FsAutoComplete.Core/KeywordList.fs index 1fd99fdc5..a657b188b 100644 --- a/src/FsAutoComplete.Core/KeywordList.fs +++ b/src/FsAutoComplete.Core/KeywordList.fs @@ -48,8 +48,9 @@ module KeywordList = |> Seq.toArray let allKeywords : string list = - FSharpKeywords.KeywordsWithDescription - |> List.map fst + keywordDescriptions + |> Seq.map ((|KeyValue|) >> fst) + |> Seq.toList let keywordCompletionItems = allKeywords diff --git a/src/FsAutoComplete/FsAutoComplete.Lsp.fs b/src/FsAutoComplete/FsAutoComplete.Lsp.fs index 65b975fbc..3ebff6fd4 100644 --- a/src/FsAutoComplete/FsAutoComplete.Lsp.fs +++ b/src/FsAutoComplete/FsAutoComplete.Lsp.fs @@ -1047,10 +1047,7 @@ type FSharpLspServer(backgroundServiceEnabled: bool, state: State, lspClient: FS let lineSegmentFCSRange = protocolRangeToRange (UMX.untag file) lineSegmentLSPRange let! lineStr = lines.GetText lineSegmentFCSRange |> Result.mapError JsonRpc.Error.InternalErrorMessage - if (lineStr.StartsWith "#" - && (KeywordList.hashDirectives.Keys - |> Seq.exists (fun k -> k.StartsWith lineStr) - || lineStr.Contains "\n")) then + if lineStr.StartsWith "#" then let completionList = { IsIncomplete = false Items = KeywordList.hashSymbolCompletionItems }