diff --git a/asyncprocmonitor.nim b/asyncprocmonitor.nim index 64073f3..6068897 100644 --- a/asyncprocmonitor.nim +++ b/asyncprocmonitor.nim @@ -12,8 +12,8 @@ type Callback* = proc() {.closure, gcsafe, raises: [].} when defined(windows): import winlean - proc hookAsyncProcMonitor*(pid: int, cb: Callback) = discard - addProcess2(pid, (arg: pointer) => cb()) + proc hookAsyncProcMonitor*(pid: int, cb: Callback) = + discard addProcess2(pid, (arg: pointer) => cb()) elif defined(posix): proc hookAsyncProcMonitor*(pid: int, cb: Callback) = diff --git a/ls.nim b/ls.nim index de09d10..57a39b4 100644 --- a/ls.nim +++ b/ls.nim @@ -675,9 +675,9 @@ proc onErrorCallback(args: (LanguageServer, string), project: Project) = finally: if project.file != "": ls.projectErrors.add ProjectError( - projectFile: project.file, + projectFile: project.file, errorMessage: project.errorMessage, - lastKnownCmd: project.lastCmd + lastKnownCmd: project.lastCmd, ) ls.sendStatusChanged() @@ -820,7 +820,9 @@ proc maybeRequestConfigurationFromClient*(ls: LanguageServer) = debug "Client does not support workspace/configuration" ls.workspaceConfiguration.complete(newJArray()) -proc getCharacter*(ls: LanguageServer, uri: string, line: int, character: int): Option[int] = +proc getCharacter*( + ls: LanguageServer, uri: string, line: int, character: int +): Option[int] = if uri in ls.openFiles and line < ls.openFiles[uri].fingerTable.len: return some ls.openFiles[uri].fingerTable[line].utf16to8(character) else: diff --git a/lstransports.nim b/lstransports.nim index 5c2acec..f7d56b3 100644 --- a/lstransports.nim +++ b/lstransports.nim @@ -4,12 +4,12 @@ import std/[syncio, os, json, strutils, strformat, streams, oids, sequtils, time import ls, utils import protocol/types, chronos/threadsync -type +type LspClientResponse* = object jsonrpc*: JsonRPC2 id*: string result*: JsonNode - + Rpc* = proc(params: RequestParamsRx): Future[JsonString] {.gcsafe, raises: [].} template flavorUsesAutomaticObjectSerialization(T: type JrpcSys): bool = @@ -40,14 +40,13 @@ proc toJson*(params: RequestParamsRx): JsonNode = for p in params.positional: result.add parseJson($p) -proc wrapRpc*[T]( - fn: proc(params: T): Future[auto] {.gcsafe, raises: [].} -): Rpc = +proc wrapRpc*[T](fn: proc(params: T): Future[auto] {.gcsafe, raises: [].}): Rpc = return proc(params: RequestParamsRx): Future[JsonString] {.gcsafe, async.} = var val = params.to(T) when typeof(fn(val)) is Future[void]: #Notification await fn(val) - return JsonString("{}") #Client doesnt expect a response. Handled in processMessage + return + JsonString("{}") #Client doesnt expect a response. Handled in processMessage else: let res = await fn(val) return JsonString($(%*res)) @@ -66,21 +65,23 @@ proc wrapRpc*[T]( return JsonString($(%*res)) proc addRpcToCancellable*(ls: LanguageServer, rpc: Rpc): Rpc = - return proc(params: RequestParamsRx): Future[JsonString] {.gcsafe, raises:[].} = + return proc(params: RequestParamsRx): Future[JsonString] {.gcsafe, raises: [].} = try: let idRequest = get[uint](params, "idRequest") let name = get[string](params, "method") - ls.pendingRequests[idRequest] = PendingRequest(id: idRequest, name: name, startTime: now(), state: prsOnGoing) + ls.pendingRequests[idRequest] = + PendingRequest(id: idRequest, name: name, startTime: now(), state: prsOnGoing) ls.sendStatusChanged var fut = rpc(params) - ls.pendingRequests[idRequest].request = fut #we need to add it before because the rpc may access to the pendingRequest to set the projectFile - fut.addCallback proc (d: pointer) = + ls.pendingRequests[idRequest].request = fut + #we need to add it before because the rpc may access to the pendingRequest to set the projectFile + fut.addCallback proc(d: pointer) = try: ls.pendingRequests[idRequest].state = prsComplete ls.pendingRequests[idRequest].endTime = now() ls.sendStatusChanged except KeyError: - error "Error completing pending requests. Id not found in pending requests" + error "Error completing pending requests. Id not found in pending requests" return fut except KeyError as ex: error "IdRequest not found in the request params" @@ -89,7 +90,6 @@ proc addRpcToCancellable*(ls: LanguageServer, rpc: Rpc): Rpc = error "Error adding request to cancellable requests" writeStackTrace(ex) - proc processContentLength*(inputStream: FileStream): string = result = inputStream.readLine() if result.startsWith(CONTENT_LENGTH): @@ -97,12 +97,14 @@ proc processContentLength*(inputStream: FileStream): string = let length = parseInt(parts[1]) discard inputStream.readLine() # skip the \r\n result = newString(length) - for i in 0.. 0 and paramStr(1) in ["-v", "--version"]: echo LSPVersion @@ -74,13 +105,13 @@ proc handleParams(): CommandLineParams = quit(1) inc i if result.transport.isSome and result.transport.get == socket: - if result.port == default(Port): - result.port = getNextFreePort() + if result.port == default(Port): + result.port = getNextFreePort() echo &"port={result.port}" if result.transport.isNone: result.transport = some stdio -proc registerProcMonitor(ls: LanguageServer) = +proc registerProcMonitor(ls: LanguageServer) = if ls.cmdLineClientProcessId.isSome: debug "Registering monitor for process id, specified on command line", clientProcessId = ls.cmdLineClientProcessId.get @@ -101,8 +132,7 @@ proc registerProcMonitor(ls: LanguageServer) = hookAsyncProcMonitor(ls.cmdLineClientProcessId.get, onCmdLineClientProcessExit) - -proc tickLs(ls: LanguageServer, time = 1.seconds) {.async.} = +proc tickLs(ls: LanguageServer, time = 1.seconds) {.async.} = await ls.tick() await sleepAsync(time) await ls.tickLs() @@ -113,9 +143,9 @@ proc main*(cmdLineParams: CommandLineParams): LanguageServer = `nimlangserver` supports both transports: stdio and socket. By default it uses stdio transport. But we do construct a RPC socket server even in stdio mode, so that we can reuse the same code for both transports. ]# - result = initLs(cmdLineParams, ensureStorageDir()) - case result.transportMode: - of stdio: + result = initLs(cmdLineParams, ensureStorageDir()) + case result.transportMode + of stdio: result.startStdioServer() of socket: result.startSocketServer(cmdLineParams.port) @@ -123,7 +153,7 @@ proc main*(cmdLineParams: CommandLineParams): LanguageServer = result.srv.registerRoutes(result) result.registerProcMonitor() -when isMainModule: +when isMainModule: try: let ls = main(handleParams()) asyncSpawn ls.tickLs() @@ -134,9 +164,7 @@ when isMainModule: ls.stopNimsuggestProcessesP() exitnow(1) runForever() - except Exception as e: error "Error in main" writeStackTrace e quit(1) - diff --git a/nimlangserver.nimble b/nimlangserver.nimble index b00aeda..bf29402 100644 --- a/nimlangserver.nimble +++ b/nimlangserver.nimble @@ -1,24 +1,19 @@ mode = ScriptMode.Verbose -packageName = "nimlangserver" -version = "1.5.2" -author = "The core Nim team" -description = "Nim language server for IDEs" -license = "MIT" -bin = @["nimlangserver"] -skipDirs = @["tests"] +packageName = "nimlangserver" +version = "1.5.2" +author = "The core Nim team" +description = "Nim language server for IDEs" +license = "MIT" +bin = @["nimlangserver"] +skipDirs = @["tests"] -requires "nim == 2.0.8", - "chronos > 4", - "json_rpc#head", - "with", - "chronicles", - "serialization", - "json_serialization", - "stew" +requires "nim == 2.0.8", + "chronos > 4", "json_rpc#head", "with", "chronicles", "serialization", + "json_serialization", "stew" - ---path:"." +--path: + "." task test, "run tests": --silent diff --git a/protocol/enums.nim b/protocol/enums.nim index eb13dbf..5d41570 100644 --- a/protocol/enums.nim +++ b/protocol/enums.nim @@ -1,135 +1,155 @@ -type - ErrorCode* = enum - RequestFailed = -32803, - ServerCancelled = -32802, - ContentModified = -32801, - 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, - UnknownErrorCode = -32001, - ServerErrorEnd = -32000 +type ErrorCode* = enum + RequestFailed = -32803 + ServerCancelled = -32802 + ContentModified = -32801 + 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 + UnknownErrorCode = -32001 + ServerErrorEnd = -32000 + # Anything below here comes from the LSP specification type DiagnosticSeverity* {.pure.} = enum - Error = 1, - Warning = 2, - Information = 3, + Error = 1 + Warning = 2 + Information = 3 Hint = 4 + DiagnosticTag* {.pure.} = enum - Unnecessary = 1, + Unnecessary = 1 Deprecated = 2 + 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, + 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, + 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, + None = 0 + Full = 1 Incremental = 2 + MessageType* {.pure.} = enum - Error = 1, - Warning = 2, - Info = 3, - Log = 4, + Error = 1 + Warning = 2 + Info = 3 + Log = 4 Debug = 5 + FileChangeType* {.pure.} = enum - Created = 1, - Changed = 2, + Created = 1 + Changed = 2 Deleted = 3 + WatchKind* {.pure.} = enum - Create = 1, - Change = 2, + Create = 1 + Change = 2 Delete = 4 + TextDocumentSaveReason* {.pure.} = enum - Manual = 1, - AfterDelay = 2, + Manual = 1 + AfterDelay = 2 FocusOut = 3 + CompletionTriggerKind* {.pure.} = enum - Invoked = 1, - TriggerCharacter = 2, + Invoked = 1 + TriggerCharacter = 2 TriggerForIncompleteCompletions = 3 + InsertTextFormat* {.pure.} = enum - PlainText = 1, + PlainText = 1 Snippet = 2 + DocumentHighlightKind* {.pure.} = enum - Text = 1, - Read = 2, + Text = 1 + Read = 2 Write = 3 + SignatureHelpTriggerKind* {.pure.} = enum - Invoked = 1, - TriggerCharacter = 2, + Invoked = 1 + TriggerCharacter = 2 ContentChange = 3 + InitializeErrorCodes* {.pure.} = enum unknownProtocolVersion = 1 + NotebookCellKind* {.pure.} = enum - Markup = 1, + Markup = 1 Code = 2 + SymbolTag* {.pure.} = enum Deprecated = 1 + InlayHintKind* {.pure.} = enum - Type = 1, + Type = 1 Parameter = 2 + CompletionItemTag* {.pure.} = enum Deprecated = 1 + InsertTextMode* {.pure.} = enum - asIs = 1, + asIs = 1 adjustIndentation = 2 + CodeActionTriggerKind* {.pure.} = enum - Invoked = 1, + Invoked = 1 Automatic = 2 + PrepareSupportDefaultBehavior* {.pure.} = enum Identifier = 1 diff --git a/protocol/types.nim b/protocol/types.nim index 6332514..ef4fde9 100644 --- a/protocol/types.nim +++ b/protocol/types.nim @@ -1005,7 +1005,7 @@ type ProjectError* = object projectFile*: string - errorMessage*: string + errorMessage*: string lastKnownCmd*: string NimLangServerStatus* = object diff --git a/routes.nim b/routes.nim index b9b8f30..b91467c 100644 --- a/routes.nim +++ b/routes.nim @@ -145,12 +145,8 @@ proc completion*( let ch = ls.getCharacter(uri, line, character) if ch.isNone: return @[] - let completions = await nimsuggest.get.sug( - uriToPath(uri), - ls.uriToStash(uri), - line + 1, - ch.get, - ) + let completions = + await nimsuggest.get.sug(uriToPath(uri), ls.uriToStash(uri), line + 1, ch.get) result = completions.map(toCompletionItem) if ls.clientCapabilities.supportSignatureHelp() and @@ -178,12 +174,7 @@ proc definition*( if ch.isNone: return @[] result = ns.get - .def( - uriToPath(uri), - ls.uriToStash(uri), - line + 1, - ch.get, - ) + .def(uriToPath(uri), ls.uriToStash(uri), line + 1, ch.get) .await() .map(toLocation) @@ -199,12 +190,7 @@ proc declaration*( if ch.isNone: return @[] result = ns.get - .declaration( - uriToPath(uri), - ls.uriToStash(uri), - line + 1, - ch.get, - ) + .declaration(uriToPath(uri), ls.uriToStash(uri), line + 1, ch.get) .await() .map(toLocation) @@ -218,14 +204,8 @@ proc expandAll*( let ch = ls.getCharacter(uri, line, character) if ch.isNone: return ExpandResult() - let expand = ns.get - .expand( - uriToPath(uri), - ls.uriToStash(uri), - line + 1, - ch.get, - ) - .await() + let expand = + ns.get.expand(uriToPath(uri), ls.uriToStash(uri), line + 1, ch.get).await() proc createRangeFromSuggest(suggest: Suggest): Range = result = range(suggest.line - 1, 0, suggest.endLine - 1, suggest.endCol) @@ -259,13 +239,7 @@ proc expand*( if ch.isNone: return ExpandResult() let expand = ns.get - .expand( - uriToPath(uri), - ls.uriToStash(uri), - line + 1, - ch.get, - fmt " {tag}", - ) + .expand(uriToPath(uri), ls.uriToStash(uri), line + 1, ch.get, fmt " {tag}") .await() if expand.len != 0: result = ExpandResult( @@ -334,12 +308,7 @@ proc typeDefinition*( if ch.isNone: return @[] result = ns.get - .`type`( - uriToPath(uri), - ls.uriToStash(uri), - line + 1, - ch.get, - ) + .`type`(uriToPath(uri), ls.uriToStash(uri), line + 1, ch.get) .await() .map(toLocation) @@ -415,12 +384,8 @@ proc hover*( let ch = ls.getCharacter(uri, line, character) if ch.isNone: return none(Hover) - let suggestions = await nimsuggest.get().def( - uriToPath(uri), - ls.uriToStash(uri), - line + 1, - ch.get, - ) + let suggestions = + await nimsuggest.get().def(uriToPath(uri), ls.uriToStash(uri), line + 1, ch.get) if suggestions.len == 0: return none[Hover]() else: @@ -435,13 +400,9 @@ proc references*( return @[] let ch = ls.getCharacter(uri, line, character) if ch.isNone: - return @[] - let refs = await nimsuggest.get.use( - uriToPath(uri), - ls.uriToStash(uri), - line + 1, - ch.get, - ) + return @[] + let refs = + await nimsuggest.get.use(uriToPath(uri), ls.uriToStash(uri), line + 1, ch.get) result = refs.filter(suggest => suggest.section != ideDef or includeDeclaration).map( toLocation ) @@ -453,16 +414,12 @@ proc prepareRename*( asyncSpawn ls.addProjectFileToPendingRequest(id.uint, uri) let nimsuggest = await ls.tryGetNimsuggest(uri) if nimsuggest.isNone: - return newJNull() + return newJNull() let ch = ls.getCharacter(uri, line, character) if ch.isNone: return newJNull() - let def = await nimsuggest.get.def( - uriToPath(uri), - ls.uriToStash(uri), - line + 1, - ch.get, - ) + let def = + await nimsuggest.get.def(uriToPath(uri), ls.uriToStash(uri), line + 1, ch.get) if def.len == 0: return newJNull() # Check if the symbol belongs to the project @@ -692,12 +649,8 @@ proc signatureHelp*( let ch = ls.getCharacter(uri, line, character) if ch.isNone: return none[SignatureHelp]() - let completions = await nimsuggest.get.con( - uriToPath(uri), - ls.uriToStash(uri), - line + 1, - ch.get, - ) + let completions = + await nimsuggest.get.con(uriToPath(uri), ls.uriToStash(uri), line + 1, ch.get) let signatures = completions.map(toSignatureInformation) if signatures.len() > 0: return some SignatureHelp( @@ -770,10 +723,7 @@ proc documentHighlight*( if ch.isNone: return @[] let suggestLocations = await nimsuggest.get.highlight( - uriToPath(uri), - ls.uriToStash(uri), - line + 1, - ch.get, + uriToPath(uri), ls.uriToStash(uri), line + 1, ch.get ) result = suggestLocations.map(toDocumentHighlight) diff --git a/suggestapi.nim b/suggestapi.nim index 819e33f..db2cf10 100644 --- a/suggestapi.nim +++ b/suggestapi.nim @@ -1,4 +1,5 @@ -import osproc, +import + osproc, strutils, strformat, times, @@ -22,14 +23,28 @@ const HighestSupportedNimSuggestProtocolVersion = 4 # coppied from Nim repo type PrefixMatch* {.pure.} = enum - None, ## no prefix detected - Abbrev ## prefix is an abbreviation of the symbol - Substr, ## prefix is a substring of the symbol - Prefix, ## prefix does match the symbol + None ## no prefix detected + Abbrev ## prefix is an abbreviation of the symbol + Substr ## prefix is a substring of the symbol + Prefix ## prefix does match the symbol IdeCmd* = enum - ideNone, ideSug, ideCon, ideDef, ideUse, ideDus, ideChk, ideMod, - ideHighlight, ideOutline, ideKnown, ideMsg, ideProject, ideType, ideExpand + ideNone + ideSug + ideCon + ideDef + ideUse + ideDus + ideChk + ideMod + ideHighlight + ideOutline + ideKnown + ideMsg + ideProject + ideType + ideExpand + NimsuggestCallback = proc(self: Nimsuggest): void {.gcsafe, raises: [].} ProjectCallback = proc(self: Project): void {.gcsafe, raises: [].} @@ -37,11 +52,11 @@ type section*: IdeCmd qualifiedPath*: seq[string] # part of 'qualifiedPath' filePath*: string - line*: int # Starts at 1 - column*: int # Starts at 0 - doc*: string # Not escaped (yet) - forth*: string # type - quality*: range[0..100] # matching quality + line*: int # Starts at 1 + column*: int # Starts at 0 + doc*: string # Not escaped (yet) + forth*: string # type + quality*: range[0 .. 100] # matching quality isGlobal*: bool # is a global variable contextFits*: bool # type/non-type context matches prefix*: PrefixMatch @@ -59,20 +74,20 @@ type command: string SuggestInlayHintKind* = enum - sihkType = "Type", + sihkType = "Type" sihkParameter = "Parameter" sihkException = "Exception" SuggestInlayHint* = ref object kind*: SuggestInlayHintKind - line*: int # Starts at 1 - column*: int # Starts at 0 + line*: int # Starts at 1 + column*: int # Starts at 0 label*: string paddingLeft*: bool paddingRight*: bool allowInsert*: bool tooltip*: string - + NimsuggestImpl* = object checkProjectInProgress*: bool needsCheckProject*: bool @@ -89,10 +104,10 @@ type nimSuggestPath*: string version*: string project*: Project - + NimSuggest* = ref NimsuggestImpl - Project* = ref object + Project* = ref object ns*: Future[NimSuggest] file*: string process*: AsyncProcessRef @@ -114,7 +129,7 @@ template benchmark(benchmarkName: string, code: untyped) = debug "CPU Time", benchmark = benchmarkName, time = elapsedStr func nimSymToLSPKind*(suggest: Suggest): CompletionItemKind = - case suggest.symKind: + case suggest.symKind of "skConst": CompletionItemKind.Value of "skEnumField": CompletionItemKind.Enum of "skForVar": CompletionItemKind.Variable @@ -133,7 +148,7 @@ func nimSymToLSPKind*(suggest: Suggest): CompletionItemKind = else: CompletionItemKind.Property func nimSymToLSPSymbolKind*(suggest: string): SymbolKind = - case suggest: + case suggest of "skConst": SymbolKind.Constant of "skEnumField": SymbolKind.EnumMember of "skField": SymbolKind.Field @@ -150,22 +165,37 @@ func nimSymToLSPSymbolKind*(suggest: string): SymbolKind = else: SymbolKind.Function func nimSymDetails*(suggest: Suggest): string = - case suggest.symKind: - of "skConst": "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 + case suggest.symKind + of "skConst": + "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 const failedToken = "::Failed::" @@ -191,7 +221,7 @@ proc parseQualifiedPath*(input: string): seq[string] = result.add item proc parseSuggestDef*(line: string): Option[Suggest] = - let tokens = line.split('\t'); + let tokens = line.split('\t') if tokens.len < 8: error "Failed to parse: ", line = line return none(Suggest) @@ -203,14 +233,15 @@ proc parseSuggestDef*(line: string): Option[Suggest] = doc: tokens[7].unescape(), forth: tokens[3], symKind: tokens[1], - section: parseEnum[IdeCmd]("ide" & capitalizeAscii(tokens[0]))) + section: parseEnum[IdeCmd]("ide" & capitalizeAscii(tokens[0])), + ) if tokens.len == 11: sug.endLine = parseInt(tokens[9]) sug.endCol = parseInt(tokens[10]) some sug proc parseSuggestInlayHint*(line: string): SuggestInlayHint = - let tokens = line.split('\t'); + let tokens = line.split('\t') if tokens.len < 8: error "Failed to parse: ", line = line raise newException(ValueError, fmt "Failed to parse line {line}") @@ -222,7 +253,8 @@ proc parseSuggestInlayHint*(line: string): SuggestInlayHint = paddingLeft: parseBool(tokens[4]), paddingRight: parseBool(tokens[5]), allowInsert: parseBool(tokens[6]), - tooltip: tokens[7]) + tooltip: tokens[7], + ) proc name*(sug: Suggest): string = return sug.qualifiedPath[^1] @@ -244,23 +276,25 @@ proc stop*(self: Project) = proc doWithTimeout*[T](fut: Future[T], timeout: int, s: string): owned(Future[bool]) = var retFuture = newFuture[bool]("asyncdispatch.`doWithTimeout`") var timeoutFuture = sleepAsync(timeout) - fut.addCallback do (): + fut.addCallback do(): if not retFuture.finished: retFuture.complete(true) - timeoutFuture.addCallback do (): + timeoutFuture.addCallback do(): if not retFuture.finished: retFuture.complete(false) return retFuture -proc detectNimsuggestVersion(root: string, - nimsuggestPath: string, - workingDir: string): int {.gcsafe.} = - var process = startProcess(command = nimsuggestPath, - workingDir = workingDir, - args = @[root, "--info:protocolVer"], - options = {poUsePath}) +proc detectNimsuggestVersion( + root: string, nimsuggestPath: string, workingDir: string +): int {.gcsafe.} = + var process = startProcess( + command = nimsuggestPath, + workingDir = workingDir, + args = @[root, "--info:protocolVer"], + options = {poUsePath}, + ) var l: string if not process.outputStream.readLine(l): l = "" @@ -272,44 +306,46 @@ proc detectNimsuggestVersion(root: string, else: return parseInt(l) -proc getNimsuggestCapabilities*(nimsuggestPath: string): - set[NimSuggestCapability] {.gcsafe.} = - +proc getNimsuggestCapabilities*( + nimsuggestPath: string +): set[NimSuggestCapability] {.gcsafe.} = proc parseCapability(c: string): Option[NimSuggestCapability] = - debug "Parsing nimsuggest capability", capability=c + debug "Parsing nimsuggest capability", capability = c try: result = some(parseEnum[NimSuggestCapability](c)) except: - debug "Capability not supported. Ignoring.", capability=c + debug "Capability not supported. Ignoring.", capability = c result = none(NimSuggestCapability) - var process = startProcess(command = nimsuggestPath, - args = @["--info:capabilities"], - options = {poUsePath}) + var process = startProcess( + command = nimsuggestPath, args = @["--info:capabilities"], options = {poUsePath} + ) var l: string if not process.outputStream.readLine(l): l = "" var exitCode = process.waitForExit() - if exitCode == 0: + if exitCode == 0: # older versions of NimSuggest don't support the --info:capabilities option for cap in l.split(" ").mapIt(parseCapability(it)): if cap.isSome: result.incl(cap.get) -proc logNsError(project: Project) {.async.} = +proc logNsError(project: Project) {.async.} = let err = string.fromBytes(project.process.stderrStream.read().await) error "NimSuggest Error (stderr)", err = err project.markFailed(err) -proc createNimsuggest*(root: string, - nimsuggestPath: string, - version: string, - timeout: int, - timeoutCallback: NimsuggestCallback, - errorCallback: ProjectCallback, - workingDir = getCurrentDir(), - enableLog: bool = false, - enableExceptionInlayHints: bool = false): Future[Project] {.async, gcsafe.} = +proc createNimsuggest*( + root: string, + nimsuggestPath: string, + version: string, + timeout: int, + timeoutCallback: NimsuggestCallback, + errorCallback: ProjectCallback, + workingDir = getCurrentDir(), + enableLog: bool = false, + enableExceptionInlayHints: bool = false, +): Future[Project] {.async, gcsafe.} = result = Project(file: root) result.ns = newFuture[NimSuggest]() result.errorCallback = errorCallback @@ -323,15 +359,14 @@ proc createNimsuggest*(root: string, ns.version = version ns.project = result - info "Starting nimsuggest", root = root, timeout = timeout, path = nimsuggestPath, - workingDir = workingDir + info "Starting nimsuggest", + root = root, timeout = timeout, path = nimsuggestPath, workingDir = workingDir if nimsuggestPath != "": ns.protocolVersion = detectNimsuggestVersion(root, nimsuggestPath, workingDir) if ns.protocolVersion > HighestSupportedNimSuggestProtocolVersion: ns.protocolVersion = HighestSupportedNimSuggestProtocolVersion - var - args = @[root, "--v" & $ns.protocolVersion, "--autobind"] + var args = @[root, "--v" & $ns.protocolVersion, "--autobind"] if ns.protocolVersion >= 4: args.add("--clientProcessId:" & $getCurrentProcessId()) if enableLog: @@ -343,28 +378,39 @@ proc createNimsuggest*(root: string, args.add("--exceptionInlayHints:on") else: args.add("--exceptionInlayHints:off") - result.process = - await startProcess(nimsuggestPath, arguments = args, options = { UsePath }, - stdoutHandle = AsyncProcess.Pipe, - stderrHandle = AsyncProcess.Pipe) + result.process = await startProcess( + nimsuggestPath, + arguments = args, + options = {UsePath}, + stdoutHandle = AsyncProcess.Pipe, + stderrHandle = AsyncProcess.Pipe, + ) asyncSpawn logNsError(result) - ns.port = (await result.process.stdoutStream.readLine(sep="\n")).parseInt + ns.port = (await result.process.stdoutStream.readLine(sep = "\n")).parseInt result.ns.complete(ns) else: - error "Unable to start nimsuggest. Unable to find binary on the $PATH", nimsuggestPath = nimsuggestPath + error "Unable to start nimsuggest. Unable to find binary on the $PATH", + nimsuggestPath = nimsuggestPath result.markFailed fmt "Unable to start nimsuggest. `{nimsuggestPath}` is not present on the PATH" proc createNimsuggest*(root: string): Future[Project] {.gcsafe.} = - result = createNimsuggest(root, "nimsuggest", "", REQUEST_TIMEOUT, - proc (ns: Nimsuggest) = discard, - proc (pr: Project) = discard) + result = createNimsuggest( + root, + "nimsuggest", + "", + REQUEST_TIMEOUT, + proc(ns: Nimsuggest) = + discard, + proc(pr: Project) = + discard, + ) proc toString*(bytes: openarray[byte]): string = result = newString(bytes.len) if bytes.len > 0: copyMem(result[0].addr, bytes[0].unsafeAddr, bytes.len) -proc processQueue(self: Nimsuggest): Future[void] {.async.}= +proc processQueue(self: Nimsuggest): Future[void] {.async.} = debug "processQueue", size = self.requestQueue.len while self.requestQueue.len != 0: let req = self.requestQueue.popFirst @@ -382,21 +428,23 @@ proc processQueue(self: Nimsuggest): Future[void] {.async.}= if not self.timeoutCallback.isNil: debug "timeoutCallback is set", timeout = self.timeout - doWithTimeout(req.future, self.timeout, fmt "running {req.commandString}").addCallback do (f: Future[bool]): + doWithTimeout(req.future, self.timeout, fmt "running {req.commandString}").addCallback do( + f: Future[bool] + ): if not f.failed and not f.read(): - debug "Calling restart" - self.timeoutCallback(self) + debug "Calling restart" + self.timeoutCallback(self) let ta = initTAddress(&"127.0.0.1:{self.port}") let transport = await ta.connect() discard await transport.write(req.commandString & "\c\L") const bufferSize = 1024 * 1024 * 4 - var buffer:seq[byte] = newSeq[byte](bufferSize); + var buffer: seq[byte] = newSeq[byte](bufferSize) var data = await transport.read() let content = data.toString() - for lineStr in content.splitLines: + for lineStr in content.splitLines: if lineStr != "": case req.command of "known": @@ -405,7 +453,7 @@ proc processQueue(self: Nimsuggest): Future[void] {.async.}= sug.forth = lineStr res.add sug of "inlayHints": - res.add Suggest( inlayHintInfo: parseSuggestInlayHint(lineStr) ) + res.add Suggest(inlayHintInfo: parseSuggestInlayHint(lineStr)) else: let sug = parseSuggestDef(lineStr) if sug.isSome: @@ -416,7 +464,9 @@ proc processQueue(self: Nimsuggest): Future[void] {.async.}= debug "Server socket closed" if not req.future.finished: debug "Call cancelled before sending error", command = req.command - req.future.fail newException(CatchableError, "Server crashed/socket closed.") + req.future.fail newException( + CatchableError, "Server crashed/socket closed." + ) if not req.future.finished: debug "Sending result(s)", length = res.len req.future.complete res @@ -427,23 +477,33 @@ proc processQueue(self: Nimsuggest): Future[void] {.async.}= transport.close() self.processing = false -proc call*(self: Nimsuggest, command: string, file: string, dirtyFile: string, - line: int, column: int, tag = ""): Future[seq[Suggest]] = +proc call*( + self: Nimsuggest, + command: string, + file: string, + dirtyFile: string, + line: int, + column: int, + tag = "", +): Future[seq[Suggest]] = result = Future[seq[Suggest]]() - let commandString = if dirtyFile != "": - fmt "{command} \"{file}\";\"{dirtyFile}\":{line}:{column}{tag}" - else: - fmt "{command} \"{file}\":{line}:{column}{tag}" + let commandString = + if dirtyFile != "": + fmt "{command} \"{file}\";\"{dirtyFile}\":{line}:{column}{tag}" + else: + fmt "{command} \"{file}\":{line}:{column}{tag}" self.requestQueue.addLast( - SuggestCall(commandString: commandString, future: result, command: command)) + SuggestCall(commandString: commandString, future: result, command: command) + ) if not self.processing: self.processing = true traceAsyncErrors processQueue(self) template createFullCommand(command: untyped) {.dirty.} = - proc command*(self: Nimsuggest, file: string, dirtyfile = "", - line: int, col: int, tag = ""): Future[seq[Suggest]] = + proc command*( + self: Nimsuggest, file: string, dirtyfile = "", line: int, col: int, tag = "" + ): Future[seq[Suggest]] = return self.call(astToStr(command), file, dirtyfile, line, col, tag) template createFileOnlyCommand(command: untyped) {.dirty.} = @@ -455,10 +515,21 @@ template createGlobalCommand(command: untyped) {.dirty.} = return self.call(astToStr(command), "-", "", 0, 0) template createRangeCommand(command: untyped) {.dirty.} = - proc command*(self: Nimsuggest, file: string, dirtyfile = "", - startLine, startCol, endLine, endCol: int, - extra: string): Future[seq[Suggest]] = - return self.call(astToStr(command), file, dirtyfile, startLine, startCol, fmt ":{endLine}:{endCol}{extra}") + proc command*( + self: Nimsuggest, + file: string, + dirtyfile = "", + startLine, startCol, endLine, endCol: int, + extra: string, + ): Future[seq[Suggest]] = + return self.call( + astToStr(command), + file, + dirtyfile, + startLine, + startCol, + fmt ":{endLine}:{endCol}{extra}", + ) # create commands createFullCommand(sug) @@ -478,16 +549,19 @@ createFileOnlyCommand(globalSymbols) createGlobalCommand(recompile) createRangeCommand(inlayHints) -proc `mod`*(nimsuggest: Nimsuggest, file: string, dirtyfile = ""): Future[seq[Suggest]] = +proc `mod`*( + nimsuggest: Nimsuggest, file: string, dirtyfile = "" +): Future[seq[Suggest]] = return nimsuggest.call("ideMod", file, dirtyfile, 0, 0) proc isKnown*(nimsuggest: Nimsuggest, filePath: string): Future[bool] {.async.} = let res = await withTimeout(nimsuggest.known(filePath)) if res.isNone: - debug "Timeout reached running [isKnown], assuming the file is not known", file = filePath + debug "Timeout reached running [isKnown], assuming the file is not known", + file = filePath return let sug = res.get() if sug.len == 0: return false debug "isKnown", filePath = filePath, sug = sug[0].forth - return sug.len > 0 and sug[0].forth == "true" \ No newline at end of file + return sug.len > 0 and sug[0].forth == "true" diff --git a/utils.nim b/utils.nim index 9b08f87..d800d82 100644 --- a/utils.nim +++ b/utils.nim @@ -135,7 +135,7 @@ proc catchOrQuit*(error: Exception) = fatal "Fatal exception reached", err = error.msg, stackTrace = getStackTrace() quit 1 -proc writeStackTrace*(ex = getCurrentException()) = +proc writeStackTrace*(ex = getCurrentException()) = try: stderr.write "An exception occured \n" stderr.write ex.msg & "\n" @@ -237,9 +237,11 @@ proc to*(params: RequestParamsRx, T: typedesc): T = let value = $params.toJson() parseJson(value).to(T) -proc head*[T](xs: seq[T]): Option[T] = - if xs.len > 0: some(xs[0]) - else: none(T) +proc head*[T](xs: seq[T]): Option[T] = + if xs.len > 0: + some(xs[0]) + else: + none(T) proc partial*[A, B, C]( fn: proc(a: A, b: B): C {.gcsafe, raises: [], nimcall.}, a: A @@ -254,41 +256,44 @@ proc partial*[A, B]( fn(a, b) proc partial*[A, B, C, D]( - fn: proc(a: A, b: B, c: C): D {.gcsafe, raises: [], nimcall.}, a: A + fn: proc(a: A, b: B, c: C): D {.gcsafe, raises: [], nimcall.}, a: A ): proc(b: B, c: C): D {.gcsafe, raises: [].} = return proc(b: B, c: C): D {.gcsafe, raises: [].} = return fn(a, b, c) - -proc ensureStorageDir*: string = + +proc ensureStorageDir*(): string = result = getTempDir() / "nimlangserver" discard existsOrCreateDir(result) proc either*[T](fut1, fut2: Future[T]): Future[T] {.async.} = - let res = await race(fut1, fut2) + let res = await race(fut1, fut2) if fut1.finished: result = fut1.read cancelSoon fut2 else: result = fut2.read cancelSoon fut1 - -proc map*[T, U](f: Future[T], fn: proc(t:T): U {.raises:[], gcsafe.}): Future[U] {.async.} = + +proc map*[T, U]( + f: Future[T], fn: proc(t: T): U {.raises: [], gcsafe.} +): Future[U] {.async.} = fn(await f) -proc map*[U](f: Future[void], fn: proc(): U {.raises:[], gcsafe.}): Future[U] {.async.} = +proc map*[U]( + f: Future[void], fn: proc(): U {.raises: [], gcsafe.} +): Future[U] {.async.} = await f fn() -proc withTimeout*[T](fut: Future[T], timeout: int = 500): Future[Option[T]] {.async.} = +proc withTimeout*[T](fut: Future[T], timeout: int = 500): Future[Option[T]] {.async.} = #Returns None when the timeout is reached and cancels the fut. Otherwise returns the Fut let timeoutFut = sleepAsync(timeout).map(() => none(T)) let optFut = fut.map((r: T) => some r) await either(optFut, timeoutFut) -proc getNextFreePort*(): Port= +proc getNextFreePort*(): Port = let s = newSocket() s.bindAddr(Port(0), "localhost") let (_, port) = s.getLocalAddr s.close() port - \ No newline at end of file