Skip to content

Commit cd9941c

Browse files
authored
Add progress reporting (python-lsp#236)
1 parent 1b58531 commit cd9941c

23 files changed

+527
-342
lines changed

pylsp/plugins/autopep8_format.py

+18-14
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,27 @@
1313

1414

1515
@hookimpl(tryfirst=True) # Prefer autopep8 over YAPF
16-
def pylsp_format_document(config, document, options): # pylint: disable=unused-argument
17-
log.info("Formatting document %s with autopep8", document)
18-
return _format(config, document)
16+
def pylsp_format_document(config, workspace, document, options): # pylint: disable=unused-argument
17+
with workspace.report_progress("format: autopep8"):
18+
log.info("Formatting document %s with autopep8", document)
19+
return _format(config, document)
1920

2021

2122
@hookimpl(tryfirst=True) # Prefer autopep8 over YAPF
22-
def pylsp_format_range(config, document, range, options): # pylint: disable=redefined-builtin,unused-argument
23-
log.info("Formatting document %s in range %s with autopep8", document, range)
24-
25-
# First we 'round' the range up/down to full lines only
26-
range['start']['character'] = 0
27-
range['end']['line'] += 1
28-
range['end']['character'] = 0
29-
30-
# Add 1 for 1-indexing vs LSP's 0-indexing
31-
line_range = (range['start']['line'] + 1, range['end']['line'] + 1)
32-
return _format(config, document, line_range=line_range)
23+
def pylsp_format_range(
24+
config, workspace, document, range, options
25+
): # pylint: disable=redefined-builtin,unused-argument
26+
with workspace.report_progress("format_range: autopep8"):
27+
log.info("Formatting document %s in range %s with autopep8", document, range)
28+
29+
# First we 'round' the range up/down to full lines only
30+
range['start']['character'] = 0
31+
range['end']['line'] += 1
32+
range['end']['character'] = 0
33+
34+
# Add 1 for 1-indexing vs LSP's 0-indexing
35+
line_range = (range['start']['line'] + 1, range['end']['line'] + 1)
36+
return _format(config, document, line_range=line_range)
3337

3438

3539
def _format(config, document, line_range=None):

pylsp/plugins/definition.py

+17-16
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,25 @@
88

99

1010
@hookimpl
11-
def pylsp_definitions(config, document, position):
12-
settings = config.plugin_settings('jedi_definition')
13-
code_position = _utils.position_to_jedi_linecolumn(document, position)
14-
definitions = document.jedi_script(use_document_path=True).goto(
15-
follow_imports=settings.get('follow_imports', True),
16-
follow_builtin_imports=settings.get('follow_builtin_imports', True),
17-
**code_position)
11+
def pylsp_definitions(config, workspace, document, position):
12+
with workspace.report_progress("go to definitions"):
13+
settings = config.plugin_settings('jedi_definition')
14+
code_position = _utils.position_to_jedi_linecolumn(document, position)
15+
definitions = document.jedi_script(use_document_path=True).goto(
16+
follow_imports=settings.get('follow_imports', True),
17+
follow_builtin_imports=settings.get('follow_builtin_imports', True),
18+
**code_position)
1819

19-
return [
20-
{
21-
'uri': uris.uri_with(document.uri, path=str(d.module_path)),
22-
'range': {
23-
'start': {'line': d.line - 1, 'character': d.column},
24-
'end': {'line': d.line - 1, 'character': d.column + len(d.name)},
20+
return [
21+
{
22+
'uri': uris.uri_with(document.uri, path=str(d.module_path)),
23+
'range': {
24+
'start': {'line': d.line - 1, 'character': d.column},
25+
'end': {'line': d.line - 1, 'character': d.column + len(d.name)},
26+
}
2527
}
26-
}
27-
for d in definitions if d.is_definition() and _not_internal_definition(d)
28-
]
28+
for d in definitions if d.is_definition() and _not_internal_definition(d)
29+
]
2930

3031

3132
def _not_internal_definition(definition):

pylsp/plugins/flake8_lint.py

+52-51
Original file line numberDiff line numberDiff line change
@@ -30,57 +30,58 @@ def pylsp_settings():
3030

3131
@hookimpl
3232
def pylsp_lint(workspace, document):
33-
config = workspace._config
34-
settings = config.plugin_settings('flake8', document_path=document.path)
35-
log.debug("Got flake8 settings: %s", settings)
36-
37-
ignores = settings.get("ignore", [])
38-
per_file_ignores = settings.get("perFileIgnores")
39-
40-
if per_file_ignores:
41-
prev_file_pat = None
42-
for path in per_file_ignores:
43-
try:
44-
file_pat, errors = path.split(":")
45-
prev_file_pat = file_pat
46-
except ValueError:
47-
# It's legal to just specify another error type for the same
48-
# file pattern:
49-
if prev_file_pat is None:
50-
log.warning(
51-
"skipping a Per-file-ignore with no file pattern")
52-
continue
53-
file_pat = prev_file_pat
54-
errors = path
55-
if PurePath(document.path).match(file_pat):
56-
ignores.extend(errors.split(","))
57-
58-
opts = {
59-
'config': settings.get('config'),
60-
'exclude': settings.get('exclude'),
61-
'filename': settings.get('filename'),
62-
'hang-closing': settings.get('hangClosing'),
63-
'ignore': ignores or None,
64-
'max-complexity': settings.get('maxComplexity'),
65-
'max-line-length': settings.get('maxLineLength'),
66-
'indent-size': settings.get('indentSize'),
67-
'select': settings.get('select'),
68-
}
69-
70-
# flake takes only absolute path to the config. So we should check and
71-
# convert if necessary
72-
if opts.get('config') and not os.path.isabs(opts.get('config')):
73-
opts['config'] = os.path.abspath(os.path.expanduser(os.path.expandvars(
74-
opts.get('config')
75-
)))
76-
log.debug("using flake8 with config: %s", opts['config'])
77-
78-
# Call the flake8 utility then parse diagnostics from stdout
79-
flake8_executable = settings.get('executable', 'flake8')
80-
81-
args = build_args(opts)
82-
output = run_flake8(flake8_executable, args, document)
83-
return parse_stdout(document, output)
33+
with workspace.report_progress("lint: flake8"):
34+
config = workspace._config
35+
settings = config.plugin_settings('flake8', document_path=document.path)
36+
log.debug("Got flake8 settings: %s", settings)
37+
38+
ignores = settings.get("ignore", [])
39+
per_file_ignores = settings.get("perFileIgnores")
40+
41+
if per_file_ignores:
42+
prev_file_pat = None
43+
for path in per_file_ignores:
44+
try:
45+
file_pat, errors = path.split(":")
46+
prev_file_pat = file_pat
47+
except ValueError:
48+
# It's legal to just specify another error type for the same
49+
# file pattern:
50+
if prev_file_pat is None:
51+
log.warning(
52+
"skipping a Per-file-ignore with no file pattern")
53+
continue
54+
file_pat = prev_file_pat
55+
errors = path
56+
if PurePath(document.path).match(file_pat):
57+
ignores.extend(errors.split(","))
58+
59+
opts = {
60+
'config': settings.get('config'),
61+
'exclude': settings.get('exclude'),
62+
'filename': settings.get('filename'),
63+
'hang-closing': settings.get('hangClosing'),
64+
'ignore': ignores or None,
65+
'max-complexity': settings.get('maxComplexity'),
66+
'max-line-length': settings.get('maxLineLength'),
67+
'indent-size': settings.get('indentSize'),
68+
'select': settings.get('select'),
69+
}
70+
71+
# flake takes only absolute path to the config. So we should check and
72+
# convert if necessary
73+
if opts.get('config') and not os.path.isabs(opts.get('config')):
74+
opts['config'] = os.path.abspath(os.path.expanduser(os.path.expandvars(
75+
opts.get('config')
76+
)))
77+
log.debug("using flake8 with config: %s", opts['config'])
78+
79+
# Call the flake8 utility then parse diagnostics from stdout
80+
flake8_executable = settings.get('executable', 'flake8')
81+
82+
args = build_args(opts)
83+
output = run_flake8(flake8_executable, args, document)
84+
return parse_stdout(document, output)
8485

8586

8687
def run_flake8(flake8_executable, args, document):

pylsp/plugins/jedi_rename.py

+37-32
Original file line numberDiff line numberDiff line change
@@ -9,39 +9,44 @@
99

1010

1111
@hookimpl
12-
def pylsp_rename(config, workspace, document, position, new_name): # pylint: disable=unused-argument
13-
log.debug('Executing rename of %s to %s', document.word_at_position(position), new_name)
14-
kwargs = _utils.position_to_jedi_linecolumn(document, position)
15-
kwargs['new_name'] = new_name
16-
try:
17-
refactoring = document.jedi_script().rename(**kwargs)
18-
except NotImplementedError as exc:
19-
raise Exception('No support for renaming in Python 2/3.5 with Jedi. '
20-
'Consider using the rope_rename plugin instead') from exc
21-
log.debug('Finished rename: %s', refactoring.get_diff())
22-
changes = []
23-
for file_path, changed_file in refactoring.get_changed_files().items():
24-
uri = uris.from_fs_path(str(file_path))
25-
doc = workspace.get_maybe_document(uri)
26-
changes.append({
27-
'textDocument': {
28-
'uri': uri,
29-
'version': doc.version if doc else None
30-
},
31-
'edits': [
32-
{
33-
'range': {
34-
'start': {'line': 0, 'character': 0},
35-
'end': {
36-
'line': _num_lines(changed_file.get_new_code()),
37-
'character': 0,
12+
def pylsp_rename(config, workspace, document, position, new_name): # pylint: disable=unused-argument,too-many-locals
13+
with workspace.report_progress("rename", percentage=0) as report_progress:
14+
log.debug('Executing rename of %s to %s', document.word_at_position(position), new_name)
15+
kwargs = _utils.position_to_jedi_linecolumn(document, position)
16+
kwargs['new_name'] = new_name
17+
report_progress("refactoring")
18+
try:
19+
refactoring = document.jedi_script().rename(**kwargs)
20+
except NotImplementedError as exc:
21+
raise Exception('No support for renaming in Python 2/3.5 with Jedi. '
22+
'Consider using the rope_rename plugin instead') from exc
23+
log.debug('Finished rename: %s', refactoring.get_diff())
24+
changes = []
25+
26+
changed_files = refactoring.get_changed_files()
27+
for n, (file_path, changed_file) in enumerate(changed_files.items()):
28+
report_progress(changed_file, percentage=n/len(changed_files)*100)
29+
uri = uris.from_fs_path(str(file_path))
30+
doc = workspace.get_maybe_document(uri)
31+
changes.append({
32+
'textDocument': {
33+
'uri': uri,
34+
'version': doc.version if doc else None
35+
},
36+
'edits': [
37+
{
38+
'range': {
39+
'start': {'line': 0, 'character': 0},
40+
'end': {
41+
'line': _num_lines(changed_file.get_new_code()),
42+
'character': 0,
43+
},
3844
},
39-
},
40-
'newText': changed_file.get_new_code(),
41-
}
42-
],
43-
})
44-
return {'documentChanges': changes}
45+
'newText': changed_file.get_new_code(),
46+
}
47+
],
48+
})
49+
return {'documentChanges': changes}
4550

4651

4752
def _num_lines(file_contents):

pylsp/plugins/mccabe_lint.py

+28-27
Original file line numberDiff line numberDiff line change
@@ -13,30 +13,31 @@
1313

1414

1515
@hookimpl
16-
def pylsp_lint(config, document):
17-
threshold = config.plugin_settings('mccabe', document_path=document.path).get(THRESHOLD, DEFAULT_THRESHOLD)
18-
log.debug("Running mccabe lint with threshold: %s", threshold)
19-
20-
try:
21-
tree = compile(document.source, document.path, "exec", ast.PyCF_ONLY_AST)
22-
except SyntaxError:
23-
# We'll let the other linters point this one out
24-
return None
25-
26-
visitor = mccabe.PathGraphingAstVisitor()
27-
visitor.preorder(tree, visitor)
28-
29-
diags = []
30-
for graph in visitor.graphs.values():
31-
if graph.complexity() >= threshold:
32-
diags.append({
33-
'source': 'mccabe',
34-
'range': {
35-
'start': {'line': graph.lineno - 1, 'character': graph.column},
36-
'end': {'line': graph.lineno - 1, 'character': len(document.lines[graph.lineno])},
37-
},
38-
'message': 'Cyclomatic complexity too high: %s (threshold %s)' % (graph.complexity(), threshold),
39-
'severity': lsp.DiagnosticSeverity.Warning
40-
})
41-
42-
return diags
16+
def pylsp_lint(config, workspace, document):
17+
with workspace.report_progress("lint: mccabe"):
18+
threshold = config.plugin_settings('mccabe', document_path=document.path).get(THRESHOLD, DEFAULT_THRESHOLD)
19+
log.debug("Running mccabe lint with threshold: %s", threshold)
20+
21+
try:
22+
tree = compile(document.source, document.path, "exec", ast.PyCF_ONLY_AST)
23+
except SyntaxError:
24+
# We'll let the other linters point this one out
25+
return None
26+
27+
visitor = mccabe.PathGraphingAstVisitor()
28+
visitor.preorder(tree, visitor)
29+
30+
diags = []
31+
for graph in visitor.graphs.values():
32+
if graph.complexity() >= threshold:
33+
diags.append({
34+
'source': 'mccabe',
35+
'range': {
36+
'start': {'line': graph.lineno - 1, 'character': graph.column},
37+
'end': {'line': graph.lineno - 1, 'character': len(document.lines[graph.lineno])},
38+
},
39+
'message': 'Cyclomatic complexity too high: %s (threshold %s)' % (graph.complexity(), threshold),
40+
'severity': lsp.DiagnosticSeverity.Warning
41+
})
42+
43+
return diags

pylsp/plugins/pycodestyle_lint.py

+25-24
Original file line numberDiff line numberDiff line change
@@ -22,30 +22,31 @@
2222

2323
@hookimpl
2424
def pylsp_lint(workspace, document):
25-
config = workspace._config
26-
settings = config.plugin_settings('pycodestyle', document_path=document.path)
27-
log.debug("Got pycodestyle settings: %s", settings)
28-
29-
opts = {
30-
'exclude': settings.get('exclude'),
31-
'filename': settings.get('filename'),
32-
'hang_closing': settings.get('hangClosing'),
33-
'ignore': settings.get('ignore'),
34-
'max_line_length': settings.get('maxLineLength'),
35-
'indent_size': settings.get('indentSize'),
36-
'select': settings.get('select'),
37-
}
38-
kwargs = {k: v for k, v in opts.items() if v}
39-
styleguide = pycodestyle.StyleGuide(kwargs)
40-
41-
c = pycodestyle.Checker(
42-
filename=document.uri, lines=document.lines, options=styleguide.options,
43-
report=PyCodeStyleDiagnosticReport(styleguide.options)
44-
)
45-
c.check_all()
46-
diagnostics = c.report.diagnostics
47-
48-
return diagnostics
25+
with workspace.report_progress("lint: pycodestyle"):
26+
config = workspace._config
27+
settings = config.plugin_settings('pycodestyle', document_path=document.path)
28+
log.debug("Got pycodestyle settings: %s", settings)
29+
30+
opts = {
31+
'exclude': settings.get('exclude'),
32+
'filename': settings.get('filename'),
33+
'hang_closing': settings.get('hangClosing'),
34+
'ignore': settings.get('ignore'),
35+
'max_line_length': settings.get('maxLineLength'),
36+
'indent_size': settings.get('indentSize'),
37+
'select': settings.get('select'),
38+
}
39+
kwargs = {k: v for k, v in opts.items() if v}
40+
styleguide = pycodestyle.StyleGuide(kwargs)
41+
42+
c = pycodestyle.Checker(
43+
filename=document.uri, lines=document.lines, options=styleguide.options,
44+
report=PyCodeStyleDiagnosticReport(styleguide.options)
45+
)
46+
c.check_all()
47+
diagnostics = c.report.diagnostics
48+
49+
return diagnostics
4950

5051

5152
class PyCodeStyleDiagnosticReport(pycodestyle.BaseReport):

0 commit comments

Comments
 (0)