From 980f9548f69fc9060c252bd1a84e3a73f8342949 Mon Sep 17 00:00:00 2001 From: jwortmann Date: Tue, 12 Jul 2022 00:15:13 +0200 Subject: [PATCH 1/4] Don't use actual linebreaks in log panel if payload is string literal (#1993) --- Syntaxes/ServerLog.sublime-syntax | 2 ++ plugin/core/windows.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Syntaxes/ServerLog.sublime-syntax b/Syntaxes/ServerLog.sublime-syntax index 690e0b7cb..847c6b9b4 100644 --- a/Syntaxes/ServerLog.sublime-syntax +++ b/Syntaxes/ServerLog.sublime-syntax @@ -83,6 +83,8 @@ contexts: - match: $ pop: true - include: scope:source.python#constants # e.g. shutdown request + - include: scope:source.python#strings + - include: scope:source.python#numbers - include: scope:source.python#lists - include: scope:source.python#dictionaries-and-sets - match: '' diff --git a/plugin/core/windows.py b/plugin/core/windows.py index 67ee376c6..f1f764e36 100644 --- a/plugin/core/windows.py +++ b/plugin/core/windows.py @@ -506,7 +506,7 @@ def log(self, message: str, params: Any) -> None: def run_on_async_worker_thread() -> None: nonlocal message - params_str = str(params) + params_str = repr(params) if 0 < userprefs().log_max_size <= len(params_str): params_str = ''.format(len(params_str)) message = "{}: {}".format(message, params_str) From 418d9931e62e63d0484279995b79843dbdfc051d Mon Sep 17 00:00:00 2001 From: Tim Masliuchenko Date: Wed, 13 Jul 2022 19:41:15 +0100 Subject: [PATCH 2/4] Optionally fallback to goto_definition in lsp_symbol_definition (#1986) --- Default.sublime-keymap | 3 ++- docs/src/features.md | 3 +++ plugin/goto.py | 41 +++++++++++++++++++++++++++++++---------- 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/Default.sublime-keymap b/Default.sublime-keymap index 26e53b5fc..f3bc70585 100644 --- a/Default.sublime-keymap +++ b/Default.sublime-keymap @@ -171,7 +171,8 @@ // { // "command": "lsp_symbol_definition", // "args": { - // "side_by_side": false + // "side_by_side": false, + // "fallback": false // }, // "keys": [ // "f12" diff --git a/docs/src/features.md b/docs/src/features.md index d170149b8..258cc0d44 100644 --- a/docs/src/features.md +++ b/docs/src/features.md @@ -31,6 +31,9 @@ In addition to the basic "Goto Definition", the protocol also provides further r - Goto Declaration - Goto Implementation +Additionally, the LSP's "Goto Definition" command can fall back to the built-in Sublime's "Goto Definition" if the `fallback` argument is set to `true`. +This way, when there are no results found the built-in "Goto Definition" command will be triggered. + ## Find References [Example GIF 1](https://user-images.githubusercontent.com/6579999/128551752-b37fe407-148c-41cf-b1e4-6fe96ed0f77c.gif) diff --git a/plugin/goto.py b/plugin/goto.py index 43206405c..fca11f357 100644 --- a/plugin/goto.py +++ b/plugin/goto.py @@ -15,28 +15,41 @@ class LspGotoCommand(LspTextCommand): method = '' + fallback_command = '' - def is_enabled(self, event: Optional[dict] = None, point: Optional[int] = None, side_by_side: bool = False) -> bool: - return super().is_enabled(event, point) + def is_enabled( + self, + event: Optional[dict] = None, + point: Optional[int] = None, + side_by_side: bool = False, + fallback: bool = False + ) -> bool: + return fallback or super().is_enabled(event, point) def run( self, _: sublime.Edit, event: Optional[dict] = None, point: Optional[int] = None, - side_by_side: bool = False + side_by_side: bool = False, + fallback: bool = False ) -> None: session = self.best_session(self.capability) position = get_position(self.view, event, point) if session and position is not None: params = text_document_position_params(self.view, position) request = Request(self.method, params, self.view, progress=True) - session.send_request(request, functools.partial(self._handle_response_async, session, side_by_side)) + session.send_request( + request, functools.partial(self._handle_response_async, session, side_by_side, fallback) + ) + else: + self._handle_no_results(fallback, side_by_side) def _handle_response_async( self, session: Session, side_by_side: bool, + fallback: bool, response: Union[None, Location, List[Location], List[LocationLink]] ) -> None: if isinstance(response, dict): @@ -44,9 +57,7 @@ def _handle_response_async( open_location_async(session, response, side_by_side) elif isinstance(response, list): if len(response) == 0: - window = self.view.window() - if window: - window.status_message("No results found") + self._handle_no_results(fallback, side_by_side) elif len(response) == 1: self.view.run_command("add_jump_record", {"selection": [(r.a, r.b) for r in self.view.sel()]}) open_location_async(session, response[0], side_by_side) @@ -54,14 +65,24 @@ def _handle_response_async( self.view.run_command("add_jump_record", {"selection": [(r.a, r.b) for r in self.view.sel()]}) sublime.set_timeout(functools.partial(LocationPicker, self.view, session, response, side_by_side)) else: - window = self.view.window() - if window: - window.status_message("No results found") + self._handle_no_results(fallback, side_by_side) + + def _handle_no_results(self, fallback: bool = False, side_by_side: bool = False) -> None: + window = self.view.window() + + if not window: + return + + if fallback and self.fallback_command: + window.run_command(self.fallback_command, {"side_by_side": side_by_side}) + else: + window.status_message("No results found") class LspSymbolDefinitionCommand(LspGotoCommand): method = "textDocument/definition" capability = method_to_capability(method)[0] + fallback_command = "goto_definition" class LspSymbolTypeDefinitionCommand(LspGotoCommand): From e7ca542ef2352fe1db08be5267d82b220f4f6cf8 Mon Sep 17 00:00:00 2001 From: jwortmann Date: Wed, 13 Jul 2022 22:09:05 +0200 Subject: [PATCH 3/4] Keep active group when using Goto commands (#1994) Fixes #1990 --- Default.sublime-keymap | 10 +++++++--- plugin/core/open.py | 7 ++++--- plugin/goto.py | 10 ++++++---- plugin/locationpicker.py | 12 ++++++++++-- 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/Default.sublime-keymap b/Default.sublime-keymap index f3bc70585..6e4298d3a 100644 --- a/Default.sublime-keymap +++ b/Default.sublime-keymap @@ -172,6 +172,7 @@ // "command": "lsp_symbol_definition", // "args": { // "side_by_side": false, + // "force_group": true, // "fallback": false // }, // "keys": [ @@ -194,7 +195,8 @@ // { // "command": "lsp_symbol_type_definition", // "args": { - // "side_by_side": false + // "side_by_side": false, + // "force_group": true // }, // "keys": [ // "UNBOUND" @@ -216,7 +218,8 @@ // { // "command": "lsp_symbol_declaration", // "args": { - // "side_by_side": false + // "side_by_side": false, + // "force_group": true // }, // "keys": [ // "UNBOUND" @@ -238,7 +241,8 @@ // { // "command": "lsp_symbol_implementation", // "args": { - // "side_by_side": false + // "side_by_side": false, + // "force_group": true // }, // "keys": [ // "UNBOUND" diff --git a/plugin/core/open.py b/plugin/core/open.py index 3657b79b7..acec50673 100644 --- a/plugin/core/open.py +++ b/plugin/core/open.py @@ -28,9 +28,10 @@ def open_file( # to open as a separate view). view = window.find_open_file(file) if view: - opens_in_current_group = group == -1 or window.active_group() == group - opens_as_new_selection = (flags & (sublime.ADD_TO_SELECTION | sublime.REPLACE_MRU)) != 0 - return_existing_view = opens_in_current_group and not opens_as_new_selection + opens_in_desired_group = not bool(flags & sublime.FORCE_GROUP) or \ + window.get_view_index(view)[0] == window.active_group() + opens_in_side_by_side = bool(flags & (sublime.ADD_TO_SELECTION | sublime.REPLACE_MRU)) + return_existing_view = opens_in_desired_group and not opens_in_side_by_side if return_existing_view: return Promise.resolve(view) diff --git a/plugin/goto.py b/plugin/goto.py index fca11f357..d6c974502 100644 --- a/plugin/goto.py +++ b/plugin/goto.py @@ -22,6 +22,7 @@ def is_enabled( event: Optional[dict] = None, point: Optional[int] = None, side_by_side: bool = False, + force_group: bool = True, fallback: bool = False ) -> bool: return fallback or super().is_enabled(event, point) @@ -32,6 +33,7 @@ def run( event: Optional[dict] = None, point: Optional[int] = None, side_by_side: bool = False, + force_group: bool = True, fallback: bool = False ) -> None: session = self.best_session(self.capability) @@ -40,8 +42,7 @@ def run( params = text_document_position_params(self.view, position) request = Request(self.method, params, self.view, progress=True) session.send_request( - request, functools.partial(self._handle_response_async, session, side_by_side, fallback) - ) + request, functools.partial(self._handle_response_async, session, side_by_side, force_group, fallback)) else: self._handle_no_results(fallback, side_by_side) @@ -49,18 +50,19 @@ def _handle_response_async( self, session: Session, side_by_side: bool, + force_group: bool, fallback: bool, response: Union[None, Location, List[Location], List[LocationLink]] ) -> None: if isinstance(response, dict): self.view.run_command("add_jump_record", {"selection": [(r.a, r.b) for r in self.view.sel()]}) - open_location_async(session, response, side_by_side) + open_location_async(session, response, side_by_side, force_group) elif isinstance(response, list): if len(response) == 0: self._handle_no_results(fallback, side_by_side) elif len(response) == 1: self.view.run_command("add_jump_record", {"selection": [(r.a, r.b) for r in self.view.sel()]}) - open_location_async(session, response[0], side_by_side) + open_location_async(session, response[0], side_by_side, force_group) else: self.view.run_command("add_jump_record", {"selection": [(r.a, r.b) for r in self.view.sel()]}) sublime.set_timeout(functools.partial(LocationPicker, self.view, session, response, side_by_side)) diff --git a/plugin/locationpicker.py b/plugin/locationpicker.py index 8f03ea818..7b70a85e7 100644 --- a/plugin/locationpicker.py +++ b/plugin/locationpicker.py @@ -11,8 +11,15 @@ import weakref -def open_location_async(session: Session, location: Union[Location, LocationLink], side_by_side: bool) -> None: +def open_location_async( + session: Session, + location: Union[Location, LocationLink], + side_by_side: bool, + force_group: bool +) -> None: flags = sublime.ENCODED_POSITION + if force_group: + flags |= sublime.FORCE_GROUP if side_by_side: flags |= sublime.ADD_TO_SELECTION | sublime.SEMI_TRANSIENT @@ -80,7 +87,8 @@ def _select_entry(self, index: int) -> None: if not self._side_by_side: open_basic_file(session, uri, position, flags) else: - sublime.set_timeout_async(functools.partial(open_location_async, session, location, self._side_by_side)) + sublime.set_timeout_async( + functools.partial(open_location_async, session, location, self._side_by_side, True)) else: self._window.focus_view(self._view) # When in side-by-side mode close the current highlighted From 9fa725a757de4d955a89235fb6bd27609463d33f Mon Sep 17 00:00:00 2001 From: jwortmann Date: Wed, 13 Jul 2022 22:09:54 +0200 Subject: [PATCH 4/4] Allow plugins to modify server response messages (#1992) --- plugin/core/sessions.py | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/plugin/core/sessions.py b/plugin/core/sessions.py index f790e499f..cca3c8710 100644 --- a/plugin/core/sessions.py +++ b/plugin/core/sessions.py @@ -875,6 +875,17 @@ def on_pre_send_notification_async(self, notification: Notification) -> None: """ pass + def on_server_response_async(self, method: str, response: Response) -> None: + """ + Notifies about a response message that has been received from the language server. + Only successful responses are passed to this method. + + :param method: The method of the request. + :param response: The response object to the request. The response.result field can be modified by the + plugin, before it gets further handled by the LSP package. + """ + pass + def on_open_uri_async(self, uri: DocumentUri, callback: Callable[[str, str, str], None]) -> bool: """ Called when a language server reports to open an URI. If you know how to handle this URI, then return True and @@ -1898,10 +1909,12 @@ def deduce_payload( return res elif "id" in payload: response_id = int(payload["id"]) - handler, result, is_error = self.response_handler(response_id, payload) - response_tuple = (handler, result, None, None, None) + handler, method, result, is_error = self.response_handler(response_id, payload) self._logger.incoming_response(response_id, result, is_error) - return response_tuple + response = Response(response_id, result) + if self._plugin and not is_error: + self._plugin.on_server_response_async(method, response) # type: ignore + return handler, response.result, None, None, None else: debug("Unknown payload type: ", payload) return (None, None, None, None, None) @@ -1925,21 +1938,25 @@ def on_payload(self, payload: Dict[str, Any]) -> None: except Exception as err: exception_log("Error handling {}".format(typestr), err) - def response_handler(self, response_id: int, response: Dict[str, Any]) -> Tuple[Optional[Callable], Any, bool]: + def response_handler( + self, + response_id: int, + response: Dict[str, Any] + ) -> Tuple[Optional[Callable], Optional[str], Any, bool]: request, handler, error_handler = self._response_handlers.pop(response_id, (None, None, None)) if not request: error = {"code": ErrorCode.InvalidParams, "message": "unknown response ID {}".format(response_id)} - return (print_to_status_bar, error, True) + return (print_to_status_bar, None, error, True) self._invoke_views(request, "on_request_finished_async", response_id) if "result" in response and "error" not in response: - return (handler, response["result"], False) + return (handler, request.method, response["result"], False) if not error_handler: error_handler = print_to_status_bar if "result" not in response and "error" in response: error = response["error"] else: error = {"code": ErrorCode.InvalidParams, "message": "invalid response payload"} - return (error_handler, error, True) + return (error_handler, request.method, error, True) def _get_handler(self, method: str) -> Optional[Callable]: return getattr(self, method2attr(method), None)