Skip to content

Commit 95d6b20

Browse files
committed
1 parent 7ef13b3 commit 95d6b20

File tree

2 files changed

+74
-5
lines changed

2 files changed

+74
-5
lines changed

jedi_language_server/jedi_utils.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,43 @@
3333
from .initialization_options import HoverDisableOptions, InitializationOptions
3434
from .type_map import get_lsp_completion_type, get_lsp_symbol_type
3535

36+
import functools
37+
import threading
38+
import inspect
39+
40+
from ast import PyCF_ONLY_AST
41+
42+
def debounce(interval_s, keyed_by=None):
43+
"""
44+
Debounce calls to this function until interval_s seconds have passed.
45+
Decorator copied from https://github.com/python-lsp/python-lsp-server
46+
"""
47+
def wrapper(func):
48+
timers = {}
49+
lock = threading.Lock()
50+
51+
@functools.wraps(func)
52+
def debounced(*args, **kwargs):
53+
sig = inspect.signature(func)
54+
call_args = sig.bind(*args, **kwargs)
55+
key = call_args.arguments[keyed_by] if keyed_by else None
56+
57+
def run():
58+
with lock:
59+
del timers[key]
60+
return func(*args, **kwargs)
61+
62+
with lock:
63+
old_timer = timers.get(key)
64+
if old_timer:
65+
old_timer.cancel()
66+
67+
timer = threading.Timer(interval_s, run)
68+
timers[key] = timer
69+
timer.start()
70+
return debounced
71+
return wrapper
72+
3673

3774
def _jedi_debug_function(
3875
color: str, # pylint: disable=unused-argument
@@ -238,6 +275,31 @@ def lsp_diagnostic(error: jedi.api.errors.SyntaxError) -> Diagnostic:
238275
)
239276

240277

278+
def lsp_python_diagnostic(uri: str, source: str) -> Diagnostic:
279+
"""Get LSP Diagnostic using the compile builtin."""
280+
try:
281+
compile(source, uri, "exec", PyCF_ONLY_AST)
282+
return None
283+
except SyntaxError as err:
284+
column, line = err.offset - 1, err.lineno - 1
285+
until_column = getattr(err, "end_offset", 0) - 1
286+
until_line = getattr(err, "end_lineno", 0) - 1
287+
288+
if (line, column) >= (until_line, until_column):
289+
until_column, until_line = column, line
290+
column = 0
291+
292+
return Diagnostic(
293+
range=Range(
294+
start=Position(line=line, character=column),
295+
end=Position(line=until_line, character=until_column),
296+
),
297+
message=err.__class__.__name__ + ': ' + str(err),
298+
severity=DiagnosticSeverity.Error,
299+
source="compile",
300+
)
301+
302+
241303
def line_column(position: Position) -> Tuple[int, int]:
242304
"""Translate pygls Position to Jedi's line/column.
243305

jedi_language_server/server.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -637,14 +637,21 @@ def did_change_configuration(
637637
# Static capability or initializeOptions functions that rely on a specific
638638
# client capability or user configuration. These are associated with
639639
# JediLanguageServer within JediLanguageServerProtocol.lsp_initialize
640+
@jedi_utils.debounce(1, keyed_by='uri')
640641
def _publish_diagnostics(server: JediLanguageServer, uri: str) -> None:
641642
"""Helper function to publish diagnostics for a file."""
642-
document = server.workspace.get_document(uri)
643-
jedi_script = jedi_utils.script(server.project, document)
644-
errors = jedi_script.get_syntax_errors()
645-
diagnostics = [jedi_utils.lsp_diagnostic(error) for error in errors]
646-
server.publish_diagnostics(uri, diagnostics)
643+
# The debounce decorator delays the execution by 1 second
644+
# canceling notifications that happen in that interval.
645+
# Since this function is executed after a delay, we need to check
646+
# whether the document still exists
647+
if not (uri in server.workspace.documents):
648+
return
647649

650+
doc = server.workspace.get_document(uri)
651+
diagnostic = jedi_utils.lsp_python_diagnostic(uri, doc.source)
652+
diagnostics = [diagnostic] if diagnostic else []
653+
654+
server.publish_diagnostics(uri, diagnostics)
648655

649656
# TEXT_DOCUMENT_DID_SAVE
650657
def did_save_diagnostics(

0 commit comments

Comments
 (0)