Skip to content

Commit

Permalink
Merge branch 'main' into tdaniel22-close-diagnostics-on-save
Browse files Browse the repository at this point in the history
* main:
  Add support for triggerKind in code action requests (sublimelsp#2042)
  Custom context menu in log panel and "Clear log panel" item (sublimelsp#2045)
  Add icons and isPreferred support for code actions (sublimelsp#2040)
  Cut 1.18.0
  • Loading branch information
rchl committed Sep 7, 2022
2 parents a96e326 + d8bfe62 commit 4006ffb
Show file tree
Hide file tree
Showing 18 changed files with 221 additions and 100 deletions.
18 changes: 18 additions & 0 deletions Context LSP Log Panel.sublime-menu
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[
{
"command": "lsp_clear_log_panel",
"caption": "Clear Log Panel"
},
{
"caption": "-"
},
{
"command": "copy"
},
{
"caption": "-"
},
{
"command": "select_all"
},
]
2 changes: 1 addition & 1 deletion LSP.sublime-settings
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@

// Log communication from and to language servers.
// Set to an array of values:
// - "panel" - log to the LSP Language Servers output panel
// - "panel" - log to the LSP Log Panel
// - "remote" - start a local websocket server on port 9981. Can be connected to with
// a websocket client to receive the log messages in real time.
// For backward-compatibility, when set to "true", enables the "panel" logger and when
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.17.0
1.18.0
3 changes: 2 additions & 1 deletion boot.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@
from .plugin.core.logging import exception_log
from .plugin.core.open import opening_files
from .plugin.core.panels import destroy_output_panels
from .plugin.core.panels import LspClearLogPanelCommand
from .plugin.core.panels import LspClearPanelCommand
from .plugin.core.panels import LspUpdatePanelCommand
from .plugin.core.panels import LspUpdateServerPanelCommand
from .plugin.core.panels import LspUpdateLogPanelCommand
from .plugin.core.panels import WindowPanelListener
from .plugin.core.protocol import Error
from .plugin.core.protocol import Location
Expand Down
1 change: 1 addition & 0 deletions messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"1.16.2": "messages/1.16.2.txt",
"1.16.3": "messages/1.16.3.txt",
"1.17.0": "messages/1.17.0.txt",
"1.18.0": "messages/1.18.0.txt",
"1.2.7": "messages/1.2.7.txt",
"1.2.8": "messages/1.2.8.txt",
"1.2.9": "messages/1.2.9.txt",
Expand Down
16 changes: 16 additions & 0 deletions messages/1.18.0.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
=> 1.18.0

# Features and Fixes

- Implement inlay hints (#2018) (predragnikolic & jwortmann) (documentation: https://lsp.sublimetext.io/features/#inlay-hints)
- Add option to highlight hover range (#2030) (jwortmann)
- Optionally fallback to goto_reference in lsp_symbol_references (#2029) (timfjord)
- Delay re-calculation of code lenses and inlay hints for currently not visible views (#2025) (jwortmann)
- Improve strategy for semantic highlighting requests (#2020) (jwortmann)
- Follow global settings more accurately whether to show snippet completions (#2017) (jwortmann)
- docs: Add ruby steep language server (#2019) (jalkoby)
- docs: Update F# guidance (#2011) (baronfel)

# API changes

- Define overridable methods in LspExecuteCommand (#2024) (rchl)
45 changes: 26 additions & 19 deletions plugin/code_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@
from .core.registry import windows
from .core.sessions import SessionBufferProtocol
from .core.settings import userprefs
from .core.typing import Any, List, Dict, Callable, Optional, Tuple, Union, Sequence
from .core.typing import Any, List, Dict, Callable, Optional, Tuple, Union
from .core.views import entire_content_region
from .core.views import first_selection_region
from .core.views import format_code_actions_for_quick_panel
from .core.views import text_document_code_action_params
from .save_command import LspSaveCommand, SaveTask
import sublime
Expand Down Expand Up @@ -81,15 +82,16 @@ def request_for_region_async(
self,
view: sublime.View,
region: sublime.Region,
session_buffer_diagnostics: Sequence[Tuple[SessionBufferProtocol, Sequence[Diagnostic]]],
session_buffer_diagnostics: List[Tuple[SessionBufferProtocol, List[Diagnostic]]],
actions_handler: Callable[[CodeActionsByConfigName], None],
only_kinds: Optional[Dict[str, bool]] = None
only_kinds: Optional[Dict[str, bool]] = None,
manual: bool = False,
) -> None:
"""
Requests code actions with provided diagnostics and specified region. If there are
no diagnostics for given session, the request will be made with empty diagnostics list.
"""
self._request_async(view, region, session_buffer_diagnostics, False, actions_handler, only_kinds)
self._request_async(view, region, session_buffer_diagnostics, False, actions_handler, only_kinds, manual)

def request_on_save(
self,
Expand All @@ -105,19 +107,21 @@ def request_on_save(
return
region = entire_content_region(view)
session_buffer_diagnostics, _ = listener.diagnostics_intersecting_region_async(region)
self._request_async(view, region, session_buffer_diagnostics, False, actions_handler, on_save_actions)
self._request_async(
view, region, session_buffer_diagnostics, False, actions_handler, on_save_actions, manual=False)

def _request_async(
self,
view: sublime.View,
region: sublime.Region,
session_buffer_diagnostics: Sequence[Tuple[SessionBufferProtocol, Sequence[Diagnostic]]],
session_buffer_diagnostics: List[Tuple[SessionBufferProtocol, List[Diagnostic]]],
only_with_diagnostics: bool,
actions_handler: Callable[[CodeActionsByConfigName], None],
on_save_actions: Optional[Dict[str, bool]] = None
on_save_actions: Optional[Dict[str, bool]] = None,
manual: bool = False,
) -> None:
location_cache_key = None
use_cache = on_save_actions is None
use_cache = on_save_actions is None and not manual
if use_cache:
location_cache_key = "{}#{}:{}:{}".format(
view.buffer_id(), view.change_count(), region, only_with_diagnostics)
Expand All @@ -128,13 +132,12 @@ def _request_async(
return
else:
self._response_cache = None

collector = CodeActionsCollector(actions_handler)
with collector:
listener = windows.listener_for_view(view)
if listener:
for session in listener.sessions_async('codeActionProvider'):
diagnostics = [] # type: Sequence[Diagnostic]
diagnostics = [] # type: List[Diagnostic]
for sb, diags in session_buffer_diagnostics:
if sb.session == session:
diagnostics = diags
Expand All @@ -143,14 +146,14 @@ def _request_async(
supported_kinds = session.get_capability('codeActionProvider.codeActionKinds')
matching_kinds = get_matching_kinds(on_save_actions, supported_kinds or [])
if matching_kinds:
params = text_document_code_action_params(view, region, diagnostics, matching_kinds)
params = text_document_code_action_params(view, region, diagnostics, matching_kinds, manual)
request = Request.codeAction(params, view)
session.send_request_async(
request, *filtering_collector(session.config.name, matching_kinds, collector))
else:
if only_with_diagnostics and not diagnostics:
continue
params = text_document_code_action_params(view, region, diagnostics)
params = text_document_code_action_params(view, region, diagnostics, None, manual)
request = Request.codeAction(params, view)
session.send_request_async(request, collector.create_collector(session.config.name))
if location_cache_key:
Expand Down Expand Up @@ -274,7 +277,7 @@ def run(
only_kinds: Optional[List[str]] = None,
commands_by_config: Optional[CodeActionsByConfigName] = None
) -> None:
self.commands = [] # type: List[Tuple[str, str, CodeActionOrCommand]]
self.commands = [] # type: List[Tuple[str, CodeActionOrCommand]]
self.commands_by_config = {} # type: CodeActionsByConfigName
if commands_by_config:
self.handle_responses_async(commands_by_config, run_first=True)
Expand All @@ -289,7 +292,7 @@ def run(
session_buffer_diagnostics, covering = listener.diagnostics_intersecting_async(region)
dict_kinds = {kind: True for kind in only_kinds} if only_kinds else None
actions_manager.request_for_region_async(
view, covering, session_buffer_diagnostics, self.handle_responses_async, dict_kinds)
view, covering, session_buffer_diagnostics, self.handle_responses_async, dict_kinds, manual=True)

def handle_responses_async(self, responses: CodeActionsByConfigName, run_first: bool = False) -> None:
self.commands_by_config = responses
Expand All @@ -299,19 +302,23 @@ def handle_responses_async(self, responses: CodeActionsByConfigName, run_first:
else:
self.show_code_actions()

def combine_commands(self) -> 'List[Tuple[str, str, CodeActionOrCommand]]':
def combine_commands(self) -> 'List[Tuple[str, CodeActionOrCommand]]':
results = []
for config, commands in self.commands_by_config.items():
for command in commands:
results.append((config, command['title'], command))
results.append((config, command))
return results

def show_code_actions(self) -> None:
if len(self.commands) > 0:
items = [command[1] for command in self.commands]
window = self.view.window()
if window:
window.show_quick_panel(items, self.handle_select, placeholder="Code action")
items, selected_index = format_code_actions_for_quick_panel([command[1] for command in self.commands])
window.show_quick_panel(
items,
self.handle_select,
selected_index=selected_index,
placeholder="Code action")
else:
self.view.show_popup('No actions available', sublime.HIDE_ON_MOUSE_MOVE_AWAY)

Expand All @@ -323,7 +330,7 @@ def run_async() -> None:
session = self.session_by_name(selected[0])
if session:
name = session.config.name
session.run_code_action_async(selected[2], progress=True).then(
session.run_code_action_async(selected[1], progress=True).then(
lambda resp: self.handle_response_async(name, resp))

sublime.set_timeout_async(run_async)
Expand Down
75 changes: 45 additions & 30 deletions plugin/core/panels.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@


# about 80 chars per line implies maintaining a buffer of about 40kb per window
SERVER_PANEL_MAX_LINES = 500
LOG_PANEL_MAX_LINES = 500

OUTPUT_PANEL_SETTINGS = {
"auto_indent": False,
Expand All @@ -32,7 +32,7 @@ class PanelName:
Diagnostics = "diagnostics"
References = "references"
Rename = "rename"
LanguageServers = "language servers"
Log = "LSP Log Panel"


@contextmanager
Expand Down Expand Up @@ -66,13 +66,13 @@ def on_pre_close_window(self, window: sublime.Window) -> None:

def on_window_command(self, window: sublime.Window, command_name: str, args: Dict) -> None:
if command_name in ('show_panel', 'hide_panel'):
sublime.set_timeout(lambda: self.maybe_update_server_panel(window))
sublime.set_timeout(lambda: self.maybe_update_log_panel(window))

def maybe_update_server_panel(self, window: sublime.Window) -> None:
if is_server_panel_open(window):
panel = ensure_server_panel(window)
def maybe_update_log_panel(self, window: sublime.Window) -> None:
if is_log_panel_open(window):
panel = ensure_log_panel(window)
if panel:
update_server_panel(panel, window.id())
update_log_panel(panel, window.id())


def create_output_panel(window: sublime.Window, name: str) -> Optional[sublime.View]:
Expand All @@ -93,27 +93,31 @@ def destroy_output_panels(window: sublime.Window) -> None:


def create_panel(window: sublime.Window, name: str, result_file_regex: str, result_line_regex: str,
syntax: str) -> Optional[sublime.View]:
syntax: str, context_menu: Optional[str] = None) -> Optional[sublime.View]:
panel = create_output_panel(window, name)
if not panel:
return None
settings = panel.settings()
if result_file_regex:
panel.settings().set("result_file_regex", result_file_regex)
settings.set("result_file_regex", result_file_regex)
if result_line_regex:
panel.settings().set("result_line_regex", result_line_regex)
settings.set("result_line_regex", result_line_regex)
if context_menu:
settings.set("context_menu", context_menu)
panel.assign_syntax(syntax)
# Call create_output_panel a second time after assigning the above
# settings, so that it'll be picked up as a result buffer
# see: Packages/Default/exec.py#L228-L230
# Call create_output_panel a second time after assigning the above settings, so that it'll be picked up
# as a result buffer. See: Packages/Default/exec.py#L228-L230
panel = window.create_output_panel(name)
# All our panels are read-only
panel.set_read_only(True)
if panel:
# All our panels are read-only
panel.set_read_only(True)
return panel


def ensure_panel(window: sublime.Window, name: str, result_file_regex: str, result_line_regex: str,
syntax: str) -> Optional[sublime.View]:
return window.find_output_panel(name) or create_panel(window, name, result_file_regex, result_line_regex, syntax)
syntax: str, context_menu: Optional[str] = None) -> Optional[sublime.View]:
return window.find_output_panel(name) or \
create_panel(window, name, result_file_regex, result_line_regex, syntax, context_menu)


class LspClearPanelCommand(sublime_plugin.TextCommand):
Expand Down Expand Up @@ -143,12 +147,13 @@ def run(self, edit: sublime.Edit, characters: Optional[str] = "") -> None:
clear_undo_stack(self.view)


def ensure_server_panel(window: sublime.Window) -> Optional[sublime.View]:
return ensure_panel(window, PanelName.LanguageServers, "", "", "Packages/LSP/Syntaxes/ServerLog.sublime-syntax")
def ensure_log_panel(window: sublime.Window) -> Optional[sublime.View]:
return ensure_panel(window, PanelName.Log, "", "", "Packages/LSP/Syntaxes/ServerLog.sublime-syntax",
"Context LSP Log Panel.sublime-menu")


def is_server_panel_open(window: sublime.Window) -> bool:
return window.is_valid() and window.active_panel() == "output.{}".format(PanelName.LanguageServers)
def is_log_panel_open(window: sublime.Window) -> bool:
return window.is_valid() and window.active_panel() == "output.{}".format(PanelName.Log)


def log_server_message(window: sublime.Window, prefix: str, message: str) -> None:
Expand All @@ -157,19 +162,19 @@ def log_server_message(window: sublime.Window, prefix: str, message: str) -> Non
return
WindowPanelListener.server_log_map[window_id].append((prefix, message))
list_len = len(WindowPanelListener.server_log_map[window_id])
if list_len >= SERVER_PANEL_MAX_LINES:
if list_len >= LOG_PANEL_MAX_LINES:
# Trim leading items in the list, leaving only the max allowed count.
del WindowPanelListener.server_log_map[window_id][:list_len - SERVER_PANEL_MAX_LINES]
panel = ensure_server_panel(window)
if is_server_panel_open(window) and panel:
update_server_panel(panel, window_id)
del WindowPanelListener.server_log_map[window_id][:list_len - LOG_PANEL_MAX_LINES]
panel = ensure_log_panel(window)
if is_log_panel_open(window) and panel:
update_log_panel(panel, window_id)


def update_server_panel(panel: sublime.View, window_id: int) -> None:
panel.run_command("lsp_update_server_panel", {"window_id": window_id})
def update_log_panel(panel: sublime.View, window_id: int) -> None:
panel.run_command("lsp_update_log_panel", {"window_id": window_id})


class LspUpdateServerPanelCommand(sublime_plugin.TextCommand):
class LspUpdateLogPanelCommand(sublime_plugin.TextCommand):

def run(self, edit: sublime.Edit, window_id: int) -> None:
to_process = WindowPanelListener.server_log_map.get(window_id) or []
Expand All @@ -183,11 +188,21 @@ def run(self, edit: sublime.Edit, window_id: int) -> None:
self.view.insert(edit, self.view.size(), ''.join(new_lines))
last_region_end = 0 # Starting from point 0 in the panel ...
total_lines, _ = self.view.rowcol(self.view.size())
for _ in range(0, max(0, total_lines - SERVER_PANEL_MAX_LINES)):
for _ in range(0, max(0, total_lines - LOG_PANEL_MAX_LINES)):
# ... collect all regions that span an entire line ...
region = self.view.full_line(last_region_end)
last_region_end = region.b
erase_region = sublime.Region(0, last_region_end)
if not erase_region.empty():
self.view.erase(edit, erase_region)
clear_undo_stack(self.view)


class LspClearLogPanelCommand(sublime_plugin.TextCommand):
def run(self, edit: sublime.Edit) -> None:
window = self.view.window()
if not window:
return
panel = ensure_log_panel(window)
if panel:
panel.run_command("lsp_clear_panel")
Loading

0 comments on commit 4006ffb

Please sign in to comment.