Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Even more Adaptive performance tweaks #1035

Closed
wants to merge 37 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
51911b4
Fixes ResolveNamespace in nested modules
TheAngryByrd Oct 12, 2022
cfeb732
Adds tests to verify ResolveNamespace fix
TheAngryByrd Oct 12, 2022
f360a10
Fixes ResolveNamespace tests
TheAngryByrd Oct 13, 2022
5d52b08
More tests around ResolveNamespace
TheAngryByrd Oct 20, 2022
ae377b7
Adds lsp server to debug launch actions
TheAngryByrd Oct 21, 2022
e7e215e
Fixes resolvenamespace with attributes on modules
TheAngryByrd Oct 21, 2022
2926f44
formatting
TheAngryByrd Oct 21, 2022
aa1b009
Clear caching on project load and incremental text support
TheAngryByrd Oct 15, 2022
91b4152
Simplifying File CancellionToken relationship
TheAngryByrd Oct 15, 2022
0d47557
Typecheck saved file first
TheAngryByrd Oct 15, 2022
91b06a1
Better completion handling
TheAngryByrd Oct 16, 2022
0e184f9
Start things async to get type check results faster
TheAngryByrd Oct 16, 2022
36cad03
Speed up completions by using stale results
TheAngryByrd Oct 16, 2022
5a0c003
Better error handling for ModifyText
TheAngryByrd Oct 16, 2022
41b7992
More resilient Completion checks
TheAngryByrd Oct 16, 2022
cccb245
be more resilient with no finding typecheck result
TheAngryByrd Oct 16, 2022
3449a2e
Revert forgetDocument changes
TheAngryByrd Oct 16, 2022
e7e00f0
Consolodated LSP Server settings
TheAngryByrd Oct 16, 2022
f68c30e
Formatting
TheAngryByrd Oct 16, 2022
5cff4c0
Cleanup/formatting
TheAngryByrd Oct 16, 2022
ebf7754
fixes around cancellationtoken replacements
TheAngryByrd Oct 19, 2022
b8a4593
Formatting and refactoring
TheAngryByrd Oct 20, 2022
a024744
Forget file and incremental text changes
TheAngryByrd Oct 19, 2022
9bfa3d6
enable incremental text again
TheAngryByrd Oct 20, 2022
5e6dbac
parallelize getSymbolUsesInProjects
TheAngryByrd Oct 20, 2022
6973108
Fixes memory leak and minor optimizations
TheAngryByrd Oct 25, 2022
2938862
Removes textChanges on forgetfile, fixesopenFilesToCheckedDeclarations
TheAngryByrd Oct 26, 2022
5f1d04f
Cleanup findFiles
TheAngryByrd Oct 26, 2022
42a821b
Use result instead of failwith
TheAngryByrd Oct 26, 2022
7d116cf
add missing log context
TheAngryByrd Oct 26, 2022
62d5af3
Fixing comments
TheAngryByrd Oct 27, 2022
bfe33f9
Type checking whole solution
TheAngryByrd Oct 27, 2022
24a1d73
Handle dependent file checks
TheAngryByrd Oct 28, 2022
5f8d32f
Fixed script files and better cancellation during changes
TheAngryByrd Oct 28, 2022
952b431
Formatting
TheAngryByrd Oct 28, 2022
1ac55c8
Better filesystem logging
TheAngryByrd Oct 29, 2022
b581804
Fixes files being wrongly marked out of date
TheAngryByrd Oct 29, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
"args": [
"--debug",
"--filter",
"FSAC.lsp.${input:loader}.${input:testName}"
"FSAC.lsp.${input:loader}.${input:lsp-server}.${input:testName}"
]
}
],
Expand All @@ -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`",
Expand Down
1 change: 1 addition & 0 deletions paket.dependencies
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions paket.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/FsAutoComplete.Core/CodeGeneration.fs
Original file line number Diff line number Diff line change
@@ -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
Expand Down
71 changes: 36 additions & 35 deletions src/FsAutoComplete.Core/Commands.fs
Original file line number Diff line number Diff line change
Expand Up @@ -713,11 +713,9 @@ module Commands =

let getSymbolUsesInProjects (symbol, projects: FSharpProjectOptions list, onFound) =
projects
|> List.map (fun p ->
asyncResult {
for file in p.SourceFiles do
do! 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)

Expand Down Expand Up @@ -770,9 +768,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")
Expand All @@ -790,37 +788,40 @@ 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)

Expand Down
50 changes: 45 additions & 5 deletions src/FsAutoComplete.Core/CompilerServiceInterface.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -26,6 +31,9 @@ type FSharpCompilerServiceChecker(hasAnalyzers) =

let entityCache = EntityCache()

let mutable lastCheckResults: IMemoryCache =
new MemoryCache(MemoryCacheOptions(SizeLimit = Nullable<_>(200L)))

let checkerLogger = LogProvider.getLoggerByName "Checker"
let optsLogger = LogProvider.getLoggerByName "Opts"

Expand Down Expand Up @@ -227,14 +235,22 @@ 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() =
let oldlastCheckResults = lastCheckResults
lastCheckResults <- new MemoryCache(MemoryCacheOptions(SizeLimit = Nullable<_>(20L)))
oldlastCheckResults.Dispose()
checker.InvalidateAll()
checker.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients()

member __.ParseFile(fn: string<LocalPath>, source, fpo) =
checkerLogger.info (Log.setMessage "ParseFile - {file}" >> Log.addContextDestructured "file" fn)

let path = UMX.untag fn
checker.ParseFile(path, source, fpo)

member __.ParseAndCheckFileInProject(filePath: string<LocalPath>, version, source: ISourceText, options) =
async {
asyncResult {
let opName = sprintf "ParseAndCheckFileInProject - %A" filePath

checkerLogger.info (Log.setMessage "{opName}" >> Log.addContextDestructured "opName" opName)
Expand All @@ -255,32 +271,56 @@ 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<LocalPath>) =
let opName = sprintf "TryGetLastCheckResultForFile - %A" file

checkerLogger.info (Log.setMessage "{opName}" >> Log.addContextDestructured "opName" opName)

match lastCheckResults.TryGetValue<ParseAndCheckResults>(file) with
| (true, v) -> Some v
| _ -> None

member __.TryGetRecentCheckResultsForFile(file: string<LocalPath>, options, source: ISourceText) =
let opName = sprintf "TryGetRecentCheckResultsForFile - %A" file

checkerLogger.info (
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 "opName" opName
>> Log.addContextDestructured "version" version
)

ParseAndCheckResults(pr, cr, entityCache))

checkerLogger.info (
Log.setMessage "{opName} - {hash} - cacheHit {cacheHit}"
Expand Down
39 changes: 27 additions & 12 deletions src/FsAutoComplete.Core/FileSystem.fs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ type NamedText(fileName: string<LocalPath>, 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<System.Text.StringBuilder>

// whole intermediate lines, including newlines
Expand All @@ -155,7 +155,7 @@ type NamedText(fileName: string<LocalPath>, 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<System.Text.StringBuilder>

Ok(builder.ToString())
Expand Down Expand Up @@ -260,8 +260,8 @@ type NamedText(fileName: string<LocalPath>, str: string) =
member x.ModifyText(m: FSharp.Compiler.Text.Range, text: string) : Result<NamedText, string> =
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}")
and! endText = x[endRange] |> Result.mapError (fun x -> $"endRange -> {x}")
let totalText = startText + text + endText
return NamedText(x.FileName, totalText)
}
Expand Down Expand Up @@ -355,6 +355,8 @@ type VolatileFile =
Lines: NamedText
Version: int option }

member this.FileName = this.Lines.FileName

/// <summary>Updates the Lines value</summary>
member this.SetLines(lines) = { this with Lines = lines }

Expand Down Expand Up @@ -407,11 +409,18 @@ type FileSystem(actualFs: IFileSystem, tryFindFile: string<LocalPath> -> Volatil
let fsLogger = LogProvider.getLoggerByName "FileSystem"

let getContent (filename: string<LocalPath>) =
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.
///
Expand Down Expand Up @@ -455,12 +464,18 @@ type FileSystem(actualFs: IFileSystem, tryFindFile: string<LocalPath> -> 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

Expand Down
2 changes: 1 addition & 1 deletion src/FsAutoComplete.Core/Lexer.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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 ->
Expand Down
Loading