From 7b5a32d0d72a24b895fbfc93c26328862ada9e3c Mon Sep 17 00:00:00 2001 From: Jake Leahy Date: Sun, 10 Dec 2023 16:42:38 +1100 Subject: [PATCH 1/5] Render type fields as fields --- src/nimlsppkg/suggestlib.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nimlsppkg/suggestlib.nim b/src/nimlsppkg/suggestlib.nim index 14fdcb6..9473bfe 100644 --- a/src/nimlsppkg/suggestlib.nim +++ b/src/nimlsppkg/suggestlib.nim @@ -43,6 +43,7 @@ func nimSymToLSPKind*(suggest: byte): SymbolKind = case TSymKind(suggest): of skConst: SymbolKind.Constant of skEnumField: SymbolKind.EnumMember + of skField: SymbolKind.Field of skIterator: SymbolKind.Function of skConverter: SymbolKind.Function of skLet: SymbolKind.Variable From 2c7e90018a8a26526a34293000e9dba7986b7de2 Mon Sep 17 00:00:00 2001 From: Jake Leahy Date: Sun, 10 Dec 2023 16:55:11 +1100 Subject: [PATCH 2/5] Add capabilities check I used a function instead of just setting flags since capabilities can have extra values assoicated with them --- src/nimlsppkg/capabilities.nim | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/nimlsppkg/capabilities.nim diff --git a/src/nimlsppkg/capabilities.nim b/src/nimlsppkg/capabilities.nim new file mode 100644 index 0000000..d92cd0e --- /dev/null +++ b/src/nimlsppkg/capabilities.nim @@ -0,0 +1,10 @@ +## Helper functions to check what the client supports + +import std/json + +using caps: ClientCapabilities + +func supportsHierarchicalSymbols*(caps): bool = + ## True if the client supports having heirarchal + ## symbols in the document outline + JsonNode(caps){"textDocument", "documentSymbol", "hierarchicalDocumentSymbolSupport"} == %true From 293f6f97b88e15c6273747bc4e551a48c80aad06 Mon Sep 17 00:00:00 2001 From: Jake Leahy Date: Sun, 10 Dec 2023 17:10:29 +1100 Subject: [PATCH 3/5] Add needed types Guess enums aren't supported which is why the other enum like fields use ints Should I write PR to add support to JSONSchema --- src/nimlsppkg/messageenums.nim | 3 +++ src/nimlsppkg/messages.nim | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/src/nimlsppkg/messageenums.nim b/src/nimlsppkg/messageenums.nim index 63b0e34..c2b66f2 100644 --- a/src/nimlsppkg/messageenums.nim +++ b/src/nimlsppkg/messageenums.nim @@ -46,6 +46,9 @@ type Operator = 25, TypeParameter = 26 + SymbolTag* = enum + Deprecated = 1 + CompletionItemKind* {.pure.} = enum Text = 1, Method = 2, diff --git a/src/nimlsppkg/messages.nim b/src/nimlsppkg/messages.nim index d449143..69080ad 100644 --- a/src/nimlsppkg/messages.nim +++ b/src/nimlsppkg/messages.nim @@ -175,6 +175,7 @@ jsonSchema: DocumentSymbolCapability: dynamicRegistration ?: bool symbolKind ?: SymbolKindCapability + hierarchicalDocumentSymbolSupport ?: bool FormattingCapability: dynamicRegistration ?: bool @@ -513,6 +514,15 @@ jsonSchema: location: Location containerName ?: string + DocumentSymbol: + name: string + detail ?: string + kind: int # SymbolKind + tags ?: int[] # SymbolTag[] + range: Range + selectionRange: Range + children ?: DocumentSymbol[] + CodeActionParams: textDocument: TextDocumentIdentifier "range": Range From 63db99b85e2d13737be507091d69e910e2d093c0 Mon Sep 17 00:00:00 2001 From: Jake Leahy Date: Sun, 10 Dec 2023 17:23:28 +1100 Subject: [PATCH 4/5] Implement returning `DocumentSymbol[]` when client supports it --- src/nimlsp.nim | 71 +++++++++++++++++++++++++++++--------- src/nimlsppkg/messages.nim | 2 +- 2 files changed, 55 insertions(+), 18 deletions(-) diff --git a/src/nimlsp.nim b/src/nimlsp.nim index 0442cd4..ce2f6bd 100644 --- a/src/nimlsp.nim +++ b/src/nimlsp.nim @@ -2,7 +2,7 @@ import std/[algorithm, asyncdispatch, asyncfile, hashes, os, osproc, sets, streams, strformat, strutils, tables, uri] import asynctools/asyncproc import nimlsppkg/[baseprotocol, logger, suggestlib, utfmapping] -include nimlsppkg/[messages, messageenums] +include nimlsppkg/[messages, messageenums, capabilities] const @@ -125,6 +125,28 @@ proc parseId(node: JsonNode): int = else: raise newException(MalformedFrame, "Invalid id node: " & repr(node)) +func newTokenRange(x: Suggest): Range = + ## Creates a [Range] that spans the length of the token + result = Range.create( + Position.create(x.line - 1, x.column), + Position.create(x.line - 1, x.column + x.tokenLen) + ) + +func newDocumentSymbol(sug: Suggest, children: seq[Suggest]): DocumentSymbol = + ## Creates a [DocumentSymbol] from a suggestion + let childSymbols = children.mapIt(newDocumentSymbol(it, @[])) + result = DocumentSymbol.create( + sug.name[], + none(string), + nimSymToLSPKind(sug.symKind).int, + none(seq[int]), + newTokenRange(sug), + # TODO: Make selectionRange use endCol/endLine. + # This requires support for v3 + newTokenRange(sug), + if children.len > 0: some(childSymbols) else: none(seq[DocumentSymbol]) + ) + proc respond(outs: Stream | AsyncFile, request: RequestMessage, data: JsonNode) {.multisync.} = let resp = create(ResponseMessage, "2.0", parseId(request["id"]), some(data), none(ResponseError)).JsonNode await outs.sendJson resp @@ -222,6 +244,7 @@ proc main(ins: Stream | AsyncFile, outs: Stream | AsyncFile) {.multisync.} = await checkVersion(outs) else: checkVersion(outs) + var capabilities: ClientCapabilities while true: try: debugLog "Trying to read frame" @@ -242,6 +265,7 @@ proc main(ins: Stream | AsyncFile, outs: Stream | AsyncFile) {.multisync.} = of "initialize": debugLog "Got initialize request, answering" initialized = true + capabilities = ClientCapabilities(message["params"].get()["capabilities"]) let resp = create(InitializeResult, create(ServerCapabilities, textDocumentSync = some(create(TextDocumentSyncOptions, openClose = some(true), @@ -461,24 +485,37 @@ proc main(ins: Stream | AsyncFile, outs: Stream | AsyncFile) {.multisync.} = if syms.len == 0: resp = newJNull() else: + # Store suggestion along with children + # that should appear under it + var symbols: OrderedTable[string, (Suggest, seq[Suggest])] resp = newJarray() for sym in syms.sortedByIt((it.line,it.column,it.quality)): - if sym.qualifiedPath.len != 2: - continue - resp.add create( - SymbolInformation, - sym.name[], - nimSymToLSPKind(sym.symKind).int, - some(false), - create(Location, - "file://" & pathToUri(sym.filepath), - create(Range, - create(Position, sym.line-1, sym.column), - create(Position, sym.line-1, sym.column + sym.qualifiedPath[^1].len) - ) - ), - none(string) - ).JsonNode + let key = sym.qualifiedPath[0..<2].join("") + if sym.qualifiedPath.len == 2: + # Parent add it + symbols[key] = (sym, @[]) + else: + # Append child to parent. + # LHS of types are semmed first so shouldn't + # run into key access problems + symbols[key][1] &= sym + + let useDocumentSymbol = capabilities.supportsHierarchicalSymbols() + for (sym, children) in symbols.values(): + let kind = nimSymToLSPKind(sym.symKind).int + if useDocumentSymbol: + resp &= newDocumentSymbol(sym, children).JsonNode + else: + resp &= SymbolInformation.create( + sym.name[], + kind, + some(false), + create(Location, + "file://" & pathToUri(sym.filepath), + newTokenRange(sym) + ), + none(string) + ).JsonNode await outs.respond(message, resp) of "textDocument/signatureHelp": message.textDocumentRequest(TextDocumentPositionParams, sigHelpRequest): diff --git a/src/nimlsppkg/messages.nim b/src/nimlsppkg/messages.nim index 69080ad..354c49d 100644 --- a/src/nimlsppkg/messages.nim +++ b/src/nimlsppkg/messages.nim @@ -519,7 +519,7 @@ jsonSchema: detail ?: string kind: int # SymbolKind tags ?: int[] # SymbolTag[] - range: Range + "range": Range selectionRange: Range children ?: DocumentSymbol[] From 78bbc1bb4c1d4d32d8e4483584bb7246f4a3a088 Mon Sep 17 00:00:00 2001 From: Jake Leahy Date: Sun, 10 Dec 2023 17:31:10 +1100 Subject: [PATCH 5/5] Only show symbols for the current files --- src/nimlsp.nim | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/nimlsp.nim b/src/nimlsp.nim index ce2f6bd..21799c3 100644 --- a/src/nimlsp.nim +++ b/src/nimlsp.nim @@ -473,10 +473,11 @@ proc main(ins: Stream | AsyncFile, outs: Stream | AsyncFile) {.multisync.} = await outs.respond(message, resp) of "textDocument/documentSymbol": message.textDocumentRequest(DocumentSymbolParams, symbolRequest): - debugLog "Running equivalent of: outline ", uriToPath(fileuri), + let filePath = uriToPath(fileuri) + debugLog "Running equivalent of: outline ", filePath, ";", filestash let syms = getNimsuggest(fileuri).outline( - uriToPath(fileuri), + filePath, dirtyfile = filestash ) debugLog "Found outlines: ", syms[0..