diff --git a/src/FsAutoComplete.Core/CompilerServiceInterface.fs b/src/FsAutoComplete.Core/CompilerServiceInterface.fs index f901b9feb..5eff676c8 100644 --- a/src/FsAutoComplete.Core/CompilerServiceInterface.fs +++ b/src/FsAutoComplete.Core/CompilerServiceInterface.fs @@ -56,9 +56,8 @@ type CompilerProjectOption = member x.SourceFilesTagged = match x with - | BackgroundCompiler(options) -> options.SourceFiles |> Array.toList - | TransparentCompiler(snapshot) -> snapshot.SourceFiles |> List.map (fun f -> f.FileName) - |> List.map Utils.normalizePath + | BackgroundCompiler(options) -> options.SourceFiles |> Array.map Utils.normalizePath |> Array.toList + | TransparentCompiler(snapshot) -> snapshot.SourceFiles |> List.map (fun f -> Utils.normalizePath f.FileName) member x.ReferencedProjectsPath = match x with @@ -189,12 +188,16 @@ type FSharpCompilerServiceChecker None let processFSIArgs args = - (([||], [||]), args) - ||> Array.fold (fun (args, files) arg -> + let argsOut = ResizeArray() + let filesOut = ResizeArray() + + for arg in args do match arg with | StartsWith "--use:" file - | StartsWith "--load:" file -> args, Array.append files [| file |] - | arg -> Array.append args [| arg |], files) + | StartsWith "--load:" file -> filesOut.Add(file) + | arg -> argsOut.Add(arg) + + argsOut.ToArray(), filesOut.ToArray() let (|Reference|_|) (opt: string) = if opt.StartsWith("-r:", StringComparison.Ordinal) then diff --git a/src/FsAutoComplete.Core/FileSystem.fs b/src/FsAutoComplete.Core/FileSystem.fs index 54ae88996..ef5eae939 100644 --- a/src/FsAutoComplete.Core/FileSystem.fs +++ b/src/FsAutoComplete.Core/FileSystem.fs @@ -169,6 +169,9 @@ module RoslynSourceText = type RoslynSourceTextFile(fileName: string, sourceText: SourceText) = + let cachedLines = + lazy (sourceText.Lines |> Seq.toArray |> Array.map (fun l -> l.ToString())) + let walk ( x: IFSACSourceText, @@ -250,8 +253,7 @@ module RoslynSourceText = member x.TotalRange: Range = (Range.mkRange (UMX.untag fileName) Position.pos0 ((x :> IFSACSourceText).LastFilePosition)) - member x.Lines: string array = - sourceText.Lines |> Seq.toArray |> Array.map (fun l -> l.ToString()) + member x.Lines: string array = cachedLines.Value member this.GetText(range: Range) : Result = range.ToRoslynTextSpan(sourceText) |> sourceText.GetSubText |> string |> Ok @@ -488,7 +490,24 @@ type FileSystem(actualFs: IFileSystem, tryFindFile: string -> Volatil >> Log.addContext "hash" (file.Source.GetHashCode()) ) - file.Source.ToString() |> System.Text.Encoding.UTF8.GetBytes) + // Write source text to bytes in chunks via CopyTo, avoiding a full intermediate string allocation + let source = file.Source :> FSharp.Compiler.Text.ISourceText + let length = source.Length + let ms = new MemoryStream() + let utf8NoBom = System.Text.UTF8Encoding(encoderShouldEmitUTF8Identifier = false) + use sw = new StreamWriter(ms, utf8NoBom, bufferSize = 4096, leaveOpen = true) + let chunkSize = 8192 + let charBuffer = Array.zeroCreate (min length chunkSize) + let mutable offset = 0 + + while offset < length do + let count = min (length - offset) charBuffer.Length + source.CopyTo(offset, charBuffer, 0, count) + sw.Write(charBuffer, 0, count) + offset <- offset + count + + sw.Flush() + ms.ToArray()) /// translation of the BCL's Windows logic for Path.IsPathRooted. /// diff --git a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs index 0b3e55043..eb1725752 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs @@ -763,10 +763,14 @@ type AdaptiveFSharpLspServer | Error e -> return Error e } - let getCompletions forceGetTypeCheckResultsStale = + let getCompletions forceGetTypeCheckResultsStale rereadFile = asyncResult { - let! volatileFile = state.GetOpenFileOrRead filePath + let! volatileFile = + if rereadFile then + state.GetOpenFileOrRead filePath + else + async { return Ok volatileFile } let! lineStr = volatileFile.Source @@ -815,8 +819,12 @@ type AdaptiveFSharpLspServer match e with | "Should not have empty completions" -> // If we don't get any completions, assume we need to wait for a full typecheck - getCompletions state.GetOpenFileTypeCheckResults - | _ -> getCompletions state.GetOpenFileTypeCheckResultsCached + // No need to re-read the file — only the typecheck results matter + getCompletions state.GetOpenFileTypeCheckResults false + | "TextDocumentCompletion was sent before TextDocumentDidChange" -> + // File content is stale, re-read on next attempt + getCompletions state.GetOpenFileTypeCheckResultsCached true + | _ -> getCompletions state.GetOpenFileTypeCheckResultsCached true let getCodeToInsert (d: DeclarationListItem) = match d.NamespaceToOpen with @@ -850,7 +858,7 @@ type AdaptiveFSharpLspServer (TimeSpan.FromMilliseconds(15.)) 100 handleError - (getCompletions state.GetOpenFileTypeCheckResultsCached) + (getCompletions state.GetOpenFileTypeCheckResultsCached false) |> AsyncResult.ofStringErr with | None -> return! LspResult.success (None) diff --git a/src/FsAutoComplete/LspServers/AdaptiveServerState.fs b/src/FsAutoComplete/LspServers/AdaptiveServerState.fs index 9871ed621..1d0f3ca59 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveServerState.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveServerState.fs @@ -1773,7 +1773,7 @@ type AdaptiveState let tags = [ SemanticConventions.fsac_sourceCodePath, box (UMX.untag file.Source.FileName) SemanticConventions.projectFilePath, box (options.ProjectFileName) - "source.text", box (file.Source.String) + "source.length", box (file.Source.Length) "source.version", box (file.Version) ]