diff --git a/src/FsAutoComplete.Core/UntypedAstUtils.fs b/src/FsAutoComplete.Core/UntypedAstUtils.fs index 273865d02..6d495c4ef 100644 --- a/src/FsAutoComplete.Core/UntypedAstUtils.fs +++ b/src/FsAutoComplete.Core/UntypedAstUtils.fs @@ -684,11 +684,25 @@ module Completion = | SynExpr.Const(SynConst.String _, _) -> Some Context.StringLiteral | SynExpr.InterpolatedString(parts, _, _) -> parts - |> List.tryPick (function - | SynInterpolatedStringPart.String(s, m) when Range.rangeContainsPos m pos -> + |> List.indexed + |> List.tryPick (fun (i, part) -> + let inRangeOfPrevious = + if i = 0 then + false + else + // With no space between FillExpr and }..." of interpolated string, + // there will be a range clash. + match List.item (i - 1) parts with + | SynInterpolatedStringPart.String(_, m) -> Range.rangeContainsPos m pos + | SynInterpolatedStringPart.FillExpr(e, _) -> Range.rangeContainsPos e.Range pos + + match part with + | SynInterpolatedStringPart.String(s, m) when Range.rangeContainsPos m pos && not inRangeOfPrevious -> Some Context.StringLiteral | SynInterpolatedStringPart.String _ -> None - | SynInterpolatedStringPart.FillExpr(e, _) when Range.rangeContainsPos e.Range pos -> + | SynInterpolatedStringPart.FillExpr(e, _) when + Range.rangeContainsPos e.Range pos && not inRangeOfPrevious + -> defaultTraverse e // gotta dive into the expr to see if we're in a literal inside the expr | SynInterpolatedStringPart.FillExpr _ -> None) | _ -> defaultTraverse expr diff --git a/test/FsAutoComplete.Tests.Lsp/CompletionTests.fs b/test/FsAutoComplete.Tests.Lsp/CompletionTests.fs index e1b61b455..31a1a8207 100644 --- a/test/FsAutoComplete.Tests.Lsp/CompletionTests.fs +++ b/test/FsAutoComplete.Tests.Lsp/CompletionTests.fs @@ -470,7 +470,63 @@ let tests state = Expect.equal resolved.Label "CancellationToken" "Just making sure we're on the right member, one that should have backticks" Expect.equal resolved.Detail (Some "property Async.CancellationToken: Async with get") "Signature shouldn't have backticks" - } |> AsyncResult.bimap id (fun e -> failwithf "%O" e))] + } |> AsyncResult.bimap id (fun e -> failwithf "%O" e)) + + testCaseAsync + "completion in interpolated string" + (async { + let! server, path = server + + let completionParams: CompletionParams = + { TextDocument = { Uri = Path.FilePathToUri path } + Position = { Line = 23; Character = 8 } // 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 in interpolated string with whitespace" + (async { + let! server, path = server + + let completionParams: CompletionParams = + { TextDocument = { Uri = Path.FilePathToUri path } + Position = { Line = 24; Character = 9 } // 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 let autocompleteTest state = diff --git a/test/FsAutoComplete.Tests.Lsp/TestCases/Completion/Script.fsx b/test/FsAutoComplete.Tests.Lsp/TestCases/Completion/Script.fsx index fc70ba126..28680bb96 100644 --- a/test/FsAutoComplete.Tests.Lsp/TestCases/Completion/Script.fsx +++ b/test/FsAutoComplete.Tests.Lsp/TestCases/Completion/Script.fsx @@ -20,3 +20,6 @@ Path.GetDirectoryName("foo") [1;2;3]. [1;2;3] + +$"{List.}" +$"{ List. }"