Skip to content

Commit fd4a0df

Browse files
authoredJun 29, 2023
Report progress even when initialization fails (#381)
1 parent 05ecbdc commit fd4a0df

File tree

3 files changed

+49
-25
lines changed

3 files changed

+49
-25
lines changed
 

‎pylsp/plugins/_rope_task_handle.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ def __init__(self, workspace: Workspace):
7676
self.observers = []
7777

7878
def create_jobset(self, name="JobSet", count: Optional[int] = None):
79-
report_iter = self.workspace.report_progress(name, None, None)
79+
report_iter = self.workspace.report_progress(
80+
name, None, None, skip_token_initialization=True
81+
)
8082
result = PylspJobSet(count, report_iter)
8183
self.job_sets.append(result)
8284
self._inform_observers()

‎pylsp/workspace.py

+37-20
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,20 @@ def report_progress(
135135
title: str,
136136
message: Optional[str] = None,
137137
percentage: Optional[int] = None,
138+
skip_token_initialization: bool = False,
138139
) -> Generator[Callable[[str, Optional[int]], None], None, None]:
140+
"""
141+
Report progress to the editor / client.
142+
143+
``skip_token_initialization` is necessary due to some current
144+
limitations of our LSP implementation. When `report_progress`
145+
is used from a synchronous LSP handler, the token initialization
146+
will time out because we can't receive the response.
147+
148+
Many editors will still correctly show the progress messages though, which
149+
is why we are giving progress users the option to skip the initialization
150+
of the progress token.
151+
"""
139152
if self._config:
140153
client_supports_progress_reporting = (
141154
self._config.capabilities.get("window", {}).get("workDoneProgress", False)
@@ -144,30 +157,21 @@ def report_progress(
144157
client_supports_progress_reporting = False
145158

146159
if client_supports_progress_reporting:
147-
try:
148-
token = self._progress_begin(title, message, percentage)
149-
except Exception: # pylint: disable=broad-exception-caught
150-
log.warning(
151-
"There was an error while trying to initialize progress reporting."
152-
"Likely progress reporting was used in a synchronous LSP handler, "
153-
"which is not supported by progress reporting yet.",
154-
exc_info=True
155-
)
160+
token = self._progress_begin(title, message, percentage, skip_token_initialization)
156161

157-
else:
158-
def progress_message(message: str, percentage: Optional[int] = None) -> None:
159-
self._progress_report(token, message, percentage)
162+
def progress_message(message: str, percentage: Optional[int] = None) -> None:
163+
self._progress_report(token, message, percentage)
160164

161-
try:
162-
yield progress_message
163-
finally:
164-
self._progress_end(token)
165+
try:
166+
yield progress_message
167+
finally:
168+
self._progress_end(token)
165169

166-
return
170+
return
167171

168172
# FALLBACK:
169-
# If the client doesn't support progress reporting, or if we failed to
170-
# initialize it, we have a dummy method for the caller to use.
173+
# If the client doesn't support progress reporting, we have a dummy method
174+
# for the caller to use.
171175
def dummy_progress_message(message: str, percentage: Optional[int] = None) -> None:
172176
# pylint: disable=unused-argument
173177
pass
@@ -179,10 +183,23 @@ def _progress_begin(
179183
title: str,
180184
message: Optional[str] = None,
181185
percentage: Optional[int] = None,
186+
skip_token_initialization: bool = False,
182187
) -> str:
183188
token = str(uuid.uuid4())
184189

185-
self._endpoint.request(self.M_INITIALIZE_PROGRESS, {'token': token}).result(timeout=1.0)
190+
if not skip_token_initialization:
191+
try:
192+
self._endpoint.request(self.M_INITIALIZE_PROGRESS, {'token': token}).result(timeout=1.0)
193+
except Exception: # pylint: disable=broad-exception-caught
194+
log.warning(
195+
"There was an error while trying to initialize progress reporting."
196+
"Likely progress reporting was used in a synchronous LSP handler, "
197+
"which is not supported by progress reporting yet. "
198+
"To prevent waiting for the timeout you can set "
199+
"`skip_token_initialization=True`. "
200+
"Not every editor will show progress then, but many will.",
201+
exc_info=True
202+
)
186203

187204
value = {
188205
"kind": "begin",

‎test/test_workspace.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -327,19 +327,24 @@ def test_progress_simple(workspace, consumer):
327327

328328

329329
@pytest.mark.parametrize("exc", [Exception("something"), TimeoutError()])
330-
def test_progress_initialization_fails(workspace, consumer, endpoint, exc):
330+
def test_progress_initialization_fails_but_is_skipped(workspace, consumer, endpoint, exc):
331331
def failing_token_initialization(self, *_args, **_kwargs):
332332
raise exc
333333
endpoint._dispatcher.m_window__work_done_progress__create = failing_token_initialization
334334

335335
workspace._config.capabilities['window'] = {"workDoneProgress": True}
336336

337-
with workspace.report_progress("some_title"):
337+
with workspace.report_progress("some_title", skip_token_initialization=True):
338338
pass
339339

340340
# we only see the failing token initialization call, no other calls
341-
init_call, = consumer.call_args_list
342-
assert init_call[0][0]['method'] == 'window/workDoneProgress/create'
341+
progress_calls = consumer.call_args_list
342+
assert all(call[0][0]["method"] == "$/progress" for call in progress_calls)
343+
assert len({call[0][0]["params"]["token"] for call in progress_calls}) == 1
344+
assert [call[0][0]["params"]["value"] for call in progress_calls] == [
345+
{"kind": "begin", "title": "some_title"},
346+
{"kind": "end"},
347+
]
343348

344349

345350
def test_progress_with_percent(workspace, consumer):

0 commit comments

Comments
 (0)
Please sign in to comment.