diff --git a/README.rst b/README.rst index 32b840c..3ae8839 100644 --- a/README.rst +++ b/README.rst @@ -25,23 +25,28 @@ or if you want debug output Supported Protocol features ======= -- [x] textDocument/didChange -- [x] textDocument/didClose -- [x] textDocument/didOpen -- [ ] textDocument/didSave - -- [ ] textDocument/codeAction -- [x] textDocument/completion -- [x] textDocument/definition -- [ ] textDocument/documentHighlight -- [ ] textDocument/documentSymbol -- [ ] textDocument/executeCommand -- [ ] textDocument/format -- [x] textDocument/hover -- [ ] textDocument/rename -- [x] textDocument/references -- [ ] textDocument/signatureHelp -- [ ] workspace/symbol +====== ================================ +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 +☐ TODO textDocument/documentSymbol +☐ TODO textDocument/executeCommand +☐ TODO textDocument/format +☑ DONE textDocument/hover +☐ TODO textDocument/rename +☑ DONE textDocument/references +☐ TODO textDocument/signatureHelp +☑ DONE textDocument/publishDiagnostics +☐ TODO workspace/symbol +====== ================================ + Setting up `nimlsp` ======= @@ -59,6 +64,7 @@ in syntax highlighting and some definitions. If you know how to disable the overlapping features or achieve this in another way please update this section. Now in order to set up LSP itself enter it's settings and add this: + .. code:: json { diff --git a/src/nimlsp.nim b/src/nimlsp.nim index 22eb783..6959f26 100644 --- a/src/nimlsp.nim +++ b/src/nimlsp.nim @@ -57,15 +57,15 @@ template textDocumentRequest(message, kind, name, body) {.dirty.} = filestash = storage / (hash(fileuri).toHex & ".nim" ) debugEcho "Got request for URI: ", fileuri, " copied to " & filestash let - rawLine = name["position"]["line"].getInt - rawChar = name["position"]["character"].getInt + rawLine = max(name["position"]["line"].getInt, 0) + rawChar = max(name["position"]["character"].getInt, 0) body template textDocumentNotification(message, kind, name, body) {.dirty.} = if message["params"].isSome: let name = message["params"].unsafeGet whenValid(name, kind): - if name["textDocument"]["languageId"].getStr == "nim": + if not name["textDocument"].hasKey("languageId") or name["textDocument"]["languageId"].getStr == "nim": let fileuri = name["textDocument"]["uri"].getStr filestash = storage / (hash(fileuri).toHex & ".nim" ) @@ -77,6 +77,9 @@ proc respond(request: RequestMessage, data: JsonNode) = proc error(request: RequestMessage, errorCode: int, message: string, data: JsonNode) = outs.sendJson create(ResponseMessage, "2.0", request["id"].getInt, none(JsonNode), some(create(ResponseError, errorCode, message, data))).JsonNode +proc notify(notification: string, data: JsonNode) = + outs.sendJson create(NotificationMessage, "2.0", notification, some(data)).JsonNode + type Certainty = enum None, Folder, @@ -106,10 +109,14 @@ proc getProjectFile(file: string): string = discard path = dir +template getNimsuggest(fileuri: string): Nimsuggest = + projectFiles[openFiles[fileuri].projectFile].nimsuggest + while true: try: + debugEcho "Trying to read frame" let frame = ins.readFrame - debugEcho frame + debugEcho "Got frame:\n" & frame let message = frame.parseJson whenValid(message, RequestMessage): debugEcho "Got valid Request message of type " & message["method"].getStr @@ -130,7 +137,7 @@ while true: change = some(TextDocumentSyncKind.Full.int), willSave = some(false), willSaveWaitUntil = some(false), - save = none(SaveOptions) + save = some(create(SaveOptions, some(true))) )), # ?: TextDocumentSyncOptions or int or float hoverProvider = some(true), # ?: bool completionProvider = some(create(CompletionOptions, @@ -162,7 +169,7 @@ while true: )).JsonNode) of "textDocument/completion": message.textDocumentRequest(CompletionParams, compRequest): - let suggestions = projectFiles[openFiles[fileuri].projectFile].nimsuggest.sug(fileuri[7..^1], dirtyfile = filestash, + let suggestions = getNimsuggest(fileuri).sug(fileuri[7..^1], dirtyfile = filestash, rawLine + 1, openFiles[fileuri].fingerTable[rawLine].utf16to8(rawChar) ) @@ -191,7 +198,7 @@ while true: message.respond completionItems of "textDocument/hover": message.textDocumentRequest(TextDocumentPositionParams, hoverRequest): - let suggestions = projectFiles[openFiles[fileuri].projectFile].nimsuggest.def(fileuri[7..^1], dirtyfile = filestash, + let suggestions = getNimsuggest(fileuri).def(fileuri[7..^1], dirtyfile = filestash, rawLine + 1, openFiles[fileuri].fingerTable[rawLine].utf16to8(rawChar) ) @@ -223,13 +230,13 @@ while true: message.respond create(Hover, markedString, rangeopt).JsonNode of "textDocument/references": message.textDocumentRequest(ReferenceParams, referenceRequest): - let suggestions = projectFiles[openFiles[fileuri].projectFile].nimsuggest.use(fileuri[7..^1], dirtyfile = filestash, + let suggestions = getNimsuggest(fileuri).use(fileuri[7..^1], dirtyfile = filestash, rawLine + 1, openFiles[fileuri].fingerTable[rawLine].utf16to8(rawChar) ) let declarations: seq[Suggestion] = if referenceRequest["context"]["includeDeclaration"].getBool: - projectFiles[openFiles[fileuri].projectFile].nimsuggest.def(fileuri[7..^1], dirtyfile = filestash, + getNimsuggest(fileuri).def(fileuri[7..^1], dirtyfile = filestash, rawLine + 1, openFiles[fileuri].fingerTable[rawLine].utf16to8(rawChar) ) @@ -260,11 +267,11 @@ while true: message.respond response of "textDocument/definition": message.textDocumentRequest(TextDocumentPositionParams, definitionRequest): - let suggestions = projectFiles[openFiles[fileuri].projectFile].nimsuggest.def(fileuri[7..^1], dirtyfile = filestash, + let suggestions = getNimsuggest(fileuri).def(fileuri[7..^1], dirtyfile = filestash, rawLine + 1, openFiles[fileuri].fingerTable[rawLine].utf16to8(rawChar) ) - let declarations = projectFiles[openFiles[fileuri].projectFile].nimsuggest.def(fileuri[7..^1], dirtyfile = filestash, + let declarations = getNimsuggest(fileuri).def(fileuri[7..^1], dirtyfile = filestash, rawLine + 1, openFiles[fileuri].fingerTable[rawLine].utf16to8(rawChar) ) @@ -296,7 +303,7 @@ while true: # let # rawLine = signRequest["position"]["line"].getInt # rawChar = signRequest["position"]["character"].getInt - # suggestions = projectFiles[openFiles[fileuri].projectFile].nimsuggest.con(fileuri[7..^1], dirtyfile = filestash, rawLine + 1, rawChar) + # suggestions = getNimsuggest(fileuri).con(fileuri[7..^1], dirtyfile = filestash, rawLine + 1, rawChar) else: debugEcho "Unknown request" @@ -350,11 +357,60 @@ while true: projectFiles[projectFile].openFiles -= 1 if projectFiles[projectFile].openFiles == 0: debugEcho "Trying to stop nimsuggest" - debugEcho "Stopped nimsuggest with code: " & $projectFiles[openFiles[fileuri].projectFile].nimsuggest.stopNimsuggest() + debugEcho "Stopped nimsuggest with code: " & $getNimsuggest(fileuri).stopNimsuggest() openFiles.del(fileuri) + of "textDocument/didSave": + message.textDocumentNotification(DidSaveTextDocumentParams, textDoc): + if textDoc["text"].isSome: + let file = open(filestash, fmWrite) + debugEcho "Got document change for URI: ", fileuri, " saving to ", filestash + openFiles[fileuri].fingerTable = @[] + for line in textDoc["text"].unsafeGet.getStr.splitLines: + openFiles[fileuri].fingerTable.add line.createUTFMapping() + file.writeLine line + file.close() + debugEcho "fileuri: ", fileuri, ", project file: ", openFiles[fileuri].projectFile, ", dirtyfile: ", filestash + let diagnostics = getNimsuggest(fileuri).chk(fileuri[7..^1], dirtyfile = filestash) + debugEcho "Found suggestions: ", + diagnostics[0..(if diagnostics.len > 10: 10 else: diagnostics.high)], + (if diagnostics.len > 10: " and " & $(diagnostics.len-10) & " more" else: "") + if diagnostics.len == 0: + notify("textDocument/publishDiagnostics", create(PublishDiagnosticsParams, + fileuri, + @[]).JsonNode + ) + else: + var response: seq[Diagnostic] + for diagnostic in diagnostics: + if diagnostic.line == 0: + continue + # Try to guess the size of the identifier + let + message = diagnostic.nimDocstring + endcolumn = max(diagnostic.column + message.rfind('\'') - message.find('\'') - 1, 0) + diagnosticLine = max(diagnostic.line-1, 0) + diagnosticColumn = max(diagnostic.column, endcolumn) + response.add create(Diagnostic, + create(Range, + create(Position, diagnosticLine, diagnosticColumn), + create(Position, diagnosticLine, diagnosticColumn) + ), + some(case diagnostic.qualifiedPath: + 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]) + ) + notify("textDocument/publishDiagnostics", create(PublishDiagnosticsParams, + fileuri, + response).JsonNode + ) else: debugEcho "Got unknown notification message" continue except IOError: - debugEcho "Got IOError: " & getCurrentExceptionMsg() break diff --git a/src/nimlsppkg/base_protocol.nim b/src/nimlsppkg/base_protocol.nim index 529a104..235624c 100644 --- a/src/nimlsppkg/base_protocol.nim +++ b/src/nimlsppkg/base_protocol.nim @@ -15,8 +15,7 @@ proc sendFrame*(s: Stream, frame: string) = when defined(debugCommunication): stderr.write(frame) stderr.write("\n") - s.write "Content-Length: " & $frame.len & "\r\n\r\n" & - frame & "\r\n\r\n" + s.write "Content-Length: " & $frame.len & "\r\n\r\n" & frame s.flush proc sendJson*(s: Stream, data: JsonNode) = diff --git a/src/nimlsppkg/mappings.nim b/src/nimlsppkg/mappings.nim index 8d102fc..84e6161 100644 --- a/src/nimlsppkg/mappings.nim +++ b/src/nimlsppkg/mappings.nim @@ -36,4 +36,4 @@ func nimSymDetails(suggest: Suggestion): string = else: suggest.signature func nimDocstring(suggest: Suggestion): string = - suggest.docstring[1 .. ^2].replace("\\x0A","\n") + suggest.docstring.unescape() diff --git a/src/nimlsppkg/messages2.nim b/src/nimlsppkg/messages2.nim index 58f10ee..e1dcdc0 100644 --- a/src/nimlsppkg/messages2.nim +++ b/src/nimlsppkg/messages2.nim @@ -139,7 +139,7 @@ jsonSchema: Range: start: Position - stop: Position + "end": Position Location: uri: string # Note that this is not checked @@ -190,6 +190,15 @@ jsonSchema: TextDocumentPositionParams: textDocument: TextDocumentIdentifier position: Position + buftype?: string # Added by LanguageCLient-neovim erroneously + character?: int # Added by LanguageCLient-neovim erroneously + filename?: string # Added by LanguageCLient-neovim erroneously + gotoCmd?: string or nil # Added by LanguageCLient-neovim erroneously + handle?: bool # Added by LanguageCLient-neovim erroneously + languageId?: string # Added by LanguageCLient-neovim erroneously + line?: int # Added by LanguageCLient-neovim erroneously + "method"?: string # Added by LanguageCLient-neovim erroneously + text?: string[] # Added by LanguageCLient-neovim erroneously DocumentFilter: language?: string diff --git a/src/nimlsppkg/nimsuggest.nim b/src/nimlsppkg/nimsuggest.nim index c56cc79..6e6489b 100644 --- a/src/nimlsppkg/nimsuggest.nim +++ b/src/nimlsppkg/nimsuggest.nim @@ -44,30 +44,40 @@ proc stopNimSuggest*(nimsuggest: NimSuggest): int = nimsuggest.close() else: result = -1 -template createCommand(command: untyped) = +template createCommand(command, body: untyped): untyped = + if not nimsuggest.running: + raise newException(IOError, "nimsuggest process is not running") + body + nimsuggest.inputStream.flush() + var line = "" + while line.len != 0 or result.len == 0: + line = nimsuggest.outputStream.readLine + if line.startsWith(command.astToStr): + result.add line.parseSuggestion + +template createFullCommand(command: untyped) {.dirty.} = proc command*(nimsuggest: NimSuggest, file: string, dirtyfile = "", line: int, col: int): seq[Suggestion] = - if not nimsuggest.running: - raise newException(IOError, "nimsuggest process is not running") - nimsuggest.inputStream.writeLine("$1 $2$3:$4:$5" % - [command.astToStr, file, (if dirtyfile.len > 0: ";$1" % [dirtyfile] else: ""), $line, $col]) - nimsuggest.inputStream.flush() - var line = "" - while line.len != 0 or result.len == 0: - line = nimsuggest.outputStream.readLine - if line.startsWith(command.astToStr): - result.add line.parseSuggestion + createCommand(command): + nimsuggest.inputStream.writeLine("$1 $2$3:$4:$5" % + [command.astToStr, file, (if dirtyfile.len > 0: ";$1" % [dirtyfile] else: ""), $line, $col]) + +template createFileOnlyCommand(command: untyped) {.dirty.} = + proc command*(nimsuggest: NimSuggest, file: string, dirtyfile = ""): seq[Suggestion] = + createCommand(command): + nimsuggest.inputStream.writeLine("$1 $2$3" % + [command.astToStr, file, (if dirtyfile.len > 0: ";$1" % [dirtyfile] else: "")]) -createCommand(sug) -createCommand(con) -createCommand(def) -createCommand(use) -createCommand(dus) -createCommand(chk) -createCommand(`mod`) -createCommand(highlight) -createCommand(outline) -createCommand(known) +createFullCommand(sug) +createFullCommand(con) +createFullCommand(def) +createFullCommand(use) +createFullCommand(dus) +createFileOnlyCommand(chk) +createFileOnlyCommand(`mod`) +createFileOnlyCommand(highlight) +createFileOnlyCommand(outline) +createFileOnlyCommand(known) when isMainModule: