From c20c164d38f7d0f2195c6f772346296578b1e55d Mon Sep 17 00:00:00 2001 From: Artem Pyanykh Date: Wed, 29 Nov 2023 10:48:54 +0000 Subject: [PATCH 1/3] feat: Add code lenses with '# of references' (server side) This just provides a lens with something like '4 references' next to a header. Clicking on the lens won't actually do anything yet because command handling needs to be implemented by every client. --- Marksman/Doc.fs | 2 ++ Marksman/Doc.fsi | 1 + Marksman/Lenses.fs | 27 +++++++++++++++++++++++++++ Marksman/Marksman.fsproj | 1 + Marksman/Server.fs | 28 +++++++++++++++++++++++++++- Tests/LensesTests.fs | 29 +++++++++++++++++++++++++++++ Tests/Tests.fsproj | 1 + 7 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 Marksman/Lenses.fs create mode 100644 Tests/LensesTests.fs diff --git a/Marksman/Doc.fs b/Marksman/Doc.fs index 7c55f35..9ece101 100644 --- a/Marksman/Doc.fs +++ b/Marksman/Doc.fs @@ -31,6 +31,8 @@ type Doc = member this.Structure = this.structure + member this.Index = this.index + interface IEquatable with member this.Equals(other) = this.id = other.id && this.text = other.text diff --git a/Marksman/Doc.fsi b/Marksman/Doc.fsi index 2013632..178825b 100644 --- a/Marksman/Doc.fsi +++ b/Marksman/Doc.fsi @@ -17,6 +17,7 @@ type Doc = member RootPath: RootPath member RelPath: RelPath member Structure: Structure + member Index: Index interface System.IComparable interface System.IComparable diff --git a/Marksman/Lenses.fs b/Marksman/Lenses.fs new file mode 100644 index 0000000..60698da --- /dev/null +++ b/Marksman/Lenses.fs @@ -0,0 +1,27 @@ +module Marksman.Lenses + +open Ionide.LanguageServerProtocol.Types + +open Marksman.Doc +open Marksman.Folder +open Marksman.Refs + +let findReferencesLens = "marksman.findReferences" + +let private humanRefCount cnt = if cnt = 1 then "1 reference" else $"{cnt} references" + +let forDoc (folder: Folder) (doc: Doc) = + seq { + for h in doc.Index.headings do + let refs = Dest.findElementRefs false folder doc (Cst.H h) + let refCount = Seq.length refs + + if refCount > 0 then + let command = + { Title = humanRefCount refCount + Command = findReferencesLens + Arguments = None } + + yield { Range = h.range; Command = Some command; Data = None } + } + |> Array.ofSeq diff --git a/Marksman/Marksman.fsproj b/Marksman/Marksman.fsproj index 182115f..8fcc95b 100644 --- a/Marksman/Marksman.fsproj +++ b/Marksman/Marksman.fsproj @@ -60,6 +60,7 @@ + diff --git a/Marksman/Server.fs b/Marksman/Server.fs index 64c2a57..960adb1 100644 --- a/Marksman/Server.fs +++ b/Marksman/Server.fs @@ -23,6 +23,7 @@ open Marksman.Refs open Marksman.Folder open Marksman.Workspace open Marksman.State +open Newtonsoft.Json.Linq module ServerUtil = let logger = LogProvider.getLoggerByName "ServerUtil" @@ -187,7 +188,9 @@ module ServerUtil = { Legend = { TokenTypes = Semato.TokenType.mapping; TokenModifiers = [||] } Range = Some true Full = { Delta = Some false } |> U2.Second |> Some } - RenameProvider = renameOptions } + RenameProvider = renameOptions + CodeLensProvider = Some { ResolveProvider = None } + ExecuteCommandProvider = Some { commands = None } } type MarksmanStatusParams = { state: string; docCount: int } @@ -1005,6 +1008,29 @@ type MarksmanServer(client: MarksmanClient) = Mutation.output (renameRange |> Option.map PrepareRenameResult.Range |> Ok) + + override this.TextDocumentCodeLens(pars) = + withState + <| fun state -> + let docPath = pars.TextDocument.Uri |> UriWith.mkAbs + + let lenses = + monad' { + let! folder, srcDoc = State.tryFindFolderAndDoc docPath state + Lenses.forDoc folder srcDoc + } + + LspResult.success lenses + + override this.WorkspaceExecuteCommand(pars) = + if pars.Command = Lenses.findReferencesLens then + // Code lenses need an associated command. We provide a dummy implementation here + // because 'showing references' need to be handled by the client and it's a bit too much + // hassle to do this for every client. + AsyncLspResult.success (JToken.FromObject(0)) + else + AsyncLspResult.invalidParams $"Command {pars.Command} is unsupported" + override this.Dispose() = (statusManager :> IDisposable).Dispose() (diagnosticsManager :> IDisposable).Dispose() diff --git a/Tests/LensesTests.fs b/Tests/LensesTests.fs new file mode 100644 index 0000000..c46eb59 --- /dev/null +++ b/Tests/LensesTests.fs @@ -0,0 +1,29 @@ +module Marksman.LensesTests + +open Xunit + +open Marksman.Helpers + +let d1 = + FakeDoc.Mk(path = "d1.md", contentLines = [| "# Doc 1"; "## Sub"; "[[#Sub]]"; "## No ref" |]) + +let d2 = + FakeDoc.Mk(path = "d2.md", contentLines = [| "# Doc 2"; "[[Doc 1#Sub]]" |]) + +let f = FakeFolder.Mk([ d1; d2 ]) + +[] +let basicLenses () = + let lenses = + Lenses.forDoc f d1 + |> Array.map (fun lens -> $"{lens.Command.Value}, {lens.Range}") + + checkInlineSnapshot + id + lenses + [ "{ Title = \"1 reference\"" + " Command = \"marksman.findReferences\"" + " Arguments = None }, (0,0)-(0,7)" + "{ Title = \"2 references\"" + " Command = \"marksman.findReferences\"" + " Arguments = None }, (1,0)-(1,6)" ] diff --git a/Tests/Tests.fsproj b/Tests/Tests.fsproj index e5654c1..e86c5de 100644 --- a/Tests/Tests.fsproj +++ b/Tests/Tests.fsproj @@ -29,6 +29,7 @@ + From 7a58ed27ffc897e9edf4180fd1bce874e2d2df8f Mon Sep 17 00:00:00 2001 From: Artem Pyanykh Date: Wed, 29 Nov 2023 11:07:23 +0000 Subject: [PATCH 2/3] feat: Add code lenses with references for markdown link defs --- Marksman/Lenses.fs | 18 ++++++++++++++++-- Tests/LensesTests.fs | 38 +++++++++++++++++++++++++++++++------- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/Marksman/Lenses.fs b/Marksman/Lenses.fs index 60698da..72c29e2 100644 --- a/Marksman/Lenses.fs +++ b/Marksman/Lenses.fs @@ -12,9 +12,10 @@ let private humanRefCount cnt = if cnt = 1 then "1 reference" else $"{cnt} refer let forDoc (folder: Folder) (doc: Doc) = seq { + // Process headers for h in doc.Index.headings do - let refs = Dest.findElementRefs false folder doc (Cst.H h) - let refCount = Seq.length refs + let refCount = + Dest.findElementRefs false folder doc (Cst.H h) |> Seq.length if refCount > 0 then let command = @@ -23,5 +24,18 @@ let forDoc (folder: Folder) (doc: Doc) = Arguments = None } yield { Range = h.range; Command = Some command; Data = None } + + // Process link defs + for ld in doc.Index.linkDefs do + let refCount = + Dest.findElementRefs false folder doc (Cst.MLD ld) |> Seq.length + + if refCount > 0 then + let command = + { Title = humanRefCount refCount + Command = findReferencesLens + Arguments = None } + + yield { Range = ld.range; Command = Some command; Data = None } } |> Array.ofSeq diff --git a/Tests/LensesTests.fs b/Tests/LensesTests.fs index c46eb59..bcaccd0 100644 --- a/Tests/LensesTests.fs +++ b/Tests/LensesTests.fs @@ -4,16 +4,19 @@ open Xunit open Marksman.Helpers -let d1 = - FakeDoc.Mk(path = "d1.md", contentLines = [| "# Doc 1"; "## Sub"; "[[#Sub]]"; "## No ref" |]) +[] +let basicHeaderLenses () = + let d1 = + FakeDoc.Mk( + path = "d1.md", + contentLines = [| "# Doc 1"; "## Sub"; "[[#Sub]]"; "## No ref" |] + ) -let d2 = - FakeDoc.Mk(path = "d2.md", contentLines = [| "# Doc 2"; "[[Doc 1#Sub]]" |]) + let d2 = + FakeDoc.Mk(path = "d2.md", contentLines = [| "# Doc 2"; "[[Doc 1#Sub]]" |]) -let f = FakeFolder.Mk([ d1; d2 ]) + let f = FakeFolder.Mk([ d1; d2 ]) -[] -let basicLenses () = let lenses = Lenses.forDoc f d1 |> Array.map (fun lens -> $"{lens.Command.Value}, {lens.Range}") @@ -27,3 +30,24 @@ let basicLenses () = "{ Title = \"2 references\"" " Command = \"marksman.findReferences\"" " Arguments = None }, (1,0)-(1,6)" ] + +[] +let basicLinkDefLenses () = + let d1 = + FakeDoc.Mk( + path = "d1.md", + contentLines = [| "[foo]"; "[foo][]"; "[bar]"; ""; "[foo]: /url" |] + ) + + let f = FakeFolder.Mk([ d1 ]) + + let lenses = + Lenses.forDoc f d1 + |> Array.map (fun lens -> $"{lens.Command.Value}, {lens.Range}") + + checkInlineSnapshot + id + lenses + [ "{ Title = \"2 references\"" + " Command = \"marksman.findReferences\"" + " Arguments = None }, (4,0)-(4,11)" ] From afa89a4faa9702544561615798b4fb9aba795575 Mon Sep 17 00:00:00 2001 From: Artem Pyanykh Date: Wed, 29 Nov 2023 11:26:47 +0000 Subject: [PATCH 3/3] fix: helix: Use an empty array of commands for ExecuteCommandProvider cap as otherwise helix wont start --- Marksman/Server.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marksman/Server.fs b/Marksman/Server.fs index 960adb1..6026ea9 100644 --- a/Marksman/Server.fs +++ b/Marksman/Server.fs @@ -190,7 +190,7 @@ module ServerUtil = Full = { Delta = Some false } |> U2.Second |> Some } RenameProvider = renameOptions CodeLensProvider = Some { ResolveProvider = None } - ExecuteCommandProvider = Some { commands = None } } + ExecuteCommandProvider = Some { commands = Some [||] } } type MarksmanStatusParams = { state: string; docCount: int }