Skip to content

Commit a687926

Browse files
committed
make progress-reporting configurable
1 parent 8c4704d commit a687926

File tree

2 files changed

+168
-152
lines changed

2 files changed

+168
-152
lines changed

README.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ Configuration
3636
``overrides`` (default is ``[True]``) specifies a list of alternate or supplemental command-line options.
3737
This modifies the options passed to ``mypy`` or the mypy-specific ones passed to ``dmypy run``. When present, the special boolean member ``True`` is replaced with the command-line options that would've been passed had ``overrides`` not been specified. Later options take precedence, which allows for replacing or negating individual default options (see ``mypy.main:process_options`` and ``mypy --help | grep inverse``).
3838

39+
``report_progress`` (default is False) report progress to the LSP client.
40+
When you editor supports LSP progress reporting, mypy will then report progress when it's running.
41+
3942
This project supports the use of ``pyproject.toml`` for configuration. It is in fact the preferred way. Using that your configuration could look like this:
4043

4144
::

pylsp_mypy/plugin.py

Lines changed: 165 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,36 @@ def apply_overrides(args: List[str], overrides: List[Any]) -> List[str]:
122122
@hookimpl
123123
def pylsp_lint(
124124
config: Config, workspace: Workspace, document: Document, is_saved: bool
125+
) -> List[Dict[str, Any]]:
126+
settings = config.plugin_settings("pylsp_mypy")
127+
oldSettings1 = config.plugin_settings("mypy-ls")
128+
if oldSettings1 != {}:
129+
raise DeprecationWarning(
130+
"Your configuration uses the namespace mypy-ls, this should be changed to pylsp_mypy"
131+
)
132+
oldSettings2 = config.plugin_settings("mypy_ls")
133+
if oldSettings2 != {}:
134+
raise DeprecationWarning(
135+
"Your configuration uses the namespace mypy_ls, this should be changed to pylsp_mypy"
136+
)
137+
if settings == {}:
138+
settings = oldSettings1
139+
if settings == {}:
140+
settings = oldSettings2
141+
142+
if settings.get("report_progress", False):
143+
with workspace.report_progress("lint: mypy"):
144+
return get_diagnostics(config, workspace, document, settings, is_saved)
145+
else:
146+
return get_diagnostics(config, workspace, document, settings, is_saved)
147+
148+
149+
def get_diagnostics(
150+
config: Config,
151+
workspace: Workspace,
152+
document: Document,
153+
settings: Dict[str, Any],
154+
is_saved: bool,
125155
) -> List[Dict[str, Any]]:
126156
"""
127157
Lints.
@@ -143,176 +173,159 @@ def pylsp_lint(
143173
List of the linting data.
144174
145175
"""
146-
with workspace.report_progress("lint: mypy"):
147-
settings = config.plugin_settings("pylsp_mypy")
148-
oldSettings1 = config.plugin_settings("mypy-ls")
149-
if oldSettings1 != {}:
150-
raise DeprecationWarning(
151-
"Your configuration uses the namespace mypy-ls, this should be changed to pylsp_mypy"
152-
)
153-
oldSettings2 = config.plugin_settings("mypy_ls")
154-
if oldSettings2 != {}:
155-
raise DeprecationWarning(
156-
"Your configuration uses the namespace mypy_ls, this should be changed to pylsp_mypy"
157-
)
158-
if settings == {}:
159-
settings = oldSettings1
160-
if settings == {}:
161-
settings = oldSettings2
176+
log.info(
177+
"lint settings = %s document.path = %s is_saved = %s",
178+
settings,
179+
document.path,
180+
is_saved,
181+
)
182+
183+
live_mode = settings.get("live_mode", True)
184+
dmypy = settings.get("dmypy", False)
185+
186+
if dmypy and live_mode:
187+
# dmypy can only be efficiently run on files that have been saved, see:
188+
# https://github.com/python/mypy/issues/9309
189+
log.warning("live_mode is not supported with dmypy, disabling")
190+
live_mode = False
162191

192+
args = ["--show-column-numbers"]
193+
194+
global tmpFile
195+
if live_mode and not is_saved:
196+
if tmpFile:
197+
tmpFile = open(tmpFile.name, "w")
198+
else:
199+
tmpFile = tempfile.NamedTemporaryFile("w", delete=False)
200+
log.info("live_mode tmpFile = %s", tmpFile.name)
201+
tmpFile.write(document.source)
202+
tmpFile.close()
203+
args.extend(["--shadow-file", document.path, tmpFile.name])
204+
elif not is_saved and document.path in last_diagnostics:
205+
# On-launch the document isn't marked as saved, so fall through and run
206+
# the diagnostics anyway even if the file contents may be out of date.
163207
log.info(
164-
"lint settings = %s document.path = %s is_saved = %s",
165-
settings,
166-
document.path,
167-
is_saved,
208+
"non-live, returning cached diagnostics len(cached) = %s",
209+
last_diagnostics[document.path],
168210
)
211+
return last_diagnostics[document.path]
169212

