From 916ac9831cd9415cafb4734b2fc7394a2e1dd88a Mon Sep 17 00:00:00 2001 From: TheBlob42 Date: Sat, 18 Nov 2023 23:37:16 +0100 Subject: [PATCH 1/4] refactor: Allow tags completion to be filtered Allow filtering of tags on the editor side to allow users to match their tag completion via text input. --- Marksman/Compl.fs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Marksman/Compl.fs b/Marksman/Compl.fs index 67ea24d..5c0144e 100644 --- a/Marksman/Compl.fs +++ b/Marksman/Compl.fs @@ -639,8 +639,6 @@ module Completions = Some { CompletionItem.Create(label) with Detail = Some detail - // Use input as filter text to avoid any extra filtering on the editor's side - FilterText = Some input TextEdit = Some { Range = range; NewText = label } } module Candidates = From bfa6b4575b04688d00961ac95a924a9cf6abe5b4 Mon Sep 17 00:00:00 2001 From: TheBlob42 Date: Sat, 18 Nov 2023 23:43:03 +0100 Subject: [PATCH 2/4] feat: List tags as workspace Symbols Also include the symbol "prefix" (e.g. H1, H2, Tag) into the symbol name to allow better client side filtering. --- Marksman/Symbols.fs | 44 +++++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/Marksman/Symbols.fs b/Marksman/Symbols.fs index d9c8d71..97ddd0d 100644 --- a/Marksman/Symbols.fs +++ b/Marksman/Symbols.fs @@ -9,11 +9,26 @@ open Marksman.Cst open Marksman.Workspace open Marksman.Index -let headingToSymbolInfo (docUri: DocId) (h: Node) : SymbolInformation = - let name = Heading.name h.data - let name = $"H{h.data.level}: {name}" +let headingToSymbolName (h: Node) : string = $"H{h.data.level}: {Heading.name h.data}" + +let tagToSymbolName (t: Node) : string = $"Tag: {t.data.name.text}" + +let tagToSymbolInfo (docUri: DocId) (t: Node) : SymbolInformation = + let name = tagToSymbolName t let kind = SymbolKind.String + let location = { Uri = docUri.uri; Range = t.range } + let sym = + { Name = name + Kind = kind + Location = location + ContainerName = None } + + sym + +let headingToSymbolInfo (docUri: DocId) (h: Node) : SymbolInformation = + let name = headingToSymbolName h + let kind = SymbolKind.String let location = { Uri = docUri.uri; Range = h.range } let sym = @@ -85,15 +100,18 @@ let workspaceSymbols (query: string) (ws: Workspace) : array seq { for folder in Workspace.folders ws do for doc in Folder.docs folder do - let headings = Doc.index doc |> Index.headings - - let matchingHeadings = - headings - |> Seq.filter (fun { data = h } -> query.IsSubSequenceOf(Heading.name h)) - - let matchingSymbols = - matchingHeadings |> Seq.map (headingToSymbolInfo (Doc.id doc)) - - yield! matchingSymbols + let matchingHeadingSymbols = + Doc.index doc + |> Index.headings + |> Seq.filter (headingToSymbolName >> query.IsSubSequenceOf) + |> Seq.map (headingToSymbolInfo (Doc.id doc)) + + let matchingTagSymbols = + Doc.index doc + |> Index.tags + |> Seq.filter (tagToSymbolName >> query.IsSubSequenceOf) + |> Seq.map (tagToSymbolInfo (Doc.id doc)) + + yield! Seq.append matchingHeadingSymbols matchingTagSymbols } |> Array.ofSeq From 86c6adaa4243f6ee09e1b1fc345ff07a8e1de2ec Mon Sep 17 00:00:00 2001 From: TheBlob42 Date: Sat, 18 Nov 2023 23:55:32 +0100 Subject: [PATCH 3/4] feat: List tags as document symbols --- Marksman/Cst.fs | 3 --- Marksman/Symbols.fs | 37 ++++++++++++++++++++++++++++++------- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/Marksman/Cst.fs b/Marksman/Cst.fs index 60efab0..de16d03 100644 --- a/Marksman/Cst.fs +++ b/Marksman/Cst.fs @@ -333,9 +333,6 @@ module Element = | MLD def -> Some def | _ -> None - let pickHeadings (elements: array) : array> = - elements |> Array.map asHeading |> Array.collect Option.toArray - let isDecl = function | WL _ diff --git a/Marksman/Symbols.fs b/Marksman/Symbols.fs index 97ddd0d..b794390 100644 --- a/Marksman/Symbols.fs +++ b/Marksman/Symbols.fs @@ -39,16 +39,34 @@ let headingToSymbolInfo (docUri: DocId) (h: Node) : SymbolInformation = sym +let rec tagToDocumentSymbol (t: Node) : DocumentSymbol = + let name = t.data.name.text + let kind = SymbolKind.String + let range = t.range + + { Name = name + Detail = None + Kind = kind + Range = range + SelectionRange = range + Children = None } + let rec headingToDocumentSymbol (isEmacs: bool) (h: Node) : DocumentSymbol = let name = Heading.name h.data let kind = SymbolKind.String let range = h.data.scope let selectionRange = h.range + let toDocumentSymbol (e: Element) : option = + match e with + | H h -> Some(headingToDocumentSymbol isEmacs h) + | T t -> Some(tagToDocumentSymbol t) + | _ -> None + let children = h.data.children - |> Element.pickHeadings - |> Array.map (headingToDocumentSymbol isEmacs) + |> Array.map toDocumentSymbol + |> Array.collect Option.toArray let children = if Array.isEmpty children then @@ -89,12 +107,17 @@ let docSymbols |> Array.ofSeq |> Second else - let allHeadings = Doc.index >> Index.headings <| doc + let allHeadings = + Doc.index >> Index.headings <| doc + |> Seq.map (headingToSymbolInfo (Doc.id doc)) + |> Array.ofSeq - allHeadings - |> Seq.map (headingToSymbolInfo (Doc.id doc)) - |> Array.ofSeq - |> First + let allTags = + Doc.index >> Index.tags <| doc + |> Seq.map (tagToSymbolInfo (Doc.id doc)) + |> Array.ofSeq + + Array.append allHeadings allTags |> First let workspaceSymbols (query: string) (ws: Workspace) : array = seq { From 34879f214e308cd590975920966095f597d91b31 Mon Sep 17 00:00:00 2001 From: TheBlob42 Date: Sat, 18 Nov 2023 23:48:32 +0100 Subject: [PATCH 4/4] test: tags test adaptions - fix tags completion tests - add tags to document symbol tests - implement workspace symbol tests (including tags) --- Tests/SymbolsTests.fs | 75 +++++++++++++++++++++++++++++++++++++- Tests/_snapshots/Tags.json | 18 ++++----- 2 files changed, 82 insertions(+), 11 deletions(-) diff --git a/Tests/SymbolsTests.fs b/Tests/SymbolsTests.fs index 02dfe19..f35b4fd 100644 --- a/Tests/SymbolsTests.fs +++ b/Tests/SymbolsTests.fs @@ -4,14 +4,76 @@ open Xunit open Ionide.LanguageServerProtocol.Types +open Marksman.Misc open Marksman.Helpers +open Marksman.Workspace + +module WorkspaceSymbol = + let doc1 = + FakeDoc.Mk(path = "doc1.md", contentLines = [| "# A"; "#tag1" |]) + + let doc2 = + FakeDoc.Mk(path = "doc2.md", contentLines = [| "# B"; "#tag1 #tag2" |]) + + let folder = FakeFolder.Mk [ doc1; doc2 ] + let workspace = Workspace.ofFolders None [ folder ] + + [] + let symbols_noQuery = + let symbols = Symbols.workspaceSymbols "" workspace + + Assert.Equal( + [ { Name = "H1: A" + Kind = SymbolKind.String + ContainerName = None + Location = { Uri = (Doc.uri doc1); Range = Range.Mk(0, 0, 0, 3) } } + { Name = "Tag: tag1" + Kind = SymbolKind.String + ContainerName = None + Location = { Uri = (Doc.uri doc1); Range = Range.Mk(1, 0, 1, 5) } } + { Name = "H1: B" + Kind = SymbolKind.String + ContainerName = None + Location = { Uri = (Doc.uri doc2); Range = Range.Mk(0, 0, 0, 3) } } + { Name = "Tag: tag1" + Kind = SymbolKind.String + ContainerName = None + Location = { Uri = (Doc.uri doc2); Range = Range.Mk(1, 0, 1, 5) } } + { Name = "Tag: tag2" + Kind = SymbolKind.String + ContainerName = None + Location = { Uri = (Doc.uri doc2); Range = Range.Mk(1, 6, 1, 11) } } ], + symbols + ) + + [] + let symbols_withQuery () = + let symbols = Symbols.workspaceSymbols "Tag:" workspace + + Assert.Equal( + [ { Name = "Tag: tag1" + Kind = SymbolKind.String + ContainerName = None + Location = { Uri = (Doc.uri doc1); Range = Range.Mk(1, 0, 1, 5) } } + { Name = "Tag: tag1" + Kind = SymbolKind.String + ContainerName = None + Location = { Uri = (Doc.uri doc2); Range = Range.Mk(1, 0, 1, 5) } } + { Name = "Tag: tag2" + Kind = SymbolKind.String + ContainerName = None + Location = { Uri = (Doc.uri doc2); Range = Range.Mk(1, 6, 1, 11) } } ], + symbols + ) module DocSymbols = let fakeDoc = FakeDoc.Mk( [| "# E" // "## D" + "#t1" "### B" + "#t2" "## C" "# A" |] ) @@ -26,7 +88,16 @@ module DocSymbols = | _ -> failwith "Unexpected symbol type" |> Array.map (fun x -> x.Name) - Assert.Equal([| "H1: E"; "H2: D"; "H3: B"; "H2: C"; "H1: A" |], symNames) + Assert.Equal( + [| "H1: E" + "H2: D" + "H3: B" + "H2: C" + "H1: A" + "Tag: t1" + "Tag: t2" |], + symNames + ) [] let order_Hierarchy () = @@ -45,4 +116,4 @@ module DocSymbols = syms |> Array.iter collect - Assert.Equal([| "E"; "D"; "B"; "C"; "A" |], names) + Assert.Equal([| "E"; "D"; "t1"; "B"; "t2"; "C"; "A" |], names) diff --git a/Tests/_snapshots/Tags.json b/Tests/_snapshots/Tags.json index 7f8928a..7d78408 100644 --- a/Tests/_snapshots/Tags.json +++ b/Tests/_snapshots/Tags.json @@ -1,18 +1,18 @@ { "tagOpening": { "AutoGenerated": [ - "(1,16)-(1,16): tag / ", - "(1,16)-(1,16): anotherTag / ", - "(1,16)-(1,16): ta / ", - "(1,16)-(1,16): somethingElse / ", - "(1,16)-(1,16): otherDocTag / " + "(1,16)-(1,16): tag / ", + "(1,16)-(1,16): anotherTag / ", + "(1,16)-(1,16): ta / ", + "(1,16)-(1,16): somethingElse / ", + "(1,16)-(1,16): otherDocTag / " ] }, "tagWithName": { "AutoGenerated": [ - "(2,13)-(2,15): tag / ta", - "(2,13)-(2,15): anotherTag / ta", - "(2,13)-(2,15): otherDocTag / ta" + "(2,13)-(2,15): tag / ", + "(2,13)-(2,15): anotherTag / ", + "(2,13)-(2,15): otherDocTag / " ] } -} \ No newline at end of file +}