From 51911b4eef331be6bbffa6b29797fada37b3a6a4 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Tue, 11 Oct 2022 21:54:44 -0400 Subject: [PATCH 01/37] Fixes ResolveNamespace in nested modules --- .../CodeFixes/ResolveNamespace.fs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/FsAutoComplete/CodeFixes/ResolveNamespace.fs b/src/FsAutoComplete/CodeFixes/ResolveNamespace.fs index a76bdff02..b3f7b93fc 100644 --- a/src/FsAutoComplete/CodeFixes/ResolveNamespace.fs +++ b/src/FsAutoComplete/CodeFixes/ResolveNamespace.fs @@ -76,7 +76,23 @@ let fix ns let lineStr = - let whitespace = String.replicate ctx.Pos.Column " " + let whitespace = + let column = + // HACK: This is a work around for inheriting the correct column of the current module + // It seems the column we get from FCS is incorrect + let previousLine = docLine - 1 + let insertionPointIsNotOutOfBoundsOfTheFile = docLine > 0 + + let theThereAreOtherOpensInThisModule = + text.GetLineString(previousLine).Contains "open " + + if insertionPointIsNotOutOfBoundsOfTheFile && theThereAreOtherOpensInThisModule then + text.GetLineString(previousLine).Split("open") |> Seq.head |> Seq.length // inherit the previous opens whitespace + else + ctx.Pos.Column + + String.replicate column " " + $"%s{whitespace}open %s{actualOpen}\n" let edits = From cfeb732882dc43d6ff59eba06a108122bf05cf1a Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Tue, 11 Oct 2022 23:03:23 -0400 Subject: [PATCH 02/37] Adds tests to verify ResolveNamespace fix --- .../CodeFixTests/Tests.fs | 44 +++++++++-- .../Utils/TextEdit.fs | 78 +++++++++---------- 2 files changed, 78 insertions(+), 44 deletions(-) diff --git a/test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs b/test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs index a3693571c..9b39bc00f 100644 --- a/test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs +++ b/test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs @@ -1331,7 +1331,7 @@ let private renameUnusedValue state = """ (Diagnostics.acceptAll) selectPrefix - + testCaseAsync "replace doesn't trigger for function" <| CodeFix.checkNotApplicable server """ @@ -1370,8 +1370,8 @@ let private replaceWithSuggestionTests state = let selectCodeFix replacement = CodeFix.withTitle (ReplaceWithSuggestion.title replacement) let validateDiags (diags: Diagnostic[]) = Diagnostics.expectCode "39" diags - Expect.exists - diags + Expect.exists + diags (fun (d: Diagnostic) -> d.Message.Contains "Maybe you want one of the following:") "Diagnostic with code 39 should suggest name" testCaseAsync "can change Min to min" <| @@ -1467,19 +1467,53 @@ let private replaceWithSuggestionTests state = let private resolveNamespaceTests state = let config = { defaultConfigDto with ResolveNamespaces = Some true } serverTestList (nameof ResolveNamespace) state config None (fun server -> [ + let selectCodeFix = CodeFix.matching (fun ca -> ca.Title.StartsWith "open") testCaseAsync "doesn't fail when target not in last line" <| CodeFix.checkApplicable server """ let x = $0Min(2.0, 1.0) """ // Note: new line at end! (Diagnostics.log >> Diagnostics.acceptAll) - (CodeFix.log >> CodeFix.matching (fun ca -> ca.Title.StartsWith "open") >> Array.take 1) + (CodeFix.log >> selectCodeFix >> Array.take 1) testCaseAsync "doesn't fail when target in last line" <| CodeFix.checkApplicable server "let x = $0Min(2.0, 1.0)" // Note: No new line at end! (Diagnostics.log >> Diagnostics.acceptAll) - (CodeFix.log >> CodeFix.matching (fun ca -> ca.Title.StartsWith "open") >> Array.take 1) + (CodeFix.log >> selectCodeFix >> Array.take 1) + testCaseAsync "place open in module correctly when having additional modules" + <| CodeFix.check + server + """ +module Foo = + open Microsoft + + let foo = Date$0Time.Now + """ + (Diagnostics.log >> Diagnostics.acceptAll) + selectCodeFix + """ +module Foo = + open Microsoft + open System + + let foo = DateTime.Now + """ + + testCaseAsync "place open in module correctly without any modules" + <| CodeFix.check + server + """ +module Foo = + let foo = $0DateTime.Now + """ + (Diagnostics.log >> Diagnostics.acceptAll) + selectCodeFix + """ +module Foo = + open System + let foo = DateTime.Now + """ //TODO: Implement & unify with `Completion.AutoOpen` (`CompletionTests.fs`) // Issues: // * Complex because of nesting modules (-> where to open) diff --git a/test/FsAutoComplete.Tests.Lsp/Utils/TextEdit.fs b/test/FsAutoComplete.Tests.Lsp/Utils/TextEdit.fs index f0f3aed18..55bca1136 100644 --- a/test/FsAutoComplete.Tests.Lsp/Utils/TextEdit.fs +++ b/test/FsAutoComplete.Tests.Lsp/Utils/TextEdit.fs @@ -6,7 +6,7 @@ open FsToolkit.ErrorHandling /// Functions to extract Cursor or Range from a given string. /// Cursor is marked in string with `$0` (`Cursor.Marker`) -/// +/// /// Note: Only `\n` is supported. Neither `\r\n` nor `\r` produce correct results. module Cursor = /// 0-based @@ -29,8 +29,8 @@ module Cursor = *) /// Returns Cursor Position BEFORE index - /// - /// Index might be `text.Length` (-> cursor AFTER last character). + /// + /// Index might be `text.Length` (-> cursor AFTER last character). /// All other out of text range indices throw exception. let beforeIndex (i: int) (text: string) : Position = assert(i >= 0) @@ -45,8 +45,8 @@ module Cursor = pos line char /// Returns index of first `$0` (`Cursor.Marker`) and the updated input text without the cursor marker. - /// - /// Note: Cursor Position is BEFORE index. + /// + /// Note: Cursor Position is BEFORE index. /// Note: Index might be `text.Length` (-> Cursor AFTER last char in text) let tryExtractIndex (text: string) = match text.IndexOf Marker with @@ -79,16 +79,16 @@ module Cursor = let tryFindAnyCursor (lines: string[]) = lines |> Seq.mapi (fun i l -> (i,l)) - |> Seq.tryPick (fun (i,line) -> - tryFindAnyCursorInLine line + |> Seq.tryPick (fun (i,line) -> + tryFindAnyCursorInLine line |> Option.map (fun (marker, c, line) -> (marker, pos i c, line)) ) |> function | None -> None - | Some (marker, p,line) -> + | Some (marker, p,line) -> lines.[p.Line] <- line Some ((marker, p), lines) - + let lines = text |> Text.lines match tryFindAnyCursor lines with | None -> None @@ -96,9 +96,9 @@ module Cursor = let text = lines |> String.concat "\n" Some ((marker, p), text) - /// Returns Position of first `$0` (`Cursor.Marker`) and the updated input text without the cursor marker. + /// Returns Position of first `$0` (`Cursor.Marker`) and the updated input text without the cursor marker. /// Only the first `$0` is processed. - /// + /// /// Note: Cursor Position is BETWEEN characters and might be outside of text range (cursor AFTER last character) let tryExtractPosition = tryExtractPositionMarkedWithAnyOf [| Marker |] @@ -109,7 +109,7 @@ module Cursor = >> Option.defaultWith (fun _ -> failtest "No cursor") /// Returns Range between the first two `$0` (`Cursor.Marker`) and the updated text without the two cursor markers. - /// + /// /// If there's only one cursor marker, the range covers exactly that position (`Start = End`) let tryExtractRange (text: string) = match tryExtractPosition text with @@ -125,9 +125,9 @@ module Cursor = /// Position is between characters, while index is on character. /// For Insert & Remove: character indices - /// + /// /// Returned index is AFTER cursor: - /// * `Column=0`: before first char; `Index=0`: on first char + /// * `Column=0`: before first char; `Index=0`: on first char /// * `Column=1`: after first char, before 2nd char; `Index=1`: on 2nd char /// * `Column=max`: after last char; `Index=max`: AFTER last char in line (-> `\n` or end of string) let tryIndexOf (pos: Position) (text: string) = @@ -157,7 +157,7 @@ module Cursor = >> Result.valueOr (failtestf "Invalid position: %s") /// Calculates cursors position after all edits are applied. - /// + /// /// When cursor inside a changed area: /// * deleted: cursor moves to start of deletion: /// ```fsharp @@ -184,7 +184,7 @@ module Cursor = /// let foo = 42 $0- 7 + 123 /// ``` /// -> like deletion - /// * Implementation detail: + /// * Implementation detail: /// Replacement is considered: First delete (-> move cursor to front), then insert (-> cursor stays) /// /// Note: `edits` must be sorted by range! @@ -213,7 +213,7 @@ module Cursor = else - e.Character + s.Character { Line = pos.Line + deltaLine; Character = pos.Character + deltaChar } - + // add new text to pos let pos = if System.String.IsNullOrEmpty edit.NewText then @@ -248,7 +248,7 @@ module Cursor = module Cursors = /// For each cursor (`$0`) in text: return text with just that one cursor - /// + /// /// Note: doesn't trim input! let iter (textWithCursors: string) = let rec collect (textsWithSingleCursor) (textWithCursors: string) = @@ -264,7 +264,7 @@ module Cursors = collect [] textWithCursors /// Returns all cursor (`$0`) positions and the text without any cursors. - /// + /// /// Unlike `iter` this extracts positions instead of reducing to texts with one cursor let extract (textWithCursors: string) = let tps = @@ -274,7 +274,7 @@ module Cursors = let text = tps |> List.head |> snd let poss = tps |> List.map fst (text, poss) - + /// Like `extract`, but instead of just extracting Cursors marked with `Cursor.Marker` (`$0`), /// this here extract all specified markers. @@ -337,8 +337,8 @@ module Text = module TextEdit = let apply (edit: TextEdit) = - // `edit` is from FSAC LSP -> might contain `\r`. - // But only `\n` handled by `Text.lines` -> remove `\r` + // `edit` is from FSAC LSP -> might contain `\r`. + // But only `\n` handled by `Text.lines` -> remove `\r` let newText = edit.NewText |> Text.removeCarriageReturn Text.replace edit.Range newText @@ -352,7 +352,7 @@ module TextEdit = && not (edit |> inserts) - // **Note**: + // **Note**: // VS Code allows TextEdits, that might not be strictly valid according to LSP Specs [^1]: // * inserts into not existing line (text has 2 line, insert into line 5 is ok) // * inserts into line way after last character (line has 15 char, insert into column 1000 is ok) @@ -360,9 +360,9 @@ module TextEdit = // * empty text edits (neither inserts nor deletes text) // // LSP Specs are quite vague. So above might or might not be ok according to Specs. - // But from FSAC perspective: Any case above most likely indicates an error in CodeFix implementation + // But from FSAC perspective: Any case above most likely indicates an error in CodeFix implementation // -> TextEdit must be STRICTLY correct and all of the cases above are considered erroneous! - // + // // [^1]: https://microsoft.github.io/language-server-protocol/specifications/specification-current/ /// Checks passed `edit` for errors: @@ -382,11 +382,11 @@ module TextEdit = Some "Expected positive End.Character, but was negative" else if edit.Range.Start > edit.Range.End then Some "Expected Range.Start <= Range.End, but was Start > End" - else if edit |> doesNothing then - Some "Expected change, but does nothing (neither delete nor insert)" + // else if edit |> doesNothing then + // Some "Expected change, but does nothing (neither delete nor insert)" else None - + module TextEdits = /// Checks edits for: @@ -394,13 +394,13 @@ module TextEdits = /// * All TextEdits are valid (`TextEdit.tryFindError`) /// * Edits don't overlap /// * For same position: All inserted before at most one replace (or delete) - /// - /// + /// + /// /// [LSP Specification for `TextEdit[]`](https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textEditArray) - /// > Text edits ranges must never overlap, that means no part of the original document must be manipulated by more than one edit. - /// > However, it is possible that multiple edits have the same start position: multiple inserts, - /// > or any number of inserts followed by a single remove or replace edit. - /// > If multiple inserts have the same position, the order in the array defines the order + /// > Text edits ranges must never overlap, that means no part of the original document must be manipulated by more than one edit. + /// > However, it is possible that multiple edits have the same start position: multiple inserts, + /// > or any number of inserts followed by a single remove or replace edit. + /// > If multiple inserts have the same position, the order in the array defines the order /// > in which the inserted strings appear in the resulting text. let tryFindError (edits: TextEdit list) = let rec tryFindOverlappingEditExample (edits: TextEdit list) = @@ -413,7 +413,7 @@ module TextEdits = | None -> tryFindOverlappingEditExample edits let (|Overlapping|_|) = tryFindOverlappingEditExample - let (|Invalids|_|) = + let (|Invalids|_|) = List.choose (fun edit -> edit |> TextEdit.tryFindError |> Option.map (fun err -> (edit, err))) >> function | [] -> None | errs -> Some errs let findSameStarts (edits: TextEdit list) = @@ -439,7 +439,7 @@ module TextEdits = | [] -> Some "Expected at least one TextEdit, but were none" // edits should be valid | Invalids errs -> - sprintf + sprintf "Expected all TextEdits to be valid, but there was at least one erroneous Edit. Invalid Edits: %A" errs |> Some @@ -448,7 +448,7 @@ module TextEdits = Some $"Expected no overlaps, but at least two edits overlap: {edit1.Range} and {edit2.Range}" // For same position: all inserts must be before at most one Delete/Replace | ReplaceNotLast errs -> - sprintf + sprintf "Expected Inserts before at most one Delete/Replace, but there was at least one Delete/Before in invalid position: Invalid Edits: %A" errs |> Some @@ -482,7 +482,7 @@ module TextEdits = module WorkspaceEdit = /// Extract `TextEdit[]` from either `DocumentChanges` or `Changes`. /// All edits MUST be for passed `textDocument`. - /// + /// /// Checks for errors: /// * Either `DocumentChanges` or `Changes`, but not both /// * FsAutoComplete sends only `DocumentChanges` @@ -500,7 +500,7 @@ module WorkspaceEdit = else match textDocument.Version, version with // only compare `Version` when `textDocument` and `version` has a Version. Otherwise ignore - | Some textDocVersion, Some version when textDocVersion <> version -> + | Some textDocVersion, Some version when textDocVersion <> version -> Some $"Edit should be for document version `{textDocVersion}`, but version was `{version}`" | _ -> None From f360a109e31556fd70e64be67be036779eeb6c2f Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Wed, 12 Oct 2022 21:49:44 -0400 Subject: [PATCH 03/37] Fixes ResolveNamespace tests --- src/FsAutoComplete/CodeFixes/ResolveNamespace.fs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/FsAutoComplete/CodeFixes/ResolveNamespace.fs b/src/FsAutoComplete/CodeFixes/ResolveNamespace.fs index b3f7b93fc..c500e6667 100644 --- a/src/FsAutoComplete/CodeFixes/ResolveNamespace.fs +++ b/src/FsAutoComplete/CodeFixes/ResolveNamespace.fs @@ -83,10 +83,10 @@ let fix let previousLine = docLine - 1 let insertionPointIsNotOutOfBoundsOfTheFile = docLine > 0 - let theThereAreOtherOpensInThisModule = + let theThereAreOtherOpensInThisModule () = text.GetLineString(previousLine).Contains "open " - if insertionPointIsNotOutOfBoundsOfTheFile && theThereAreOtherOpensInThisModule then + if insertionPointIsNotOutOfBoundsOfTheFile && theThereAreOtherOpensInThisModule () then text.GetLineString(previousLine).Split("open") |> Seq.head |> Seq.length // inherit the previous opens whitespace else ctx.Pos.Column From 5d52b08436abbe41c466742ab9b404341cf2ba80 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Thu, 20 Oct 2022 10:48:04 -0400 Subject: [PATCH 04/37] More tests around ResolveNamespace --- .../CodeFixes/ResolveNamespace.fs | 14 +---------- .../CodeFixTests/Tests.fs | 23 ++++++++++++++++++- .../Utils/TextEdit.fs | 4 ++-- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/FsAutoComplete/CodeFixes/ResolveNamespace.fs b/src/FsAutoComplete/CodeFixes/ResolveNamespace.fs index c500e6667..8d988e568 100644 --- a/src/FsAutoComplete/CodeFixes/ResolveNamespace.fs +++ b/src/FsAutoComplete/CodeFixes/ResolveNamespace.fs @@ -95,19 +95,7 @@ let fix $"%s{whitespace}open %s{actualOpen}\n" - let edits = - [| yield insertLine docLine lineStr - if - text.GetLineCount() < docLine + 1 - && text.GetLineString(docLine + 1).Trim() <> "" - then - yield insertLine (docLine + 1) "" - if - (ctx.Pos.Column = 0 || ctx.ScopeKind = ScopeKind.Namespace) - && docLine > 0 - && not (text.GetLineString(docLine - 1).StartsWith "open") - then - yield insertLine (docLine - 1) "" |] + let edits = [| yield insertLine docLine lineStr |] { Edits = edits File = file diff --git a/test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs b/test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs index 9b39bc00f..59ac5791a 100644 --- a/test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs +++ b/test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs @@ -1514,6 +1514,27 @@ module Foo = open System let foo = DateTime.Now """ + + + + testCaseAsync "With-attribute" + <| CodeFix.check + server + """ +[] +module Foo = + + let foo = $0DateTime.Now + """ + (Diagnostics.log >> Diagnostics.acceptAll) + selectCodeFix + """ +[] +module Foo = + open System + + let foo = DateTime.Now + """ //TODO: Implement & unify with `Completion.AutoOpen` (`CompletionTests.fs`) // Issues: // * Complex because of nesting modules (-> where to open) @@ -1613,7 +1634,7 @@ let private wrapExpressionInParenthesesTests state = selectCodeFix ]) -let tests state = testList "CodeFix tests" [ +let tests state = testList "CodeFix-tests" [ HelpersTests.tests AddExplicitTypeAnnotationTests.tests state diff --git a/test/FsAutoComplete.Tests.Lsp/Utils/TextEdit.fs b/test/FsAutoComplete.Tests.Lsp/Utils/TextEdit.fs index 55bca1136..741503e39 100644 --- a/test/FsAutoComplete.Tests.Lsp/Utils/TextEdit.fs +++ b/test/FsAutoComplete.Tests.Lsp/Utils/TextEdit.fs @@ -382,8 +382,8 @@ module TextEdit = Some "Expected positive End.Character, but was negative" else if edit.Range.Start > edit.Range.End then Some "Expected Range.Start <= Range.End, but was Start > End" - // else if edit |> doesNothing then - // Some "Expected change, but does nothing (neither delete nor insert)" + else if edit |> doesNothing then + Some "Expected change, but does nothing (neither delete nor insert)" else None From ae377b74beb43bb0db336982cdb1a8fd393b95fb Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Thu, 20 Oct 2022 21:23:24 -0400 Subject: [PATCH 05/37] Adds lsp server to debug launch actions --- .vscode/launch.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index c5ba1a784..9237446c0 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -78,7 +78,7 @@ "args": [ "--debug", "--filter", - "FSAC.lsp.${input:loader}.${input:testName}" + "FSAC.lsp.${input:loader}.${input:lsp-server}.${input:testName}" ] } ], @@ -103,6 +103,17 @@ "default": "WorkspaceLoader", "type": "pickString" }, + + { + "id": "lsp-server", + "description": "The lsp serrver", + "options": [ + "FSharpLspServer", + "AdaptiveLspServer" + ], + "default": "FSharpLspServer", + "type": "pickString" + }, { "id": "testName", "description": "the name of the test as provided to `testCase`", From e7e215eb30bdbef482696d15f6a5058b210bebcb Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Thu, 20 Oct 2022 21:23:45 -0400 Subject: [PATCH 06/37] Fixes resolvenamespace with attributes on modules --- .../CodeFixes/ResolveNamespace.fs | 54 ++++++++++--------- .../CodeFixTests/Tests.fs | 2 +- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/src/FsAutoComplete/CodeFixes/ResolveNamespace.fs b/src/FsAutoComplete/CodeFixes/ResolveNamespace.fs index 8d988e568..d759e97da 100644 --- a/src/FsAutoComplete/CodeFixes/ResolveNamespace.fs +++ b/src/FsAutoComplete/CodeFixes/ResolveNamespace.fs @@ -29,30 +29,36 @@ let fix let adjustInsertionPoint (lines: ISourceText) (ctx: InsertionContext) = let l = ctx.Pos.Line - - match ctx.ScopeKind with - | ScopeKind.TopModule when l > 1 -> - let line = lines.GetLineString(l - 2) - - let isImplicitTopLevelModule = - not (line.StartsWith "module" && not (line.EndsWith "=")) - - if isImplicitTopLevelModule then 1 else l - | ScopeKind.TopModule -> 1 - | ScopeKind.Namespace -> - let mostRecentNamespaceInScope = - let lineNos = if l = 0 then [] else [ 0 .. l - 1 ] - - lineNos - |> List.mapi (fun i line -> i, lines.GetLineString line) - |> List.choose (fun (i, lineStr) -> if lineStr.StartsWith "namespace" then Some i else None) - |> List.tryLast - - match mostRecentNamespaceInScope with - // move to the next line below "namespace" and convert it to F# 1-based line number - | Some line -> line + 2 - | None -> l - | _ -> l + let retVal = + match ctx.ScopeKind with + | ScopeKind.TopModule when l > 1 -> + let line = lines.GetLineString(l - 2) + + let isImplicitTopLevelModule = + not (line.StartsWith "module" && not (line.EndsWith "=")) + + if isImplicitTopLevelModule then 1 else l + | ScopeKind.TopModule -> 1 + | ScopeKind.Namespace -> + let mostRecentNamespaceInScope = + let lineNos = if l = 0 then [] else [ 0 .. l - 1 ] + + lineNos + |> List.mapi (fun i line -> i, lines.GetLineString line) + |> List.choose (fun (i, lineStr) -> if lineStr.StartsWith "namespace" then Some i else None) + |> List.tryLast + + match mostRecentNamespaceInScope with + // move to the next line below "namespace" and convert it to F# 1-based line number + | Some line -> line + 2 + | None -> l + | _ -> l + let containsAttribute (x : string) = x.Contains "[<" + let currentLine = System.Math.Max(retVal - 2 , 0) |> lines.GetLineString + if currentLine |> containsAttribute then + retVal + 1 + else + retVal let qualifierFix file diagnostic qual = { SourceDiagnostic = Some diagnostic diff --git a/test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs b/test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs index 59ac5791a..a7b347f64 100644 --- a/test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs +++ b/test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs @@ -1517,7 +1517,7 @@ module Foo = - testCaseAsync "With-attribute" + testCaseAsync "With attribute" <| CodeFix.check server """ From 2926f447da0236b21f785aa7897b2c417cfda4cd Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Thu, 20 Oct 2022 21:25:51 -0400 Subject: [PATCH 07/37] formatting --- src/FsAutoComplete/CodeFixes/ResolveNamespace.fs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/FsAutoComplete/CodeFixes/ResolveNamespace.fs b/src/FsAutoComplete/CodeFixes/ResolveNamespace.fs index d759e97da..bd5522bed 100644 --- a/src/FsAutoComplete/CodeFixes/ResolveNamespace.fs +++ b/src/FsAutoComplete/CodeFixes/ResolveNamespace.fs @@ -29,6 +29,7 @@ let fix let adjustInsertionPoint (lines: ISourceText) (ctx: InsertionContext) = let l = ctx.Pos.Line + let retVal = match ctx.ScopeKind with | ScopeKind.TopModule when l > 1 -> @@ -53,8 +54,10 @@ let fix | Some line -> line + 2 | None -> l | _ -> l - let containsAttribute (x : string) = x.Contains "[<" - let currentLine = System.Math.Max(retVal - 2 , 0) |> lines.GetLineString + + let containsAttribute (x: string) = x.Contains "[<" + let currentLine = System.Math.Max(retVal - 2, 0) |> lines.GetLineString + if currentLine |> containsAttribute then retVal + 1 else From aa1b009f3b57040ff57c599dcd2319f818d3e709 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Sat, 15 Oct 2022 00:41:05 -0400 Subject: [PATCH 08/37] Clear caching on project load and incremental text support --- .../CompilerServiceInterface.fs | 6 +- src/FsAutoComplete.Core/FileSystem.fs | 8 +- .../LspServers/AdaptiveFSharpLspServer.fs | 238 ++++++++++++++---- 3 files changed, 193 insertions(+), 59 deletions(-) diff --git a/src/FsAutoComplete.Core/CompilerServiceInterface.fs b/src/FsAutoComplete.Core/CompilerServiceInterface.fs index d983c3037..20b11a1ec 100644 --- a/src/FsAutoComplete.Core/CompilerServiceInterface.fs +++ b/src/FsAutoComplete.Core/CompilerServiceInterface.fs @@ -227,6 +227,11 @@ type FSharpCompilerServiceChecker(hasAnalyzers) = member __.ScriptTypecheckRequirementsChanged = scriptTypecheckRequirementsChanged.Publish + /// This function is called when the entire environment is known to have changed for reasons not encoded in the ProjectOptions of any project/compilation. + member _.ClearCaches () = + checker.InvalidateAll() + checker.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients() + member __.ParseFile(fn: string, source, fpo) = checkerLogger.info (Log.setMessage "ParseFile - {file}" >> Log.addContextDestructured "file" fn) @@ -241,7 +246,6 @@ type FSharpCompilerServiceChecker(hasAnalyzers) = let options = clearProjectReferences options let path = UMX.untag filePath - try let! (p, c) = checker.ParseAndCheckFileInProject(path, version, source, options, userOpName = opName) diff --git a/src/FsAutoComplete.Core/FileSystem.fs b/src/FsAutoComplete.Core/FileSystem.fs index e8b2b9b30..ebfcf555a 100644 --- a/src/FsAutoComplete.Core/FileSystem.fs +++ b/src/FsAutoComplete.Core/FileSystem.fs @@ -144,7 +144,7 @@ type NamedText(fileName: string, str: string) = // because we know there are lines after the first line let firstLine = (x :> ISourceText).GetLineString(m.StartLine - 1) - builder.AppendLine(firstLine.Substring(m.StartColumn)) + builder.AppendLine(firstLine.Substring(Math.Min(firstLine.Length, m.StartColumn))) |> ignore // whole intermediate lines, including newlines @@ -155,7 +155,7 @@ type NamedText(fileName: string, str: string) = // final part, potential slice, so we do not include the trailing newline let lastLine = (x :> ISourceText).GetLineString(m.EndLine - 1) - builder.Append(lastLine.Substring(0, m.EndColumn)) + builder.Append(lastLine.Substring(0, Math.Min(lastLine.Length, m.EndColumn))) |> ignore Ok(builder.ToString()) @@ -260,8 +260,8 @@ type NamedText(fileName: string, str: string) = member x.ModifyText(m: FSharp.Compiler.Text.Range, text: string) : Result = result { let startRange, endRange = x.SplitAt(m) - let! startText = x[startRange] - let! endText = x[endRange] + let! startText = x[startRange] |> Result.mapError(fun x -> $"startRange -> {x}") + let! endText = x[endRange] |> Result.mapError(fun x -> $"endRange -> {x}") let totalText = startText + text + endText return NamedText(x.FileName, totalText) } diff --git a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs index b026413b1..881078b72 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs @@ -235,11 +235,11 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let diagnosticCollections = new DiagnosticCollection(sendDiagnostics) - let notifications = Event() + let notifications = Event() let scriptFileProjectOptions = Event() - let handleCommandEvents (n: NotificationEvent) = + let handleCommandEvents (n: NotificationEvent, ct : CancellationToken) = try async { try @@ -432,11 +432,11 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar () } - |> Async.RunSynchronouslyWithCT CancellationToken.None - + |> Async.RunSynchronouslyWithCT ct with :? OperationCanceledException as e -> () + do disposables.Add( (notifications.Publish :> IObservable<_>) @@ -445,6 +445,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar .Subscribe(fun e -> handleCommandEvents e) ) + let adaptiveFile (filePath: string) = let file = AdaptiveFile.GetLastWriteTimeUtc(UMX.untag filePath) @@ -520,10 +521,12 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar projects |> Seq.iter (fun (proj: string, _) -> - UMX.untag proj - |> ProjectResponse.ProjectLoading - |> NotificationEvent.Workspace - |> notifications.Trigger) + let not = + UMX.untag proj + |> ProjectResponse.ProjectLoading + |> NotificationEvent.Workspace + notifications.Trigger(not, CancellationToken.None) + ) let projectOptions = loader.LoadProjects(projects |> Seq.map (fst >> UMX.untag) |> Seq.toList) @@ -562,7 +565,8 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar projectOptions, additionalDependencies) - + let! checker = checker + checker.ClearCaches() // if we got new projects assume we're gonna need to clear caches let options = projectOptions |> List.map (fun o -> @@ -597,14 +601,16 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar Extra = extraInfo ProjectItems = projViewerItemsNormalized.Items Additionals = Map.empty } - - ProjectResponse.Project(ws, false) + let not = + ProjectResponse.Project(ws, false) + |> NotificationEvent.Workspace + notifications.Trigger(not, CancellationToken.None) + ) + let not = + ProjectResponse.WorkspaceLoad true |> NotificationEvent.Workspace - |> notifications.Trigger) - ProjectResponse.WorkspaceLoad true - |> NotificationEvent.Workspace - |> notifications.Trigger + notifications.Trigger(not, CancellationToken.None) return options |> List.map fst }) @@ -618,16 +624,129 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let openFilesA = openFiles |> AMap.map' (fun v -> v :> aval<_>) + let textChanges = cmap, cset> () + let sortedTextChanges = + textChanges + |> AMap.map'( fun x -> x :> aset<_> + // ASet.sortBy(fun (x : DidChangeTextDocumentParams) -> x.TextDocument.Version.Value) + ) + let openFilesWithChanges = + + openFilesA + |> AMap.map(fun filePath file -> + aval { + let! (file, cts) = file + and! changes = sortedTextChanges |> AMap.tryFind filePath + match changes with + | None -> return (file, cts) + | Some c -> + let! ps = c |> ASet.toAVal + let changes = ps |> Seq.sortBy(fun x -> x.TextDocument.Version.Value) |> Seq.collect(fun p -> p.ContentChanges |> Array.map(fun x -> x, p.TextDocument.Version.Value)) + // try + // cts.Cancel() ; + // cts.Dispose(); + // with _ -> + // () + let file = + (file, changes) + ||> Seq.fold (fun text (change, version) -> + match change.Range with + | None -> // replace entire content + // We want to update the DateTime here since TextDocumentDidChange will not have changes reflected on disk + VolatileFile.Create(filePath, change.Text, Some version, DateTime.UtcNow) + | Some rangeToReplace -> + // replace just this slice + let fcsRangeToReplace = protocolRangeToRange (UMX.untag filePath) rangeToReplace + try + match text.Lines.ModifyText(fcsRangeToReplace, change.Text) with + | Ok text -> + VolatileFile.Create(text, Some version, DateTime.UtcNow) + + | Error message -> + logger.error ( + Log.setMessage "Error applying {change} to document {file} for version {version} - {range} : {message} " + >> Log.addContextDestructured "file" filePath + >> Log.addContextDestructured "version" version + >> Log.addContextDestructured "message" message + >> Log.addContextDestructured "range" fcsRangeToReplace + >> Log.addContextDestructured "change" change + ) + + text + with e -> + logger.error ( + Log.setMessage "Error applying {change} to document {file} for version {version} - {range}" + >> Log.addContextDestructured "file" filePath + >> Log.addContextDestructured "range" fcsRangeToReplace + >> Log.addContextDestructured "version" version + >> Log.addContextDestructured "change" change + >> Log.addExn e + ) + text + ) + + return (file, new CancellationTokenSource()) + + // let file = + // (file,c) + // ||> AList.fold(fun text change -> + // let version = change.TextDocument.Version + // (text, change.ContentChanges) + // ||> Seq.fold (fun text change -> + // match change.Range with + // | None -> // replace entire content + // // We want to update the DateTime here since TextDocumentDidChange will not have changes reflected on disk + // VolatileFile.Create(filePath, change.Text, version, DateTime.UtcNow) + // | Some rangeToReplace -> + // // replace just this slice + // let fcsRangeToReplace = protocolRangeToRange (UMX.untag filePath) rangeToReplace + // match text.Lines.ModifyText(fcsRangeToReplace, change.Text) with + // | Ok text -> + // VolatileFile.Create(text, version, DateTime.UtcNow) + + // | Error message -> + // logger.error ( + // Log.setMessage "Error applying change to document {file} for version {version}: {message} - {range}" + // >> Log.addContextDestructured "file" filePath + // >> Log.addContextDestructured "version" version + // >> Log.addContextDestructured "message" message + // >> Log.addContextDestructured "range" fcsRangeToReplace + // ) + + // text + // ) + // ) + // return! file |> AVal.map(fun f -> cts.Cancel() ;cts.Dispose(); f, new CancellationTokenSource()) + } + ) let cancelAllOpenFileCheckRequests () = - transact (fun () -> - let files = openFiles |> AMap.force + let files = openFiles |> AMap.force - for (_, fileVal) in files do - let (oldFile, cts: CancellationTokenSource) = fileVal |> AVal.force - cts.Cancel() - cts.Dispose() - fileVal.Value <- oldFile, new CancellationTokenSource()) + for (_, fileVal) in files do + let (oldFile, cts: CancellationTokenSource) = fileVal |> AVal.force + // try + cts.Cancel() + cts.Dispose() + // with _ -> () + transact (fun () -> fileVal.Value <- oldFile, new CancellationTokenSource()) + let cancelForFile filePath = + openFilesWithChanges |> AMap.tryFind filePath |> AVal.force + |> Option.iter(fun fileVal -> + let (oldFile, cts) = fileVal |> AVal.force + try + logger.info( + Log.setMessage "Cancelling {filePath} - {version}" + >> Log.addContextDestructured "filePath" filePath + >> Log.addContextDestructured "version" oldFile.Version + ) + cts.Cancel() + cts.Dispose() + with _ -> () + // transact(fun () -> + // fileVal.Value <- oldFile, new CancellationTokenSource() + // ) + ) let updateOpenFiles (file: VolatileFile) = let adder _ = @@ -635,13 +754,15 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let updater _ (v: cval<_>) = let (oldFile, cts: CancellationTokenSource) = v.Value - cts.Cancel() - cts.Dispose() + try + cts.Cancel() + cts.Dispose() + with _ -> () v.Value <- file, new CancellationTokenSource() transact (fun () -> openFiles.AddOrElse(file.Lines.FileName, adder, updater)) - let findFileInOpenFiles file = openFilesA |> AMap.tryFindA file + let findFileInOpenFiles file = openFilesWithChanges |> AMap.tryFindA file let forceFindOpenFileOrRead file = findFileInOpenFiles file @@ -684,7 +805,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar forceFindOpenFileOrRead filePath |> Result.map (fun (f, _) -> f.Lines) let openFilesToProjectOptions = - openFilesA + openFilesWithChanges |> AMap.mapAdaptiveValue (fun (info, cts) -> let file = info.Lines.FileName @@ -728,10 +849,11 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let checkUnusedOpens = async { try + let! ct = Async.CancellationToken let! unused = UnusedOpens.getUnusedOpens (tyRes.GetCheckResults, (fun i -> (source: ISourceText).GetLineString(i - 1))) - notifications.Trigger(NotificationEvent.UnusedOpens(filePath, (unused |> List.toArray))) + notifications.Trigger(NotificationEvent.UnusedOpens(filePath, (unused |> List.toArray)), ct) with e -> logger.error (Log.setMessage "checkUnusedOpens failed" >> Log.addExn e) } @@ -739,11 +861,12 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let checkUnusedDeclarations = async { try + let! ct = Async.CancellationToken let isScript = Utils.isAScript (UMX.untag filePath) let! unused = UnusedDeclarations.getUnusedDeclarations (tyRes.GetCheckResults, isScript) let unused = unused |> Seq.toArray - notifications.Trigger(NotificationEvent.UnusedDeclarations(filePath, unused)) + notifications.Trigger(NotificationEvent.UnusedDeclarations(filePath, unused), ct) with e -> logger.error (Log.setMessage "checkUnusedDeclarations failed" >> Log.addExn e) } @@ -753,9 +876,10 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar try let getSourceLine lineNo = source.GetLineString(lineNo - 1) + let! ct = Async.CancellationToken let! simplified = SimplifyNames.getSimplifiableNames (tyRes.GetCheckResults, getSourceLine) let simplified = Array.ofSeq simplified - notifications.Trigger(NotificationEvent.SimplifyNames(filePath, simplified)) + notifications.Trigger(NotificationEvent.SimplifyNames(filePath, simplified), ct) with e -> logger.error (Log.setMessage "checkSimplifiedNames failed" >> Log.addExn e) } @@ -789,7 +913,6 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar >> Log.addContextDestructured "hash" (file.Lines.GetHashCode()) >> Log.addContextDestructured "date" (file.Touched) ) - use cts = new CancellationTokenSource() cts.CancelAfter(TimeSpan.FromSeconds(60.)) @@ -797,7 +920,8 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar checker.ParseAndCheckFileInProject(file.Lines.FileName, (file.Lines.GetHashCode()), file.Lines, opts) |> Async.withCancellation cts.Token - notifications.Trigger(NotificationEvent.FileParsed(file.Lines.FileName)) + let! ct = Async.CancellationToken + notifications.Trigger(NotificationEvent.FileParsed(file.Lines.FileName), ct) match result with | Error e -> @@ -823,8 +947,8 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar |> Array.distinctBy (fun e -> e.Severity, e.ErrorNumber, e.StartLine, e.StartColumn, e.EndLine, e.EndColumn, e.Message) - NotificationEvent.ParseError(errors, file.Lines.FileName) - |> notifications.Trigger + notifications.Trigger(NotificationEvent.ParseError(errors, file.Lines.FileName), ct) + do! analyzeFile config (file.Lines.FileName, file.Version, file.Lines, parseAndCheck) @@ -853,7 +977,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar } let openFilesToParsedResults = - openFilesA + openFilesWithChanges |> AMap.mapAdaptiveValue (fun (info, cts) -> aval { let file = info.Lines.FileName @@ -875,7 +999,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let openFilesToRecentCheckedFilesResults = - openFilesA + openFilesWithChanges |> AMap.mapAdaptiveValue (fun (info, _) -> aval { let file = info.Lines.FileName @@ -891,7 +1015,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar }) let openFilesToCheckedFilesResults = - openFilesA + openFilesWithChanges |> AMap.mapAdaptiveValue (fun (info, cts) -> aval { let file = info.Lines.FileName @@ -902,10 +1026,10 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar match List.tryHead projectOptions with | Some (opts) -> let parseAndCheck = - Debug.measure "parseAndCheckFile" + Debug.measure $"parseAndCheckFile - {file}" <| fun () -> parseAndCheckFile checker info opts config - |> Async.RunSynchronouslyWithCTSafe(fun () -> cts.Token) + |> Async.RunSynchronouslyWithCTSafe (fun () -> cts.Token) return parseAndCheck | None -> return None @@ -1051,7 +1175,6 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let writeAbstractClassStub = AbstractClassStubGenerator.writeAbstractClassStub codeGenServer - let foo bar fizz = fizz * fizz let getAbstractClassStub tyRes objExprRange lines lineStr = Commands.getAbstractClassStub @@ -1120,6 +1243,9 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar UseTripleQuotedInterpolation.fix tryGetParseResultsForFile getRangeText RenameParamToMatchSignature.fix tryGetParseResultsForFile |]) + + + let forgetDocument (uri: DocumentUri) = let filePath = uri |> Path.FileUriToLocalPath |> Utils.normalizePath @@ -1161,7 +1287,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar |> AVal.force - if doesNotExist filePath || isOutsideWorkspace filePath then + if true || doesNotExist filePath || isOutsideWorkspace filePath then logger.info ( Log.setMessage "Removing cached data for {file}." >> Log.addContext "file" filePath @@ -1497,7 +1623,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar Some { TextDocumentSyncOptions.Default with OpenClose = Some true - Change = Some TextDocumentSyncKind.Full + Change = Some TextDocumentSyncKind.Incremental Save = Some { IncludeText = Some true } } FoldingRangeProvider = Some true SelectionRangeProvider = Some true @@ -1592,17 +1718,20 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar >> Log.addContextDestructured "parms" p ) - let filePath = p.TextDocument.GetFilePath() |> Utils.normalizePath - let changes = p.ContentChanges |> Array.head + let doc = p.TextDocument + let filePath = doc.GetFilePath() |> Utils.normalizePath - // We want to update the DateTime here since TextDocumentDidChange will not have changes reflected on disk - // TODO: Incremental changes - let file = - VolatileFile.Create(filePath, changes.Text, p.TextDocument.Version, DateTime.UtcNow) - updateOpenFiles file - forceGetTypeCheckResults filePath |> ignore + transact(fun () -> + cancelForFile filePath + textChanges.AddOrElse(filePath, (fun _ -> cset<_> [p]), (fun _ v -> v.Add p |> ignore)) + ) + // forceGetTypeCheckResults filePath |> ignore + // async { + // do! Async.Sleep(1000) + // forceGetTypeCheckResults filePath |> ignore + // } |> Async.Start return () with e -> @@ -1636,15 +1765,16 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar |> Option.defaultWith (fun () -> // Very unlikely to get here VolatileFile.Create(filePath, p.Text.Value, None, DateTime.UtcNow)) - - updateOpenFiles file - let knownFiles = openFilesA |> AMap.force + transact(fun () -> + updateOpenFiles file + textChanges.Remove filePath |> ignore + ) + let knownFiles = openFilesWithChanges |> AMap.force logger.info ( Log.setMessage "typechecking for files {files}" >> Log.addContextDestructured "files" knownFiles ) - cancelAllOpenFileCheckRequests () for (file, aFile) in knownFiles do @@ -1676,7 +1806,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar Log.setMessage "TextDocumentCompletion Request: {parms}" >> Log.addContextDestructured "parms" p ) - + do! Async.Sleep(100) // TextDocumentCompletion will get sent before TextDocumentDidChange sometimes let (filePath, pos) = getFilePathAndPosition p let! (namedText, _) = forceFindOpenFileOrRead filePath |> Result.ofStringErr From 91b4152e04b725287126b7071085bef096973bb8 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Sat, 15 Oct 2022 01:10:51 -0400 Subject: [PATCH 09/37] Simplifying File CancellionToken relationship --- src/FsAutoComplete.Core/FileSystem.fs | 1 + .../LspServers/AdaptiveFSharpLspServer.fs | 284 +++++++++--------- 2 files changed, 142 insertions(+), 143 deletions(-) diff --git a/src/FsAutoComplete.Core/FileSystem.fs b/src/FsAutoComplete.Core/FileSystem.fs index ebfcf555a..56b106f87 100644 --- a/src/FsAutoComplete.Core/FileSystem.fs +++ b/src/FsAutoComplete.Core/FileSystem.fs @@ -354,6 +354,7 @@ type VolatileFile = { Touched: DateTime Lines: NamedText Version: int option } + member this.FileName = this.Lines.FileName /// Updates the Lines value member this.SetLines(lines) = { this with Lines = lines } diff --git a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs index 881078b72..73f5f84c0 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs @@ -239,7 +239,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let scriptFileProjectOptions = Event() - let handleCommandEvents (n: NotificationEvent, ct : CancellationToken) = + let handleCommandEvents (n: NotificationEvent, ct: CancellationToken) = try async { try @@ -522,11 +522,9 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar projects |> Seq.iter (fun (proj: string, _) -> let not = - UMX.untag proj - |> ProjectResponse.ProjectLoading - |> NotificationEvent.Workspace - notifications.Trigger(not, CancellationToken.None) - ) + UMX.untag proj |> ProjectResponse.ProjectLoading |> NotificationEvent.Workspace + + notifications.Trigger(not, CancellationToken.None)) let projectOptions = loader.LoadProjects(projects |> Seq.map (fst >> UMX.untag) |> Seq.toList) @@ -567,6 +565,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let! checker = checker checker.ClearCaches() // if we got new projects assume we're gonna need to clear caches + let options = projectOptions |> List.map (fun o -> @@ -601,14 +600,11 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar Extra = extraInfo ProjectItems = projViewerItemsNormalized.Items Additionals = Map.empty } - let not = - ProjectResponse.Project(ws, false) - |> NotificationEvent.Workspace - notifications.Trigger(not, CancellationToken.None) - ) - let not = - ProjectResponse.WorkspaceLoad true - |> NotificationEvent.Workspace + + let not = ProjectResponse.Project(ws, false) |> NotificationEvent.Workspace + notifications.Trigger(not, CancellationToken.None)) + + let not = ProjectResponse.WorkspaceLoad true |> NotificationEvent.Workspace notifications.Trigger(not, CancellationToken.None) @@ -619,150 +615,147 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let fantomasLogger = LogProvider.getLoggerByName "Fantomas" let fantomasService: FantomasService = new LSPFantomasService() :> FantomasService - let openFiles = - cmap, cval> () + let openFiles = cmap, cval> () let openFilesA = openFiles |> AMap.map' (fun v -> v :> aval<_>) let textChanges = cmap, cset> () + let sortedTextChanges = - textChanges - |> AMap.map'( fun x -> x :> aset<_> - // ASet.sortBy(fun (x : DidChangeTextDocumentParams) -> x.TextDocument.Version.Value) - ) - let openFilesWithChanges = + textChanges + |> AMap.map' (fun x -> x :> aset<_> + // ASet.sortBy (fun (x: DidChangeTextDocumentParams) -> x.TextDocument.Version.Value) + ) + + let openFilesWithChanges: amap<_, aval> = openFilesA - |> AMap.map(fun filePath file -> - aval { - let! (file, cts) = file - and! changes = sortedTextChanges |> AMap.tryFind filePath - match changes with - | None -> return (file, cts) - | Some c -> - let! ps = c |> ASet.toAVal - let changes = ps |> Seq.sortBy(fun x -> x.TextDocument.Version.Value) |> Seq.collect(fun p -> p.ContentChanges |> Array.map(fun x -> x, p.TextDocument.Version.Value)) - // try - // cts.Cancel() ; - // cts.Dispose(); - // with _ -> - // () - let file = - (file, changes) - ||> Seq.fold (fun text (change, version) -> - match change.Range with - | None -> // replace entire content - // We want to update the DateTime here since TextDocumentDidChange will not have changes reflected on disk - VolatileFile.Create(filePath, change.Text, Some version, DateTime.UtcNow) - | Some rangeToReplace -> - // replace just this slice - let fcsRangeToReplace = protocolRangeToRange (UMX.untag filePath) rangeToReplace - try - match text.Lines.ModifyText(fcsRangeToReplace, change.Text) with - | Ok text -> - VolatileFile.Create(text, Some version, DateTime.UtcNow) - - | Error message -> - logger.error ( - Log.setMessage "Error applying {change} to document {file} for version {version} - {range} : {message} " - >> Log.addContextDestructured "file" filePath - >> Log.addContextDestructured "version" version - >> Log.addContextDestructured "message" message - >> Log.addContextDestructured "range" fcsRangeToReplace - >> Log.addContextDestructured "change" change - ) + |> AMap.map (fun filePath file -> + aval { + let! file = file + and! changes = sortedTextChanges |> AMap.tryFind filePath - text - with e -> - logger.error ( - Log.setMessage "Error applying {change} to document {file} for version {version} - {range}" - >> Log.addContextDestructured "file" filePath - >> Log.addContextDestructured "range" fcsRangeToReplace - >> Log.addContextDestructured "version" version - >> Log.addContextDestructured "change" change - >> Log.addExn e - ) - text - ) + match changes with + | None -> return (file, new CancellationTokenSource()) + | Some c -> + let! ps = c |> ASet.toAVal + + let changes = + ps + |> Seq.sortBy (fun x -> x.TextDocument.Version.Value) + |> Seq.collect (fun p -> p.ContentChanges |> Array.map (fun x -> x, p.TextDocument.Version.Value)) + + let file = + (file, changes) + ||> Seq.fold (fun text (change, version) -> + match change.Range with + | None -> // replace entire content + // We want to update the DateTime here since TextDocumentDidChange will not have changes reflected on disk + VolatileFile.Create(filePath, change.Text, Some version, DateTime.UtcNow) + | Some rangeToReplace -> + // replace just this slice + let fcsRangeToReplace = protocolRangeToRange (UMX.untag filePath) rangeToReplace + + try + match text.Lines.ModifyText(fcsRangeToReplace, change.Text) with + | Ok text -> VolatileFile.Create(text, Some version, DateTime.UtcNow) + + | Error message -> + logger.error ( + Log.setMessage + "Error applying {change} to document {file} for version {version} - {range} : {message} " + >> Log.addContextDestructured "file" filePath + >> Log.addContextDestructured "version" version + >> Log.addContextDestructured "message" message + >> Log.addContextDestructured "range" fcsRangeToReplace + >> Log.addContextDestructured "change" change + ) + + text + with e -> + logger.error ( + Log.setMessage "Error applying {change} to document {file} for version {version} - {range}" + >> Log.addContextDestructured "file" filePath + >> Log.addContextDestructured "range" fcsRangeToReplace + >> Log.addContextDestructured "version" version + >> Log.addContextDestructured "change" change + >> Log.addExn e + ) + + text) + + return (file, new CancellationTokenSource()) + // way worse on memory + // let file = + // (file, c) + // ||> AList.fold (fun text change -> + // let version = change.TextDocument.Version + + // (text, change.ContentChanges) + // ||> Seq.fold (fun text change -> + // match change.Range with + // | None -> // replace entire content + // // We want to update the DateTime here since TextDocumentDidChange will not have changes reflected on disk + // VolatileFile.Create(filePath, change.Text, version, DateTime.UtcNow) + // | Some rangeToReplace -> + // // replace just this slice + // let fcsRangeToReplace = protocolRangeToRange (UMX.untag filePath) rangeToReplace + + // match text.Lines.ModifyText(fcsRangeToReplace, change.Text) with + // | Ok text -> VolatileFile.Create(text, version, DateTime.UtcNow) + + // | Error message -> + // logger.error ( + // Log.setMessage + // "Error applying change to document {file} for version {version}: {message} - {range}" + // >> Log.addContextDestructured "file" filePath + // >> Log.addContextDestructured "version" version + // >> Log.addContextDestructured "message" message + // >> Log.addContextDestructured "range" fcsRangeToReplace + // ) + + // text)) + + // return! file |> AVal.map (fun f -> f, new CancellationTokenSource()) + }) - return (file, new CancellationTokenSource()) - - // let file = - // (file,c) - // ||> AList.fold(fun text change -> - // let version = change.TextDocument.Version - // (text, change.ContentChanges) - // ||> Seq.fold (fun text change -> - // match change.Range with - // | None -> // replace entire content - // // We want to update the DateTime here since TextDocumentDidChange will not have changes reflected on disk - // VolatileFile.Create(filePath, change.Text, version, DateTime.UtcNow) - // | Some rangeToReplace -> - // // replace just this slice - // let fcsRangeToReplace = protocolRangeToRange (UMX.untag filePath) rangeToReplace - // match text.Lines.ModifyText(fcsRangeToReplace, change.Text) with - // | Ok text -> - // VolatileFile.Create(text, version, DateTime.UtcNow) - - // | Error message -> - // logger.error ( - // Log.setMessage "Error applying change to document {file} for version {version}: {message} - {range}" - // >> Log.addContextDestructured "file" filePath - // >> Log.addContextDestructured "version" version - // >> Log.addContextDestructured "message" message - // >> Log.addContextDestructured "range" fcsRangeToReplace - // ) - - // text - // ) - // ) - // return! file |> AVal.map(fun f -> cts.Cancel() ;cts.Dispose(); f, new CancellationTokenSource()) - } - ) let cancelAllOpenFileCheckRequests () = - let files = openFiles |> AMap.force + let files = openFilesWithChanges |> AMap.force for (_, fileVal) in files do + () let (oldFile, cts: CancellationTokenSource) = fileVal |> AVal.force - // try cts.Cancel() cts.Dispose() - // with _ -> () - transact (fun () -> fileVal.Value <- oldFile, new CancellationTokenSource()) - let cancelForFile filePath = - openFilesWithChanges |> AMap.tryFind filePath |> AVal.force - |> Option.iter(fun fileVal -> - let (oldFile, cts) = fileVal |> AVal.force - try - logger.info( - Log.setMessage "Cancelling {filePath} - {version}" - >> Log.addContextDestructured "filePath" filePath - >> Log.addContextDestructured "version" oldFile.Version - ) - cts.Cancel() - cts.Dispose() - with _ -> () - // transact(fun () -> - // fileVal.Value <- oldFile, new CancellationTokenSource() - // ) - ) - let updateOpenFiles (file: VolatileFile) = - - let adder _ = - cval (file, new CancellationTokenSource()) + let cancelForFile filePath = + openFilesWithChanges + |> AMap.tryFind filePath + |> AVal.force + |> Option.iter (fun fileVal -> + let (oldFile, cts) = fileVal |> AVal.force - let updater _ (v: cval<_>) = - let (oldFile, cts: CancellationTokenSource) = v.Value try + logger.info ( + Log.setMessage "Cancelling {filePath} - {version}" + >> Log.addContextDestructured "filePath" filePath + >> Log.addContextDestructured "version" oldFile.Version + ) + cts.Cancel() cts.Dispose() - with _ -> () - v.Value <- file, new CancellationTokenSource() + with _ -> + ()) + + let updateOpenFiles (file: VolatileFile) = + cancelForFile file.FileName + let adder _ = cval file + let updater _ (v: cval<_>) = v.Value <- file transact (fun () -> openFiles.AddOrElse(file.Lines.FileName, adder, updater)) - let findFileInOpenFiles file = openFilesWithChanges |> AMap.tryFindA file + let findFileInOpenFiles file = + openFilesWithChanges |> AMap.tryFindA file let forceFindOpenFileOrRead file = findFileInOpenFiles file @@ -850,6 +843,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar async { try let! ct = Async.CancellationToken + let! unused = UnusedOpens.getUnusedOpens (tyRes.GetCheckResults, (fun i -> (source: ISourceText).GetLineString(i - 1))) @@ -913,6 +907,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar >> Log.addContextDestructured "hash" (file.Lines.GetHashCode()) >> Log.addContextDestructured "date" (file.Touched) ) + use cts = new CancellationTokenSource() cts.CancelAfter(TimeSpan.FromSeconds(60.)) @@ -1029,7 +1024,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar Debug.measure $"parseAndCheckFile - {file}" <| fun () -> parseAndCheckFile checker info opts config - |> Async.RunSynchronouslyWithCTSafe (fun () -> cts.Token) + |> Async.RunSynchronouslyWithCTSafe(fun () -> cts.Token) return parseAndCheck | None -> return None @@ -1723,10 +1718,9 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let filePath = doc.GetFilePath() |> Utils.normalizePath - transact(fun () -> + transact (fun () -> cancelForFile filePath - textChanges.AddOrElse(filePath, (fun _ -> cset<_> [p]), (fun _ v -> v.Add p |> ignore)) - ) + textChanges.AddOrElse(filePath, (fun _ -> cset<_> [ p ]), (fun _ v -> v.Add p |> ignore))) // forceGetTypeCheckResults filePath |> ignore // async { // do! Async.Sleep(1000) @@ -1753,6 +1747,8 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar >> Log.addContextDestructured "parms" p ) + cancelAllOpenFileCheckRequests () + let doc = p.TextDocument let filePath = doc.GetFilePath() |> Utils.normalizePath @@ -1765,17 +1761,18 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar |> Option.defaultWith (fun () -> // Very unlikely to get here VolatileFile.Create(filePath, p.Text.Value, None, DateTime.UtcNow)) - transact(fun () -> + + transact (fun () -> updateOpenFiles file - textChanges.Remove filePath |> ignore - ) + textChanges.Remove filePath |> ignore) + let knownFiles = openFilesWithChanges |> AMap.force logger.info ( Log.setMessage "typechecking for files {files}" >> Log.addContextDestructured "files" knownFiles ) - cancelAllOpenFileCheckRequests () + for (file, aFile) in knownFiles do let (_, cts) = aFile |> AVal.force @@ -1806,6 +1803,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar Log.setMessage "TextDocumentCompletion Request: {parms}" >> Log.addContextDestructured "parms" p ) + do! Async.Sleep(100) // TextDocumentCompletion will get sent before TextDocumentDidChange sometimes let (filePath, pos) = getFilePathAndPosition p From 0d47557980d16dd7635fca5dd546f9518e660090 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Sat, 15 Oct 2022 01:26:23 -0400 Subject: [PATCH 10/37] Typecheck saved file first --- .../LspServers/AdaptiveFSharpLspServer.fs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs index 73f5f84c0..58c948295 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs @@ -725,8 +725,12 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar for (_, fileVal) in files do () let (oldFile, cts: CancellationTokenSource) = fileVal |> AVal.force - cts.Cancel() - cts.Dispose() + + try + cts.Cancel() + cts.Dispose() + with e -> + () let cancelForFile filePath = openFilesWithChanges @@ -1766,14 +1770,21 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar updateOpenFiles file textChanges.Remove filePath |> ignore) - let knownFiles = openFilesWithChanges |> AMap.force + let knownFiles = + openFilesWithChanges + |> AMap.force + |> Seq.sortWith (fun (name, _) (name2, _) -> + // Force the current document to be checked first + if name = filePath || name2 = filePath then + -1 + else + compare name name2) logger.info ( Log.setMessage "typechecking for files {files}" >> Log.addContextDestructured "files" knownFiles ) - for (file, aFile) in knownFiles do let (_, cts) = aFile |> AVal.force From 91b06a1422bbb0e239d8a66fe8d07feaa4097618 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Sat, 15 Oct 2022 21:43:18 -0400 Subject: [PATCH 11/37] Better completion handling --- .../ParseAndCheckResults.fs | 3 +- .../LspServers/AdaptiveFSharpLspServer.fs | 55 ++++++++++++------- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/src/FsAutoComplete.Core/ParseAndCheckResults.fs b/src/FsAutoComplete.Core/ParseAndCheckResults.fs index fbf6d1a0d..58b9316f8 100644 --- a/src/FsAutoComplete.Core/ParseAndCheckResults.fs +++ b/src/FsAutoComplete.Core/ParseAndCheckResults.fs @@ -580,10 +580,11 @@ type ParseAndCheckResults | 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 + | None when isEmpty -> return None | _ -> let results = - checkResults.GetDeclarationListInfo(Some parseResults, pos.Line, lineStr, longName, getAllSymbols) + checkResults.GetDeclarationListInfo(Some parseResults, pos.Line, lineStr, longName, getSymbols) let getKindPriority = function diff --git a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs index 58c948295..655450f80 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs @@ -950,7 +950,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar - do! analyzeFile config (file.Lines.FileName, file.Version, file.Lines, parseAndCheck) + Async.Start(analyzeFile config (file.Lines.FileName, file.Version, file.Lines, parseAndCheck), ct) // LargeObjectHeap gets fragmented easily for really large files, which F# can easily have. // Yes this seems excessive doing this every time we type check but it's the best current kludge. @@ -1721,15 +1721,15 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let doc = p.TextDocument let filePath = doc.GetFilePath() |> Utils.normalizePath - transact (fun () -> cancelForFile filePath textChanges.AddOrElse(filePath, (fun _ -> cset<_> [ p ]), (fun _ v -> v.Add p |> ignore))) // forceGetTypeCheckResults filePath |> ignore - // async { - // do! Async.Sleep(1000) - // forceGetTypeCheckResults filePath |> ignore - // } |> Async.Start + async { + do! Async.Sleep(10) + forceGetTypeCheckResults filePath |> ignore + } + |> Async.Start return () with e -> @@ -1815,22 +1815,22 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar >> Log.addContextDestructured "parms" p ) - do! Async.Sleep(100) // TextDocumentCompletion will get sent before TextDocumentDidChange sometimes let (filePath, pos) = getFilePathAndPosition p - let! (namedText, _) = forceFindOpenFileOrRead filePath |> Result.ofStringErr + let! (namedText2, _) = forceFindOpenFileOrRead filePath |> Result.ofStringErr - let! lineStr = namedText.Lines |> tryGetLineStr pos |> Result.ofStringErr + let! lineStr2 = namedText2.Lines |> tryGetLineStr pos |> Result.ofStringErr let completionList = { IsIncomplete = false Items = KeywordList.hashSymbolCompletionItems } - if lineStr.StartsWith "#" then + if lineStr2.StartsWith "#" then let completionList = { IsIncomplete = false Items = KeywordList.hashSymbolCompletionItems } + return! success (Some completionList) else let config = AVal.force config @@ -1847,7 +1847,25 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let getCompletions = asyncResult { - let! typeCheckResults = forceGetTypeCheckResults filePath + + let! (namedText, _) = forceFindOpenFileOrRead filePath + let! lineStr = namedText.Lines |> tryGetLineStr pos + + let quickCheck = + aval { + let! checker = checker + and! projectOptions = getProjectOptionsForFile filePath + return checker.TryGetRecentCheckResultsForFile(filePath, List.head projectOptions, namedText.Lines) + } + + let! typeCheckResults = + asyncResult { + // Try to get the latest text as TextDocumentDidChange and this + // might not have been sent in the correct order + match AVal.force quickCheck with + | Some s -> return s + | None -> return! forceGetTypeCheckResults filePath + } let getAllSymbols () = if config.ExternalAutocomplete then @@ -1855,21 +1873,20 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar else [] - match! + let! (decls, residue, shouldKeywords) = Debug.measure "TextDocumentCompletion.TryGetCompletions" (fun () -> - typeCheckResults.TryGetCompletions pos lineStr None getAllSymbols) - with - | None -> return None - | Some (decls, residue, shouldKeywords) -> - return Some(decls, residue, shouldKeywords, typeCheckResults, getAllSymbols) + typeCheckResults.TryGetCompletions pos lineStr None getAllSymbols + |> AsyncResult.ofOption (fun () -> "No TryGetCompletions results")) + + return Some(decls, residue, shouldKeywords, typeCheckResults, getAllSymbols, namedText) } match! - retryAsyncOption (TimeSpan.FromMilliseconds(100.)) 5 getCompletions + retryAsyncOption (TimeSpan.FromMilliseconds(10.)) 100 getCompletions |> AsyncResult.ofStringErr with | None -> return! success (Some completionList) - | Some (decls, residue, shouldKeywords, typeCheckResults, getAllSymbols) -> + | Some (decls, residue, shouldKeywords, typeCheckResults, _, namedText) -> return! Debug.measure "TextDocumentCompletion.TryGetCompletions success" From 0e184f933fd116fecfe9808a3118f0f3c0210efc Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Sat, 15 Oct 2022 22:29:55 -0400 Subject: [PATCH 12/37] Start things async to get type check results faster --- .../LspServers/AdaptiveFSharpLspServer.fs | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs index 655450f80..6f2e9f422 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs @@ -440,8 +440,8 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar do disposables.Add( (notifications.Publish :> IObservable<_>) - .BufferedDebounce(TimeSpan.FromMilliseconds(200.)) - .SelectMany(fun l -> l.Distinct()) + // .BufferedDebounce(TimeSpan.FromMilliseconds(200.)) + // .SelectMany(fun l -> l.Distinct()) .Subscribe(fun e -> handleCommandEvents e) ) @@ -916,8 +916,9 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar cts.CancelAfter(TimeSpan.FromSeconds(60.)) let! result = - checker.ParseAndCheckFileInProject(file.Lines.FileName, (file.Lines.GetHashCode()), file.Lines, opts) - |> Async.withCancellation cts.Token + Debug.measureAsync $"checker.ParseAndCheckFileInProject - {file.Lines.FileName}" <| + checker.ParseAndCheckFileInProject(file.Lines.FileName, (file.Lines.GetHashCode()), file.Lines, opts) + |> Async.withCancellation cts.Token let! ct = Async.CancellationToken notifications.Trigger(NotificationEvent.FileParsed(file.Lines.FileName), ct) @@ -937,28 +938,32 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar >> Log.addContextDestructured "file" file.Lines.FileName ) + Async.Start(async { + let checkErrors = parseAndCheck.GetParseResults.Diagnostics + let parseErrors = parseAndCheck.GetCheckResults.Diagnostics - let checkErrors = parseAndCheck.GetParseResults.Diagnostics - let parseErrors = parseAndCheck.GetCheckResults.Diagnostics + let errors = + Array.append checkErrors parseErrors + |> Array.distinctBy (fun e -> + e.Severity, e.ErrorNumber, e.StartLine, e.StartColumn, e.EndLine, e.EndColumn, e.Message) - let errors = - Array.append checkErrors parseErrors - |> Array.distinctBy (fun e -> - e.Severity, e.ErrorNumber, e.StartLine, e.StartColumn, e.EndLine, e.EndColumn, e.Message) + notifications.Trigger(NotificationEvent.ParseError(errors, file.Lines.FileName), ct) + }, ct) - notifications.Trigger(NotificationEvent.ParseError(errors, file.Lines.FileName), ct) Async.Start(analyzeFile config (file.Lines.FileName, file.Version, file.Lines, parseAndCheck), ct) - // LargeObjectHeap gets fragmented easily for really large files, which F# can easily have. - // Yes this seems excessive doing this every time we type check but it's the best current kludge. - System.Runtime.GCSettings.LargeObjectHeapCompactionMode <- - System.Runtime.GCLargeObjectHeapCompactionMode.CompactOnce - GC.Collect() - GC.WaitForPendingFinalizers() + Async.Start( async { + // LargeObjectHeap gets fragmented easily for really large files, which F# can easily have. + // Yes this seems excessive doing this every time we type check but it's the best current kludge. + System.Runtime.GCSettings.LargeObjectHeapCompactionMode <- + System.Runtime.GCLargeObjectHeapCompactionMode.CompactOnce + GC.Collect() + GC.WaitForPendingFinalizers() + }, ct) return parseAndCheck } From 36cad03a53774b8311a3b490d7081a2995a49cf9 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Sat, 15 Oct 2022 23:16:19 -0400 Subject: [PATCH 13/37] Speed up completions by using stale results --- paket.dependencies | 1 + paket.lock | 22 ++++++++ .../CompilerServiceInterface.fs | 51 ++++++++++++++++--- src/FsAutoComplete.Core/paket.references | 1 + .../LspServers/AdaptiveFSharpLspServer.fs | 3 +- 5 files changed, 70 insertions(+), 8 deletions(-) diff --git a/paket.dependencies b/paket.dependencies index e853b8c67..c1c7c7755 100644 --- a/paket.dependencies +++ b/paket.dependencies @@ -50,6 +50,7 @@ nuget YoloDev.Expecto.TestSdk nuget AltCover nuget GitHubActionsTestLogger nuget Ionide.LanguageServerProtocol +nuget Microsoft.Extensions.Caching.Memory group Build source https://api.nuget.org/v3/index.json diff --git a/paket.lock b/paket.lock index b916a8568..828b430c0 100644 --- a/paket.lock +++ b/paket.lock @@ -176,12 +176,33 @@ NUGET System.Text.Encoding.CodePages (>= 4.0.1) - restriction: || (&& (== net7.0) (< net6.0)) (== netstandard2.0) Microsoft.CodeCoverage (17.3) - restriction: || (== net6.0) (== net7.0) (&& (== netstandard2.0) (>= net45)) (&& (== netstandard2.0) (>= netcoreapp1.0)) Microsoft.DotNet.PlatformAbstractions (3.1.6) - restriction: || (== net6.0) (== net7.0) (&& (== netstandard2.0) (>= net5.0)) + Microsoft.Extensions.Caching.Abstractions (6.0) + Microsoft.Extensions.Primitives (>= 6.0) + Microsoft.Extensions.Caching.Memory (6.0.1) + Microsoft.Extensions.Caching.Abstractions (>= 6.0) + Microsoft.Extensions.DependencyInjection.Abstractions (>= 6.0) + Microsoft.Extensions.Logging.Abstractions (>= 6.0) + Microsoft.Extensions.Options (>= 6.0) + Microsoft.Extensions.Primitives (>= 6.0) + Microsoft.Extensions.DependencyInjection.Abstractions (6.0) + Microsoft.Bcl.AsyncInterfaces (>= 6.0) - restriction: || (&& (== net6.0) (>= net461)) (&& (== net6.0) (< netstandard2.1)) (&& (== net7.0) (>= net461)) (&& (== net7.0) (< netstandard2.1)) (== netstandard2.0) + System.Threading.Tasks.Extensions (>= 4.5.4) - restriction: || (&& (== net6.0) (>= net461)) (&& (== net6.0) (< netstandard2.1)) (&& (== net7.0) (>= net461)) (&& (== net7.0) (< netstandard2.1)) (== netstandard2.0) Microsoft.Extensions.DependencyModel (6.0) - restriction: || (== net6.0) (== net7.0) (&& (== netstandard2.0) (>= net5.0)) System.Buffers (>= 4.5.1) System.Memory (>= 4.5.4) System.Runtime.CompilerServices.Unsafe (>= 6.0) System.Text.Encodings.Web (>= 6.0) System.Text.Json (>= 6.0) + Microsoft.Extensions.Logging.Abstractions (6.0.2) + System.Buffers (>= 4.5.1) - restriction: || (&& (== net6.0) (>= net461)) (&& (== net7.0) (>= net461)) (&& (== net7.0) (< net6.0)) (== netstandard2.0) + System.Memory (>= 4.5.4) - restriction: || (&& (== net6.0) (>= net461)) (&& (== net7.0) (>= net461)) (&& (== net7.0) (< net6.0)) (== netstandard2.0) + Microsoft.Extensions.Options (6.0) + Microsoft.Extensions.DependencyInjection.Abstractions (>= 6.0) + Microsoft.Extensions.Primitives (>= 6.0) + System.ComponentModel.Annotations (>= 5.0) - restriction: || (&& (== net6.0) (< netstandard2.1)) (&& (== net7.0) (< netstandard2.1)) (== netstandard2.0) + Microsoft.Extensions.Primitives (6.0) + System.Memory (>= 4.5.4) - restriction: || (&& (== net6.0) (>= net461)) (&& (== net6.0) (< netcoreapp3.1)) (&& (== net7.0) (>= net461)) (&& (== net7.0) (< netcoreapp3.1)) (== netstandard2.0) + System.Runtime.CompilerServices.Unsafe (>= 6.0) Microsoft.NET.StringTools (17.3.1) - copy_local: false System.Memory (>= 4.5.5) System.Runtime.CompilerServices.Unsafe (>= 6.0) @@ -319,6 +340,7 @@ NUGET System.Runtime.CompilerServices.Unsafe (>= 6.0) System.CommandLine (2.0.0-beta4.22272.1) System.Memory (>= 4.5.4) - restriction: || (&& (== net7.0) (< net6.0)) (== netstandard2.0) + System.ComponentModel.Annotations (5.0) - restriction: || (&& (== net6.0) (< netstandard2.1)) (&& (== net7.0) (< netstandard2.1)) (== netstandard2.0) System.Configuration.ConfigurationManager (6.0) System.Security.Cryptography.ProtectedData (>= 6.0) System.Security.Permissions (>= 6.0) diff --git a/src/FsAutoComplete.Core/CompilerServiceInterface.fs b/src/FsAutoComplete.Core/CompilerServiceInterface.fs index 20b11a1ec..5a3aa080b 100644 --- a/src/FsAutoComplete.Core/CompilerServiceInterface.fs +++ b/src/FsAutoComplete.Core/CompilerServiceInterface.fs @@ -9,6 +9,11 @@ open Ionide.ProjInfo.ProjectSystem open FSharp.UMX open FSharp.Compiler.EditorServices open FSharp.Compiler.Symbols +open Microsoft.Extensions.Caching.Memory +open System +open FsToolkit.ErrorHandling + + type Version = int @@ -26,6 +31,9 @@ type FSharpCompilerServiceChecker(hasAnalyzers) = let entityCache = EntityCache() + let mutable lastCheckResults: IMemoryCache = + new MemoryCache(MemoryCacheOptions(SizeLimit = Nullable<_>(20L))) + let checkerLogger = LogProvider.getLoggerByName "Checker" let optsLogger = LogProvider.getLoggerByName "Opts" @@ -228,7 +236,11 @@ type FSharpCompilerServiceChecker(hasAnalyzers) = scriptTypecheckRequirementsChanged.Publish /// This function is called when the entire environment is known to have changed for reasons not encoded in the ProjectOptions of any project/compilation. - member _.ClearCaches () = + member _.ClearCaches() = + // todo clear lastCheckResults + let oldlastCheckResults = lastCheckResults + lastCheckResults <- new MemoryCache(MemoryCacheOptions(SizeLimit = Nullable<_>(20L))) + oldlastCheckResults.Dispose() checker.InvalidateAll() checker.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients() @@ -239,13 +251,14 @@ type FSharpCompilerServiceChecker(hasAnalyzers) = checker.ParseFile(path, source, fpo) member __.ParseAndCheckFileInProject(filePath: string, version, source: ISourceText, options) = - async { + asyncResult { let opName = sprintf "ParseAndCheckFileInProject - %A" filePath checkerLogger.info (Log.setMessage "{opName}" >> Log.addContextDestructured "opName" opName) let options = clearProjectReferences options let path = UMX.untag filePath + try let! (p, c) = checker.ParseAndCheckFileInProject(path, version, source, options, userOpName = opName) @@ -259,18 +272,37 @@ type FSharpCompilerServiceChecker(hasAnalyzers) = >> Log.addContextDestructured "errors" (List.ofArray p.Diagnostics) ) - return ResultOrString.Error(sprintf "Check aborted (%A). Errors: %A" c parseErrors) + return! ResultOrString.Error(sprintf "Check aborted (%A). Errors: %A" c parseErrors) | FSharpCheckFileAnswer.Succeeded (c) -> checkerLogger.info ( Log.setMessage "{opName} completed successfully" >> Log.addContextDestructured "opName" opName ) - return Ok(ParseAndCheckResults(p, c, entityCache)) + let r = ParseAndCheckResults(p, c, entityCache) + + let ops = + MemoryCacheEntryOptions() + .SetSize(1) + .SetSlidingExpiration(TimeSpan.FromMinutes(2.)) + + return lastCheckResults.Set(filePath, r, ops) with ex -> - return ResultOrString.Error(ex.ToString()) + return! ResultOrString.Error(ex.ToString()) } + member _.TryGetLastCheckResultForFile(file: string) = + let opName = sprintf "TryGetLastCheckResultForFile - %A" file + + checkerLogger.info ( + Log.setMessage "{opName}" + >> Log.addContextDestructured "opName" opName + + ) + match lastCheckResults.TryGetValue(file) with + | (true, v) -> Some v + | _ -> None + member __.TryGetRecentCheckResultsForFile(file: string, options, source: ISourceText) = let opName = sprintf "TryGetRecentCheckResultsForFile - %A" file @@ -278,13 +310,20 @@ type FSharpCompilerServiceChecker(hasAnalyzers) = Log.setMessage "{opName} - {hash}" >> Log.addContextDestructured "opName" opName >> Log.addContextDestructured "hash" (source.GetHashCode() |> int) + ) let options = clearProjectReferences options let result = checker.TryGetRecentCheckResultsForFile(UMX.untag file, options, sourceText = source, userOpName = opName) - |> Option.map (fun (pr, cr, _) -> ParseAndCheckResults(pr, cr, entityCache)) + |> Option.map (fun (pr, cr, version) -> + checkerLogger.info ( + Log.setMessage "{opName} - got results - {version}" + >> Log.addContextDestructured "version" version + ) + + ParseAndCheckResults(pr, cr, entityCache)) checkerLogger.info ( Log.setMessage "{opName} - {hash} - cacheHit {cacheHit}" diff --git a/src/FsAutoComplete.Core/paket.references b/src/FsAutoComplete.Core/paket.references index 2290a3a13..323acb9d2 100644 --- a/src/FsAutoComplete.Core/paket.references +++ b/src/FsAutoComplete.Core/paket.references @@ -13,3 +13,4 @@ System.Reflection.Metadata Microsoft.Build.Utilities.Core Ionide.LanguageServerProtocol Ionide.KeepAChangelog.Tasks +Microsoft.Extensions.Caching.Memory diff --git a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs index 6f2e9f422..53d823d21 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs @@ -1859,8 +1859,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let quickCheck = aval { let! checker = checker - and! projectOptions = getProjectOptionsForFile filePath - return checker.TryGetRecentCheckResultsForFile(filePath, List.head projectOptions, namedText.Lines) + return checker.TryGetLastCheckResultForFile(filePath) } let! typeCheckResults = From 5a0c0034ce7ae1ce4a5c339739712e57bdcdddc9 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Sun, 16 Oct 2022 14:53:05 -0400 Subject: [PATCH 14/37] Better error handling for ModifyText --- src/FsAutoComplete.Core/FileSystem.fs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/FsAutoComplete.Core/FileSystem.fs b/src/FsAutoComplete.Core/FileSystem.fs index 56b106f87..c6a3091e7 100644 --- a/src/FsAutoComplete.Core/FileSystem.fs +++ b/src/FsAutoComplete.Core/FileSystem.fs @@ -260,8 +260,8 @@ type NamedText(fileName: string, str: string) = member x.ModifyText(m: FSharp.Compiler.Text.Range, text: string) : Result = result { let startRange, endRange = x.SplitAt(m) - let! startText = x[startRange] |> Result.mapError(fun x -> $"startRange -> {x}") - let! endText = x[endRange] |> Result.mapError(fun x -> $"endRange -> {x}") + let! startText = x[startRange] |> Result.mapError (fun x -> $"startRange -> {x}") + let! endText = x[endRange] |> Result.mapError (fun x -> $"endRange -> {x}") let totalText = startText + text + endText return NamedText(x.FileName, totalText) } @@ -354,6 +354,7 @@ type VolatileFile = { Touched: DateTime Lines: NamedText Version: int option } + member this.FileName = this.Lines.FileName /// Updates the Lines value From 41b799209aeb96fb498b6eeaaf7c8987b874d1f5 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Sun, 16 Oct 2022 14:55:39 -0400 Subject: [PATCH 15/37] More resilient Completion checks --- .../ParseAndCheckResults.fs | 7 +++- .../LspServers/AdaptiveFSharpLspServer.fs | 37 ++++++++++--------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/FsAutoComplete.Core/ParseAndCheckResults.fs b/src/FsAutoComplete.Core/ParseAndCheckResults.fs index 58b9316f8..fa39c0c78 100644 --- a/src/FsAutoComplete.Core/ParseAndCheckResults.fs +++ b/src/FsAutoComplete.Core/ParseAndCheckResults.fs @@ -549,6 +549,11 @@ type ParseAndCheckResults let residue = longName.PartialIdent + logger.info ( + Log.setMessage "TryGetCompletions - lineStr: {lineStr}" + >> Log.addContextDestructured "lineStr" lineStr + ) + logger.info ( Log.setMessage "TryGetCompletions - long name: {longName}" >> Log.addContextDestructured "longName" longName @@ -580,9 +585,7 @@ type ParseAndCheckResults | 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 - | None when isEmpty -> return None | _ -> - let results = checkResults.GetDeclarationListInfo(Some parseResults, pos.Line, lineStr, longName, getSymbols) diff --git a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs index 53d823d21..374bb7f71 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs @@ -211,7 +211,6 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar transact (fun () -> config.Value <- c) mutableConfigChanges |> AVal.force - let tfmConfig = config |> AVal.map (fun c -> @@ -1058,16 +1057,16 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar |> Result.ofOption (fun () -> $"No parse results for {filePath}") let forceGetTypeCheckResults filePath = - let tyResults = getTypeCheckResults (filePath) + let tyResults = getTypeCheckResults (filePath) - match getRecentTypeCheckResults filePath |> AVal.force with - | Some s -> - if lock tyResults (fun () -> tyResults.OutOfDate) then - Async.Start(async { tyResults |> AVal.force |> ignore }) + match getRecentTypeCheckResults filePath |> AVal.force with + | Some s -> + if lock tyResults (fun () -> tyResults.OutOfDate) then + Async.Start(async { tyResults |> AVal.force |> ignore }) - Some s - | None -> tyResults |> AVal.force - |> Result.ofOption (fun () -> $"No typecheck results for {filePath}") + Some s + | None -> tyResults |> AVal.force + |> Result.ofOption (fun () -> $"No typecheck results for {filePath}") let openFilesToCheckedDeclarations = openFilesToCheckedFilesResults @@ -1826,10 +1825,6 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let! lineStr2 = namedText2.Lines |> tryGetLineStr pos |> Result.ofStringErr - let completionList = - { IsIncomplete = false - Items = KeywordList.hashSymbolCompletionItems } - if lineStr2.StartsWith "#" then let completionList = { IsIncomplete = false @@ -1864,8 +1859,6 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let! typeCheckResults = asyncResult { - // Try to get the latest text as TextDocumentDidChange and this - // might not have been sent in the correct order match AVal.force quickCheck with | Some s -> return s | None -> return! forceGetTypeCheckResults filePath @@ -1876,6 +1869,16 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar typeCheckResults.GetAllEntities true else [] + // TextDocumentCompletion will sometimes come in before TextDocumentDidChange + // This will require the trigger character to be at the place VSCode says it is + // Otherwise we'll fail here and our retry logic will come into place + do! + match p.Context with + | Some({triggerKind = CompletionTriggerKind.TriggerCharacter } as context) -> + namedText.Lines.TryGetChar pos = context.triggerCharacter + | _ -> true + |> Result.requireTrue $"TextDocumentCompletion was sent before TextDocumentDidChange" + let! (decls, residue, shouldKeywords) = Debug.measure "TextDocumentCompletion.TryGetCompletions" (fun () -> @@ -1889,8 +1892,8 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar retryAsyncOption (TimeSpan.FromMilliseconds(10.)) 100 getCompletions |> AsyncResult.ofStringErr with - | None -> return! success (Some completionList) - | Some (decls, residue, shouldKeywords, typeCheckResults, _, namedText) -> + | None -> return! success (None) + | Some (decls, _, shouldKeywords, typeCheckResults, _, namedText) -> return! Debug.measure "TextDocumentCompletion.TryGetCompletions success" From cccb24500875708676864053a15fb31648402384 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Sun, 16 Oct 2022 14:57:50 -0400 Subject: [PATCH 16/37] be more resilient with no finding typecheck result --- .../LspServers/AdaptiveFSharpLspServer.fs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs index 374bb7f71..d95682611 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs @@ -1057,6 +1057,8 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar |> Result.ofOption (fun () -> $"No parse results for {filePath}") let forceGetTypeCheckResults filePath = + let rec doIt tryAgain = + let result = let tyResults = getTypeCheckResults (filePath) match getRecentTypeCheckResults filePath |> AVal.force with @@ -1067,6 +1069,14 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar Some s | None -> tyResults |> AVal.force |> Result.ofOption (fun () -> $"No typecheck results for {filePath}") + match result with + | Error e when tryAgain -> + // mark this file as outdated to force typecheck + transact ((findFileInOpenFiles filePath).MarkOutdated) + doIt false + | Error e -> Error e + | Ok x -> Ok x + doIt true let openFilesToCheckedDeclarations = openFilesToCheckedFilesResults From 3449a2efdf9c786bf706a0eb5b2e6ba946ce4a93 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Sun, 16 Oct 2022 14:58:36 -0400 Subject: [PATCH 17/37] Revert forgetDocument changes This will be an issue with bigger projects but this needs discussion how it should work --- src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs index d95682611..26c01fa24 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs @@ -1300,7 +1300,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar |> AVal.force - if true || doesNotExist filePath || isOutsideWorkspace filePath then + if doesNotExist filePath || isOutsideWorkspace filePath then logger.info ( Log.setMessage "Removing cached data for {file}." >> Log.addContext "file" filePath From e7e00f0f072725f54e3a3f83c93a3da3f8d455eb Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Sun, 16 Oct 2022 17:57:40 -0400 Subject: [PATCH 18/37] Consolodated LSP Server settings --- .../LspServers/AdaptiveFSharpLspServer.fs | 44 +------------ src/FsAutoComplete/LspServers/Common.fs | 64 ++++++++++++++++++- .../LspServers/FsAutoComplete.Lsp.fs | 45 +------------ 3 files changed, 63 insertions(+), 90 deletions(-) diff --git a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs index 26c01fa24..93c5fd689 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs @@ -1604,49 +1604,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar return { InitializeResult.Default with - Capabilities = - { ServerCapabilities.Default with - HoverProvider = Some true - RenameProvider = Some(U2.First true) - DefinitionProvider = Some true - TypeDefinitionProvider = Some true - ImplementationProvider = Some true - ReferencesProvider = Some true - DocumentHighlightProvider = Some true - DocumentSymbolProvider = Some true - WorkspaceSymbolProvider = Some true - DocumentFormattingProvider = Some true - DocumentRangeFormattingProvider = Some true - SignatureHelpProvider = - Some - { TriggerCharacters = Some [| '('; ','; ' ' |] - RetriggerCharacters = Some [| ','; ')'; ' ' |] } - CompletionProvider = - Some - { ResolveProvider = Some true - TriggerCharacters = Some([| '.'; ''' |]) - AllCommitCharacters = None //TODO: what chars shoudl commit completions? - } - CodeLensProvider = Some { CodeLensOptions.ResolveProvider = Some true } - CodeActionProvider = - Some - { CodeActionKinds = None - ResolveProvider = None } - TextDocumentSync = - Some - { TextDocumentSyncOptions.Default with - OpenClose = Some true - Change = Some TextDocumentSyncKind.Incremental - Save = Some { IncludeText = Some true } } - FoldingRangeProvider = Some true - SelectionRangeProvider = Some true - SemanticTokensProvider = - Some - { Legend = - createTokenLegend - Range = Some true - Full = Some(U2.First true) } - InlayHintProvider = Some { ResolveProvider = Some false } } } + Capabilities = Helpers.defaultServerCapabilities } with e -> logger.error ( diff --git a/src/FsAutoComplete/LspServers/Common.fs b/src/FsAutoComplete/LspServers/Common.fs index 24d40c351..18a4ac821 100644 --- a/src/FsAutoComplete/LspServers/Common.fs +++ b/src/FsAutoComplete/LspServers/Common.fs @@ -137,8 +137,15 @@ type DiagnosticCollection(sendDiagnostics: DocumentUri -> Diagnostic[] -> Async< cts.Cancel() module Async = + open FsAutoComplete.Logging + open FsAutoComplete.Logging.Types open System.Threading.Tasks + let rec logger = LogProvider.getLoggerByQuotation <@ logger @> + + let inline logCancelled e = + logger.trace (Log.setMessage "Operation Cancelled" >> Log.addExn e) + let withCancellation (ct: CancellationToken) (a: Async<'a>) : Async<'a> = async { let! ct2 = Async.CancellationToken @@ -165,8 +172,11 @@ module Async = let! result = withCancellation (ct ()) work return Some result with - | :? OperationCanceledException as e -> return None + | :? OperationCanceledException as e -> + logCancelled e + return None | :? ObjectDisposedException as e when e.Message.Contains("CancellationTokenSource has been disposed") -> + logCancelled e return None } @@ -177,8 +187,12 @@ module Async = try work |> RunSynchronouslyWithCT(ct ()) |> Some with - | :? OperationCanceledException as e -> None - | :? ObjectDisposedException as e when e.Message.Contains("CancellationTokenSource has been disposed") -> None + | :? OperationCanceledException as e -> + logCancelled e + None + | :? ObjectDisposedException as e when e.Message.Contains("CancellationTokenSource has been disposed") -> + logCancelled e + None [] module ObservableExtensions = @@ -197,3 +211,47 @@ module Helpers = let ignoreNotification = async.Return(()) let fullPathNormalized = Path.GetFullPath >> Utils.normalizePath >> UMX.untag + + let defaultServerCapabilities = + { ServerCapabilities.Default with + HoverProvider = Some true + RenameProvider = Some(U2.First true) + DefinitionProvider = Some true + TypeDefinitionProvider = Some true + ImplementationProvider = Some true + ReferencesProvider = Some true + DocumentHighlightProvider = Some true + DocumentSymbolProvider = Some true + WorkspaceSymbolProvider = Some true + DocumentFormattingProvider = Some true + DocumentRangeFormattingProvider = Some true + SignatureHelpProvider = + Some + { TriggerCharacters = Some [| '('; ','; ' ' |] + RetriggerCharacters = Some [| ','; ')'; ' ' |] } + CompletionProvider = + Some + { ResolveProvider = Some true + TriggerCharacters = Some([| '.'; ''' |]) + AllCommitCharacters = None //TODO: what chars shoudl commit completions? + } + CodeLensProvider = Some { CodeLensOptions.ResolveProvider = Some true } + CodeActionProvider = + Some + { CodeActionKinds = None + ResolveProvider = None } + TextDocumentSync = + Some + { TextDocumentSyncOptions.Default with + OpenClose = Some true + Change = Some TextDocumentSyncKind.Full + Save = Some { IncludeText = Some true } } + FoldingRangeProvider = Some true + SelectionRangeProvider = Some true + SemanticTokensProvider = + Some + { Legend = + createTokenLegend + Range = Some true + Full = Some(U2.First true) } + InlayHintProvider = Some { ResolveProvider = Some false } } diff --git a/src/FsAutoComplete/LspServers/FsAutoComplete.Lsp.fs b/src/FsAutoComplete/LspServers/FsAutoComplete.Lsp.fs index eaa723cae..df5166a5c 100644 --- a/src/FsAutoComplete/LspServers/FsAutoComplete.Lsp.fs +++ b/src/FsAutoComplete/LspServers/FsAutoComplete.Lsp.fs @@ -1243,50 +1243,7 @@ type FSharpLspServer(state: State, lspClient: FSharpLspClient) = |> Async.Start return - { InitializeResult.Default with - Capabilities = - { ServerCapabilities.Default with - HoverProvider = Some true - RenameProvider = Some(U2.First true) - DefinitionProvider = Some true - TypeDefinitionProvider = Some true - ImplementationProvider = Some true - ReferencesProvider = Some true - DocumentHighlightProvider = Some true - DocumentSymbolProvider = Some true - WorkspaceSymbolProvider = Some true - DocumentFormattingProvider = Some true - DocumentRangeFormattingProvider = Some true - SignatureHelpProvider = - Some - { TriggerCharacters = Some [| '('; ','; ' ' |] - RetriggerCharacters = Some [| ','; ')'; ' ' |] } - CompletionProvider = - Some - { ResolveProvider = Some true - TriggerCharacters = Some([| '.'; ''' |]) - AllCommitCharacters = None //TODO: what chars shoudl commit completions? - } - CodeLensProvider = Some { CodeLensOptions.ResolveProvider = Some true } - CodeActionProvider = - Some - { CodeActionKinds = None - ResolveProvider = None } - TextDocumentSync = - Some - { TextDocumentSyncOptions.Default with - OpenClose = Some true - Change = Some TextDocumentSyncKind.Full - Save = Some { IncludeText = Some true } } - FoldingRangeProvider = Some true - SelectionRangeProvider = Some true - SemanticTokensProvider = - Some - { Legend = - createTokenLegend - Range = Some true - Full = Some(U2.First true) } - InlayHintProvider = Some { ResolveProvider = Some false } } } + { InitializeResult.Default with Capabilities = Helpers.defaultServerCapabilities } |> success } From f68c30edc4a7d5c41d3e1683751ff0a61309507e Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Sun, 16 Oct 2022 17:58:18 -0400 Subject: [PATCH 19/37] Formatting --- .../LspServers/AdaptiveFSharpLspServer.fs | 55 +++++++++++-------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs index 93c5fd689..1dbb8e1e8 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs @@ -915,9 +915,9 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar cts.CancelAfter(TimeSpan.FromSeconds(60.)) let! result = - Debug.measureAsync $"checker.ParseAndCheckFileInProject - {file.Lines.FileName}" <| - checker.ParseAndCheckFileInProject(file.Lines.FileName, (file.Lines.GetHashCode()), file.Lines, opts) - |> Async.withCancellation cts.Token + Debug.measureAsync $"checker.ParseAndCheckFileInProject - {file.Lines.FileName}" + <| checker.ParseAndCheckFileInProject(file.Lines.FileName, (file.Lines.GetHashCode()), file.Lines, opts) + |> Async.withCancellation cts.Token let! ct = Async.CancellationToken notifications.Trigger(NotificationEvent.FileParsed(file.Lines.FileName), ct) @@ -937,17 +937,20 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar >> Log.addContextDestructured "file" file.Lines.FileName ) - Async.Start(async { - let checkErrors = parseAndCheck.GetParseResults.Diagnostics - let parseErrors = parseAndCheck.GetCheckResults.Diagnostics + Async.Start( + async { + let checkErrors = parseAndCheck.GetParseResults.Diagnostics + let parseErrors = parseAndCheck.GetCheckResults.Diagnostics - let errors = - Array.append checkErrors parseErrors - |> Array.distinctBy (fun e -> - e.Severity, e.ErrorNumber, e.StartLine, e.StartColumn, e.EndLine, e.EndColumn, e.Message) + let errors = + Array.append checkErrors parseErrors + |> Array.distinctBy (fun e -> + e.Severity, e.ErrorNumber, e.StartLine, e.StartColumn, e.EndLine, e.EndColumn, e.Message) - notifications.Trigger(NotificationEvent.ParseError(errors, file.Lines.FileName), ct) - }, ct) + notifications.Trigger(NotificationEvent.ParseError(errors, file.Lines.FileName), ct) + }, + ct + ) @@ -955,14 +958,18 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar Async.Start(analyzeFile config (file.Lines.FileName, file.Version, file.Lines, parseAndCheck), ct) - Async.Start( async { - // LargeObjectHeap gets fragmented easily for really large files, which F# can easily have. - // Yes this seems excessive doing this every time we type check but it's the best current kludge. - System.Runtime.GCSettings.LargeObjectHeapCompactionMode <- - System.Runtime.GCLargeObjectHeapCompactionMode.CompactOnce - GC.Collect() - GC.WaitForPendingFinalizers() - }, ct) + Async.Start( + async { + // LargeObjectHeap gets fragmented easily for really large files, which F# can easily have. + // Yes this seems excessive doing this every time we type check but it's the best current kludge. + System.Runtime.GCSettings.LargeObjectHeapCompactionMode <- + System.Runtime.GCLargeObjectHeapCompactionMode.CompactOnce + + GC.Collect() + GC.WaitForPendingFinalizers() + }, + ct + ) return parseAndCheck } @@ -1069,6 +1076,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar Some s | None -> tyResults |> AVal.force |> Result.ofOption (fun () -> $"No typecheck results for {filePath}") + match result with | Error e when tryAgain -> // mark this file as outdated to force typecheck @@ -1076,6 +1084,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar doIt false | Error e -> Error e | Ok x -> Ok x + doIt true let openFilesToCheckedDeclarations = @@ -1602,9 +1611,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar workspacePaths.Value <- WorkspaceChosen.Projs(HashSet.ofList projs)) - return - { InitializeResult.Default with - Capabilities = Helpers.defaultServerCapabilities } + return { InitializeResult.Default with Capabilities = Helpers.defaultServerCapabilities } with e -> logger.error ( @@ -1842,7 +1849,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar // Otherwise we'll fail here and our retry logic will come into place do! match p.Context with - | Some({triggerKind = CompletionTriggerKind.TriggerCharacter } as context) -> + | Some ({ triggerKind = CompletionTriggerKind.TriggerCharacter } as context) -> namedText.Lines.TryGetChar pos = context.triggerCharacter | _ -> true |> Result.requireTrue $"TextDocumentCompletion was sent before TextDocumentDidChange" From 5cff4c053a87e81c0fc295949b553f496e876d6f Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Sun, 16 Oct 2022 18:17:04 -0400 Subject: [PATCH 20/37] Cleanup/formatting --- .../CompilerServiceInterface.fs | 5 +-- .../LspServers/AdaptiveFSharpLspServer.fs | 45 ++----------------- 2 files changed, 5 insertions(+), 45 deletions(-) diff --git a/src/FsAutoComplete.Core/CompilerServiceInterface.fs b/src/FsAutoComplete.Core/CompilerServiceInterface.fs index 5a3aa080b..9a391e8a6 100644 --- a/src/FsAutoComplete.Core/CompilerServiceInterface.fs +++ b/src/FsAutoComplete.Core/CompilerServiceInterface.fs @@ -237,7 +237,6 @@ type FSharpCompilerServiceChecker(hasAnalyzers) = /// This function is called when the entire environment is known to have changed for reasons not encoded in the ProjectOptions of any project/compilation. member _.ClearCaches() = - // todo clear lastCheckResults let oldlastCheckResults = lastCheckResults lastCheckResults <- new MemoryCache(MemoryCacheOptions(SizeLimit = Nullable<_>(20L))) oldlastCheckResults.Dispose() @@ -295,10 +294,10 @@ type FSharpCompilerServiceChecker(hasAnalyzers) = let opName = sprintf "TryGetLastCheckResultForFile - %A" file checkerLogger.info ( - Log.setMessage "{opName}" - >> Log.addContextDestructured "opName" opName + Log.setMessage "{opName}" >> Log.addContextDestructured "opName" opName ) + match lastCheckResults.TryGetValue(file) with | (true, v) -> Some v | _ -> None diff --git a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs index 1dbb8e1e8..a0fd4fe43 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs @@ -620,11 +620,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let textChanges = cmap, cset> () - let sortedTextChanges = - textChanges - |> AMap.map' (fun x -> x :> aset<_> - // ASet.sortBy (fun (x: DidChangeTextDocumentParams) -> x.TextDocument.Version.Value) - ) + let sortedTextChanges = textChanges |> AMap.map' (fun x -> x :> aset<_>) let openFilesWithChanges: amap<_, aval> = @@ -684,38 +680,6 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar text) return (file, new CancellationTokenSource()) - // way worse on memory - // let file = - // (file, c) - // ||> AList.fold (fun text change -> - // let version = change.TextDocument.Version - - // (text, change.ContentChanges) - // ||> Seq.fold (fun text change -> - // match change.Range with - // | None -> // replace entire content - // // We want to update the DateTime here since TextDocumentDidChange will not have changes reflected on disk - // VolatileFile.Create(filePath, change.Text, version, DateTime.UtcNow) - // | Some rangeToReplace -> - // // replace just this slice - // let fcsRangeToReplace = protocolRangeToRange (UMX.untag filePath) rangeToReplace - - // match text.Lines.ModifyText(fcsRangeToReplace, change.Text) with - // | Ok text -> VolatileFile.Create(text, version, DateTime.UtcNow) - - // | Error message -> - // logger.error ( - // Log.setMessage - // "Error applying change to document {file} for version {version}: {message} - {range}" - // >> Log.addContextDestructured "file" filePath - // >> Log.addContextDestructured "version" version - // >> Log.addContextDestructured "message" message - // >> Log.addContextDestructured "range" fcsRangeToReplace - // ) - - // text)) - - // return! file |> AVal.map (fun f -> f, new CancellationTokenSource()) }) let cancelAllOpenFileCheckRequests () = @@ -1703,7 +1667,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar transact (fun () -> cancelForFile filePath textChanges.AddOrElse(filePath, (fun _ -> cset<_> [ p ]), (fun _ v -> v.Add p |> ignore))) - // forceGetTypeCheckResults filePath |> ignore + async { do! Async.Sleep(10) forceGetTypeCheckResults filePath |> ignore @@ -1754,10 +1718,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar |> AMap.force |> Seq.sortWith (fun (name, _) (name2, _) -> // Force the current document to be checked first - if name = filePath || name2 = filePath then - -1 - else - compare name name2) + if name = filePath then -1 else compare name name2) logger.info ( Log.setMessage "typechecking for files {files}" From ebf7754ce323543c60875ed29060f5703c8ff5b3 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Wed, 19 Oct 2022 19:16:56 -0400 Subject: [PATCH 21/37] fixes around cancellationtoken replacements --- .../LspServers/AdaptiveFSharpLspServer.fs | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs index a0fd4fe43..fec198ea7 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs @@ -614,7 +614,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let fantomasLogger = LogProvider.getLoggerByName "Fantomas" let fantomasService: FantomasService = new LSPFantomasService() :> FantomasService - let openFiles = cmap, cval> () + let openFiles = cmap, cval> () let openFilesA = openFiles |> AMap.map' (fun v -> v :> aval<_>) @@ -627,11 +627,11 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar openFilesA |> AMap.map (fun filePath file -> aval { - let! file = file + let! (file, token) = file and! changes = sortedTextChanges |> AMap.tryFind filePath match changes with - | None -> return (file, new CancellationTokenSource()) + | None -> return (file, token) | Some c -> let! ps = c |> ASet.toAVal @@ -679,14 +679,13 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar text) - return (file, new CancellationTokenSource()) + return (file, token) }) let cancelAllOpenFileCheckRequests () = - let files = openFilesWithChanges |> AMap.force + let files = openFiles |> AMap.force for (_, fileVal) in files do - () let (oldFile, cts: CancellationTokenSource) = fileVal |> AVal.force try @@ -694,9 +693,10 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar cts.Dispose() with e -> () + transact(fun () -> fileVal.Value <- oldFile, new CancellationTokenSource()) let cancelForFile filePath = - openFilesWithChanges + openFiles |> AMap.tryFind filePath |> AVal.force |> Option.iter (fun fileVal -> @@ -712,12 +712,16 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar cts.Cancel() cts.Dispose() with _ -> - ()) + () + transact(fun () -> fileVal.Value <- oldFile, new CancellationTokenSource()) + ) let updateOpenFiles (file: VolatileFile) = cancelForFile file.FileName - let adder _ = cval file - let updater _ (v: cval<_>) = v.Value <- file + let adder _ = cval (file, new CancellationTokenSource()) + let updater _ (v: cval<_>) = + let (_, cts) = v |> AVal.force + v.Value <- file, cts transact (fun () -> openFiles.AddOrElse(file.Lines.FileName, adder, updater)) From b8a4593a75280b66de0b65ae25196bf01f56b0f0 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Wed, 19 Oct 2022 21:23:41 -0400 Subject: [PATCH 22/37] Formatting and refactoring --- .../LspServers/AdaptiveFSharpLspServer.fs | 99 ++++++++++--------- 1 file changed, 52 insertions(+), 47 deletions(-) diff --git a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs index fec198ea7..c29a8ba37 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs @@ -614,13 +614,14 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let fantomasLogger = LogProvider.getLoggerByName "Fantomas" let fantomasService: FantomasService = new LSPFantomasService() :> FantomasService - let openFiles = cmap, cval> () + let openFiles = + cmap, cval> () let openFilesA = openFiles |> AMap.map' (fun v -> v :> aval<_>) let textChanges = cmap, cset> () - let sortedTextChanges = textChanges |> AMap.map' (fun x -> x :> aset<_>) + let textChangesA = textChanges |> AMap.map' (fun x -> x :> aset<_>) let openFilesWithChanges: amap<_, aval> = @@ -628,7 +629,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar |> AMap.map (fun filePath file -> aval { let! (file, token) = file - and! changes = sortedTextChanges |> AMap.tryFind filePath + and! changes = textChangesA |> AMap.tryFind filePath match changes with | None -> return (file, token) @@ -682,48 +683,55 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar return (file, token) }) - let cancelAllOpenFileCheckRequests () = - let files = openFiles |> AMap.force - for (_, fileVal) in files do - let (oldFile, cts: CancellationTokenSource) = fileVal |> AVal.force + let resetFileVal (fileVal: cval<_>) = + let (oldFile: VolatileFile, cts: CancellationTokenSource) = fileVal |> AVal.force - try - cts.Cancel() - cts.Dispose() - with e -> - () - transact(fun () -> fileVal.Value <- oldFile, new CancellationTokenSource()) + try + logger.info ( + Log.setMessage "Cancelling {filePath} - {version}" + >> Log.addContextDestructured "filePath" oldFile.FileName + >> Log.addContextDestructured "version" oldFile.Version + ) - let cancelForFile filePath = - openFiles - |> AMap.tryFind filePath - |> AVal.force - |> Option.iter (fun fileVal -> - let (oldFile, cts) = fileVal |> AVal.force + cts.Cancel() + cts.Dispose() + with + | :? OperationCanceledException + | :? ObjectDisposedException as e when e.Message.Contains("CancellationTokenSource has been disposed") -> + // ignore if already cancelled + () - try - logger.info ( - Log.setMessage "Cancelling {filePath} - {version}" - >> Log.addContextDestructured "filePath" filePath - >> Log.addContextDestructured "version" oldFile.Version - ) + transact (fun () -> fileVal.Value <- oldFile, new CancellationTokenSource()) - cts.Cancel() - cts.Dispose() - with _ -> - () - transact(fun () -> fileVal.Value <- oldFile, new CancellationTokenSource()) - ) + let resetCancellationToken filePath = + openFiles |> AMap.tryFind filePath |> AVal.force |> Option.iter (resetFileVal) + + let resetAllCancellationTokens () = + let files = openFiles |> AMap.force + + for (_, fileVal) in files do + resetFileVal fileVal let updateOpenFiles (file: VolatileFile) = - cancelForFile file.FileName - let adder _ = cval (file, new CancellationTokenSource()) + let adder _ = + cval (file, new CancellationTokenSource()) + let updater _ (v: cval<_>) = let (_, cts) = v |> AVal.force v.Value <- file, cts - transact (fun () -> openFiles.AddOrElse(file.Lines.FileName, adder, updater)) + transact (fun () -> + resetCancellationToken file.FileName + openFiles.AddOrElse(file.Lines.FileName, adder, updater)) + + let updateTextchanges filePath p = + let adder _ = cset<_> [ p ] + let updater _ (v: cset<_>) = v.Add p |> ignore + + transact (fun () -> + resetCancellationToken filePath + textChanges.AddOrElse(filePath, adder, updater)) let findFileInOpenFiles file = openFilesWithChanges |> AMap.tryFindA file @@ -879,6 +887,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar >> Log.addContextDestructured "date" (file.Touched) ) + // HACK: Insurance for a bug where FCS invalidates graph nodes incorrectly and seems to typecheck forever use cts = new CancellationTokenSource() cts.CancelAfter(TimeSpan.FromSeconds(60.)) @@ -920,16 +929,14 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar ct ) - - - Async.Start(analyzeFile config (file.Lines.FileName, file.Version, file.Lines, parseAndCheck), ct) - Async.Start( async { - // LargeObjectHeap gets fragmented easily for really large files, which F# can easily have. + // HACK: LargeObjectHeap gets fragmented easily for really large files, which F# can easily have. // Yes this seems excessive doing this every time we type check but it's the best current kludge. + // It maybe better to set default environment variables from https://learn.microsoft.com/en-us/dotnet/core/runtime-config/garbage-collector + // instead of manually calling this, specifically the `DOTNET_GCConserveMemory` variable. System.Runtime.GCSettings.LargeObjectHeapCompactionMode <- System.Runtime.GCLargeObjectHeapCompactionMode.CompactOnce @@ -1048,7 +1055,10 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar match result with | Error e when tryAgain -> // mark this file as outdated to force typecheck - transact ((findFileInOpenFiles filePath).MarkOutdated) + transact (fun () -> + (openFilesA |> AMap.tryFind filePath |> AVal.force) + |> Option.iter (fun x -> x.MarkOutdated())) + doIt false | Error e -> Error e | Ok x -> Ok x @@ -1233,9 +1243,6 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar UseTripleQuotedInterpolation.fix tryGetParseResultsForFile getRangeText RenameParamToMatchSignature.fix tryGetParseResultsForFile |]) - - - let forgetDocument (uri: DocumentUri) = let filePath = uri |> Path.FileUriToLocalPath |> Utils.normalizePath @@ -1668,9 +1675,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let doc = p.TextDocument let filePath = doc.GetFilePath() |> Utils.normalizePath - transact (fun () -> - cancelForFile filePath - textChanges.AddOrElse(filePath, (fun _ -> cset<_> [ p ]), (fun _ v -> v.Add p |> ignore))) + updateTextchanges filePath p async { do! Async.Sleep(10) @@ -1698,7 +1703,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar >> Log.addContextDestructured "parms" p ) - cancelAllOpenFileCheckRequests () + resetAllCancellationTokens () let doc = p.TextDocument let filePath = doc.GetFilePath() |> Utils.normalizePath From a024744b37879a34f970363d8a74086183cef9f1 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Wed, 19 Oct 2022 12:55:26 -0400 Subject: [PATCH 23/37] Forget file and incremental text changes --- .../LspServers/AdaptiveFSharpLspServer.fs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs index c29a8ba37..d5edb9932 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs @@ -1284,7 +1284,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar |> AVal.force - if doesNotExist filePath || isOutsideWorkspace filePath then + if true || doesNotExist filePath || isOutsideWorkspace filePath then logger.info ( Log.setMessage "Removing cached data for {file}." >> Log.addContext "file" filePath @@ -1585,8 +1585,17 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar updateConfig c workspacePaths.Value <- WorkspaceChosen.Projs(HashSet.ofList projs)) + let defaultSettings = + { Helpers.defaultServerCapabilities + with + TextDocumentSync = + Helpers.defaultServerCapabilities.TextDocumentSync + |> Option.map(fun x -> + { x with Change = Some TextDocumentSyncKind.Incremental } + ) + } - return { InitializeResult.Default with Capabilities = Helpers.defaultServerCapabilities } + return { InitializeResult.Default with Capabilities = defaultSettings } with e -> logger.error ( @@ -2585,7 +2594,6 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar ) let fn = p.TextDocument.GetFilePath() |> Utils.normalizePath - match getDeclarations (fn) |> AVal.force with | None -> return None | Some decls -> From 9bfa3d6b3a9e722e38439b085d3d0ba6f64ca335 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Thu, 20 Oct 2022 10:53:51 -0400 Subject: [PATCH 24/37] enable incremental text again --- src/FsAutoComplete/LspServers/Common.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FsAutoComplete/LspServers/Common.fs b/src/FsAutoComplete/LspServers/Common.fs index 18a4ac821..8d85681f9 100644 --- a/src/FsAutoComplete/LspServers/Common.fs +++ b/src/FsAutoComplete/LspServers/Common.fs @@ -244,7 +244,7 @@ module Helpers = Some { TextDocumentSyncOptions.Default with OpenClose = Some true - Change = Some TextDocumentSyncKind.Full + Change = Some TextDocumentSyncKind.Incremental Save = Some { IncludeText = Some true } } FoldingRangeProvider = Some true SelectionRangeProvider = Some true From 5e6dbacbe44e769a445285515eddff0aceebed70 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Thu, 20 Oct 2022 10:54:06 -0400 Subject: [PATCH 25/37] parallelize getSymbolUsesInProjects --- src/FsAutoComplete.Core/Commands.fs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/FsAutoComplete.Core/Commands.fs b/src/FsAutoComplete.Core/Commands.fs index 5afc3211e..140b3cb20 100644 --- a/src/FsAutoComplete.Core/Commands.fs +++ b/src/FsAutoComplete.Core/Commands.fs @@ -713,11 +713,11 @@ module Commands = let getSymbolUsesInProjects (symbol, projects: FSharpProjectOptions list, onFound) = projects - |> List.map (fun p -> - asyncResult { + |> List.collect (fun p -> [ for file in p.SourceFiles do - do! findReferencesInFile (file, symbol, p, onFound) - }) + yield findReferencesInFile (file, symbol, p, onFound) + ] + ) |> Async.Parallel |> Async.map (Array.toList >> FsToolkit.ErrorHandling.List.sequenceResultM) From 6973108f83791df92d2210a6944173ceca4bcf46 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Tue, 25 Oct 2022 17:52:07 -0400 Subject: [PATCH 26/37] Fixes memory leak and minor optimizations openFilesToCheckedDeclarations was causing memory leaks by using the AMap.map(tick) function. This has some strange caching of functions that was effectively saving every variation. --- src/FsAutoComplete.Core/FileSystem.fs | 2 +- .../LspServers/AdaptiveFSharpLspServer.fs | 64 ++++++++++--------- 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/src/FsAutoComplete.Core/FileSystem.fs b/src/FsAutoComplete.Core/FileSystem.fs index c6a3091e7..129a27af1 100644 --- a/src/FsAutoComplete.Core/FileSystem.fs +++ b/src/FsAutoComplete.Core/FileSystem.fs @@ -261,7 +261,7 @@ type NamedText(fileName: string, str: string) = result { let startRange, endRange = x.SplitAt(m) let! startText = x[startRange] |> Result.mapError (fun x -> $"startRange -> {x}") - let! endText = x[endRange] |> Result.mapError (fun x -> $"endRange -> {x}") + and! endText = x[endRange] |> Result.mapError (fun x -> $"endRange -> {x}") let totalText = startText + text + endText return NamedText(x.FileName, totalText) } diff --git a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs index d5edb9932..25b9efcf5 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs @@ -458,7 +458,8 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar )) - file |> AVal.mapDisposableTuple (fun x -> x, cb) + file + |> AVal.mapDisposableTuple (fun x -> x, cb) let loader = cval workspaceLoader @@ -562,7 +563,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar projectOptions, additionalDependencies) - let! checker = checker + and! checker = checker checker.ClearCaches() // if we got new projects assume we're gonna need to clear caches let options = @@ -617,11 +618,11 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let openFiles = cmap, cval> () - let openFilesA = openFiles |> AMap.map' (fun v -> v :> aval<_>) + let openFilesA = openFiles |> AMap.map (fun _ v -> v :> aval<_>) let textChanges = cmap, cset> () - let textChangesA = textChanges |> AMap.map' (fun x -> x :> aset<_>) + let textChangesA = textChanges |> AMap.map (fun _ x -> x :> aset<_>) let openFilesWithChanges: amap<_, aval> = @@ -940,8 +941,8 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar System.Runtime.GCSettings.LargeObjectHeapCompactionMode <- System.Runtime.GCLargeObjectHeapCompactionMode.CompactOnce - GC.Collect() - GC.WaitForPendingFinalizers() + // GC.Collect() + // GC.WaitForPendingFinalizers() }, ct ) @@ -1067,10 +1068,13 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let openFilesToCheckedDeclarations = openFilesToCheckedFilesResults - |> AMap.map' (AVal.mapOption (fun parseAndCheck -> parseAndCheck.GetParseResults.GetNavigationItems().Declarations)) + |> AMap.map (fun _ v -> v |> AVal.mapOption (fun parseAndCheck -> parseAndCheck.GetParseResults.GetNavigationItems().Declarations)) let getDeclarations filename = - openFilesToCheckedDeclarations |> AMap.tryFindAndFlatten (filename) + // openFilesToCheckedDeclarations |> AMap.tryFindAndFlatten (filename) + openFilesToCheckedFilesResults + |> AMap.tryFindAndFlatten filename + |> AVal.mapOption(fun c -> c.GetParseResults.GetNavigationItems().Declarations) let getFilePathAndPosition (p: ITextDocumentPositionParams) = let filePath = p.GetFilePath() |> Utils.normalizePath @@ -1107,14 +1111,14 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar } member x.GetSymbolAndUseAtPositionOfKind(fileName, pos, kind) = - asyncMaybe { + asyncOption { let! symbol = x.GetSymbolAtPosition(fileName, pos) if symbol.Kind = kind then let! (text, _) = forceFindOpenFileOrRead fileName |> Option.ofResult let! line = tryGetLineStr pos text.Lines |> Option.ofResult - let! result = forceGetTypeCheckResults fileName |> Option.ofResult - let symbolUse = result.TryGetSymbolUse pos line + let! tyRes = forceGetTypeCheckResults fileName |> Option.ofResult + let symbolUse = tyRes.TryGetSymbolUse pos line return! Some(symbol, symbolUse) else return! None @@ -1130,7 +1134,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar asyncResult { let! (file, _) = forceFindOpenFileOrRead filePath let! lineStr = file.Lines |> tryGetLineStr pos - let! tyRes = forceGetTypeCheckResults filePath + and! tyRes = forceGetTypeCheckResults filePath return tyRes, lineStr, file.Lines } @@ -1951,7 +1955,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar else sym - let decls = openFilesToCheckedDeclarations |> AMap.force |> Seq.map (snd) + // let decls = openFilesToCheckedDeclarations |> AMap.force |> Seq.map (snd) match getAutoCompleteByDeclName sym |> AVal.force with | None -> //Isn't in sync filled cache, we don't have result @@ -2005,9 +2009,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let (filePath, pos) = getFilePathAndPosition p let! (namedText, _) = forceFindOpenFileOrRead filePath |> Result.ofStringErr - - let! lineStr = namedText.Lines |> tryGetLineStr pos |> Result.ofStringErr - let! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr + and! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr let charAtCaret = p.Context |> Option.bind (fun c -> c.TriggerCharacter) @@ -2065,7 +2067,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let (filePath, pos) = getFilePathAndPosition p let! (namedText, _) = forceFindOpenFileOrRead filePath |> Result.ofStringErr let! lineStr = namedText.Lines |> tryGetLineStr pos |> Result.ofStringErr - let! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr + and! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr match tyRes.TryGetToolTipEnhanced pos lineStr with | Ok (Some (tip, signature, footer, typeDoc)) -> @@ -2141,7 +2143,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let (filePath, pos) = getFilePathAndPosition p let! (namedText, _) = forceFindOpenFileOrRead filePath |> Result.ofStringErr let! lineStr = namedText.Lines |> tryGetLineStr pos |> Result.ofStringErr - let! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr + and! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr let! documentsAndRanges = @@ -2197,7 +2199,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let! (namedText, _) = forceFindOpenFileOrRead filePath |> Result.ofStringErr let! lineStr = namedText.Lines |> tryGetLineStr pos |> Result.ofStringErr - let! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr + and! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr let! decl = tyRes.TryFindDeclaration pos lineStr |> AsyncResult.ofStringErr return decl |> findDeclToLspLocation |> GotoResult.Single |> Some with e -> @@ -2222,7 +2224,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let! (namedText, _) = forceFindOpenFileOrRead filePath |> Result.ofStringErr let! lineStr = namedText.Lines |> tryGetLineStr pos |> Result.ofStringErr - let! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr + and! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr let! decl = tyRes.TryFindTypeDeclaration pos lineStr |> AsyncResult.ofStringErr return decl |> findDeclToLspLocation |> GotoResult.Single |> Some with e -> @@ -2246,7 +2248,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let (filePath, pos) = getFilePathAndPosition p let! (namedText, _) = forceFindOpenFileOrRead filePath |> Result.ofStringErr let! lineStr = tryGetLineStr pos namedText.Lines |> Result.ofStringErr - let! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr + and! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr let! usages = symbolUseWorkspace pos lineStr namedText.Lines tyRes @@ -2286,7 +2288,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let (filePath, pos) = getFilePathAndPosition p let! (namedText, _) = forceFindOpenFileOrRead filePath |> Result.ofStringErr let! lineStr = tryGetLineStr pos namedText.Lines |> Result.ofStringErr - let! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr + and! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr let! (symbol, uses) = tyRes.TryGetSymbolUseAndUsages pos lineStr |> Result.ofStringErr @@ -2318,7 +2320,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let (filePath, pos) = getFilePathAndPosition p let! (namedText, _) = forceFindOpenFileOrRead filePath |> Result.ofStringErr let! lineStr = tryGetLineStr pos namedText.Lines |> Result.ofStringErr - let! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr + and! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr logger.info ( Log.setMessage "TextDocumentImplementation Request: {parms}" @@ -2832,7 +2834,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let getParseResultsForFile file = asyncResult { let! namedText = forceFindSourceText file - let! parseResults = forceGetParseResults file + and! parseResults = forceGetParseResults file return namedText, parseResults } @@ -2944,7 +2946,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let filePath = p.TextDocument.GetFilePath() |> Utils.normalizePath let! (namedText, _) = forceFindOpenFileOrRead filePath |> Result.ofStringErr - let! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr + and! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr let fcsRange = protocolRangeToRange (UMX.untag filePath) p.Range let config = config |> AVal.force @@ -3215,7 +3217,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let! (namedText, _) = forceFindOpenFileOrRead filePath |> Result.ofStringErr let! lineStr = namedText.Lines |> tryGetLineStr pos |> Result.ofStringErr - let! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr + and! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr let! tip = Commands.typesig tyRes pos lineStr |> Result.ofCoreResponse return { Content = CommandResponse.typeSig FsAutoComplete.JsonSerializer.writeJson tip } @@ -3244,7 +3246,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let! (namedText, _) = forceFindOpenFileOrRead filePath |> Result.ofStringErr let! lineStr = namedText.Lines |> tryGetLineStr pos |> Result.ofStringErr - let! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr + and! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr let! (typ, parms, generics) = tyRes.TryGetSignatureData pos lineStr |> Result.ofStringErr return @@ -3273,7 +3275,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let! (namedText, _) = forceFindOpenFileOrRead filePath |> Result.ofStringErr let! lineStr = namedText.Lines |> tryGetLineStr pos |> Result.ofStringErr - let! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr + and! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr let! { InsertPosition = insertPos InsertText = text } = @@ -3291,7 +3293,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar NewText = text } |] } |] Changes = None } } - let! response = lspClient.WorkspaceApplyEdit edit + let! _ = lspClient.WorkspaceApplyEdit edit return () with e -> @@ -3586,7 +3588,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let (filePath, pos) = getFilePathAndPosition p let! (namedText, _) = forceFindOpenFileOrRead filePath |> Result.ofStringErr let! lineStr = namedText.Lines |> tryGetLineStr pos |> Result.ofStringErr - let! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr + and! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr let! t = Commands.Help tyRes pos lineStr |> Result.ofCoreResponse return { Content = CommandResponse.help FsAutoComplete.JsonSerializer.writeJson t } with e -> @@ -3610,7 +3612,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let (filePath, pos) = getFilePathAndPosition p let! (namedText, _) = forceFindOpenFileOrRead filePath |> Result.ofStringErr let! lineStr = namedText.Lines |> tryGetLineStr pos |> Result.ofStringErr - let! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr + and! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr lastFSharpDocumentationTypeCheck <- Some tyRes let! t = Commands.FormattedDocumentation tyRes pos lineStr |> Result.ofCoreResponse return { Content = CommandResponse.formattedDocumentation FsAutoComplete.JsonSerializer.writeJson t } From 29388628b6a8aab1dc0bcfe05fd008c01f219b27 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Tue, 25 Oct 2022 22:01:08 -0400 Subject: [PATCH 27/37] Removes textChanges on forgetfile, fixesopenFilesToCheckedDeclarations --- .../LspServers/AdaptiveFSharpLspServer.fs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs index 25b9efcf5..30f0c92c0 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs @@ -1066,9 +1066,10 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar doIt true - let openFilesToCheckedDeclarations = + let openFilesToCheckedDeclarations () = openFilesToCheckedFilesResults - |> AMap.map (fun _ v -> v |> AVal.mapOption (fun parseAndCheck -> parseAndCheck.GetParseResults.GetNavigationItems().Declarations)) + |> AMap.force + |> HashMap.map(fun _ v -> v |> AVal.mapOption(fun c -> c.GetParseResults.GetNavigationItems().Declarations) |> AVal.force ) let getDeclarations filename = // openFilesToCheckedDeclarations |> AMap.tryFindAndFlatten (filename) @@ -1287,14 +1288,18 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar false)) |> AVal.force + let thisShouldBeASettingToTurnOffHoldingFilesInMemory = true - if true || doesNotExist filePath || isOutsideWorkspace filePath then + if thisShouldBeASettingToTurnOffHoldingFilesInMemory || doesNotExist filePath || isOutsideWorkspace filePath then logger.info ( Log.setMessage "Removing cached data for {file}." >> Log.addContext "file" filePath ) - transact (fun () -> openFiles.Remove filePath |> ignore) + transact (fun () -> + openFiles.Remove filePath |> ignore + textChanges.Remove filePath |> ignore + ) diagnosticCollections.ClearFor(uri) else logger.info ( @@ -2414,10 +2419,8 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let glyphToSymbolKind = glyphToSymbolKind |> AVal.force let decls = - openFilesToCheckedDeclarations - |> AMap.force + openFilesToCheckedDeclarations () |> Seq.toArray - |> Array.map (fun (p, ns) -> p, AVal.force ns) |> Array.choose (fun (p, ns) -> ns |> Option.map (fun ns -> p, ns)) let res = From 5f1d04f381e939067db9f2ef2dd13903dde7f4a3 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Wed, 26 Oct 2022 19:30:00 -0400 Subject: [PATCH 28/37] Cleanup findFiles --- .../LspServers/AdaptiveFSharpLspServer.fs | 58 ++++++++++--------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs index 30f0c92c0..9b35e8680 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs @@ -735,7 +735,10 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar textChanges.AddOrElse(filePath, adder, updater)) let findFileInOpenFiles file = - openFilesWithChanges |> AMap.tryFindA file + openFilesWithChanges |> AMap.tryFindA file |> AVal.mapOption fst + + let forceFindOpenFile filePath = + findFileInOpenFiles filePath |> AVal.force let forceFindOpenFileOrRead file = findFileInOpenFiles file @@ -754,7 +757,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar Lines = NamedText(file, change) Version = None } - (file, new CancellationTokenSource()) |> Some + Some file else None with e -> @@ -767,15 +770,14 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar None) |> Result.ofOption (fun () -> $"Could not read file: {file}") - let forceFindOpenFile filePath = - findFileInOpenFiles filePath |> AVal.force |> Option.map fst + do FSharp.Compiler.IO.FileSystemAutoOpens.FileSystem <- FileSystem(FSharp.Compiler.IO.FileSystemAutoOpens.FileSystem, forceFindOpenFile) let forceFindSourceText filePath = - forceFindOpenFileOrRead filePath |> Result.map (fun (f, _) -> f.Lines) + forceFindOpenFileOrRead filePath |> Result.map (fun f -> f.Lines) let openFilesToProjectOptions = openFilesWithChanges @@ -958,7 +960,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let config = config |> AVal.force match findFileInOpenFiles f |> AVal.force, getProjectOptionsForFile f |> AVal.force |> List.tryHead with - | Some (fileInfo, _), Some (opts) -> return! parseAndCheckFile checker fileInfo opts config |> Async.Ignore + | Some (fileInfo), Some (opts) -> return! parseAndCheckFile checker fileInfo opts config |> Async.Ignore | _, _ -> () } @@ -1092,7 +1094,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar { new ICodeGenerationService with member x.TokenizeLine(file, i) = option { - let! (text, _) = forceFindOpenFileOrRead file |> Option.ofResult + let! (text) = forceFindOpenFileOrRead file |> Option.ofResult try let! line = text.Lines.GetLine(Position.mkPos i 0) @@ -1104,7 +1106,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar member x.GetSymbolAtPosition(file, pos) = option { try - let! (text, _) = forceFindOpenFileOrRead file |> Option.ofResult + let! (text) = forceFindOpenFileOrRead file |> Option.ofResult let! line = tryGetLineStr pos text.Lines |> Option.ofResult return! Lexer.getSymbol pos.Line pos.Column line SymbolLookupKind.Fuzzy [||] with _ -> @@ -1116,7 +1118,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let! symbol = x.GetSymbolAtPosition(fileName, pos) if symbol.Kind = kind then - let! (text, _) = forceFindOpenFileOrRead fileName |> Option.ofResult + let! (text) = forceFindOpenFileOrRead fileName |> Option.ofResult let! line = tryGetLineStr pos text.Lines |> Option.ofResult let! tyRes = forceGetTypeCheckResults fileName |> Option.ofResult let symbolUse = tyRes.TryGetSymbolUse pos line @@ -1133,7 +1135,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let tryGetParseResultsForFile filePath pos = asyncResult { - let! (file, _) = forceFindOpenFileOrRead filePath + let! (file) = forceFindOpenFileOrRead filePath let! lineStr = file.Lines |> tryGetLineStr pos and! tyRes = forceGetTypeCheckResults filePath return tyRes, lineStr, file.Lines @@ -1784,7 +1786,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let (filePath, pos) = getFilePathAndPosition p - let! (namedText2, _) = forceFindOpenFileOrRead filePath |> Result.ofStringErr + let! (namedText2) = forceFindOpenFileOrRead filePath |> Result.ofStringErr let! lineStr2 = namedText2.Lines |> tryGetLineStr pos |> Result.ofStringErr @@ -1811,7 +1813,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let getCompletions = asyncResult { - let! (namedText, _) = forceFindOpenFileOrRead filePath + let! (namedText) = forceFindOpenFileOrRead filePath let! lineStr = namedText.Lines |> tryGetLineStr pos let quickCheck = @@ -2013,7 +2015,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let (filePath, pos) = getFilePathAndPosition p - let! (namedText, _) = forceFindOpenFileOrRead filePath |> Result.ofStringErr + let! (namedText) = forceFindOpenFileOrRead filePath |> Result.ofStringErr and! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr let charAtCaret = p.Context |> Option.bind (fun c -> c.TriggerCharacter) @@ -2070,7 +2072,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar ) let (filePath, pos) = getFilePathAndPosition p - let! (namedText, _) = forceFindOpenFileOrRead filePath |> Result.ofStringErr + let! (namedText) = forceFindOpenFileOrRead filePath |> Result.ofStringErr let! lineStr = namedText.Lines |> tryGetLineStr pos |> Result.ofStringErr and! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr @@ -2146,7 +2148,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar ) let (filePath, pos) = getFilePathAndPosition p - let! (namedText, _) = forceFindOpenFileOrRead filePath |> Result.ofStringErr + let! (namedText) = forceFindOpenFileOrRead filePath |> Result.ofStringErr let! lineStr = namedText.Lines |> tryGetLineStr pos |> Result.ofStringErr and! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr @@ -2172,7 +2174,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let version = forceFindOpenFileOrRead namedText.FileName |> Option.ofResult - |> Option.bind (fun (f, _) -> f.Version) + |> Option.bind (fun (f) -> f.Version) { TextDocument = { Uri = Path.FilePathToUri(UMX.untag namedText.FileName) @@ -2201,7 +2203,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar ) let (filePath, pos) = getFilePathAndPosition p - let! (namedText, _) = forceFindOpenFileOrRead filePath |> Result.ofStringErr + let! (namedText) = forceFindOpenFileOrRead filePath |> Result.ofStringErr let! lineStr = namedText.Lines |> tryGetLineStr pos |> Result.ofStringErr and! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr @@ -2227,7 +2229,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let (filePath, pos) = getFilePathAndPosition p - let! (namedText, _) = forceFindOpenFileOrRead filePath |> Result.ofStringErr + let! (namedText) = forceFindOpenFileOrRead filePath |> Result.ofStringErr let! lineStr = namedText.Lines |> tryGetLineStr pos |> Result.ofStringErr and! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr let! decl = tyRes.TryFindTypeDeclaration pos lineStr |> AsyncResult.ofStringErr @@ -2251,7 +2253,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar ) let (filePath, pos) = getFilePathAndPosition p - let! (namedText, _) = forceFindOpenFileOrRead filePath |> Result.ofStringErr + let! (namedText) = forceFindOpenFileOrRead filePath |> Result.ofStringErr let! lineStr = tryGetLineStr pos namedText.Lines |> Result.ofStringErr and! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr @@ -2291,7 +2293,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar ) let (filePath, pos) = getFilePathAndPosition p - let! (namedText, _) = forceFindOpenFileOrRead filePath |> Result.ofStringErr + let! (namedText) = forceFindOpenFileOrRead filePath |> Result.ofStringErr let! lineStr = tryGetLineStr pos namedText.Lines |> Result.ofStringErr and! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr @@ -2323,7 +2325,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar ) let (filePath, pos) = getFilePathAndPosition p - let! (namedText, _) = forceFindOpenFileOrRead filePath |> Result.ofStringErr + let! (namedText) = forceFindOpenFileOrRead filePath |> Result.ofStringErr let! lineStr = tryGetLineStr pos namedText.Lines |> Result.ofStringErr and! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr @@ -2568,7 +2570,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let tryGetFileVersion filePath = forceFindOpenFileOrRead filePath |> Option.ofResult - |> Option.bind (fun (f, _) -> f.Version) + |> Option.bind (fun (f) -> f.Version) let clientCapabilities = clientCapabilities |> AVal.force @@ -2947,7 +2949,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar ) let filePath = p.TextDocument.GetFilePath() |> Utils.normalizePath - let! (namedText, _) = forceFindOpenFileOrRead filePath |> Result.ofStringErr + let! (namedText) = forceFindOpenFileOrRead filePath |> Result.ofStringErr and! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr @@ -3217,7 +3219,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar ) let (filePath, pos) = getFilePathAndPosition p - let! (namedText, _) = forceFindOpenFileOrRead filePath |> Result.ofStringErr + let! (namedText) = forceFindOpenFileOrRead filePath |> Result.ofStringErr let! lineStr = namedText.Lines |> tryGetLineStr pos |> Result.ofStringErr and! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr @@ -3246,7 +3248,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar FSharp.Compiler.Text.Position.mkPos (p.Position.Line) (p.Position.Character + 2) let filePath = p.TextDocument.GetFilePath() |> Utils.normalizePath - let! (namedText, _) = forceFindOpenFileOrRead filePath |> Result.ofStringErr + let! (namedText) = forceFindOpenFileOrRead filePath |> Result.ofStringErr let! lineStr = namedText.Lines |> tryGetLineStr pos |> Result.ofStringErr and! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr @@ -3275,7 +3277,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar ) let (filePath, pos) = getFilePathAndPosition p - let! (namedText, _) = forceFindOpenFileOrRead filePath |> Result.ofStringErr + let! (namedText) = forceFindOpenFileOrRead filePath |> Result.ofStringErr let! lineStr = namedText.Lines |> tryGetLineStr pos |> Result.ofStringErr and! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr @@ -3589,7 +3591,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar ) let (filePath, pos) = getFilePathAndPosition p - let! (namedText, _) = forceFindOpenFileOrRead filePath |> Result.ofStringErr + let! (namedText) = forceFindOpenFileOrRead filePath |> Result.ofStringErr let! lineStr = namedText.Lines |> tryGetLineStr pos |> Result.ofStringErr and! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr let! t = Commands.Help tyRes pos lineStr |> Result.ofCoreResponse @@ -3613,7 +3615,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar ) let (filePath, pos) = getFilePathAndPosition p - let! (namedText, _) = forceFindOpenFileOrRead filePath |> Result.ofStringErr + let! (namedText) = forceFindOpenFileOrRead filePath |> Result.ofStringErr let! lineStr = namedText.Lines |> tryGetLineStr pos |> Result.ofStringErr and! tyRes = forceGetTypeCheckResults filePath |> Result.ofStringErr lastFSharpDocumentationTypeCheck <- Some tyRes From 42a821bc2bb41bc565a3213c3f8c31996cd898e7 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Wed, 26 Oct 2022 19:30:15 -0400 Subject: [PATCH 29/37] Use result instead of failwith --- src/FsAutoComplete.Core/Commands.fs | 67 ++++++++++++++++------------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/src/FsAutoComplete.Core/Commands.fs b/src/FsAutoComplete.Core/Commands.fs index 140b3cb20..323f6968d 100644 --- a/src/FsAutoComplete.Core/Commands.fs +++ b/src/FsAutoComplete.Core/Commands.fs @@ -770,9 +770,9 @@ module Commands = let symbolRange = symbol.DefinitionRange.NormalizeDriveLetterCasing() let symbolFile = symbolRange.TaggedFileName - let symbolFileText = + let! symbolFileText = tryGetFileSource (symbolFile) - |> Result.fold id (fun e -> failwith $"Unable to get file source for file '{symbolFile}'") + |> Result.mapError (fun e -> e + $"Unable to get file source for file '{symbolFile}'") let! symbolText = symbolFileText.[symbolRange] // |> Result.fold id (fun e -> failwith "Unable to get text for initial symbol use") @@ -790,37 +790,44 @@ module Commands = |> List.distinctBy (fun x -> x.ProjectFileName) let onFound (symbolUseRange: range) = - async { + asyncResult { let symbolUseRange = symbolUseRange.NormalizeDriveLetterCasing() let symbolFile = symbolUseRange.TaggedFileName - let targetText = tryGetFileSource (symbolFile) - - match targetText with - | Error e -> () - | Ok sourceText -> - let sourceSpan = - sourceText.[symbolUseRange] - |> Result.fold id (fun e -> failwith "Unable to get text for symbol use") - - // There are two kinds of ranges we get back: - // * ranges that exactly match the short name of the symbol - // * ranges that are longer than the short name of the symbol, - // typically because we're talking about some kind of fully-qualified usage - // For the latter, we need to adjust the reported range to just be the portion - // of the fully-qualfied text that is the symbol name. - if sourceSpan = symbolText then - symbolUseRanges.Add symbolUseRange - else - match sourceSpan.IndexOf(symbolText) with - | -1 -> () - | n -> - if sourceSpan.Length >= n + symbolText.Length then - let startPos = symbolUseRange.Start.IncColumn n - let endPos = symbolUseRange.Start.IncColumn(n + symbolText.Length) - - let actualUseRange = Range.mkRange symbolUseRange.FileName startPos endPos - symbolUseRanges.Add actualUseRange + let! sourceText = tryGetFileSource (symbolFile) + + + let! sourceSpan = + sourceText.[symbolUseRange] + |> Result.mapError (fun e -> e + "Unable to get text for symbol use") + + // There are two kinds of ranges we get back: + // * ranges that exactly match the short name of the symbol + // * ranges that are longer than the short name of the symbol, + // typically because we're talking about some kind of fully-qualified usage + // For the latter, we need to adjust the reported range to just be the portion + // of the fully-qualfied text that is the symbol name. + if sourceSpan = symbolText then + symbolUseRanges.Add symbolUseRange + else + match sourceSpan.IndexOf(symbolText) with + | -1 -> () + | n -> + if sourceSpan.Length >= n + symbolText.Length then + let startPos = symbolUseRange.Start.IncColumn n + let endPos = symbolUseRange.Start.IncColumn(n + symbolText.Length) + + let actualUseRange = Range.mkRange symbolUseRange.FileName startPos endPos + symbolUseRanges.Add actualUseRange } + |> Async.map(fun x -> + match x with + | Ok () -> () + | Error e -> + commandsLogger.info( + Log.setMessage "OnFound failed: {errpr}" + >> Log.addContextDestructured "error" e + ) + ) let! _ = getSymbolUsesInProjects (symbol, projects, onFound) From 7d116cf06aa2f25b92a8098eb573236ad8384fc9 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Wed, 26 Oct 2022 19:30:27 -0400 Subject: [PATCH 30/37] add missing log context --- src/FsAutoComplete.Core/CompilerServiceInterface.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FsAutoComplete.Core/CompilerServiceInterface.fs b/src/FsAutoComplete.Core/CompilerServiceInterface.fs index 9a391e8a6..ef64e1a14 100644 --- a/src/FsAutoComplete.Core/CompilerServiceInterface.fs +++ b/src/FsAutoComplete.Core/CompilerServiceInterface.fs @@ -295,7 +295,6 @@ type FSharpCompilerServiceChecker(hasAnalyzers) = checkerLogger.info ( Log.setMessage "{opName}" >> Log.addContextDestructured "opName" opName - ) match lastCheckResults.TryGetValue(file) with @@ -319,6 +318,7 @@ type FSharpCompilerServiceChecker(hasAnalyzers) = |> Option.map (fun (pr, cr, version) -> checkerLogger.info ( Log.setMessage "{opName} - got results - {version}" + >> Log.addContextDestructured "opName" opName >> Log.addContextDestructured "version" version ) From 62d5af36f1bfc31e2f978e4d0e8f0b1dda2e9bec Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Wed, 26 Oct 2022 23:17:10 -0400 Subject: [PATCH 31/37] Fixing comments --- src/FsAutoComplete.Core/CodeGeneration.fs | 2 +- src/FsAutoComplete.Core/Lexer.fs | 2 +- src/FsAutoComplete.Core/TypedAstPatterns.fs | 2 +- src/FsAutoComplete.Core/TypedAstUtils.fs | 2 +- src/FsAutoComplete/CodeFixes.fs | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/FsAutoComplete.Core/CodeGeneration.fs b/src/FsAutoComplete.Core/CodeGeneration.fs index b5e9056dd..a990c2e9d 100644 --- a/src/FsAutoComplete.Core/CodeGeneration.fs +++ b/src/FsAutoComplete.Core/CodeGeneration.fs @@ -1,4 +1,4 @@ -/// Original code from VisualFSharpPowerTools project: https://github.com/fsprojects/VisualFSharpPowerTools/blob/master/src/FSharp.Editing/CodeGeneration/CodeGeneration.fs +// Original code from VisualFSharpPowerTools project: https://github.com/fsprojects/VisualFSharpPowerTools/blob/master/src/FSharp.Editing/CodeGeneration/CodeGeneration.fs namespace FsAutoComplete open System diff --git a/src/FsAutoComplete.Core/Lexer.fs b/src/FsAutoComplete.Core/Lexer.fs index 88f831ef9..0d1303b4f 100644 --- a/src/FsAutoComplete.Core/Lexer.fs +++ b/src/FsAutoComplete.Core/Lexer.fs @@ -247,7 +247,7 @@ module Lexer = | StaticallyResolvedTypeParameter | Keyword -> true | _ -> false) - /// Gets the option if Some x, otherwise try to get another value + // Gets the option if Some x, otherwise try to get another value |> Option.orElseWith (fun _ -> tokensUnderCursor |> List.tryFind (fun { DraftToken.Kind = k } -> k = Operator)) |> Option.map (fun token -> diff --git a/src/FsAutoComplete.Core/TypedAstPatterns.fs b/src/FsAutoComplete.Core/TypedAstPatterns.fs index 1a623f9dd..bbec371dd 100644 --- a/src/FsAutoComplete.Core/TypedAstPatterns.fs +++ b/src/FsAutoComplete.Core/TypedAstPatterns.fs @@ -254,7 +254,7 @@ module SymbolUse = | _ -> None [] -/// Active patterns over `FSharpSymbol`. +// Active patterns over `FSharpSymbol`. module SymbolPatterns = let private attributeSuffixLength = "Attribute".Length diff --git a/src/FsAutoComplete.Core/TypedAstUtils.fs b/src/FsAutoComplete.Core/TypedAstUtils.fs index 63d0d8871..2ae1b3601 100644 --- a/src/FsAutoComplete.Core/TypedAstUtils.fs +++ b/src/FsAutoComplete.Core/TypedAstUtils.fs @@ -1,4 +1,4 @@ -///Original code from VisualFSharpPowerTools project: https://github.com/fsprojects/VisualFSharpPowerTools/blob/master/src/FSharp.Editing/Common/TypedAstUtils.fs +//Original code from VisualFSharpPowerTools project: https://github.com/fsprojects/VisualFSharpPowerTools/blob/master/src/FSharp.Editing/Common/TypedAstUtils.fs namespace FsAutoComplete open System diff --git a/src/FsAutoComplete/CodeFixes.fs b/src/FsAutoComplete/CodeFixes.fs index b6cde1051..378b0e289 100644 --- a/src/FsAutoComplete/CodeFixes.fs +++ b/src/FsAutoComplete/CodeFixes.fs @@ -1,5 +1,5 @@ -/// This module contains the logic for codefixes that FSAC surfaces, as well as conversion logic between -/// compiler diagnostics and LSP diagnostics/code actions +// This module contains the logic for codefixes that FSAC surfaces, as well as conversion logic between +// compiler diagnostics and LSP diagnostics/code actions namespace FsAutoComplete.CodeFix open FsAutoComplete From bfe33f92c6e208e1c6bccc5ed2f9722905b46a36 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Wed, 26 Oct 2022 23:47:28 -0400 Subject: [PATCH 32/37] Type checking whole solution This also fixes issues with renaming and references --- .../CompilerServiceInterface.fs | 2 +- .../LspServers/AdaptiveFSharpLspServer.fs | 168 +++++++++++------- src/FsAutoComplete/LspServers/Common.fs | 2 + 3 files changed, 106 insertions(+), 66 deletions(-) diff --git a/src/FsAutoComplete.Core/CompilerServiceInterface.fs b/src/FsAutoComplete.Core/CompilerServiceInterface.fs index ef64e1a14..05f9f9754 100644 --- a/src/FsAutoComplete.Core/CompilerServiceInterface.fs +++ b/src/FsAutoComplete.Core/CompilerServiceInterface.fs @@ -32,7 +32,7 @@ type FSharpCompilerServiceChecker(hasAnalyzers) = let entityCache = EntityCache() let mutable lastCheckResults: IMemoryCache = - new MemoryCache(MemoryCacheOptions(SizeLimit = Nullable<_>(20L))) + new MemoryCache(MemoryCacheOptions(SizeLimit = Nullable<_>(200L))) let checkerLogger = LogProvider.getLoggerByName "Checker" let optsLogger = LogProvider.getLoggerByName "Opts" diff --git a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs index 9b35e8680..9fe97c756 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs @@ -802,9 +802,10 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let getProjectOptionsForFile file = - openFilesToProjectOptions - |> AMap.tryFind file - |> AVal.bind (Option.defaultValue (AVal.constant [])) + loadedProjectOptions + |> AVal.map (fun opts -> + opts + |> List.filter (fun (opts) -> opts.SourceFiles |> Array.map Utils.normalizePath |> Array.contains (file))) let autoCompleteItems: cmap * (Position -> option) * FSharp.Compiler.Syntax.ParsedInput> = cmap () @@ -952,16 +953,30 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar return parseAndCheck } + let mutable typeCheckerToken = new CancellationTokenSource() + + let resetAndGetTypeCheckerToken () = + lock typeCheckerToken <| fun () -> + typeCheckerToken.Cancel() + typeCheckerToken.Dispose() + typeCheckerToken <- new CancellationTokenSource() + typeCheckerToken.Token + /// Bypass Adaptive checking and tell the checker to check a file - let forceTypeCheck f = + let forceTypeCheck f opts = async { - logger.info (Log.setMessage "Forced Check : {file}" >> Log.addContextDestructured "file" f) - let checker = checker |> AVal.force - let config = config |> AVal.force + try + logger.info (Log.setMessage "Forced Check : {file}" >> Log.addContextDestructured "file" f) + let checker = checker |> AVal.force + let config = config |> AVal.force + let opts = opts |> Option.orElseWith(fun () -> getProjectOptionsForFile f |> AVal.force |> Seq.tryHead ) + + match forceFindOpenFileOrRead f, opts with + | Ok (fileInfo), Some opts -> return! parseAndCheckFile checker fileInfo opts config |> Async.Ignore + | _, _ -> () + with e -> - match findFileInOpenFiles f |> AVal.force, getProjectOptionsForFile f |> AVal.force |> List.tryHead with - | Some (fileInfo), Some (opts) -> return! parseAndCheckFile checker fileInfo opts config |> Async.Ignore - | _, _ -> () + logger.warn (Log.setMessage "Forced Check error : {file}" >> Log.addContextDestructured "file" f >> Log.addExn e) } let openFilesToParsedResults = @@ -1290,7 +1305,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar false)) |> AVal.force - let thisShouldBeASettingToTurnOffHoldingFilesInMemory = true + let thisShouldBeASettingToTurnOffHoldingFilesInMemory = false if thisShouldBeASettingToTurnOffHoldingFilesInMemory || doesNotExist filePath || isOutsideWorkspace filePath then logger.info ( @@ -1309,6 +1324,73 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar >> Log.addContext "file" filePath ) + let getDependentFilesForFile file = + let projects = getProjectOptionsForFile file |> AVal.force + projects + |> List.toArray + |> Array.collect(fun proj -> + logger.info ( + Log.setMessage "Source Files: {sourceFiles}" + >> Log.addContextDestructured "sourceFiles" proj.SourceFiles + ) + let idx = proj.SourceFiles |> Array.findIndex(fun x -> x = UMX.untag file) + proj.SourceFiles + |> Array.splitAt idx + |> snd + |> Array.map(fun sourceFile -> proj, sourceFile) + ) + |> Array.distinct + + let getDependentProjectsOfProjects ps = + let projectSnapshot = loadedProjectOptions |> AVal.force + + let allDependents = System.Collections.Generic.HashSet() + + let currentPass = ResizeArray() + currentPass.AddRange(ps |> List.map (fun p -> p.ProjectFileName)) + + let mutable continueAlong = true + + while continueAlong do + let dependents = + projectSnapshot + |> Seq.filter (fun p -> + p.ReferencedProjects + |> Seq.exists (fun r -> + match r.ProjectFilePath with + | None -> false + | Some p -> currentPass.Contains(p))) + + if Seq.isEmpty dependents then + continueAlong <- false + currentPass.Clear() + else + for d in dependents do + allDependents.Add d |> ignore + + currentPass.Clear() + currentPass.AddRange(dependents |> Seq.map (fun p -> p.ProjectFileName)) + + Seq.toList allDependents + + let forceCheckDepenenciesForFile filePath = async { + let dependentFiles = + getDependentFilesForFile filePath + let dependentProjects = + getProjectOptionsForFile filePath + |> AVal.force + |> getDependentProjectsOfProjects + |> List.toArray + |> Array.collect(fun proj -> + proj.SourceFiles + |> Array.map(fun sourceFile -> proj, sourceFile) + ) + do! + Array.concat [|dependentFiles; dependentProjects|] + |> Array.map(fun (proj, file) -> forceTypeCheck (UMX.tag file) (Some proj)) + |> Async.Sequential + |> Async.Ignore + } let symbolUseWorkspace pos lineStr text tyRes = @@ -1327,37 +1409,6 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar |> AVal.force |> Seq.tryFind (fun x -> x.ProjectFileName = file) - let getDependentProjectsOfProjects ps = - let projectSnapshot = loadedProjectOptions |> AVal.force - - let allDependents = System.Collections.Generic.HashSet() - - let currentPass = ResizeArray() - currentPass.AddRange(ps |> List.map (fun p -> p.ProjectFileName)) - - let mutable continueAlong = true - - while continueAlong do - let dependents = - projectSnapshot - |> Seq.filter (fun p -> - p.ReferencedProjects - |> Seq.exists (fun r -> - match r.ProjectFilePath with - | None -> false - | Some p -> currentPass.Contains(p))) - - if Seq.isEmpty dependents then - continueAlong <- false - currentPass.Clear() - else - for d in dependents do - allDependents.Add d |> ignore - - currentPass.Clear() - currentPass.AddRange(dependents |> Seq.map (fun p -> p.ProjectFileName)) - - Seq.toList allDependents let getDeclarationLocation (symUse, text) = SymbolLocation.getDeclarationLocation ( @@ -1700,8 +1751,10 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar async { do! Async.Sleep(10) forceGetTypeCheckResults filePath |> ignore + do! forceCheckDepenenciesForFile filePath + } - |> Async.Start + |> Async.StartWithCT (resetAndGetTypeCheckerToken()) return () with e -> @@ -1712,7 +1765,6 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar ) return () - } override __.TextDocumentDidSave(p) = @@ -1741,28 +1793,14 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar transact (fun () -> updateOpenFiles file textChanges.Remove filePath |> ignore) + async { + do! Async.Sleep(10) + forceGetTypeCheckResults filePath |> ignore + do! lspClient.CodeLensRefresh() + do! forceCheckDepenenciesForFile filePath - let knownFiles = - openFilesWithChanges - |> AMap.force - |> Seq.sortWith (fun (name, _) (name2, _) -> - // Force the current document to be checked first - if name = filePath then -1 else compare name name2) - - logger.info ( - Log.setMessage "typechecking for files {files}" - >> Log.addContextDestructured "files" knownFiles - ) - - for (file, aFile) in knownFiles do - let (_, cts) = aFile |> AVal.force - - do! - forceTypeCheck file - |> Async.withCancellationSafe (fun () -> cts.Token) - |> Async.Ignore - - do! lspClient.CodeLensRefresh() + } + |> Async.StartWithCT (resetAndGetTypeCheckerToken()) return () with e -> diff --git a/src/FsAutoComplete/LspServers/Common.fs b/src/FsAutoComplete/LspServers/Common.fs index 8d85681f9..7e9ba4ac6 100644 --- a/src/FsAutoComplete/LspServers/Common.fs +++ b/src/FsAutoComplete/LspServers/Common.fs @@ -180,6 +180,8 @@ module Async = return None } + let StartWithCT ct work = Async.Start(work, ct) + let RunSynchronouslyWithCT ct work = Async.RunSynchronously(work, cancellationToken = ct) From 24a1d73cdc0cb3291ba6c3c9e00db4fdabc30711 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Fri, 28 Oct 2022 10:54:12 -0400 Subject: [PATCH 33/37] Handle dependent file checks --- .../LspServers/AdaptiveFSharpLspServer.fs | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs index 9fe97c756..546700de6 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs @@ -953,14 +953,16 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar return parseAndCheck } - let mutable typeCheckerToken = new CancellationTokenSource() - let resetAndGetTypeCheckerToken () = - lock typeCheckerToken <| fun () -> + let typeCheckerTokens = new System.Collections.Concurrent.ConcurrentDictionary,CancellationTokenSource>() + + let getCTForFile filePath = + let add x = new CancellationTokenSource() + let update x (typeCheckerToken : CancellationTokenSource) = typeCheckerToken.Cancel() - typeCheckerToken.Dispose() - typeCheckerToken <- new CancellationTokenSource() - typeCheckerToken.Token + // typeCheckerToken.Dispose() + new CancellationTokenSource() + typeCheckerTokens.AddOrUpdate(filePath, add, update).Token /// Bypass Adaptive checking and tell the checker to check a file let forceTypeCheck f opts = @@ -1387,9 +1389,13 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar ) do! Array.concat [|dependentFiles; dependentProjects|] - |> Array.map(fun (proj, file) -> forceTypeCheck (UMX.tag file) (Some proj)) + |> Array.map(fun (proj, file) -> + let file = UMX.tag file + let token = getCTForFile file + forceTypeCheck (file) (Some proj) |> Async.withCancellationSafe (fun () -> token) + ) |> Async.Sequential - |> Async.Ignore + |> Async.Ignore } let symbolUseWorkspace pos lineStr text tyRes = @@ -1751,10 +1757,13 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar async { do! Async.Sleep(10) forceGetTypeCheckResults filePath |> ignore - do! forceCheckDepenenciesForFile filePath + + //! for smaller projects this isn't really an issue type checking all dependants but bigger ones it is + //? Should we have a setting to enable/disable this? + // do! forceCheckDepenenciesForFile filePath } - |> Async.StartWithCT (resetAndGetTypeCheckerToken()) + |> Async.Start return () with e -> @@ -1800,7 +1809,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar do! forceCheckDepenenciesForFile filePath } - |> Async.StartWithCT (resetAndGetTypeCheckerToken()) + |> Async.Start return () with e -> From 5f8d32f7bf5884ad5683b8cbc756c47d9a58a00c Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Fri, 28 Oct 2022 18:16:47 -0400 Subject: [PATCH 34/37] Fixed script files and better cancellation during changes --- .../LspServers/AdaptiveFSharpLspServer.fs | 86 ++++++++++++------- 1 file changed, 54 insertions(+), 32 deletions(-) diff --git a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs index 546700de6..bed83f7dd 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs @@ -164,7 +164,7 @@ module AMap = /// Adaptively applies the given mapping function to all elements and returns a new amap containing the results. let mapAdaptiveValue mapper (map: amap<_, aval<'b>>) = - map |> AMap.mapA (fun k v -> AVal.map mapper v) + map |> AMap.mapA (fun k v -> AVal.map (mapper k) v) [] @@ -734,8 +734,16 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar resetCancellationToken filePath textChanges.AddOrElse(filePath, adder, updater)) + let isFileOpen file = + openFilesA + |> AMap.tryFindA file + |> AVal.map(Option.isSome) + + let findFileInOpenFiles' file = + openFilesWithChanges |> AMap.tryFindA file + let findFileInOpenFiles file = - openFilesWithChanges |> AMap.tryFindA file |> AVal.mapOption fst + findFileInOpenFiles' file |> AVal.mapOption fst let forceFindOpenFile filePath = findFileInOpenFiles filePath |> AVal.force @@ -779,33 +787,39 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let forceFindSourceText filePath = forceFindOpenFileOrRead filePath |> Result.map (fun f -> f.Lines) - let openFilesToProjectOptions = - openFilesWithChanges - |> AMap.mapAdaptiveValue (fun (info, cts) -> - let file = info.Lines.FileName - if Utils.isAScript (UMX.untag file) then - (checker, tfmConfig) - ||> AVal.map2 (fun checker tfm -> - let opts = - checker.GetProjectOptionsFromScript(file, info.Lines, tfm) - |> Async.RunSynchronouslyWithCTSafe(fun () -> cts.Token) + let getProjectOptionsForFile' getFile (filePath : string) = + aval { + if Utils.isAScript (UMX.untag filePath) then + let! checker = checker + and! tfmConfig = tfmConfig + and! openFile = getFile filePath + return + openFile + |> Option.bind(fun (info, cts : CancellationTokenSource) -> + let opts = + checker.GetProjectOptionsFromScript(filePath, info.Lines, tfmConfig) + |> Async.RunSynchronouslyWithCTSafe(fun () -> cts.Token) + opts |> Option.iter (scriptFileProjectOptions.Trigger) + opts + ) + |> Option.toList + else + let! opts = loadedProjectOptions + return + opts + |> List.filter (fun (opts) -> opts.SourceFiles |> Array.map Utils.normalizePath |> Array.contains (filePath)) + } - opts |> Option.iter (scriptFileProjectOptions.Trigger) - opts |> Option.map List.singleton |> Option.defaultValue List.empty) - else - loadedProjectOptions - |> AVal.map (fun opts -> - opts - |> List.filter (fun (opts) -> opts.SourceFiles |> Array.map Utils.normalizePath |> Array.contains (file)))) + let getProjectOptionsForFile (filePath : string) = getProjectOptionsForFile' findFileInOpenFiles' filePath - let getProjectOptionsForFile file = - loadedProjectOptions - |> AVal.map (fun opts -> - opts - |> List.filter (fun (opts) -> opts.SourceFiles |> Array.map Utils.normalizePath |> Array.contains (file))) + let openFilesToProjectOptions = + openFilesWithChanges + |> AMap.map (fun name file -> + getProjectOptionsForFile' (fun _ -> AVal.map Some file) name + ) let autoCompleteItems: cmap * (Position -> option) * FSharp.Compiler.Syntax.ParsedInput> = cmap () @@ -964,6 +978,8 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar new CancellationTokenSource() typeCheckerTokens.AddOrUpdate(filePath, add, update).Token + let cancelCTForFile filePath = getCTForFile filePath |> ignore + /// Bypass Adaptive checking and tell the checker to check a file let forceTypeCheck f opts = async { @@ -983,7 +999,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let openFilesToParsedResults = openFilesWithChanges - |> AMap.mapAdaptiveValue (fun (info, cts) -> + |> AMap.mapAdaptiveValue (fun _ (info, cts) -> aval { let file = info.Lines.FileName @@ -1005,7 +1021,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let openFilesToRecentCheckedFilesResults = openFilesWithChanges - |> AMap.mapAdaptiveValue (fun (info, _) -> + |> AMap.mapAdaptiveValue (fun _ (info, _) -> aval { let file = info.Lines.FileName let! checker = checker @@ -1021,7 +1037,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let openFilesToCheckedFilesResults = openFilesWithChanges - |> AMap.mapAdaptiveValue (fun (info, cts) -> + |> AMap.mapAdaptiveValue (fun _ (info, cts) -> aval { let file = info.Lines.FileName let! checker = checker @@ -1394,6 +1410,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let token = getCTForFile file forceTypeCheck (file) (Some proj) |> Async.withCancellationSafe (fun () -> token) ) + |> Seq.toArray // Force iteration |> Async.Sequential |> Async.Ignore } @@ -1703,11 +1720,14 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let doc = p.TextDocument let filePath = doc.GetFilePath() |> Utils.normalizePath - // We want to try to use the file system's datetime if available - let file = VolatileFile.Create(filePath, doc.Text, (Some doc.Version)) - updateOpenFiles file - forceGetTypeCheckResults filePath |> ignore - return () + if isFileOpen filePath |> AVal.force then + return() + else + // We want to try to use the file system's datetime if available + let file = VolatileFile.Create(filePath, doc.Text, (Some doc.Version)) + updateOpenFiles file + forceGetTypeCheckResults filePath |> ignore + return () with e -> logger.error ( Log.setMessage "TextDocumentDidOpen Request Errored {p}" @@ -1751,6 +1771,8 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let doc = p.TextDocument let filePath = doc.GetFilePath() |> Utils.normalizePath + resetAllCancellationTokens () + cancelCTForFile filePath updateTextchanges filePath p From 952b43135ed8944aa9cbc4be2d03d96d14076ef8 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Fri, 28 Oct 2022 19:37:28 -0400 Subject: [PATCH 35/37] Formatting --- src/FsAutoComplete.Core/Commands.fs | 16 +- .../CompilerServiceInterface.fs | 4 +- .../LspServers/AdaptiveFSharpLspServer.fs | 154 ++++++++++-------- 3 files changed, 91 insertions(+), 83 deletions(-) diff --git a/src/FsAutoComplete.Core/Commands.fs b/src/FsAutoComplete.Core/Commands.fs index 323f6968d..64646f5a2 100644 --- a/src/FsAutoComplete.Core/Commands.fs +++ b/src/FsAutoComplete.Core/Commands.fs @@ -713,11 +713,9 @@ module Commands = let getSymbolUsesInProjects (symbol, projects: FSharpProjectOptions list, onFound) = projects - |> List.collect (fun p -> [ - for file in p.SourceFiles do - yield findReferencesInFile (file, symbol, p, onFound) - ] - ) + |> List.collect (fun p -> + [ for file in p.SourceFiles do + yield findReferencesInFile (file, symbol, p, onFound) ]) |> Async.Parallel |> Async.map (Array.toList >> FsToolkit.ErrorHandling.List.sequenceResultM) @@ -819,15 +817,11 @@ module Commands = let actualUseRange = Range.mkRange symbolUseRange.FileName startPos endPos symbolUseRanges.Add actualUseRange } - |> Async.map(fun x -> + |> Async.map (fun x -> match x with | Ok () -> () | Error e -> - commandsLogger.info( - Log.setMessage "OnFound failed: {errpr}" - >> Log.addContextDestructured "error" e - ) - ) + commandsLogger.info (Log.setMessage "OnFound failed: {errpr}" >> Log.addContextDestructured "error" e)) let! _ = getSymbolUsesInProjects (symbol, projects, onFound) diff --git a/src/FsAutoComplete.Core/CompilerServiceInterface.fs b/src/FsAutoComplete.Core/CompilerServiceInterface.fs index 05f9f9754..7bc6b751f 100644 --- a/src/FsAutoComplete.Core/CompilerServiceInterface.fs +++ b/src/FsAutoComplete.Core/CompilerServiceInterface.fs @@ -293,9 +293,7 @@ type FSharpCompilerServiceChecker(hasAnalyzers) = member _.TryGetLastCheckResultForFile(file: string) = let opName = sprintf "TryGetLastCheckResultForFile - %A" file - checkerLogger.info ( - Log.setMessage "{opName}" >> Log.addContextDestructured "opName" opName - ) + checkerLogger.info (Log.setMessage "{opName}" >> Log.addContextDestructured "opName" opName) match lastCheckResults.TryGetValue(file) with | (true, v) -> Some v diff --git a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs index bed83f7dd..0b9673599 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs @@ -458,8 +458,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar )) - file - |> AVal.mapDisposableTuple (fun x -> x, cb) + file |> AVal.mapDisposableTuple (fun x -> x, cb) let loader = cval workspaceLoader @@ -735,9 +734,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar textChanges.AddOrElse(filePath, adder, updater)) let isFileOpen file = - openFilesA - |> AMap.tryFindA file - |> AVal.map(Option.isSome) + openFilesA |> AMap.tryFindA file |> AVal.map (Option.isSome) let findFileInOpenFiles' file = openFilesWithChanges |> AMap.tryFindA file @@ -788,38 +785,39 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar forceFindOpenFileOrRead filePath |> Result.map (fun f -> f.Lines) - let getProjectOptionsForFile' getFile (filePath : string) = + let getProjectOptionsForFile' getFile (filePath: string) = aval { if Utils.isAScript (UMX.untag filePath) then let! checker = checker and! tfmConfig = tfmConfig and! openFile = getFile filePath + return openFile - |> Option.bind(fun (info, cts : CancellationTokenSource) -> - let opts = - checker.GetProjectOptionsFromScript(filePath, info.Lines, tfmConfig) - |> Async.RunSynchronouslyWithCTSafe(fun () -> cts.Token) - opts |> Option.iter (scriptFileProjectOptions.Trigger) - opts - ) + |> Option.bind (fun (info, cts: CancellationTokenSource) -> + let opts = + checker.GetProjectOptionsFromScript(filePath, info.Lines, tfmConfig) + |> Async.RunSynchronouslyWithCTSafe(fun () -> cts.Token) + + opts |> Option.iter (scriptFileProjectOptions.Trigger) + opts) |> Option.toList else let! opts = loadedProjectOptions + return - opts - |> List.filter (fun (opts) -> opts.SourceFiles |> Array.map Utils.normalizePath |> Array.contains (filePath)) + opts + |> List.filter (fun (opts) -> opts.SourceFiles |> Array.map Utils.normalizePath |> Array.contains (filePath)) } - let getProjectOptionsForFile (filePath : string) = getProjectOptionsForFile' findFileInOpenFiles' filePath + let getProjectOptionsForFile (filePath: string) = + getProjectOptionsForFile' findFileInOpenFiles' filePath let openFilesToProjectOptions = openFilesWithChanges - |> AMap.map (fun name file -> - getProjectOptionsForFile' (fun _ -> AVal.map Some file) name - ) + |> AMap.map (fun name file -> getProjectOptionsForFile' (fun _ -> AVal.map Some file) name) let autoCompleteItems: cmap * (Position -> option) * FSharp.Compiler.Syntax.ParsedInput> = cmap () @@ -958,8 +956,8 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar System.Runtime.GCSettings.LargeObjectHeapCompactionMode <- System.Runtime.GCLargeObjectHeapCompactionMode.CompactOnce - // GC.Collect() - // GC.WaitForPendingFinalizers() + // GC.Collect() + // GC.WaitForPendingFinalizers() }, ct ) @@ -968,14 +966,17 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar } - let typeCheckerTokens = new System.Collections.Concurrent.ConcurrentDictionary,CancellationTokenSource>() + let typeCheckerTokens = + new System.Collections.Concurrent.ConcurrentDictionary, CancellationTokenSource>() let getCTForFile filePath = let add x = new CancellationTokenSource() - let update x (typeCheckerToken : CancellationTokenSource) = + + let update x (typeCheckerToken: CancellationTokenSource) = typeCheckerToken.Cancel() // typeCheckerToken.Dispose() new CancellationTokenSource() + typeCheckerTokens.AddOrUpdate(filePath, add, update).Token let cancelCTForFile filePath = getCTForFile filePath |> ignore @@ -987,14 +988,21 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar logger.info (Log.setMessage "Forced Check : {file}" >> Log.addContextDestructured "file" f) let checker = checker |> AVal.force let config = config |> AVal.force - let opts = opts |> Option.orElseWith(fun () -> getProjectOptionsForFile f |> AVal.force |> Seq.tryHead ) + + let opts = + opts + |> Option.orElseWith (fun () -> getProjectOptionsForFile f |> AVal.force |> Seq.tryHead) match forceFindOpenFileOrRead f, opts with | Ok (fileInfo), Some opts -> return! parseAndCheckFile checker fileInfo opts config |> Async.Ignore | _, _ -> () with e -> - logger.warn (Log.setMessage "Forced Check error : {file}" >> Log.addContextDestructured "file" f >> Log.addExn e) + logger.warn ( + Log.setMessage "Forced Check error : {file}" + >> Log.addContextDestructured "file" f + >> Log.addExn e + ) } let openFilesToParsedResults = @@ -1021,7 +1029,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let openFilesToRecentCheckedFilesResults = openFilesWithChanges - |> AMap.mapAdaptiveValue (fun _ (info, _) -> + |> AMap.mapAdaptiveValue (fun _ (info, _) -> aval { let file = info.Lines.FileName let! checker = checker @@ -1104,13 +1112,16 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let openFilesToCheckedDeclarations () = openFilesToCheckedFilesResults |> AMap.force - |> HashMap.map(fun _ v -> v |> AVal.mapOption(fun c -> c.GetParseResults.GetNavigationItems().Declarations) |> AVal.force ) + |> HashMap.map (fun _ v -> + v + |> AVal.mapOption (fun c -> c.GetParseResults.GetNavigationItems().Declarations) + |> AVal.force) let getDeclarations filename = // openFilesToCheckedDeclarations |> AMap.tryFindAndFlatten (filename) openFilesToCheckedFilesResults |> AMap.tryFindAndFlatten filename - |> AVal.mapOption(fun c -> c.GetParseResults.GetNavigationItems().Declarations) + |> AVal.mapOption (fun c -> c.GetParseResults.GetNavigationItems().Declarations) let getFilePathAndPosition (p: ITextDocumentPositionParams) = let filePath = p.GetFilePath() |> Utils.normalizePath @@ -1325,16 +1336,20 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let thisShouldBeASettingToTurnOffHoldingFilesInMemory = false - if thisShouldBeASettingToTurnOffHoldingFilesInMemory || doesNotExist filePath || isOutsideWorkspace filePath then + if + thisShouldBeASettingToTurnOffHoldingFilesInMemory + || doesNotExist filePath + || isOutsideWorkspace filePath + then logger.info ( Log.setMessage "Removing cached data for {file}." >> Log.addContext "file" filePath ) transact (fun () -> - openFiles.Remove filePath |> ignore - textChanges.Remove filePath |> ignore - ) + openFiles.Remove filePath |> ignore + textChanges.Remove filePath |> ignore) + diagnosticCollections.ClearFor(uri) else logger.info ( @@ -1344,19 +1359,21 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let getDependentFilesForFile file = let projects = getProjectOptionsForFile file |> AVal.force + projects |> List.toArray - |> Array.collect(fun proj -> + |> Array.collect (fun proj -> logger.info ( Log.setMessage "Source Files: {sourceFiles}" >> Log.addContextDestructured "sourceFiles" proj.SourceFiles ) - let idx = proj.SourceFiles |> Array.findIndex(fun x -> x = UMX.untag file) + + let idx = proj.SourceFiles |> Array.findIndex (fun x -> x = UMX.untag file) + proj.SourceFiles |> Array.splitAt idx |> snd - |> Array.map(fun sourceFile -> proj, sourceFile) - ) + |> Array.map (fun sourceFile -> proj, sourceFile)) |> Array.distinct let getDependentProjectsOfProjects ps = @@ -1391,29 +1408,29 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar Seq.toList allDependents - let forceCheckDepenenciesForFile filePath = async { - let dependentFiles = - getDependentFilesForFile filePath - let dependentProjects = - getProjectOptionsForFile filePath - |> AVal.force - |> getDependentProjectsOfProjects - |> List.toArray - |> Array.collect(fun proj -> - proj.SourceFiles - |> Array.map(fun sourceFile -> proj, sourceFile) - ) - do! - Array.concat [|dependentFiles; dependentProjects|] - |> Array.map(fun (proj, file) -> - let file = UMX.tag file - let token = getCTForFile file - forceTypeCheck (file) (Some proj) |> Async.withCancellationSafe (fun () -> token) - ) - |> Seq.toArray // Force iteration - |> Async.Sequential - |> Async.Ignore - } + let forceCheckDepenenciesForFile filePath = + async { + let dependentFiles = getDependentFilesForFile filePath + + let dependentProjects = + getProjectOptionsForFile filePath + |> AVal.force + |> getDependentProjectsOfProjects + |> List.toArray + |> Array.collect (fun proj -> proj.SourceFiles |> Array.map (fun sourceFile -> proj, sourceFile)) + + do! + Array.concat [| dependentFiles; dependentProjects |] + |> Array.map (fun (proj, file) -> + let file = UMX.tag file + let token = getCTForFile file + + forceTypeCheck (file) (Some proj) + |> Async.withCancellationSafe (fun () -> token)) + |> Seq.toArray // Force iteration + |> Async.Sequential + |> Async.Ignore + } let symbolUseWorkspace pos lineStr text tyRes = @@ -1671,14 +1688,10 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar workspacePaths.Value <- WorkspaceChosen.Projs(HashSet.ofList projs)) let defaultSettings = - { Helpers.defaultServerCapabilities - with + { Helpers.defaultServerCapabilities with TextDocumentSync = Helpers.defaultServerCapabilities.TextDocumentSync - |> Option.map(fun x -> - { x with Change = Some TextDocumentSyncKind.Incremental } - ) - } + |> Option.map (fun x -> { x with Change = Some TextDocumentSyncKind.Incremental }) } return { InitializeResult.Default with Capabilities = defaultSettings } @@ -1720,8 +1733,9 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let doc = p.TextDocument let filePath = doc.GetFilePath() |> Utils.normalizePath + if isFileOpen filePath |> AVal.force then - return() + return () else // We want to try to use the file system's datetime if available let file = VolatileFile.Create(filePath, doc.Text, (Some doc.Version)) @@ -1780,9 +1794,9 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar do! Async.Sleep(10) forceGetTypeCheckResults filePath |> ignore - //! for smaller projects this isn't really an issue type checking all dependants but bigger ones it is - //? Should we have a setting to enable/disable this? - // do! forceCheckDepenenciesForFile filePath + //! for smaller projects this isn't really an issue type checking all dependants but bigger ones it is + //? Should we have a setting to enable/disable this? + // do! forceCheckDepenenciesForFile filePath } |> Async.Start @@ -1824,6 +1838,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar transact (fun () -> updateOpenFiles file textChanges.Remove filePath |> ignore) + async { do! Async.Sleep(10) forceGetTypeCheckResults filePath |> ignore @@ -2670,6 +2685,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar ) let fn = p.TextDocument.GetFilePath() |> Utils.normalizePath + match getDeclarations (fn) |> AVal.force with | None -> return None | Some decls -> From 1ac55c81cc7bca8fb798757a210bece1fee7c620 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Fri, 28 Oct 2022 22:12:50 -0400 Subject: [PATCH 36/37] Better filesystem logging --- src/FsAutoComplete.Core/FileSystem.fs | 29 +++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/FsAutoComplete.Core/FileSystem.fs b/src/FsAutoComplete.Core/FileSystem.fs index 129a27af1..edbe18105 100644 --- a/src/FsAutoComplete.Core/FileSystem.fs +++ b/src/FsAutoComplete.Core/FileSystem.fs @@ -409,11 +409,18 @@ type FileSystem(actualFs: IFileSystem, tryFindFile: string -> Volatil let fsLogger = LogProvider.getLoggerByName "FileSystem" let getContent (filename: string) = - fsLogger.debug (Log.setMessage "Getting content of `{path}`" >> Log.addContext "path" filename) + filename |> tryFindFile - |> Option.map (fun file -> file.Lines.ToString() |> System.Text.Encoding.UTF8.GetBytes) + |> Option.map (fun file -> + fsLogger.debug ( + Log.setMessage "Getting content of `{path}` - {hash}" + >> Log.addContext "path" filename + >> Log.addContext "hash" (file.Lines.GetHashCode()) + ) + file.Lines.ToString() |> System.Text.Encoding.UTF8.GetBytes + ) /// translation of the BCL's Windows logic for Path.IsPathRooted. /// @@ -457,12 +464,18 @@ type FileSystem(actualFs: IFileSystem, tryFindFile: string -> Volatil expanded - member _.GetLastWriteTimeShim(f: string) = - f - |> Utils.normalizePath - |> tryFindFile - |> Option.map (fun f -> f.Touched) - |> Option.defaultWith (fun () -> actualFs.GetLastWriteTimeShim f) + member _.GetLastWriteTimeShim(filename: string) = + let result = + filename + |> Utils.normalizePath + |> tryFindFile + |> Option.map (fun f -> f.Touched) + |> Option.defaultWith (fun () -> actualFs.GetLastWriteTimeShim filename) + + fsLogger.debug (Log.setMessage "GetLastWriteTimeShim of `{path}` - {date} " + >> Log.addContext "path" filename + >> Log.addContext "date" result) + result member _.NormalizePathShim(f: string) = f |> Utils.normalizePath |> UMX.untag From b5818044be1613a3295cbb499b58e860395745ac Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Fri, 28 Oct 2022 22:13:13 -0400 Subject: [PATCH 37/37] Fixes files being wrongly marked out of date --- .../LspServers/AdaptiveFSharpLspServer.fs | 88 ++++++++++++------- 1 file changed, 55 insertions(+), 33 deletions(-) diff --git a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs index 0b9673599..7ead25333 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs @@ -614,8 +614,15 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let fantomasLogger = LogProvider.getLoggerByName "Fantomas" let fantomasService: FantomasService = new LSPFantomasService() :> FantomasService + let openFilesTokens = + cmap, cval> () + + + let openFilesTokensA = + openFilesTokens |> AMap.map (fun _ v -> v :> aval<_>) + let openFiles = - cmap, cval> () + cmap, cval> () let openFilesA = openFiles |> AMap.map (fun _ v -> v :> aval<_>) @@ -623,16 +630,16 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let textChangesA = textChanges |> AMap.map (fun _ x -> x :> aset<_>) - let openFilesWithChanges: amap<_, aval> = + let openFilesWithChanges: amap<_, aval> = openFilesA |> AMap.map (fun filePath file -> aval { - let! (file, token) = file + let! (file) = file and! changes = textChangesA |> AMap.tryFind filePath match changes with - | None -> return (file, token) + | None -> return (file) | Some c -> let! ps = c |> ASet.toAVal @@ -680,18 +687,18 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar text) - return (file, token) + return (file) }) let resetFileVal (fileVal: cval<_>) = - let (oldFile: VolatileFile, cts: CancellationTokenSource) = fileVal |> AVal.force + let cts: CancellationTokenSource = fileVal |> AVal.force try logger.info ( Log.setMessage "Cancelling {filePath} - {version}" - >> Log.addContextDestructured "filePath" oldFile.FileName - >> Log.addContextDestructured "version" oldFile.Version + >> Log.addContextDestructured "filePath" fileVal + // >> Log.addContextDestructured "version" oldFile.Version ) cts.Cancel() @@ -702,28 +709,38 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar // ignore if already cancelled () - transact (fun () -> fileVal.Value <- oldFile, new CancellationTokenSource()) + transact (fun () -> fileVal.Value <- new CancellationTokenSource()) let resetCancellationToken filePath = - openFiles |> AMap.tryFind filePath |> AVal.force |> Option.iter (resetFileVal) + openFilesTokens |> AMap.tryFind filePath |> AVal.force |> Option.iter (resetFileVal) + + let adder _ = + cval (new CancellationTokenSource()) + + let updater _ (v: cval<_>) = + resetFileVal v + + transact (fun () -> + openFilesTokens.AddOrElse(filePath, adder, updater) + ) let resetAllCancellationTokens () = - let files = openFiles |> AMap.force + let files = openFilesTokens |> AMap.force for (_, fileVal) in files do resetFileVal fileVal let updateOpenFiles (file: VolatileFile) = let adder _ = - cval (file, new CancellationTokenSource()) + cval file let updater _ (v: cval<_>) = - let (_, cts) = v |> AVal.force - v.Value <- file, cts + v.Value <- file transact (fun () -> resetCancellationToken file.FileName - openFiles.AddOrElse(file.Lines.FileName, adder, updater)) + openFiles.AddOrElse(file.Lines.FileName, adder, updater) + ) let updateTextchanges filePath p = let adder _ = cset<_> [ p ] @@ -740,7 +757,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar openFilesWithChanges |> AMap.tryFindA file let findFileInOpenFiles file = - findFileInOpenFiles' file |> AVal.mapOption fst + findFileInOpenFiles' file let forceFindOpenFile filePath = findFileInOpenFiles filePath |> AVal.force @@ -790,17 +807,20 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar if Utils.isAScript (UMX.untag filePath) then let! checker = checker and! tfmConfig = tfmConfig - and! openFile = getFile filePath - + and! (openFile : Option<_>) = getFile filePath + and! cts = openFilesTokensA |> AMap.tryFindA filePath return - openFile - |> Option.bind (fun (info, cts: CancellationTokenSource) -> - let opts = + option { + let! (info : VolatileFile) = openFile + and! cts = cts + + let! opts = checker.GetProjectOptionsFromScript(filePath, info.Lines, tfmConfig) |> Async.RunSynchronouslyWithCTSafe(fun () -> cts.Token) - opts |> Option.iter (scriptFileProjectOptions.Trigger) - opts) + opts |> scriptFileProjectOptions.Trigger + return opts + } |> Option.toList else let! opts = loadedProjectOptions @@ -1007,15 +1027,16 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let openFilesToParsedResults = openFilesWithChanges - |> AMap.mapAdaptiveValue (fun _ (info, cts) -> + |> AMap.mapAdaptiveValue (fun _ (info) -> aval { let file = info.Lines.FileName let! checker = checker and! projectOptions = getProjectOptionsForFile file + and! cts = openFilesTokensA |> AMap.tryFindA file - match List.tryHead projectOptions with - | Some opts -> + match List.tryHead projectOptions, cts with + | Some opts, Some cts -> return Debug.measure "parseFile" <| fun () -> @@ -1023,13 +1044,13 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar checker.ParseFile(file, info.Lines, opts) |> Async.RunSynchronouslyWithCTSafe(fun () -> cts.Token) - | None -> return None + | _ -> return None }) let openFilesToRecentCheckedFilesResults = openFilesWithChanges - |> AMap.mapAdaptiveValue (fun _ (info, _) -> + |> AMap.mapAdaptiveValue (fun _ (info) -> aval { let file = info.Lines.FileName let! checker = checker @@ -1040,20 +1061,21 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let parseAndCheck = checker.TryGetRecentCheckResultsForFile(file, opts, info.Lines) return parseAndCheck - | None -> return None + | _ -> return None }) let openFilesToCheckedFilesResults = openFilesWithChanges - |> AMap.mapAdaptiveValue (fun _ (info, cts) -> + |> AMap.mapAdaptiveValue (fun _ (info) -> aval { let file = info.Lines.FileName let! checker = checker and! projectOptions = getProjectOptionsForFile file and! config = config + and! cts = openFilesTokensA |> AMap.tryFindA file - match List.tryHead projectOptions with - | Some (opts) -> + match List.tryHead projectOptions, cts with + | Some (opts), Some cts -> let parseAndCheck = Debug.measure $"parseAndCheckFile - {file}" <| fun () -> @@ -1061,7 +1083,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar |> Async.RunSynchronouslyWithCTSafe(fun () -> cts.Token) return parseAndCheck - | None -> return None + | _ -> return None }) let getParseResults filePath =