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

Several tag related improvements #262

Merged
merged 4 commits into from
Nov 20, 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: 0 additions & 2 deletions Marksman/Compl.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TheBlob42 did this line cause problems?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly. The FilterText property is the string that the user input is matched against when filtering the completion candidates (see here). If you set this value for example to Some "foo" and trigger the completion then typing "foo" in your editor should still show all completion candidates (this is an extreme example I know).

The problem here was that the filter text was set to the user input at the time of the completion candidates creation. Therefore every "additional" character would not match this text anymore and the completion candidates would be gone.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the fix here! I think there's a same-ish problem with other types of completions... I haven't fully figured out the deal with client side filtering of completion candidates.

FilterText = Some input
TextEdit = Some { Range = range; NewText = label } }

module Candidates =
Expand Down
3 changes: 0 additions & 3 deletions Marksman/Cst.fs
Original file line number Diff line number Diff line change
Expand Up @@ -333,9 +333,6 @@ module Element =
| MLD def -> Some def
| _ -> None

let pickHeadings (elements: array<Element>) : array<Node<Heading>> =
elements |> Array.map asHeading |> Array.collect Option.toArray

let isDecl =
function
| WL _
Expand Down
81 changes: 61 additions & 20 deletions Marksman/Symbols.fs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,26 @@ open Marksman.Cst
open Marksman.Workspace
open Marksman.Index

let headingToSymbolInfo (docUri: DocId) (h: Node<Heading>) : SymbolInformation =
let name = Heading.name h.data
let name = $"H{h.data.level}: {name}"
let headingToSymbolName (h: Node<Heading>) : string = $"H{h.data.level}: {Heading.name h.data}"

let tagToSymbolName (t: Node<Tag>) : string = $"Tag: {t.data.name.text}"

let tagToSymbolInfo (docUri: DocId) (t: Node<Tag>) : 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<Heading>) : SymbolInformation =
let name = headingToSymbolName h
let kind = SymbolKind.String
let location = { Uri = docUri.uri; Range = h.range }

let sym =
Expand All @@ -24,16 +39,34 @@ let headingToSymbolInfo (docUri: DocId) (h: Node<Heading>) : SymbolInformation =

sym

let rec tagToDocumentSymbol (t: Node<Tag>) : 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<Heading>) : 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<DocumentSymbol> =
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
Expand Down Expand Up @@ -74,26 +107,34 @@ 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<SymbolInformation> =
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
75 changes: 73 additions & 2 deletions Tests/SymbolsTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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 ]

[<Fact>]
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
)

[<Fact>]
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" |]
)
Expand All @@ -26,7 +88,16 @@ module DocSymbols =
| _ -> failwith "Unexpected symbol type"
|> Array.map (fun x -> x.Name)

Assert.Equal<string>([| "H1: E"; "H2: D"; "H3: B"; "H2: C"; "H1: A" |], symNames)
Assert.Equal<string>(
[| "H1: E"
"H2: D"
"H3: B"
"H2: C"
"H1: A"
"Tag: t1"
"Tag: t2" |],
symNames
)

[<Fact>]
let order_Hierarchy () =
Expand All @@ -45,4 +116,4 @@ module DocSymbols =

syms |> Array.iter collect

Assert.Equal<string>([| "E"; "D"; "B"; "C"; "A" |], names)
Assert.Equal<string>([| "E"; "D"; "t1"; "B"; "t2"; "C"; "A" |], names)
18 changes: 9 additions & 9 deletions Tests/_snapshots/Tags.json
Original file line number Diff line number Diff line change
@@ -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 / <no-filter>",
"(1,16)-(1,16): anotherTag / <no-filter>",
"(1,16)-(1,16): ta / <no-filter>",
"(1,16)-(1,16): somethingElse / <no-filter>",
"(1,16)-(1,16): otherDocTag / <no-filter>"
]
},
"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 / <no-filter>",
"(2,13)-(2,15): anotherTag / <no-filter>",
"(2,13)-(2,15): otherDocTag / <no-filter>"
]
}
}
}