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

Make FSAC initialization assume workspacePaths[0] instead of RootPath #1223

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
2 changes: 1 addition & 1 deletion paket.lock
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ NUGET
System.Reflection.Metadata (>= 5.0)
Ionide.Analyzers (0.7)
Ionide.KeepAChangelog.Tasks (0.1.8) - copy_local: true
Ionide.LanguageServerProtocol (0.4.20)
Ionide.LanguageServerProtocol (0.4.22)
FSharp.Core (>= 6.0)
Newtonsoft.Json (>= 13.0.1)
StreamJsonRpc (>= 2.16.36)
Expand Down
18 changes: 14 additions & 4 deletions src/FsAutoComplete.Core/KeywordList.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ open FSharp.Compiler.Tokenization
open FSharp.Compiler.EditorServices
open FSharp.Compiler.Symbols

// 44 is the 'This construct is deprecated' error - we've addressed these by moving to TextEdit for the completionItems here,
// but the helper function for the CompletionItem record has to init the field to None, so it's still being counted as used.
#nowarn "44"

module KeywordList =

let keywordDescriptions = FSharpKeywords.KeywordsWithDescription |> dict
Expand Down Expand Up @@ -39,15 +43,21 @@ module KeywordList =
"line", "Indicates the original source code line" ]
|> dict

let hashSymbolCompletionItems =
let private textEdit text pos : U2<TextEdit, _> =
U2.First(
{ Range = { Start = pos; End = pos }
NewText = text }
)

let hashSymbolCompletionItems pos =
hashDirectives
|> Seq.map (fun kv ->
let label = "#" + kv.Key

{ CompletionItem.Create(kv.Key) with
Data = Some(Newtonsoft.Json.Linq.JValue(label))
Kind = Some CompletionItemKind.Keyword
InsertText = Some kv.Key
TextEdit = Some(textEdit kv.Value pos)
FilterText = Some kv.Key
SortText = Some kv.Key
Documentation = Some(Documentation.String kv.Value)
Expand All @@ -57,13 +67,13 @@ module KeywordList =
let allKeywords: string list =
keywordDescriptions |> Seq.map ((|KeyValue|) >> fst) |> Seq.toList

let keywordCompletionItems =
let keywordCompletionItems pos =
allKeywords
|> List.mapi (fun id k ->
{ CompletionItem.Create(k) with
Data = Some(Newtonsoft.Json.Linq.JValue(k))
Kind = Some CompletionItemKind.Keyword
InsertText = Some k
TextEdit = Some(textEdit k pos)
SortText = Some(sprintf "1000000%d" id)
FilterText = Some k
Label = k })
Expand Down
47 changes: 38 additions & 9 deletions src/FsAutoComplete/LspHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
type FcsPos = FSharp.Compiler.Text.Position
module FcsPos = FSharp.Compiler.Text.Position

#nowarn "44" // We're not using the Deprecated member of DocumentSymbol here, but it's still required to be _set_.

module FcsPos =
let subtractColumn (pos: FcsPos) (column: int) = FcsPos.mkPos pos.Line (pos.Column - column)
Expand Down Expand Up @@ -115,13 +116,13 @@
Data = None
CodeDescription = Some { Href = Some(Uri(urlForCompilerCode error.ErrorNumber)) } }

let getSymbolInformations
let getWorkspaceSymbols
(uri: DocumentUri)
(glyphToSymbolKind: FSharpGlyph -> SymbolKind option)
(topLevel: NavigationTopLevelDeclaration)
(symbolFilter: SymbolInformation -> bool)
: SymbolInformation[] =
let inner (container: string option) (decl: NavigationItem) : SymbolInformation option =
(symbolFilter: WorkspaceSymbol -> bool)
: WorkspaceSymbol[] =

Check notice

Code scanning / Ionide.Analyzers.Cli

Detect if generic type should be in the postfix position. Note

Prefer postfix syntax for arrays.
let inner (container: string option) (decl: NavigationItem) : WorkspaceSymbol option =
// We should nearly always have a kind, if the client doesn't send weird capabilities,
// if we don't why not assume module...
let kind = defaultArg (glyphToSymbolKind decl.Glyph) SymbolKind.Module
Expand All @@ -130,20 +131,48 @@
{ Uri = uri
Range = fcsRangeToLsp decl.Range }

let sym: SymbolInformation =
let sym: WorkspaceSymbol =
{ Name = decl.LogicalName
Kind = kind
Location = location
ContainerName = container
Location = U2.First location
Kind = kind
Tags = None
Deprecated = None }
Data = None }

