Skip to content

Commit

Permalink
Fix some LSP server return wrong diagnostics, such as rust-analyzer.
Browse files Browse the repository at this point in the history
  • Loading branch information
manateelazycat committed Jun 28, 2023
1 parent e0f623a commit 0b01f18
Showing 1 changed file with 52 additions and 45 deletions.
97 changes: 52 additions & 45 deletions core/fileaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def create_file_action_with_single_server(filepath, single_server_info, single_s
if get_from_path_dict(FILE_ACTION_DICT, filepath).single_server != single_server:
logger.warn("File {} is opened by different lsp server.".format(filepath))
return

action = FileAction(filepath, single_server_info, single_server, None, None, external_file_link)
add_to_path_dict(FILE_ACTION_DICT, filepath, action)
return action
Expand All @@ -53,31 +53,31 @@ def __init__(self, filepath, single_server_info, single_server, multi_servers_in
self.single_server: LspServer = single_server
self.multi_servers = multi_servers
self.multi_servers_info = multi_servers_info

self.code_actions = {}
self.code_action_counter = 0

self.completion_item_resolve_key = None
self.completion_items = {}

self.diagnostics = {}
self.diagnostics_ticker = 0

self.external_file_link = external_file_link
self.filepath = filepath

self.last_change_cursor_time = -1.0
self.last_change_file_time = -1.0

self.request_dict = {}

self.try_completion_timer = None

self.version = 1

self.org_file = os.path.splitext(filepath)[-1] == '.org'
self.org_line_bias = None

# We need multiple servers to handle org files
self.org_lang_servers = {}
self.org_server_infos = {}
Expand All @@ -90,9 +90,9 @@ def __init__(self, filepath, single_server_info, single_server, multi_servers_in
logger.debug("Handlers: " + pprint.pformat(Handler.__subclasses__()))
for handler_cls in Handler.__subclasses__():
self.handlers[handler_cls.name] = handler_cls(self)
(self.enable_auto_import,
self.completion_items_limit,

(self.enable_auto_import,
self.completion_items_limit,
self.insert_spaces,
self.enable_push_diagnostics,
self.push_diagnostic_idle,
Expand Down Expand Up @@ -155,21 +155,21 @@ def call(self, method, *args, **kwargs):
method_server_names = self.multi_servers_info[method]
else:
method_server_names = [self.multi_servers_info[method]]

for method_server_name in method_server_names:
method_server = self.multi_servers[method_server_name]
self.send_request(method_server, method, handler, *args, **kwargs)
elif hasattr(self, method):
getattr(self, method)(*args, **kwargs)

def send_request(self, method_server, method, handler, *args, **kwargs):
if hasattr(handler, "provider"):
if getattr(method_server, getattr(handler, "provider")):
self.send_server_request(method_server, method, *args, **kwargs)
self.send_server_request(method_server, method, *args, **kwargs)
elif hasattr(handler, "provider_message"):
message_emacs(getattr(handler, "provider_message"))
else:
self.send_server_request(method_server, method, *args, **kwargs)
self.send_server_request(method_server, method, *args, **kwargs)

def change_file(self, start, end, range_length, change_text, position, before_char, buffer_name, prefix):
if self.org_file:
Expand Down Expand Up @@ -204,7 +204,7 @@ def change_file(self, start, end, range_length, change_text, position, before_ch
delay = 0 if is_running_in_server() else 0.1
self.try_completion_timer = threading.Timer(delay, lambda : self.try_completion(position, before_char, prefix))
self.try_completion_timer.start()

def update_file(self, buffer_name, org_line_bias=None):
self.org_line_bias = org_line_bias
buffer_content = get_buffer_content(self.filepath, buffer_name)
Expand All @@ -219,7 +219,7 @@ def try_completion(self, position, before_char, prefix):
self.send_server_request(lsp_server, "completion", lsp_server, position, before_char, prefix)
else:
self.send_server_request(self.single_server, "completion", self.single_server, position, before_char, prefix)

def change_cursor(self, position):
# Record change cursor time.
self.last_change_cursor_time = time.time()
Expand Down Expand Up @@ -255,7 +255,7 @@ def list_diagnostics(self):
message_emacs("No diagnostics found.")
else:
eval_in_emacs("lsp-bridge-diagnostic--list", self.get_diagnostics())

def sort_diagnostic(self, diagnostic_a, diagnostic_b):
score_a = [diagnostic_a["range"]["start"]["line"],
diagnostic_a["range"]["start"]["character"],
Expand All @@ -265,28 +265,37 @@ def sort_diagnostic(self, diagnostic_a, diagnostic_b):
diagnostic_b["range"]["start"]["character"],
diagnostic_b["range"]["end"]["line"],
diagnostic_b["range"]["end"]["character"]]

if score_a < score_b:
return -1
elif score_a > score_b:
return 1
else:
return 0


def fix_diagnostic(self, diagnostics):
if self.single_server_info and self.single_server_info["name"] == "rust-analyzer":
diagnostics = list(filter(lambda d: d["source"] == "rustc", diagnostics))

return diagnostics

def record_diagnostics(self, diagnostics, server_name):
log_time("Record diagnostics from '{}' for file {}".format(server_name, os.path.basename(self.filepath)))

# Fix some LSP server return wrong diagnostics, such as rust-analyzer.
diagnostics = self.fix_diagnostic(diagnostics)

# Record diagnostics data that push from LSP server.
import functools
self.diagnostics[server_name] = sorted(diagnostics, key=functools.cmp_to_key(self.sort_diagnostic))
self.diagnostics_ticker += 1
self.diagnostics_ticker += 1

# Try to push diagnostics to Emacs.
if self.enable_push_diagnostics:
push_diagnostic_ticker = self.diagnostics_ticker
push_diagnostic_timer = threading.Timer(self.push_diagnostic_idle, lambda : self.try_push_diagnostics(push_diagnostic_ticker))
push_diagnostic_timer.start()

def try_push_diagnostics(self, ticker):
# Only push diagnostics to Emacs when ticker is newest.
# Drop all temporarily diagnostics when typing.
Expand Down Expand Up @@ -341,25 +350,25 @@ def send_code_action_request(self, lsp_server, range_start, range_end, action_ki
def save_file(self, buffer_name):
for lsp_server in self.get_lsp_servers():
lsp_server.send_did_save_notification(self.filepath, buffer_name)

def completion_item_resolve(self, item_key, server_name):
if server_name in self.completion_items:
self.completion_item_resolve_key = item_key

if item_key in self.completion_items[server_name]:

if self.multi_servers:
method_server = self.multi_servers[server_name]
else:
method_server = self.single_server

if method_server.completion_resolve_provider:
self.send_server_request(method_server, "completion_item_resolve", item_key, server_name, self.completion_items[server_name][item_key])
else:
item = self.completion_items[server_name][item_key]

self.completion_item_update(
item_key,
item_key,
server_name,
item["documentation"] if "documentation" in item else "",
item["additionalTextEdits"] if "additionalTextEdits" in item else "")
Expand All @@ -372,7 +381,7 @@ def completion_item_update(self, item_key, server_name, documentation, additiona
if type(documentation) == dict:
if "kind" in documentation:
documentation = documentation["value"]

eval_in_emacs("lsp-bridge-completion-item--update",
{
"filepath": self.filepath,
Expand All @@ -390,12 +399,12 @@ def rename_file(self, old_filepath, new_filepath):

def send_server_request(self, lsp_server, handler_name, *args, **kwargs):
handler: Handler = self.method_handlers[lsp_server.server_info["name"]][handler_name]

handler.latest_request_id = request_id = generate_request_id()
handler.last_change = self.last_change

lsp_server.record_request_id(request_id, handler)

params = handler.process_request(*args, **kwargs)
if handler.send_document_uri:
params["textDocument"] = {"uri": lsp_server.parse_document_uri(self.filepath, self.external_file_link)}
Expand All @@ -404,42 +413,40 @@ def send_server_request(self, lsp_server, handler_name, *args, **kwargs):
method=handler.method,
params=params,
request_id=request_id)

def exit(self):
for lsp_server in (self.org_lang_servers.values() if self.org_file else self.get_lsp_servers()):
if lsp_server.server_name in LSP_SERVER_DICT:
lsp_server = LSP_SERVER_DICT[lsp_server.server_name]
lsp_server.close_file(self.filepath)

# Clean FILE_ACTION_DICT after close file.
remove_from_path_dict(FILE_ACTION_DICT, self.filepath)

def get_lsp_servers(self):
return self.multi_servers.values() if self.multi_servers else [self.single_server]

def get_lsp_server_names(self):
return list(map(lambda lsp_server: lsp_server.server_info["name"], self.get_lsp_servers()))

def get_lsp_server_project_path(self):
return self.single_server.project_path.encode('utf-8')

def get_match_lsp_servers(self, method):
if self.multi_servers:
server_names = self.multi_servers_info[method] # type: ignore
if type(server_names) == str:
server_names = [server_names]

return list(map(lambda server_name: self.multi_servers[server_name], server_names)) # type: ignore
else:
return [self.single_server]

def create_external_file_action(self, external_file, external_file_link=None):
if self.multi_servers:
create_file_action_with_multi_servers(external_file, self.multi_servers_info, self.multi_servers, external_file_link)
else:
create_file_action_with_single_server(external_file, self.single_server_info, self.single_server, external_file_link)

FILE_ACTION_DICT: Dict[str, FileAction] = {} # use for contain file action
LSP_SERVER_DICT: Dict[str, LspServer] = {} # use for contain lsp server


1 comment on commit 0b01f18

@manateelazycat
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ruff also has similar problem, like this: astral-sh/ruff#2571

Please sign in to comment.