170-
live_mode = settings.get("live_mode", True)
171-
dmypy = settings.get("dmypy", False)
172-
173-
if dmypy and live_mode:
174-
# dmypy can only be efficiently run on files that have been saved, see:
175-
# https://github.com/python/mypy/issues/9309
176-
log.warning("live_mode is not supported with dmypy, disabling")
177-
live_mode = False
178-
179-
args = ["--show-column-numbers"]
180-
181-
global tmpFile
182-
if live_mode and not is_saved:
183-
if tmpFile:
184-
tmpFile = open(tmpFile.name, "w")
185-
else:
186-
tmpFile = tempfile.NamedTemporaryFile("w", delete=False)
187-
log.info("live_mode tmpFile = %s", tmpFile.name)
188-
tmpFile.write(document.source)
189-
tmpFile.close()
190-
args.extend(["--shadow-file", document.path, tmpFile.name])
191-
elif not is_saved and document.path in last_diagnostics:
192-
# On-launch the document isn't marked as saved, so fall through and run
193-
# the diagnostics anyway even if the file contents may be out of date.
194-
log.info(
195-
"non-live, returning cached diagnostics len(cached) = %s",
196-
last_diagnostics[document.path],
197-
)
198-
return last_diagnostics[document.path]
199-
200-
mypyConfigFile = mypyConfigFileMap.get(workspace.root_path)
201-
if mypyConfigFile:
202-
args.append("--config-file")
203-
args.append(mypyConfigFile)
213+
mypyConfigFile = mypyConfigFileMap.get(workspace.root_path)
214+
if mypyConfigFile:
215+
args.append("--config-file")
216+
args.append(mypyConfigFile)
204217

205-
args.append(document.path)
218+
args.append(document.path)
206219

207-
if settings.get("strict", False):
208-
args.append("--strict")
220+
if settings.get("strict", False):
221+
args.append("--strict")
209222

210-
overrides = settings.get("overrides", [True])
211-
exit_status = 0
223+
overrides = settings.get("overrides", [True])
224+
exit_status = 0
212225

213-
if not dmypy:
214-
args.extend(["--incremental", "--follow-imports", "silent"])
215-
args = apply_overrides(args, overrides)
226+
if not dmypy:
227+
args.extend(["--incremental", "--follow-imports", "silent"])
228+
args = apply_overrides(args, overrides)
216229

