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

feat: Add code lenses with '# of references' (server side) #272

Merged
merged 3 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from all 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: 2 additions & 0 deletions Marksman/Doc.fs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ type Doc =

member this.Structure = this.structure

member this.Index = this.index

interface IEquatable<Doc> with
member this.Equals(other) = this.id = other.id && this.text = other.text

Expand Down
1 change: 1 addition & 0 deletions Marksman/Doc.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type Doc =
member RootPath: RootPath
member RelPath: RelPath
member Structure: Structure
member Index: Index

interface System.IComparable
interface System.IComparable<Doc>
Expand Down
41 changes: 41 additions & 0 deletions Marksman/Lenses.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
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 {
// Process headers
for h in doc.Index.headings do
let refCount =
Dest.findElementRefs false folder doc (Cst.H h) |> Seq.length

if refCount > 0 then
let command =
{ Title = humanRefCount refCount
Command = findReferencesLens
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
1 change: 1 addition & 0 deletions Marksman/Marksman.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
<Compile Include="Compl.fs"/>
<Compile Include="Refactor.fs"/>
<Compile Include="Symbols.fs"/>
<Compile Include="Lenses.fs"/>
<Compile Include="Server.fs"/>
<Compile Include="Program.fs"/>
</ItemGroup>
Expand Down
28 changes: 27 additions & 1 deletion Marksman/Server.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 = Some [||] } }

type MarksmanStatusParams = { state: string; docCount: int }

Expand Down Expand Up @@ -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()
Expand Down
53 changes: 53 additions & 0 deletions Tests/LensesTests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
module Marksman.LensesTests

open Xunit

open Marksman.Helpers

[<Fact>]
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 f = FakeFolder.Mk([ d1; d2 ])

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)" ]

[<Fact>]
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)" ]
1 change: 1 addition & 0 deletions Tests/Tests.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<Compile Include="AstTests.fs" />
<Compile Include="ConnTest.fs" />
<Compile Include="MMapTests.fs" />
<Compile Include="LensesTests.fs" />
<Compile Include="Program.fs" />
</ItemGroup>

Expand Down