Skip to content

Commit 0c287fe

Browse files
authored
Handles .nimble and .nims files. Fixes #36 (#273)
* wip nimcheck * Integrates nim check into the ls * Fixes an issue where configuration wasnt properly updated. Opt-out `useNimCheck` * Fixes a crash. Uses `nim` project path for `nim check` * Handles `.nimble` and `.nims` files. Fixes #36 Automatically import `sytem/nimscript` Includes `nimscriptapit.nim` for nimble files * adds nimscriptapi template
1 parent 481a5c1 commit 0c287fe

File tree

5 files changed

+290
-5
lines changed

5 files changed

+290
-5
lines changed

ls.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -683,7 +683,7 @@ proc toDiagnostic(checkResult: CheckResult): Diagnostic =
683683
return node.to(Diagnostic)
684684

685685
proc sendDiagnostics*(ls: LanguageServer, diagnostics: seq[Suggest] | seq[CheckResult], path: string) =
686-
debug "Sending diagnostics", count = diagnostics.len, path = path
686+
trace "Sending diagnostics", count = diagnostics.len, path = path
687687
let params =
688688
PublishDiagnosticsParams %*
689689
{"uri": pathToUri(path), "diagnostics": diagnostics.map(x => x.toUtf16Pos(ls).toDiagnostic)}

nimcheck.nim

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import chronos, chronos/asyncproc
44
import stew/[byteutils]
55
import chronicles
66
import protocol/types
7+
import utils
78

89
type
910
CheckStacktrace* = object
@@ -69,18 +70,24 @@ proc parseCheckResults(lines: seq[string]): seq[CheckResult] =
6970
if messageText.len > 0 and result.len > 0:
7071
result[^1].msg &= "\n" & messageText
7172

72-
7373
proc nimCheck*(filePath: string, nimPath: string): Future[seq[CheckResult]] {.async.} =
7474
debug "nimCheck", filePath = filePath, nimPath = nimPath
75+
let isNimble = filePath.endsWith(".nimble")
76+
let isNimScript = filePath.endsWith(".nims") or isNimble
77+
var extraArgs = newSeq[string]()
78+
if isNimScript:
79+
extraArgs.add("--import: system/nimscript")
80+
if isNimble:
81+
extraArgs.add("--include: " & getNimScriptAPITemplatePath())
7582
let process = await startProcess(
7683
nimPath,
77-
arguments = @["check", "--listFullPaths", filePath],
84+
arguments = @["check", "--listFullPaths"] & extraArgs & @[filePath],
7885
options = {UsePath},
7986
stderrHandle = AsyncProcess.Pipe,
8087
stdoutHandle = AsyncProcess.Pipe,
8188
)
8289
let res = await process.waitForExit(InfiniteDuration)
83-
debug "nimCheck exit", res = res
90+
# debug "nimCheck exit", res = res
8491
var output = ""
8592
if res == 0: #Nim check return 0 if there are no errors but we still need to check for hints and warnings
8693
output = string.fromBytes(process.stdoutStream.read().await)