217-
if shutil.which("mypy"):
218-
# mypy exists on path
219-
# -> use mypy on path
220-
log.info("executing mypy args = %s on path", args)
221-
completed_process = subprocess.run(
222-
["mypy", *args], stdout=subprocess.PIPE, stderr=subprocess.PIPE, **windows_flag
230+
if shutil.which("mypy"):
231+
# mypy exists on path
232+
# -> use mypy on path
233+
log.info("executing mypy args = %s on path", args)
234+
completed_process = subprocess.run(
235+
["mypy", *args], stdout=subprocess.PIPE, stderr=subprocess.PIPE, **windows_flag
236+
)
237+
report = completed_process.stdout.decode()
238+
errors = completed_process.stderr.decode()
239+
exit_status = completed_process.returncode
240+
else:
241+
# mypy does not exist on path, but must exist in the env pylsp-mypy is installed in
242+
# -> use mypy via api
243+
log.info("executing mypy args = %s via api", args)
244+
report, errors, exit_status = mypy_api.run(args)
245+
else:
246+
# If dmypy daemon is non-responsive calls to run will block.
247+
# Check daemon status, if non-zero daemon is dead or hung.
248+
# If daemon is hung, kill will reset
249+
# If daemon is dead/absent, kill will no-op.
250+
# In either case, reset to fresh state
251+
252+
if shutil.which("dmypy"):
253+
# dmypy exists on path
254+
# -> use mypy on path
255+
completed_process = subprocess.run(
256+
["dmypy", "status"], stderr=subprocess.PIPE, **windows_flag
257+
)
258+
errors = completed_process.stderr.decode()
259+
exit_status = completed_process.returncode
260+
if exit_status != 0:
261+
log.info(
262+
"restarting dmypy from status: %s message: %s via path",
263+
exit_status,
264+
errors.strip(),
223265
)
224-
report = completed_process.stdout.decode()
225-
errors = completed_process.stderr.decode()
226-
exit_status = completed_process.returncode
227-
else:
228-
# mypy does not exist on path, but must exist in the env pylsp-mypy is installed in
229-
# -> use mypy via api
230-
log.info("executing mypy args = %s via api", args)
231-
report, errors, exit_status = mypy_api.run(args)
266+
subprocess.run(["dmypy", "restart"], **windows_flag)
232267
else:
233-
# If dmypy daemon is non-responsive calls to run will block.
234-
# Check daemon status, if non-zero daemon is dead or hung.
235-
# If daemon is hung, kill will reset
236-
# If daemon is dead/absent, kill will no-op.
237-
# In either case, reset to fresh state
238-
239-
if shutil.which("dmypy"):
240-
# dmypy exists on path
241-
# -> use mypy on path
242-
completed_process = subprocess.run(
243-
["dmypy", "status"], stderr=subprocess.PIPE, **windows_flag
268+
# dmypy does not exist on path, but must exist in the env pylsp-mypy is installed in
269+
# -> use dmypy via api
270+
_, errors, exit_status = mypy_api.run_dmypy(["status"])
271+
if exit_status != 0:
272+
log.info(
273+
"restarting dmypy from status: %s message: %s via api",
274+
exit_status,
275+
errors.strip(),
244276
)
245-
errors = completed_process.stderr.decode()
246-
exit_status = completed_process.returncode
247-
if exit_status != 0:
248-
log.info(
249-
"restarting dmypy from status: %s message: %s via path",
250-
exit_status,
251-
errors.strip(),
252-
)
253-
subprocess.run(["dmypy", "restart"], **windows_flag)
254-
else:
255-
# dmypy does not exist on path, but must exist in the env pylsp-mypy is installed in
256-
# -> use dmypy via api
257-
_, errors, exit_status = mypy_api.run_dmypy(["status"])
258-
if exit_status != 0:
259-
log.info(
260-
"restarting dmypy from status: %s message: %s via api",
261-
exit_status,
262-
errors.strip(),
263-
)
264-
mypy_api.run_dmypy(["restart"])
277+
mypy_api.run_dmypy(["restart"])
265278

266-
# run to use existing daemon or restart if required
267-
args = ["run", "--"] + apply_overrides(args, overrides)
279+
# run to use existing daemon or restart if required
280+
args = ["run", "--"] + apply_overrides(args, overrides)
268281

269-
if shutil.which("dmypy"):
270-
# dmypy exists on path
271-
# -> use mypy on path
272-
log.info("dmypy run args = %s via path", args)
273-
completed_process = subprocess.run(
274-
["dmypy", *args], stdout=subprocess.PIPE, stderr=subprocess.PIPE, **windows_flag
275-
)
276-
report = completed_process.stdout.decode()
277-
errors = completed_process.stderr.decode()
278-
exit_status = completed_process.returncode
279-
else:
280-
# dmypy does not exist on path, but must exist in the env pylsp-mypy is installed in
281-
# -> use dmypy via api
282-
log.info("dmypy run args = %s via api", args)
283-
report, errors, exit_status = mypy_api.run_dmypy(args)
284-
285-
log.debug("report:\n%s", report)
286-
log.debug("errors:\n%s", errors)
287-
288-
diagnostics = []
289-
290-
# Expose generic mypy error on the first line.
291-
if errors:
292-
diagnostics.append(
293-
{
294-
"source": "mypy",
295-
"range": {
296-
"start": {"line": 0, "character": 0},
297-
# Client is supposed to clip end column to line length.
298-
"end": {"line": 0, "character": 1000},
299-
},
300-
"message": errors,
301-
# Error if exited with error or warning.
302-
"severity": 1 if exit_status != 0 else 2,
303-
}
282+
if shutil.which("dmypy"):
283+
# dmypy exists on path
284+
# -> use mypy on path
285+
log.info("dmypy run args = %s via path", args)
286+
completed_process = subprocess.run(
287+
["dmypy", *args], stdout=subprocess.PIPE, stderr=subprocess.PIPE, **windows_flag
304288
)
289+
report = completed_process.stdout.decode()
290+
errors = completed_process.stderr.decode()
291+
exit_status = completed_process.returncode
292+
else:
293+
# dmypy does not exist on path, but must exist in the env pylsp-mypy is installed in
294+
# -> use dmypy via api
295+
log.info("dmypy run args = %s via api", args)
296+
report, errors, exit_status = mypy_api.run_dmypy(args)
297+
298+
log.debug("report:\n%s", report)
299+
log.debug("errors:\n%s", errors)
300+
301+
diagnostics = []
302+
303+
# Expose generic mypy error on the first line.
304+
if errors:
305+
diagnostics.append(
306+
{
307+
"source": "mypy",
308+
"range": {
309+
"start": {"line": 0, "character": 0},
310+
# Client is supposed to clip end column to line length.
311+
"end": {"line": 0, "character": 1000},
312+
},
313+
"message": errors,
314+
# Error if exited with error or warning.
315+
"severity": 1 if exit_status != 0 else 2,
316+
}
317+
)
305318

306-
for line in report.splitlines():
307-
log.debug("parsing: line = %r", line)
308-
diag = parse_line(line, document)
309-
if diag:
310-
diagnostics.append(diag)
319+
for line in report.splitlines():
320+
log.debug("parsing: line = %r", line)
321+
diag = parse_line(line, document)
322+
if diag:
323+
diagnostics.append(diag)
311324

312-
log.info("pylsp-mypy len(diagnostics) = %s", len(diagnostics))
325+
log.info("pylsp-mypy len(diagnostics) = %s", len(diagnostics))
313326

314-
last_diagnostics[document.path] = diagnostics
315-
return diagnostics
327+
last_diagnostics[document.path] = diagnostics
328+
return diagnostics
316329

317330

318331
@hookimpl

0 commit comments

Comments
 (0)