Skip to content

Commit

Permalink
lsp: Add support for Progress (#1987)
Browse files Browse the repository at this point in the history
  • Loading branch information
fox0430 authored Jan 2, 2024
1 parent 4c47892 commit 88ac504
Show file tree
Hide file tree
Showing 10 changed files with 737 additions and 11 deletions.
7 changes: 7 additions & 0 deletions changelog.d/20240101_222848_GitHub_Actions_progress.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.. _#1987: https://github.com/fox0430/moe/pull/1987

Added
.....

- `#1987`_ lsp: Add support for Progress

5 changes: 4 additions & 1 deletion documents/lsp.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@ Please feedback, bug reports and PRs.
- `shutdown`
- `window/showMessage`
- `window/logMessage`
- `textDocument/publishDiagnostics`
- `window/workDoneProgress/create`
- `workspace/configuration`
- `workspace/didChangeConfiguration`
- `textDocument/publishDiagnostics`
- `textDocument/didOpen`
- `textDocument/didChange`
- `textDocument/didSave`
- `textDocument/didClose`
- `textDocument/hover`
- `$/progress`

## Configuration

Expand Down
1 change: 0 additions & 1 deletion src/moepkg/bufferstatus.nim
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import std/[tables, times, options, os, strformat]
import pkg/results
import syntax/highlite
import lsp/utils
import gapbuffer, unicodeext, fileutils, highlight, independentutils, git,
syntaxcheck

Expand Down
93 changes: 93 additions & 0 deletions src/moepkg/lsp.nim
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ proc showLspServerLog(
commandLine : var CommandLine,
notify: JsonNode): Result[(), string] =
## Show the log to the command line.
##
## window/showMessage

let m = parseWindowShowMessageNotify(notify)
Expand Down Expand Up @@ -162,6 +163,7 @@ proc lspDiagnostics(
bufStatuses: var seq[BufferStatus],
notify: JsonNode): Result[(), string] =
## Set BufferStatus.syntaxCheckResults to diagnostics results.
##
## textDocument/publishDiagnostics

let parseResult = parseTextDocumentPublishDiagnosticsNotify(notify)
Expand Down Expand Up @@ -195,6 +197,90 @@ proc lspDiagnostics(

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

proc lspProgressCreate(
c: var LspClient,
notify: JsonNode): Result[(), string] =
## Init a LSP progress.
##
## window/workDoneProgress/create

let token = parseWindowWorkDnoneProgressCreateNotify(notify)
if token.isErr:
return Result[(), string].err fmt"Invalid server notify: {token.error}"

c.createProgress(token.get)

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

proc progressMessage(p: ProgressReport): string {.inline.} =
case p.state:
of begin:
if p.message.len > 0:
return fmt"{p.title}: {p.message}"
else:
return p.title
of report:
result = fmt"{p.title}: "
if p.percentage.isSome: result &= fmt"{$p.percentage.get}%: "
result &= p.message
of `end`:
return fmt"{p.title}: {p.message}"
else:
return ""

proc lspProgress(
status: var EditorStatus,
notify: JsonNode): Result[(), string] =
## Begin/Report/End the LSP progress.
##
## $/progress

let token = workDoneProgressToken(notify)

if isWorkDoneProgressBegin(notify):
let begin = parseWorkDoneProgressBegin(notify)
if begin.isErr:
return Result[(), string].err fmt"Invalid server notify: {begin.error}"

if isCancellable(begin.get):
# Cancel
return lspClient.delProgress(token)

let err = lspClient.beginProgress(token, begin.get)
if err.isErr:
return Result[(), string].err fmt"Invalid server notify: {err.error}"
elif isWorkDoneProgressReport(notify):
let report = parseWorkDoneProgressReport(notify)
if report.isErr:
return Result[(), string].err fmt"Invalid server notify: {report.error}"

if isCancellable(report.get):
# Cancel
return lspClient.delProgress(token)

let err = lspClient.reportProgress(token, report.get)
if err.isErr:
return Result[(), string].err fmt"Invalid server notify: {err.error}"
elif isWorkDoneProgressEnd(notify):
let `end` = parseWorkDoneProgressEnd(notify)
if `end`.isErr:
return Result[(), string].err fmt"Invalid server notify: {`end`.error}"

let err = lspClient.endProgress(token, `end`.get)
if err.isErr:
return Result[(), string].err fmt"Invalid server notify: {err.error}"
else:
return Result[(), string].err fmt"Invalid server notify: {notify}"

case lspClient.progress[token].state:
of begin, report, `end`:
status.commandLine.writeLspProgress(
progressMessage(lspClient.progress[token]))
else:
discard

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

proc handleLspServerNotify(
status: var EditorStatus,
notify: JsonNode): Result[(), string] =
Expand All @@ -212,6 +298,13 @@ proc handleLspServerNotify(
of windowLogMessage:
# Already logged to LspClint.log.
return Result[(), string].ok ()
of workspaceConfiguration:
# TODO: Configure settings based on notifications if necessary.
return Result[(), string].ok ()
of windowWorkDnoneProgressCreate:
return lspClient.lspProgressCreate(notify)
of progress:
return status.lspProgress(notify)
of textDocumentPublishDiagnostics:
return status.bufStatus.lspDiagnostics(notify)
else:
Expand Down
78 changes: 77 additions & 1 deletion src/moepkg/lsp/client.nim
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
# NOTE: Language Server Protocol Specification - 3.17
# https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/

import std/[strformat, strutils, json, options, os, osproc, posix]
import std/[strformat, strutils, json, options, os, osproc, posix, tables]
import pkg/results

import ../appinfo
Expand Down Expand Up @@ -53,13 +53,17 @@ type
LspCapabilities* = object
hover*: bool

LspProgressTable* = Table[ProgressToken, ProgressReport]

LspClient* = ref object
serverProcess: Process
# LSP server process.
serverStreams: Streams
# Input/Output streams for the LSP server process.
capabilities*: Option[LspCapabilities]
# LSP server capabilities
progress*: LspProgressTable
# Use in window/workDoneProgress
waitingResponse*: Option[LspMethod]
# The waiting response from the LSP server.
log*: LspLog
Expand Down Expand Up @@ -111,6 +115,75 @@ proc addNotifyFromServerLog*(c: var LspClient, m: JsonNode) {.inline.} =
proc clearWaitingResponse*(c: var LspClient) {.inline.} =
c.waitingResponse = none(LspMethod)

proc createProgress*(c: var LspClient, token: ProgressToken) =
## Add a new progress to the `LspClint.progress`.

if not c.progress.contains(token):
c.progress[token] = ProgressReport(state: ProgressState.create)

proc beginProgress*(
c: var LspClient,
token: ProgressToken,
p: WorkDoneProgressBegin): Result[(), string] =
## Begin the progress in the `LspClint.progress`.

if not c.progress.contains(token):
return Result[(), string].err "token not found"

c.progress[token].state = ProgressState.begin
c.progress[token].title = p.title
if p.message.isSome:
c.progress[token].message = p.message.get
if p.percentage.isSome:
c.progress[token].percentage = some(p.percentage.get.Natural)

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

proc reportProgress*(
c: var LspClient,
token: ProgressToken,
report: WorkDoneProgressReport): Result[(), string] =
## Update the progress in the `LspClint.progress`.

if not c.progress.contains(token):
return Result[(), string].err "token not found"

if ProgressState.report != c.progress[token].state:
c.progress[token].state = ProgressState.report

if report.message.isSome:
c.progress[token].message = report.message.get
if report.percentage.isSome:
c.progress[token].percentage = some(report.percentage.get.Natural)

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

proc endProgress*(
c: var LspClient,
token: ProgressToken,
p: WorkDoneProgressEnd): Result[(), string] =
## End the progress in the `LspClint.progress`.

if not c.progress.contains(token):
return Result[(), string].err "token not found"

c.progress[token].state = ProgressState.end

if p.message.isSome:
c.progress[token].message = p.message.get

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

proc delProgress*(c: var LspClient, token: ProgressToken): Result[(), string] =
## Delete the progress from the `LspClint.progress`.

if not c.progress.contains(token):
return Result[(), string].err "token not found"

c.progress.del(token)

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

proc readable*(c: LspClient, timeout: int = 1): LspClientReadableResult =
## Return when output is written from the LSP server or timesout.
## Wait for the output from process to be written using poll(2).
Expand Down Expand Up @@ -253,6 +326,9 @@ proc initInitializeParams*(
publishDiagnostics: some(PublishDiagnosticsCapability(
dynamicRegistration: some(true)
))
)),
window: some(WindowCapabilities(
workDoneProgress: some(true)
))
),
workspaceFolders: some(
Expand Down
14 changes: 11 additions & 3 deletions src/moepkg/lsp/protocol/types.nim
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,12 @@ type
kind*: string
message*: Option[string]

ProgressParams* = ref object of RootObj
token*: string # can be also int but the server will send strings
value*: OptionalNode
WorkDoneProgressCreateParams* = ref object of RootObj
token*: OptionalNode # int or string (ProgressToken)

ProgressTokenParams* = ref object of RootObj
token*: OptionalNode # int or string (ProgressToken)
value*: OptionalNode # T

ConfigurationItem* = ref object of RootObj
scopeUri*: Option[string]
Expand Down Expand Up @@ -262,6 +265,8 @@ type

WindowCapabilities* = ref object of RootObj
workDoneProgress*: Option[bool]
showMessage*: Option[ShowMessageRequestParams]
showDocument*: Option[ShowDocumentClientCapabilities]

ClientCapabilities* = ref object of RootObj
workspace*: Option[WorkspaceClientCapabilities]
Expand Down Expand Up @@ -369,6 +374,9 @@ type
message*: string
actions*: OptionalSeq[MessageActionItem]

ShowDocumentClientCapabilities* = ref object of RootObj
support*: bool

LogMessageParams* = ref object of RootObj
`type`*: int
message*: string
Expand Down
Loading

0 comments on commit 88ac504

Please sign in to comment.