Skip to content

Commit 88ac504

Browse files
authored
lsp: Add support for Progress (#1987)
1 parent 4c47892 commit 88ac504

File tree

10 files changed

+737
-11
lines changed

10 files changed

+737
-11
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.. _#1987: https://github.com/fox0430/moe/pull/1987
2+
3+
Added
4+
.....
5+
6+
- `#1987`_ lsp: Add support for Progress
7+

documents/lsp.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@ Please feedback, bug reports and PRs.
1212
- `shutdown`
1313
- `window/showMessage`
1414
- `window/logMessage`
15-
- `textDocument/publishDiagnostics`
15+
- `window/workDoneProgress/create`
16+
- `workspace/configuration`
1617
- `workspace/didChangeConfiguration`
18+
- `textDocument/publishDiagnostics`
1719
- `textDocument/didOpen`
1820
- `textDocument/didChange`
1921
- `textDocument/didSave`
2022
- `textDocument/didClose`
2123
- `textDocument/hover`
24+
- `$/progress`
2225

2326
## Configuration
2427

src/moepkg/bufferstatus.nim

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import std/[tables, times, options, os, strformat]
2121
import pkg/results
2222
import syntax/highlite
23-
import lsp/utils
2423
import gapbuffer, unicodeext, fileutils, highlight, independentutils, git,
2524
syntaxcheck
2625

src/moepkg/lsp.nim

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ proc showLspServerLog(
130130
commandLine : var CommandLine,
131131
notify: JsonNode): Result[(), string] =
132132
## Show the log to the command line.
133+
##
133134
## window/showMessage
134135

135136
let m = parseWindowShowMessageNotify(notify)
@@ -162,6 +163,7 @@ proc lspDiagnostics(
162163
bufStatuses: var seq[BufferStatus],
163164
notify: JsonNode): Result[(), string] =
164165
## Set BufferStatus.syntaxCheckResults to diagnostics results.
166+
##
165167
## textDocument/publishDiagnostics
166168

167169
let parseResult = parseTextDocumentPublishDiagnosticsNotify(notify)
@@ -195,6 +197,90 @@ proc lspDiagnostics(
195197

196198
return Result[(), string].ok ()
197199

200+
proc lspProgressCreate(
201+
c: var LspClient,
202+
notify: JsonNode): Result[(), string] =
203+
## Init a LSP progress.
204+
##
205+
## window/workDoneProgress/create
206+
207+
let token = parseWindowWorkDnoneProgressCreateNotify(notify)
208+
if token.isErr:
209+
return Result[(), string].err fmt"Invalid server notify: {token.error}"
210+
211+
c.createProgress(token.get)
212+
213+
return Result[(), string].ok ()
214+
215+
proc progressMessage(p: ProgressReport): string {.inline.} =
216+
case p.state:
217+
of begin:
218+
if p.message.len > 0:
219+
return fmt"{p.title}: {p.message}"
220+
else:
221+
return p.title
222+
of report:
223+
result = fmt"{p.title}: "
224+
if p.percentage.isSome: result &= fmt"{$p.percentage.get}%: "
225+
result &= p.message
226+
of `end`:
227+
return fmt"{p.title}: {p.message}"
228+
else:
229+
return ""
230+
231+
proc lspProgress(
232+
status: var EditorStatus,
233+
notify: JsonNode): Result[(), string] =
234+
## Begin/Report/End the LSP progress.
235+
##
236+
## $/progress
237+
238+
let token = workDoneProgressToken(notify)
239+
240+
if isWorkDoneProgressBegin(notify):
241+
let begin = parseWorkDoneProgressBegin(notify)
242+
if begin.isErr:
243+
return Result[(), string].err fmt"Invalid server notify: {begin.error}"
244+
245+
if isCancellable(begin.get):
246+
# Cancel
247+
return lspClient.delProgress(token)
248+
249+
let err = lspClient.beginProgress(token, begin.get)
250+
if err.isErr:
251+
return Result[(), string].err fmt"Invalid server notify: {err.error}"
252+
elif isWorkDoneProgressReport(notify):
253+
let report = parseWorkDoneProgressReport(notify)
254+
if report.isErr:
255+
return Result[(), string].err fmt"Invalid server notify: {report.error}"
256+
257+
if isCancellable(report.get):
258+
# Cancel
259+
return lspClient.delProgress(token)
260+
261+
let err = lspClient.reportProgress(token, report.get)
262+
if err.isErr:
263+
return Result[(), string].err fmt"Invalid server notify: {err.error}"
264+
elif isWorkDoneProgressEnd(notify):
265+
let `end` = parseWorkDoneProgressEnd(notify)
266+
if `end`.isErr:
267+
return Result[(), string].err fmt"Invalid server notify: {`end`.error}"
268+
269+
let err = lspClient.endProgress(token, `end`.get)
270+
if err.isErr:
271+
return Result[(), string].err fmt"Invalid server notify: {err.error}"
272+
else:
273+
return Result[(), string].err fmt"Invalid server notify: {notify}"
274+
275+
case lspClient.progress[token].state:
276+
of begin, report, `end`:
277+
status.commandLine.writeLspProgress(
278+
progressMessage(lspClient.progress[token]))
279+
else:
280+
discard
281+
282+
return Result[(), string].ok ()
283+
198284
proc handleLspServerNotify(
199285
status: var EditorStatus,
200286
notify: JsonNode): Result[(), string] =
@@ -212,6 +298,13 @@ proc handleLspServerNotify(
212298
of windowLogMessage:
213299
# Already logged to LspClint.log.
214300
return Result[(), string].ok ()
301+
of workspaceConfiguration:
302+
# TODO: Configure settings based on notifications if necessary.
303+
return Result[(), string].ok ()
304+
of windowWorkDnoneProgressCreate:
305+
return lspClient.lspProgressCreate(notify)
306+
of progress:
307+
return status.lspProgress(notify)
215308
of textDocumentPublishDiagnostics:
216309
return status.bufStatus.lspDiagnostics(notify)
217310
else:

src/moepkg/lsp/client.nim

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
# NOTE: Language Server Protocol Specification - 3.17
2121
# https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/
2222

23-
import std/[strformat, strutils, json, options, os, osproc, posix]
23+
import std/[strformat, strutils, json, options, os, osproc, posix, tables]
2424
import pkg/results
2525

2626
import ../appinfo
@@ -53,13 +53,17 @@ type
5353
LspCapabilities* = object
5454
hover*: bool
5555

56+
LspProgressTable* = Table[ProgressToken, ProgressReport]
57+
5658
LspClient* = ref object
5759
serverProcess: Process
5860
# LSP server process.
5961
serverStreams: Streams
6062
# Input/Output streams for the LSP server process.
6163
capabilities*: Option[LspCapabilities]
6264
# LSP server capabilities
65+
progress*: LspProgressTable
66+
# Use in window/workDoneProgress
6367
waitingResponse*: Option[LspMethod]
6468
# The waiting response from the LSP server.
6569
log*: LspLog
@@ -111,6 +115,75 @@ proc addNotifyFromServerLog*(c: var LspClient, m: JsonNode) {.inline.} =
111115
proc clearWaitingResponse*(c: var LspClient) {.inline.} =
112116
c.waitingResponse = none(LspMethod)
113117

118+
proc createProgress*(c: var LspClient, token: ProgressToken) =
119+
## Add a new progress to the `LspClint.progress`.
120+
121+
if not c.progress.contains(token):
122+
c.progress[token] = ProgressReport(state: ProgressState.create)
123+
124+
proc beginProgress*(
125+
c: var LspClient,
126+
token: ProgressToken,
127+
p: WorkDoneProgressBegin): Result[(), string] =
128+
## Begin the progress in the `LspClint.progress`.
129+
130+
if not c.progress.contains(token):
131+
return Result[(), string].err "token not found"
132+
133+
c.progress[token].state = ProgressState.begin
134+
c.progress[token].title = p.title
135+
if p.message.isSome:
136+
c.progress[token].message = p.message.get
137+
if p.percentage.isSome:
138+
c.progress[token].percentage = some(p.percentage.get.Natural)
139+
140+
return Result[(), string].ok ()
141+
142+
proc reportProgress*(
143+
c: var LspClient,
144+
token: ProgressToken,
145+
report: WorkDoneProgressReport): Result[(), string] =
146+
## Update the progress in the `LspClint.progress`.
147+
148+
if not c.progress.contains(token):
149+
return Result[(), string].err "token not found"
150+
151+
if ProgressState.report != c.progress[token].state:
152+
c.progress[token].state = ProgressState.report
153+
154+
if report.message.isSome:
155+
c.progress[token].message = report.message.get
156+
if report.percentage.isSome:
157+
c.progress[token].percentage = some(report.percentage.get.Natural)
158+
159+
return Result[(), string].ok ()
160+
161+
proc endProgress*(
162+
c: var LspClient,
163+
token: ProgressToken,
164+
p: WorkDoneProgressEnd): Result[(), string] =
165+
## End the progress in the `LspClint.progress`.
166+
167+
if not c.progress.contains(token):
168+
return Result[(), string].err "token not found"
169+
170+
c.progress[token].state = ProgressState.end
171+
172+
if p.message.isSome:
173+
c.progress[token].message = p.message.get
174+
175+
return Result[(), string].ok ()
176+
177+
proc delProgress*(c: var LspClient, token: ProgressToken): Result[(), string] =
178+
## Delete the progress from the `LspClint.progress`.
179+
180+
if not c.progress.contains(token):
181+
return Result[(), string].err "token not found"
182+
183+
c.progress.del(token)
184+
185+
return Result[(), string].ok ()
186+
114187
proc readable*(c: LspClient, timeout: int = 1): LspClientReadableResult =
115188
## Return when output is written from the LSP server or timesout.
116189
## Wait for the output from process to be written using poll(2).
@@ -253,6 +326,9 @@ proc initInitializeParams*(
253326
publishDiagnostics: some(PublishDiagnosticsCapability(
254327
dynamicRegistration: some(true)
255328
))
329+
)),
330+
window: some(WindowCapabilities(
331+
workDoneProgress: some(true)
256332
))
257333
),
258334
workspaceFolders: some(

src/moepkg/lsp/protocol/types.nim

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,12 @@ type
120120
kind*: string
121121
message*: Option[string]
122122

123-
ProgressParams* = ref object of RootObj
124-
token*: string # can be also int but the server will send strings
125-
value*: OptionalNode
123+
WorkDoneProgressCreateParams* = ref object of RootObj
124+
token*: OptionalNode # int or string (ProgressToken)
125+
126+
ProgressTokenParams* = ref object of RootObj
127+
token*: OptionalNode # int or string (ProgressToken)
128+
value*: OptionalNode # T
126129

127130
ConfigurationItem* = ref object of RootObj
128131
scopeUri*: Option[string]
@@ -262,6 +265,8 @@ type
262265

263266
WindowCapabilities* = ref object of RootObj
264267
workDoneProgress*: Option[bool]
268+
showMessage*: Option[ShowMessageRequestParams]
269+
showDocument*: Option[ShowDocumentClientCapabilities]
265270

266271
ClientCapabilities* = ref object of RootObj
267272
workspace*: Option[WorkspaceClientCapabilities]
@@ -369,6 +374,9 @@ type
369374
message*: string
370375
actions*: OptionalSeq[MessageActionItem]
371376

377+
ShowDocumentClientCapabilities* = ref object of RootObj
378+
support*: bool
379+
372380
LogMessageParams* = ref object of RootObj
373381
`type`*: int
374382
message*: string

0 commit comments

Comments
 (0)