Skip to content

Commit ea08803

Browse files
committed
support for LSP work_done and progress
1 parent 43a9573 commit ea08803

File tree

5 files changed

+196
-31
lines changed

5 files changed

+196
-31
lines changed

robotcode/language_server/common/lsp_types.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1929,3 +1929,52 @@ class SelectionRangeParams(WorkDoneProgressParams, PartialResultParams, _Selecti
19291929
class SelectionRange(Model):
19301930
range: Range
19311931
parent: Optional[SelectionRange] = None
1932+
1933+
1934+
@dataclass(repr=False)
1935+
class ProgressParams(Model):
1936+
token: ProgressToken
1937+
value: Any
1938+
1939+
1940+
@dataclass(repr=False)
1941+
class WorkDoneProgressCreateParams(Model):
1942+
token: ProgressToken
1943+
1944+
1945+
@dataclass(repr=False)
1946+
class WorkDoneProgressCancelParams(Model):
1947+
token: ProgressToken
1948+
1949+
1950+
@dataclass(repr=False)
1951+
class WorkDoneProgressBase(Model):
1952+
kind: Literal["begin", "end", "report"]
1953+
1954+
1955+
@dataclass(repr=False)
1956+
class _WorkDoneProgressCommon(Model):
1957+
title: str
1958+
message: Optional[str] = None
1959+
percentage: Optional[int] = None
1960+
cancellable: Optional[bool] = None
1961+
1962+
1963+
@dataclass(repr=False)
1964+
class WorkDoneProgressBegin(WorkDoneProgressBase, _WorkDoneProgressCommon):
1965+
kind: Literal["begin"] = "begin"
1966+
1967+
1968+
@dataclass(repr=False)
1969+
class WorkDoneProgressReport(WorkDoneProgressBase, _WorkDoneProgressCommon):
1970+
kind: Literal["report"] = "report"
1971+
1972+
1973+
@dataclass(repr=False)
1974+
class _WorkDoneProgressEnd(Model):
1975+
message: Optional[str] = None
1976+
1977+
1978+
@dataclass(repr=False)
1979+
class WorkDoneProgressEnd(WorkDoneProgressBase, _WorkDoneProgressEnd):
1980+
kind: Literal["end"] = "end"

robotcode/language_server/common/parts/diagnostics.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ def __init__(
3737
version: Optional[int],
3838
factory: Callable[..., asyncio.Future[Any]],
3939
done_callback: Callable[[PublishDiagnosticsEntry], Any],
40+
no_wait: bool = False,
4041
) -> None:
4142

4243
self.uri = uri
@@ -48,6 +49,7 @@ def __init__(
4849
self._future: Optional[asyncio.Future[Any]] = None
4950

5051
self.done = False
52+
self.no_wait = no_wait
5153

5254
def _done(t: asyncio.Future[Any]) -> None:
5355
self.done = True
@@ -57,7 +59,9 @@ def _done(t: asyncio.Future[Any]) -> None:
5759
self._future.add_done_callback(_done)
5860

5961
async def _wait_and_run(self) -> None:
60-
await asyncio.sleep(DIAGNOSTICS_DEBOUNCE)
62+
if not self.no_wait:
63+
await asyncio.sleep(DIAGNOSTICS_DEBOUNCE)
64+
6165
await self._factory()
6266

6367
def __del__(self) -> None:
@@ -190,21 +194,23 @@ def _delete_entry(self, e: PublishDiagnosticsEntry) -> None:
190194
self._running_diagnostics.pop(e.uri, None)
191195

192196
@_logger.call
193-
async def start_publish_diagnostics_task(self, document: TextDocument) -> None:
197+
async def start_publish_diagnostics_task(self, document: TextDocument, no_wait: bool = False) -> None:
194198
async with self._tasks_lock:
195199
entry = self._running_diagnostics.get(document.uri, None)
196200

197-
if entry is not None and entry.version == document.version:
198-
return
201+
if entry is not None and entry.version == document.version:
202+
return
199203

200-
await self._cancel_entry(entry)
204+
await self._cancel_entry(entry)
201205

202-
self._running_diagnostics[document.uri] = PublishDiagnosticsEntry(
203-
document.uri,
204-
document.version,
205-
lambda: run_coroutine_in_thread(self.publish_diagnostics, document.document_uri),
206-
self._delete_entry,
207-
)
206+
self._running_diagnostics[document.uri] = PublishDiagnosticsEntry(
207+
document.uri,
208+
document.version,
209+
lambda: run_coroutine_in_thread(self.publish_diagnostics, document.document_uri),
210+
# lambda: create_sub_task(self.publish_diagnostics(document.document_uri)),
211+
self._delete_entry,
212+
no_wait,
213+
)
208214

209215
@_logger.call
210216
async def publish_diagnostics(self, document_uri: DocumentUri) -> None:

robotcode/language_server/common/parts/window.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
1+
import uuid
12
from typing import List, Optional
23

34
from ..lsp_types import (
45
URI,
56
LogMessageParams,
67
MessageActionItem,
78
MessageType,
9+
ProgressParams,
10+
ProgressToken,
811
Range,
912
ShowDocumentParams,
1013
ShowDocumentResult,
1114
ShowMessageParams,
1215
ShowMessageRequestParams,
16+
WorkDoneProgressBase,
17+
WorkDoneProgressBegin,
18+
WorkDoneProgressCancelParams,
19+
WorkDoneProgressCreateParams,
20+
WorkDoneProgressEnd,
21+
WorkDoneProgressReport,
1322
)
1423
from .protocol_part import LanguageServerProtocolPart
1524

@@ -44,3 +53,91 @@ async def show_document(
4453
ShowDocumentResult,
4554
)
4655
).success
56+
57+
async def create_progress(self) -> Optional[ProgressToken]:
58+
59+
if (
60+
self.parent.client_capabilities
61+
and self.parent.client_capabilities.window
62+
and self.parent.client_capabilities.window.work_done_progress
63+
):
64+
token = str(uuid.uuid4())
65+
await self.parent.send_request_async("window/workDoneProgress/create", WorkDoneProgressCreateParams(token))
66+
return token
67+
68+
return None
69+
70+
def progress_cancel(self, token: Optional[ProgressToken]) -> None:
71+
if (
72+
token is not None
73+
and self.parent.client_capabilities
74+
and self.parent.client_capabilities.window
75+
and self.parent.client_capabilities.window.work_done_progress
76+
):
77+
self.parent.send_notification("window/workDoneProgress/cancel", WorkDoneProgressCancelParams(token))
78+
79+
def _progress(self, token: Optional[ProgressToken], value: WorkDoneProgressBase) -> None:
80+
if (
81+
token is not None
82+
and self.parent.client_capabilities
83+
and self.parent.client_capabilities.window
84+
and self.parent.client_capabilities.window.work_done_progress
85+
):
86+
self.parent.send_notification("$/progress", ProgressParams(token, value))
87+
88+
_default_title = "Dummy"
89+
90+
def progress_begin(
91+
self,
92+
token: Optional[ProgressToken],
93+
message: Optional[str] = None,
94+
percentage: Optional[int] = None,
95+
cancellable: Optional[bool] = None,
96+
title: Optional[str] = None,
97+
) -> None:
98+
if (
99+
token is not None
100+
and self.parent.client_capabilities
101+
and self.parent.client_capabilities.window
102+
and self.parent.client_capabilities.window.work_done_progress
103+
):
104+
self._progress(
105+
token,
106+
WorkDoneProgressBegin(
107+
title or self.parent.name or self._default_title, message, percentage, cancellable
108+
),
109+
)
110+
111+
def progress_report(
112+
self,
113+
token: Optional[ProgressToken],
114+
message: Optional[str] = None,
115+
percentage: Optional[int] = None,
116+
cancellable: Optional[bool] = None,
117+
title: Optional[str] = None,
118+
) -> None:
119+
if (
120+
token is not None
121+
and self.parent.client_capabilities
122+
and self.parent.client_capabilities.window
123+
and self.parent.client_capabilities.window.work_done_progress
124+
):
125+
self._progress(
126+
token,
127+
WorkDoneProgressReport(
128+
title or self.parent.name or self._default_title, message, percentage, cancellable
129+
),
130+
)
131+
132+
def progress_end(
133+
self,
134+
token: Optional[ProgressToken],
135+
message: Optional[str] = None,
136+
) -> None:
137+
if (
138+
token is not None
139+
and self.parent.client_capabilities
140+
and self.parent.client_capabilities.window
141+
and self.parent.client_capabilities.window.work_done_progress
142+
):
143+
self._progress(token, WorkDoneProgressEnd(message))

robotcode/language_server/common/protocol.py

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
InitializeParams,
2525
InitializeResult,
2626
InitializeResultServerInfo,
27+
ProgressToken,
2728
Registration,
2829
RegistrationParams,
2930
SaveOptions,
@@ -164,6 +165,7 @@ async def _initialize(
164165
trace: Optional[TraceValue] = None,
165166
client_info: Optional[ClientInfo] = None,
166167
workspace_folders: Optional[List[WorkspaceFolder]] = None,
168+
work_done_token: Optional[ProgressToken] = None,
167169
*args: Any,
168170
**kwargs: Any,
169171
) -> InitializeResult:
@@ -175,27 +177,38 @@ async def _initialize(
175177

176178
self._workspace = Workspace(self, root_uri=root_uri, root_path=root_path, workspace_folders=workspace_folders)
177179

178-
self.initialization_options = initialization_options
179-
try:
180-
await self.on_initialize(self, initialization_options)
181-
except (asyncio.CancelledError, SystemExit, KeyboardInterrupt):
182-
raise
183-
except JsonRPCErrorException:
184-
raise
185-
except BaseException as e:
186-
raise JsonRPCErrorException(
187-
JsonRPCErrors.INTERNAL_ERROR, f"Can't start language server: {e}", InitializeError(retry=False)
188-
) from e
189-
190-
return InitializeResult(
191-
capabilities=self.capabilities,
192-
**(
193-
{"server_info": InitializeResultServerInfo(name=self.name, version=self.version)}
194-
if self.name is not None
195-
else {}
196-
),
180+
folders = (
181+
", ".join((f"'{v.name}'" for v in self._workspace.workspace_folders))
182+
if self._workspace.workspace_folders
183+
else ""
197184
)
198185

186+
self.window.progress_begin(work_done_token, f"Initialize {folders}...")
187+
188+
try:
189+
self.initialization_options = initialization_options
190+
try:
191+
await self.on_initialize(self, initialization_options)
192+
except (asyncio.CancelledError, SystemExit, KeyboardInterrupt):
193+
raise
194+
except JsonRPCErrorException:
195+
raise
196+
except BaseException as e:
197+
raise JsonRPCErrorException(
198+
JsonRPCErrors.INTERNAL_ERROR, f"Can't start language server: {e}", InitializeError(retry=False)
199+
) from e
200+
201+
return InitializeResult(
202+
capabilities=self.capabilities,
203+
**(
204+
{"server_info": InitializeResultServerInfo(name=self.name, version=self.version)}
205+
if self.name is not None
206+
else {}
207+
),
208+
)
209+
finally:
210+
self.window.progress_end(work_done_token)
211+
199212
@async_event
200213
async def on_initialize(sender, initialization_options: Optional[Any] = None) -> None: # pragma: no cover, NOSONAR
201214
...

robotcode/language_server/robotframework/protocol.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ class RobotLanguageServerProtocol(LanguageServerProtocol):
9292
robot_discovering = ProtocolPartDescriptor(DiscoveringProtocolPart)
9393
robot_debugging_utils = ProtocolPartDescriptor(RobotDebuggingUtilsProtocolPart)
9494

95-
name = "RobotCode"
95+
name = "RobotCode Language Server"
9696
version = __version__
9797

9898
file_extensions = {"robot", "resource", "py"}

0 commit comments

Comments
 (0)