From e96d3f88f2f1cc97c52a5e8b888ac528b9331676 Mon Sep 17 00:00:00 2001 From: bung87 Date: Sun, 10 Sep 2023 03:11:24 +0800 Subject: [PATCH 01/84] tool: LSP support --- compiler/ast/ast_parsed_types.nim | 1 + compiler/ast/parser.nim | 2 +- compiler/modules/modules.nim | 1 - compiler/tools/suggest.nim | 16 +- nimlsp/README.rst | 244 ++++++++++ nimlsp/config.nims | 4 + nimlsp/nimlsp.nim | 670 ++++++++++++++++++++++++++++ nimlsp/nimlsp.nim.cfg | 20 + nimlsp/nimlsp.nimble | 25 ++ nimlsp/nimlsp_debug.nim | 1 + nimlsp/nimlsp_debug.nim.cfg | 22 + nimlsp/nimlsppkg/baseprotocol.nim | 85 ++++ nimlsp/nimlsppkg/jsonschema.nim | 479 ++++++++++++++++++++ nimlsp/nimlsppkg/logger.nim | 38 ++ nimlsp/nimlsppkg/messageenums.nim | 114 +++++ nimlsp/nimlsppkg/messages.nim | 596 +++++++++++++++++++++++++ nimlsp/nimlsppkg/nimsuggest.nim | 293 ++++++++++++ nimlsp/nimlsppkg/suggestlib.nim | 114 +++++ nimlsp/nimlsppkg/suggestlib.nim.cfg | 20 + nimlsp/nimlsppkg/utfmapping.nim | 73 +++ nimlsp/tests/nim.cfg | 1 + nimlsp/tests/test_messages2.nim | 38 ++ nimsuggest/tests/tinclude.nim | 1 + 23 files changed, 2842 insertions(+), 16 deletions(-) create mode 100644 nimlsp/README.rst create mode 100644 nimlsp/config.nims create mode 100644 nimlsp/nimlsp.nim create mode 100644 nimlsp/nimlsp.nim.cfg create mode 100644 nimlsp/nimlsp.nimble create mode 100644 nimlsp/nimlsp_debug.nim create mode 100644 nimlsp/nimlsp_debug.nim.cfg create mode 100644 nimlsp/nimlsppkg/baseprotocol.nim create mode 100644 nimlsp/nimlsppkg/jsonschema.nim create mode 100644 nimlsp/nimlsppkg/logger.nim create mode 100644 nimlsp/nimlsppkg/messageenums.nim create mode 100644 nimlsp/nimlsppkg/messages.nim create mode 100644 nimlsp/nimlsppkg/nimsuggest.nim create mode 100644 nimlsp/nimlsppkg/suggestlib.nim create mode 100644 nimlsp/nimlsppkg/suggestlib.nim.cfg create mode 100644 nimlsp/nimlsppkg/utfmapping.nim create mode 100644 nimlsp/tests/nim.cfg create mode 100644 nimlsp/tests/test_messages2.nim diff --git a/compiler/ast/ast_parsed_types.nim b/compiler/ast/ast_parsed_types.nim index 391d918bf80..4a93c197bb9 100644 --- a/compiler/ast/ast_parsed_types.nim +++ b/compiler/ast/ast_parsed_types.nim @@ -173,6 +173,7 @@ type pnkExportExceptStmt pnkTypeSection pnkConstSection + pnkTypeSection pnkLetSection pnkVarSection pnkProcDef diff --git a/compiler/ast/parser.nim b/compiler/ast/parser.nim index 91cf06356af..ebbaa2dd63a 100644 --- a/compiler/ast/parser.nim +++ b/compiler/ast/parser.nim @@ -2321,7 +2321,7 @@ proc parseAll(p: var Parser): ParsedNode = if p.tok.indent != 0: p.invalidIndentation() -proc parseTopLevelStmt(p: var Parser): ParsedNode = +proc parseTopLevelStmt*(p: var Parser): ParsedNode = ## Implements an iterator which, when called repeatedly, returns the next ## top-level statement or emptyNode if end of stream. result = p.emptyNode diff --git a/compiler/modules/modules.nim b/compiler/modules/modules.nim index 3286cff44fb..17bd6c3676e 100644 --- a/compiler/modules/modules.nim +++ b/compiler/modules/modules.nim @@ -151,7 +151,6 @@ proc compileModule*(graph: ModuleGraph; fileIdx: FileIndex; flags: TSymFlags, fr initStrTables(graph, result) result.ast = nil processModuleAux("import(dirty)") - graph.markClientsDirty(fileIdx) proc importModule*(graph: ModuleGraph; s: PSym, fileIdx: FileIndex): PSym = diff --git a/compiler/tools/suggest.nim b/compiler/tools/suggest.nim index 11a8fef8a7b..9945872a729 100644 --- a/compiler/tools/suggest.nim +++ b/compiler/tools/suggest.nim @@ -449,8 +449,9 @@ proc inCheckpoint*(current, trackPos: TLineInfo): TCheckPointResult = proc isTracked*(current, trackPos: TLineInfo, tokenLen: int): bool = if current.fileIndex == trackPos.fileIndex and - current.line == trackPos.line: + current.line == trackPos.line: let col = trackPos.col + if col >= current.col and col <= current.col + tokenLen - 1: if col >= current.col and col <= current.col + tokenLen - 1: return true @@ -540,19 +541,6 @@ proc suggestSym*(g: ModuleGraph; info: TLineInfo; s: PSym; usageSym: var PSym; i suggestResult(conf, symToSuggest(g, s, isLocal=false, ideDef, info, 100, PrefixMatch.None, false, 0)) elif conf.ideCmd == ideHighlight and info.fileIndex == conf.m.trackPos.fileIndex: suggestResult(conf, symToSuggest(g, s, isLocal=false, ideHighlight, info, 100, PrefixMatch.None, false, 0)) - elif conf.ideCmd == ideOutline and isDecl: - # if a module is included then the info we have is inside the include and - # we need to walk up the owners until we find the outer most module, - # which will be the last skModule prior to an skPackage. - var - parentFileIndex = info.fileIndex # assume we're in the correct module - parentModule = s.owner - while parentModule != nil and parentModule.kind == skModule: - parentFileIndex = parentModule.info.fileIndex - parentModule = parentModule.owner - - if parentFileIndex == conf.m.trackPos.fileIndex: - suggestResult(conf, symToSuggest(g, s, isLocal=false, ideOutline, info, 100, PrefixMatch.None, false, 0)) proc safeSemExpr*(c: PContext, n: PNode): PNode = # use only for idetools support! diff --git a/nimlsp/README.rst b/nimlsp/README.rst new file mode 100644 index 00000000000..b9f2aa71800 --- /dev/null +++ b/nimlsp/README.rst @@ -0,0 +1,244 @@ +========== +Nim Language Server Protocol +========== + +This is a `Language Server Protocol +`_ implementation in +Nim, for Nim. +It is based on nimsuggest, which means that every editor that +supports LSP will now have the same quality of suggestions that has previously +only been available in supported editors. + +Installing ``nimlsp`` +======= +If you have installed Nim through ``choosenim`` (recommended) the easiest way to +install ``nimlsp`` is to use ``nimble`` with: + +.. code:: bash + + nimble install nimlsp + +This will compile and install it in the ``nimble`` binary directory, which if +you have set up ``nimble`` correctly it should be in your path. When compiling +and using ``nimlsp`` it needs to have Nim's sources available in order to work. +With Nim installed through ``choosenim`` these should already be on your system +and ``nimlsp`` should be able to find and use them automatically. However if you +have installed ``nimlsp`` in a different way you might run into issues where it +can't find certain files during compilation/running. To fix this you need to +grab a copy of Nim sources and then point ``nimlsp`` at them on compile-time by +using ``-d:explicitSourcePath=PATH``, where ``PATH`` is where you have your Nim +sources. You can also pass them at run-time (if for example you're working with +a custom copy of the stdlib by passing it as an argument to ``nimlsp``. How +exectly to do that will depend on the LSP client. + +Compile ``nimlsp`` +======= +If you want more control over the compilation feel free to clone the +repository. ``nimlsp`` depends on the ``nimsuggest`` sources which are in the main +Nim repository, so make sure you have a copy of that somewhere. Manually having a +copy of Nim this way means the default source path will not work so you need to +set it explicitly on compilation with ``-d:explicitSourcePath=PATH`` and point to +it at runtime (technically the runtime should only need the stdlib, so omitting +it will make ``nimlsp`` try to find it from your Nim install). + +To do the standard build run: + +.. code:: bash + + nimble build + +Or if you want debug output when ``nimlsp`` is running: + +.. code:: bash + + nimble debug + +Or if you want even more debug output from the LSP format: + +.. code:: bash + + nimble debug -d:debugLogging + +Supported Protocol features +======= + +====== ================================ +Status LSP Command +====== ================================ +☑ DONE textDocument/didChange +☑ DONE textDocument/didClose +☑ DONE textDocument/didOpen +☑ DONE textDocument/didSave +☐ TODO textDocument/codeAction +☑ DONE textDocument/completion +☑ DONE textDocument/definition +☐ TODO textDocument/documentHighlight +☑ DONE textDocument/documentSymbol +☐ TODO textDocument/executeCommand +☐ TODO textDocument/format +☑ DONE textDocument/hover +☑ DONE textDocument/rename +☑ DONE textDocument/references +☑ DONE textDocument/signatureHelp +☑ DONE textDocument/publishDiagnostics +☐ TODO workspace/symbol +====== ================================ + + +Setting up ``nimlsp`` +======= +Sublime Text +------- +Install the `LSP plugin `_. +Install the `NimLime plugin `_ for syntax highlighting. + +Apart from syntax highlighting, NimLime can perform many of the features that ``nimlsp`` provides. +It is recommended to disable those for optimal experience. +For this, navigate to ``Preferences > Package Settings > NimLime > Settings`` and set ``*.enabled`` settings to ``false``: + +.. code:: js + + { + "error_handler.enabled": false, + "check.on_save.enabled": false, + "check.current_file.enabled": false, + "check.external_file.enabled": false, + "check.clear_errors.enabled": false, + } + +To set up LSP, run ``Preferences: LSP settings`` from the command palette and add the following: + +.. code:: js + + { + "clients": { + "nimlsp": { + "command": ["nimlsp"], + "enabled": true, + + // ST4 only + "selector": "source.nim", + + // ST3 only + "languageId": "nim", + "scopes": ["source.nim"], + "syntaxes": ["Packages/NimLime/Syntaxes/Nim.tmLanguage"] + } + } + } + +*Note: Make sure ``/.nimble/bin`` is added to your ``PATH``.* + +Vim +------- +To use ``nimlsp`` in Vim install the ``prabirshrestha/vim-lsp`` plugin and +dependencies: + +.. code:: vim + + Plugin 'prabirshrestha/asyncomplete.vim' + Plugin 'prabirshrestha/async.vim' + Plugin 'prabirshrestha/vim-lsp' + Plugin 'prabirshrestha/asyncomplete-lsp.vim' + +Then set it up to use ``nimlsp`` for Nim files: + +.. code:: vim + + let s:nimlspexecutable = "nimlsp" + let g:lsp_log_verbose = 1 + let g:lsp_log_file = expand('/tmp/vim-lsp.log') + " for asyncomplete.vim log + let g:asyncomplete_log_file = expand('/tmp/asyncomplete.log') + + let g:asyncomplete_auto_popup = 0 + + if has('win32') + let s:nimlspexecutable = "nimlsp.cmd" + " Windows has no /tmp directory, but has $TEMP environment variable + let g:lsp_log_file = expand('$TEMP/vim-lsp.log') + let g:asyncomplete_log_file = expand('$TEMP/asyncomplete.log') + endif + if executable(s:nimlspexecutable) + au User lsp_setup call lsp#register_server({ + \ 'name': 'nimlsp', + \ 'cmd': {server_info->[s:nimlspexecutable]}, + \ 'whitelist': ['nim'], + \ }) + endif + + function! s:check_back_space() abort + let col = col('.') - 1 + return !col || getline('.')[col - 1] =~ '\s' + endfunction + + inoremap + \ pumvisible() ? "\" : + \ check_back_space() ? "\" : + \ asyncomplete#force_refresh() + inoremap pumvisible() ? "\" : "\" + +This configuration allows you to hit Tab to get auto-complete, and to call +various functions to rename and get definitions. Of course you are free to +configure this any way you'd like. + +Emacs +------- + +With lsp-mode and use-package: + +.. code:: emacs-lisp + + (use-package nim-mode + :ensure t + :hook + (nim-mode . lsp)) + +Intellij +------- +You will need to install the `LSP support plugin `_. +For syntax highlighting i would recommend the "official" `nim plugin `_ +(its not exactly official, but its developed by an intellij dev), the plugin will eventually use nimsuggest and have support for +all this things and probably more, but since its still very new most of the features are still not implemented, so the LSP is a +decent solution (and the only one really). + +To use it: + +1. Install the LSP and the nim plugin. + +2. Go into ``settings > Language & Frameworks > Language Server Protocol > Server Definitions``. + +3. Set the LSP mode to ``executable``, the extension to ``nim`` and in the Path, the path to your nimlsp executable. + +4. Hit apply and everything should be working now. + +Kate +------- +The LSP plugin has to be enabled in the Kate (version >= 19.12.0) plugins menu: + +1. In ``Settings > Configure Kate > Application > Plugins``, check box next to ``LSP Client`` to enable LSP functionality. + +2. Go to the now-available LSP Client menu (``Settings > Configure Kate > Application``) and enter the following in the User Server Settings tab: + +.. code:: json + + { + "servers": { + "nim": { + "command": [".nimble/bin/nimlsp"], + "url": "https://github.com/PMunch/nimlsp", + "highlightingModeRegex": "^Nim$" + } + } + } + +This assumes that nimlsp was installed through nimble. +*Note: Server initialization may fail without full path specified, from home directory, under the ``"command"`` entry, even if nimlsp is in system's ``PATH``.* + +Run Tests +========= +Not too many at the moment unfortunately, but they can be run with: + +.. code:: bash + + nimble test diff --git a/nimlsp/config.nims b/nimlsp/config.nims new file mode 100644 index 00000000000..2885677a493 --- /dev/null +++ b/nimlsp/config.nims @@ -0,0 +1,4 @@ +import std/os + +const explicitSourcePath {.strdefine.} = getCurrentCompilerExe().parentDir.parentDir +switch "path", explicitSourcePath diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim new file mode 100644 index 00000000000..2f3c9bc4837 --- /dev/null +++ b/nimlsp/nimlsp.nim @@ -0,0 +1,670 @@ +import std/[algorithm, hashes, os, osproc, sets, + streams, strformat, strutils, tables, uri] + +import nimlsppkg/[baseprotocol, logger, suggestlib, utfmapping] +include nimlsppkg/[messages, messageenums] + + +const + version = block: + var version = "0.0.0" + let nimbleFile = staticRead(currentSourcePath().parentDir / "nimlsp.nimble") + for line in nimbleFile.splitLines: + let keyval = line.split('=') + if keyval.len == 2: + if keyval[0].strip == "version": + version = keyval[1].strip(chars = Whitespace + {'"'}) + break + version + # This is used to explicitly set the default source path + explicitSourcePath {.strdefine.} = getCurrentCompilerExe().parentDir.parentDir + +type + UriParseError* = object of Defect + uri: string + +var nimpath = explicitSourcePath + +infoLog("Version: ", version) +infoLog("explicitSourcePath: ", explicitSourcePath) +for i in 1..paramCount(): + infoLog("Argument ", i, ": ", paramStr(i)) + +var + gotShutdown = false + initialized = false + projectFiles = initTable[string, tuple[nimsuggest: NimSuggest, openFiles: OrderedSet[string]]]() + openFiles = initTable[string, tuple[projectFile: string, fingerTable: seq[seq[tuple[u16pos, offset: int]]]]]() + +template fileuri(p: untyped): string = + p["textDocument"]["uri"].getStr + +template filePath(p: untyped): string = + p.fileuri[7..^1] + +template filestash(p: untyped): string = + storage / (hash(p.fileuri).toHex & ".nim" ) + +template rawLine(p: untyped): int = + p["position"]["line"].getInt + +template rawChar(p: untyped): int = + p["position"]["character"].getInt + +template col(openFiles: typeof openFiles; p: untyped): int = + openFiles[p.fileuri].fingerTable[p.rawLine].utf16to8(p.rawChar) + +template textDocumentRequest(message: typed; kind: typed; name, body: untyped): untyped = + if message.hasKey("params"): + let p = message["params"] + var name = kind(p) + if p.isValid(kind, allowExtra = false): + body + else: + debugLog("Unable to parse data as ", kind) + +template textDocumentNotification(message: typed; kind: typed; name, body: untyped): untyped = + if message.hasKey("params"): + var p = message["params"] + var name = kind(p) + if p.isValid(kind, allowExtra = false): + if "languageId" notin name["textDocument"] or name["textDocument"]["languageId"].getStr == "nim": + body + else: + debugLog("Unable to parse data as ", kind) + +proc pathToUri(path: string): string = + # This is a modified copy of encodeUrl in the uri module. This doesn't encode + # the / character, meaning a full file path can be passed in without breaking + # it. + result = newStringOfCap(path.len + path.len shr 2) # assume 12% non-alnum-chars + when defined(windows): + result.add '/' + for c in path: + case c + # https://tools.ietf.org/html/rfc3986#section-2.3 + of 'a'..'z', 'A'..'Z', '0'..'9', '-', '.', '_', '~', '/': result.add c + of '\\': + when defined(windows): + result.add '/' + else: + result.add '%' + result.add toHex(ord(c), 2) + else: + result.add '%' + result.add toHex(ord(c), 2) + +proc uriToPath(uri: string): string = + ## Convert an RFC 8089 file URI to a native, platform-specific, absolute path. + #let startIdx = when defined(windows): 8 else: 7 + #normalizedPath(uri[startIdx..^1]) + let parsed = uri.parseUri + if parsed.scheme != "file": + var e = newException(UriParseError, &"Invalid scheme: {parsed.scheme}, only \"file\" is supported") + e.uri = uri + raise e + if parsed.hostname != "": + var e = newException(UriParseError, &"Invalid hostname: {parsed.hostname}, only empty hostname is supported") + e.uri = uri + raise e + return normalizedPath( + when defined(windows): + parsed.path[1..^1] + else: + parsed.path).decodeUrl + +proc parseId(node: JsonNode): string = + if node == nil: return + if node.kind == JString: + node.getStr + elif node.kind == JInt: + $node.getInt + else: + "" + +proc respond(outs: Stream, request: JsonNode, data: JsonNode) = + let resp = create(ResponseMessage, "2.0", parseId(request["id"]), some(data), none(ResponseError)).JsonNode + outs.sendJson resp + +proc error(outs: Stream, request: JsonNode, errorCode: ErrorCode, message: string, data: JsonNode) = + let err = some(create(ResponseError, ord(errorCode), message, data)) + let resp = create(ResponseMessage, "2.0", parseId(request{"id"}), none(JsonNode), err).JsonNode + outs.sendJson resp + +proc notify(outs: Stream, notification: string, data: JsonNode) = + let resp = create(NotificationMessage, "2.0", notification, some(data)).JsonNode + outs.sendJson resp + +type Certainty = enum + None, + Folder, + Cfg, + Nimble + +proc getProjectFile(file: string): string = + result = file + let (dir, _, _) = result.splitFile() + var + path = dir + certainty = None + while not path.isRootDir: + let + (dir, fname, ext) = path.splitFile() + current = fname & ext + if fileExists(path / current.addFileExt(".nim")) and certainty <= Folder: + result = path / current.addFileExt(".nim") + certainty = Folder + if fileExists(path / current.addFileExt(".nim")) and + (fileExists(path / current.addFileExt(".nim.cfg")) or + fileExists(path / current.addFileExt(".nims"))) and certainty <= Cfg: + result = path / current.addFileExt(".nim") + certainty = Cfg + if certainty <= Nimble: + for nimble in walkFiles(path / "*.nimble"): + let info = execProcess("nimble dump " & nimble) + var sourceDir, name: string + for line in info.splitLines: + if line.startsWith("srcDir"): + sourceDir = path / line[(1 + line.find '"')..^2] + if line.startsWith("name"): + name = line[(1 + line.find '"')..^2] + let projectFile = sourceDir / (name & ".nim") + if sourceDir.len != 0 and name.len != 0 and + file.isRelativeTo(sourceDir) and fileExists(projectFile): + result = projectFile + certainty = Nimble + path = dir + +template getNimsuggest(fileuri: string): Nimsuggest = + projectFiles[openFiles[fileuri].projectFile].nimsuggest + +if paramCount() == 1: + case paramStr(1): + of "--help": + echo "Usage: nimlsp [OPTION | PATH]\n" + echo "--help, shows this message" + echo "--version, shows only the version" + echo "PATH, path to the Nim source directory, defaults to \"", nimpath, "\"" + quit 0 + of "--version": + echo "nimlsp v", version + when defined(debugLogging): echo "Compiled with debug logging" + when defined(debugCommunication): echo "Compiled with communication logging" + quit 0 + else: nimpath = expandFilename(paramStr(1)) +if not fileExists(nimpath / "config/nim.cfg"): + stderr.write &"""Unable to find "config/nim.cfg" in "{nimpath + }". Supply the Nim project folder by adding it as an argument. +""" + quit 1 + +proc checkVersion(outs: Stream) = + let + nimoutputTuple = + execCmdEx("nim --version", options = {osproc.poEvalCommand, osproc.poUsePath}) + if nimoutputTuple.exitcode == 0: + let + nimoutput = nimoutputTuple.output + versionStart = "Nim Compiler Version ".len + version = nimoutput[versionStart.. 10: &" and {suggestions.len-10} more" else: "" + var + completionItems = newJarray() + seenLabels: CountTable[string] + addedSuggestions: HashSet[string] + for suggestion in suggestions: + seenLabels.inc suggestion.collapseByIdentifier + for i in 0..suggestions.high: + let + suggestion = suggestions[i] + collapsed = suggestion.collapseByIdentifier + if not addedSuggestions.contains collapsed: + addedSuggestions.incl collapsed + let + seenTimes = seenLabels[collapsed] + detail = + if seenTimes == 1: some(nimSymDetails(suggestion)) + else: some(&"[{seenTimes} overloads]") + completionItems.add create(CompletionItem, + label = suggestion.qualifiedPath[^1].strip(chars = {'`'}), + kind = some(nimSymToLSPKind(suggestion).int), + detail = detail, + documentation = some(suggestion.doc), + deprecated = none(bool), + preselect = none(bool), + sortText = some(fmt"{i:04}"), + filterText = none(string), + insertText = none(string), + insertTextFormat = none(int), + textEdit = none(TextEdit), + additionalTextEdits = none(seq[TextEdit]), + commitCharacters = none(seq[string]), + command = none(Command), + data = none(JsonNode) + ).JsonNode + outs.respond(message, completionItems) + of "textDocument/hover": + textDocumentRequest(message, TextDocumentPositionParams, req): + debugLog "Running equivalent of: def ", req.filePath, " ", req.filestash, "(", + req.rawLine + 1, ":", + openFiles.col(req), ")" + let suggestions = getNimsuggest(req.fileuri).def(req.filePath, dirtyfile = req.filestash, + req.rawLine + 1, + openFiles.col(req) + ) + debugLog "Found suggestions: ", + suggestions[0 ..< min(suggestions.len, 10)], + if suggestions.len > 10: &" and {suggestions.len-10} more" else: "" + var resp: JsonNode + if suggestions.len == 0: + resp = newJNull() + else: + var label = suggestions[0].qualifiedPath.join(".") + if suggestions[0].forth != "": + label &= ": " + label &= suggestions[0].forth + let + rangeopt = + some(create(Range, + create(Position, req.rawLine, req.rawChar), + create(Position, req.rawLine, req.rawChar + suggestions[0].qualifiedPath[^1].len) + )) + markedString = create(MarkedStringOption, "nim", label) + if suggestions[0].doc != "": + resp = create(Hover, + @[ + markedString, + create(MarkedStringOption, "", suggestions[0].doc), + ], + rangeopt + ).JsonNode + else: + resp = create(Hover, markedString, rangeopt).JsonNode; + outs.respond(message, resp) + of "textDocument/references": + textDocumentRequest(message, ReferenceParams, req): + debugLog "Running equivalent of: use ", req.fileuri, " ", req.filestash, "(", + req.rawLine + 1, ":", + openFiles.col(req), ")" + let suggestions = getNimsuggest(req.fileuri).use(req.filePath, dirtyfile = req.filestash, + req.rawLine + 1, + openFiles.col(req) + ) + debugLog "Found suggestions: ", + suggestions[0 ..< min(suggestions.len, 10)], + if suggestions.len > 10: &" and {suggestions.len-10} more" else: "" + var response = newJarray() + for suggestion in suggestions: + if suggestion.section == ideUse or req["context"]["includeDeclaration"].getBool: + response.add create(Location, + "file://" & pathToUri(suggestion.filepath), + create(Range, + create(Position, suggestion.line-1, suggestion.column), + create(Position, suggestion.line-1, suggestion.column + suggestion.qualifiedPath[^1].len) + ) + ).JsonNode + if response.len == 0: + outs.respond(message, newJNull()) + else: + outs.respond(message, response) + of "textDocument/rename": + textDocumentRequest(message, RenameParams, req): + debugLog "Running equivalent of: use ", req.fileuri, " ", req.filestash, "(", + req.rawLine + 1, ":", + openFiles.col(req), ")" + let suggestions = getNimsuggest(req.fileuri).use(req.filePath, dirtyfile = req.filestash, + req.rawLine + 1, + openFiles.col(req) + ) + debugLog "Found suggestions: ", + suggestions[0.. 10: &" and {suggestions.len-10} more" else: "" + var resp: JsonNode + if suggestions.len == 0: + resp = newJNull() + else: + var textEdits = newJObject() + for suggestion in suggestions: + let uri = "file://" & pathToUri(suggestion.filepath) + if uri notin textEdits: + textEdits[uri] = newJArray() + textEdits[uri].add create(TextEdit, create(Range, + create(Position, suggestion.line-1, suggestion.column), + create(Position, suggestion.line-1, suggestion.column + suggestion.qualifiedPath[^1].len) + ), + req["newName"].getStr + ).JsonNode + resp = create(WorkspaceEdit, + some(textEdits), + none(seq[TextDocumentEdit]) + ).JsonNode + outs.respond(message, resp) + of "textDocument/definition": + textDocumentRequest(message, TextDocumentPositionParams, req): + debugLog "Running equivalent of: def ", req.fileuri, " ", req.filestash, "(", + req.rawLine + 1, ":", + openFiles.col(req), ")" + let declarations = getNimsuggest(req.fileuri).def(req.filePath, dirtyfile = req.filestash, + req.rawLine + 1, + openFiles.col(req) + ) + debugLog "Found suggestions: ", + declarations[0.. 10: &" and {declarations.len-10} more" else: "" + var resp: JsonNode + if declarations.len == 0: + resp = newJNull() + else: + resp = newJarray() + for declaration in declarations: + resp.add create(Location, + "file://" & pathToUri(declaration.filepath), + create(Range, + create(Position, declaration.line-1, declaration.column), + create(Position, declaration.line-1, declaration.column + declaration.qualifiedPath[^1].len) + ) + ).JsonNode + outs.respond(message, resp) + of "textDocument/documentSymbol": + textDocumentRequest(message, DocumentSymbolParams, req): + debugLog "Running equivalent of: outline ", req.fileuri, + " ", req.filestash + let projectFile = openFiles[req.fileuri].projectFile + let syms = getNimsuggest(req.fileuri).outline( + req.filePath, + dirtyfile = req.filestash + ) + debugLog "Found outlines: ", syms[0.. 10: &" and {syms.len-10} more" else: "" + var resp: JsonNode + if syms.len == 0: + resp = newJNull() + else: + resp = newJarray() + for sym in syms.sortedByIt((it.line,it.column,it.quality)): + if sym.qualifiedPath.len != 2: + continue + resp.add create( + SymbolInformation, + sym.qualifiedPath[^1], + 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 + outs.respond(message, resp) + of "textDocument/signatureHelp": + textDocumentRequest(message, TextDocumentPositionParams, req): + debugLog "Running equivalent of: con ", req.filePath, " ", req.filestash, "(", + req.rawLine + 1, ":", + openFiles.col(req), ")" + let suggestions = getNimsuggest(req.fileuri).con(req.filePath, dirtyfile = req.filestash, req.rawLine + 1, req.rawChar) + var signatures = newSeq[SignatureInformation]() + for suggestion in suggestions: + var label = suggestion.qualifiedPath.join(".") + if suggestion.forth != "": + label &= ": " + label &= suggestion.forth + signatures.add create(SignatureInformation, + label = label, + documentation = some(suggestion.doc), + parameters = none(seq[ParameterInformation]) + ) + let resp = create(SignatureHelp, + signatures = signatures, + activeSignature = some(0), + activeParameter = some(0) + ).JsonNode + outs.respond(message, resp) + else: + let msg = "Unknown request method: " & message["method"].getStr + debugLog msg + outs.error(message, MethodNotFound, msg, newJObject()) + continue + elif isValid(message, NotificationMessage): + debugLog "Got valid Notification message of type ", message["method"].getStr + if not initialized and message["method"].getStr != "exit": + continue + case message["method"].getStr: + of "exit": + debugLog "Exiting" + if gotShutdown: + quit 0 + else: + quit 1 + of "initialized": + discard + of "textDocument/didOpen": + textDocumentNotification(message, DidOpenTextDocumentParams, req): + let + file = open(req.filestash, fmWrite) + projectFile = getProjectFile(uriToPath(req.fileuri)) + debugLog "New document: ", req.fileuri, " stash: ", req.filestash + openFiles[req.fileuri] = ( + projectFile: projectFile, + fingerTable: @[] + ) + + if projectFile notin projectFiles: + debugLog "Initialising with project file: ", projectFile + projectFiles[projectFile] = (nimsuggest: initNimsuggest(projectFile, nimpath), openFiles: initOrderedSet[string]()) + projectFiles[projectFile].openFiles.incl(req.fileuri) + + for line in req["textDocument"]["text"].getStr.splitLines: + openFiles[req.fileuri].fingerTable.add line.createUTFMapping() + file.writeLine line + file.close() + of "textDocument/didChange": + textDocumentNotification(message, DidChangeTextDocumentParams, req): + let file = open(req.filestash, fmWrite) + debugLog "Got document change for URI: ", req.fileuri, " saving to ", req.filestash + openFiles[req.fileuri].fingerTable = @[] + for line in req["contentChanges"][0]["text"].getStr.splitLines: + openFiles[req.fileuri].fingerTable.add line.createUTFMapping() + file.writeLine line + file.close() + + # Notify nimsuggest about a file modification. + discard getNimsuggest(req.fileuri).mod(req.filePath, dirtyfile = req.filestash) + of "textDocument/didClose": + textDocumentNotification(message, DidCloseTextDocumentParams, req): + let projectFile = getProjectFile(uriToPath(req.fileuri)) + debugLog "Got document close for URI: ", req.fileuri, " copied to ", req.filestash + removeFile(req.filestash) + projectFiles[projectFile].openFiles.excl(req.fileuri) + if projectFiles[projectFile].openFiles.len == 0: + debugLog "Trying to stop nimsuggest" + debugLog "Stopped nimsuggest with code: ", + getNimsuggest(req.fileuri).stopNimsuggest() + openFiles.del(req.fileuri) + of "textDocument/didSave": + textDocumentNotification(message, DidSaveTextDocumentParams, req): + if req["text"].isSome: + let file = open(req.filestash, fmWrite) + debugLog "Got document save for URI: ", req.fileuri, " saving to ", req.filestash + openFiles[req.fileuri].fingerTable = @[] + for line in req["text"].unsafeGet.getStr.splitLines: + openFiles[req.fileuri].fingerTable.add line.createUTFMapping() + file.writeLine line + file.close() + debugLog "fileuri: ", req.fileuri, ", project file: ", openFiles[req.fileuri].projectFile, ", dirtyfile: ", req.filestash + let diagnostics = getNimsuggest(req.fileuri).chk(req.filePath, dirtyfile = req.filestash) + debugLog "Got diagnostics: ", + diagnostics[0.. 10: &" and {diagnostics.len-10} more" else: "" + var response: seq[Diagnostic] + for diagnostic in diagnostics: + if diagnostic.line == 0: + continue + + if diagnostic.filePath != req.filePath: + continue + # Try to guess the size of the identifier + let + message = diagnostic.doc + endcolumn = diagnostic.column + message.rfind('\'') - message.find('\'') - 1 + response.add create(Diagnostic, + create(Range, + create(Position, diagnostic.line-1, diagnostic.column), + create(Position, diagnostic.line-1, max(diagnostic.column, endcolumn)) + ), + some(case diagnostic.forth: + of "Error": DiagnosticSeverity.Error.int + of "Hint": DiagnosticSeverity.Hint.int + of "Warning": DiagnosticSeverity.Warning.int + else: DiagnosticSeverity.Error.int), + none(int), + some("nimsuggest chk"), + message, + none(seq[DiagnosticRelatedInformation]) + ) + + # Invoke chk on all open files. + let projectFile = openFiles[req.fileuri].projectFile + for f in projectFiles[projectFile].openFiles.items: + let diagnostics = getNimsuggest(f).chk(req.filePath, dirtyfile = req.filestash) + debugLog "Got diagnostics: ", + diagnostics[0 ..< min(diagnostics.len, 10)], + if diagnostics.len > 10: &" and {diagnostics.len-10} more" else: "" + + var response: seq[Diagnostic] + for diagnostic in diagnostics: + if diagnostic.line == 0: + continue + + if diagnostic.filePath != uriToPath(f): + continue + # Try to guess the size of the identifier + let + message = diagnostic.doc + endcolumn = diagnostic.column + message.rfind('\'') - message.find('\'') - 1 + + response.add create( + Diagnostic, + create(Range, + create(Position, diagnostic.line-1, diagnostic.column), + create(Position, diagnostic.line-1, max(diagnostic.column, endcolumn)) + ), + some(case diagnostic.forth: + of "Error": DiagnosticSeverity.Error.int + of "Hint": DiagnosticSeverity.Hint.int + of "Warning": DiagnosticSeverity.Warning.int + else: DiagnosticSeverity.Error.int), + none(int), + some("nimsuggest chk"), + message, + none(seq[DiagnosticRelatedInformation]) + ) + let resp = create(PublishDiagnosticsParams, f, response).JsonNode + outs.notify("textDocument/publishDiagnostics", resp) + let resp = create(PublishDiagnosticsParams, + req.fileuri, + response).JsonNode + outs.notify("textDocument/publishDiagnostics", resp) + else: + let msg = "Unknown notification method: " & message["method"].getStr + warnLog msg + outs.error(message, MethodNotFound, msg, newJObject()) + continue + else: + let msg = "Invalid message: " & frame + debugLog msg + outs.error(message, InvalidRequest, msg, newJObject()) + except MalformedFrame as e: + warnLog "Got Invalid message id: ", e.msg + continue + except UriParseError as e: + warnLog "Got exception parsing URI: ", e.msg + continue + except IOError as e: + errorLog "Got IOError: ", e.msg + break + except CatchableError as e: + warnLog "Got exception: ", e.msg + continue + +var + ins = newFileStream(stdin) + outs = newFileStream(stdout) +main(ins, outs) diff --git a/nimlsp/nimlsp.nim.cfg b/nimlsp/nimlsp.nim.cfg new file mode 100644 index 00000000000..5092829b85e --- /dev/null +++ b/nimlsp/nimlsp.nim.cfg @@ -0,0 +1,20 @@ +hint[XDeclaredButNotUsed]:off + +path:"$lib/packages/docutils" +path:"$config/.." + +define:useStdoutAsStdmsg +define:nimsuggest +define:nimcore + +# die when nimsuggest uses more than 4GB: +@if cpu32: + define:"nimMaxHeap=2000" +@else: + define:"nimMaxHeap=4000" +@end + +--threads:off +--warning[Spacing]:off # The JSON schema macro uses a syntax similar to TypeScript +# --warning[CaseTransition]:off +-d:nimOldCaseObjects diff --git a/nimlsp/nimlsp.nimble b/nimlsp/nimlsp.nimble new file mode 100644 index 00000000000..77414e31cd2 --- /dev/null +++ b/nimlsp/nimlsp.nimble @@ -0,0 +1,25 @@ +# Package + +version = "0.4.4" +author = "PMunch" +description = "Nim Language Server Protocol - nimlsp implements the Language Server Protocol" +license = "MIT" +srcDir = "src" +bin = @["nimlsp", "nimlsp_debug"] + +# Dependencies + +# nimble test does not work for me out of the box +#task test, "Runs the test suite": + #exec "nim c -r tests/test_messages.nim" +# exec "nim c -d:debugLogging -d:jsonSchemaDebug -r tests/test_messages2.nim" + +task debug, "Builds the language server": + exec "nim c --threads:on -d:nimcore -d:nimsuggest -d:debugCommunication -d:debugLogging -o:nimlsp src/nimlsp" + +before test: + exec "nimble build" + +task findNim, "Tries to find the current Nim installation": + echo NimVersion + echo currentSourcePath diff --git a/nimlsp/nimlsp_debug.nim b/nimlsp/nimlsp_debug.nim new file mode 100644 index 00000000000..f4533775873 --- /dev/null +++ b/nimlsp/nimlsp_debug.nim @@ -0,0 +1 @@ +include nimlsp diff --git a/nimlsp/nimlsp_debug.nim.cfg b/nimlsp/nimlsp_debug.nim.cfg new file mode 100644 index 00000000000..739903ae492 --- /dev/null +++ b/nimlsp/nimlsp_debug.nim.cfg @@ -0,0 +1,22 @@ +hint[XDeclaredButNotUsed]:off + +path:"$lib/packages/docutils" +path:"$config/.." + +define:useStdoutAsStdmsg +define:nimsuggest +define:nimcore +define:debugCommunication +define:debugLogging + +# die when nimsuggest uses more than 4GB: +@if cpu32: + define:"nimMaxHeap=2000" +@else: + define:"nimMaxHeap=4000" +@end + +--threads:off +--warning[Spacing]:off # The JSON schema macro uses a syntax similar to TypeScript +# --warning[CaseTransition]:off +-d:nimOldCaseObjects diff --git a/nimlsp/nimlsppkg/baseprotocol.nim b/nimlsp/nimlsppkg/baseprotocol.nim new file mode 100644 index 00000000000..7fe61aed293 --- /dev/null +++ b/nimlsp/nimlsppkg/baseprotocol.nim @@ -0,0 +1,85 @@ +import std/[json, parseutils, streams, strformat, + strutils] +when defined(debugCommunication): + import logger + +type + BaseProtocolError* = object of CatchableError + + MalformedFrame* = object of BaseProtocolError + UnsupportedEncoding* = object of BaseProtocolError + +proc skipWhitespace(x: string, pos: int): int = + result = pos + while result < x.len and x[result] in Whitespace: + inc result + +proc sendFrame*(s: Stream, frame: string) = + when defined(debugCommunication): + frameLog(Out, frame) + when s is Stream: + s.write frame + s.flush + else: + s.write frame + +proc formFrame*(data: JsonNode): string = + var frame = newStringOfCap(1024) + toUgly(frame, data) + result = &"Content-Length: {frame.len}\r\n\r\n{frame}" + +proc sendJson*(s: Stream, data: JsonNode) = + let frame = formFrame(data) + s.sendFrame(frame) + +proc readFrame*(s: Stream): string = + var contentLen = -1 + var headerStarted = false + var ln: string + while true: + ln = s.readLine() + if ln.len != 0: + headerStarted = true + let sep = ln.find(':') + if sep == -1: + raise newException(MalformedFrame, "invalid header line: " & ln) + + let valueStart = ln.skipWhitespace(sep + 1) + + case ln[0 ..< sep] + of "Content-Type": + if ln.find("utf-8", valueStart) == -1 and ln.find("utf8", valueStart) == -1: + raise newException(UnsupportedEncoding, "only utf-8 is supported") + of "Content-Length": + if parseInt(ln, contentLen, valueStart) == 0: + raise newException(MalformedFrame, "invalid Content-Length: " & + ln.substr(valueStart)) + else: + # Unrecognized headers are ignored + discard + when defined(debugCommunication): + frameLog(In, ln) + elif not headerStarted: + continue + else: + when defined(debugCommunication): + frameLog(In, ln) + if contentLen != -1: + when s is Stream: + var buf = s.readStr(contentLen) + else: + var + buf = newString(contentLen) + head = 0 + while contentLen > 0: + let bytesRead = s.readBuffer(buf[head].addr, contentLen) + if bytesRead == 0: + raise newException(MalformedFrame, "Unexpected EOF") + contentLen -= bytesRead + head += bytesRead + when defined(debugCommunication): + frameLog(In, buf) + return buf + else: + raise newException(MalformedFrame, "missing Content-Length header") + diff --git a/nimlsp/nimlsppkg/jsonschema.nim b/nimlsp/nimlsppkg/jsonschema.nim new file mode 100644 index 00000000000..6a22ca73136 --- /dev/null +++ b/nimlsp/nimlsppkg/jsonschema.nim @@ -0,0 +1,479 @@ +import std/[macros, json, sequtils, options, strutils, tables] +import experimental/ast_pattern_matching + +const ManglePrefix {.strdefine.}: string = "the" + +type NilType* = enum Nil + +proc extractKinds(node: NimNode): seq[tuple[name: string, isArray: bool]] = + if node.kind == nnkIdent: + return @[(name: $node, isArray: false)] + elif node.kind == nnkInfix and node[0].kind == nnkIdent and $node[0] == "or": + result = node[2].extractKinds + result.insert(node[1].extractKinds) + elif node.kind == nnkBracketExpr and node[0].kind == nnkIdent: + return @[(name: $node[0], isArray: true)] + elif node.kind == nnkNilLit: + return @[(name: "nil", isArray: false)] + elif node.kind == nnkBracketExpr and node[0].kind == nnkNilLit: + raise newException(AssertionError, "Array of nils not allowed") + else: + raise newException(AssertionError, "Unknown node kind: " & $node.kind) + +proc matchDefinition(pattern: NimNode): + tuple[ + name: string, + kinds: seq[tuple[name: string, isArray: bool]], + optional: bool, + mangle: bool + ] {.compileTime.} = + matchAst(pattern): + of nnkCall( + `name` @ nnkIdent, + nnkStmtList( + `kind` + ) + ): + return ( + name: $name, + kinds: kind.extractKinds, + optional: false, + mangle: false + ) + of nnkInfix( + ident"?:", + `name` @ nnkIdent, + `kind` + ): + return ( + name: $name, + kinds: kind.extractKinds, + optional: true, + mangle: false + ) + of nnkCall( + `name` @ nnkStrLit, + nnkStmtList( + `kind` + ) + ): + return ( + name: $name, + kinds: kind.extractKinds, + optional: false, + mangle: true + ) + of nnkInfix( + ident"?:", + `name` @ nnkStrLit, + `kind` + ): + return ( + name: $name, + kinds: kind.extractKinds, + optional: true, + mangle: true + ) + +proc matchDefinitions(definitions: NimNode): + seq[ + tuple[ + name: string, + kinds: seq[ + tuple[ + name: string, + isArray: bool + ] + ], + optional: bool, + mangle: bool + ] + ] {.compileTime.} = + result = @[] + for definition in definitions: + result.add matchDefinition(definition) + +macro jsonSchema*(pattern: untyped): untyped = + var types: seq[ + tuple[ + name: string, + extends: string, + definitions:seq[ + tuple[ + name: string, + kinds: seq[ + tuple[ + name: string, + isArray: bool + ] + ], + optional: bool, + mangle: bool + ] + ] + ] + ] = @[] + for part in pattern: + matchAst(part): + of nnkCall( + `objectName` @ nnkIdent, + `definitions` @ nnkStmtList + ): + let defs = definitions.matchDefinitions + types.add (name: $objectName, extends: "", definitions: defs) + of nnkCommand( + `objectName` @ nnkIdent, + nnkCommand( + ident"extends", + `extends` @ nnkIdent + ), + `definitions` @ nnkStmtList + ): + let defs = definitions.matchDefinitions + types.add (name: $objectName, extends: $extends, definitions: defs) + + var + typeDefinitions = newStmtList() + validationBodies = initOrderedTable[string, NimNode]() + validFields = initOrderedTable[string, NimNode]() + optionalFields = initOrderedTable[string, NimNode]() + creatorBodies = initOrderedTable[string, NimNode]() + createArgs = initOrderedTable[string, NimNode]() + let + data = newIdentNode("data") + fields = newIdentNode("fields") + traverse = newIdentNode("traverse") + allowExtra = newIdentNode("allowExtra") + ret = newIdentNode("ret") + for t in types: + let + name = newIdentNode(t.name) + objname = newIdentNode(t.name & "Obj") + creatorBodies[t.name] = newStmtList() + typeDefinitions.add quote do: + type + `objname` = distinct JsonNodeObj + `name` = ref `objname` + #converter toJsonNode(input: `name`): JsonNode {.used.} = input.JsonNode + + var + requiredFields = 0 + validations = newStmtList() + validFields[t.name] = nnkBracket.newTree() + optionalFields[t.name] = nnkBracket.newTree() + createArgs[t.name] = nnkFormalParams.newTree(name) + for field in t.definitions: + let + fname = field.name + aname = if field.mangle: newIdentNode(ManglePrefix & field.name) else: newIdentNode(field.name) + cname = quote do: + `data`[`fname`] + if field.optional: + optionalFields[t.name].add newLit(field.name) + else: + validFields[t.name].add newLit(field.name) + var + checks: seq[NimNode] = @[] + argumentChoices: seq[NimNode] = @[] + for kind in field.kinds: + let + tKind = if kind.name == "any": + if kind.isArray: + nnkBracketExpr.newTree( + newIdentNode("seq"), + newIdentNode("JsonNode") + ) + else: + newIdentNode("JsonNode") + elif kind.isArray: + nnkBracketExpr.newTree( + newIdentNode("seq"), + newIdentNode(kind.name) + ) + else: + newIdentNode(kind.name) + isBaseType = kind.name.toLowerASCII in + ["int", "string", "float", "bool"] + if kind.name != "nil": + if kind.isArray: + argumentChoices.add tkind + else: + argumentChoices.add tkind + else: + argumentChoices.add newIdentNode("NilType") + if isBaseType: + let + jkind = newIdentNode("J" & kind.name) + if kind.isArray: + checks.add quote do: + `cname`.kind != JArray or `cname`.anyIt(it.kind != `jkind`) + else: + checks.add quote do: + `cname`.kind != `jkind` + elif kind.name == "any": + if kind.isArray: + checks.add quote do: + `cname`.kind != JArray + else: + checks.add newLit(false) + elif kind.name == "nil": + checks.add quote do: + `cname`.kind != JNull + else: + let kindNode = newIdentNode(kind.name) + if kind.isArray: + checks.add quote do: + `cname`.kind != JArray or + (`traverse` and not `cname`.allIt(it.isValid(`kindNode`, allowExtra = `allowExtra`))) + else: + checks.add quote do: + (`traverse` and not `cname`.isValid(`kindNode`, allowExtra = `allowExtra`)) + if kind.name == "nil": + if field.optional: + creatorBodies[t.name].add quote do: + when `aname` is Option[NilType]: + if `aname`.isSome: + `ret`[`fname`] = newJNull() + else: + creatorBodies[t.name].add quote do: + when `aname` is NilType: + `ret`[`fname`] = newJNull() + elif kind.isArray: + let + i = newIdentNode("i") + accs = if isBaseType: + quote do: + %`i` + else: + quote do: + `i`.JsonNode + if field.optional: + creatorBodies[t.name].add quote do: + when `aname` is Option[`tkind`]: + if `aname`.isSome: + `ret`[`fname`] = newJArray() + for `i` in `aname`.unsafeGet: + `ret`[`fname`].add `accs` + else: + creatorBodies[t.name].add quote do: + when `aname` is `tkind`: + `ret`[`fname`] = newJArray() + for `i` in `aname`: + `ret`[`fname`].add `accs` + else: + if field.optional: + let accs = if isBaseType: + quote do: + %`aname`.unsafeGet + else: + quote do: + `aname`.unsafeGet.JsonNode + creatorBodies[t.name].add quote do: + when `aname` is Option[`tkind`]: + if `aname`.isSome: + `ret`[`fname`] = `accs` + else: + let accs = if isBaseType: + quote do: + %`aname` + else: + quote do: + `aname`.JsonNode + creatorBodies[t.name].add quote do: + when `aname` is `tkind`: + `ret`[`fname`] = `accs` + while checks.len != 1: + let newFirst = nnkInfix.newTree( + newIdentNode("and"), + checks[0], + checks[1] + ) + checks = checks[2..^1] + checks.insert(newFirst) + if field.optional: + argumentChoices[0] = nnkBracketExpr.newTree( + newIdentNode("Option"), + argumentChoices[0] + ) + while argumentChoices.len != 1: + let newFirst = nnkInfix.newTree( + newIdentNode("or"), + argumentChoices[0], + if not field.optional: argumentChoices[1] + else: nnkBracketExpr.newTree( + newIdentNode("Option"), + argumentChoices[1] + ) + ) + argumentChoices = argumentChoices[2..^1] + argumentChoices.insert(newFirst) + createArgs[t.name].add nnkIdentDefs.newTree( + aname, + argumentChoices[0], + newEmptyNode() + ) + let check = checks[0] + if field.optional: + validations.add quote do: + if `data`.hasKey(`fname`): + `fields` += 1 + if `check`: return false + else: + requiredFields += 1 + validations.add quote do: + if not `data`.hasKey(`fname`): return false + if `check`: return false + + if t.extends.len == 0: + validationBodies[t.name] = quote do: + var `fields` = `requiredFields` + `validations` + else: + let extends = validationBodies[t.extends] + validationBodies[t.name] = quote do: + `extends` + `fields` += `requiredFields` + `validations` + for i in countdown(createArgs[t.extends].len - 1, 1): + createArgs[t.name].insert(1, createArgs[t.extends][i]) + creatorBodies[t.name].insert(0, creatorBodies[t.extends]) + for field in validFields[t.extends]: + validFields[t.name].add field + for field in optionalFields[t.extends]: + optionalFields[t.name].add field + + var forwardDecls = newStmtList() + var validators = newStmtList() + let schemaType = newIdentNode("schemaType") + for kind, body in validationBodies.pairs: + let kindIdent = newIdentNode(kind) + validators.add quote do: + proc isValid(`data`: JsonNode, `schemaType`: typedesc[`kindIdent`], + `traverse` = true, `allowExtra` = false): bool {.used.} = + if `data`.kind != JObject: return false + `body` + if not `allowExtra` and `fields` != `data`.len: return false + return true + forwardDecls.add quote do: + proc isValid(`data`: JsonNode, `schemaType`: typedesc[`kindIdent`], + `traverse` = true, `allowExtra` = false): bool {.used.} + var accessors = newStmtList() + var creators = newStmtList() + for t in types: + let + creatorBody = creatorBodies[t.name] + kindIdent = newIdentNode(t.name) + kindName = t.name + var creatorArgs = createArgs[t.name] + creatorArgs.insert(1, nnkIdentDefs.newTree( + schemaType, + nnkBracketExpr.newTree( + newIdentNode("typedesc"), + kindIdent + ), + newEmptyNode() + )) + var createProc = quote do: + proc create() {.used.} = + var `ret` = newJObject() + `creatorBody` + return `ret`.`kindIdent` + createProc[3] = creatorArgs + creators.add createProc + var forwardCreateProc = quote do: + proc create() {.used.} + forwardCreateProc[3] = creatorArgs + forwardDecls.add forwardCreateProc + + let macroName = nnkAccQuoted.newTree( + newIdentNode("[]") + ) + let + validFieldsList = validFields[t.name] + optionalFieldsList = optionalFields[t.name] + data = newIdentNode("data") + field = newIdentNode("field") + var accessorbody = nnkIfExpr.newTree() + if validFields[t.name].len != 0: + accessorbody.add nnkElifBranch.newTree(nnkInfix.newTree(newIdentNode("in"), field, validFieldsList), quote do: + return nnkStmtList.newTree( + nnkCall.newTree( + newIdentNode("unsafeAccess"), + `data`, + newLit(`field`) + ) + ) + ) + if optionalFields[t.name].len != 0: + accessorbody.add nnkElifBranch.newTree(nnkInfix.newTree(newIdentNode("in"), field, optionalFieldsList), quote do: + return nnkStmtList.newTree( + nnkCall.newTree( + newIdentNode("unsafeOptAccess"), + `data`, + newLit(`field`) + ) + ) + ) + accessorbody.add nnkElse.newTree(quote do: + raise newException(KeyError, "unable to access field \"" & `field` & "\" in data with schema " & `kindName`) + ) + accessors.add quote do: + proc unsafeAccess(data: `kindIdent`, field: static[string]): JsonNode {.used.} = + JsonNode(data)[field] + proc unsafeOptAccess(data: `kindIdent`, field: static[string]): Option[JsonNode] {.used.} = + if JsonNode(data).hasKey(field): + some(JsonNode(data)[field]) + else: + none(JsonNode) + + macro `macroName`(`data`: `kindIdent`, `field`: static[string]): untyped {.used.} = + `accessorbody` + + result = quote do: + import macros + `typeDefinitions` + `forwardDecls` + `validators` + `creators` + `accessors` + + when defined(jsonSchemaDebug): + echo result.repr + +when isMainModule: + jsonSchema: + CancelParams: + id?: int or string or float + something?: float + + WrapsCancelParams: + cp: CancelParams + name: string + + ExtendsCancelParams extends CancelParams: + name: string + + WithArrayAndAny: + test?: CancelParams[] + ralph: int[] or float + bob: any + john?: int or nil + + NameTest: + "method": string + "result": int + "if": bool + "type": float + + var wcp = create(WrapsCancelParams, + create(CancelParams, some(10), none(float)), "Hello" + ) + echo wcp.JsonNode.isValid(WrapsCancelParams) == true + echo wcp.JsonNode.isValid(WrapsCancelParams, false) == true + var ecp = create(ExtendsCancelParams, some(10), some(5.3), "Hello") + echo ecp.JsonNode.isValid(ExtendsCancelParams) == true + var war = create(WithArrayAndAny, some(@[ + create(CancelParams, some(10), some(1.0)), + create(CancelParams, some("hello"), none(float)) + ]), 2.0, %*{"hello": "world"}, none(NilType)) + echo war.JsonNode.isValid(WithArrayAndAny) == true \ No newline at end of file diff --git a/nimlsp/nimlsppkg/logger.nim b/nimlsp/nimlsppkg/logger.nim new file mode 100644 index 00000000000..6fdfb2f511a --- /dev/null +++ b/nimlsp/nimlsppkg/logger.nim @@ -0,0 +1,38 @@ +import std/[logging, os] + + +let storage* = getTempDir() / "nimlsp-" & $getCurrentProcessId() +discard existsOrCreateDir(storage) +let rollingLog = newRollingFileLogger(storage / "nimlsp.log") +addHandler(rollingLog) + +template debugLog*(args: varargs[string, `$`]) = + when defined(debugLogging): + debug join(args) + flushFile rollingLog.file + +template infoLog*(args: varargs[string, `$`]) = + when defined(debugLogging): + info join(args) + flushFile rollingLog.file + +template errorLog*(args: varargs[string, `$`]) = + when defined(debugLogging): + error join(args) + +template warnLog*(args: varargs[string, `$`]) = + when defined(debugLogging): + warn join(args) + +type FrameDirection* = enum In, Out + +template frameLog*(direction: FrameDirection, args: varargs[string, `$`]) = + let oldFmtStr = rollingLog.fmtStr + case direction: + of Out: rollingLog.fmtStr = "<< " + of In: rollingLog.fmtStr = ">> " + let msg = join(args) + for line in msg.splitLines: + info line + flushFile rollingLog.file + rollingLog.fmtStr = oldFmtStr \ No newline at end of file diff --git a/nimlsp/nimlsppkg/messageenums.nim b/nimlsp/nimlsppkg/messageenums.nim new file mode 100644 index 00000000000..63b0e34867f --- /dev/null +++ b/nimlsp/nimlsppkg/messageenums.nim @@ -0,0 +1,114 @@ +type + ErrorCode* = enum + RequestCancelled = -32800 # All the other error codes are from JSON-RPC + ParseError = -32700, + InternalError = -32603, + InvalidParams = -32602, + MethodNotFound = -32601, + InvalidRequest = -32600, + ServerErrorStart = -32099, + ServerNotInitialized = -32002, + ServerErrorEnd = -32000 + +# Anything below here comes from the LSP specification +type + DiagnosticSeverity* {.pure.} = enum + Error = 1, + Warning = 2, + Information = 3, + Hint = 4 + + SymbolKind* {.pure.} = enum + File = 1, + Module = 2, + Namespace = 3, + Package = 4, + Class = 5, + Method = 6, + Property = 7, + Field = 8, + Constructor = 9, + Enum = 10, + Interface = 11, + Function = 12, + Variable = 13, + Constant = 14, + String = 15, + Number = 16, + Boolean = 17, + Array = 18, + Object = 19, + Key = 20, + Null = 21, + EnumMember = 22, + Struct = 23, + Event = 24, + Operator = 25, + TypeParameter = 26 + + CompletionItemKind* {.pure.} = enum + Text = 1, + Method = 2, + Function = 3, + Constructor = 4, + Field = 5, + Variable = 6, + Class = 7, + Interface = 8, + Module = 9, + Property = 10, + Unit = 11, + Value = 12, + Enum = 13, + Keyword = 14, + Snippet = 15, + Color = 16, + File = 17, + Reference = 18, + Folder = 19, + EnumMember = 20, + Constant = 21, + Struct = 22, + Event = 23, + Operator = 24, + TypeParameter = 25 + + TextDocumentSyncKind* {.pure.} = enum + None = 0, + Full = 1, + Incremental = 2 + + MessageType* {.pure.} = enum + Error = 1, + Warning = 2, + Info = 3, + Log = 4 + + FileChangeType* {.pure.} = enum + Created = 1, + Changed = 2, + Deleted = 3 + + WatchKind* {.pure.} = enum + Create = 1, + Change = 2, + Delete = 4 + + TextDocumentSaveReason* {.pure.} = enum + Manual = 1, + AfterDelay = 2, + FocusOut = 3 + + CompletionTriggerKind* {.pure.} = enum + Invoked = 1, + TriggerCharacter = 2, + TriggerForIncompleteCompletions = 3 + + InsertTextFormat* {.pure.} = enum + PlainText = 1, + Snippet = 2 + + DocumentHighlightKind* {.pure.} = enum + Text = 1, + Read = 2, + Write = 3 diff --git a/nimlsp/nimlsppkg/messages.nim b/nimlsp/nimlsppkg/messages.nim new file mode 100644 index 00000000000..e6ed66f3456 --- /dev/null +++ b/nimlsp/nimlsppkg/messages.nim @@ -0,0 +1,596 @@ +import std/[json, options, sequtils] +# Anything below here comes from the LSP specification +import ./jsonschema + + +jsonSchema: + Message: + jsonrpc: string + + RequestMessage extends Message: + id: int or float or string + "method": string + params ?: any[] or any + + ResponseMessage extends Message: + id: int or float or string or nil + "result" ?: any + error ?: ResponseError + + ResponseError: + code: int or float + message: string + data: any + + NotificationMessage extends Message: + "method": string + params ?: any[] or any + + CancelParams: + id: int or float or string + + Position: + line: int or float + character: int or float + + Range: + start: Position + "end": Position + + Location: + uri: string # Note that this is not checked + "range": Range + + Diagnostic: + "range": Range + severity ?: int or float + code ?: int or float or string + source ?: string + message: string + relatedInformation ?: DiagnosticRelatedInformation[] + + DiagnosticRelatedInformation: + location: Location + message: string + + Command: + title: string + command: string + arguments ?: any[] + + TextEdit: + "range": Range + newText: string + + TextDocumentEdit: + textDocument: VersionedTextDocumentIdentifier + edits: TextEdit[] + + WorkspaceEdit: + changes ?: any # This is a uri(string) to TextEdit[] mapping + documentChanges ?: TextDocumentEdit[] + + TextDocumentIdentifier: + uri: string # Note that this is not checked + + TextDocumentItem: + uri: string + languageId: string + version: int or float + text: string + + VersionedTextDocumentIdentifier extends TextDocumentIdentifier: + version: int or float or nil + languageId ?: string # SublimeLSP adds this field erroneously + + TextDocumentPositionParams: + textDocument: TextDocumentIdentifier + position: Position + + DocumentFilter: + language ?: string + scheme ?: string + pattern ?: string + + MarkupContent: + kind: string # "plaintext" or "markdown" + value: string + + InitializeParams: + processId: int or float or nil + rootPath ?: string or nil + rootUri: string or nil # String is DocumentUri + initializationOptions ?: any + capabilities: ClientCapabilities + trace ?: string # 'off' or 'messages' or 'verbose' + workspaceFolders ?: WorkspaceFolder[] or nil + + WorkspaceEditCapability: + documentChanges ?: bool + + DidChangeConfigurationCapability: + dynamicRegistration ?: bool + + DidChangeWatchedFilesCapability: + dynamicRegistration ?: bool + + SymbolKindCapability: + valueSet ?: int # SymbolKind enum + + SymbolCapability: + dynamicRegistration ?: bool + symbolKind ?: SymbolKindCapability + + ExecuteCommandCapability: + dynamicRegistration ?: bool + + WorkspaceClientCapabilities: + applyEdit ?: bool + workspaceEdit ?: WorkspaceEditCapability + didChangeConfiguration ?: DidChangeConfigurationCapability + didChangeWatchedFiles ?: DidChangeWatchedFilesCapability + symbol ?: SymbolCapability + executeCommand ?: ExecuteCommandCapability + workspaceFolders ?: bool + configuration ?: bool + + SynchronizationCapability: + dynamicRegistration ?: bool + willSave ?: bool + willSaveWaitUntil ?: bool + didSave ?: bool + + CompletionItemCapability: + snippetSupport ?: bool + commitCharactersSupport ?: bool + documentFormat ?: string[] # MarkupKind + deprecatedSupport ?: bool + + CompletionItemKindCapability: + valueSet ?: int[] # CompletionItemKind enum + + CompletionCapability: + dynamicRegistration ?: bool + completionItem ?: CompletionItemCapability + completionItemKind ?: CompletionItemKindCapability + contextSupport ?: bool + + HoverCapability: + dynamicRegistration ?: bool + contentFormat ?: string[] # MarkupKind + + SignatureInformationCapability: + documentationFormat ?: string[] # MarkupKind + + SignatureHelpCapability: + dynamicRegistration ?: bool + signatureInformation ?: SignatureInformationCapability + + ReferencesCapability: + dynamicRegistration ?: bool + + DocumentHighlightCapability: + dynamicRegistration ?: bool + + DocumentSymbolCapability: + dynamicRegistration ?: bool + symbolKind ?: SymbolKindCapability + + FormattingCapability: + dynamicRegistration ?: bool + + RangeFormattingCapability: + dynamicRegistration ?: bool + + OnTypeFormattingCapability: + dynamicRegistration ?: bool + + DefinitionCapability: + dynamicRegistration ?: bool + + TypeDefinitionCapability: + dynamicRegistration ?: bool + + ImplementationCapability: + dynamicRegistration ?: bool + + CodeActionCapability: + dynamicRegistration ?: bool + + CodeLensCapability: + dynamicRegistration ?: bool + + DocumentLinkCapability: + dynamicRegistration ?: bool + + ColorProviderCapability: + dynamicRegistration ?: bool + + RenameCapability: + dynamicRegistration ?: bool + + PublishDiagnosticsCapability: + dynamicRegistration ?: bool + + TextDocumentClientCapabilities: + synchronization ?: SynchronizationCapability + completion ?: CompletionCapability + hover ?: HoverCapability + signatureHelp ?: SignatureHelpCapability + references ?: ReferencesCapability + documentHighlight ?: DocumentHighlightCapability + documentSymbol ?: DocumentSymbolCapability + formatting ?: FormattingCapability + rangeFormatting ?: RangeFormattingCapability + onTypeFormatting ?: OnTypeFormattingCapability + definition ?: DefinitionCapability + typeDefinition ?: TypeDefinitionCapability + implementation ?: ImplementationCapability + codeAction ?: CodeActionCapability + codeLens ?: CodeLensCapability + documentLink ?: DocumentLinkCapability + colorProvider ?: ColorProviderCapability + rename ?: RenameCapability + publishDiagnostics ?: PublishDiagnosticsCapability + + ClientCapabilities: + workspace ?: WorkspaceClientCapabilities + textDocument ?: TextDocumentClientCapabilities + experimental ?: any + + WorkspaceFolder: + uri: string + name: string + + InitializeResult: + capabilities: ServerCapabilities + + InitializeError: + retry: bool + + CompletionOptions: + resolveProvider ?: bool + triggerCharacters ?: string[] + + SignatureHelpOptions: + triggerCharacters ?: string[] + + CodeLensOptions: + resolveProvider ?: bool + + DocumentOnTypeFormattingOptions: + firstTriggerCharacter: string + moreTriggerCharacter ?: string[] + + DocumentLinkOptions: + resolveProvider ?: bool + + ExecuteCommandOptions: + commands: string[] + + SaveOptions: + includeText ?: bool + + ColorProviderOptions: + DUMMY ?: nil # This is actually an empty object + + TextDocumentSyncOptions: + openClose ?: bool + change ?: int or float + willSave ?: bool + willSaveWaitUntil ?: bool + save ?: SaveOptions + + StaticRegistrationOptions: + id ?: string + + WorkspaceFolderCapability: + supported ?: bool + changeNotifications ?: string or bool + + WorkspaceCapability: + workspaceFolders ?: WorkspaceFolderCapability + + TextDocumentRegistrationOptions: + documentSelector: DocumentFilter[] or nil + + TextDocumentAndStaticRegistrationOptions extends TextDocumentRegistrationOptions: + id ?: string + + ServerCapabilities: + textDocumentSync ?: TextDocumentSyncOptions or int or float + hoverProvider ?: bool + completionProvider ?: CompletionOptions + signatureHelpProvider ?: SignatureHelpOptions + definitionProvider ?: bool + typeDefinitionProvider ?: bool or TextDocumentAndStaticRegistrationOptions + implementationProvider ?: bool or TextDocumentAndStaticRegistrationOptions + referencesProvider ?: bool + documentHighlightProvider ?: bool + documentSymbolProvider ?: bool + workspaceSymbolProvider ?: bool + codeActionProvider ?: bool + codeLensProvider ?: CodeLensOptions + documentFormattingProvider ?: bool + documentRangeFormattingProvider ?: bool + documentOnTypeFormattingProvider ?: DocumentOnTypeFormattingOptions + renameProvider ?: bool + documentLinkProvider ?: DocumentLinkOptions + colorProvider ?: bool or ColorProviderOptions or TextDocumentAndStaticRegistrationOptions + executeCommandProvider ?: ExecuteCommandOptions + workspace ?: WorkspaceCapability + experimental ?: any + + InitializedParams: + DUMMY ?: nil # This is actually an empty object + + ShowMessageParams: + "type": int # MessageType + message: string + + MessageActionItem: + title: string + + ShowMessageRequestParams: + "type": int # MessageType + message: string + actions ?: MessageActionItem[] + + LogMessageParams: + "type": int # MessageType + message: string + + Registration: + id: string + "method": string + registrationOptions ?: any + + RegistrationParams: + registrations: Registration[] + + Unregistration: + id: string + "method": string + + UnregistrationParams: + unregistrations: Unregistration[] + + WorkspaceFoldersChangeEvent: + added: WorkspaceFolder[] + removed: WorkspaceFolder[] + + DidChangeWorkspaceFoldersParams: + event: WorkspaceFoldersChangeEvent + + DidChangeConfigurationParams: + settings: any + + ConfigurationParams: + "items": ConfigurationItem[] + + ConfigurationItem: + scopeUri ?: string + section ?: string + + FileEvent: + uri: string # DocumentUri + "type": int # FileChangeType + + DidChangeWatchedFilesParams: + changes: FileEvent[] + + DidChangeWatchedFilesRegistrationOptions: + watchers: FileSystemWatcher[] + + FileSystemWatcher: + globPattern: string + kind ?: int # WatchKindCreate (bitmap) + + WorkspaceSymbolParams: + query: string + + ExecuteCommandParams: + command: string + arguments ?: any[] + + ExecuteCommandRegistrationOptions: + commands: string[] + + ApplyWorkspaceEditParams: + label ?: string + edit: WorkspaceEdit + + ApplyWorkspaceEditResponse: + applied: bool + + DidOpenTextDocumentParams: + textDocument: TextDocumentItem + + DidChangeTextDocumentParams: + textDocument: VersionedTextDocumentIdentifier + contentChanges: TextDocumentContentChangeEvent[] + + TextDocumentContentChangeEvent: + range ?: Range + rangeLength ?: int or float + text: string + + TextDocumentChangeRegistrationOptions extends TextDocumentRegistrationOptions: + syncKind: int or float + + WillSaveTextDocumentParams: + textDocument: TextDocumentIdentifier + reason: int # TextDocumentSaveReason + + DidSaveTextDocumentParams: + textDocument: TextDocumentIdentifier + text ?: string + + TextDocumentSaveRegistrationOptions extends TextDocumentRegistrationOptions: + includeText ?: bool + + DidCloseTextDocumentParams: + textDocument: TextDocumentIdentifier + + PublishDiagnosticsParams: + uri: string # DocumentUri + diagnostics: Diagnostic[] + + CompletionParams extends TextDocumentPositionParams: + context ?: CompletionContext + + CompletionContext: + triggerKind: int # CompletionTriggerKind + triggerCharacter ?: string + + CompletionList: + isIncomplete: bool + "items": CompletionItem[] + + CompletionItem: + label: string + kind ?: int # CompletionItemKind + detail ?: string + documentation ?: string or MarkupContent + deprecated ?: bool + preselect ?: bool + sortText ?: string + filterText ?: string + insertText ?: string + insertTextFormat ?: int #InsertTextFormat + textEdit ?: TextEdit + additionalTextEdits ?: TextEdit[] + commitCharacters ?: string[] + command ?: Command + data ?: any + + CompletionRegistrationOptions extends TextDocumentRegistrationOptions: + triggerCharacters ?: string[] + resolveProvider ?: bool + + MarkedStringOption: + language: string + value: string + + Hover: + contents: string or MarkedStringOption or string[] or MarkedStringOption[] or MarkupContent + range ?: Range + + SignatureHelp: + signatures: SignatureInformation[] + activeSignature ?: int or float + activeParameter ?: int or float + + SignatureInformation: + label: string + documentation ?: string or MarkupContent + parameters ?: ParameterInformation[] + + ParameterInformation: + label: string + documentation ?: string or MarkupContent + + SignatureHelpRegistrationOptions extends TextDocumentRegistrationOptions: + triggerCharacters ?: string[] + + ReferenceParams extends TextDocumentPositionParams: + context: ReferenceContext + + ReferenceContext: + includeDeclaration: bool + + DocumentHighlight: + "range": Range + kind ?: int # DocumentHighlightKind + + DocumentSymbolParams: + textDocument: TextDocumentIdentifier + + SymbolInformation: + name: string + kind: int # SymbolKind + deprecated ?: bool + location: Location + containerName ?: string + + CodeActionParams: + textDocument: TextDocumentIdentifier + "range": Range + context: CodeActionContext + + CodeActionContext: + diagnostics: Diagnostic[] + + CodeLensParams: + textDocument: TextDocumentIdentifier + + CodeLens: + "range": Range + command ?: Command + data ?: any + + CodeLensRegistrationOptions extends TextDocumentRegistrationOptions: + resolveProvider ?: bool + + DocumentLinkParams: + textDocument: TextDocumentIdentifier + + DocumentLink: + "range": Range + target ?: string # DocumentUri + data ?: any + + DocumentLinkRegistrationOptions extends TextDocumentRegistrationOptions: + resolveProvider ?: bool + + DocumentColorParams: + textDocument: TextDocumentIdentifier + + ColorInformation: + "range": Range + color: Color + + Color: + red: int or float + green: int or float + blue: int or float + alpha: int or float + + ColorPresentationParams: + textDocument: TextDocumentIdentifier + color: Color + "range": Range + + ColorPresentation: + label: string + textEdit ?: TextEdit + additionalTextEdits ?: TextEdit[] + + DocumentFormattingParams: + textDocument: TextDocumentIdentifier + options: any # FormattingOptions + + #FormattingOptions: + # tabSize: int or float + # insertSpaces: bool + # [key: string]: boolean | int or float | string (jsonschema doesn't support variable key objects) + + DocumentRangeFormattingParams: + textDocument: TextDocumentIdentifier + "range": Range + options: any # FormattingOptions + + DocumentOnTypeFormattingParams: + textDocument: TextDocumentIdentifier + position: Position + ch: string + options: any # FormattingOptions + + DocumentOnTypeFormattingRegistrationOptions extends TextDocumentRegistrationOptions: + firstTriggerCharacter: string + moreTriggerCharacter ?: string[] + + RenameParams: + textDocument: TextDocumentIdentifier + position: Position + newName: string diff --git a/nimlsp/nimlsppkg/nimsuggest.nim b/nimlsp/nimlsppkg/nimsuggest.nim new file mode 100644 index 00000000000..6fc4d2e1971 --- /dev/null +++ b/nimlsp/nimlsppkg/nimsuggest.nim @@ -0,0 +1,293 @@ +when not defined(nimcore): + {.error: "nimcore MUST be defined for Nim's core tooling".} + +import std/[os, net] +import std/options as stdOptions +import + compiler/ast/[ + idents, + lineinfos, + ast, + syntaxes, + parser, + ast_parsed_types, + ast_types + ], + compiler/modules/[ + modules, + modulegraphs + ], + compiler/front/[ + options, + optionsprocessor, + # commands, + msgs, + cmdlinehelper, + cli_reporter + ], + compiler/utils/[ + # prefixmatches, + pathutils + ], + compiler/sem/[ + sem, + passes, + passaux, + ] + +from compiler/ast/reports import Report, + category, + kind, + location + +from compiler/front/main import customizeForBackend + +from compiler/tools/suggest import isTracked, listUsages, suggestSym, `$` + +export Suggest +export IdeCmd +export AbsoluteFile +type + CachedMsgs = seq[Report] + NimSuggest* = ref object + graph: ModuleGraph + idle: int + cachedMsgs: CachedMsgs + +proc defaultStructuredReportHook(conf: ConfigRef, report: Report): TErrorHandling = + discard + +proc initNimSuggest*(project: string, nimPath: string = ""): NimSuggest = + var retval: ModuleGraph + proc mockCommand(graph: ModuleGraph) = + retval = graph + let conf = graph.config + clearPasses(graph) + registerPass graph, verbosePass + registerPass graph, semPass + conf.setCmd cmdIdeTools + + add(conf.searchPaths, conf.libpath) + + conf.setErrorMaxHighMaybe + # do not print errors, but log them + conf.writelnHook = proc(conf: ConfigRef, msg: string, flags: MsgFlags) = + discard + conf.structuredReportHook = defaultStructuredReportHook + + # compile the project before showing any input so that we already + # can answer questions right away: + compileProject(graph) + + + proc mockCmdLine(pass: TCmdLinePass, argv: openArray[string]; + conf: ConfigRef) = + conf.writeHook = proc(conf: ConfigRef, s: string, flags: MsgFlags) = discard + + let a = unixToNativePath(project) + if dirExists(a) and not fileExists(a.addFileExt("nim")): + conf.projectName = findProjectNimFile(conf, a) + # don't make it worse, report the error the old way: + if conf.projectName.len == 0: conf.projectName = a + else: + conf.projectName = a + let + cache = newIdentCache() + conf = newConfigRef(cli_reporter.reportHook) + self = NimProg( + suggestMode: true, + processCmdLine: mockCmdLine + ) + conf.astDiagToLegacyReport = cli_reporter.legacyReportBridge + self.initDefinesProg(conf, "nimsuggest") + + self.processCmdLineAndProjectPath(conf, []) + + # Find Nim's prefix dir. + if nimPath == "": + let binaryPath = findExe("nim") + if binaryPath == "": + raise newException(IOError, + "Cannot find Nim standard library: Nim compiler not in PATH") + conf.prefixDir = AbsoluteDir binaryPath.splitPath().head.parentDir() + if not dirExists(conf.prefixDir / RelativeDir"lib"): + conf.prefixDir = AbsoluteDir"" + else: + conf.prefixDir = AbsoluteDir nimPath + + var graph = newModuleGraph(cache, conf) + graph.onMarkUsed = proc (g: ModuleGraph; info: TLineInfo; s: PSym; usageSym: var PSym; isDecl: bool) = + suggestSym(g, info, s, usageSym, isDecl) + graph.onSymImport = graph.onMarkUsed # same callback + if self.loadConfigsAndProcessCmdLine(cache, conf, graph, []): + customizeForBackend(graph, conf, backendC) + mockCommand(graph) + + retval.doStopCompile = proc (): bool = false + return NimSuggest(graph: retval, idle: 0, cachedMsgs: @[]) + +proc findNode(n: PNode; trackPos: TLineInfo): PSym = + if n.kind == nkSym: + if isTracked(n.info, trackPos, n.sym.name.s.len): return n.sym + else: + for i in 0 ..< safeLen(n): + let res = findNode(n[i], trackPos) + if res != nil: return res + +proc symFromInfo(graph: ModuleGraph; trackPos: TLineInfo; moduleIdx: FileIndex): PSym = + let m = graph.getModule(moduleIdx) + if m != nil and m.ast != nil: + result = findNode(m.ast, trackPos) + +proc getSymNode(node: ParsedNode): ParsedNode = + result = node + if result.kind == pnkPostfix: + result = result[^1] + elif result.kind == pnkPragmaExpr: + result = getSymNode(result[0]) + +proc pnkToSymKind(kind: ParsedNodeKind): TSymKind = + result = skUnknown + case kind + of pnkConstSection, pnkConstDef: result = skConst + of pnkLetSection: result = skLet + of pnkVarSection: result = skVar + of pnkProcDef: result = skProc + of pnkFuncDef: result = skFunc + of pnkMethodDef: result = skMethod + of pnkConverterDef: result = skConverter + of pnkIteratorDef: result = skIterator + of pnkMacroDef: result = skMacro + of pnkTemplateDef: result = skTemplate + of pnkTypeDef, pnkTypeSection: result = skType + else: discard + +proc getName(node: ParsedNode): string = + if node.kind == pnkIdent: + result = node.startToken.ident.s + elif node.kind == pnkAccQuoted: + result = "`" + for t in node.idents: + result.add t.ident.s + result.add "`" + +proc parsedNodeToSugget(n: ParsedNode; moduleName: string): Suggest = + if n.kind in {pnkError, pnkEmpty}: return + if n.kind notin {pnkConstSection..pnkTypeDef, pnkIdentDefs}: return + new(result) + let token = getToken(n) + var name = "" + + if n.kind in {pnkProcDef..pnkTypeDef, pnkIdentDefs}: + var node: ParsedNode = getSymNode(n[0]) + if node.kind != pnkError: + name = getName(node) + + if name != "": + result.qualifiedPath = @[moduleName, name] + result.line = token.line.int + result.column = token.col.int + result.symkind = byte pnkToSymKind(n.kind) + +proc outline(graph: ModuleGraph; file: AbsoluteFile; fileIdx: FileIndex) = + let conf = graph.config + var parser: Parser + var sug: Suggest + var parsedNode: ParsedNode + var s: ParsedNode + let m = splitFile(file.string) + const Sections = {pnkTypeSection, pnkConstSection, pnkLetSection, pnkVarSection} + template suggestIt(parsedNode: ParsedNode) = + sug = parsedNodeToSugget(parsedNode, m.name) + if sug != nil: + sug.filepath = file.string + conf.suggestionResultHook(sug) + if setupParser(parser, fileIdx, graph.cache, conf): + while true: + parsedNode = parser.parseTopLevelStmt() + if parsedNode.kind == pnkEmpty: + break + + if parsedNode.kind in Sections: + for node in parsedNode.sons: + suggestIt(node) + else: + suggestIt(parsedNode) + closeParser(parser) + +proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int, + graph: ModuleGraph) = + let conf = graph.config + conf.ideCmd = cmd + + var isKnownFile = true + let dirtyIdx = fileInfoIdx(conf, file, isKnownFile) + + if not dirtyfile.isEmpty: msgs.setDirtyFile(conf, dirtyIdx, dirtyfile) + else: msgs.setDirtyFile(conf, dirtyIdx, AbsoluteFile"") + + conf.m.trackPos = newLineInfo(dirtyIdx, line, col) + conf.m.trackPosAttached = false + conf.errorCounter = 0 + var moduleIdx: FileIndex + var needCompile = true + if conf.ideCmd in {ideUse, ideDus} and + dirtyfile.isEmpty: + needCompile = false + if conf.ideCmd == ideOutline: + needCompile = false + outline(graph, file, dirtyIdx) + + if needCompile: + if not isKnownFile: + moduleIdx = dirtyIdx + # stderr.writeLine "Compile unknown module: " & toFullPath(conf, moduleIdx) + discard graph.compileModule(moduleIdx, {}) + else: + moduleIdx = graph.parentModule(dirtyIdx) + # stderr.writeLine "Compile known module: " & toFullPath(conf, moduleIdx) + graph.markDirty dirtyIdx + graph.markClientsDirty dirtyIdx + # partially recompiling the project means that that VM and JIT state + # would become stale, which we prevent by discarding all of it: + graph.vm = nil + if conf.ideCmd != ideMod: + discard graph.compileModule(moduleIdx, {}) + if conf.ideCmd in {ideUse, ideDus}: + let u = graph.symFromInfo(conf.m.trackPos, moduleIdx) + if u != nil: + listUsages(graph, u) + else: + stderr.writeLine "found no symbol at position: " & (conf $ conf.m.trackPos) + +proc runCmd*(nimsuggest: NimSuggest, cmd: IdeCmd, file, + dirtyfile: AbsoluteFile, line, col: int): seq[Suggest] = + var retval: seq[Suggest] = @[] + let conf = nimsuggest.graph.config + conf.ideCmd = cmd + conf.suggestionResultHook = proc (s: Suggest) = + retval.add(s) + + if conf.ideCmd == ideKnown: + retval.add(Suggest(section: ideKnown, quality: ord(fileInfoKnown(conf, file)))) + elif conf.ideCmd == ideProject: + retval.add(Suggest(section: ideProject, + filePath: string conf.projectFull)) + else: + # if conf.ideCmd == ideChk: + # for cm in nimsuggest.cachedMsgs: errorHook(conf, cm.info, cm.msg, cm.sev) + if conf.ideCmd == ideChk: + conf.structuredReportHook = proc (conf: ConfigRef, report: Report): TErrorHandling = + let loc = report.location() + if stdOptions.isSome(loc): + let info = loc.get() + retval.add(Suggest(section: ideChk, filePath: toFullPath(conf, + info), + line: toLinenumber(info), column: toColumn(info), + forth: $severity(conf, report))) + return doNothing + else: + conf.structuredReportHook = defaultStructuredReportHook + executeNoHooks(conf.ideCmd, file, dirtyfile, line, col, + nimsuggest.graph) + return retval diff --git a/nimlsp/nimlsppkg/suggestlib.nim b/nimlsp/nimlsppkg/suggestlib.nim new file mode 100644 index 00000000000..fcee5fc023b --- /dev/null +++ b/nimlsp/nimlsppkg/suggestlib.nim @@ -0,0 +1,114 @@ +import std/[strformat, strutils] +import messageenums +import nimsuggest +export Suggest +export IdeCmd +export NimSuggest +export initNimSuggest +import + compiler/ast/[ + ast + ] + +proc stopNimSuggest*(nimsuggest: NimSuggest): int = 42 + +proc `$`*(suggest: Suggest): string = + &"""(section: {suggest.section}, symKind: {suggest.symkind.TSymKind + }, qualifiedPath: {suggest.qualifiedPath.join(".")}, forth: {suggest.forth + }, filePath: {suggest.filePath}, line: {suggest.line}, column: {suggest.column + }, doc: {suggest.doc}, quality: {suggest.quality}, prefix: {suggest.prefix})""" + +func collapseByIdentifier*(suggest: Suggest): string = + ## Function to create an identifier that can be used to remove duplicates in a list + fmt"{suggest.qualifiedPath[^1]}__{suggest.symKind.TSymKind}" + +func nimSymToLSPKind*(suggest: Suggest): CompletionItemKind = + case suggest.symKind.TSymKind: + of skConst: CompletionItemKind.Value + of skEnumField: CompletionItemKind.Enum + of skForVar: CompletionItemKind.Variable + of skIterator: CompletionItemKind.Keyword + of skLabel: CompletionItemKind.Keyword + of skLet: CompletionItemKind.Value + of skMacro: CompletionItemKind.Snippet + of skMethod: CompletionItemKind.Method + of skParam: CompletionItemKind.Variable + of skProc: CompletionItemKind.Function + of skResult: CompletionItemKind.Value + of skTemplate: CompletionItemKind.Snippet + of skType: CompletionItemKind.Class + of skVar: CompletionItemKind.Field + of skFunc: CompletionItemKind.Function + else: CompletionItemKind.Property + +func nimSymToLSPKind*(suggest: byte): SymbolKind = + case TSymKind(suggest): + of skConst: SymbolKind.Constant + of skEnumField: SymbolKind.EnumMember + of skIterator: SymbolKind.Function + of skConverter: SymbolKind.Function + of skLet: SymbolKind.Variable + of skMacro: SymbolKind.Function + of skMethod: SymbolKind.Method + of skProc: SymbolKind.Function + of skTemplate: SymbolKind.Function + of skType: SymbolKind.Class + of skVar: SymbolKind.Variable + of skFunc: SymbolKind.Function + else: SymbolKind.Function + +func nimSymDetails*(suggest: Suggest): string = + case suggest.symKind.TSymKind: + of skConst: fmt"""const {suggest.qualifiedPath.join(".")}: {suggest.forth}""" + of skEnumField: "enum " & suggest.forth + of skForVar: "for var of " & suggest.forth + of skIterator: suggest.forth + of skLabel: "label" + of skLet: "let of " & suggest.forth + of skMacro: "macro" + of skMethod: suggest.forth + of skParam: "param" + of skProc: suggest.forth + of skResult: "result" + of skTemplate: suggest.forth + of skType: "type " & suggest.qualifiedPath.join(".") + of skVar: "var of " & suggest.forth + else: suggest.forth + + +template createFullCommand(command: untyped) = + proc command*(nimsuggest: NimSuggest, file: string, dirtyfile = "", + line: int, col: int): seq[Suggest] = + nimsuggest.runCmd(`ide command`, AbsoluteFile file, AbsoluteFile dirtyfile, line, col) + +template createFileOnlyCommand(command: untyped) = + proc command*(nimsuggest: NimSuggest, file: string, dirtyfile = ""): seq[Suggest] = + nimsuggest.runCmd(`ide command`, AbsoluteFile file, AbsoluteFile dirtyfile, 0, 0) + +createFullCommand(sug) +createFullCommand(con) +createFullCommand(def) +createFullCommand(use) +createFullCommand(dus) +createFileOnlyCommand(chk) +createFileOnlyCommand(highlight) +createFileOnlyCommand(outline) +createFileOnlyCommand(known) + +proc `mod`*(nimsuggest: NimSuggest, file: string, dirtyfile = ""): seq[Suggest] = + nimsuggest.runCmd(ideMod, AbsoluteFile file, AbsoluteFile dirtyfile, 0, 0) + +when isMainModule: + import os, sequtils, algorithm + const PkgDir = currentSourcePath.parentDir.parentDir + var graph = initNimSuggest(PkgDir / "nimlsp.nim", nimPath = PkgDir.parentDir) + var files = toSeq(walkDirRec(PkgDir)).filterIt(it.endsWith(".nim")).sorted() + for f in files: + echo "outline:" & f + let syms = graph.outline(f, f) + echo "outline: symbols(" & $syms.len & ")" + + var suggestions = graph.sug(currentSourcePath, currentSourcePath, 86, 16) + echo "Got ", suggestions.len, " suggestions" + for suggestion in suggestions: + echo suggestion \ No newline at end of file diff --git a/nimlsp/nimlsppkg/suggestlib.nim.cfg b/nimlsp/nimlsppkg/suggestlib.nim.cfg new file mode 100644 index 00000000000..89f43620c13 --- /dev/null +++ b/nimlsp/nimlsppkg/suggestlib.nim.cfg @@ -0,0 +1,20 @@ +hint[XDeclaredButNotUsed]:off + +path:"$lib/packages/docutils" +path:"$config/.." +path:"../.." +define:useStdoutAsStdmsg +define:nimsuggest +define:nimcore + +# die when nimsuggest uses more than 4GB: +@if cpu32: + define:"nimMaxHeap=2000" +@else: + define:"nimMaxHeap=4000" +@end + +--threads:off +--warning[Spacing]:off # The JSON schema macro uses a syntax similar to TypeScript +# --warning[CaseTransition]:off +-d:nimOldCaseObjects diff --git a/nimlsp/nimlsppkg/utfmapping.nim b/nimlsp/nimlsppkg/utfmapping.nim new file mode 100644 index 00000000000..818a060aa8d --- /dev/null +++ b/nimlsp/nimlsppkg/utfmapping.nim @@ -0,0 +1,73 @@ +import std/unicode + + +type FingerTable = seq[tuple[u16pos, offset: int]] + +proc createUTFMapping*(line: string): FingerTable = + var pos = 0 + for rune in line.runes: + #echo pos + #echo rune.int32 + case rune.int32: + of 0x0000..0x007F: + # One UTF-16 unit, one UTF-8 unit + pos += 1 + of 0x0080..0x07FF: + # One UTF-16 unit, two UTF-8 units + result.add (u16pos: pos, offset: 1) + pos += 1 + of 0x0800..0xFFFF: + # One UTF-16 unit, three UTF-8 units + result.add (u16pos: pos, offset: 2) + pos += 1 + of 0x10000..0x10FFFF: + # Two UTF-16 units, four UTF-8 units + result.add (u16pos: pos, offset: 2) + pos += 2 + else: discard + + #echo fingerTable + +proc utf16to8*(fingerTable: FingerTable, utf16pos: int): int = + result = utf16pos + for finger in fingerTable: + if finger.u16pos < utf16pos: + result += finger.offset + else: + break + +when isMainModule: + import termstyle + var x = "heållo☀☀wor𐐀𐐀☀ld heållo☀wor𐐀ld heållo☀wor𐐀ld" + var fingerTable = populateUTFMapping(x) + + var corrected = utf16to8(fingerTable, 5) + for y in x: + if corrected == 0: + echo "-" + if ord(y) > 125: + echo ord(y).red + else: + echo ord(y) + corrected -= 1 + + echo "utf16\tchar\tutf8\tchar\tchk" + var pos = 0 + for c in x.runes: + stdout.write pos + stdout.write '\t' + stdout.write c + stdout.write '\t' + var corrected = utf16to8(fingerTable, pos) + stdout.write corrected + stdout.write '\t' + stdout.write x.runeAt(corrected) + if c.int32 == x.runeAt(corrected).int32: + stdout.write "\tOK".green + else: + stdout.write "\tERR".red + stdout.write '\n' + if c.int >= 0x10000: + pos += 2 + else: + pos += 1 diff --git a/nimlsp/tests/nim.cfg b/nimlsp/tests/nim.cfg new file mode 100644 index 00000000000..28cc8e2ac15 --- /dev/null +++ b/nimlsp/tests/nim.cfg @@ -0,0 +1 @@ +--path:"../" \ No newline at end of file diff --git a/nimlsp/tests/test_messages2.nim b/nimlsp/tests/test_messages2.nim new file mode 100644 index 00000000000..ec84a643837 --- /dev/null +++ b/nimlsp/tests/test_messages2.nim @@ -0,0 +1,38 @@ +import std / [unittest] +include nimlsppkg / [messages, messageenums] + +let message = "Hello World" + +suite "Create ResponseError": + test "Generate a parse error message": + let error = JsonNode(create(ResponseError, ParseError.ord, message, + newJNull())) + check(getInt(error["code"]) == ord(ParseError)) + check(getStr(error["message"]) == message) + +suite "Create ResponseMessage": + test "Generate a response": + let res = create(ResponseMessage, "2.0", 100, some(%*{"result": "Success"}), + none(ResponseError)) + check(getStr(res["result"].unsafeGet()["result"]) == "Success") + test "Generate an error response": + let response = create(ResponseMessage, "2.0", 101, none(JsonNode), some( + create(ResponseError, ParseError.ord, message, newJNull()))) + check(getInt(response["id"]) == 101) + check(getInt(response["error"].unsafeGet()["code"]) == ord(ParseError)) + +suite "Read RequestMessage": + const requestMessage = """{ + "jsonrpc": "2.0", + "id": 100, + "method": "something", + }""" + const notificationMessage = """{ + "jsonrpc": "2.0", + "method": "something", + }""" + test "Verify RequestMessage": + check(parseJson(requestMessage).isValid(RequestMessage)) + test "Verify NotificationMessage": + check(parseJson(notificationMessage).isValid(NotificationMessage)) + diff --git a/nimsuggest/tests/tinclude.nim b/nimsuggest/tests/tinclude.nim index b67440b9e39..1025eb52810 100644 --- a/nimsuggest/tests/tinclude.nim +++ b/nimsuggest/tests/tinclude.nim @@ -9,6 +9,7 @@ proc go() = go() discard """ +disabled:true $nimsuggest --tester $file >def $path/tinclude.nim:7:14 def;;skProc;;minclude_import.create;;proc (greeting: string, subject: string): Greet{.noSideEffect, gcsafe, locks: 0.};;*fixtures/minclude_include.nim;;3;;5;;"";;100 From b10946c7307a0160421e876694ec4ed7e2faed77 Mon Sep 17 00:00:00 2001 From: bung87 Date: Mon, 11 Sep 2023 10:41:50 +0800 Subject: [PATCH 02/84] use MarkupContent for hover --- nimlsp/nimlsp.nim | 39 +++++++++++++++++++++++++---------- nimlsp/nimlsppkg/messages.nim | 2 +- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index 2f3c9bc4837..c485918b359 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -212,6 +212,32 @@ proc checkVersion(outs: Stream) = if version != NimVersion: outs.notify("window/showMessage", create(ShowMessageParams, MessageType.Warning.int, message = "Current Nim version does not match the one NimLSP is built against " & version & " != " & NimVersion).JsonNode) +proc createMarkupContent(label: string; content: string): MarkupContent = + let label = "```nim\n" & label & "\n```\n" + var + c: string + isCodeBlock = false + const BlockStart = ".. code-block::" + const BlockLen = BlockStart.len + 1 + for line in splitLines(content, true): + let isCodeBlockStart = line.startsWith(BlockStart) + if isCodeBlockStart: + isCodeBlock = true + if line.endsWith("Nim\n") or line.endsWith("nim\n") or + line.len == BlockLen: + c.add "```nim\n" + else: + c.add "```\n" + elif isCodeBlock and line.strip() == "": + c.add "```\n" + isCodeBlock = false + else: + c.add line + if isCodeBlock: + # single code block and ends without trailing line + c.add "```\n" + result = create(MarkupContent, "markdown", label & c) + proc main(ins: Stream, outs: Stream) = checkVersion(outs) var message: JsonNode @@ -344,17 +370,8 @@ proc main(ins: Stream, outs: Stream) = create(Position, req.rawLine, req.rawChar), create(Position, req.rawLine, req.rawChar + suggestions[0].qualifiedPath[^1].len) )) - markedString = create(MarkedStringOption, "nim", label) - if suggestions[0].doc != "": - resp = create(Hover, - @[ - markedString, - create(MarkedStringOption, "", suggestions[0].doc), - ], - rangeopt - ).JsonNode - else: - resp = create(Hover, markedString, rangeopt).JsonNode; + markupContent = createMarkupContent(label, suggestions[0].doc) + resp = create(Hover, markupContent, rangeopt).JsonNode; outs.respond(message, resp) of "textDocument/references": textDocumentRequest(message, ReferenceParams, req): diff --git a/nimlsp/nimlsppkg/messages.nim b/nimlsp/nimlsppkg/messages.nim index e6ed66f3456..cf57e4e4440 100644 --- a/nimlsp/nimlsppkg/messages.nim +++ b/nimlsp/nimlsppkg/messages.nim @@ -468,7 +468,7 @@ jsonSchema: triggerCharacters ?: string[] resolveProvider ?: bool - MarkedStringOption: + MarkedStringOption: # deprecated language: string value: string From acb0222c126c9ae0f3b41cbe5dfe0b0c5b3a64e2 Mon Sep 17 00:00:00 2001 From: bung87 Date: Mon, 11 Sep 2023 13:46:17 +0800 Subject: [PATCH 03/84] fix indent problem --- nimlsp/nimlsp.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index c485918b359..72f3d8750a6 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -371,8 +371,8 @@ proc main(ins: Stream, outs: Stream) = create(Position, req.rawLine, req.rawChar + suggestions[0].qualifiedPath[^1].len) )) markupContent = createMarkupContent(label, suggestions[0].doc) - resp = create(Hover, markupContent, rangeopt).JsonNode; - outs.respond(message, resp) + resp = create(Hover, markupContent, rangeopt).JsonNode + outs.respond(message, resp) of "textDocument/references": textDocumentRequest(message, ReferenceParams, req): debugLog "Running equivalent of: use ", req.fileuri, " ", req.filestash, "(", From 3fca2bcead3e621c4cec2092e5dd376eda851bee Mon Sep 17 00:00:00 2001 From: bung87 Date: Mon, 11 Sep 2023 16:48:33 +0800 Subject: [PATCH 04/84] handle admonition labels in Suggest.doc --- nimlsp/nimlsp.nim | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index 72f3d8750a6..c2336c6d2d7 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -236,6 +236,19 @@ proc createMarkupContent(label: string; content: string): MarkupContent = if isCodeBlock: # single code block and ends without trailing line c.add "```\n" + # admonition labels + c = multiReplace(c, + (".. attention::", "**attention**"), + (".. caution::", "**caution**"), + (".. danger::", "**danger**"), + (".. error::", "**error**"), + (".. hint::", "**hint**"), + (".. important::", "**important**"), + (".. note::", "**note**"), + (".. seealso::", "**seealso**"), + (".. tip::", "**tip**"), + (".. warning::", "**warning**"), + ) result = create(MarkupContent, "markdown", label & c) proc main(ins: Stream, outs: Stream) = From 3bc0ad10f6e91527f96a0162c328b7b1b4981799 Mon Sep 17 00:00:00 2001 From: bung87 Date: Mon, 11 Sep 2023 18:52:07 +0800 Subject: [PATCH 05/84] refactor --- nimlsp/nimlsp.nim | 114 ++++++++---------------------- nimlsp/nimlsppkg/baseprotocol.nim | 55 +++++++++++++- 2 files changed, 84 insertions(+), 85 deletions(-) diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index c2336c6d2d7..c0b147356b8 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -1,5 +1,5 @@ import std/[algorithm, hashes, os, osproc, sets, - streams, strformat, strutils, tables, uri] + streams, strformat, strutils, tables] import nimlsppkg/[baseprotocol, logger, suggestlib, utfmapping] include nimlsppkg/[messages, messageenums] @@ -19,17 +19,7 @@ const # This is used to explicitly set the default source path explicitSourcePath {.strdefine.} = getCurrentCompilerExe().parentDir.parentDir -type - UriParseError* = object of Defect - uri: string - var nimpath = explicitSourcePath - -infoLog("Version: ", version) -infoLog("explicitSourcePath: ", explicitSourcePath) -for i in 1..paramCount(): - infoLog("Argument ", i, ": ", paramStr(i)) - var gotShutdown = false initialized = false @@ -73,55 +63,6 @@ template textDocumentNotification(message: typed; kind: typed; name, body: untyp else: debugLog("Unable to parse data as ", kind) -proc pathToUri(path: string): string = - # This is a modified copy of encodeUrl in the uri module. This doesn't encode - # the / character, meaning a full file path can be passed in without breaking - # it. - result = newStringOfCap(path.len + path.len shr 2) # assume 12% non-alnum-chars - when defined(windows): - result.add '/' - for c in path: - case c - # https://tools.ietf.org/html/rfc3986#section-2.3 - of 'a'..'z', 'A'..'Z', '0'..'9', '-', '.', '_', '~', '/': result.add c - of '\\': - when defined(windows): - result.add '/' - else: - result.add '%' - result.add toHex(ord(c), 2) - else: - result.add '%' - result.add toHex(ord(c), 2) - -proc uriToPath(uri: string): string = - ## Convert an RFC 8089 file URI to a native, platform-specific, absolute path. - #let startIdx = when defined(windows): 8 else: 7 - #normalizedPath(uri[startIdx..^1]) - let parsed = uri.parseUri - if parsed.scheme != "file": - var e = newException(UriParseError, &"Invalid scheme: {parsed.scheme}, only \"file\" is supported") - e.uri = uri - raise e - if parsed.hostname != "": - var e = newException(UriParseError, &"Invalid hostname: {parsed.hostname}, only empty hostname is supported") - e.uri = uri - raise e - return normalizedPath( - when defined(windows): - parsed.path[1..^1] - else: - parsed.path).decodeUrl - -proc parseId(node: JsonNode): string = - if node == nil: return - if node.kind == JString: - node.getStr - elif node.kind == JInt: - $node.getInt - else: - "" - proc respond(outs: Stream, request: JsonNode, data: JsonNode) = let resp = create(ResponseMessage, "2.0", parseId(request["id"]), some(data), none(ResponseError)).JsonNode outs.sendJson resp @@ -178,26 +119,6 @@ proc getProjectFile(file: string): string = template getNimsuggest(fileuri: string): Nimsuggest = projectFiles[openFiles[fileuri].projectFile].nimsuggest -if paramCount() == 1: - case paramStr(1): - of "--help": - echo "Usage: nimlsp [OPTION | PATH]\n" - echo "--help, shows this message" - echo "--version, shows only the version" - echo "PATH, path to the Nim source directory, defaults to \"", nimpath, "\"" - quit 0 - of "--version": - echo "nimlsp v", version - when defined(debugLogging): echo "Compiled with debug logging" - when defined(debugCommunication): echo "Compiled with communication logging" - quit 0 - else: nimpath = expandFilename(paramStr(1)) -if not fileExists(nimpath / "config/nim.cfg"): - stderr.write &"""Unable to find "config/nim.cfg" in "{nimpath - }". Supply the Nim project folder by adding it as an argument. -""" - quit 1 - proc checkVersion(outs: Stream) = let nimoutputTuple = @@ -694,7 +615,32 @@ proc main(ins: Stream, outs: Stream) = warnLog "Got exception: ", e.msg continue -var - ins = newFileStream(stdin) - outs = newFileStream(stdout) -main(ins, outs) +when isMainModule: + infoLog("Version: ", version) + infoLog("explicitSourcePath: ", explicitSourcePath) + for i in 1..paramCount(): + infoLog("Argument ", i, ": ", paramStr(i)) + if paramCount() == 1: + case paramStr(1): + of "--help": + echo "Usage: nimlsp [OPTION | PATH]\n" + echo "--help, shows this message" + echo "--version, shows only the version" + echo "PATH, path to the Nim source directory, defaults to \"", nimpath, "\"" + quit 0 + of "--version": + echo "nimlsp v", version + when defined(debugLogging): echo "Compiled with debug logging" + when defined(debugCommunication): echo "Compiled with communication logging" + quit 0 + else: nimpath = expandFilename(paramStr(1)) + if not fileExists(nimpath / "config/nim.cfg"): + stderr.write &"""Unable to find "config/nim.cfg" in "{nimpath + }". Supply the Nim project folder by adding it as an argument. + """ + quit 1 + + var + ins = newFileStream(stdin) + outs = newFileStream(stdout) + main(ins, outs) diff --git a/nimlsp/nimlsppkg/baseprotocol.nim b/nimlsp/nimlsppkg/baseprotocol.nim index 7fe61aed293..b0ddcff72e2 100644 --- a/nimlsp/nimlsppkg/baseprotocol.nim +++ b/nimlsp/nimlsppkg/baseprotocol.nim @@ -1,5 +1,6 @@ import std/[json, parseutils, streams, strformat, - strutils] + strutils, os] +from std/uri import decodeUrl, parseUri when defined(debugCommunication): import logger @@ -9,6 +10,58 @@ type MalformedFrame* = object of BaseProtocolError UnsupportedEncoding* = object of BaseProtocolError + UriParseError* = object of Defect + uri*: string + +proc pathToUri*(path: string): string = + # This is a modified copy of encodeUrl in the uri module. This doesn't encode + # the / character, meaning a full file path can be passed in without breaking + # it. + result = newStringOfCap(path.len + path.len shr 2) # assume 12% non-alnum-chars + when defined(windows): + result.add '/' + for c in path: + case c + # https://tools.ietf.org/html/rfc3986#section-2.3 + of 'a'..'z', 'A'..'Z', '0'..'9', '-', '.', '_', '~', '/': result.add c + of '\\': + when defined(windows): + result.add '/' + else: + result.add '%' + result.add toHex(ord(c), 2) + else: + result.add '%' + result.add toHex(ord(c), 2) + +proc uriToPath*(uri: string): string = + ## Convert an RFC 8089 file URI to a native, platform-specific, absolute path. + #let startIdx = when defined(windows): 8 else: 7 + #normalizedPath(uri[startIdx..^1]) + let parsed = parseUri(uri) + if parsed.scheme != "file": + var e = newException(UriParseError, &"Invalid scheme: {parsed.scheme}, only \"file\" is supported") + e.uri = uri + raise e + if parsed.hostname != "": + var e = newException(UriParseError, &"Invalid hostname: {parsed.hostname}, only empty hostname is supported") + e.uri = uri + raise e + return normalizedPath( + when defined(windows): + parsed.path[1..^1] + else: + parsed.path).decodeUrl + +proc parseId*(node: JsonNode): string = + if node == nil: return + if node.kind == JString: + node.getStr + elif node.kind == JInt: + $node.getInt + else: + "" + proc skipWhitespace(x: string, pos: int): int = result = pos while result < x.len and x[result] in Whitespace: From e1e621fffbc54b730a1328a462797dc4704e9454 Mon Sep 17 00:00:00 2001 From: bung87 Date: Tue, 12 Sep 2023 03:17:46 +0800 Subject: [PATCH 06/84] use DocumentSymbol for outline --- compiler/tools/suggest.nim | 8 ++++---- nimlsp/nimlsp.nim | 21 +++++++++++---------- nimlsp/nimlsppkg/messageenums.nim | 3 +++ nimlsp/nimlsppkg/messages.nim | 9 +++++++++ nimlsp/nimlsppkg/nimsuggest.nim | 4 +++- 5 files changed, 30 insertions(+), 15 deletions(-) diff --git a/compiler/tools/suggest.nim b/compiler/tools/suggest.nim index 9945872a729..7ca64a2123f 100644 --- a/compiler/tools/suggest.nim +++ b/compiler/tools/suggest.nim @@ -194,10 +194,10 @@ proc symToSuggest(g: ModuleGraph; s: PSym, isLocal: bool, section: IdeCmd, info: result.filePath = toFullPath(g.config, infox) result.line = toLinenumber(infox) result.column = toColumn(infox) - result.tokenLen = if section != ideHighlight: - s.name.s.len - else: - getTokenLenFromSource(g.config, s.name.s, infox) + if section in {ideHighlight}: + result.tokenLen = getTokenLenFromSource(g.config, s.name.s, infox) + else: + result.tokenLen = s.name.s.len proc `$`*(suggest: Suggest): string = result = $suggest.section diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index c0b147356b8..3a00b097683 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -410,19 +410,20 @@ proc main(ins: Stream, outs: Stream) = for sym in syms.sortedByIt((it.line,it.column,it.quality)): if sym.qualifiedPath.len != 2: continue - resp.add create( - SymbolInformation, + resp.add create(DocumentSymbol, sym.qualifiedPath[^1], + some(sym.forth), nimSymToLSPKind(sym.symKind).int, - some(false), - create(Location, - "file://" & pathToUri(sym.filepath), - create(Range, + none(seq[int]), + create(Range, create(Position, sym.line-1, sym.column), - create(Position, sym.line-1, sym.column + sym.qualifiedPath[^1].len) - ) - ), - none(string) + create(Position, sym.line-1, sym.column + sym.tokenLen) + ), + create(Range, + create(Position, sym.line-1, sym.column), + create(Position, sym.line-1, sym.column + sym.tokenLen) + ), + none(seq[DocumentSymbol]) ).JsonNode outs.respond(message, resp) of "textDocument/signatureHelp": diff --git a/nimlsp/nimlsppkg/messageenums.nim b/nimlsp/nimlsppkg/messageenums.nim index 63b0e34867f..81433dab994 100644 --- a/nimlsp/nimlsppkg/messageenums.nim +++ b/nimlsp/nimlsppkg/messageenums.nim @@ -112,3 +112,6 @@ type Text = 1, Read = 2, Write = 3 + + SymbolTag* {.pure.} = enum + Deprecated = 1 diff --git a/nimlsp/nimlsppkg/messages.nim b/nimlsp/nimlsppkg/messages.nim index cf57e4e4440..9a046a642e0 100644 --- a/nimlsp/nimlsppkg/messages.nim +++ b/nimlsp/nimlsppkg/messages.nim @@ -513,6 +513,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 diff --git a/nimlsp/nimlsppkg/nimsuggest.nim b/nimlsp/nimlsppkg/nimsuggest.nim index 6fc4d2e1971..a528e24635b 100644 --- a/nimlsp/nimlsppkg/nimsuggest.nim +++ b/nimlsp/nimlsppkg/nimsuggest.nim @@ -175,11 +175,12 @@ proc parsedNodeToSugget(n: ParsedNode; moduleName: string): Suggest = if n.kind in {pnkError, pnkEmpty}: return if n.kind notin {pnkConstSection..pnkTypeDef, pnkIdentDefs}: return new(result) - let token = getToken(n) + var token = getToken(n) var name = "" if n.kind in {pnkProcDef..pnkTypeDef, pnkIdentDefs}: var node: ParsedNode = getSymNode(n[0]) + token = getToken(node) if node.kind != pnkError: name = getName(node) @@ -187,6 +188,7 @@ proc parsedNodeToSugget(n: ParsedNode; moduleName: string): Suggest = result.qualifiedPath = @[moduleName, name] result.line = token.line.int result.column = token.col.int + result.tokenLen = name.len result.symkind = byte pnkToSymKind(n.kind) proc outline(graph: ModuleGraph; file: AbsoluteFile; fileIdx: FileIndex) = From d8eb25174ed8f6da2829e5404268371ec3a1ff9c Mon Sep 17 00:00:00 2001 From: bung87 Date: Tue, 12 Sep 2023 03:36:43 +0800 Subject: [PATCH 07/84] add license and remove nimble file --- nimlsp/LICENSE | 21 +++++++++++++++++++++ nimlsp/nimlsp.nim | 18 +----------------- nimlsp/nimlsp.nimble | 25 ------------------------- 3 files changed, 22 insertions(+), 42 deletions(-) create mode 100644 nimlsp/LICENSE delete mode 100644 nimlsp/nimlsp.nimble diff --git a/nimlsp/LICENSE b/nimlsp/LICENSE new file mode 100644 index 00000000000..4ba6bd42bff --- /dev/null +++ b/nimlsp/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 PMunch + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index 3a00b097683..e667a073f37 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -6,16 +6,6 @@ include nimlsppkg/[messages, messageenums] const - version = block: - var version = "0.0.0" - let nimbleFile = staticRead(currentSourcePath().parentDir / "nimlsp.nimble") - for line in nimbleFile.splitLines: - let keyval = line.split('=') - if keyval.len == 2: - if keyval[0].strip == "version": - version = keyval[1].strip(chars = Whitespace + {'"'}) - break - version # This is used to explicitly set the default source path explicitSourcePath {.strdefine.} = getCurrentCompilerExe().parentDir.parentDir @@ -617,7 +607,6 @@ proc main(ins: Stream, outs: Stream) = continue when isMainModule: - infoLog("Version: ", version) infoLog("explicitSourcePath: ", explicitSourcePath) for i in 1..paramCount(): infoLog("Argument ", i, ": ", paramStr(i)) @@ -626,14 +615,9 @@ when isMainModule: of "--help": echo "Usage: nimlsp [OPTION | PATH]\n" echo "--help, shows this message" - echo "--version, shows only the version" echo "PATH, path to the Nim source directory, defaults to \"", nimpath, "\"" quit 0 - of "--version": - echo "nimlsp v", version - when defined(debugLogging): echo "Compiled with debug logging" - when defined(debugCommunication): echo "Compiled with communication logging" - quit 0 + else: nimpath = expandFilename(paramStr(1)) if not fileExists(nimpath / "config/nim.cfg"): stderr.write &"""Unable to find "config/nim.cfg" in "{nimpath diff --git a/nimlsp/nimlsp.nimble b/nimlsp/nimlsp.nimble deleted file mode 100644 index 77414e31cd2..00000000000 --- a/nimlsp/nimlsp.nimble +++ /dev/null @@ -1,25 +0,0 @@ -# Package - -version = "0.4.4" -author = "PMunch" -description = "Nim Language Server Protocol - nimlsp implements the Language Server Protocol" -license = "MIT" -srcDir = "src" -bin = @["nimlsp", "nimlsp_debug"] - -# Dependencies - -# nimble test does not work for me out of the box -#task test, "Runs the test suite": - #exec "nim c -r tests/test_messages.nim" -# exec "nim c -d:debugLogging -d:jsonSchemaDebug -r tests/test_messages2.nim" - -task debug, "Builds the language server": - exec "nim c --threads:on -d:nimcore -d:nimsuggest -d:debugCommunication -d:debugLogging -o:nimlsp src/nimlsp" - -before test: - exec "nimble build" - -task findNim, "Tries to find the current Nim installation": - echo NimVersion - echo currentSourcePath From 2cfafea86fad89f1e2b510842eeaa3cbbff4f475 Mon Sep 17 00:00:00 2001 From: bung87 Date: Tue, 12 Sep 2023 22:37:20 +0800 Subject: [PATCH 08/84] Suggest add flags field, only care deprecated pragma for now --- compiler/front/options.nim | 4 ++++ nimlsp/nimlsp.nim | 6 +++++- nimlsp/nimlsppkg/nimsuggest.nim | 15 +++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/compiler/front/options.nim b/compiler/front/options.nim index 44107b86ae8..ab55da46f46 100644 --- a/compiler/front/options.nim +++ b/compiler/front/options.nim @@ -140,6 +140,9 @@ type CfileList* = seq[Cfile] + SuggestFlag* {.pure.} = enum + deprecated = 1 + Suggest* = ref object section*: IdeCmd qualifiedPath*: seq[string] @@ -158,6 +161,7 @@ type scope*:int localUsages*, globalUsages*: int # usage counters tokenLen*: int + flags*: set[SuggestFlag] Suggestions* = seq[Suggest] diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index e667a073f37..6f7fa868775 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -393,6 +393,7 @@ proc main(ins: Stream, outs: Stream) = debugLog "Found outlines: ", syms[0.. 10: &" and {syms.len-10} more" else: "" var resp: JsonNode + var flags = newSeq[int]() if syms.len == 0: resp = newJNull() else: @@ -400,11 +401,14 @@ proc main(ins: Stream, outs: Stream) = for sym in syms.sortedByIt((it.line,it.column,it.quality)): if sym.qualifiedPath.len != 2: continue + flags.setLen(0) + for f in sym.flags: + flags.add f.int resp.add create(DocumentSymbol, sym.qualifiedPath[^1], some(sym.forth), nimSymToLSPKind(sym.symKind).int, - none(seq[int]), + some(flags), create(Range, create(Position, sym.line-1, sym.column), create(Position, sym.line-1, sym.column + sym.tokenLen) diff --git a/nimlsp/nimlsppkg/nimsuggest.nim b/nimlsp/nimlsppkg/nimsuggest.nim index a528e24635b..04120afd2cc 100644 --- a/nimlsp/nimlsppkg/nimsuggest.nim +++ b/nimlsp/nimlsppkg/nimsuggest.nim @@ -171,6 +171,16 @@ proc getName(node: ParsedNode): string = result.add t.ident.s result.add "`" +proc processFlags(sug: Suggest; n: ParsedNode) = + var + identDeprecated: bool + colonDeprecated: bool + for s in n.sons: + identDeprecated = s.kind == pnkIdent and getName(s) == "deprecated" + colonDeprecated = s.kind == pnkExprColonExpr and getName(s[0]) == "deprecated" + if identDeprecated or colonDeprecated: + sug.flags.incl SuggestFlag.deprecated + proc parsedNodeToSugget(n: ParsedNode; moduleName: string): Suggest = if n.kind in {pnkError, pnkEmpty}: return if n.kind notin {pnkConstSection..pnkTypeDef, pnkIdentDefs}: return @@ -179,6 +189,11 @@ proc parsedNodeToSugget(n: ParsedNode; moduleName: string): Suggest = var name = "" if n.kind in {pnkProcDef..pnkTypeDef, pnkIdentDefs}: + if n.kind in pnkRoutineDefs and n[pragmasPos].kind == pnkPragma: + processFlags(result, n[pragmasPos]) + elif n[0].kind == pnkPragmaExpr and n[0][^1].kind == pnkPragma: + processFlags(result, n[0][^1]) + var node: ParsedNode = getSymNode(n[0]) token = getToken(node) if node.kind != pnkError: From 700b15d16fb901413279a4d389bd2a1d1de5978e Mon Sep 17 00:00:00 2001 From: bung87 Date: Wed, 13 Sep 2023 06:03:52 +0800 Subject: [PATCH 09/84] special cases for quoted procs --- nimlsp/nimlsppkg/nimsuggest.nim | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/nimlsp/nimlsppkg/nimsuggest.nim b/nimlsp/nimlsppkg/nimsuggest.nim index 04120afd2cc..9da9b85ace9 100644 --- a/nimlsp/nimlsppkg/nimsuggest.nim +++ b/nimlsp/nimlsppkg/nimsuggest.nim @@ -198,6 +198,13 @@ proc parsedNodeToSugget(n: ParsedNode; moduleName: string): Suggest = token = getToken(node) if node.kind != pnkError: name = getName(node) + # special cases + # if n.kind in pnkRoutineDefs and node.kind == pnkAccQuoted: + # let identsLen = n[paramsPos].sons.len + # for i in countup(1, identsLen - 1): + # name.add getName(n[paramsPos][i][1]) + # if i != identsLen - 1: + # name.add "," if name != "": result.qualifiedPath = @[moduleName, name] From e697a89d78c24af1a1404ceb2191c4e1618625fe Mon Sep 17 00:00:00 2001 From: bung87 Date: Wed, 13 Sep 2023 06:25:06 +0800 Subject: [PATCH 10/84] outline no need filename param --- nimlsp/nimlsppkg/nimsuggest.nim | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/nimlsp/nimlsppkg/nimsuggest.nim b/nimlsp/nimlsppkg/nimsuggest.nim index 9da9b85ace9..d11293236c2 100644 --- a/nimlsp/nimlsppkg/nimsuggest.nim +++ b/nimlsp/nimlsppkg/nimsuggest.nim @@ -181,7 +181,7 @@ proc processFlags(sug: Suggest; n: ParsedNode) = if identDeprecated or colonDeprecated: sug.flags.incl SuggestFlag.deprecated -proc parsedNodeToSugget(n: ParsedNode; moduleName: string): Suggest = +proc parsedNodeToSugget(n: ParsedNode; module: PSym): Suggest = if n.kind in {pnkError, pnkEmpty}: return if n.kind notin {pnkConstSection..pnkTypeDef, pnkIdentDefs}: return new(result) @@ -207,24 +207,24 @@ proc parsedNodeToSugget(n: ParsedNode; moduleName: string): Suggest = # name.add "," if name != "": - result.qualifiedPath = @[moduleName, name] + result.qualifiedPath = @[module.name.s, name] result.line = token.line.int result.column = token.col.int result.tokenLen = name.len result.symkind = byte pnkToSymKind(n.kind) -proc outline(graph: ModuleGraph; file: AbsoluteFile; fileIdx: FileIndex) = +proc outline(graph: ModuleGraph; fileIdx: FileIndex) = let conf = graph.config var parser: Parser var sug: Suggest var parsedNode: ParsedNode var s: ParsedNode - let m = splitFile(file.string) + let m = graph.getModule fileIdx const Sections = {pnkTypeSection, pnkConstSection, pnkLetSection, pnkVarSection} template suggestIt(parsedNode: ParsedNode) = - sug = parsedNodeToSugget(parsedNode, m.name) + sug = parsedNodeToSugget(parsedNode, m) if sug != nil: - sug.filepath = file.string + sug.filepath = toFullPath(conf, fileIdx) conf.suggestionResultHook(sug) if setupParser(parser, fileIdx, graph.cache, conf): while true: @@ -258,9 +258,8 @@ proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int, if conf.ideCmd in {ideUse, ideDus} and dirtyfile.isEmpty: needCompile = false - if conf.ideCmd == ideOutline: + if conf.ideCmd == ideOutline and isKnownFile: needCompile = false - outline(graph, file, dirtyIdx) if needCompile: if not isKnownFile: @@ -283,6 +282,8 @@ proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int, listUsages(graph, u) else: stderr.writeLine "found no symbol at position: " & (conf $ conf.m.trackPos) + elif conf.ideCmd == ideOutline: + outline(graph, dirtyIdx) proc runCmd*(nimsuggest: NimSuggest, cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int): seq[Suggest] = From 23861dc2b3ae0d560e059ec4918f315eb8780019 Mon Sep 17 00:00:00 2001 From: bung87 Date: Thu, 14 Sep 2023 03:17:36 +0800 Subject: [PATCH 11/84] fix conflicts --- compiler/tools/suggest.nim | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/tools/suggest.nim b/compiler/tools/suggest.nim index 7ca64a2123f..9255b57f33b 100644 --- a/compiler/tools/suggest.nim +++ b/compiler/tools/suggest.nim @@ -451,7 +451,6 @@ proc isTracked*(current, trackPos: TLineInfo, tokenLen: int): bool = if current.fileIndex == trackPos.fileIndex and current.line == trackPos.line: let col = trackPos.col - if col >= current.col and col <= current.col + tokenLen - 1: if col >= current.col and col <= current.col + tokenLen - 1: return true From 8b9db0c3327f35bb904fe1749ec09d023e9e9538 Mon Sep 17 00:00:00 2001 From: bung87 Date: Thu, 14 Sep 2023 15:21:02 +0800 Subject: [PATCH 12/84] outline fill symbol detail distinguish variant variables and functions --- nimlsp/nimlsp.nim | 2 +- nimlsp/nimlsppkg/nimsuggest.nim | 12 ++++++------ nimlsp/nimlsppkg/suggestlib.nim | 17 +++++++++++++++++ 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index 6f7fa868775..2bcd142f62c 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -406,7 +406,7 @@ proc main(ins: Stream, outs: Stream) = flags.add f.int resp.add create(DocumentSymbol, sym.qualifiedPath[^1], - some(sym.forth), + some(symKindToString(sym.symKind)), nimSymToLSPKind(sym.symKind).int, some(flags), create(Range, diff --git a/nimlsp/nimlsppkg/nimsuggest.nim b/nimlsp/nimlsppkg/nimsuggest.nim index d11293236c2..691f6fdd4d8 100644 --- a/nimlsp/nimlsppkg/nimsuggest.nim +++ b/nimlsp/nimlsppkg/nimsuggest.nim @@ -181,7 +181,7 @@ proc processFlags(sug: Suggest; n: ParsedNode) = if identDeprecated or colonDeprecated: sug.flags.incl SuggestFlag.deprecated -proc parsedNodeToSugget(n: ParsedNode; module: PSym): Suggest = +proc parsedNodeToSugget(n: ParsedNode; originKind: ParsedNodeKind; module: PSym): Suggest = if n.kind in {pnkError, pnkEmpty}: return if n.kind notin {pnkConstSection..pnkTypeDef, pnkIdentDefs}: return new(result) @@ -211,7 +211,7 @@ proc parsedNodeToSugget(n: ParsedNode; module: PSym): Suggest = result.line = token.line.int result.column = token.col.int result.tokenLen = name.len - result.symkind = byte pnkToSymKind(n.kind) + result.symkind = byte pnkToSymKind(originKind) proc outline(graph: ModuleGraph; fileIdx: FileIndex) = let conf = graph.config @@ -221,8 +221,8 @@ proc outline(graph: ModuleGraph; fileIdx: FileIndex) = var s: ParsedNode let m = graph.getModule fileIdx const Sections = {pnkTypeSection, pnkConstSection, pnkLetSection, pnkVarSection} - template suggestIt(parsedNode: ParsedNode) = - sug = parsedNodeToSugget(parsedNode, m) + template suggestIt(parsedNode: ParsedNode; originKind: ParsedNodeKind) = + sug = parsedNodeToSugget(parsedNode, originKind, m) if sug != nil: sug.filepath = toFullPath(conf, fileIdx) conf.suggestionResultHook(sug) @@ -234,9 +234,9 @@ proc outline(graph: ModuleGraph; fileIdx: FileIndex) = if parsedNode.kind in Sections: for node in parsedNode.sons: - suggestIt(node) + suggestIt(node, parsedNode.kind) else: - suggestIt(parsedNode) + suggestIt(parsedNode, parsedNode.kind) closeParser(parser) proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int, diff --git a/nimlsp/nimlsppkg/suggestlib.nim b/nimlsp/nimlsppkg/suggestlib.nim index fcee5fc023b..e687b590149 100644 --- a/nimlsp/nimlsppkg/suggestlib.nim +++ b/nimlsp/nimlsppkg/suggestlib.nim @@ -75,6 +75,23 @@ func nimSymDetails*(suggest: Suggest): string = of skVar: "var of " & suggest.forth else: suggest.forth +func symKindToString*(suggest: byte): string = + case TSymKind(suggest) + of skConst: discard + of skEnumField: discard + of skIterator: result = "iterator" + of skConverter: result = "converter" + of skLet: result = "let" + of skMacro: result = "macro" + of skMethod: result = "method" + of skProc: result = "proc" + of skTemplate: result = "template" + of skType: discard + of skVar: result = "var" + of skFunc: result = "func" + of skLabel: result = "label" + of skUnknown: result = "unkown" + else: result = "" template createFullCommand(command: untyped) = proc command*(nimsuggest: NimSuggest, file: string, dirtyfile = "", From 72f13e438728de65f994d99a022e6a9b5b4a9016 Mon Sep 17 00:00:00 2001 From: bung87 Date: Sun, 10 Sep 2023 03:11:24 +0800 Subject: [PATCH 13/84] tool: LSP support --- compiler/tools/suggest.nim | 1 + nimlsp/nimlsp.nim | 150 ++++++++++++++++++++++++++++++ nimlsp/nimlsp.nimble | 25 +++++ nimlsp/nimlsppkg/baseprotocol.nim | 7 ++ nimlsp/nimlsppkg/messageenums.nim | 3 + nimlsp/nimlsppkg/messages.nim | 7 ++ nimlsp/nimlsppkg/suggestlib.nim | 3 + 7 files changed, 196 insertions(+) create mode 100644 nimlsp/nimlsp.nimble diff --git a/compiler/tools/suggest.nim b/compiler/tools/suggest.nim index 9255b57f33b..7ca64a2123f 100644 --- a/compiler/tools/suggest.nim +++ b/compiler/tools/suggest.nim @@ -451,6 +451,7 @@ proc isTracked*(current, trackPos: TLineInfo, tokenLen: int): bool = if current.fileIndex == trackPos.fileIndex and current.line == trackPos.line: let col = trackPos.col + if col >= current.col and col <= current.col + tokenLen - 1: if col >= current.col and col <= current.col + tokenLen - 1: return true diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index 2bcd142f62c..9164e209c19 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -1,15 +1,46 @@ import std/[algorithm, hashes, os, osproc, sets, +<<<<<<< HEAD streams, strformat, strutils, tables] +======= + streams, strformat, strutils, tables, uri] +>>>>>>> 19a04042ad (tool: LSP support) import nimlsppkg/[baseprotocol, logger, suggestlib, utfmapping] include nimlsppkg/[messages, messageenums] const +<<<<<<< HEAD # This is used to explicitly set the default source path explicitSourcePath {.strdefine.} = getCurrentCompilerExe().parentDir.parentDir var nimpath = explicitSourcePath +======= + version = block: + var version = "0.0.0" + let nimbleFile = staticRead(currentSourcePath().parentDir / "nimlsp.nimble") + for line in nimbleFile.splitLines: + let keyval = line.split('=') + if keyval.len == 2: + if keyval[0].strip == "version": + version = keyval[1].strip(chars = Whitespace + {'"'}) + break + version + # This is used to explicitly set the default source path + explicitSourcePath {.strdefine.} = getCurrentCompilerExe().parentDir.parentDir + +type + UriParseError* = object of Defect + uri: string + +var nimpath = explicitSourcePath + +infoLog("Version: ", version) +infoLog("explicitSourcePath: ", explicitSourcePath) +for i in 1..paramCount(): + infoLog("Argument ", i, ": ", paramStr(i)) + +>>>>>>> 19a04042ad (tool: LSP support) var gotShutdown = false initialized = false @@ -53,6 +84,58 @@ template textDocumentNotification(message: typed; kind: typed; name, body: untyp else: debugLog("Unable to parse data as ", kind) +<<<<<<< HEAD +======= +proc pathToUri(path: string): string = + # This is a modified copy of encodeUrl in the uri module. This doesn't encode + # the / character, meaning a full file path can be passed in without breaking + # it. + result = newStringOfCap(path.len + path.len shr 2) # assume 12% non-alnum-chars + when defined(windows): + result.add '/' + for c in path: + case c + # https://tools.ietf.org/html/rfc3986#section-2.3 + of 'a'..'z', 'A'..'Z', '0'..'9', '-', '.', '_', '~', '/': result.add c + of '\\': + when defined(windows): + result.add '/' + else: + result.add '%' + result.add toHex(ord(c), 2) + else: + result.add '%' + result.add toHex(ord(c), 2) + +proc uriToPath(uri: string): string = + ## Convert an RFC 8089 file URI to a native, platform-specific, absolute path. + #let startIdx = when defined(windows): 8 else: 7 + #normalizedPath(uri[startIdx..^1]) + let parsed = uri.parseUri + if parsed.scheme != "file": + var e = newException(UriParseError, &"Invalid scheme: {parsed.scheme}, only \"file\" is supported") + e.uri = uri + raise e + if parsed.hostname != "": + var e = newException(UriParseError, &"Invalid hostname: {parsed.hostname}, only empty hostname is supported") + e.uri = uri + raise e + return normalizedPath( + when defined(windows): + parsed.path[1..^1] + else: + parsed.path).decodeUrl + +proc parseId(node: JsonNode): string = + if node == nil: return + if node.kind == JString: + node.getStr + elif node.kind == JInt: + $node.getInt + else: + "" + +>>>>>>> 19a04042ad (tool: LSP support) proc respond(outs: Stream, request: JsonNode, data: JsonNode) = let resp = create(ResponseMessage, "2.0", parseId(request["id"]), some(data), none(ResponseError)).JsonNode outs.sendJson resp @@ -109,6 +192,29 @@ proc getProjectFile(file: string): string = template getNimsuggest(fileuri: string): Nimsuggest = projectFiles[openFiles[fileuri].projectFile].nimsuggest +<<<<<<< HEAD +======= +if paramCount() == 1: + case paramStr(1): + of "--help": + echo "Usage: nimlsp [OPTION | PATH]\n" + echo "--help, shows this message" + echo "--version, shows only the version" + echo "PATH, path to the Nim source directory, defaults to \"", nimpath, "\"" + quit 0 + of "--version": + echo "nimlsp v", version + when defined(debugLogging): echo "Compiled with debug logging" + when defined(debugCommunication): echo "Compiled with communication logging" + quit 0 + else: nimpath = expandFilename(paramStr(1)) +if not fileExists(nimpath / "config/nim.cfg"): + stderr.write &"""Unable to find "config/nim.cfg" in "{nimpath + }". Supply the Nim project folder by adding it as an argument. +""" + quit 1 + +>>>>>>> 19a04042ad (tool: LSP support) proc checkVersion(outs: Stream) = let nimoutputTuple = @@ -123,6 +229,7 @@ proc checkVersion(outs: Stream) = if version != NimVersion: outs.notify("window/showMessage", create(ShowMessageParams, MessageType.Warning.int, message = "Current Nim version does not match the one NimLSP is built against " & version & " != " & NimVersion).JsonNode) +<<<<<<< HEAD proc createMarkupContent(label: string; content: string): MarkupContent = let label = "```nim\n" & label & "\n```\n" var @@ -162,6 +269,8 @@ proc createMarkupContent(label: string; content: string): MarkupContent = ) result = create(MarkupContent, "markdown", label & c) +======= +>>>>>>> 19a04042ad (tool: LSP support) proc main(ins: Stream, outs: Stream) = checkVersion(outs) var message: JsonNode @@ -294,9 +403,24 @@ proc main(ins: Stream, outs: Stream) = create(Position, req.rawLine, req.rawChar), create(Position, req.rawLine, req.rawChar + suggestions[0].qualifiedPath[^1].len) )) +<<<<<<< HEAD markupContent = createMarkupContent(label, suggestions[0].doc) resp = create(Hover, markupContent, rangeopt).JsonNode outs.respond(message, resp) +======= + markedString = create(MarkedStringOption, "nim", label) + if suggestions[0].doc != "": + resp = create(Hover, + @[ + markedString, + create(MarkedStringOption, "", suggestions[0].doc), + ], + rangeopt + ).JsonNode + else: + resp = create(Hover, markedString, rangeopt).JsonNode; + outs.respond(message, resp) +>>>>>>> 19a04042ad (tool: LSP support) of "textDocument/references": textDocumentRequest(message, ReferenceParams, req): debugLog "Running equivalent of: use ", req.fileuri, " ", req.filestash, "(", @@ -393,7 +517,10 @@ proc main(ins: Stream, outs: Stream) = debugLog "Found outlines: ", syms[0.. 10: &" and {syms.len-10} more" else: "" var resp: JsonNode +<<<<<<< HEAD var flags = newSeq[int]() +======= +>>>>>>> 19a04042ad (tool: LSP support) if syms.len == 0: resp = newJNull() else: @@ -401,6 +528,7 @@ proc main(ins: Stream, outs: Stream) = for sym in syms.sortedByIt((it.line,it.column,it.quality)): if sym.qualifiedPath.len != 2: continue +<<<<<<< HEAD flags.setLen(0) for f in sym.flags: flags.add f.int @@ -418,6 +546,21 @@ proc main(ins: Stream, outs: Stream) = create(Position, sym.line-1, sym.column + sym.tokenLen) ), none(seq[DocumentSymbol]) +======= + resp.add create( + SymbolInformation, + sym.qualifiedPath[^1], + 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) +>>>>>>> 19a04042ad (tool: LSP support) ).JsonNode outs.respond(message, resp) of "textDocument/signatureHelp": @@ -610,6 +753,7 @@ proc main(ins: Stream, outs: Stream) = warnLog "Got exception: ", e.msg continue +<<<<<<< HEAD when isMainModule: infoLog("explicitSourcePath: ", explicitSourcePath) for i in 1..paramCount(): @@ -633,3 +777,9 @@ when isMainModule: ins = newFileStream(stdin) outs = newFileStream(stdout) main(ins, outs) +======= +var + ins = newFileStream(stdin) + outs = newFileStream(stdout) +main(ins, outs) +>>>>>>> 19a04042ad (tool: LSP support) diff --git a/nimlsp/nimlsp.nimble b/nimlsp/nimlsp.nimble new file mode 100644 index 00000000000..77414e31cd2 --- /dev/null +++ b/nimlsp/nimlsp.nimble @@ -0,0 +1,25 @@ +# Package + +version = "0.4.4" +author = "PMunch" +description = "Nim Language Server Protocol - nimlsp implements the Language Server Protocol" +license = "MIT" +srcDir = "src" +bin = @["nimlsp", "nimlsp_debug"] + +# Dependencies + +# nimble test does not work for me out of the box +#task test, "Runs the test suite": + #exec "nim c -r tests/test_messages.nim" +# exec "nim c -d:debugLogging -d:jsonSchemaDebug -r tests/test_messages2.nim" + +task debug, "Builds the language server": + exec "nim c --threads:on -d:nimcore -d:nimsuggest -d:debugCommunication -d:debugLogging -o:nimlsp src/nimlsp" + +before test: + exec "nimble build" + +task findNim, "Tries to find the current Nim installation": + echo NimVersion + echo currentSourcePath diff --git a/nimlsp/nimlsppkg/baseprotocol.nim b/nimlsp/nimlsppkg/baseprotocol.nim index b0ddcff72e2..9624db56b2c 100644 --- a/nimlsp/nimlsppkg/baseprotocol.nim +++ b/nimlsp/nimlsppkg/baseprotocol.nim @@ -1,6 +1,10 @@ import std/[json, parseutils, streams, strformat, +<<<<<<< HEAD strutils, os] from std/uri import decodeUrl, parseUri +======= + strutils] +>>>>>>> 19a04042ad (tool: LSP support) when defined(debugCommunication): import logger @@ -10,6 +14,7 @@ type MalformedFrame* = object of BaseProtocolError UnsupportedEncoding* = object of BaseProtocolError +<<<<<<< HEAD UriParseError* = object of Defect uri*: string @@ -62,6 +67,8 @@ proc parseId*(node: JsonNode): string = else: "" +======= +>>>>>>> 19a04042ad (tool: LSP support) proc skipWhitespace(x: string, pos: int): int = result = pos while result < x.len and x[result] in Whitespace: diff --git a/nimlsp/nimlsppkg/messageenums.nim b/nimlsp/nimlsppkg/messageenums.nim index 81433dab994..4aa72c7a9f9 100644 --- a/nimlsp/nimlsppkg/messageenums.nim +++ b/nimlsp/nimlsppkg/messageenums.nim @@ -112,6 +112,9 @@ type Text = 1, Read = 2, Write = 3 +<<<<<<< HEAD SymbolTag* {.pure.} = enum Deprecated = 1 +======= +>>>>>>> 19a04042ad (tool: LSP support) diff --git a/nimlsp/nimlsppkg/messages.nim b/nimlsp/nimlsppkg/messages.nim index 9a046a642e0..90173ca67d2 100644 --- a/nimlsp/nimlsppkg/messages.nim +++ b/nimlsp/nimlsppkg/messages.nim @@ -468,7 +468,11 @@ jsonSchema: triggerCharacters ?: string[] resolveProvider ?: bool +<<<<<<< HEAD MarkedStringOption: # deprecated +======= + MarkedStringOption: +>>>>>>> 19a04042ad (tool: LSP support) language: string value: string @@ -513,6 +517,7 @@ jsonSchema: location: Location containerName ?: string +<<<<<<< HEAD DocumentSymbol: name: string detail ?: string @@ -522,6 +527,8 @@ jsonSchema: selectionRange: Range children ?: DocumentSymbol[] +======= +>>>>>>> 19a04042ad (tool: LSP support) CodeActionParams: textDocument: TextDocumentIdentifier "range": Range diff --git a/nimlsp/nimlsppkg/suggestlib.nim b/nimlsp/nimlsppkg/suggestlib.nim index e687b590149..fc6ce5b590c 100644 --- a/nimlsp/nimlsppkg/suggestlib.nim +++ b/nimlsp/nimlsppkg/suggestlib.nim @@ -75,6 +75,7 @@ func nimSymDetails*(suggest: Suggest): string = of skVar: "var of " & suggest.forth else: suggest.forth +<<<<<<< HEAD func symKindToString*(suggest: byte): string = case TSymKind(suggest) of skConst: discard @@ -92,6 +93,8 @@ func symKindToString*(suggest: byte): string = of skLabel: result = "label" of skUnknown: result = "unkown" else: result = "" +======= +>>>>>>> 19a04042ad (tool: LSP support) template createFullCommand(command: untyped) = proc command*(nimsuggest: NimSuggest, file: string, dirtyfile = "", From ae014684134a1603358913e9227f695e7b9f9b14 Mon Sep 17 00:00:00 2001 From: bung87 Date: Thu, 14 Sep 2023 03:17:36 +0800 Subject: [PATCH 14/84] fix conflicts --- compiler/tools/suggest.nim | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/tools/suggest.nim b/compiler/tools/suggest.nim index 7ca64a2123f..9255b57f33b 100644 --- a/compiler/tools/suggest.nim +++ b/compiler/tools/suggest.nim @@ -451,7 +451,6 @@ proc isTracked*(current, trackPos: TLineInfo, tokenLen: int): bool = if current.fileIndex == trackPos.fileIndex and current.line == trackPos.line: let col = trackPos.col - if col >= current.col and col <= current.col + tokenLen - 1: if col >= current.col and col <= current.col + tokenLen - 1: return true From 6c709f42347fdc2ccf43c771a14c75ddb4b258fd Mon Sep 17 00:00:00 2001 From: bung87 Date: Sat, 16 Sep 2023 13:39:14 +0800 Subject: [PATCH 15/84] refactor --- compiler/tools/suggest.nim | 2 +- nimlsp/nimlsp.nim | 156 +----------------------------- nimlsp/nimlsppkg/baseprotocol.nim | 7 -- nimlsp/nimlsppkg/messageenums.nim | 3 - nimlsp/nimlsppkg/messages.nim | 7 -- nimlsp/nimlsppkg/nimsuggest.nim | 34 +------ nimlsp/nimlsppkg/suggestlib.nim | 3 - 7 files changed, 6 insertions(+), 206 deletions(-) diff --git a/compiler/tools/suggest.nim b/compiler/tools/suggest.nim index 9255b57f33b..84d6f9e0ceb 100644 --- a/compiler/tools/suggest.nim +++ b/compiler/tools/suggest.nim @@ -449,7 +449,7 @@ proc inCheckpoint*(current, trackPos: TLineInfo): TCheckPointResult = proc isTracked*(current, trackPos: TLineInfo, tokenLen: int): bool = if current.fileIndex == trackPos.fileIndex and - current.line == trackPos.line: + current.line == trackPos.line: let col = trackPos.col if col >= current.col and col <= current.col + tokenLen - 1: return true diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index 9164e209c19..689852e18e2 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -1,46 +1,15 @@ import std/[algorithm, hashes, os, osproc, sets, -<<<<<<< HEAD streams, strformat, strutils, tables] -======= - streams, strformat, strutils, tables, uri] ->>>>>>> 19a04042ad (tool: LSP support) import nimlsppkg/[baseprotocol, logger, suggestlib, utfmapping] include nimlsppkg/[messages, messageenums] const -<<<<<<< HEAD # This is used to explicitly set the default source path explicitSourcePath {.strdefine.} = getCurrentCompilerExe().parentDir.parentDir var nimpath = explicitSourcePath -======= - version = block: - var version = "0.0.0" - let nimbleFile = staticRead(currentSourcePath().parentDir / "nimlsp.nimble") - for line in nimbleFile.splitLines: - let keyval = line.split('=') - if keyval.len == 2: - if keyval[0].strip == "version": - version = keyval[1].strip(chars = Whitespace + {'"'}) - break - version - # This is used to explicitly set the default source path - explicitSourcePath {.strdefine.} = getCurrentCompilerExe().parentDir.parentDir - -type - UriParseError* = object of Defect - uri: string - -var nimpath = explicitSourcePath - -infoLog("Version: ", version) -infoLog("explicitSourcePath: ", explicitSourcePath) -for i in 1..paramCount(): - infoLog("Argument ", i, ": ", paramStr(i)) - ->>>>>>> 19a04042ad (tool: LSP support) var gotShutdown = false initialized = false @@ -84,58 +53,6 @@ template textDocumentNotification(message: typed; kind: typed; name, body: untyp else: debugLog("Unable to parse data as ", kind) -<<<<<<< HEAD -======= -proc pathToUri(path: string): string = - # This is a modified copy of encodeUrl in the uri module. This doesn't encode - # the / character, meaning a full file path can be passed in without breaking - # it. - result = newStringOfCap(path.len + path.len shr 2) # assume 12% non-alnum-chars - when defined(windows): - result.add '/' - for c in path: - case c - # https://tools.ietf.org/html/rfc3986#section-2.3 - of 'a'..'z', 'A'..'Z', '0'..'9', '-', '.', '_', '~', '/': result.add c - of '\\': - when defined(windows): - result.add '/' - else: - result.add '%' - result.add toHex(ord(c), 2) - else: - result.add '%' - result.add toHex(ord(c), 2) - -proc uriToPath(uri: string): string = - ## Convert an RFC 8089 file URI to a native, platform-specific, absolute path. - #let startIdx = when defined(windows): 8 else: 7 - #normalizedPath(uri[startIdx..^1]) - let parsed = uri.parseUri - if parsed.scheme != "file": - var e = newException(UriParseError, &"Invalid scheme: {parsed.scheme}, only \"file\" is supported") - e.uri = uri - raise e - if parsed.hostname != "": - var e = newException(UriParseError, &"Invalid hostname: {parsed.hostname}, only empty hostname is supported") - e.uri = uri - raise e - return normalizedPath( - when defined(windows): - parsed.path[1..^1] - else: - parsed.path).decodeUrl - -proc parseId(node: JsonNode): string = - if node == nil: return - if node.kind == JString: - node.getStr - elif node.kind == JInt: - $node.getInt - else: - "" - ->>>>>>> 19a04042ad (tool: LSP support) proc respond(outs: Stream, request: JsonNode, data: JsonNode) = let resp = create(ResponseMessage, "2.0", parseId(request["id"]), some(data), none(ResponseError)).JsonNode outs.sendJson resp @@ -192,29 +109,6 @@ proc getProjectFile(file: string): string = template getNimsuggest(fileuri: string): Nimsuggest = projectFiles[openFiles[fileuri].projectFile].nimsuggest -<<<<<<< HEAD -======= -if paramCount() == 1: - case paramStr(1): - of "--help": - echo "Usage: nimlsp [OPTION | PATH]\n" - echo "--help, shows this message" - echo "--version, shows only the version" - echo "PATH, path to the Nim source directory, defaults to \"", nimpath, "\"" - quit 0 - of "--version": - echo "nimlsp v", version - when defined(debugLogging): echo "Compiled with debug logging" - when defined(debugCommunication): echo "Compiled with communication logging" - quit 0 - else: nimpath = expandFilename(paramStr(1)) -if not fileExists(nimpath / "config/nim.cfg"): - stderr.write &"""Unable to find "config/nim.cfg" in "{nimpath - }". Supply the Nim project folder by adding it as an argument. -""" - quit 1 - ->>>>>>> 19a04042ad (tool: LSP support) proc checkVersion(outs: Stream) = let nimoutputTuple = @@ -229,7 +123,6 @@ proc checkVersion(outs: Stream) = if version != NimVersion: outs.notify("window/showMessage", create(ShowMessageParams, MessageType.Warning.int, message = "Current Nim version does not match the one NimLSP is built against " & version & " != " & NimVersion).JsonNode) -<<<<<<< HEAD proc createMarkupContent(label: string; content: string): MarkupContent = let label = "```nim\n" & label & "\n```\n" var @@ -269,8 +162,6 @@ proc createMarkupContent(label: string; content: string): MarkupContent = ) result = create(MarkupContent, "markdown", label & c) -======= ->>>>>>> 19a04042ad (tool: LSP support) proc main(ins: Stream, outs: Stream) = checkVersion(outs) var message: JsonNode @@ -403,24 +294,9 @@ proc main(ins: Stream, outs: Stream) = create(Position, req.rawLine, req.rawChar), create(Position, req.rawLine, req.rawChar + suggestions[0].qualifiedPath[^1].len) )) -<<<<<<< HEAD markupContent = createMarkupContent(label, suggestions[0].doc) resp = create(Hover, markupContent, rangeopt).JsonNode outs.respond(message, resp) -======= - markedString = create(MarkedStringOption, "nim", label) - if suggestions[0].doc != "": - resp = create(Hover, - @[ - markedString, - create(MarkedStringOption, "", suggestions[0].doc), - ], - rangeopt - ).JsonNode - else: - resp = create(Hover, markedString, rangeopt).JsonNode; - outs.respond(message, resp) ->>>>>>> 19a04042ad (tool: LSP support) of "textDocument/references": textDocumentRequest(message, ReferenceParams, req): debugLog "Running equivalent of: use ", req.fileuri, " ", req.filestash, "(", @@ -517,10 +393,7 @@ proc main(ins: Stream, outs: Stream) = debugLog "Found outlines: ", syms[0.. 10: &" and {syms.len-10} more" else: "" var resp: JsonNode -<<<<<<< HEAD var flags = newSeq[int]() -======= ->>>>>>> 19a04042ad (tool: LSP support) if syms.len == 0: resp = newJNull() else: @@ -528,7 +401,6 @@ proc main(ins: Stream, outs: Stream) = for sym in syms.sortedByIt((it.line,it.column,it.quality)): if sym.qualifiedPath.len != 2: continue -<<<<<<< HEAD flags.setLen(0) for f in sym.flags: flags.add f.int @@ -545,25 +417,8 @@ proc main(ins: Stream, outs: Stream) = create(Position, sym.line-1, sym.column), create(Position, sym.line-1, sym.column + sym.tokenLen) ), - none(seq[DocumentSymbol]) -======= - resp.add create( - SymbolInformation, - sym.qualifiedPath[^1], - 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) ->>>>>>> 19a04042ad (tool: LSP support) - ).JsonNode - outs.respond(message, resp) - of "textDocument/signatureHelp": + none(seq[DocumentSymbol])).JsonNode + outs.respond(message, resp) textDocumentRequest(message, TextDocumentPositionParams, req): debugLog "Running equivalent of: con ", req.filePath, " ", req.filestash, "(", req.rawLine + 1, ":", @@ -753,7 +608,6 @@ proc main(ins: Stream, outs: Stream) = warnLog "Got exception: ", e.msg continue -<<<<<<< HEAD when isMainModule: infoLog("explicitSourcePath: ", explicitSourcePath) for i in 1..paramCount(): @@ -777,9 +631,3 @@ when isMainModule: ins = newFileStream(stdin) outs = newFileStream(stdout) main(ins, outs) -======= -var - ins = newFileStream(stdin) - outs = newFileStream(stdout) -main(ins, outs) ->>>>>>> 19a04042ad (tool: LSP support) diff --git a/nimlsp/nimlsppkg/baseprotocol.nim b/nimlsp/nimlsppkg/baseprotocol.nim index 9624db56b2c..b0ddcff72e2 100644 --- a/nimlsp/nimlsppkg/baseprotocol.nim +++ b/nimlsp/nimlsppkg/baseprotocol.nim @@ -1,10 +1,6 @@ import std/[json, parseutils, streams, strformat, -<<<<<<< HEAD strutils, os] from std/uri import decodeUrl, parseUri -======= - strutils] ->>>>>>> 19a04042ad (tool: LSP support) when defined(debugCommunication): import logger @@ -14,7 +10,6 @@ type MalformedFrame* = object of BaseProtocolError UnsupportedEncoding* = object of BaseProtocolError -<<<<<<< HEAD UriParseError* = object of Defect uri*: string @@ -67,8 +62,6 @@ proc parseId*(node: JsonNode): string = else: "" -======= ->>>>>>> 19a04042ad (tool: LSP support) proc skipWhitespace(x: string, pos: int): int = result = pos while result < x.len and x[result] in Whitespace: diff --git a/nimlsp/nimlsppkg/messageenums.nim b/nimlsp/nimlsppkg/messageenums.nim index 4aa72c7a9f9..81433dab994 100644 --- a/nimlsp/nimlsppkg/messageenums.nim +++ b/nimlsp/nimlsppkg/messageenums.nim @@ -112,9 +112,6 @@ type Text = 1, Read = 2, Write = 3 -<<<<<<< HEAD SymbolTag* {.pure.} = enum Deprecated = 1 -======= ->>>>>>> 19a04042ad (tool: LSP support) diff --git a/nimlsp/nimlsppkg/messages.nim b/nimlsp/nimlsppkg/messages.nim index 90173ca67d2..9a046a642e0 100644 --- a/nimlsp/nimlsppkg/messages.nim +++ b/nimlsp/nimlsppkg/messages.nim @@ -468,11 +468,7 @@ jsonSchema: triggerCharacters ?: string[] resolveProvider ?: bool -<<<<<<< HEAD MarkedStringOption: # deprecated -======= - MarkedStringOption: ->>>>>>> 19a04042ad (tool: LSP support) language: string value: string @@ -517,7 +513,6 @@ jsonSchema: location: Location containerName ?: string -<<<<<<< HEAD DocumentSymbol: name: string detail ?: string @@ -527,8 +522,6 @@ jsonSchema: selectionRange: Range children ?: DocumentSymbol[] -======= ->>>>>>> 19a04042ad (tool: LSP support) CodeActionParams: textDocument: TextDocumentIdentifier "range": Range diff --git a/nimlsp/nimlsppkg/nimsuggest.nim b/nimlsp/nimlsppkg/nimsuggest.nim index 691f6fdd4d8..a9d511a9939 100644 --- a/nimlsp/nimlsppkg/nimsuggest.nim +++ b/nimlsp/nimlsppkg/nimsuggest.nim @@ -20,13 +20,11 @@ import compiler/front/[ options, optionsprocessor, - # commands, msgs, cmdlinehelper, cli_reporter ], compiler/utils/[ - # prefixmatches, pathutils ], compiler/sem/[ @@ -42,7 +40,7 @@ from compiler/ast/reports import Report, from compiler/front/main import customizeForBackend -from compiler/tools/suggest import isTracked, listUsages, suggestSym, `$` +from compiler/tools/suggest import findTrackedSym, executeCmd, listUsages, suggestSym, `$` export Suggest export IdeCmd @@ -126,19 +124,6 @@ proc initNimSuggest*(project: string, nimPath: string = ""): NimSuggest = retval.doStopCompile = proc (): bool = false return NimSuggest(graph: retval, idle: 0, cachedMsgs: @[]) -proc findNode(n: PNode; trackPos: TLineInfo): PSym = - if n.kind == nkSym: - if isTracked(n.info, trackPos, n.sym.name.s.len): return n.sym - else: - for i in 0 ..< safeLen(n): - let res = findNode(n[i], trackPos) - if res != nil: return res - -proc symFromInfo(graph: ModuleGraph; trackPos: TLineInfo; moduleIdx: FileIndex): PSym = - let m = graph.getModule(moduleIdx) - if m != nil and m.ast != nil: - result = findNode(m.ast, trackPos) - proc getSymNode(node: ParsedNode): ParsedNode = result = node if result.kind == pnkPostfix: @@ -262,22 +247,9 @@ proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int, needCompile = false if needCompile: - if not isKnownFile: - moduleIdx = dirtyIdx - # stderr.writeLine "Compile unknown module: " & toFullPath(conf, moduleIdx) - discard graph.compileModule(moduleIdx, {}) - else: - moduleIdx = graph.parentModule(dirtyIdx) - # stderr.writeLine "Compile known module: " & toFullPath(conf, moduleIdx) - graph.markDirty dirtyIdx - graph.markClientsDirty dirtyIdx - # partially recompiling the project means that that VM and JIT state - # would become stale, which we prevent by discarding all of it: - graph.vm = nil - if conf.ideCmd != ideMod: - discard graph.compileModule(moduleIdx, {}) + executeCmd(cmd, file, dirtyfile, line, col, graph) if conf.ideCmd in {ideUse, ideDus}: - let u = graph.symFromInfo(conf.m.trackPos, moduleIdx) + let u = graph.findTrackedSym() if u != nil: listUsages(graph, u) else: diff --git a/nimlsp/nimlsppkg/suggestlib.nim b/nimlsp/nimlsppkg/suggestlib.nim index fc6ce5b590c..e687b590149 100644 --- a/nimlsp/nimlsppkg/suggestlib.nim +++ b/nimlsp/nimlsppkg/suggestlib.nim @@ -75,7 +75,6 @@ func nimSymDetails*(suggest: Suggest): string = of skVar: "var of " & suggest.forth else: suggest.forth -<<<<<<< HEAD func symKindToString*(suggest: byte): string = case TSymKind(suggest) of skConst: discard @@ -93,8 +92,6 @@ func symKindToString*(suggest: byte): string = of skLabel: result = "label" of skUnknown: result = "unkown" else: result = "" -======= ->>>>>>> 19a04042ad (tool: LSP support) template createFullCommand(command: untyped) = proc command*(nimsuggest: NimSuggest, file: string, dirtyfile = "", From 8c01cf51a921bb0d433d19ed9277be16ab652bd6 Mon Sep 17 00:00:00 2001 From: bung87 Date: Sun, 17 Sep 2023 14:16:24 +0800 Subject: [PATCH 16/84] handle cachedMsgs --- nimlsp/nimlsppkg/nimsuggest.nim | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/nimlsp/nimlsppkg/nimsuggest.nim b/nimlsp/nimlsppkg/nimsuggest.nim index a9d511a9939..4dcae2ea210 100644 --- a/nimlsp/nimlsppkg/nimsuggest.nim +++ b/nimlsp/nimlsppkg/nimsuggest.nim @@ -11,7 +11,8 @@ import syntaxes, parser, ast_parsed_types, - ast_types + ast_types, + report_enums ], compiler/modules/[ modules, @@ -57,6 +58,7 @@ proc defaultStructuredReportHook(conf: ConfigRef, report: Report): TErrorHandlin proc initNimSuggest*(project: string, nimPath: string = ""): NimSuggest = var retval: ModuleGraph + var cachedMsgs: CachedMsgs = @[] proc mockCommand(graph: ModuleGraph) = retval = graph let conf = graph.config @@ -71,7 +73,10 @@ proc initNimSuggest*(project: string, nimPath: string = ""): NimSuggest = # do not print errors, but log them conf.writelnHook = proc(conf: ConfigRef, msg: string, flags: MsgFlags) = discard - conf.structuredReportHook = defaultStructuredReportHook + conf.structuredReportHook = proc (conf: ConfigRef, report: Report): TErrorHandling = + if report.kind notin {rsemProcessing, rsemProcessingStmt}: + # pre-filter to save memory + cachedMsgs.add(report) # compile the project before showing any input so that we already # can answer questions right away: @@ -122,7 +127,7 @@ proc initNimSuggest*(project: string, nimPath: string = ""): NimSuggest = mockCommand(graph) retval.doStopCompile = proc (): bool = false - return NimSuggest(graph: retval, idle: 0, cachedMsgs: @[]) + return NimSuggest(graph: retval, idle: 0, cachedMsgs: cachedMsgs) proc getSymNode(node: ParsedNode): ParsedNode = result = node @@ -271,17 +276,19 @@ proc runCmd*(nimsuggest: NimSuggest, cmd: IdeCmd, file, retval.add(Suggest(section: ideProject, filePath: string conf.projectFull)) else: - # if conf.ideCmd == ideChk: - # for cm in nimsuggest.cachedMsgs: errorHook(conf, cm.info, cm.msg, cm.sev) + template addReport(report: Report) = + let loc = report.location() + if stdOptions.isSome(loc): + let info = loc.get() + retval.add(Suggest(section: ideChk, filePath: toFullPath(conf,info), + line: toLinenumber(info), column: toColumn(info), + forth: $severity(conf, report))) + if conf.ideCmd == ideChk: + for cm in nimsuggest.cachedMsgs: addReport(cm) + nimsuggest.cachedMsgs.setLen 0 conf.structuredReportHook = proc (conf: ConfigRef, report: Report): TErrorHandling = - let loc = report.location() - if stdOptions.isSome(loc): - let info = loc.get() - retval.add(Suggest(section: ideChk, filePath: toFullPath(conf, - info), - line: toLinenumber(info), column: toColumn(info), - forth: $severity(conf, report))) + addReport(report) return doNothing else: conf.structuredReportHook = defaultStructuredReportHook From d8ccb36e2f70cad02badda3c16b325de8f2b5372 Mon Sep 17 00:00:00 2001 From: bung87 Date: Sun, 17 Sep 2023 15:37:46 +0800 Subject: [PATCH 17/84] fix conflicts --- nimlsp/nimlsp.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index 689852e18e2..3de10483865 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -419,6 +419,7 @@ proc main(ins: Stream, outs: Stream) = ), none(seq[DocumentSymbol])).JsonNode outs.respond(message, resp) + of "textDocument/signatureHelp": textDocumentRequest(message, TextDocumentPositionParams, req): debugLog "Running equivalent of: con ", req.filePath, " ", req.filestash, "(", req.rawLine + 1, ":", From e8d28ca88aec28cbc08cfc8e886d28f437377c57 Mon Sep 17 00:00:00 2001 From: bung87 Date: Sun, 17 Sep 2023 17:01:06 +0800 Subject: [PATCH 18/84] lsp message log --- nimlsp/nimlsp.nim | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index 3de10483865..f4fbe98bbf6 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -16,6 +16,10 @@ var projectFiles = initTable[string, tuple[nimsuggest: NimSuggest, openFiles: OrderedSet[string]]]() openFiles = initTable[string, tuple[projectFile: string, fingerTable: seq[seq[tuple[u16pos, offset: int]]]]]() +template location(req: untyped): string = + let lineCol = "(" & $(req.rawLine() + 1) & ":" & $openFiles.col(req) & ")" + req.filePath & lineCol + template fileuri(p: untyped): string = p["textDocument"]["uri"].getStr @@ -178,12 +182,10 @@ proc main(ins: Stream, outs: Stream) = continue case message["method"].getStr: of "shutdown": - debugLog "Got shutdown request, answering" let resp = newJNull() outs.respond(message, resp) gotShutdown = true of "initialize": - debugLog "Got initialize request, answering" initialized = true let resp = create(InitializeResult, create(ServerCapabilities, textDocumentSync = some(create(TextDocumentSyncOptions, @@ -223,9 +225,7 @@ proc main(ins: Stream, outs: Stream) = outs.respond(message,resp) of "textDocument/completion": textDocumentRequest(message, CompletionParams, req): - debugLog "Running equivalent of: sug ", req.filePath, " ", req.filestash, "(", - req.rawLine + 1, ":", - openFiles.col(req), ")" + debugLog location(req) let suggestions = getNimsuggest(req.fileuri).sug(req.filePath, dirtyfile = req.filestash, req.rawLine + 1, openFiles.col(req) @@ -270,9 +270,7 @@ proc main(ins: Stream, outs: Stream) = outs.respond(message, completionItems) of "textDocument/hover": textDocumentRequest(message, TextDocumentPositionParams, req): - debugLog "Running equivalent of: def ", req.filePath, " ", req.filestash, "(", - req.rawLine + 1, ":", - openFiles.col(req), ")" + debugLog location(req) let suggestions = getNimsuggest(req.fileuri).def(req.filePath, dirtyfile = req.filestash, req.rawLine + 1, openFiles.col(req) @@ -299,9 +297,7 @@ proc main(ins: Stream, outs: Stream) = outs.respond(message, resp) of "textDocument/references": textDocumentRequest(message, ReferenceParams, req): - debugLog "Running equivalent of: use ", req.fileuri, " ", req.filestash, "(", - req.rawLine + 1, ":", - openFiles.col(req), ")" + debugLog location(req) let suggestions = getNimsuggest(req.fileuri).use(req.filePath, dirtyfile = req.filestash, req.rawLine + 1, openFiles.col(req) @@ -325,9 +321,7 @@ proc main(ins: Stream, outs: Stream) = outs.respond(message, response) of "textDocument/rename": textDocumentRequest(message, RenameParams, req): - debugLog "Running equivalent of: use ", req.fileuri, " ", req.filestash, "(", - req.rawLine + 1, ":", - openFiles.col(req), ")" + debugLog location(req) let suggestions = getNimsuggest(req.fileuri).use(req.filePath, dirtyfile = req.filestash, req.rawLine + 1, openFiles.col(req) @@ -357,9 +351,7 @@ proc main(ins: Stream, outs: Stream) = outs.respond(message, resp) of "textDocument/definition": textDocumentRequest(message, TextDocumentPositionParams, req): - debugLog "Running equivalent of: def ", req.fileuri, " ", req.filestash, "(", - req.rawLine + 1, ":", - openFiles.col(req), ")" + debugLog location(req) let declarations = getNimsuggest(req.fileuri).def(req.filePath, dirtyfile = req.filestash, req.rawLine + 1, openFiles.col(req) @@ -421,9 +413,7 @@ proc main(ins: Stream, outs: Stream) = outs.respond(message, resp) of "textDocument/signatureHelp": textDocumentRequest(message, TextDocumentPositionParams, req): - debugLog "Running equivalent of: con ", req.filePath, " ", req.filestash, "(", - req.rawLine + 1, ":", - openFiles.col(req), ")" + debugLog location(req) let suggestions = getNimsuggest(req.fileuri).con(req.filePath, dirtyfile = req.filestash, req.rawLine + 1, req.rawChar) var signatures = newSeq[SignatureInformation]() for suggestion in suggestions: From 586f3bbaeb586bbdd3fcd4f1fd4b60a3b7546341 Mon Sep 17 00:00:00 2001 From: bung87 Date: Sun, 17 Sep 2023 17:05:13 +0800 Subject: [PATCH 19/84] comment checkVersion call --- nimlsp/nimlsp.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index f4fbe98bbf6..31a6c1d62ac 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -167,7 +167,7 @@ proc createMarkupContent(label: string; content: string): MarkupContent = result = create(MarkupContent, "markdown", label & c) proc main(ins: Stream, outs: Stream) = - checkVersion(outs) + # checkVersion(outs) xxx: enable when deployment seperately var message: JsonNode var frame: string while true: From 5bc5481d6a7bfcfb5c2b316da4b90b265db77e7d Mon Sep 17 00:00:00 2001 From: bung87 Date: Sun, 17 Sep 2023 17:15:07 +0800 Subject: [PATCH 20/84] refactor checkVersion --- nimlsp/nimlsp.nim | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index 31a6c1d62ac..9653ab5b494 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -8,6 +8,7 @@ include nimlsppkg/[messages, messageenums] const # This is used to explicitly set the default source path explicitSourcePath {.strdefine.} = getCurrentCompilerExe().parentDir.parentDir + VerionMisMatch = "Current Nim version #1 does not match the one NimLSP is built against #2" var nimpath = explicitSourcePath var @@ -114,9 +115,10 @@ template getNimsuggest(fileuri: string): Nimsuggest = projectFiles[openFiles[fileuri].projectFile].nimsuggest proc checkVersion(outs: Stream) = + # xxx: usable when the server deployment seperately let nimoutputTuple = - execCmdEx("nim --version", options = {osproc.poEvalCommand, osproc.poUsePath}) + execCmdEx("nim --version", options = {poEvalCommand, poUsePath}) if nimoutputTuple.exitcode == 0: let nimoutput = nimoutputTuple.output @@ -125,7 +127,10 @@ proc checkVersion(outs: Stream) = #hashStart = nimoutput.find("git hash") + 10 #hash = nimoutput[hashStart..nimoutput.find("\n", hashStart)] if version != NimVersion: - outs.notify("window/showMessage", create(ShowMessageParams, MessageType.Warning.int, message = "Current Nim version does not match the one NimLSP is built against " & version & " != " & NimVersion).JsonNode) + let + text = VerionMisMatch % [version, NimVersion] + msg = create(ShowMessageParams, MessageType.Warning.int, message = text) + outs.notify("window/showMessage", msg.JsonNode) proc createMarkupContent(label: string; content: string): MarkupContent = let label = "```nim\n" & label & "\n```\n" From 4e8294e443c1ff3cec393aa3137a00abb3febb22 Mon Sep 17 00:00:00 2001 From: bung87 Date: Sun, 17 Sep 2023 17:23:44 +0800 Subject: [PATCH 21/84] remove exit log --- nimlsp/nimlsp.nim | 1 - 1 file changed, 1 deletion(-) diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index 9653ab5b494..e3f2a13292f 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -448,7 +448,6 @@ proc main(ins: Stream, outs: Stream) = continue case message["method"].getStr: of "exit": - debugLog "Exiting" if gotShutdown: quit 0 else: From 6873debe8bd72b7a93c07a2feeac69fe3a899de8 Mon Sep 17 00:00:00 2001 From: bung87 Date: Sun, 17 Sep 2023 17:27:59 +0800 Subject: [PATCH 22/84] fix VerionMisMatch placeholder --- nimlsp/nimlsp.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index e3f2a13292f..48954c6e341 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -8,7 +8,7 @@ include nimlsppkg/[messages, messageenums] const # This is used to explicitly set the default source path explicitSourcePath {.strdefine.} = getCurrentCompilerExe().parentDir.parentDir - VerionMisMatch = "Current Nim version #1 does not match the one NimLSP is built against #2" + VerionMisMatch = "Current Nim version $1 does not match the one NimLSP is built against $2" var nimpath = explicitSourcePath var From c4b3fd31cc95ae2254b6f5091900ffd77a52237a Mon Sep 17 00:00:00 2001 From: bung87 Date: Sun, 17 Sep 2023 18:44:58 +0800 Subject: [PATCH 23/84] refactor --- nimlsp/nimlsp.nim | 83 ++------------------------------------ nimlsp/nimlsppkg/utils.nim | 78 +++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 80 deletions(-) create mode 100644 nimlsp/nimlsppkg/utils.nim diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index 48954c6e341..9c01de62f2e 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -1,17 +1,15 @@ import std/[algorithm, hashes, os, osproc, sets, streams, strformat, strutils, tables] - -import nimlsppkg/[baseprotocol, logger, suggestlib, utfmapping] +import nimlsppkg/[baseprotocol, logger, suggestlib, utfmapping, utils] include nimlsppkg/[messages, messageenums] - const # This is used to explicitly set the default source path explicitSourcePath {.strdefine.} = getCurrentCompilerExe().parentDir.parentDir VerionMisMatch = "Current Nim version $1 does not match the one NimLSP is built against $2" -var nimpath = explicitSourcePath var + nimpath = explicitSourcePath gotShutdown = false initialized = false projectFiles = initTable[string, tuple[nimsuggest: NimSuggest, openFiles: OrderedSet[string]]]() @@ -71,46 +69,6 @@ proc notify(outs: Stream, notification: string, data: JsonNode) = let resp = create(NotificationMessage, "2.0", notification, some(data)).JsonNode outs.sendJson resp -type Certainty = enum - None, - Folder, - Cfg, - Nimble - -proc getProjectFile(file: string): string = - result = file - let (dir, _, _) = result.splitFile() - var - path = dir - certainty = None - while not path.isRootDir: - let - (dir, fname, ext) = path.splitFile() - current = fname & ext - if fileExists(path / current.addFileExt(".nim")) and certainty <= Folder: - result = path / current.addFileExt(".nim") - certainty = Folder - if fileExists(path / current.addFileExt(".nim")) and - (fileExists(path / current.addFileExt(".nim.cfg")) or - fileExists(path / current.addFileExt(".nims"))) and certainty <= Cfg: - result = path / current.addFileExt(".nim") - certainty = Cfg - if certainty <= Nimble: - for nimble in walkFiles(path / "*.nimble"): - let info = execProcess("nimble dump " & nimble) - var sourceDir, name: string - for line in info.splitLines: - if line.startsWith("srcDir"): - sourceDir = path / line[(1 + line.find '"')..^2] - if line.startsWith("name"): - name = line[(1 + line.find '"')..^2] - let projectFile = sourceDir / (name & ".nim") - if sourceDir.len != 0 and name.len != 0 and - file.isRelativeTo(sourceDir) and fileExists(projectFile): - result = projectFile - certainty = Nimble - path = dir - template getNimsuggest(fileuri: string): Nimsuggest = projectFiles[openFiles[fileuri].projectFile].nimsuggest @@ -134,42 +92,7 @@ proc checkVersion(outs: Stream) = proc createMarkupContent(label: string; content: string): MarkupContent = let label = "```nim\n" & label & "\n```\n" - var - c: string - isCodeBlock = false - const BlockStart = ".. code-block::" - const BlockLen = BlockStart.len + 1 - for line in splitLines(content, true): - let isCodeBlockStart = line.startsWith(BlockStart) - if isCodeBlockStart: - isCodeBlock = true - if line.endsWith("Nim\n") or line.endsWith("nim\n") or - line.len == BlockLen: - c.add "```nim\n" - else: - c.add "```\n" - elif isCodeBlock and line.strip() == "": - c.add "```\n" - isCodeBlock = false - else: - c.add line - if isCodeBlock: - # single code block and ends without trailing line - c.add "```\n" - # admonition labels - c = multiReplace(c, - (".. attention::", "**attention**"), - (".. caution::", "**caution**"), - (".. danger::", "**danger**"), - (".. error::", "**error**"), - (".. hint::", "**hint**"), - (".. important::", "**important**"), - (".. note::", "**note**"), - (".. seealso::", "**seealso**"), - (".. tip::", "**tip**"), - (".. warning::", "**warning**"), - ) - result = create(MarkupContent, "markdown", label & c) + result = create(MarkupContent, "markdown", label & rstToMarkdown(content)) proc main(ins: Stream, outs: Stream) = # checkVersion(outs) xxx: enable when deployment seperately diff --git a/nimlsp/nimlsppkg/utils.nim b/nimlsp/nimlsppkg/utils.nim new file mode 100644 index 00000000000..acecc83ab9a --- /dev/null +++ b/nimlsp/nimlsppkg/utils.nim @@ -0,0 +1,78 @@ +import std/[ os, osproc, strutils] +type + Certainty = enum + None, + Folder, + Cfg, + Nimble + +proc getProjectFile*(file: string): string = + result = file + let (dir, _, _) = result.splitFile() + var + path = dir + certainty = None + while not path.isRootDir: + let + (dir, fname, ext) = path.splitFile() + current = fname & ext + if fileExists(path / current.addFileExt(".nim")) and certainty <= Folder: + result = path / current.addFileExt(".nim") + certainty = Folder + if fileExists(path / current.addFileExt(".nim")) and + (fileExists(path / current.addFileExt(".nim.cfg")) or + fileExists(path / current.addFileExt(".nims"))) and certainty <= Cfg: + result = path / current.addFileExt(".nim") + certainty = Cfg + if certainty <= Nimble: + for nimble in walkFiles(path / "*.nimble"): + let info = execProcess("nimble dump " & nimble) + var sourceDir, name: string + for line in info.splitLines: + if line.startsWith("srcDir"): + sourceDir = path / line[(1 + line.find '"')..^2] + if line.startsWith("name"): + name = line[(1 + line.find '"')..^2] + let projectFile = sourceDir / (name & ".nim") + if sourceDir.len != 0 and name.len != 0 and + file.isRelativeTo(sourceDir) and fileExists(projectFile): + result = projectFile + certainty = Nimble + path = dir + +proc rstToMarkdown*(content: string): string = + var + c: string + isCodeBlock = false + const BlockStart = ".. code-block::" + const BlockLen = BlockStart.len + 1 + for line in splitLines(content, true): + let isCodeBlockStart = line.startsWith(BlockStart) + if isCodeBlockStart: + isCodeBlock = true + if line.endsWith("Nim\n") or line.endsWith("nim\n") or + line.len == BlockLen: + c.add "```nim\n" + else: + c.add "```\n" + elif isCodeBlock and line.strip() == "": + c.add "```\n" + isCodeBlock = false + else: + c.add line + if isCodeBlock: + # single code block and ends without trailing line + c.add "```\n" + # admonition labels + c = multiReplace(c, + (".. attention::", "**attention**"), + (".. caution::", "**caution**"), + (".. danger::", "**danger**"), + (".. error::", "**error**"), + (".. hint::", "**hint**"), + (".. important::", "**important**"), + (".. note::", "**note**"), + (".. seealso::", "**seealso**"), + (".. tip::", "**tip**"), + (".. warning::", "**warning**"), + ) \ No newline at end of file From 7d0b10f52c2084fdb76f0ca32b9da000bf080366 Mon Sep 17 00:00:00 2001 From: bung87 Date: Sun, 17 Sep 2023 22:09:58 +0800 Subject: [PATCH 24/84] refactor logs --- nimlsp/nimlsp.nim | 61 ++++++++++++++++----------------- nimlsp/nimlsppkg/logger.nim | 3 ++ nimlsp/nimlsppkg/suggestlib.nim | 9 +++++ 3 files changed, 42 insertions(+), 31 deletions(-) diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index 9c01de62f2e..b94b131b08b 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -1,5 +1,5 @@ import std/[algorithm, hashes, os, osproc, sets, - streams, strformat, strutils, tables] + streams, strformat, strutils, tables, logging] import nimlsppkg/[baseprotocol, logger, suggestlib, utfmapping, utils] include nimlsppkg/[messages, messageenums] @@ -15,10 +15,18 @@ var projectFiles = initTable[string, tuple[nimsuggest: NimSuggest, openFiles: OrderedSet[string]]]() openFiles = initTable[string, tuple[projectFile: string, fingerTable: seq[seq[tuple[u16pos, offset: int]]]]]() +proc debugSuggests(suggests: seq[Suggest]) = + for sug in suggests: + debug logFormat(sug) + flushLog() + template location(req: untyped): string = let lineCol = "(" & $(req.rawLine() + 1) & ":" & $openFiles.col(req) & ")" req.filePath & lineCol +template uriAndStash(req: untyped): string = + "uri: " & req.fileuri & "stash: " & req.filestash + template fileuri(p: untyped): string = p["textDocument"]["uri"].getStr @@ -158,9 +166,8 @@ proc main(ins: Stream, outs: Stream) = req.rawLine + 1, openFiles.col(req) ) - debugLog "Found suggestions: ", - suggestions[0 ..< min(suggestions.len, 10)], - if suggestions.len > 10: &" and {suggestions.len-10} more" else: "" + debugLog "Found suggestions: " & $suggestions.len + debugSuggests(suggestions[0 ..< min(suggestions.len, 10)]) var completionItems = newJarray() seenLabels: CountTable[string] @@ -203,9 +210,8 @@ proc main(ins: Stream, outs: Stream) = req.rawLine + 1, openFiles.col(req) ) - debugLog "Found suggestions: ", - suggestions[0 ..< min(suggestions.len, 10)], - if suggestions.len > 10: &" and {suggestions.len-10} more" else: "" + debugLog "Found suggestions: " & $suggestions.len + debugSuggests(suggestions[0 ..< min(suggestions.len, 10)]) var resp: JsonNode if suggestions.len == 0: resp = newJNull() @@ -230,9 +236,8 @@ proc main(ins: Stream, outs: Stream) = req.rawLine + 1, openFiles.col(req) ) - debugLog "Found suggestions: ", - suggestions[0 ..< min(suggestions.len, 10)], - if suggestions.len > 10: &" and {suggestions.len-10} more" else: "" + debugLog "Found suggestions: " & $suggestions.len + debugSuggests(suggestions[0 ..< min(suggestions.len, 10)]) var response = newJarray() for suggestion in suggestions: if suggestion.section == ideUse or req["context"]["includeDeclaration"].getBool: @@ -254,9 +259,8 @@ proc main(ins: Stream, outs: Stream) = req.rawLine + 1, openFiles.col(req) ) - debugLog "Found suggestions: ", - suggestions[0.. 10: &" and {suggestions.len-10} more" else: "" + debugLog "Found suggestions: " & $suggestions.len + debugSuggests(suggestions[0.. 10: &" and {declarations.len-10} more" else: "" + debugLog "Found suggestions: " & $declarations.len + debugSuggests(declarations[0.. 10: &" and {syms.len-10} more" else: "" + debugLog "Found outlines: " & $syms.len + debugSuggests(syms[0.. 10: &" and {diagnostics.len-10} more" else: "" + debugLog "Got diagnostics: " & $diagnostics.len + debugSuggests(diagnostics[0.. 10: &" and {diagnostics.len-10} more" else: "" + debugLog "Got diagnostics: " & $diagnostics.len + debugSuggests(diagnostics[0 ..< min(diagnostics.len, 10)]) var response: seq[Diagnostic] for diagnostic in diagnostics: diff --git a/nimlsp/nimlsppkg/logger.nim b/nimlsp/nimlsppkg/logger.nim index 6fdfb2f511a..6f63bb54194 100644 --- a/nimlsp/nimlsppkg/logger.nim +++ b/nimlsp/nimlsppkg/logger.nim @@ -6,6 +6,9 @@ discard existsOrCreateDir(storage) let rollingLog = newRollingFileLogger(storage / "nimlsp.log") addHandler(rollingLog) +template flushLog*() = + flushFile rollingLog.file + template debugLog*(args: varargs[string, `$`]) = when defined(debugLogging): debug join(args) diff --git a/nimlsp/nimlsppkg/suggestlib.nim b/nimlsp/nimlsppkg/suggestlib.nim index e687b590149..cb377277909 100644 --- a/nimlsp/nimlsppkg/suggestlib.nim +++ b/nimlsp/nimlsppkg/suggestlib.nim @@ -18,6 +18,15 @@ proc `$`*(suggest: Suggest): string = }, filePath: {suggest.filePath}, line: {suggest.line}, column: {suggest.column }, doc: {suggest.doc}, quality: {suggest.quality}, prefix: {suggest.prefix})""" +template ellipsis(s: string): string = + if s.len > 0: "..." else: "" + +proc logFormat*(sug: Suggest): string = + &"""(section: {sug.section}, symKind: {TSymKind(sug.symkind)}, + qualifiedPath: {sug.qualifiedPath.join(".")}, forth: {sug.forth}, + filePath: {sug.filePath.ellipsis()}, line: {sug.line}, column: {sug.column}, + doc: {sug.doc.ellipsis()}, quality: {sug.quality}, prefix: {sug.prefix})""" + func collapseByIdentifier*(suggest: Suggest): string = ## Function to create an identifier that can be used to remove duplicates in a list fmt"{suggest.qualifiedPath[^1]}__{suggest.symKind.TSymKind}" From d47ecd31f71a745db409d89d737cff9bf0bea924 Mon Sep 17 00:00:00 2001 From: bung87 Date: Sun, 17 Sep 2023 23:59:09 +0800 Subject: [PATCH 25/84] configs --- nimlsp/nimlsp.nim.cfg | 2 -- nimlsp/nimlsp_debug.nim.cfg | 2 -- nimlsp/nimlsppkg/suggestlib.nim.cfg | 2 -- 3 files changed, 6 deletions(-) diff --git a/nimlsp/nimlsp.nim.cfg b/nimlsp/nimlsp.nim.cfg index 5092829b85e..ba41f16cc54 100644 --- a/nimlsp/nimlsp.nim.cfg +++ b/nimlsp/nimlsp.nim.cfg @@ -1,6 +1,5 @@ hint[XDeclaredButNotUsed]:off -path:"$lib/packages/docutils" path:"$config/.." define:useStdoutAsStdmsg @@ -16,5 +15,4 @@ define:nimcore --threads:off --warning[Spacing]:off # The JSON schema macro uses a syntax similar to TypeScript -# --warning[CaseTransition]:off -d:nimOldCaseObjects diff --git a/nimlsp/nimlsp_debug.nim.cfg b/nimlsp/nimlsp_debug.nim.cfg index 739903ae492..78130d59a48 100644 --- a/nimlsp/nimlsp_debug.nim.cfg +++ b/nimlsp/nimlsp_debug.nim.cfg @@ -1,6 +1,5 @@ hint[XDeclaredButNotUsed]:off -path:"$lib/packages/docutils" path:"$config/.." define:useStdoutAsStdmsg @@ -18,5 +17,4 @@ define:debugLogging --threads:off --warning[Spacing]:off # The JSON schema macro uses a syntax similar to TypeScript -# --warning[CaseTransition]:off -d:nimOldCaseObjects diff --git a/nimlsp/nimlsppkg/suggestlib.nim.cfg b/nimlsp/nimlsppkg/suggestlib.nim.cfg index 89f43620c13..c3770e54654 100644 --- a/nimlsp/nimlsppkg/suggestlib.nim.cfg +++ b/nimlsp/nimlsppkg/suggestlib.nim.cfg @@ -1,6 +1,5 @@ hint[XDeclaredButNotUsed]:off -path:"$lib/packages/docutils" path:"$config/.." path:"../.." define:useStdoutAsStdmsg @@ -16,5 +15,4 @@ define:nimcore --threads:off --warning[Spacing]:off # The JSON schema macro uses a syntax similar to TypeScript -# --warning[CaseTransition]:off -d:nimOldCaseObjects From 447998705fba01d8660c3452d775442d2083c33d Mon Sep 17 00:00:00 2001 From: bung87 Date: Mon, 18 Sep 2023 00:55:08 +0800 Subject: [PATCH 26/84] refactor --- compiler/tools/suggest.nim | 2 +- nimlsp/nimlsppkg/nimsuggest.nim | 32 +++++++------------------------- nimlsp/nimlsppkg/suggestlib.nim | 5 ----- 3 files changed, 8 insertions(+), 31 deletions(-) diff --git a/compiler/tools/suggest.nim b/compiler/tools/suggest.nim index 84d6f9e0ceb..90bc42243e1 100644 --- a/compiler/tools/suggest.nim +++ b/compiler/tools/suggest.nim @@ -480,7 +480,7 @@ proc executeCmd*(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int; if dirtyfile.isEmpty: msgs.setDirtyFile(conf, dirtyIdx, AbsoluteFile"") else: msgs.setDirtyFile(conf, dirtyIdx, dirtyfile) - + if cmd == ideOutline: return conf.m.trackPos = newLineInfo(dirtyIdx, line, col) conf.m.trackPosAttached = false conf.errorCounter = 0 diff --git a/nimlsp/nimlsppkg/nimsuggest.nim b/nimlsp/nimlsppkg/nimsuggest.nim index 4dcae2ea210..c590e9b6821 100644 --- a/nimlsp/nimlsppkg/nimsuggest.nim +++ b/nimlsp/nimlsppkg/nimsuggest.nim @@ -171,7 +171,7 @@ proc processFlags(sug: Suggest; n: ParsedNode) = if identDeprecated or colonDeprecated: sug.flags.incl SuggestFlag.deprecated -proc parsedNodeToSugget(n: ParsedNode; originKind: ParsedNodeKind; module: PSym): Suggest = +proc parsedNodeToSugget(n: ParsedNode; originKind: ParsedNodeKind; module: string): Suggest = if n.kind in {pnkError, pnkEmpty}: return if n.kind notin {pnkConstSection..pnkTypeDef, pnkIdentDefs}: return new(result) @@ -197,7 +197,7 @@ proc parsedNodeToSugget(n: ParsedNode; originKind: ParsedNodeKind; module: PSym) # name.add "," if name != "": - result.qualifiedPath = @[module.name.s, name] + result.qualifiedPath = @[module, name] result.line = token.line.int result.column = token.col.int result.tokenLen = name.len @@ -209,10 +209,11 @@ proc outline(graph: ModuleGraph; fileIdx: FileIndex) = var sug: Suggest var parsedNode: ParsedNode var s: ParsedNode - let m = graph.getModule fileIdx + let name = toFilename(conf, fileIdx) + const Sections = {pnkTypeSection, pnkConstSection, pnkLetSection, pnkVarSection} template suggestIt(parsedNode: ParsedNode; originKind: ParsedNodeKind) = - sug = parsedNodeToSugget(parsedNode, originKind, m) + sug = parsedNodeToSugget(parsedNode, originKind, name) if sug != nil: sug.filepath = toFullPath(conf, fileIdx) conf.suggestionResultHook(sug) @@ -232,27 +233,7 @@ proc outline(graph: ModuleGraph; fileIdx: FileIndex) = proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int, graph: ModuleGraph) = let conf = graph.config - conf.ideCmd = cmd - - var isKnownFile = true - let dirtyIdx = fileInfoIdx(conf, file, isKnownFile) - - if not dirtyfile.isEmpty: msgs.setDirtyFile(conf, dirtyIdx, dirtyfile) - else: msgs.setDirtyFile(conf, dirtyIdx, AbsoluteFile"") - - conf.m.trackPos = newLineInfo(dirtyIdx, line, col) - conf.m.trackPosAttached = false - conf.errorCounter = 0 - var moduleIdx: FileIndex - var needCompile = true - if conf.ideCmd in {ideUse, ideDus} and - dirtyfile.isEmpty: - needCompile = false - if conf.ideCmd == ideOutline and isKnownFile: - needCompile = false - - if needCompile: - executeCmd(cmd, file, dirtyfile, line, col, graph) + executeCmd(cmd, file, dirtyfile, line, col, graph) if conf.ideCmd in {ideUse, ideDus}: let u = graph.findTrackedSym() if u != nil: @@ -260,6 +241,7 @@ proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int, else: stderr.writeLine "found no symbol at position: " & (conf $ conf.m.trackPos) elif conf.ideCmd == ideOutline: + let dirtyIdx = fileInfoIdx(conf, file) outline(graph, dirtyIdx) proc runCmd*(nimsuggest: NimSuggest, cmd: IdeCmd, file, diff --git a/nimlsp/nimlsppkg/suggestlib.nim b/nimlsp/nimlsppkg/suggestlib.nim index cb377277909..09361dcffb6 100644 --- a/nimlsp/nimlsppkg/suggestlib.nim +++ b/nimlsp/nimlsppkg/suggestlib.nim @@ -133,8 +133,3 @@ when isMainModule: echo "outline:" & f let syms = graph.outline(f, f) echo "outline: symbols(" & $syms.len & ")" - - var suggestions = graph.sug(currentSourcePath, currentSourcePath, 86, 16) - echo "Got ", suggestions.len, " suggestions" - for suggestion in suggestions: - echo suggestion \ No newline at end of file From b382c000f3727d8ac369782a07558e294559988d Mon Sep 17 00:00:00 2001 From: bung87 Date: Mon, 18 Sep 2023 02:50:59 +0800 Subject: [PATCH 27/84] refactor --- nimlsp/nimlsp.nim | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index b94b131b08b..9876c1dc988 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -162,7 +162,7 @@ proc main(ins: Stream, outs: Stream) = of "textDocument/completion": textDocumentRequest(message, CompletionParams, req): debugLog location(req) - let suggestions = getNimsuggest(req.fileuri).sug(req.filePath, dirtyfile = req.filestash, + let suggestions = getNimsuggest(req.fileuri).sug(req.filePath, req.filestash, req.rawLine + 1, openFiles.col(req) ) @@ -206,7 +206,7 @@ proc main(ins: Stream, outs: Stream) = of "textDocument/hover": textDocumentRequest(message, TextDocumentPositionParams, req): debugLog location(req) - let suggestions = getNimsuggest(req.fileuri).def(req.filePath, dirtyfile = req.filestash, + let suggestions = getNimsuggest(req.fileuri).def(req.filePath, req.filestash, req.rawLine + 1, openFiles.col(req) ) @@ -232,7 +232,7 @@ proc main(ins: Stream, outs: Stream) = of "textDocument/references": textDocumentRequest(message, ReferenceParams, req): debugLog location(req) - let suggestions = getNimsuggest(req.fileuri).use(req.filePath, dirtyfile = req.filestash, + let suggestions = getNimsuggest(req.fileuri).use(req.filePath, req.filestash, req.rawLine + 1, openFiles.col(req) ) @@ -255,7 +255,7 @@ proc main(ins: Stream, outs: Stream) = of "textDocument/rename": textDocumentRequest(message, RenameParams, req): debugLog location(req) - let suggestions = getNimsuggest(req.fileuri).use(req.filePath, dirtyfile = req.filestash, + let suggestions = getNimsuggest(req.fileuri).use(req.filePath, req.filestash, req.rawLine + 1, openFiles.col(req) ) @@ -284,7 +284,7 @@ proc main(ins: Stream, outs: Stream) = of "textDocument/definition": textDocumentRequest(message, TextDocumentPositionParams, req): debugLog location(req) - let declarations = getNimsuggest(req.fileuri).def(req.filePath, dirtyfile = req.filestash, + let declarations = getNimsuggest(req.fileuri).def(req.filePath, req.filestash, req.rawLine + 1, openFiles.col(req) ) @@ -310,7 +310,7 @@ proc main(ins: Stream, outs: Stream) = let projectFile = openFiles[req.fileuri].projectFile let syms = getNimsuggest(req.fileuri).outline( req.filePath, - dirtyfile = req.filestash + req.filestash ) debugLog "Found outlines: " & $syms.len debugSuggests(syms[0.. Date: Mon, 18 Sep 2023 02:56:56 +0800 Subject: [PATCH 28/84] refactor --- nimlsp/nimlsp.nim | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index 9876c1dc988..a300eb49eee 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -308,10 +308,7 @@ proc main(ins: Stream, outs: Stream) = textDocumentRequest(message, DocumentSymbolParams, req): debugLog req.uriAndStash() let projectFile = openFiles[req.fileuri].projectFile - let syms = getNimsuggest(req.fileuri).outline( - req.filePath, - req.filestash - ) + let syms = getNimsuggest(req.fileuri).outline(req.filePath, req.filestash) debugLog "Found outlines: " & $syms.len debugSuggests(syms[0.. Date: Mon, 18 Sep 2023 03:30:16 +0800 Subject: [PATCH 29/84] refactor --- nimlsp/nimlsp.nim | 22 +++++++++++----------- nimlsp/nimlsppkg/messages.nim | 1 + 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index a300eb49eee..d0698dafd2c 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -102,6 +102,13 @@ proc createMarkupContent(label: string; content: string): MarkupContent = let label = "```nim\n" & label & "\n```\n" result = create(MarkupContent, "markdown", label & rstToMarkdown(content)) +proc createMarkupContent(sug: Suggest): MarkupContent = + var label = sug.qualifiedPath.join(".") + if sug.forth != "": + label &= ": " + label &= sug.forth + createMarkupContent(label, sug.doc) + proc main(ins: Stream, outs: Stream) = # checkVersion(outs) xxx: enable when deployment seperately var message: JsonNode @@ -188,8 +195,9 @@ proc main(ins: Stream, outs: Stream) = completionItems.add create(CompletionItem, label = suggestion.qualifiedPath[^1].strip(chars = {'`'}), kind = some(nimSymToLSPKind(suggestion).int), + tags = some(suggestion.flags.mapIt(it.int)), detail = detail, - documentation = some(suggestion.doc), + documentation = some(createMarkupContent(suggestion)), deprecated = none(bool), preselect = none(bool), sortText = some(fmt"{i:04}"), @@ -216,17 +224,13 @@ proc main(ins: Stream, outs: Stream) = if suggestions.len == 0: resp = newJNull() else: - var label = suggestions[0].qualifiedPath.join(".") - if suggestions[0].forth != "": - label &= ": " - label &= suggestions[0].forth let rangeopt = some(create(Range, create(Position, req.rawLine, req.rawChar), create(Position, req.rawLine, req.rawChar + suggestions[0].qualifiedPath[^1].len) )) - markupContent = createMarkupContent(label, suggestions[0].doc) + markupContent = createMarkupContent(suggestions[0]) resp = create(Hover, markupContent, rangeopt).JsonNode outs.respond(message, resp) of "textDocument/references": @@ -312,7 +316,6 @@ proc main(ins: Stream, outs: Stream) = debugLog "Found outlines: " & $syms.len debugSuggests(syms[0.. Date: Mon, 18 Sep 2023 15:26:47 +0800 Subject: [PATCH 30/84] refactor --- nimlsp/nimlsp.nim | 64 ++++++++++++--------------------- nimlsp/nimlsppkg/nimsuggest.nim | 6 ++-- 2 files changed, 24 insertions(+), 46 deletions(-) diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index d0698dafd2c..fe7f451cf02 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -109,6 +109,26 @@ proc createMarkupContent(sug: Suggest): MarkupContent = label &= sug.forth createMarkupContent(label, sug.doc) +proc createDiagnostic(sug: Suggest): Diagnostic = + let + message = sug.doc + endcolumn = sug.column + message.rfind('\'') - message.find('\'') - 1 + result = create(Diagnostic, + create(Range, + create(Position, sug.line-1, sug.column), + create(Position, sug.line-1, max(sug.column, endcolumn)) + ), + some(case sug.forth: + of "Error": DiagnosticSeverity.Error.int + of "Hint": DiagnosticSeverity.Hint.int + of "Warning": DiagnosticSeverity.Warning.int + else: DiagnosticSeverity.Error.int), + none(int), + some("nimsuggest chk"), + message, + none(seq[DiagnosticRelatedInformation]) + ) + proc main(ins: Stream, outs: Stream) = # checkVersion(outs) xxx: enable when deployment seperately var message: JsonNode @@ -433,28 +453,9 @@ proc main(ins: Stream, outs: Stream) = for diagnostic in diagnostics: if diagnostic.line == 0: continue - if diagnostic.filePath != req.filePath: continue - # Try to guess the size of the identifier - let - message = diagnostic.doc - endcolumn = diagnostic.column + message.rfind('\'') - message.find('\'') - 1 - response.add create(Diagnostic, - create(Range, - create(Position, diagnostic.line-1, diagnostic.column), - create(Position, diagnostic.line-1, max(diagnostic.column, endcolumn)) - ), - some(case diagnostic.forth: - of "Error": DiagnosticSeverity.Error.int - of "Hint": DiagnosticSeverity.Hint.int - of "Warning": DiagnosticSeverity.Warning.int - else: DiagnosticSeverity.Error.int), - none(int), - some("nimsuggest chk"), - message, - none(seq[DiagnosticRelatedInformation]) - ) + response.add createDiagnostic(diagnostic) # Invoke chk on all open files. let projectFile = openFiles[req.fileuri].projectFile @@ -467,30 +468,9 @@ proc main(ins: Stream, outs: Stream) = for diagnostic in diagnostics: if diagnostic.line == 0: continue - if diagnostic.filePath != uriToPath(f): continue - # Try to guess the size of the identifier - let - message = diagnostic.doc - endcolumn = diagnostic.column + message.rfind('\'') - message.find('\'') - 1 - - response.add create( - Diagnostic, - create(Range, - create(Position, diagnostic.line-1, diagnostic.column), - create(Position, diagnostic.line-1, max(diagnostic.column, endcolumn)) - ), - some(case diagnostic.forth: - of "Error": DiagnosticSeverity.Error.int - of "Hint": DiagnosticSeverity.Hint.int - of "Warning": DiagnosticSeverity.Warning.int - else: DiagnosticSeverity.Error.int), - none(int), - some("nimsuggest chk"), - message, - none(seq[DiagnosticRelatedInformation]) - ) + response.add createDiagnostic(diagnostic) let resp = create(PublishDiagnosticsParams, f, response).JsonNode outs.notify("textDocument/publishDiagnostics", resp) let resp = create(PublishDiagnosticsParams, diff --git a/nimlsp/nimlsppkg/nimsuggest.nim b/nimlsp/nimlsppkg/nimsuggest.nim index c590e9b6821..e4c1eda28aa 100644 --- a/nimlsp/nimlsppkg/nimsuggest.nim +++ b/nimlsp/nimlsppkg/nimsuggest.nim @@ -255,8 +255,7 @@ proc runCmd*(nimsuggest: NimSuggest, cmd: IdeCmd, file, if conf.ideCmd == ideKnown: retval.add(Suggest(section: ideKnown, quality: ord(fileInfoKnown(conf, file)))) elif conf.ideCmd == ideProject: - retval.add(Suggest(section: ideProject, - filePath: string conf.projectFull)) + retval.add(Suggest(section: ideProject, filePath: string conf.projectFull)) else: template addReport(report: Report) = let loc = report.location() @@ -274,6 +273,5 @@ proc runCmd*(nimsuggest: NimSuggest, cmd: IdeCmd, file, return doNothing else: conf.structuredReportHook = defaultStructuredReportHook - executeNoHooks(conf.ideCmd, file, dirtyfile, line, col, - nimsuggest.graph) + executeNoHooks(conf.ideCmd, file, dirtyfile, line, col, nimsuggest.graph) return retval From 2c574961a82b4aebb26823072ac041c5d3e26d93 Mon Sep 17 00:00:00 2001 From: bung87 Date: Mon, 18 Sep 2023 16:06:22 +0800 Subject: [PATCH 31/84] refactor --- nimlsp/nimlsp.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index fe7f451cf02..c5a72be4488 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -453,7 +453,7 @@ proc main(ins: Stream, outs: Stream) = for diagnostic in diagnostics: if diagnostic.line == 0: continue - if diagnostic.filePath != req.filePath: + if diagnostic.filePath != uriToPath(req.fileuri): continue response.add createDiagnostic(diagnostic) From 6fcdc2e3b99738e86b9f273d7479c113086761b0 Mon Sep 17 00:00:00 2001 From: bung87 Date: Mon, 18 Sep 2023 17:37:59 +0800 Subject: [PATCH 32/84] refactor structuredReportHook --- nimlsp/nimlsp.nim | 5 ++--- nimlsp/nimlsppkg/nimsuggest.nim | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index c5a72be4488..d8f36a008f3 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -331,7 +331,6 @@ proc main(ins: Stream, outs: Stream) = of "textDocument/documentSymbol": textDocumentRequest(message, DocumentSymbolParams, req): debugLog req.uriAndStash() - let projectFile = openFiles[req.fileuri].projectFile let syms = getNimsuggest(req.fileuri).outline(req.filePath, req.filestash) debugLog "Found outlines: " & $syms.len debugSuggests(syms[0.. Date: Mon, 18 Sep 2023 18:01:33 +0800 Subject: [PATCH 33/84] refactor --- nimlsp/nimlsp.nim | 8 +++----- nimlsp/nimlsppkg/nimsuggest.nim | 1 - 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index d8f36a008f3..55afca4154c 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -421,7 +421,6 @@ proc main(ins: Stream, outs: Stream) = openFiles[req.fileuri].fingerTable.add line.createUTFMapping() file.writeLine line file.close() - # Notify nimsuggest about a file modification. discard getNimsuggest(req.fileuri).mod(req.filePath, req.filestash) of "textDocument/didClose": @@ -456,22 +455,21 @@ proc main(ins: Stream, outs: Stream) = continue response.add createDiagnostic(diagnostic) - # Invoke chk on all open files. + # Invoke chk on other open files. let projectFile = openFiles[req.fileuri].projectFile for f in projectFiles[projectFile].openFiles.items: + if f == req.fileuri: continue let diagnostics = getNimsuggest(f).chk(req.filePath, req.filestash) debugLog "Got diagnostics: " & $diagnostics.len debugSuggests(diagnostics[0 ..< min(diagnostics.len, 10)]) - var response = newSeq[Diagnostic]() for diagnostic in diagnostics: if diagnostic.line == 0: continue if diagnostic.filePath != uriToPath(f): continue response.add createDiagnostic(diagnostic) - let resp = create(PublishDiagnosticsParams, f, response).JsonNode - outs.notify("textDocument/publishDiagnostics", resp) + let resp = create(PublishDiagnosticsParams, req.fileuri, response).JsonNode diff --git a/nimlsp/nimlsppkg/nimsuggest.nim b/nimlsp/nimlsppkg/nimsuggest.nim index adbeebd011f..b2932ba47ca 100644 --- a/nimlsp/nimlsppkg/nimsuggest.nim +++ b/nimlsp/nimlsppkg/nimsuggest.nim @@ -275,7 +275,6 @@ proc runCmd*(nimsuggest: NimSuggest, cmd: IdeCmd, file, report.kind in {rsemProcessing, rsemProcessingStmt}: # skip processing statements return - let info = report.location().get(unknownLineInfo) addReport(report) else: discard From 39d5603d872d71b4a61b824127f5b513b78d64c2 Mon Sep 17 00:00:00 2001 From: bung87 Date: Mon, 18 Sep 2023 19:13:09 +0800 Subject: [PATCH 34/84] refactor --- compiler/ast/reports.nim | 9 ++++++++- nimlsp/nimlsppkg/nimsuggest.nim | 15 ++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/compiler/ast/reports.nim b/compiler/ast/reports.nim index 808b1d08d52..489bbab2034 100644 --- a/compiler/ast/reports.nim +++ b/compiler/ast/reports.nim @@ -18,7 +18,7 @@ ## `compilerDebugCompilerReportStatistics`: output stats of counts for various ## report kinds -import std/[options] +import std/[options, hashes] import compiler/ast/[ @@ -308,3 +308,10 @@ func actualType*(r: SemReport): PType = r.typeMismatch[0].actualType func formalType*(r: SemReport): PType = r.typeMismatch[0].formalType func formalTypeKind*(r: SemReport): set[TTypeKind] = r.typeMismatch[0].formalTypeKind func symstr*(r: SemReport | VMReport): string = r.sym.name.s +func hash*(x: Report): Hash = + var h: Hash = 0 + let loc = x.location().get(unknownLineInfo) + h = h !& hash(loc) + h = h !& hash(x.kind) + h = h !& hash(x.category) + result = !$h diff --git a/nimlsp/nimlsppkg/nimsuggest.nim b/nimlsp/nimlsppkg/nimsuggest.nim index b2932ba47ca..f28c4ab7fe9 100644 --- a/nimlsp/nimlsppkg/nimsuggest.nim +++ b/nimlsp/nimlsppkg/nimsuggest.nim @@ -1,7 +1,7 @@ when not defined(nimcore): {.error: "nimcore MUST be defined for Nim's core tooling".} -import std/[os, net] +import std/[os, net, sets] import std/options as stdOptions import compiler/ast/[ @@ -37,7 +37,8 @@ import from compiler/ast/reports import Report, category, kind, - location + location, + hash from compiler/front/main import customizeForBackend @@ -257,13 +258,17 @@ proc runCmd*(nimsuggest: NimSuggest, cmd: IdeCmd, file, elif conf.ideCmd == ideProject: retval.add(Suggest(section: ideProject, filePath: string conf.projectFull)) else: + var s: HashSet[int] template addReport(report: Report) = let loc = report.location() if stdOptions.isSome(loc): let info = loc.get() - retval.add(Suggest(section: ideChk, filePath: toFullPath(conf,info), - line: toLinenumber(info), column: toColumn(info), - doc: conf.reportShort(report), forth: $severity(conf, report))) + let h = hash(report) + if h notin s: + s.incl h + retval.add(Suggest(section: ideChk, filePath: toFullPath(conf,info), + line: toLinenumber(info), column: toColumn(info), + doc: conf.reportShort(report), forth: $severity(conf, report))) if conf.ideCmd == ideChk: for cm in nimsuggest.cachedMsgs: addReport(cm) From 4ee6f712eafd4eaa932061e190bec75ae9f49419 Mon Sep 17 00:00:00 2001 From: bung87 Date: Mon, 18 Sep 2023 20:45:34 +0800 Subject: [PATCH 35/84] refactor --- nimlsp/nimlsp.nim | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index 55afca4154c..d0c24840054 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -77,6 +77,10 @@ proc notify(outs: Stream, notification: string, data: JsonNode) = let resp = create(NotificationMessage, "2.0", notification, some(data)).JsonNode outs.sendJson resp +proc publishDiagnostics(outs: Stream, uri:string, diagnostics: seq[Diagnostic]) = + let data = create(PublishDiagnosticsParams, uri, diagnostics).JsonNode + notify(outs, "textDocument/publishDiagnostics", data) + template getNimsuggest(fileuri: string): Nimsuggest = projectFiles[openFiles[fileuri].projectFile].nimsuggest @@ -447,13 +451,13 @@ proc main(ins: Stream, outs: Stream) = let diagnostics = getNimsuggest(req.fileuri).chk(req.filePath, req.filestash) debugLog "Got diagnostics: " & $diagnostics.len debugSuggests(diagnostics[0.. Date: Mon, 18 Sep 2023 22:13:15 +0800 Subject: [PATCH 36/84] refactor --- nimlsp/nimlsppkg/nimsuggest.nim | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/nimlsp/nimlsppkg/nimsuggest.nim b/nimlsp/nimlsppkg/nimsuggest.nim index f28c4ab7fe9..7dfc16a1afc 100644 --- a/nimlsp/nimlsppkg/nimsuggest.nim +++ b/nimlsp/nimlsppkg/nimsuggest.nim @@ -71,13 +71,6 @@ proc initNimSuggest*(project: string, nimPath: string = ""): NimSuggest = add(conf.searchPaths, conf.libpath) conf.setErrorMaxHighMaybe - # do not print errors, but log them - conf.writelnHook = proc(conf: ConfigRef, msg: string, flags: MsgFlags) = - discard - conf.structuredReportHook = proc (conf: ConfigRef, report: Report): TErrorHandling = - if report.kind notin {rsemProcessing, rsemProcessingStmt}: - # pre-filter to save memory - cachedMsgs.add(report) # compile the project before showing any input so that we already # can answer questions right away: @@ -86,8 +79,6 @@ proc initNimSuggest*(project: string, nimPath: string = ""): NimSuggest = proc mockCmdLine(pass: TCmdLinePass, argv: openArray[string]; conf: ConfigRef) = - conf.writeHook = proc(conf: ConfigRef, s: string, flags: MsgFlags) = discard - let a = unixToNativePath(project) if dirExists(a) and not fileExists(a.addFileExt("nim")): conf.projectName = findProjectNimFile(conf, a) @@ -95,13 +86,19 @@ proc initNimSuggest*(project: string, nimPath: string = ""): NimSuggest = if conf.projectName.len == 0: conf.projectName = a else: conf.projectName = a + proc reportHook(conf: ConfigRef, report: Report): TErrorHandling = + if report.kind notin {rsemProcessing, rsemProcessingStmt}: + # pre-filter to save memory + cachedMsgs.add(report) let cache = newIdentCache() - conf = newConfigRef(cli_reporter.reportHook) + conf = newConfigRef(reportHook) self = NimProg( suggestMode: true, processCmdLine: mockCmdLine ) + conf.writeHook = proc(conf: ConfigRef, s: string, flags: MsgFlags) = discard + conf.writelnHook = proc(conf: ConfigRef, s: string, flags: MsgFlags) = discard conf.astDiagToLegacyReport = cli_reporter.legacyReportBridge self.initDefinesProg(conf, "nimsuggest") From 507bf315ca21f6effe1768a1d0db3a10b83f0a7d Mon Sep 17 00:00:00 2001 From: bung87 Date: Mon, 18 Sep 2023 23:43:32 +0800 Subject: [PATCH 37/84] refactor --- nimlsp/nimlsppkg/nimsuggest.nim | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/nimlsp/nimlsppkg/nimsuggest.nim b/nimlsp/nimlsppkg/nimsuggest.nim index 7dfc16a1afc..0581ee6d3de 100644 --- a/nimlsp/nimlsppkg/nimsuggest.nim +++ b/nimlsp/nimlsppkg/nimsuggest.nim @@ -271,16 +271,16 @@ proc runCmd*(nimsuggest: NimSuggest, cmd: IdeCmd, file, for cm in nimsuggest.cachedMsgs: addReport(cm) nimsuggest.cachedMsgs.setLen 0 conf.structuredReportHook = proc (conf: ConfigRef, report: Report): TErrorHandling = + result = doNothing case report.category - of repParser, repLexer, repSem, repVM: - if report.category == repSem and - report.kind in {rsemProcessing, rsemProcessingStmt}: - # skip processing statements - return - addReport(report) - else: discard - - return doNothing + of repParser, repLexer, repSem, repVM: + if report.category == repSem and + report.kind in {rsemProcessing, rsemProcessingStmt}: + # skip processing statements + return + addReport(report) + else: discard + else: conf.structuredReportHook = defaultStructuredReportHook executeNoHooks(conf.ideCmd, file, dirtyfile, line, col, nimsuggest.graph) From 448be9e0509062387413971720e10f018f9344c8 Mon Sep 17 00:00:00 2001 From: bung87 Date: Tue, 19 Sep 2023 03:21:16 +0800 Subject: [PATCH 38/84] refactor --- compiler/tools/suggest.nim | 4 +-- nimlsp/nimlsp.nim | 63 ++++++++++++++++++--------------- nimlsp/nimlsppkg/nimsuggest.nim | 21 +++++------ 3 files changed, 47 insertions(+), 41 deletions(-) diff --git a/compiler/tools/suggest.nim b/compiler/tools/suggest.nim index 90bc42243e1..52113bb6bdb 100644 --- a/compiler/tools/suggest.nim +++ b/compiler/tools/suggest.nim @@ -485,7 +485,7 @@ proc executeCmd*(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int; conf.m.trackPosAttached = false conf.errorCounter = 0 if not isKnownFile: - graph.compileProject(dirtyIdx) + discard graph.compileModule(dirtyIdx, {}) if conf.ideCmd in {ideUse, ideDus} and dirtyfile.isEmpty: discard "no need to recompile anything" @@ -498,7 +498,7 @@ proc executeCmd*(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int; graph.vm = nil if conf.ideCmd != ideMod: if isKnownFile: - graph.compileProject(modIdx) + discard graph.compileModule(dirtyIdx, {}) when defined(nimsuggest): diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index d0c24840054..54561066f34 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -45,7 +45,7 @@ template rawChar(p: untyped): int = template col(openFiles: typeof openFiles; p: untyped): int = openFiles[p.fileuri].fingerTable[p.rawLine].utf16to8(p.rawChar) -template textDocumentRequest(message: typed; kind: typed; name, body: untyped): untyped = +template textDocumentRequest(message, kind: typed; name, body: untyped): untyped = if message.hasKey("params"): let p = message["params"] var name = kind(p) @@ -54,32 +54,35 @@ template textDocumentRequest(message: typed; kind: typed; name, body: untyped): else: debugLog("Unable to parse data as ", kind) -template textDocumentNotification(message: typed; kind: typed; name, body: untyped): untyped = +template textDocumentNotification(message, kind: typed; name, body: untyped): untyped = if message.hasKey("params"): var p = message["params"] var name = kind(p) if p.isValid(kind, allowExtra = false): - if "languageId" notin name["textDocument"] or name["textDocument"]["languageId"].getStr == "nim": + if "languageId" notin name["textDocument"] or + name["textDocument"]["languageId"].getStr == "nim": body else: debugLog("Unable to parse data as ", kind) -proc respond(outs: Stream, request: JsonNode, data: JsonNode) = - let resp = create(ResponseMessage, "2.0", parseId(request["id"]), some(data), none(ResponseError)).JsonNode - outs.sendJson resp +proc respond(outs: Stream, req: JsonNode, data: JsonNode) = + let id = parseId(req["id"]) + let resp = create(ResponseMessage, "2.0", id, some(data), none(ResponseError)) + outs.sendJson resp.JsonNode -proc error(outs: Stream, request: JsonNode, errorCode: ErrorCode, message: string, data: JsonNode) = - let err = some(create(ResponseError, ord(errorCode), message, data)) - let resp = create(ResponseMessage, "2.0", parseId(request{"id"}), none(JsonNode), err).JsonNode - outs.sendJson resp +proc error(outs: Stream, req: JsonNode, code: ErrorCode, msg: string, data: JsonNode) = + let err = some(create(ResponseError, ord(code), msg, data)) + let id = parseId(req{"id"}) + let resp = create(ResponseMessage, "2.0", id, none(JsonNode), err) + outs.sendJson resp.JsonNode proc notify(outs: Stream, notification: string, data: JsonNode) = - let resp = create(NotificationMessage, "2.0", notification, some(data)).JsonNode - outs.sendJson resp + let resp = create(NotificationMessage, "2.0", notification, some(data)) + outs.sendJson resp.JsonNode proc publishDiagnostics(outs: Stream, uri:string, diagnostics: seq[Diagnostic]) = - let data = create(PublishDiagnosticsParams, uri, diagnostics).JsonNode - notify(outs, "textDocument/publishDiagnostics", data) + let data = create(PublishDiagnosticsParams, uri, diagnostics) + notify(outs, "textDocument/publishDiagnostics", data.JsonNode) template getNimsuggest(fileuri: string): Nimsuggest = projectFiles[openFiles[fileuri].projectFile].nimsuggest @@ -267,13 +270,13 @@ proc main(ins: Stream, outs: Stream) = debugLog "Found suggestions: " & $suggestions.len debugSuggests(suggestions[0 ..< min(suggestions.len, 10)]) var response = newJarray() - for suggestion in suggestions: - if suggestion.section == ideUse or req["context"]["includeDeclaration"].getBool: + for sug in suggestions: + if sug.section == ideUse or req["context"]["includeDeclaration"].getBool: response.add create(Location, - "file://" & pathToUri(suggestion.filepath), + "file://" & pathToUri(sug.filepath), create(Range, - create(Position, suggestion.line-1, suggestion.column), - create(Position, suggestion.line-1, suggestion.column + suggestion.qualifiedPath[^1].len) + create(Position, sug.line-1, sug.column), + create(Position, sug.line-1, sug.column + sug.qualifiedPath[^1].len) ) ).JsonNode if response.len == 0: @@ -294,13 +297,13 @@ proc main(ins: Stream, outs: Stream) = resp = newJNull() else: var textEdits = newJObject() - for suggestion in suggestions: - let uri = "file://" & pathToUri(suggestion.filepath) + for sug in suggestions: + let uri = "file://" & pathToUri(sug.filepath) if uri notin textEdits: textEdits[uri] = newJArray() textEdits[uri].add create(TextEdit, create(Range, - create(Position, suggestion.line-1, suggestion.column), - create(Position, suggestion.line-1, suggestion.column + suggestion.qualifiedPath[^1].len) + create(Position, sug.line-1, sug.column), + create(Position, sug.line-1, sug.column + sug.qualifiedPath[^1].len) ), req["newName"].getStr ).JsonNode @@ -323,12 +326,12 @@ proc main(ins: Stream, outs: Stream) = resp = newJNull() else: resp = newJarray() - for declaration in declarations: + for decl in declarations: resp.add create(Location, - "file://" & pathToUri(declaration.filepath), + "file://" & pathToUri(decl.filepath), create(Range, - create(Position, declaration.line-1, declaration.column), - create(Position, declaration.line-1, declaration.column + declaration.qualifiedPath[^1].len) + create(Position, decl.line-1, decl.column), + create(Position, decl.line-1, decl.column + decl.qualifiedPath[^1].len) ) ).JsonNode outs.respond(message, resp) @@ -364,7 +367,8 @@ proc main(ins: Stream, outs: Stream) = of "textDocument/signatureHelp": textDocumentRequest(message, TextDocumentPositionParams, req): debugLog location(req) - let suggestions = getNimsuggest(req.fileuri).con(req.filePath, req.filestash, req.rawLine + 1, req.rawChar) + let suggestions = getNimsuggest(req.fileuri).con(req.filePath, req.filestash, + req.rawLine + 1, req.rawChar) var signatures = newSeq[SignatureInformation]() for suggestion in suggestions: var label = suggestion.qualifiedPath.join(".") @@ -409,7 +413,8 @@ proc main(ins: Stream, outs: Stream) = if projectFile notin projectFiles: debugLog "Initialising with project file: ", projectFile - projectFiles[projectFile] = (nimsuggest: initNimsuggest(projectFile, nimpath), openFiles: initOrderedSet[string]()) + projectFiles[projectFile] = (nimsuggest: initNimsuggest(projectFile, nimpath), + openFiles: initOrderedSet[string]()) projectFiles[projectFile].openFiles.incl(req.fileuri) for line in req["textDocument"]["text"].getStr.splitLines: diff --git a/nimlsp/nimlsppkg/nimsuggest.nim b/nimlsp/nimlsppkg/nimsuggest.nim index 0581ee6d3de..6b9da77bd87 100644 --- a/nimlsp/nimlsppkg/nimsuggest.nim +++ b/nimlsp/nimlsppkg/nimsuggest.nim @@ -54,8 +54,8 @@ type idle: int cachedMsgs: CachedMsgs -proc defaultStructuredReportHook(conf: ConfigRef, report: Report): TErrorHandling = - discard +proc defaultReportHook(conf: ConfigRef, report: Report): TErrorHandling = + doNothing proc initNimSuggest*(project: string, nimPath: string = ""): NimSuggest = var retval: ModuleGraph @@ -87,6 +87,7 @@ proc initNimSuggest*(project: string, nimPath: string = ""): NimSuggest = else: conf.projectName = a proc reportHook(conf: ConfigRef, report: Report): TErrorHandling = + result = doNothing if report.kind notin {rsemProcessing, rsemProcessingStmt}: # pre-filter to save memory cachedMsgs.add(report) @@ -255,17 +256,17 @@ proc runCmd*(nimsuggest: NimSuggest, cmd: IdeCmd, file, elif conf.ideCmd == ideProject: retval.add(Suggest(section: ideProject, filePath: string conf.projectFull)) else: - var s: HashSet[int] + # var s: HashSet[int] template addReport(report: Report) = let loc = report.location() if stdOptions.isSome(loc): let info = loc.get() - let h = hash(report) - if h notin s: - s.incl h - retval.add(Suggest(section: ideChk, filePath: toFullPath(conf,info), - line: toLinenumber(info), column: toColumn(info), - doc: conf.reportShort(report), forth: $severity(conf, report))) + # let h = hash(report) + # if h notin s: + # s.incl h + retval.add(Suggest(section: ideChk, filePath: toFullPath(conf,info), + line: toLinenumber(info), column: toColumn(info), + doc: conf.reportShort(report), forth: $severity(conf, report))) if conf.ideCmd == ideChk: for cm in nimsuggest.cachedMsgs: addReport(cm) @@ -282,6 +283,6 @@ proc runCmd*(nimsuggest: NimSuggest, cmd: IdeCmd, file, else: discard else: - conf.structuredReportHook = defaultStructuredReportHook + conf.structuredReportHook = defaultReportHook executeNoHooks(conf.ideCmd, file, dirtyfile, line, col, nimsuggest.graph) return retval From 731b67db496c65f77953965823977fec86e74610 Mon Sep 17 00:00:00 2001 From: bung87 Date: Tue, 19 Sep 2023 03:51:24 +0800 Subject: [PATCH 39/84] refactor --- nimlsp/nimlsp.nim | 42 ++++++++++++++++-------------------------- 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index 54561066f34..eae847e2d34 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -84,6 +84,12 @@ proc publishDiagnostics(outs: Stream, uri:string, diagnostics: seq[Diagnostic]) let data = create(PublishDiagnosticsParams, uri, diagnostics) notify(outs, "textDocument/publishDiagnostics", data.JsonNode) +proc createRange(line, col, length: int): Range = + create(Range, + create(Position, line, col), + create(Position, line, col + length) + ) + template getNimsuggest(fileuri: string): Nimsuggest = projectFiles[openFiles[fileuri].projectFile].nimsuggest @@ -148,7 +154,8 @@ proc main(ins: Stream, outs: Stream) = if isValid(message, RequestMessage): debugLog "Got valid Request message of type ", message["method"].getStr if not initialized and message["method"].getStr != "initialize": - outs.error(message, ServerNotInitialized, "Unable to accept requests before being initialized", newJNull()) + const msg = "Unable to accept requests before being initialized" + outs.error(message, ServerNotInitialized, msg, newJNull()) continue case message["method"].getStr: of "shutdown": @@ -252,11 +259,8 @@ proc main(ins: Stream, outs: Stream) = resp = newJNull() else: let - rangeopt = - some(create(Range, - create(Position, req.rawLine, req.rawChar), - create(Position, req.rawLine, req.rawChar + suggestions[0].qualifiedPath[^1].len) - )) + length = suggestions[0].qualifiedPath[^1].len + rangeopt = some(createRange(req.rawLine, req.rawChar, length)) markupContent = createMarkupContent(suggestions[0]) resp = create(Hover, markupContent, rangeopt).JsonNode outs.respond(message, resp) @@ -274,10 +278,7 @@ proc main(ins: Stream, outs: Stream) = if sug.section == ideUse or req["context"]["includeDeclaration"].getBool: response.add create(Location, "file://" & pathToUri(sug.filepath), - create(Range, - create(Position, sug.line-1, sug.column), - create(Position, sug.line-1, sug.column + sug.qualifiedPath[^1].len) - ) + createRange(sug.line-1, sug.column, sug.qualifiedPath[^1].len) ).JsonNode if response.len == 0: outs.respond(message, newJNull()) @@ -301,10 +302,8 @@ proc main(ins: Stream, outs: Stream) = let uri = "file://" & pathToUri(sug.filepath) if uri notin textEdits: textEdits[uri] = newJArray() - textEdits[uri].add create(TextEdit, create(Range, - create(Position, sug.line-1, sug.column), - create(Position, sug.line-1, sug.column + sug.qualifiedPath[^1].len) - ), + textEdits[uri].add create(TextEdit, + createRange(sug.line-1, sug.column, sug.qualifiedPath[^1].len), req["newName"].getStr ).JsonNode resp = create(WorkspaceEdit, @@ -329,10 +328,7 @@ proc main(ins: Stream, outs: Stream) = for decl in declarations: resp.add create(Location, "file://" & pathToUri(decl.filepath), - create(Range, - create(Position, decl.line-1, decl.column), - create(Position, decl.line-1, decl.column + decl.qualifiedPath[^1].len) - ) + createRange(decl.line-1, decl.column, decl.qualifiedPath[^1].len) ).JsonNode outs.respond(message, resp) of "textDocument/documentSymbol": @@ -354,14 +350,8 @@ proc main(ins: Stream, outs: Stream) = some(symKindToString(sym.symKind)), nimSymToLSPKind(sym.symKind).int, some(sym.flags.mapIt(it.int)), - create(Range, - create(Position, sym.line-1, sym.column), - create(Position, sym.line-1, sym.column + sym.tokenLen) - ), - create(Range, - create(Position, sym.line-1, sym.column), - create(Position, sym.line-1, sym.column + sym.tokenLen) - ), + createRange(sym.line-1, sym.column, sym.tokenLen), + createRange(sym.line-1, sym.column, sym.tokenLen), none(seq[DocumentSymbol])).JsonNode outs.respond(message, resp) of "textDocument/signatureHelp": From f9aaecf8e7757cb7dbc8963c136f5a9acd16bb4a Mon Sep 17 00:00:00 2001 From: bung87 Date: Tue, 19 Sep 2023 15:51:43 +0800 Subject: [PATCH 40/84] handle cachedMsgs;refactor --- compiler/ast/reports.nim | 9 +-------- nimlsp/nimlsp.nim | 13 ++++++++++++- nimlsp/nimlsppkg/nimsuggest.nim | 30 ++++++++++++++++++++---------- nimlsp/nimlsppkg/suggestlib.nim | 3 +++ 4 files changed, 36 insertions(+), 19 deletions(-) diff --git a/compiler/ast/reports.nim b/compiler/ast/reports.nim index 489bbab2034..808b1d08d52 100644 --- a/compiler/ast/reports.nim +++ b/compiler/ast/reports.nim @@ -18,7 +18,7 @@ ## `compilerDebugCompilerReportStatistics`: output stats of counts for various ## report kinds -import std/[options, hashes] +import std/[options] import compiler/ast/[ @@ -308,10 +308,3 @@ func actualType*(r: SemReport): PType = r.typeMismatch[0].actualType func formalType*(r: SemReport): PType = r.typeMismatch[0].formalType func formalTypeKind*(r: SemReport): set[TTypeKind] = r.typeMismatch[0].formalTypeKind func symstr*(r: SemReport | VMReport): string = r.sym.name.s -func hash*(x: Report): Hash = - var h: Hash = 0 - let loc = x.location().get(unknownLineInfo) - h = h !& hash(loc) - h = h !& hash(x.kind) - h = h !& hash(x.category) - result = !$h diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index eae847e2d34..f778e233421 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -6,7 +6,7 @@ include nimlsppkg/[messages, messageenums] const # This is used to explicitly set the default source path explicitSourcePath {.strdefine.} = getCurrentCompilerExe().parentDir.parentDir - VerionMisMatch = "Current Nim version $1 does not match the one NimLSP is built against $2" + VerionMisMatch = "Current Nim version $1 does not match the NimLSP is built against $2" var nimpath = explicitSourcePath @@ -411,6 +411,17 @@ proc main(ins: Stream, outs: Stream) = openFiles[req.fileuri].fingerTable.add line.createUTFMapping() file.writeLine line file.close() + let diagnostics = getNimsuggest(req.fileuri).fetchCachedReports(req.filePath) + debugLog "Got cached diagnostics: " & $diagnostics.len + debugSuggests(diagnostics[0.. Date: Tue, 19 Sep 2023 16:34:55 +0800 Subject: [PATCH 41/84] refactor --- nimlsp/nimlsppkg/nimsuggest.nim | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/nimlsp/nimlsppkg/nimsuggest.nim b/nimlsp/nimlsppkg/nimsuggest.nim index a2af96bc0c6..a217122b21d 100644 --- a/nimlsp/nimlsppkg/nimsuggest.nim +++ b/nimlsp/nimlsppkg/nimsuggest.nim @@ -241,23 +241,26 @@ proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int, let dirtyIdx = fileInfoIdx(conf, file) outline(graph, dirtyIdx) -proc fetchCachedReports*(nimsuggest: NimSuggest, file: AbsoluteFile): seq[Suggest] = - let rLen = nimsuggest.cachedMsgs.len +proc reportToSuggest(conf: ConfigRef, info: TLineInfo, r: Report): Suggest = + Suggest(section: ideChk, filePath: toFullPath(conf, info), + line: toLinenumber(info), column: toColumn(info), + doc: conf.reportShort(r), forth: $severity(conf, r)) + +proc fetchCachedReports*(ins: NimSuggest, file: AbsoluteFile): seq[Suggest] = + let rLen = ins.cachedMsgs.len if rLen == 0: return - let conf = nimsuggest.graph.config + let conf = ins.graph.config let fileIdx = fileInfoIdx(conf, file) var outs: seq[int] = @[] - for i, report in nimsuggest.cachedMsgs: + for i, report in ins.cachedMsgs: let loc = report.location() if stdOptions.isSome(loc): let info = loc.get() if info.fileIndex == fileIdx: outs.add i - result.add(Suggest(section: ideChk, filePath: toFullPath(conf,info), - line: toLinenumber(info), column: toColumn(info), - doc: conf.reportShort(report), forth: $severity(conf, report))) + result.add(reportToSuggest(conf, info, report)) for i in countdown(outs.len - 1, 0): - nimsuggest.cachedMsgs.delete(outs[i]) + ins.cachedMsgs.delete(outs[i]) proc runCmd*(nimsuggest: NimSuggest, cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int): seq[Suggest] = @@ -276,9 +279,7 @@ proc runCmd*(nimsuggest: NimSuggest, cmd: IdeCmd, file, let loc = report.location() if stdOptions.isSome(loc): let info = loc.get() - retval.add(Suggest(section: ideChk, filePath: toFullPath(conf,info), - line: toLinenumber(info), column: toColumn(info), - doc: conf.reportShort(report), forth: $severity(conf, report))) + retval.add(reportToSuggest(conf, info, report)) if conf.ideCmd == ideChk: conf.structuredReportHook = proc (conf: ConfigRef, report: Report): TErrorHandling = From 3d49eecd11654e29c522be29bdaa0dd421128bfa Mon Sep 17 00:00:00 2001 From: bung87 Date: Wed, 20 Sep 2023 00:35:17 +0800 Subject: [PATCH 42/84] signatureHelp doc rstToMarkdown --- nimlsp/nimlsp.nim | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index f778e233421..01c7d0eee20 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -367,7 +367,8 @@ proc main(ins: Stream, outs: Stream) = label &= suggestion.forth signatures.add create(SignatureInformation, label = label, - documentation = some(suggestion.doc), + documentation = some(rstToMarkdown(suggestion.doc)), + # TODO: fill parameters info parameters = none(seq[ParameterInformation]) ) let resp = create(SignatureHelp, From 00ec8e1fa3ac8b373bb746c605f02cd6732e63f3 Mon Sep 17 00:00:00 2001 From: bung87 Date: Wed, 20 Sep 2023 00:35:32 +0800 Subject: [PATCH 43/84] add more todos --- nimlsp/README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nimlsp/README.rst b/nimlsp/README.rst index b9f2aa71800..3704efb0b97 100644 --- a/nimlsp/README.rst +++ b/nimlsp/README.rst @@ -82,6 +82,8 @@ Status LSP Command ☑ DONE textDocument/signatureHelp ☑ DONE textDocument/publishDiagnostics ☐ TODO workspace/symbol +☐ TODO `$/progress` +☐ TODO `textDocument/codeAction` ====== ================================ From c3ebcb71db51d09e25ef33be0aac843d8dd3d474 Mon Sep 17 00:00:00 2001 From: bung87 Date: Thu, 21 Sep 2023 01:31:13 +0800 Subject: [PATCH 44/84] progress handle --- nimlsp/nimlsp.nim | 25 ++++++++++++++++++++++++- nimlsp/nimlsppkg/messages.nim | 19 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index 01c7d0eee20..70fd0a36949 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -70,6 +70,10 @@ proc respond(outs: Stream, req: JsonNode, data: JsonNode) = let resp = create(ResponseMessage, "2.0", id, some(data), none(ResponseError)) outs.sendJson resp.JsonNode +proc request(outs: Stream, id: string, meth: string, data: JsonNode) = + let resp = create(RequestMessage, "2.0", id, meth, some(data)) + outs.sendJson resp.JsonNode + proc error(outs: Stream, req: JsonNode, code: ErrorCode, msg: string, data: JsonNode) = let err = some(create(ResponseError, ord(code), msg, data)) let id = parseId(req{"id"}) @@ -84,6 +88,25 @@ proc publishDiagnostics(outs: Stream, uri:string, diagnostics: seq[Diagnostic]) let data = create(PublishDiagnosticsParams, uri, diagnostics) notify(outs, "textDocument/publishDiagnostics", data.JsonNode) +proc workDoneProgressCreate(id: string, token: string) = + # https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#serverInitiatedProgress + let data = create(WorkDoneProgressCreateParams, token) + outs.request(id, "window/workDoneProgress/create", data.JsonNode) + +proc progressBegin(outs: Stream, title: string, cancellable = none(bool), + message = none(string), percentage = none(int)) = + let data = create(WorkDoneProgressBegin, "begin", title, cancellable, message, percentage) + outs.notify("$/progress", data.JsonNode) + +proc progressReport(outs: Stream, cancellable = none(bool), + message = none(string), percentage = none(int)) = + let data = create(WorkDoneProgressReport, "report", cancellable, message, percentage) + outs.notify("$/progress", data.JsonNode) + +proc progressEnd(outs: Stream, message = none(string)) = + let data = create(WorkDoneProgressEnd, "end", message) + outs.notify("$/progress", data.JsonNode) + proc createRange(line, col, length: int): Range = create(Range, create(Position, line, col), @@ -163,6 +186,7 @@ proc main(ins: Stream, outs: Stream) = outs.respond(message, resp) gotShutdown = true of "initialize": + # xxx handle InitializeParams initialized = true let resp = create(InitializeResult, create(ServerCapabilities, textDocumentSync = some(create(TextDocumentSyncOptions, @@ -401,7 +425,6 @@ proc main(ins: Stream, outs: Stream) = projectFile = getProjectFile(uriToPath(req.fileuri)) debugLog req.uriAndStash() openFiles[req.fileuri] = (projectFile: projectFile, fingerTable: @[]) - if projectFile notin projectFiles: debugLog "Initialising with project file: ", projectFile projectFiles[projectFile] = (nimsuggest: initNimsuggest(projectFile, nimpath), diff --git a/nimlsp/nimlsppkg/messages.nim b/nimlsp/nimlsppkg/messages.nim index ce0f2cc09e2..90de361ffe0 100644 --- a/nimlsp/nimlsppkg/messages.nim +++ b/nimlsp/nimlsppkg/messages.nim @@ -604,3 +604,22 @@ jsonSchema: textDocument: TextDocumentIdentifier position: Position newName: string + + WorkDoneProgressCreateParams: + token: int or string + + WorkDoneProgressBegin: + kind: string + title: string + cancellable ?: bool + message ?: string + percentage ?: int + + WorkDoneProgressReport: + kind: string + cancellable ?: bool + message ?: string + percentage ?: int # 0 .. 100 + + WorkDoneProgressEnd: + message ?: string From df97726dc23462b67cb2b2bec6d00aa9ebb2ce63 Mon Sep 17 00:00:00 2001 From: bung87 Date: Fri, 22 Sep 2023 11:43:02 +0800 Subject: [PATCH 45/84] merge --- compiler/ast/ast_parsed_types.nim | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/ast/ast_parsed_types.nim b/compiler/ast/ast_parsed_types.nim index 4a93c197bb9..391d918bf80 100644 --- a/compiler/ast/ast_parsed_types.nim +++ b/compiler/ast/ast_parsed_types.nim @@ -173,7 +173,6 @@ type pnkExportExceptStmt pnkTypeSection pnkConstSection - pnkTypeSection pnkLetSection pnkVarSection pnkProcDef From adbeadeb3f5e3bc8998e7148515a978f5b9841eb Mon Sep 17 00:00:00 2001 From: bung87 Date: Fri, 22 Sep 2023 11:44:37 +0800 Subject: [PATCH 46/84] merege --- compiler/tools/suggest.nim | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/tools/suggest.nim b/compiler/tools/suggest.nim index 52113bb6bdb..be23095bba8 100644 --- a/compiler/tools/suggest.nim +++ b/compiler/tools/suggest.nim @@ -194,10 +194,10 @@ proc symToSuggest(g: ModuleGraph; s: PSym, isLocal: bool, section: IdeCmd, info: result.filePath = toFullPath(g.config, infox) result.line = toLinenumber(infox) result.column = toColumn(infox) - if section in {ideHighlight}: - result.tokenLen = getTokenLenFromSource(g.config, s.name.s, infox) - else: - result.tokenLen = s.name.s.len + result.tokenLen = if section != ideHighlight: + s.name.s.len + else: + getTokenLenFromSource(g.config, s.name.s, infox) proc `$`*(suggest: Suggest): string = result = $suggest.section From 7dcbfa0361bfbbca94a04449eef8fb6d67d0665b Mon Sep 17 00:00:00 2001 From: bung87 Date: Fri, 22 Sep 2023 13:19:01 +0800 Subject: [PATCH 47/84] handle pnkVarTuple --- nimlsp/nimlsp.nim | 2 +- nimlsp/nimlsppkg/messages.nim | 1 + nimlsp/nimlsppkg/nimsuggest.nim | 36 +++++++++++++++++++-------------- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index 70fd0a36949..306c59addab 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -88,7 +88,7 @@ proc publishDiagnostics(outs: Stream, uri:string, diagnostics: seq[Diagnostic]) let data = create(PublishDiagnosticsParams, uri, diagnostics) notify(outs, "textDocument/publishDiagnostics", data.JsonNode) -proc workDoneProgressCreate(id: string, token: string) = +proc workDoneProgressCreate(outs: Stream, id: string, token: string) = # https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#serverInitiatedProgress let data = create(WorkDoneProgressCreateParams, token) outs.request(id, "window/workDoneProgress/create", data.JsonNode) diff --git a/nimlsp/nimlsppkg/messages.nim b/nimlsp/nimlsppkg/messages.nim index 90de361ffe0..2bf87c9d048 100644 --- a/nimlsp/nimlsppkg/messages.nim +++ b/nimlsp/nimlsppkg/messages.nim @@ -622,4 +622,5 @@ jsonSchema: percentage ?: int # 0 .. 100 WorkDoneProgressEnd: + kind: string message ?: string diff --git a/nimlsp/nimlsppkg/nimsuggest.nim b/nimlsp/nimlsppkg/nimsuggest.nim index a217122b21d..1357e4e11f4 100644 --- a/nimlsp/nimlsppkg/nimsuggest.nim +++ b/nimlsp/nimlsppkg/nimsuggest.nim @@ -171,29 +171,34 @@ proc processFlags(sug: Suggest; n: ParsedNode) = proc parsedNodeToSugget(n: ParsedNode; originKind: ParsedNodeKind; module: string): Suggest = if n.kind in {pnkError, pnkEmpty}: return - if n.kind notin {pnkConstSection..pnkTypeDef, pnkIdentDefs}: return + if n.kind notin {pnkProcDef..pnkVarTuple}: return new(result) var token = getToken(n) var name = "" - if n.kind in {pnkProcDef..pnkTypeDef, pnkIdentDefs}: - if n.kind in pnkRoutineDefs and n[pragmasPos].kind == pnkPragma: - processFlags(result, n[pragmasPos]) - elif n[0].kind == pnkPragmaExpr and n[0][^1].kind == pnkPragma: - processFlags(result, n[0][^1]) + if n.kind in pnkRoutineDefs and n[pragmasPos].kind == pnkPragma: + processFlags(result, n[pragmasPos]) + elif n[0].kind == pnkPragmaExpr and n[0][^1].kind == pnkPragma: + processFlags(result, n[0][^1]) + if n.kind != pnkVarTuple: var node: ParsedNode = getSymNode(n[0]) token = getToken(node) if node.kind != pnkError: name = getName(node) - # special cases - # if n.kind in pnkRoutineDefs and node.kind == pnkAccQuoted: - # let identsLen = n[paramsPos].sons.len - # for i in countup(1, identsLen - 1): - # name.add getName(n[paramsPos][i][1]) - # if i != identsLen - 1: - # name.add "," - + when false: + if n.kind in pnkRoutineDefs and node.kind == pnkAccQuoted: + let identsLen = n[paramsPos].sons.len + for i in countup(1, identsLen - 1): + name.add getName(n[paramsPos][i][1]) + if i != identsLen - 1: + name.add "," + else: + name.add "(" + for c in n.sons: + if c.kind == pnkEmpty: break + name.add getName(c) & "," + name.add ")" if name != "": result.qualifiedPath = @[module, name] result.line = token.line.int @@ -208,12 +213,13 @@ proc outline(graph: ModuleGraph; fileIdx: FileIndex) = var parsedNode: ParsedNode let name = toFilename(conf, fileIdx) - const Sections = {pnkTypeSection, pnkConstSection, pnkLetSection, pnkVarSection} + const Sections = {pnkTypeSection, pnkConstSection, pnkLetSection} template suggestIt(parsedNode: ParsedNode; originKind: ParsedNodeKind) = sug = parsedNodeToSugget(parsedNode, originKind, name) if sug != nil: sug.filepath = toFullPath(conf, fileIdx) conf.suggestionResultHook(sug) + if setupParser(parser, fileIdx, graph.cache, conf): while true: parsedNode = parser.parseTopLevelStmt() From 6bd74959812eea6d256ccfc0fb6888499f262422 Mon Sep 17 00:00:00 2001 From: bung87 Date: Fri, 22 Sep 2023 21:17:20 +0800 Subject: [PATCH 48/84] fix --- nimlsp/nimlsppkg/nimsuggest.nim | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nimlsp/nimlsppkg/nimsuggest.nim b/nimlsp/nimlsppkg/nimsuggest.nim index 1357e4e11f4..8da6a292c0e 100644 --- a/nimlsp/nimlsppkg/nimsuggest.nim +++ b/nimlsp/nimlsppkg/nimsuggest.nim @@ -211,13 +211,15 @@ proc outline(graph: ModuleGraph; fileIdx: FileIndex) = var parser: Parser var sug: Suggest var parsedNode: ParsedNode - let name = toFilename(conf, fileIdx) + let name = splitFile(toFilename(conf, fileIdx)).name const Sections = {pnkTypeSection, pnkConstSection, pnkLetSection} template suggestIt(parsedNode: ParsedNode; originKind: ParsedNodeKind) = sug = parsedNodeToSugget(parsedNode, originKind, name) if sug != nil: - sug.filepath = toFullPath(conf, fileIdx) + sug.section = ideOutline + sug.quality = 100 + sug.filePath = toFullPath(conf, fileIdx) conf.suggestionResultHook(sug) if setupParser(parser, fileIdx, graph.cache, conf): From 9fd2b3976491f8b3ce3610dbe6403acbca7309f5 Mon Sep 17 00:00:00 2001 From: bung87 Date: Sun, 24 Sep 2023 11:26:44 +0800 Subject: [PATCH 49/84] remove unrelated changes --- compiler/tools/suggest.nim | 14 +++- nimlsp/nimlsppkg/nimsuggest.nim | 111 -------------------------------- 2 files changed, 13 insertions(+), 112 deletions(-) diff --git a/compiler/tools/suggest.nim b/compiler/tools/suggest.nim index be23095bba8..51e7fbe85c3 100644 --- a/compiler/tools/suggest.nim +++ b/compiler/tools/suggest.nim @@ -480,7 +480,6 @@ proc executeCmd*(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int; if dirtyfile.isEmpty: msgs.setDirtyFile(conf, dirtyIdx, AbsoluteFile"") else: msgs.setDirtyFile(conf, dirtyIdx, dirtyfile) - if cmd == ideOutline: return conf.m.trackPos = newLineInfo(dirtyIdx, line, col) conf.m.trackPosAttached = false conf.errorCounter = 0 @@ -540,6 +539,19 @@ proc suggestSym*(g: ModuleGraph; info: TLineInfo; s: PSym; usageSym: var PSym; i suggestResult(conf, symToSuggest(g, s, isLocal=false, ideDef, info, 100, PrefixMatch.None, false, 0)) elif conf.ideCmd == ideHighlight and info.fileIndex == conf.m.trackPos.fileIndex: suggestResult(conf, symToSuggest(g, s, isLocal=false, ideHighlight, info, 100, PrefixMatch.None, false, 0)) + elif conf.ideCmd == ideOutline and isDecl: + # if a module is included then the info we have is inside the include and + # we need to walk up the owners until we find the outer most module, + # which will be the last skModule prior to an skPackage. + var + parentFileIndex = info.fileIndex # assume we're in the correct module + parentModule = s.owner + while parentModule != nil and parentModule.kind == skModule: + parentFileIndex = parentModule.info.fileIndex + parentModule = parentModule.owner + + if parentFileIndex == conf.m.trackPos.fileIndex: + suggestResult(conf, symToSuggest(g, s, isLocal=false, ideOutline, info, 100, PrefixMatch.None, false, 0)) proc safeSemExpr*(c: PContext, n: PNode): PNode = # use only for idetools support! diff --git a/nimlsp/nimlsppkg/nimsuggest.nim b/nimlsp/nimlsppkg/nimsuggest.nim index 8da6a292c0e..88cebe9684c 100644 --- a/nimlsp/nimlsppkg/nimsuggest.nim +++ b/nimlsp/nimlsppkg/nimsuggest.nim @@ -127,114 +127,6 @@ proc initNimSuggest*(project: string, nimPath: string = ""): NimSuggest = retval.doStopCompile = proc (): bool = false return NimSuggest(graph: retval, idle: 0, cachedMsgs: cachedMsgs) -proc getSymNode(node: ParsedNode): ParsedNode = - result = node - if result.kind == pnkPostfix: - result = result[^1] - elif result.kind == pnkPragmaExpr: - result = getSymNode(result[0]) - -proc pnkToSymKind(kind: ParsedNodeKind): TSymKind = - result = skUnknown - case kind - of pnkConstSection, pnkConstDef: result = skConst - of pnkLetSection: result = skLet - of pnkVarSection: result = skVar - of pnkProcDef: result = skProc - of pnkFuncDef: result = skFunc - of pnkMethodDef: result = skMethod - of pnkConverterDef: result = skConverter - of pnkIteratorDef: result = skIterator - of pnkMacroDef: result = skMacro - of pnkTemplateDef: result = skTemplate - of pnkTypeDef, pnkTypeSection: result = skType - else: discard - -proc getName(node: ParsedNode): string = - if node.kind == pnkIdent: - result = node.startToken.ident.s - elif node.kind == pnkAccQuoted: - result = "`" - for t in node.idents: - result.add t.ident.s - result.add "`" - -proc processFlags(sug: Suggest; n: ParsedNode) = - var - identDeprecated: bool - colonDeprecated: bool - for s in n.sons: - identDeprecated = s.kind == pnkIdent and getName(s) == "deprecated" - colonDeprecated = s.kind == pnkExprColonExpr and getName(s[0]) == "deprecated" - if identDeprecated or colonDeprecated: - sug.flags.incl SuggestFlag.deprecated - -proc parsedNodeToSugget(n: ParsedNode; originKind: ParsedNodeKind; module: string): Suggest = - if n.kind in {pnkError, pnkEmpty}: return - if n.kind notin {pnkProcDef..pnkVarTuple}: return - new(result) - var token = getToken(n) - var name = "" - - if n.kind in pnkRoutineDefs and n[pragmasPos].kind == pnkPragma: - processFlags(result, n[pragmasPos]) - elif n[0].kind == pnkPragmaExpr and n[0][^1].kind == pnkPragma: - processFlags(result, n[0][^1]) - - if n.kind != pnkVarTuple: - var node: ParsedNode = getSymNode(n[0]) - token = getToken(node) - if node.kind != pnkError: - name = getName(node) - when false: - if n.kind in pnkRoutineDefs and node.kind == pnkAccQuoted: - let identsLen = n[paramsPos].sons.len - for i in countup(1, identsLen - 1): - name.add getName(n[paramsPos][i][1]) - if i != identsLen - 1: - name.add "," - else: - name.add "(" - for c in n.sons: - if c.kind == pnkEmpty: break - name.add getName(c) & "," - name.add ")" - if name != "": - result.qualifiedPath = @[module, name] - result.line = token.line.int - result.column = token.col.int - result.tokenLen = name.len - result.symkind = byte pnkToSymKind(originKind) - -proc outline(graph: ModuleGraph; fileIdx: FileIndex) = - let conf = graph.config - var parser: Parser - var sug: Suggest - var parsedNode: ParsedNode - let name = splitFile(toFilename(conf, fileIdx)).name - - const Sections = {pnkTypeSection, pnkConstSection, pnkLetSection} - template suggestIt(parsedNode: ParsedNode; originKind: ParsedNodeKind) = - sug = parsedNodeToSugget(parsedNode, originKind, name) - if sug != nil: - sug.section = ideOutline - sug.quality = 100 - sug.filePath = toFullPath(conf, fileIdx) - conf.suggestionResultHook(sug) - - if setupParser(parser, fileIdx, graph.cache, conf): - while true: - parsedNode = parser.parseTopLevelStmt() - if parsedNode.kind == pnkEmpty: - break - - if parsedNode.kind in Sections: - for node in parsedNode.sons: - suggestIt(node, parsedNode.kind) - else: - suggestIt(parsedNode, parsedNode.kind) - closeParser(parser) - proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int, graph: ModuleGraph) = let conf = graph.config @@ -245,9 +137,6 @@ proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int, listUsages(graph, u) else: stderr.writeLine "found no symbol at position: " & (conf $ conf.m.trackPos) - elif conf.ideCmd == ideOutline: - let dirtyIdx = fileInfoIdx(conf, file) - outline(graph, dirtyIdx) proc reportToSuggest(conf: ConfigRef, info: TLineInfo, r: Report): Suggest = Suggest(section: ideChk, filePath: toFullPath(conf, info), From 873489d039b194bcea2dd07aa2d1e1dfa4126f81 Mon Sep 17 00:00:00 2001 From: bung87 Date: Sun, 24 Sep 2023 11:33:17 +0800 Subject: [PATCH 50/84] remove unrelated changes --- compiler/ast/parser.nim | 2 +- nimsuggest/tests/tinclude.nim | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/ast/parser.nim b/compiler/ast/parser.nim index ebbaa2dd63a..91cf06356af 100644 --- a/compiler/ast/parser.nim +++ b/compiler/ast/parser.nim @@ -2321,7 +2321,7 @@ proc parseAll(p: var Parser): ParsedNode = if p.tok.indent != 0: p.invalidIndentation() -proc parseTopLevelStmt*(p: var Parser): ParsedNode = +proc parseTopLevelStmt(p: var Parser): ParsedNode = ## Implements an iterator which, when called repeatedly, returns the next ## top-level statement or emptyNode if end of stream. result = p.emptyNode diff --git a/nimsuggest/tests/tinclude.nim b/nimsuggest/tests/tinclude.nim index 1025eb52810..b67440b9e39 100644 --- a/nimsuggest/tests/tinclude.nim +++ b/nimsuggest/tests/tinclude.nim @@ -9,7 +9,6 @@ proc go() = go() discard """ -disabled:true $nimsuggest --tester $file >def $path/tinclude.nim:7:14 def;;skProc;;minclude_import.create;;proc (greeting: string, subject: string): Greet{.noSideEffect, gcsafe, locks: 0.};;*fixtures/minclude_include.nim;;3;;5;;"";;100 From f3dab17785b174fb09f2c02035a77c3c96d0268e Mon Sep 17 00:00:00 2001 From: bung87 Date: Sun, 24 Sep 2023 11:45:56 +0800 Subject: [PATCH 51/84] remove unrelated changes --- compiler/modules/modules.nim | 1 + compiler/tools/suggest.nim | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/modules/modules.nim b/compiler/modules/modules.nim index 17bd6c3676e..3286cff44fb 100644 --- a/compiler/modules/modules.nim +++ b/compiler/modules/modules.nim @@ -151,6 +151,7 @@ proc compileModule*(graph: ModuleGraph; fileIdx: FileIndex; flags: TSymFlags, fr initStrTables(graph, result) result.ast = nil processModuleAux("import(dirty)") + graph.markClientsDirty(fileIdx) proc importModule*(graph: ModuleGraph; s: PSym, fileIdx: FileIndex): PSym = diff --git a/compiler/tools/suggest.nim b/compiler/tools/suggest.nim index 51e7fbe85c3..11a8fef8a7b 100644 --- a/compiler/tools/suggest.nim +++ b/compiler/tools/suggest.nim @@ -480,11 +480,12 @@ proc executeCmd*(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int; if dirtyfile.isEmpty: msgs.setDirtyFile(conf, dirtyIdx, AbsoluteFile"") else: msgs.setDirtyFile(conf, dirtyIdx, dirtyfile) + conf.m.trackPos = newLineInfo(dirtyIdx, line, col) conf.m.trackPosAttached = false conf.errorCounter = 0 if not isKnownFile: - discard graph.compileModule(dirtyIdx, {}) + graph.compileProject(dirtyIdx) if conf.ideCmd in {ideUse, ideDus} and dirtyfile.isEmpty: discard "no need to recompile anything" @@ -497,7 +498,7 @@ proc executeCmd*(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int; graph.vm = nil if conf.ideCmd != ideMod: if isKnownFile: - discard graph.compileModule(dirtyIdx, {}) + graph.compileProject(modIdx) when defined(nimsuggest): From fbb77f18e3147054d1593f6016dec6ea09378549 Mon Sep 17 00:00:00 2001 From: bung87 Date: Mon, 25 Sep 2023 04:00:23 +0800 Subject: [PATCH 52/84] flags --- compiler/tools/suggest.nim | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/tools/suggest.nim b/compiler/tools/suggest.nim index 11a8fef8a7b..e57dd48ba95 100644 --- a/compiler/tools/suggest.nim +++ b/compiler/tools/suggest.nim @@ -149,6 +149,8 @@ proc symToSuggest(g: ModuleGraph; s: PSym, isLocal: bool, section: IdeCmd, info: inTypeContext: bool; scope: int; useSuppliedInfo = false): Suggest = new(result) + if sfDeprecated in s.flags: + result.flags.incl SuggestFlag.deprecated result.section = section result.quality = quality result.isGlobal = sfGlobal in s.flags From 418e600fca959dc017d15989222fd7cd42bf6114 Mon Sep 17 00:00:00 2001 From: bung87 Date: Mon, 25 Sep 2023 04:20:37 +0800 Subject: [PATCH 53/84] outline sort --- nimlsp/nimlsp.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index 306c59addab..5eb2141eafc 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -366,7 +366,7 @@ proc main(ins: Stream, outs: Stream) = resp = newJNull() else: resp = newJarray() - for sym in syms.sortedByIt((it.line,it.column,it.quality)): + for sym in syms.sortedByIt((it.line,it.column)): if sym.qualifiedPath.len != 2: continue resp.add create(DocumentSymbol, From 9b6f4c663258c8278266ebfa97068c7cf273600d Mon Sep 17 00:00:00 2001 From: bung87 Date: Mon, 25 Sep 2023 04:21:38 +0800 Subject: [PATCH 54/84] fix indent --- nimlsp/nimlsp.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index 5eb2141eafc..1ea7693a69b 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -377,7 +377,7 @@ proc main(ins: Stream, outs: Stream) = createRange(sym.line-1, sym.column, sym.tokenLen), createRange(sym.line-1, sym.column, sym.tokenLen), none(seq[DocumentSymbol])).JsonNode - outs.respond(message, resp) + outs.respond(message, resp) of "textDocument/signatureHelp": textDocumentRequest(message, TextDocumentPositionParams, req): debugLog location(req) From 1e105e3cd3e1bf4fd4f4a12d3f64c8be5496ebdd Mon Sep 17 00:00:00 2001 From: bung87 Date: Mon, 25 Sep 2023 04:23:12 +0800 Subject: [PATCH 55/84] fix indent --- nimlsp/nimlsp.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index 1ea7693a69b..70f8b794ead 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -334,7 +334,7 @@ proc main(ins: Stream, outs: Stream) = some(textEdits), none(seq[TextDocumentEdit]) ).JsonNode - outs.respond(message, resp) + outs.respond(message, resp) of "textDocument/definition": textDocumentRequest(message, TextDocumentPositionParams, req): debugLog location(req) From 09f9c42363cb224a97e531e5e300895ac4efdf9c Mon Sep 17 00:00:00 2001 From: bung87 Date: Mon, 25 Sep 2023 16:00:35 +0800 Subject: [PATCH 56/84] ignore gensym when outline --- compiler/tools/suggest.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/tools/suggest.nim b/compiler/tools/suggest.nim index e57dd48ba95..ace28877f30 100644 --- a/compiler/tools/suggest.nim +++ b/compiler/tools/suggest.nim @@ -543,6 +543,7 @@ proc suggestSym*(g: ModuleGraph; info: TLineInfo; s: PSym; usageSym: var PSym; i elif conf.ideCmd == ideHighlight and info.fileIndex == conf.m.trackPos.fileIndex: suggestResult(conf, symToSuggest(g, s, isLocal=false, ideHighlight, info, 100, PrefixMatch.None, false, 0)) elif conf.ideCmd == ideOutline and isDecl: + if sfGenSym in s.flags: return # if a module is included then the info we have is inside the include and # we need to walk up the owners until we find the outer most module, # which will be the last skModule prior to an skPackage. From 711811d18457bf6c41b2f995a93e66f4652e3fc3 Mon Sep 17 00:00:00 2001 From: bung87 Date: Mon, 25 Sep 2023 16:48:09 +0800 Subject: [PATCH 57/84] suggest proc later --- compiler/sem/semexprs.nim | 1 + compiler/sem/semstmts.nim | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/sem/semexprs.nim b/compiler/sem/semexprs.nim index fe22ae5b71a..395ef8246e6 100644 --- a/compiler/sem/semexprs.nim +++ b/compiler/sem/semexprs.nim @@ -3809,6 +3809,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = skProc result[namePos] = semRoutineName(c, n[namePos], kind, allowAnon = true) result = semProcAux(c, result, lambdaPragmas, flags) + suggestSym(c.graph, result[namePos].info, result[namePos].sym, c.graph.usageSym) of nkDerefExpr: result = semDeref(c, n) of nkAddr: result = n diff --git a/compiler/sem/semstmts.nim b/compiler/sem/semstmts.nim index 58a720f0457..712671c1a7a 100644 --- a/compiler/sem/semstmts.nim +++ b/compiler/sem/semstmts.nim @@ -2512,7 +2512,6 @@ proc semRoutineName(c: PContext, n: PNode, kind: TSymKind; allowAnon = true): PN if c.isTopLevel: incl(s.flags, sfGlobal) - suggestSym(c.graph, getIdentLineInfo(n), s, c.graph.usageSym) styleCheckDef(c.config, s) else: # XXX: this should be the resonsibility of the macro sanitizer instead @@ -3108,6 +3107,7 @@ proc semRoutineDef(c: PContext, n: PNode): PNode = of skTemplate: semTemplateDef(c, result) of skMacro: semMacroDef(c, result) else: unreachable(kind) + suggestSym(c.graph, result[namePos].info, result[namePos].sym, c.graph.usageSym) proc evalInclude(c: PContext, n: PNode): PNode = proc incMod(c: PContext, n, it, includeStmtResult: PNode) {.nimcall.} = From 33e86bc0e60b333b6023d007dbe022efbf424fd8 Mon Sep 17 00:00:00 2001 From: bung87 Date: Mon, 25 Sep 2023 18:25:36 +0800 Subject: [PATCH 58/84] outline ignore gensym by name --- compiler/tools/suggest.nim | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/tools/suggest.nim b/compiler/tools/suggest.nim index ace28877f30..18ce68b66aa 100644 --- a/compiler/tools/suggest.nim +++ b/compiler/tools/suggest.nim @@ -543,7 +543,9 @@ proc suggestSym*(g: ModuleGraph; info: TLineInfo; s: PSym; usageSym: var PSym; i elif conf.ideCmd == ideHighlight and info.fileIndex == conf.m.trackPos.fileIndex: suggestResult(conf, symToSuggest(g, s, isLocal=false, ideHighlight, info, 100, PrefixMatch.None, false, 0)) elif conf.ideCmd == ideOutline and isDecl: - if sfGenSym in s.flags: return + if s.kind == skTemp or sfGenSym in s.flags: return + if "`gensym" in s.name.s: return + if s.owner.kind == skModule and s.owner.position != conf.m.trackPos.fileIndex.int32: return # if a module is included then the info we have is inside the include and # we need to walk up the owners until we find the outer most module, # which will be the last skModule prior to an skPackage. From 74ec4000f791cca7f18a5f19e2ad8e2c39f53599 Mon Sep 17 00:00:00 2001 From: bung87 Date: Mon, 25 Sep 2023 19:53:22 +0800 Subject: [PATCH 59/84] unique reports --- compiler/ast/reports.nim | 7 ++++++- nimlsp/nimlsppkg/nimsuggest.nim | 7 +++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/compiler/ast/reports.nim b/compiler/ast/reports.nim index 808b1d08d52..a1889ff15cf 100644 --- a/compiler/ast/reports.nim +++ b/compiler/ast/reports.nim @@ -18,7 +18,7 @@ ## `compilerDebugCompilerReportStatistics`: output stats of counts for various ## report kinds -import std/[options] +import std/[options, hashes] import compiler/ast/[ @@ -308,3 +308,8 @@ func actualType*(r: SemReport): PType = r.typeMismatch[0].actualType func formalType*(r: SemReport): PType = r.typeMismatch[0].formalType func formalTypeKind*(r: SemReport): set[TTypeKind] = r.typeMismatch[0].formalTypeKind func symstr*(r: SemReport | VMReport): string = r.sym.name.s +func hash*(x: Report): Hash = + var h: Hash = 0 + h = h !& hash(x.location().get(unknownLineInfo)) + h = h !& x.kind.int + result = !$h diff --git a/nimlsp/nimlsppkg/nimsuggest.nim b/nimlsp/nimlsppkg/nimsuggest.nim index 88cebe9684c..4cfef853812 100644 --- a/nimlsp/nimlsppkg/nimsuggest.nim +++ b/nimlsp/nimlsppkg/nimsuggest.nim @@ -1,7 +1,7 @@ when not defined(nimcore): {.error: "nimcore MUST be defined for Nim's core tooling".} -import std/[os, net] +import std/[os, net, sets] import std/options as stdOptions import compiler/ast/[ @@ -37,7 +37,8 @@ import from compiler/ast/reports import Report, category, kind, - location + location, + hash from compiler/front/main import customizeForBackend @@ -172,9 +173,11 @@ proc runCmd*(nimsuggest: NimSuggest, cmd: IdeCmd, file, elif conf.ideCmd == ideProject: retval.add(Suggest(section: ideProject, filePath: string conf.projectFull)) else: + var reports = default(HashSet[int]) template addReport(report: Report) = let loc = report.location() if stdOptions.isSome(loc): + if containsOrIncl(reports, hash(report)): return let info = loc.get() retval.add(reportToSuggest(conf, info, report)) From fab7c48dbe9e393e17dca9c574fa2056c9bb1e57 Mon Sep 17 00:00:00 2001 From: bung87 Date: Mon, 25 Sep 2023 21:26:44 +0800 Subject: [PATCH 60/84] guard on suggestSym --- compiler/sem/semexprs.nim | 3 ++- compiler/sem/semstmts.nim | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/sem/semexprs.nim b/compiler/sem/semexprs.nim index 395ef8246e6..80fc19411e6 100644 --- a/compiler/sem/semexprs.nim +++ b/compiler/sem/semexprs.nim @@ -3809,7 +3809,8 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = skProc result[namePos] = semRoutineName(c, n[namePos], kind, allowAnon = true) result = semProcAux(c, result, lambdaPragmas, flags) - suggestSym(c.graph, result[namePos].info, result[namePos].sym, c.graph.usageSym) + if result.kind != nkError: + suggestSym(c.graph, result[namePos].info, result[namePos].sym, c.graph.usageSym) of nkDerefExpr: result = semDeref(c, n) of nkAddr: result = n diff --git a/compiler/sem/semstmts.nim b/compiler/sem/semstmts.nim index 712671c1a7a..9db99e9cb45 100644 --- a/compiler/sem/semstmts.nim +++ b/compiler/sem/semstmts.nim @@ -3107,7 +3107,8 @@ proc semRoutineDef(c: PContext, n: PNode): PNode = of skTemplate: semTemplateDef(c, result) of skMacro: semMacroDef(c, result) else: unreachable(kind) - suggestSym(c.graph, result[namePos].info, result[namePos].sym, c.graph.usageSym) + if result.kind != nkError: + suggestSym(c.graph, result[namePos].info, result[namePos].sym, c.graph.usageSym) proc evalInclude(c: PContext, n: PNode): PNode = proc incMod(c: PContext, n, it, includeStmtResult: PNode) {.nimcall.} = From 337f65257ded13d7f8bb08f6a79f5e3094f06055 Mon Sep 17 00:00:00 2001 From: bung87 Date: Tue, 26 Sep 2023 01:58:28 +0800 Subject: [PATCH 61/84] code action type --- nimlsp/nimlsppkg/messageenums.nim | 15 +++++++++++++++ nimlsp/nimlsppkg/messages.nim | 21 +++++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/nimlsp/nimlsppkg/messageenums.nim b/nimlsp/nimlsppkg/messageenums.nim index 81433dab994..9a97289e04c 100644 --- a/nimlsp/nimlsppkg/messageenums.nim +++ b/nimlsp/nimlsppkg/messageenums.nim @@ -115,3 +115,18 @@ type SymbolTag* {.pure.} = enum Deprecated = 1 + + CodeActionKind* {.pure.} = enum + Empty = "" + QuickFix = "quickfix" + Refactor = "refactor" + RefactorExtract = "refactor.extract" + RefactorInline = "refactor.inline" + RefactorRewrite = "refactor.rewrite" + Source = "source" + SourceOrganizeImports = "source.organizeImports" + SourceFixAll = "source.fixAll" + + CodeActionTriggerKind* {.pure.} = enum + Invoked = 1 + Automatic = 2 diff --git a/nimlsp/nimlsppkg/messages.nim b/nimlsp/nimlsppkg/messages.nim index 2bf87c9d048..8860966de46 100644 --- a/nimlsp/nimlsppkg/messages.nim +++ b/nimlsp/nimlsppkg/messages.nim @@ -296,7 +296,9 @@ jsonSchema: TextDocumentAndStaticRegistrationOptions extends TextDocumentRegistrationOptions: id ?: string - + CodeActionOptions: + codeActionKinds ?: int[] # CodeActionKind + resolveProvider ?: bool ServerCapabilities: textDocumentSync ?: TextDocumentSyncOptions or int or float hoverProvider ?: bool @@ -309,7 +311,7 @@ jsonSchema: documentHighlightProvider ?: bool documentSymbolProvider ?: bool workspaceSymbolProvider ?: bool - codeActionProvider ?: bool + codeActionProvider ?: bool or CodeActionOptions codeLensProvider ?: CodeLensOptions documentFormattingProvider ?: bool documentRangeFormattingProvider ?: bool @@ -530,6 +532,21 @@ jsonSchema: CodeActionContext: diagnostics: Diagnostic[] + only ?: int[] # CodeActionKind + triggerKind ?: int # CodeActionTriggerKind + + CodeActionDisabled: + reason: string + + CodeAction: + title: string + kind ?: int # CodeActionKind + diagnostics ?: Diagnostic[] + isPreferred ?: bool + disabled ?: CodeActionDisabled + edit ?: WorkspaceEdit + command ?: Command + data ?: any CodeLensParams: textDocument: TextDocumentIdentifier From a83ea3f5bf5501ac346137136b102684c8372681 Mon Sep 17 00:00:00 2001 From: bung87 Date: Tue, 26 Sep 2023 21:37:16 +0800 Subject: [PATCH 62/84] enable code action --- nimlsp/nimlsp.nim | 5 ++++- nimlsp/nimlsppkg/messages.nim | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index 70f8b794ead..8581e177aa6 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -211,7 +211,10 @@ proc main(ins: Stream, outs: Stream) = documentHighlightProvider = none(bool), #?: bool documentSymbolProvider = some(true), #?: bool workspaceSymbolProvider = none(bool), #?: bool - codeActionProvider = none(bool), #?: bool + codeActionProvider = some(create(CodeActionOptions, + codeActionKinds = some(@[$CodeActionKind.QuickFix]), + resolveProvider = none(bool) + )), #?: bool codeLensProvider = none(CodeLensOptions), #?: CodeLensOptions documentFormattingProvider = none(bool), #?: bool documentRangeFormattingProvider = none(bool), #?: bool diff --git a/nimlsp/nimlsppkg/messages.nim b/nimlsp/nimlsppkg/messages.nim index 8860966de46..1252647c67a 100644 --- a/nimlsp/nimlsppkg/messages.nim +++ b/nimlsp/nimlsppkg/messages.nim @@ -297,7 +297,7 @@ jsonSchema: TextDocumentAndStaticRegistrationOptions extends TextDocumentRegistrationOptions: id ?: string CodeActionOptions: - codeActionKinds ?: int[] # CodeActionKind + codeActionKinds ?: string[] # CodeActionKind resolveProvider ?: bool ServerCapabilities: textDocumentSync ?: TextDocumentSyncOptions or int or float From 34d033d14dbada67f5786db85415e2d0c937efbd Mon Sep 17 00:00:00 2001 From: bung87 Date: Tue, 26 Sep 2023 21:47:31 +0800 Subject: [PATCH 63/84] outline only suggest current tracked file --- compiler/tools/suggest.nim | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/compiler/tools/suggest.nim b/compiler/tools/suggest.nim index 18ce68b66aa..49d5926060f 100644 --- a/compiler/tools/suggest.nim +++ b/compiler/tools/suggest.nim @@ -542,22 +542,10 @@ proc suggestSym*(g: ModuleGraph; info: TLineInfo; s: PSym; usageSym: var PSym; i suggestResult(conf, symToSuggest(g, s, isLocal=false, ideDef, info, 100, PrefixMatch.None, false, 0)) elif conf.ideCmd == ideHighlight and info.fileIndex == conf.m.trackPos.fileIndex: suggestResult(conf, symToSuggest(g, s, isLocal=false, ideHighlight, info, 100, PrefixMatch.None, false, 0)) - elif conf.ideCmd == ideOutline and isDecl: + elif conf.ideCmd == ideOutline and isDecl and info.fileIndex == conf.m.trackPos.fileIndex: if s.kind == skTemp or sfGenSym in s.flags: return if "`gensym" in s.name.s: return - if s.owner.kind == skModule and s.owner.position != conf.m.trackPos.fileIndex.int32: return - # if a module is included then the info we have is inside the include and - # we need to walk up the owners until we find the outer most module, - # which will be the last skModule prior to an skPackage. - var - parentFileIndex = info.fileIndex # assume we're in the correct module - parentModule = s.owner - while parentModule != nil and parentModule.kind == skModule: - parentFileIndex = parentModule.info.fileIndex - parentModule = parentModule.owner - - if parentFileIndex == conf.m.trackPos.fileIndex: - suggestResult(conf, symToSuggest(g, s, isLocal=false, ideOutline, info, 100, PrefixMatch.None, false, 0)) + suggestResult(conf, symToSuggest(g, s, isLocal=false, ideOutline, info, 100, PrefixMatch.None, false, 0)) proc safeSemExpr*(c: PContext, n: PNode): PNode = # use only for idetools support! From d0adbf78861796bd147255a0fe991c7f4e9a6c45 Mon Sep 17 00:00:00 2001 From: bung87 Date: Wed, 27 Sep 2023 00:17:48 +0800 Subject: [PATCH 64/84] fix suggest empty container error --- compiler/tools/suggest.nim | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/tools/suggest.nim b/compiler/tools/suggest.nim index 49d5926060f..c3f91ceffbc 100644 --- a/compiler/tools/suggest.nim +++ b/compiler/tools/suggest.nim @@ -73,7 +73,7 @@ const sep = '\t' proc findDocComment(n: PNode): PNode = - if n == nil: return nil + if n == nil or n.kind == nkError: return nil if n.comment.len > 0: return n if n.kind in {nkStmtList, nkStmtListExpr, nkObjectTy, nkRecList} and n.len > 0: result = findDocComment(n[0]) @@ -291,10 +291,10 @@ proc suggestField(c: PContext, s: PSym; f: PNode; info: TLineInfo; outputs: var outputs.add(symToSuggest(c.graph, s, isLocal=true, ideSug, info, s.getQuality, pm, c.inTypeContext > 0, 0)) -template wholeSymTab(cond, section: untyped) {.dirty.} = +template wholeSymTab(cond, section: untyped) = for (item, scopeN, isLocal) in allSyms(c): - let it = item - var pm: PrefixMatch + let it {.inject.} = item + var pm {.inject.}: PrefixMatch if cond: outputs.add(symToSuggest(c.graph, it, isLocal = isLocal, section, info, getQuality(it), pm, c.inTypeContext > 0, scopeN)) @@ -316,9 +316,9 @@ proc suggestObject(c: PContext, n, f: PNode; info: TLineInfo, outputs: var Sugge else: discard proc nameFits(c: PContext, s: PSym, n: PNode): bool = - var op = if n.kind in nkCallKinds: n[0] else: n + var op = if n.kind in nkCallKinds and n.len > 0: n[0] else: n if op.kind in {nkOpenSymChoice, nkClosedSymChoice}: op = op[0] - if op.kind == nkDotExpr: op = op[1] + if op.kind == nkDotExpr and op.len > 1: op = op[1] var opr: PIdent case op.kind of nkSym: opr = op.sym.name From d2eee4c1d31c963da95743ec837f8aa0adce42d8 Mon Sep 17 00:00:00 2001 From: bung87 Date: Wed, 27 Sep 2023 02:21:00 +0800 Subject: [PATCH 65/84] safe --- compiler/sem/semexprs.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/sem/semexprs.nim b/compiler/sem/semexprs.nim index 80fc19411e6..50613731f29 100644 --- a/compiler/sem/semexprs.nim +++ b/compiler/sem/semexprs.nim @@ -1775,7 +1775,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = when defined(nimsuggest): if c.config.cmd == cmdIdeTools: suggestExpr(c, n) - if c.config.m.trackPos == n[1].info: suggestExprNoCheck(c, n) + if n.len > 0 and c.config.m.trackPos == n[1].info: suggestExprNoCheck(c, n) let s = qualifiedLookUp(c, n, {checkAmbiguity, checkUndeclared, checkModule}) if s.isError: From 04699e48d74ef41cf471f18defa787dafebfee35 Mon Sep 17 00:00:00 2001 From: bung87 Date: Wed, 27 Sep 2023 15:58:53 +0800 Subject: [PATCH 66/84] prepare code action --- nimlsp/nimlsp.nim | 10 +++++++++- nimlsp/nimlsppkg/nimsuggest.nim | 2 +- nimlsp/nimlsppkg/suggestlib.nim | 5 ++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index 8581e177aa6..5754922be98 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -159,7 +159,7 @@ proc createDiagnostic(sug: Suggest): Diagnostic = of "Hint": DiagnosticSeverity.Hint.int of "Warning": DiagnosticSeverity.Warning.int else: DiagnosticSeverity.Error.int), - none(int), + some(sug.scope), some("nimsuggest chk"), message, none(seq[DiagnosticRelatedInformation]) @@ -404,6 +404,14 @@ proc main(ins: Stream, outs: Stream) = activeParameter = some(0) ).JsonNode outs.respond(message, resp) + of "textDocument/codeAction": + textDocumentRequest(message, CodeActionParams, req): + debugLog $message["range"] + for ele in message["params"]["context"]["diagnostics"].getElems: + let kind = ReportKind(ele["code"].getInt()) + stderr.writeLine $kind + # (Command | CodeAction)[] | null + outs.respond(message, newJNull()) else: let msg = "Unknown request method: " & message["method"].getStr debugLog msg diff --git a/nimlsp/nimlsppkg/nimsuggest.nim b/nimlsp/nimlsppkg/nimsuggest.nim index 4cfef853812..a45cfe5d2be 100644 --- a/nimlsp/nimlsppkg/nimsuggest.nim +++ b/nimlsp/nimlsppkg/nimsuggest.nim @@ -142,7 +142,7 @@ proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int, proc reportToSuggest(conf: ConfigRef, info: TLineInfo, r: Report): Suggest = Suggest(section: ideChk, filePath: toFullPath(conf, info), line: toLinenumber(info), column: toColumn(info), - doc: conf.reportShort(r), forth: $severity(conf, r)) + doc: conf.reportShort(r), forth: $severity(conf, r), scope: r.kind.int) proc fetchCachedReports*(ins: NimSuggest, file: AbsoluteFile): seq[Suggest] = let rLen = ins.cachedMsgs.len diff --git a/nimlsp/nimlsppkg/suggestlib.nim b/nimlsp/nimlsppkg/suggestlib.nim index 857e9e1c2a3..c1569cf7cfb 100644 --- a/nimlsp/nimlsppkg/suggestlib.nim +++ b/nimlsp/nimlsppkg/suggestlib.nim @@ -7,9 +7,12 @@ export NimSuggest export initNimSuggest import compiler/ast/[ - ast + ast, + report_enums ] +export ReportKind + proc stopNimSuggest*(nimsuggest: NimSuggest): int = 42 proc `$`*(suggest: Suggest): string = From 6de948046ceea4dfed66b19afb319e5745b0b19e Mon Sep 17 00:00:00 2001 From: bung87 Date: Wed, 27 Sep 2023 19:28:27 +0800 Subject: [PATCH 67/84] avoid sfTemplateParam conflict error --- compiler/sem/lookups.nim | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/sem/lookups.nim b/compiler/sem/lookups.nim index bba1f93a20e..ea4e06e0125 100644 --- a/compiler/sem/lookups.nim +++ b/compiler/sem/lookups.nim @@ -502,6 +502,8 @@ proc wrongRedefinition*( conflictsWith: PSym) = ## Emit a redefinition error if in non-interactive mode if c.config.cmd != cmdInteractive: + if sfTemplateParam in (s.flags + conflictsWith.flags): + return c.config.localReport(info, reportSymbols( rsemRedefinitionOf, @[s, conflictsWith])) From 23c4d3211b0c09bde811d1711f804fdb144a3ffc Mon Sep 17 00:00:00 2001 From: bung87 Date: Wed, 27 Sep 2023 19:44:37 +0800 Subject: [PATCH 68/84] add comments --- compiler/tools/suggest.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/tools/suggest.nim b/compiler/tools/suggest.nim index c3f91ceffbc..3f80b275263 100644 --- a/compiler/tools/suggest.nim +++ b/compiler/tools/suggest.nim @@ -543,8 +543,8 @@ proc suggestSym*(g: ModuleGraph; info: TLineInfo; s: PSym; usageSym: var PSym; i elif conf.ideCmd == ideHighlight and info.fileIndex == conf.m.trackPos.fileIndex: suggestResult(conf, symToSuggest(g, s, isLocal=false, ideHighlight, info, 100, PrefixMatch.None, false, 0)) elif conf.ideCmd == ideOutline and isDecl and info.fileIndex == conf.m.trackPos.fileIndex: - if s.kind == skTemp or sfGenSym in s.flags: return - if "`gensym" in s.name.s: return + if s.kind == skTemp or {sfGenSym, sfTemplateParam} * s.flags != {}: return + if "`gensym" in s.name.s: return # prevent constant in template show up suggestResult(conf, symToSuggest(g, s, isLocal=false, ideOutline, info, 100, PrefixMatch.None, false, 0)) proc safeSemExpr*(c: PContext, n: PNode): PNode = From fe3392e75eaf47fa3bf6309553affe27c8e10df2 Mon Sep 17 00:00:00 2001 From: bung87 Date: Wed, 27 Sep 2023 20:03:54 +0800 Subject: [PATCH 69/84] unique reports --- nimlsp/nimlsppkg/nimsuggest.nim | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/nimlsp/nimlsppkg/nimsuggest.nim b/nimlsp/nimlsppkg/nimsuggest.nim index a45cfe5d2be..2afb2baf6f8 100644 --- a/nimlsp/nimlsppkg/nimsuggest.nim +++ b/nimlsp/nimlsppkg/nimsuggest.nim @@ -53,6 +53,7 @@ type graph: ModuleGraph idle: int cachedMsgs: CachedMsgs + reported: HashSet[int] proc defaultReportHook(conf: ConfigRef, report: Report): TErrorHandling = doNothing @@ -60,6 +61,7 @@ proc defaultReportHook(conf: ConfigRef, report: Report): TErrorHandling = proc initNimSuggest*(project: string, nimPath: string = ""): NimSuggest = var retval: ModuleGraph var cachedMsgs: CachedMsgs = @[] + var reported = default(HashSet[int]) proc mockCommand(graph: ModuleGraph) = retval = graph let conf = graph.config @@ -90,6 +92,7 @@ proc initNimSuggest*(project: string, nimPath: string = ""): NimSuggest = result = doNothing if report.kind notin {rsemProcessing, rsemProcessingStmt}: # pre-filter to save memory + if containsOrIncl(reported, hash(report)): return cachedMsgs.add(report) let cache = newIdentCache() @@ -126,7 +129,7 @@ proc initNimSuggest*(project: string, nimPath: string = ""): NimSuggest = mockCommand(graph) retval.doStopCompile = proc (): bool = false - return NimSuggest(graph: retval, idle: 0, cachedMsgs: cachedMsgs) + return NimSuggest(graph: retval, idle: 0, cachedMsgs: cachedMsgs, reported: reported) proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int, graph: ModuleGraph) = @@ -173,11 +176,10 @@ proc runCmd*(nimsuggest: NimSuggest, cmd: IdeCmd, file, elif conf.ideCmd == ideProject: retval.add(Suggest(section: ideProject, filePath: string conf.projectFull)) else: - var reports = default(HashSet[int]) template addReport(report: Report) = let loc = report.location() if stdOptions.isSome(loc): - if containsOrIncl(reports, hash(report)): return + if containsOrIncl(nimsuggest.reported, hash(report)): return let info = loc.get() retval.add(reportToSuggest(conf, info, report)) From 99a02cbbf1ef54b93858c3967441f29cbe88e904 Mon Sep 17 00:00:00 2001 From: bung87 Date: Wed, 27 Sep 2023 20:09:41 +0800 Subject: [PATCH 70/84] remove unused imports --- nimlsp/nimlsppkg/nimsuggest.nim | 2 -- 1 file changed, 2 deletions(-) diff --git a/nimlsp/nimlsppkg/nimsuggest.nim b/nimlsp/nimlsppkg/nimsuggest.nim index 2afb2baf6f8..1dbe24d2b0f 100644 --- a/nimlsp/nimlsppkg/nimsuggest.nim +++ b/nimlsp/nimlsppkg/nimsuggest.nim @@ -8,8 +8,6 @@ import idents, lineinfos, ast, - syntaxes, - parser, ast_parsed_types, ast_types, report_enums From f5b839a385e5a0f28895096131650bec2b2ef2c4 Mon Sep 17 00:00:00 2001 From: bung87 Date: Thu, 28 Sep 2023 00:00:59 +0800 Subject: [PATCH 71/84] report respect config.notes --- nimlsp/nimlsppkg/nimsuggest.nim | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nimlsp/nimlsppkg/nimsuggest.nim b/nimlsp/nimlsppkg/nimsuggest.nim index 1dbe24d2b0f..fb3a51d8d36 100644 --- a/nimlsp/nimlsppkg/nimsuggest.nim +++ b/nimlsp/nimlsppkg/nimsuggest.nim @@ -88,7 +88,8 @@ proc initNimSuggest*(project: string, nimPath: string = ""): NimSuggest = conf.projectName = a proc reportHook(conf: ConfigRef, report: Report): TErrorHandling = result = doNothing - if report.kind notin {rsemProcessing, rsemProcessingStmt}: + if report.kind notin {rsemProcessing, rsemProcessingStmt} and + conf.isEnabled(report.kind): # pre-filter to save memory if containsOrIncl(reported, hash(report)): return cachedMsgs.add(report) @@ -190,7 +191,8 @@ proc runCmd*(nimsuggest: NimSuggest, cmd: IdeCmd, file, report.kind in {rsemProcessing, rsemProcessingStmt}: # skip processing statements return - addReport(report) + if conf.isEnabled(report.kind): + addReport(report) else: discard else: From 7dfb5912b638ce25305b58536dc0759264ee8d24 Mon Sep 17 00:00:00 2001 From: bung87 Date: Tue, 10 Oct 2023 19:31:23 +0800 Subject: [PATCH 72/84] follow upstream changes --- compiler/sem/lookups.nim | 2 -- compiler/sem/semexprs.nim | 2 +- compiler/tools/suggest.nim | 3 +-- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/compiler/sem/lookups.nim b/compiler/sem/lookups.nim index ea4e06e0125..bba1f93a20e 100644 --- a/compiler/sem/lookups.nim +++ b/compiler/sem/lookups.nim @@ -502,8 +502,6 @@ proc wrongRedefinition*( conflictsWith: PSym) = ## Emit a redefinition error if in non-interactive mode if c.config.cmd != cmdInteractive: - if sfTemplateParam in (s.flags + conflictsWith.flags): - return c.config.localReport(info, reportSymbols( rsemRedefinitionOf, @[s, conflictsWith])) diff --git a/compiler/sem/semexprs.nim b/compiler/sem/semexprs.nim index 50613731f29..80fc19411e6 100644 --- a/compiler/sem/semexprs.nim +++ b/compiler/sem/semexprs.nim @@ -1775,7 +1775,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = when defined(nimsuggest): if c.config.cmd == cmdIdeTools: suggestExpr(c, n) - if n.len > 0 and c.config.m.trackPos == n[1].info: suggestExprNoCheck(c, n) + if c.config.m.trackPos == n[1].info: suggestExprNoCheck(c, n) let s = qualifiedLookUp(c, n, {checkAmbiguity, checkUndeclared, checkModule}) if s.isError: diff --git a/compiler/tools/suggest.nim b/compiler/tools/suggest.nim index 3f80b275263..3c097ae60cf 100644 --- a/compiler/tools/suggest.nim +++ b/compiler/tools/suggest.nim @@ -73,7 +73,7 @@ const sep = '\t' proc findDocComment(n: PNode): PNode = - if n == nil or n.kind == nkError: return nil + if n == nil: return nil if n.comment.len > 0: return n if n.kind in {nkStmtList, nkStmtListExpr, nkObjectTy, nkRecList} and n.len > 0: result = findDocComment(n[0]) @@ -543,7 +543,6 @@ proc suggestSym*(g: ModuleGraph; info: TLineInfo; s: PSym; usageSym: var PSym; i elif conf.ideCmd == ideHighlight and info.fileIndex == conf.m.trackPos.fileIndex: suggestResult(conf, symToSuggest(g, s, isLocal=false, ideHighlight, info, 100, PrefixMatch.None, false, 0)) elif conf.ideCmd == ideOutline and isDecl and info.fileIndex == conf.m.trackPos.fileIndex: - if s.kind == skTemp or {sfGenSym, sfTemplateParam} * s.flags != {}: return if "`gensym" in s.name.s: return # prevent constant in template show up suggestResult(conf, symToSuggest(g, s, isLocal=false, ideOutline, info, 100, PrefixMatch.None, false, 0)) From bf9b75f69c7394513d1e4b26c4584fb54d494a7f Mon Sep 17 00:00:00 2001 From: bung87 Date: Tue, 10 Oct 2023 20:06:23 +0800 Subject: [PATCH 73/84] follow upstream changes --- nimlsp/nimlsppkg/nimsuggest.nim | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/nimlsp/nimlsppkg/nimsuggest.nim b/nimlsp/nimlsppkg/nimsuggest.nim index fb3a51d8d36..1dbe24d2b0f 100644 --- a/nimlsp/nimlsppkg/nimsuggest.nim +++ b/nimlsp/nimlsppkg/nimsuggest.nim @@ -88,8 +88,7 @@ proc initNimSuggest*(project: string, nimPath: string = ""): NimSuggest = conf.projectName = a proc reportHook(conf: ConfigRef, report: Report): TErrorHandling = result = doNothing - if report.kind notin {rsemProcessing, rsemProcessingStmt} and - conf.isEnabled(report.kind): + if report.kind notin {rsemProcessing, rsemProcessingStmt}: # pre-filter to save memory if containsOrIncl(reported, hash(report)): return cachedMsgs.add(report) @@ -191,8 +190,7 @@ proc runCmd*(nimsuggest: NimSuggest, cmd: IdeCmd, file, report.kind in {rsemProcessing, rsemProcessingStmt}: # skip processing statements return - if conf.isEnabled(report.kind): - addReport(report) + addReport(report) else: discard else: From 6235d8f3de5ea57ec5e73b60421b7e1d227bfdd3 Mon Sep 17 00:00:00 2001 From: bung87 Date: Wed, 11 Oct 2023 10:54:57 +0800 Subject: [PATCH 74/84] report no symbol at position --- nimlsp/nimlsppkg/nimsuggest.nim | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/nimlsp/nimlsppkg/nimsuggest.nim b/nimlsp/nimlsppkg/nimsuggest.nim index 1dbe24d2b0f..2fc081d77e0 100644 --- a/nimlsp/nimlsppkg/nimsuggest.nim +++ b/nimlsp/nimlsppkg/nimsuggest.nim @@ -32,6 +32,10 @@ import passaux, ] +from compiler/ast/reports_sem import reportSem +from compiler/ast/report_enums import ReportCategory, + ReportKind + from compiler/ast/reports import Report, category, kind, @@ -138,7 +142,7 @@ proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int, if u != nil: listUsages(graph, u) else: - stderr.writeLine "found no symbol at position: " & (conf $ conf.m.trackPos) + localReport(conf, conf.m.trackPos, reportSem(rsemSugNoSymbolAtPosition)) proc reportToSuggest(conf: ConfigRef, info: TLineInfo, r: Report): Suggest = Suggest(section: ideChk, filePath: toFullPath(conf, info), From 9b1ecbd427f33fcfadbf2a02d7ce0cc9ca1fd76e Mon Sep 17 00:00:00 2001 From: bung87 Date: Wed, 11 Oct 2023 15:15:31 +0800 Subject: [PATCH 75/84] fix when parentModule not found --- compiler/modules/modulegraphs.nim | 2 +- compiler/tools/suggest.nim | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/modules/modulegraphs.nim b/compiler/modules/modulegraphs.nim index 95226f44a6c..23e06e0c67e 100644 --- a/compiler/modules/modulegraphs.nim +++ b/compiler/modules/modulegraphs.nim @@ -599,7 +599,7 @@ proc parentModule*(g: ModuleGraph; fileIdx: FileIndex): FileIndex = if fileIdx.int32 >= 0 and fileIdx.int32 < g.ifaces.len and g.ifaces[fileIdx.int32].module != nil: result = fileIdx else: - result = g.inclToMod.getOrDefault(fileIdx) + result = g.inclToMod.getOrDefault(fileIdx, InvalidFileIdx) proc markDirty*(g: ModuleGraph; fileIdx: FileIndex) = let m = g.getModule fileIdx diff --git a/compiler/tools/suggest.nim b/compiler/tools/suggest.nim index 3c097ae60cf..f18588b12e9 100644 --- a/compiler/tools/suggest.nim +++ b/compiler/tools/suggest.nim @@ -492,7 +492,9 @@ proc executeCmd*(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int; dirtyfile.isEmpty: discard "no need to recompile anything" else: - let modIdx = graph.parentModule(dirtyIdx) + var modIdx = graph.parentModule(dirtyIdx) + if modIdx == InvalidFileIdx: + modIdx = dirtyIdx graph.markDirty dirtyIdx graph.markClientsDirty dirtyIdx # partially recompiling the project means that that VM and JIT state From 697e445789a3c254604845ab47b08a495376b5d6 Mon Sep 17 00:00:00 2001 From: bung87 Date: Thu, 12 Oct 2023 22:41:08 +0800 Subject: [PATCH 76/84] fix rstToMarkdown --- nimlsp/nimlsppkg/utils.nim | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/nimlsp/nimlsppkg/utils.nim b/nimlsp/nimlsppkg/utils.nim index acecc83ab9a..81bc05a7fc2 100644 --- a/nimlsp/nimlsppkg/utils.nim +++ b/nimlsp/nimlsppkg/utils.nim @@ -42,7 +42,6 @@ proc getProjectFile*(file: string): string = proc rstToMarkdown*(content: string): string = var - c: string isCodeBlock = false const BlockStart = ".. code-block::" const BlockLen = BlockStart.len + 1 @@ -52,19 +51,19 @@ proc rstToMarkdown*(content: string): string = isCodeBlock = true if line.endsWith("Nim\n") or line.endsWith("nim\n") or line.len == BlockLen: - c.add "```nim\n" + result.add "```nim\n" else: - c.add "```\n" + result.add "```\n" elif isCodeBlock and line.strip() == "": - c.add "```\n" + result.add "```\n" isCodeBlock = false else: - c.add line + result.add line if isCodeBlock: # single code block and ends without trailing line - c.add "```\n" + result.add "```\n" # admonition labels - c = multiReplace(c, + result = multiReplace(result, (".. attention::", "**attention**"), (".. caution::", "**caution**"), (".. danger::", "**danger**"), From f977269c5ce700acf48d63360766641642ab2dbd Mon Sep 17 00:00:00 2001 From: bung87 Date: Fri, 13 Oct 2023 00:30:38 +0800 Subject: [PATCH 77/84] map label --- nimlsp/nimlsppkg/suggestlib.nim | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nimlsp/nimlsppkg/suggestlib.nim b/nimlsp/nimlsppkg/suggestlib.nim index c1569cf7cfb..2e7ba6e8751 100644 --- a/nimlsp/nimlsppkg/suggestlib.nim +++ b/nimlsp/nimlsppkg/suggestlib.nim @@ -67,7 +67,8 @@ func nimSymToLSPKind*(suggest: byte): SymbolKind = of skType: SymbolKind.Class of skVar: SymbolKind.Variable of skFunc: SymbolKind.Function - else: SymbolKind.Function + of skLabel: SymbolKind.Key + else: SymbolKind.Field func nimSymDetails*(suggest: Suggest): string = case suggest.symKind.TSymKind: From 107d6f87ed2b1c62aa0d4807b17653d9d2a48c10 Mon Sep 17 00:00:00 2001 From: bung87 Date: Sat, 14 Oct 2023 16:10:03 +0800 Subject: [PATCH 78/84] generic params in outline --- compiler/front/options.nim | 3 +-- compiler/sem/semstmts.nim | 6 +++++- compiler/tools/suggest.nim | 23 ++++++++++++++++++++--- nimlsp/nimlsp.nim | 2 +- 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/compiler/front/options.nim b/compiler/front/options.nim index 5e78196c1a7..9582caf257b 100644 --- a/compiler/front/options.nim +++ b/compiler/front/options.nim @@ -146,8 +146,7 @@ type Suggest* = ref object section*: IdeCmd qualifiedPath*: seq[string] - name*: ptr string ## not used beyond sorting purposes; name is - ## also part of 'qualifiedPath' + name*: string ## display name filePath*: string line*: int ## Starts at 1 column*: int ## Starts at 0 diff --git a/compiler/sem/semstmts.nim b/compiler/sem/semstmts.nim index a4beb49e917..c1a2bd0eb30 100644 --- a/compiler/sem/semstmts.nim +++ b/compiler/sem/semstmts.nim @@ -364,7 +364,8 @@ proc semIdentDef(c: PContext, n: PNode, kind: TSymKind): PSym = result.options = c.config.options let info = getIdentLineInfo(n) - suggestSym(c.graph, info, result, c.graph.usageSym) + if kind != skType: + suggestSym(c.graph, info, result, c.graph.usageSym) proc checkNilableOrError(c: PContext; def: PNode): PNode = ## checks if a symbol node is nilable, on success returns def, else nkError @@ -2118,6 +2119,9 @@ proc typeSectionFinalPass(c: PContext, n: PNode) = # fix bug #5170, bug #17162, bug #15526: ensure locally scoped types get a unique name: if s.typ.kind in {tyEnum, tyRef, tyObject} and not isTopLevel(c): incl(s.flags, sfGenSym) + when defined(nimsuggest): + if c.config.cmd == cmdIdeTools: + suggestSym(c.graph, s.info, s, c.graph.usageSym) proc semTypeSection(c: PContext, n: PNode): PNode = ## Processes a type section. This must be done in separate passes, in order diff --git a/compiler/tools/suggest.nim b/compiler/tools/suggest.nim index f18588b12e9..6df24f83deb 100644 --- a/compiler/tools/suggest.nim +++ b/compiler/tools/suggest.nim @@ -106,7 +106,7 @@ proc cmpSuggestions(a, b: Suggest): int = cf globalUsages # if all is equal, sort alphabetically for deterministic output, # independent of hashing order: - result = cmp(a.name[], b.name[]) + result = cmp(a.name[0], b.name[0]) proc getTokenLenFromSource(conf: ConfigRef; ident: string; info: TLineInfo): int = let @@ -158,7 +158,24 @@ proc symToSuggest(g: ModuleGraph; s: PSym, isLocal: bool, section: IdeCmd, info: if section in {ideSug, ideCon}: result.contextFits = inTypeContext == (s.kind in {skType, skGenericParam}) result.scope = scope - result.name = addr s.name.s + result.name = s.name.s + if isGenericRoutineStrict(s): + let params = s.ast[genericParamsPos] + let len = params.safeLen + var genericParams = if len > 0: "[" else: "" + for i in 0 ..< len: + genericParams.add getPIdent(params[i]).s + if i < len - 1: genericParams.add(", ") + if len > 0: genericParams.add "]" + result.name.add genericParams + elif s.kind == skType: + let len = s.typ.sons.len - 1 + var genericParams = if len > 0: "[" else: "" + for i in 0 ..< len: + genericParams.add typeToString(s.typ.sons[i]) + if i < len - 1: genericParams.add(", ") + if len > 0: genericParams.add "]" + result.name.add genericParams when defined(nimsuggest): if section in {ideSug, ideCon}: result.globalUsages = s.allUsages.len @@ -180,7 +197,7 @@ proc symToSuggest(g: ModuleGraph; s: PSym, isLocal: bool, section: IdeCmd, info: result.qualifiedPath.add('`' & s.name.s & '`') else: result.qualifiedPath.add(s.name.s) - + if s.typ != nil: result.forth = typeToString(s.typ) else: diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index 5754922be98..1133e810bf6 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -373,7 +373,7 @@ proc main(ins: Stream, outs: Stream) = if sym.qualifiedPath.len != 2: continue resp.add create(DocumentSymbol, - sym.qualifiedPath[^1], + sym.name, some(symKindToString(sym.symKind)), nimSymToLSPKind(sym.symKind).int, some(sym.flags.mapIt(it.int)), From 96e1dc94319e9a0ff197b8b325885eb822f794fe Mon Sep 17 00:00:00 2001 From: bung87 Date: Sat, 14 Oct 2023 19:05:27 +0800 Subject: [PATCH 79/84] clean readme --- nimlsp/README.rst | 213 +--------------------------------------------- 1 file changed, 2 insertions(+), 211 deletions(-) diff --git a/nimlsp/README.rst b/nimlsp/README.rst index 3704efb0b97..f1732bc7300 100644 --- a/nimlsp/README.rst +++ b/nimlsp/README.rst @@ -1,64 +1,14 @@ ========== -Nim Language Server Protocol +Nimskull Language Server Protocol ========== This is a `Language Server Protocol `_ implementation in -Nim, for Nim. +Nimskull, for Nimskull. It is based on nimsuggest, which means that every editor that supports LSP will now have the same quality of suggestions that has previously only been available in supported editors. -Installing ``nimlsp`` -======= -If you have installed Nim through ``choosenim`` (recommended) the easiest way to -install ``nimlsp`` is to use ``nimble`` with: - -.. code:: bash - - nimble install nimlsp - -This will compile and install it in the ``nimble`` binary directory, which if -you have set up ``nimble`` correctly it should be in your path. When compiling -and using ``nimlsp`` it needs to have Nim's sources available in order to work. -With Nim installed through ``choosenim`` these should already be on your system -and ``nimlsp`` should be able to find and use them automatically. However if you -have installed ``nimlsp`` in a different way you might run into issues where it -can't find certain files during compilation/running. To fix this you need to -grab a copy of Nim sources and then point ``nimlsp`` at them on compile-time by -using ``-d:explicitSourcePath=PATH``, where ``PATH`` is where you have your Nim -sources. You can also pass them at run-time (if for example you're working with -a custom copy of the stdlib by passing it as an argument to ``nimlsp``. How -exectly to do that will depend on the LSP client. - -Compile ``nimlsp`` -======= -If you want more control over the compilation feel free to clone the -repository. ``nimlsp`` depends on the ``nimsuggest`` sources which are in the main -Nim repository, so make sure you have a copy of that somewhere. Manually having a -copy of Nim this way means the default source path will not work so you need to -set it explicitly on compilation with ``-d:explicitSourcePath=PATH`` and point to -it at runtime (technically the runtime should only need the stdlib, so omitting -it will make ``nimlsp`` try to find it from your Nim install). - -To do the standard build run: - -.. code:: bash - - nimble build - -Or if you want debug output when ``nimlsp`` is running: - -.. code:: bash - - nimble debug - -Or if you want even more debug output from the LSP format: - -.. code:: bash - - nimble debug -d:debugLogging - Supported Protocol features ======= @@ -85,162 +35,3 @@ Status LSP Command ☐ TODO `$/progress` ☐ TODO `textDocument/codeAction` ====== ================================ - - -Setting up ``nimlsp`` -======= -Sublime Text -------- -Install the `LSP plugin `_. -Install the `NimLime plugin `_ for syntax highlighting. - -Apart from syntax highlighting, NimLime can perform many of the features that ``nimlsp`` provides. -It is recommended to disable those for optimal experience. -For this, navigate to ``Preferences > Package Settings > NimLime > Settings`` and set ``*.enabled`` settings to ``false``: - -.. code:: js - - { - "error_handler.enabled": false, - "check.on_save.enabled": false, - "check.current_file.enabled": false, - "check.external_file.enabled": false, - "check.clear_errors.enabled": false, - } - -To set up LSP, run ``Preferences: LSP settings`` from the command palette and add the following: - -.. code:: js - - { - "clients": { - "nimlsp": { - "command": ["nimlsp"], - "enabled": true, - - // ST4 only - "selector": "source.nim", - - // ST3 only - "languageId": "nim", - "scopes": ["source.nim"], - "syntaxes": ["Packages/NimLime/Syntaxes/Nim.tmLanguage"] - } - } - } - -*Note: Make sure ``/.nimble/bin`` is added to your ``PATH``.* - -Vim -------- -To use ``nimlsp`` in Vim install the ``prabirshrestha/vim-lsp`` plugin and -dependencies: - -.. code:: vim - - Plugin 'prabirshrestha/asyncomplete.vim' - Plugin 'prabirshrestha/async.vim' - Plugin 'prabirshrestha/vim-lsp' - Plugin 'prabirshrestha/asyncomplete-lsp.vim' - -Then set it up to use ``nimlsp`` for Nim files: - -.. code:: vim - - let s:nimlspexecutable = "nimlsp" - let g:lsp_log_verbose = 1 - let g:lsp_log_file = expand('/tmp/vim-lsp.log') - " for asyncomplete.vim log - let g:asyncomplete_log_file = expand('/tmp/asyncomplete.log') - - let g:asyncomplete_auto_popup = 0 - - if has('win32') - let s:nimlspexecutable = "nimlsp.cmd" - " Windows has no /tmp directory, but has $TEMP environment variable - let g:lsp_log_file = expand('$TEMP/vim-lsp.log') - let g:asyncomplete_log_file = expand('$TEMP/asyncomplete.log') - endif - if executable(s:nimlspexecutable) - au User lsp_setup call lsp#register_server({ - \ 'name': 'nimlsp', - \ 'cmd': {server_info->[s:nimlspexecutable]}, - \ 'whitelist': ['nim'], - \ }) - endif - - function! s:check_back_space() abort - let col = col('.') - 1 - return !col || getline('.')[col - 1] =~ '\s' - endfunction - - inoremap - \ pumvisible() ? "\" : - \ check_back_space() ? "\" : - \ asyncomplete#force_refresh() - inoremap pumvisible() ? "\" : "\" - -This configuration allows you to hit Tab to get auto-complete, and to call -various functions to rename and get definitions. Of course you are free to -configure this any way you'd like. - -Emacs -------- - -With lsp-mode and use-package: - -.. code:: emacs-lisp - - (use-package nim-mode - :ensure t - :hook - (nim-mode . lsp)) - -Intellij -------- -You will need to install the `LSP support plugin `_. -For syntax highlighting i would recommend the "official" `nim plugin `_ -(its not exactly official, but its developed by an intellij dev), the plugin will eventually use nimsuggest and have support for -all this things and probably more, but since its still very new most of the features are still not implemented, so the LSP is a -decent solution (and the only one really). - -To use it: - -1. Install the LSP and the nim plugin. - -2. Go into ``settings > Language & Frameworks > Language Server Protocol > Server Definitions``. - -3. Set the LSP mode to ``executable``, the extension to ``nim`` and in the Path, the path to your nimlsp executable. - -4. Hit apply and everything should be working now. - -Kate -------- -The LSP plugin has to be enabled in the Kate (version >= 19.12.0) plugins menu: - -1. In ``Settings > Configure Kate > Application > Plugins``, check box next to ``LSP Client`` to enable LSP functionality. - -2. Go to the now-available LSP Client menu (``Settings > Configure Kate > Application``) and enter the following in the User Server Settings tab: - -.. code:: json - - { - "servers": { - "nim": { - "command": [".nimble/bin/nimlsp"], - "url": "https://github.com/PMunch/nimlsp", - "highlightingModeRegex": "^Nim$" - } - } - } - -This assumes that nimlsp was installed through nimble. -*Note: Server initialization may fail without full path specified, from home directory, under the ``"command"`` entry, even if nimlsp is in system's ``PATH``.* - -Run Tests -========= -Not too many at the moment unfortunately, but they can be run with: - -.. code:: bash - - nimble test From 3244035a7d61cb41575f567444813d7d231226b4 Mon Sep 17 00:00:00 2001 From: bung87 Date: Sat, 14 Oct 2023 19:05:38 +0800 Subject: [PATCH 80/84] remove nimble file --- nimlsp/nimlsp.nimble | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 nimlsp/nimlsp.nimble diff --git a/nimlsp/nimlsp.nimble b/nimlsp/nimlsp.nimble deleted file mode 100644 index 77414e31cd2..00000000000 --- a/nimlsp/nimlsp.nimble +++ /dev/null @@ -1,25 +0,0 @@ -# Package - -version = "0.4.4" -author = "PMunch" -description = "Nim Language Server Protocol - nimlsp implements the Language Server Protocol" -license = "MIT" -srcDir = "src" -bin = @["nimlsp", "nimlsp_debug"] - -# Dependencies - -# nimble test does not work for me out of the box -#task test, "Runs the test suite": - #exec "nim c -r tests/test_messages.nim" -# exec "nim c -d:debugLogging -d:jsonSchemaDebug -r tests/test_messages2.nim" - -task debug, "Builds the language server": - exec "nim c --threads:on -d:nimcore -d:nimsuggest -d:debugCommunication -d:debugLogging -o:nimlsp src/nimlsp" - -before test: - exec "nimble build" - -task findNim, "Tries to find the current Nim installation": - echo NimVersion - echo currentSourcePath From c533c21ce442b7eac10faa9506fac1d2c6e8e5de Mon Sep 17 00:00:00 2001 From: bung87 Date: Sat, 14 Oct 2023 19:24:34 +0800 Subject: [PATCH 81/84] meta type guard --- compiler/tools/suggest.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/tools/suggest.nim b/compiler/tools/suggest.nim index 6df24f83deb..88d04f5fe14 100644 --- a/compiler/tools/suggest.nim +++ b/compiler/tools/suggest.nim @@ -168,7 +168,7 @@ proc symToSuggest(g: ModuleGraph; s: PSym, isLocal: bool, section: IdeCmd, info: if i < len - 1: genericParams.add(", ") if len > 0: genericParams.add "]" result.name.add genericParams - elif s.kind == skType: + elif s.kind == skType and s.typ.isMetaType: let len = s.typ.sons.len - 1 var genericParams = if len > 0: "[" else: "" for i in 0 ..< len: From b90eb550a55026f2cfceb2ec6684b8a04befb16d Mon Sep 17 00:00:00 2001 From: bung87 Date: Wed, 18 Oct 2023 18:49:04 +0800 Subject: [PATCH 82/84] unrelated changes --- compiler/tools/suggest.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/tools/suggest.nim b/compiler/tools/suggest.nim index a67a15170c9..ec16b751d37 100644 --- a/compiler/tools/suggest.nim +++ b/compiler/tools/suggest.nim @@ -195,7 +195,7 @@ proc symToSuggest(g: ModuleGraph; s: PSym, isLocal: bool, section: IdeCmd, info: result.qualifiedPath.add('`' & s.name.s & '`') else: result.qualifiedPath.add(s.name.s) - + if s.typ != nil: result.forth = typeToString(s.typ) else: From 8057ad37aab7019e43f26574ab0f1a68ec92c592 Mon Sep 17 00:00:00 2001 From: bung87 Date: Wed, 18 Oct 2023 20:19:08 +0800 Subject: [PATCH 83/84] sync upstream --- compiler/tools/suggest.nim | 10 ++-------- nimlsp/nimlsppkg/nimsuggest.nim | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/compiler/tools/suggest.nim b/compiler/tools/suggest.nim index ec16b751d37..ca5b47d15e1 100644 --- a/compiler/tools/suggest.nim +++ b/compiler/tools/suggest.nim @@ -43,6 +43,7 @@ import lexer, lineinfos, linter, + renderer, types, typesrenderer, wordrecg, @@ -158,14 +159,7 @@ proc symToSuggest(g: ModuleGraph; s: PSym, isLocal: bool, section: IdeCmd, info: result.scope = scope result.name = s.name.s if isGenericRoutineStrict(s): - let params = s.ast[genericParamsPos] - let len = params.safeLen - var genericParams = if len > 0: "[" else: "" - for i in 0 ..< len: - genericParams.add getPIdent(params[i]).s - if i < len - 1: genericParams.add(", ") - if len > 0: genericParams.add "]" - result.name.add genericParams + result.name.add renderTree(s.ast[genericParamsPos]) elif s.kind == skType and s.typ.isMetaType: let len = s.typ.sons.len - 1 var genericParams = if len > 0: "[" else: "" diff --git a/nimlsp/nimlsppkg/nimsuggest.nim b/nimlsp/nimlsppkg/nimsuggest.nim index 2fc081d77e0..d6882695863 100644 --- a/nimlsp/nimlsppkg/nimsuggest.nim +++ b/nimlsp/nimlsppkg/nimsuggest.nim @@ -44,7 +44,7 @@ from compiler/ast/reports import Report, from compiler/front/main import customizeForBackend -from compiler/tools/suggest import findTrackedSym, executeCmd, listUsages, suggestSym, `$` +from compiler/tools/suggest import findTrackedSym, executeCmd, listUsages, suggestSym export Suggest export IdeCmd From 2fd2c2ddc0ca552148e0b3bd71a3226f3290fe5f Mon Sep 17 00:00:00 2001 From: bung87 Date: Sat, 6 Jan 2024 12:26:31 +0800 Subject: [PATCH 84/84] sleep 16ms when using synchronous io --- nimlsp/nimlsp.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/nimlsp/nimlsp.nim b/nimlsp/nimlsp.nim index 1133e810bf6..21e645cb1d4 100644 --- a/nimlsp/nimlsp.nim +++ b/nimlsp/nimlsp.nim @@ -538,6 +538,7 @@ proc main(ins: Stream, outs: Stream) = except CatchableError as e: warnLog "Got exception: ", e.msg continue + sleep(16) when isMainModule: infoLog("explicitSourcePath: ", explicitSourcePath)