if symbolFilter sym then Some sym else None

[| yield! inner None topLevel.Declaration |> Option.toArray
yield! topLevel.Nested |> Array.choose (inner (Some topLevel.Declaration.LogicalName)) |]

let applyQuery (query: string) (info: SymbolInformation) =
let getDocumentSymbol
(glyphToSymbolKind: FSharpGlyph -> SymbolKind option)
(topLevelItem: NavigationTopLevelDeclaration)
: DocumentSymbol =
let makeChildDocumentSymbol (item: NavigationItem) : DocumentSymbol =
{ Name = item.LogicalName
Detail = None
Kind = defaultArg (glyphToSymbolKind item.Glyph) SymbolKind.Module
Deprecated = None
Range = fcsRangeToLsp item.Range
SelectionRange = fcsRangeToLsp item.Range
Children = None
Tags = None }

{ Name = topLevelItem.Declaration.LogicalName
// TODO: what Detail actually influences
Detail = None
Kind = defaultArg (glyphToSymbolKind topLevelItem.Declaration.Glyph) SymbolKind.Module
Deprecated = None
// TODO: it would be good if we could get just the 'interesting' part of the Declaration.Range here for the SelectionRange
Range = fcsRangeToLsp topLevelItem.Declaration.Range
SelectionRange = fcsRangeToLsp topLevelItem.Declaration.Range
Children =
match topLevelItem.Nested with
| [||] -> None
| children -> Some(Array.map makeChildDocumentSymbol children)
Tags = None }

let applyQuery (query: string) (info: WorkspaceSymbol) =
match query.Split([| '.' |], StringSplitOptions.RemoveEmptyEntries) with
| [||] -> false
| [| fullName |] -> info.Name.StartsWith(fullName, StringComparison.Ordinal)
Expand Down
13 changes: 9 additions & 4 deletions src/FsAutoComplete/LspHelpers.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,19 @@ module Conversions =
val urlForCompilerCode: number: int -> string
val fcsErrorToDiagnostic: error: FSharpDiagnostic -> Diagnostic

val getSymbolInformations:
val getWorkspaceSymbols:
uri: DocumentUri ->
glyphToSymbolKind: (FSharpGlyph -> SymbolKind option) ->
topLevel: NavigationTopLevelDeclaration ->
symbolFilter: (SymbolInformation -> bool) ->
SymbolInformation array
symbolFilter: (WorkspaceSymbol -> bool) ->
WorkspaceSymbol array

val applyQuery: query: string -> info: SymbolInformation -> bool
val getDocumentSymbol:
glyphToSymbolKind: (FSharpGlyph -> SymbolKind option) ->
topLevelItem: NavigationTopLevelDeclaration ->
DocumentSymbol

val applyQuery: query: string -> info: WorkspaceSymbol -> bool

val getCodeLensInformation:
uri: DocumentUri -> typ: string -> topLevel: NavigationTopLevelDeclaration -> CodeLens array
Expand Down
89 changes: 49 additions & 40 deletions src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ open System.Threading.Tasks
open FsAutoComplete.FCSPatches
open Helpers

#nowarn "44" // we create CompletionItems here that have two deprecated properties. Since records always have to set each property....

type AdaptiveFSharpLspServer
(workspaceLoader: IWorkspaceLoader, lspClient: FSharpLspClient, sourceTextFactory: ISourceTextFactory) =

Expand Down Expand Up @@ -305,15 +307,22 @@ type AdaptiveFSharpLspServer
| Some false -> None
| None -> None

