diff --git a/Default.sublime-commands b/Default.sublime-commands index b1b0acab4..068e17fb0 100644 --- a/Default.sublime-commands +++ b/Default.sublime-commands @@ -57,6 +57,11 @@ "caption": "LSP: Format File", "command": "lsp_format_document", }, + { + "caption": "LSP: Format File With…", + "command": "lsp_format_document", + "args": {"select": true} + }, { "caption": "LSP: Format Selection", "command": "lsp_format_document_range", diff --git a/plugin/core/windows.py b/plugin/core/windows.py index 39481ea13..5ebebcc36 100644 --- a/plugin/core/windows.py +++ b/plugin/core/windows.py @@ -80,6 +80,8 @@ def __init__(self, window: sublime.Window, workspace: ProjectFolders, config_man self._server_log = [] # type: List[Tuple[str, str]] self.panel_manager = PanelManager(self._window) # type: Optional[PanelManager] self.tree_view_sheets = {} # type: Dict[str, TreeViewSheet] + self.formatters = {} # type: Dict[str, str] + self.suppress_sessions_restart_on_project_update = False self.total_error_count = 0 self.total_warning_count = 0 sublime.set_timeout(functools.partial(self._update_panel_main_thread, _NO_DIAGNOSTICS_PLACEHOLDER, [])) @@ -106,6 +108,9 @@ def on_load_project_async(self) -> None: self._config_manager.update() def on_post_save_project_async(self) -> None: + if self.suppress_sessions_restart_on_project_update: + self.suppress_sessions_restart_on_project_update = False + return self.on_load_project_async() def update_workspace_folders_async(self) -> None: diff --git a/plugin/formatting.py b/plugin/formatting.py index f6247c836..73287e98d 100644 --- a/plugin/formatting.py +++ b/plugin/formatting.py @@ -1,9 +1,11 @@ +from .core.collections import DottedDict from .core.edit import parse_text_edit from .core.promise import Promise from .core.protocol import Error from .core.protocol import TextDocumentSaveReason from .core.protocol import TextEdit from .core.registry import LspTextCommand +from .core.registry import windows from .core.sessions import Session from .core.settings import userprefs from .core.typing import Any, Callable, List, Optional, Iterator, Union @@ -15,14 +17,28 @@ from .core.views import text_document_ranges_formatting from .core.views import will_save_wait_until from .save_command import LspSaveCommand, SaveTask +from functools import partial import sublime FormatResponse = Union[List[TextEdit], None, Error] -def format_document(text_command: LspTextCommand) -> Promise[FormatResponse]: +def get_formatter(window: Optional[sublime.Window], base_scope: str) -> Optional[str]: + window_manager = windows.lookup(window) + if not window_manager: + return None + project_data = window_manager.window.project_data() + return DottedDict(project_data).get('settings.LSP.formatters.{}'.format(base_scope)) if \ + isinstance(project_data, dict) else window_manager.formatters.get(base_scope) + + +def format_document(text_command: LspTextCommand, formatter: Optional[str] = None) -> Promise[FormatResponse]: view = text_command.view + if formatter: + session = text_command.session_by_name(formatter, LspFormatDocumentCommand.capability) + if session: + return session.send_request_task(text_document_formatting(view)) session = text_command.best_session(LspFormatDocumentCommand.capability) if session: # Either use the documentFormattingProvider ... @@ -84,7 +100,9 @@ def is_applicable(cls, view: sublime.View) -> bool: def run_async(self) -> None: super().run_async() self._purge_changes_async() - format_document(self._task_runner).then(self._on_response) + base_scope = self._task_runner.view.syntax().scope + formatter = get_formatter(self._task_runner.view.window(), base_scope) + format_document(self._task_runner, formatter).then(self._on_response) def _on_response(self, response: FormatResponse) -> None: if response and not isinstance(response, Error) and not self._cancelled: @@ -100,16 +118,59 @@ class LspFormatDocumentCommand(LspTextCommand): capability = 'documentFormattingProvider' - def is_enabled(self, event: Optional[dict] = None, point: Optional[int] = None) -> bool: + def is_enabled(self, event: Optional[dict] = None, select: bool = False) -> bool: + if select: + return len(list(self.sessions(self.capability))) > 1 return super().is_enabled() or bool(self.best_session(LspFormatDocumentRangeCommand.capability)) - def run(self, edit: sublime.Edit, event: Optional[dict] = None) -> None: - format_document(self).then(self.on_result) + def run(self, edit: sublime.Edit, event: Optional[dict] = None, select: bool = False) -> None: + session_names = [session.config.name for session in self.sessions(self.capability)] + base_scope = self.view.syntax().scope + if select: + self.select_formatter(base_scope, session_names) + elif len(session_names) > 1: + formatter = get_formatter(self.view.window(), base_scope) + if formatter: + session = self.session_by_name(formatter, self.capability) + if session: + session.send_request_task(text_document_formatting(self.view)).then(self.on_result) + return + self.select_formatter(base_scope, session_names) + else: + format_document(self).then(self.on_result) def on_result(self, result: FormatResponse) -> None: if result and not isinstance(result, Error): apply_text_edits_to_view(result, self.view) + def select_formatter(self, base_scope: str, session_names: List[str]) -> None: + window = self.view.window() + if not window: + return + window.show_quick_panel( + session_names, partial(self.on_select_formatter, base_scope, session_names), placeholder="Select Formatter") + + def on_select_formatter(self, base_scope: str, session_names: List[str], index: int) -> None: + if index == -1: + return + session_name = session_names[index] + window_manager = windows.lookup(self.view.window()) + if window_manager: + window = window_manager.window + project_data = window.project_data() + if isinstance(project_data, dict): + project_settings = project_data.setdefault('settings', dict()) + project_lsp_settings = project_settings.setdefault('LSP', dict()) + project_formatter_settings = project_lsp_settings.setdefault('formatters', dict()) + project_formatter_settings[base_scope] = session_name + window_manager.suppress_sessions_restart_on_project_update = True + window.set_project_data(project_data) + else: # Save temporarily for this window + window_manager.formatters[base_scope] = session_name + session = self.session_by_name(session_name, self.capability) + if session: + session.send_request_task(text_document_formatting(self.view)).then(self.on_result) + class LspFormatDocumentRangeCommand(LspTextCommand): diff --git a/sublime-package.json b/sublime-package.json index db3c9faab..4d2b137da 100644 --- a/sublime-package.json +++ b/sublime-package.json @@ -780,6 +780,14 @@ "LSP": { "type": "object", "markdownDescription": "The dictionary of your configured language servers or overrides for existing configurations. The keys of this dictionary are free-form. They give a humany-friendly name to the server configuration. They are shown in the bottom-left corner in the status bar once attached to a view (unless you have `\"show_view_status\"` set to `false`).", + "properties": { + "formatters": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, "additionalProperties": { "$ref": "sublime://settings/LSP#/definitions/ClientConfig" }