suggestapi.nim

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,14 @@ proc createNimsuggest*(
350350
result = Project(file: root)
351351
result.ns = newFuture[NimSuggest]()
352352
result.errorCallback = some errorCallback
353+
let isNimble = root.endsWith(".nimble")
354+
let isNimScript = root.endsWith(".nims") or isNimble
355+
var extraArgs = newSeq[string]()
356+
if isNimScript:
357+
extraArgs.add("--import: system/nimscript")
358+
if isNimble:
359+
let nimScriptApiPath = getNimScriptAPITemplatePath()
360+
extraArgs.add("--include: " & nimScriptApiPath)
353361

354362
let ns = Nimsuggest()
355363
ns.requestQueue = Deque[SuggestCall]()
@@ -367,7 +375,7 @@ proc createNimsuggest*(
367375
ns.protocolVersion = detectNimsuggestVersion(root, nimsuggestPath, workingDir)
368376
if ns.protocolVersion > HighestSupportedNimSuggestProtocolVersion:
369377
ns.protocolVersion = HighestSupportedNimSuggestProtocolVersion
370-
var args = @[root, "--v" & $ns.protocolVersion, "--autobind"]
378+
var args = @[root, "--v" & $ns.protocolVersion, "--autobind"] & extraArgs
371379
if ns.protocolVersion >= 4:
372380
args.add("--clientProcessId:" & $getCurrentProcessId())
373381
if enableLog:

templates/nimscriptapi.nim

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
# Copyright (C) Dominik Picheta. All rights reserved.
2+
# BSD License. Look at license.txt for more info.
3+
4+
## This module is implicitly imported in NimScript .nimble files.
5+
6+
import system except getCommand, setCommand, switch, `--`
7+
import strformat, strutils, tables, sequtils
8+
export tables
9+
10+
when (NimMajor, NimMinor) < (1, 3):
11+
when not defined(nimscript):
12+
import os
13+
else:
14+
import os
15+
16+
var
17+
packageName* = "" ## Set this to the package name. It
18+
## is usually not required to do that, nims' filename is
19+
## the default.
20+
version*: string ## The package's version.
21+
author*: string ## The package's author.
22+
description*: string ## The package's description.
23+
license*: string ## The package's license.
24+
srcDir*: string ## The package's source directory.
25+
binDir*: string ## The package's binary directory.
26+
backend*: string ## The package's backend.
27+
28+
skipDirs*, skipFiles*, skipExt*, installDirs*, installFiles*,
29+
installExt*, bin*, paths*, entryPoints*: seq[string] = @[] ## Nimble metadata.
30+
requiresData*: seq[string] = @[] ## The package's dependencies.
31+
taskRequiresData*: Table[string, seq[string]] ## Task dependencies
32+
foreignDeps*: seq[string] = @[] ## The foreign dependencies. Only
33+
## exported for 'distros.nim'.
34+
35+
nimbleTasks: seq[tuple[name, description: string]] = @[]
36+
beforeHooks: seq[string] = @[]
37+
afterHooks: seq[string] = @[]
38+
flags: Table[string, seq[string]]
39+
namedBin*: Table[string, string]
40+
41+
command = "e"
42+
project = ""
43+
success = false
44+
retVal = true
45+
nimblePathsEnv = "__NIMBLE_PATHS"
46+
47+
proc requires*(deps: varargs[string]) =
48+
## Call this to set the list of requirements of your Nimble
49+
## package.
50+
for d in deps: requiresData.add(d)
51+
52+
proc taskRequires*(task: string, deps: varargs[string]) =
53+
## Call this to set the list of requirements for a certain task
54+
if task notin taskRequiresData:
55+
taskRequiresData[task] = @[]
56+
for d in deps:
57+
taskRequiresData[task] &= d
58+
59+
proc getParams(): tuple[scriptFile, projectFile, outFile, actionName: string,
60+
commandLineParams: seq[string]] =
61+
# Called by nimscriptwrapper.nim:execNimscript()
62+
# nim e --flags /full/path/to/file.nims /full/path/to/file.nimble /full/path/to/file.out action
63+
for i in 2 .. paramCount():
64+
let
65+
param = paramStr(i)
66+
if param[0] != '-':
67+
if result.scriptFile.len == 0:
68+
result.scriptFile = param
69+
elif result.projectFile.len == 0:
70+
result.projectFile = param
71+
elif result.outFile.len == 0:
72+
result.outFile = param
73+
elif result.actionName.len == 0:
74+
result.actionName = param.normalize
75+
else:
76+
result.commandLineParams.add param
77+
else:
78+
result.commandLineParams.add param
79+
80+
const
81+
# Command line values are const so that thisDir() works at compile time
82+
(scriptFile, projectFile, outFile, actionName, commandLineParams*) = getParams()
83+
NimbleVersion* {.strdefine.} = ""
84+
NimbleMajor* {.intdefine.} = 0
85+
NimbleMinor* {.intdefine.} = 0
86+
NimblePatch* {.intdefine.} = 0
87+
88+
proc getCommand*(): string =
89+
return command
90+
91+
proc setCommand*(cmd: string, prj = "") =
92+
command = cmd
93+
if prj.len != 0:
94+
project = prj
95+
96+
proc switch*(key: string, value="") =
97+
if flags.hasKey(key):
98+
flags[key].add(value)
99+
else:
100+
flags[key] = @[value]
101+
102+
template `--`*(key, val: untyped) =
103+
switch(astToStr(key), strip astToStr(val))
104+
105+
template `--`*(key: untyped) =
106+
switch(astToStr(key), "")
107+
108+
template printIfLen(varName) =
109+
if varName.len != 0:
110+
result &= astToStr(varName) & ": \"\"\"" & varName & "\"\"\"\n"
111+
112+
template printSeqIfLen(name: string, varName: untyped) =
113+
if varName.len != 0:
114+
result &= name & ": \"" & varName.join(", ") & "\"\n"
115+
116+
template printSeqIfLen(varName) =
117+
printSeqIfLen(astToStr(varName), varName)
118+
119+
proc printPkgInfo(): string =
120+
if backend.len == 0:
121+
backend = "c"
122+
123+
# Forward `namedBin` entries in `bin`
124+
for k, v in namedBin:
125+
let idx = bin.find(k)
126+
if idx == -1:
127+
bin.add k & "=" & v
128+
else:
129+
bin[idx] = k & "=" & v
130+
131+
result = "[Package]\n"
132+
if packageName.len != 0:
133+
result &= "name: \"" & packageName & "\"\n"
134+
printIfLen version
135+
printIfLen author
136+
printIfLen description
137+
printIfLen license
138+
printIfLen srcDir
139+
printIfLen binDir
140+
printIfLen backend
141+
142+
printSeqIfLen skipDirs
143+
printSeqIfLen skipFiles
144+
printSeqIfLen skipExt
145+
printSeqIfLen installDirs
146+
printSeqIfLen installFiles
147+
printSeqIfLen installExt
148+
printSeqIfLen paths
149+
printSeqIfLen entryPoints
150+
printSeqIfLen bin
151+
printSeqIfLen "nimbleTasks", nimbleTasks.unzip()[0]
152+
printSeqIfLen beforeHooks
153+
printSeqIfLen afterHooks
154+
155+
if requiresData.len != 0 or taskRequiresData.len != 0:
156+
result &= "\n[Deps]\n"
157+
# Write package level dependencies
158+
if requiresData.len != 0:
159+
result &= &"requires: \"{requiresData.join(\", \")}\"\n"
160+
# Write task level dependencies
161+
for task, requiresData in taskRequiresData.pairs:
162+
result &= &"{task}Requires: \"{requiresData.join(\", \")}\"\n"
163+
164+
165+
proc onExit*() =
166+
if actionName.len == 0 or actionName == "help":
167+
var maxNameLen = 8
168+
for (name, _) in nimbleTasks:
169+
maxNameLen = max(maxNameLen, name.len)
170+
for (name, description) in nimbleTasks:
171+
echo alignLeft(name, maxNameLen + 2), description
172+
173+
if "printPkgInfo".normalize == actionName:
174+
if outFile.len != 0:
175+
writeFile(outFile, printPkgInfo())
176+
else:
177+
var
178+
output = ""
179+
output &= "\"success\": " & $success & ", "
180+
output &= "\"command\": \"" & command & "\", "
181+
if project.len != 0:
182+
output &= "\"project\": \"" & project & "\", "
183+
if flags.len != 0:
184+
output &= "\"flags\": {"
185+
for key, val in flags.pairs:
186+
output &= "\"" & key & "\": ["
187+
for v in val:
188+
let v = if v.len > 0 and v[0] == '"': strutils.unescape(v)
189+
else: v
190+
output &= v.escape & ", "
191+
output = output[0 .. ^3] & "], "
192+
output = output[0 .. ^3] & "}, "
193+
194+
output &= "\"retVal\": " & $retVal
195+
196+
if outFile.len != 0:
197+
writeFile(outFile, "{" & output & "}")
198+
199+
# TODO: New release of Nim will move this `task` template under a
200+
# `when not defined(nimble)`. This will allow us to override it in the future.
201+
template task*(name: untyped; description: string; body: untyped): untyped =
202+
## Defines a task. Hidden tasks are supported via an empty description.
203+
## Example:
204+
##
205+
## .. code-block:: nim
206+
## task build, "default build is via the C backend":
207+
## setCommand "c"
208+
proc `name Task`*() = body
209+
210+
nimbleTasks.add (astToStr(name), description)
211+
212+
if actionName.len == 0 or actionName == "help":
213+
success = true
214+
elif actionName == astToStr(name).normalize:
215+
success = true
216+
`name Task`()
217+
218+
template before*(action: untyped, body: untyped): untyped =
219+
## Defines a block of code which is evaluated before ``action`` is executed.
220+
proc `action Before`*(): bool =
221+
result = true
222+
body
223+
224+
beforeHooks.add astToStr(action)
225+
226+
if (astToStr(action) & "Before").normalize == actionName:
227+
success = true
228+
retVal = `action Before`()
229+
230+
template after*(action: untyped, body: untyped): untyped =
231+
## Defines a block of code which is evaluated after ``action`` is executed.
232+
proc `action After`*(): bool =
233+
result = true
234+
body
235+
236+
afterHooks.add astToStr(action)
237+
238+
if (astToStr(action) & "After").normalize == actionName:
239+
success = true
240+
retVal = `action After`()
241+
242+
const nimbleExe* {.strdefine.} = "nimble"
243+
244+
proc getPkgDir*(): string =
245+
## Returns the package directory containing the .nimble file currently
246+
## being evaluated.
247+
result = projectFile.rsplit(seps={'/', '\\', ':'}, maxsplit=1)[0]
248+
249+
proc thisDir*(): string = getPkgDir()
250+
251+
proc getPaths*(): seq[string] =
252+
## Returns the paths to the dependencies
253+
return getEnv(nimblePathsEnv).split("|")
254+
255+
proc getPathsClause*(): string =
256+
## Returns the paths to the dependencies as consumed by the nim compiler.
257+
return getPaths().mapIt("--path:" & it).join(" ")

utils.nim

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ type
88
UriParseError* = object of Defect
99
uri: string
1010

11+
const
12+
NIM_SCRIPT_API_TEMPLATE* = staticRead("templates/nimscriptapi.nim") #We add this file to nimsuggest and `nim check` to support nimble files
13+
14+
1115
proc writeStackTrace*(ex = getCurrentException()) =
1216
try:
1317
if ex != nil:
@@ -327,3 +331,12 @@ func isWord*(str: string): bool =
327331
if c.int notin 97 .. 122:
328332
return false
329333
return true
334+
335+
proc getNimScriptAPITemplatePath*(): string =
336+
result = getCacheDir("nimlangserver")
337+
createDir(result)
338+
result = result / "nimscriptapi.nim"
339+
340+
if not result.fileExists:
341+
writeFile(result, NIM_SCRIPT_API_TEMPLATE)
342+
debug "NimScriptApiPath", path = result

0 commit comments

Comments
 (0)