let actualRootPath =
match p.RootUri with
| Some rootUri -> Some(Path.FileUriToLocalPath rootUri)
| None -> p.RootPath
let workspaceRoot =
match p.WorkspaceFolders with
| None
| Some [||] ->
failwith
"Unable to start LSP server - no workspacePaths are available and we do not support the deprecated RootPath and RootUri options."
| Some folders -> Some(Path.FileUriToLocalPath folders[0].Uri)

let projs =
match p.RootPath, c.AutomaticWorkspaceInit with
match workspaceRoot, c.AutomaticWorkspaceInit with
// if no workspace root available, or one is available but we don't want to automatically
// initialize the workspace, then we don't have any projects to initialize.
// in this case we need the client to call WorkspacePeek/WorkspaceLoad explicitly
| None, _
| _, false -> state.WorkspacePaths
| _, false -> WorkspaceChosen.NotChosen
// otherwise, try to infer the workspace from the automatic workspace information
| Some actualRootPath, true ->
let peeks =
WorkspacePeek.peek
Expand Down Expand Up @@ -342,7 +351,7 @@ type AdaptiveFSharpLspServer
|> WorkspaceChosen.Projs

transact (fun () ->
state.RootPath <- actualRootPath
state.RootPath <- workspaceRoot
state.ClientCapabilities <- p.Capabilities
lspClient.ClientCapabilities <- p.Capabilities

Expand Down Expand Up @@ -545,7 +554,7 @@ type AdaptiveFSharpLspServer
if lineStr.StartsWith("#", StringComparison.Ordinal) then
let completionList =
{ IsIncomplete = false
Items = KeywordList.hashSymbolCompletionItems
Items = KeywordList.hashSymbolCompletionItems p.Position
ItemDefaults = None }


Expand Down Expand Up @@ -624,7 +633,13 @@ type AdaptiveFSharpLspServer
| Some no when config.FullNameExternalAutocomplete -> sprintf "%s.%s" no d.NameInCode
| _ -> d.NameInCode

let createCompletionItem (config: FSharpConfig) (id: int) (d: DeclarationListItem) =
let textEdit text pos : U2<TextEdit, _> =
U2.First(
{ Range = { Start = pos; End = pos }
NewText = text }
)

let createCompletionItem (config: FSharpConfig) insertPos (id: int) (d: DeclarationListItem) =
let code = getCodeToInsert d

/// The `label` for completion "System.Math.Ceiling" will be displayed as "Ceiling (System.Math)". This is to bias the viewer towards the member name,
Expand All @@ -641,7 +656,7 @@ type AdaptiveFSharpLspServer
{ CompletionItem.Create(d.NameInList) with
Data = Some(JValue(d.FullName))
Kind = (state.GlyphToCompletionKind) d.Glyph
InsertText = Some code
TextEdit = Some(textEdit code insertPos)
SortText = Some(sprintf "%06d" id)
FilterText = Some filterText
Label = label }
Expand All @@ -668,13 +683,13 @@ type AdaptiveFSharpLspServer

let includeKeywords = config.KeywordsAutocomplete && shouldKeywords

let items = decls |> Array.mapi (createCompletionItem config)
let items = decls |> Array.mapi (createCompletionItem config p.Position)

let its =
if not includeKeywords then
items
else
Array.append items KeywordList.keywordCompletionItems
Array.append items (KeywordList.keywordCompletionItems p.Position)

let completionList =
{ IsIncomplete = false
Expand Down Expand Up @@ -899,45 +914,46 @@ type AdaptiveFSharpLspServer
else
TipFormatter.FormatCommentStyle.Legacy

let md text : MarkupContent =
{ Kind = MarkupKind.Markdown
Value = text }

match TipFormatter.tryFormatTipEnhanced tooltipResult.ToolTipText formatCommentStyle with
| TipFormatter.TipFormatterResult.Success tooltipInfo ->

// Display the signature as a code block

let fsharpCode s = "```fsharp\n" + s + "\n```"

let signature =
tooltipResult.Signature
|> TipFormatter.prepareSignature
|> (fun content -> MarkedString.WithLanguage { Language = "fsharp"; Value = content })
tooltipResult.Signature |> TipFormatter.prepareSignature |> fsharpCode

// Display each footer line as a separate line
let footerLines =
tooltipResult.Footer
|> TipFormatter.prepareFooterLines
|> Array.map MarkedString.String
let footerLines = tooltipResult.Footer |> TipFormatter.prepareFooterLines

let contents =
[| signature
MarkedString.String tooltipInfo.DocComment
tooltipInfo.DocComment
match tooltipResult.SymbolInfo with
| TryGetToolTipEnhancedResult.Keyword _ -> ()
| TryGetToolTipEnhancedResult.Symbol symbolInfo ->
TipFormatter.renderShowDocumentationLink
tooltipInfo.HasTruncatedExamples
symbolInfo.XmlDocSig
symbolInfo.Assembly
|> MarkedString.String
yield! footerLines |]

let response =
{ Contents = MarkedStrings contents
{ Contents = contents |> String.join Environment.NewLine |> md |> MarkupContent
Range = None }

return (Some response)

| TipFormatter.TipFormatterResult.Error error ->
let contents = [| MarkedString.String "<Note>"; MarkedString.String error |]
let contents = md $"<Note>\n {error}"

let response =
{ Contents = MarkedStrings contents
{ Contents = MarkupContent contents
Range = None }

return (Some response)
Expand Down Expand Up @@ -1267,11 +1283,10 @@ type AdaptiveFSharpLspServer
| Some decls ->
return
decls
|> Array.collect (fun top ->
getSymbolInformations p.TextDocument.Uri state.GlyphToSymbolKind top (fun _s -> true))
|> U2.First
|> Array.map (getDocumentSymbol state.GlyphToSymbolKind)
|> U2.Second
|> Some
| None -> return! LspResult.internalError $"No declarations for {fn}"
| None -> return None
with e ->
trace |> Tracing.recordException e

Expand All @@ -1284,7 +1299,6 @@ type AdaptiveFSharpLspServer
return! returnException e
}


override __.WorkspaceSymbol(symbolRequest: WorkspaceSymbolParams) =
asyncResult {
let tags = [ "WorkspaceSymbolParams", box symbolRequest ]
Expand All @@ -1306,12 +1320,9 @@ type AdaptiveFSharpLspServer
let uri = Path.LocalPathToUri p

ns
|> Array.collect (fun n ->
getSymbolInformations uri glyphToSymbolKind n (applyQuery symbolRequest.Query)))
|> U2.First
|> Some
|> Array.collect (fun n -> getWorkspaceSymbols uri glyphToSymbolKind n (applyQuery symbolRequest.Query)))

return res
return Some(U2.Second res)
with e ->
trace |> Tracing.recordException e

Expand Down Expand Up @@ -3062,24 +3073,24 @@ type AdaptiveFSharpLspServer

override x.Dispose() = disposables.Dispose()

member this.WorkDoneProgressCancel(token: ProgressToken) : Async<unit> =
member this.WorkDoneProgressCancel(progressParams: WorkDoneProgressCancelParams) : Async<unit> =
async {

let tags = [ "ProgressToken", box token ]
let tags = [ "ProgressToken", box progressParams.token ]
use trace = fsacActivitySource.StartActivityForType(thisType, tags = tags)

try
logger.info (
Log.setMessage "WorkDoneProgressCancel Request: {params}"
>> Log.addContextDestructured "params" token
>> Log.addContextDestructured "params" progressParams.token
)

with e ->
trace |> Tracing.recordException e

logger.error (
Log.setMessage "WorkDoneProgressCancel Request Errored {p}"
>> Log.addContextDestructured "token" token
>> Log.addContextDestructured "token" progressParams.token
>> Log.addExn e
)

Expand Down Expand Up @@ -3147,8 +3158,6 @@ module AdaptiveFSharpLspServer =

}



let startCore toolsPath workspaceLoaderFactory sourceTextFactory =
use input = Console.OpenStandardInput()
use output = Console.OpenStandardOutput()
Expand Down
Loading
Loading