├── .python-version ├── plugin ├── core │ ├── __init__.py │ ├── version.py │ ├── rpc.py │ ├── css.py │ ├── typing.py │ ├── logging.py │ ├── message_request_handler.py │ ├── paths.py │ ├── file_watcher.py │ ├── progress.py │ ├── url.py │ ├── edit.py │ ├── active_request.py │ ├── diagnostics_storage.py │ ├── panels.py │ ├── configurations.py │ ├── collections.py │ ├── workspace.py │ └── open.py ├── __init__.py ├── document_link.py ├── diagnostics.py ├── color.py ├── configuration.py ├── selection_range.py ├── execute_command.py ├── panels.py ├── goto.py ├── save_command.py ├── semantic_highlighting.py ├── edit.py └── code_lens.py ├── icons ├── info.png ├── error.png ├── error@2x.png ├── error@3x.png ├── info@2x.png ├── info@3x.png ├── lightbulb.png ├── warning.png ├── warning@2x.png ├── warning@3x.png ├── lightbulb@2x.png ├── lightbulb@3x.png ├── lightbulb_colored.png └── LICENSE ├── messages ├── 1.16.1.txt ├── 1.12.1.txt ├── 1.10.0.txt ├── 1.10.1.txt ├── 2.5.0.txt ├── 1.3.1.txt ├── 1.2.9.txt ├── 2.6.0.txt ├── 2.1.0.txt ├── 1.3.0.txt ├── 1.2.8.txt ├── 2.2.0.txt ├── 1.2.7.txt ├── 1.12.0.txt ├── 1.16.2.txt ├── 1.28.0.txt ├── 1.9.0.txt ├── 1.16.3.txt ├── 1.18.0.txt ├── 1.11.0.txt ├── install.txt ├── 1.30.0.txt ├── 1.25.0.txt ├── 1.16.0.txt ├── 1.6.1.txt ├── 1.8.0.txt ├── 1.17.0.txt ├── 1.29.0.txt ├── 1.20.0.txt ├── 1.26.0.txt ├── 1.6.0.txt ├── 1.14.0.txt ├── 2.0.0.txt ├── 1.4.0.txt ├── 2.7.0.txt ├── 1.22.0.txt ├── 1.27.0.txt ├── 1.24.0.txt ├── 1.15.0.txt ├── 1.13.0.txt ├── 1.21.0.txt ├── 2.4.0.txt ├── 1.7.0.txt ├── 2.3.0.txt ├── 1.19.0.txt ├── 1.23.0.txt └── 1.5.0.txt ├── third_party ├── __init__.py └── websocket_server │ ├── __init__.py │ └── LICENSE ├── pyrightconfig.json ├── .gitignore ├── netlify.toml ├── dependencies.json ├── sheets.css ├── inlay_hints.css ├── notification.css ├── annotations.css ├── ColorSchemes ├── Breakers.sublime-color-scheme ├── Celeste.sublime-color-scheme ├── Mariana.sublime-color-scheme ├── Monokai.sublime-color-scheme └── Sixteen.sublime-color-scheme ├── .gitattributes ├── Context LSP Log Panel.sublime-menu ├── lsp_utils.sublime-settings ├── LICENSE ├── Syntaxes ├── References.sublime-syntax ├── Diagnostics.sublime-syntax └── ServerLog.sublime-syntax ├── messages.json ├── Context.sublime-menu ├── popups.css ├── CONTRIBUTING.md ├── README.md └── Default.sublime-commands /.python-version: -------------------------------------------------------------------------------- 1 | 3.8 2 | -------------------------------------------------------------------------------- /plugin/core/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plugin/core/version.py: -------------------------------------------------------------------------------- 1 | __version__ = (2, 7, 0) 2 | -------------------------------------------------------------------------------- /icons/info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sublimelsp/LSP/HEAD/icons/info.png -------------------------------------------------------------------------------- /icons/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sublimelsp/LSP/HEAD/icons/error.png -------------------------------------------------------------------------------- /icons/error@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sublimelsp/LSP/HEAD/icons/error@2x.png -------------------------------------------------------------------------------- /icons/error@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sublimelsp/LSP/HEAD/icons/error@3x.png -------------------------------------------------------------------------------- /icons/info@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sublimelsp/LSP/HEAD/icons/info@2x.png -------------------------------------------------------------------------------- /icons/info@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sublimelsp/LSP/HEAD/icons/info@3x.png -------------------------------------------------------------------------------- /icons/lightbulb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sublimelsp/LSP/HEAD/icons/lightbulb.png -------------------------------------------------------------------------------- /icons/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sublimelsp/LSP/HEAD/icons/warning.png -------------------------------------------------------------------------------- /icons/warning@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sublimelsp/LSP/HEAD/icons/warning@2x.png -------------------------------------------------------------------------------- /icons/warning@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sublimelsp/LSP/HEAD/icons/warning@3x.png -------------------------------------------------------------------------------- /icons/lightbulb@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sublimelsp/LSP/HEAD/icons/lightbulb@2x.png -------------------------------------------------------------------------------- /icons/lightbulb@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sublimelsp/LSP/HEAD/icons/lightbulb@3x.png -------------------------------------------------------------------------------- /icons/lightbulb_colored.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sublimelsp/LSP/HEAD/icons/lightbulb_colored.png -------------------------------------------------------------------------------- /messages/1.16.1.txt: -------------------------------------------------------------------------------- 1 | => 1.16.1 2 | 3 | # Features and Fixes 4 | 5 | - Fix missing logs in the logging panel (Raoul Wols) 6 | -------------------------------------------------------------------------------- /third_party/__init__.py: -------------------------------------------------------------------------------- 1 | from .websocket_server import WebsocketServer 2 | 3 | __all__ = [ 4 | 'WebsocketServer' 5 | ] 6 | -------------------------------------------------------------------------------- /third_party/websocket_server/__init__.py: -------------------------------------------------------------------------------- 1 | from .websocket_server import WebsocketServer 2 | 3 | __all__ = [ 4 | 'WebsocketServer' 5 | ] -------------------------------------------------------------------------------- /messages/1.12.1.txt: -------------------------------------------------------------------------------- 1 | => 1.12.1 2 | 3 | # Fixes 4 | 5 | - Revert "Don't allow old nested dict style keys anymore (#1865)" due to regression. Pending better fix. 6 | -------------------------------------------------------------------------------- /messages/1.10.0.txt: -------------------------------------------------------------------------------- 1 | => 1.10.0 2 | 3 | # Features 4 | 5 | - Add additional_formatting_options plugin API for adjusting formatting options (#1832) (Rafał Chłodnicki) 6 | -------------------------------------------------------------------------------- /pyrightconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "pythonVersion": "3.11", 3 | "reportMissingModuleSource": "none", 4 | "reportIncompatibleMethodOverride": "none", 5 | "stubPath": "./stubs", 6 | } 7 | -------------------------------------------------------------------------------- /plugin/core/rpc.py: -------------------------------------------------------------------------------- 1 | # only used for LSP-* packages out in the wild that now import from "private" modules 2 | # TODO: Announce removal and remove this import 3 | from .sessions import method2attr # noqa 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | LSP.sublime-workspace 2 | LSP.sublime-project 3 | .DS_Store 4 | .mypy_cache 5 | .pytest_cache 6 | .tox 7 | .ropeproject 8 | *.pyc 9 | .coverage 10 | mypycov 11 | htmlcov 12 | docs/site 13 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | # https://docs.netlify.com/configure-builds/file-based-configuration 2 | 3 | [build] 4 | environment = { PYTHON_VERSION = "3.8" } 5 | base = "docs/" 6 | publish = "site/" 7 | command = "make build" 8 | -------------------------------------------------------------------------------- /dependencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "*": { 3 | ">=4096": [ 4 | "bracex", 5 | "mdpopups", 6 | "orjson", 7 | "typing_extensions", 8 | "wcmatch" 9 | ] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /messages/1.10.1.txt: -------------------------------------------------------------------------------- 1 | # New Client Configurations 2 | 3 | - Add instructions for F# (FSharpAutoComplete) (Evgeniy Andreev) 4 | 5 | # Features and Fixes 6 | 7 | - Revert "Always clear undo stack after a mutation on any panel (#1824)" (Raoul Wols) 8 | -------------------------------------------------------------------------------- /messages/2.5.0.txt: -------------------------------------------------------------------------------- 1 | => 2.5.0 2 | 3 | # Features 4 | 5 | - Add selector API in AbstractPlugin (#2620) (Rafał Chłodnicki) 6 | 7 | # Fixes 8 | 9 | - skip diagnostics pull if diagnostics already received (#2623) (Rafał Chłodnicki) 10 | 11 | # Enhancements 12 | 13 | - Expose publicly Manager.get_session (#2624) (Rafał Chłodnicki) 14 | -------------------------------------------------------------------------------- /sheets.css: -------------------------------------------------------------------------------- 1 | .lsp_sheet { 2 | padding: 1rem; 3 | } 4 | .lsp_sheet h1 { 5 | margin-top: 0; 6 | font-family: system; 7 | } 8 | .lsp_sheet h2, 9 | .lsp_sheet h3, 10 | .lsp_sheet h4, 11 | .lsp_sheet p { 12 | margin-top: 1rem; 13 | font-family: system; 14 | } 15 | .lsp_sheet li { 16 | font-family: system; 17 | } 18 | -------------------------------------------------------------------------------- /messages/1.3.1.txt: -------------------------------------------------------------------------------- 1 | => 1.3.1 2 | 3 | # Features and Fixes 4 | 5 | - Disable crashed servers in-memory when they crash on start (Rafal Chlodnicki) 6 | - Documentation: pyls is now called pylsp (see https://lsp.sublimetext.io) (Gavin S) 7 | - Resolve completions completion item before applying additional text edits (#1651) (Предраг Николић / Predrag Nikolic) 8 | -------------------------------------------------------------------------------- /inlay_hints.css: -------------------------------------------------------------------------------- 1 | .inlay-hint { 2 | color: color(var(--foreground) alpha(0.6)); 3 | background-color: color(var(--foreground) alpha(0.08)); 4 | border-radius: 4px; 5 | padding: 0.05em 4px; 6 | font-size: 0.9em; 7 | } 8 | 9 | .inlay-hint a { 10 | color: color(var(--foreground) alpha(0.6)); 11 | text-decoration: none; 12 | } 13 | -------------------------------------------------------------------------------- /messages/1.2.9.txt: -------------------------------------------------------------------------------- 1 | => 1.2.9 2 | 3 | Fixes & Features: 4 | 5 | - Fix "go-to" actions with side-by-side flag after a breaking change in ST 4096 (#1563) (Rafał Chłodnicki) 6 | - Look up the sig help triggers via the buffer capabilities (Raoul Wols) 7 | - Always look up the session name for lsp_execute (#1557) (Raoul Wols) 8 | - Extend disabled_capabilities with more options (#1555) (Predrag Nikolic) 9 | -------------------------------------------------------------------------------- /notification.css: -------------------------------------------------------------------------------- 1 | .notification { 2 | margin: 0.5rem; 3 | padding: 1rem; 4 | } 5 | 6 | .notification h2 { 7 | margin-bottom: 1rem; 8 | } 9 | 10 | .notification .actions { 11 | margin-top: 2rem; 12 | } 13 | 14 | .notification .actions a { 15 | border: 2px solid color(var(--foreground) alpha(0.25)); 16 | color: var(--foreground); 17 | padding: 0.5rem; 18 | text-decoration: none; 19 | } 20 | -------------------------------------------------------------------------------- /annotations.css: -------------------------------------------------------------------------------- 1 | .lsp_annotation { 2 | margin: 0; 3 | border-width: 0; 4 | } 5 | .lsp_annotation .errors { 6 | color: color(var(--redish) alpha(0.85)); 7 | } 8 | .lsp_annotation .warnings { 9 | color: color(var(--yellowish) alpha(0.85)); 10 | } 11 | .lsp_annotation .info { 12 | color: color(var(--bluish) alpha(0.85)); 13 | } 14 | .lsp_annotation .hints { 15 | color: color(var(--bluish) alpha(0.85)); 16 | } 17 | -------------------------------------------------------------------------------- /messages/2.6.0.txt: -------------------------------------------------------------------------------- 1 | => 2.6.0 2 | 3 | # Features 4 | 5 | - Allow to suppress opening new view on open URI with plugin (#2640) (jwortmann) 6 | 7 | # Enhancements 8 | 9 | - respect `refactoring_auto_save` in more cases (#2643) (Rafał Chłodnicki) 10 | - Send 'erb' as language id for ERB files (#2642) (Guillermo) 11 | - Limit length of text in completion popup details pane (#2636) (jwortmann) 12 | - Don't show error dialog for failed workspace command (#2634) (jwortmann) 13 | -------------------------------------------------------------------------------- /ColorSchemes/Breakers.sublime-color-scheme: -------------------------------------------------------------------------------- 1 | { 2 | "rules": [ 3 | { 4 | "scope": "markup.highlight", 5 | "background": "color(var(grey2) alpha(0.2))" 6 | }, 7 | { 8 | "scope": "markup.highlight.hover", 9 | "background": "color(var(blue2) alpha(0.15))" 10 | }, 11 | { 12 | "scope": "meta.semantic-token", 13 | "background": "#00000001" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /ColorSchemes/Celeste.sublime-color-scheme: -------------------------------------------------------------------------------- 1 | { 2 | "rules": [ 3 | { 4 | "scope": "markup.highlight", 5 | "background": "color(var(dark_gray) alpha(0.1))" 6 | }, 7 | { 8 | "scope": "markup.highlight.hover", 9 | "background": "color(var(teal) alpha(0.15))" 10 | }, 11 | { 12 | "scope": "meta.semantic-token", 13 | "background": "#00000001" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /ColorSchemes/Mariana.sublime-color-scheme: -------------------------------------------------------------------------------- 1 | { 2 | "rules": [ 3 | { 4 | "scope": "markup.highlight", 5 | "background": "color(var(white3) alpha(0.15))" 6 | }, 7 | { 8 | "scope": "markup.highlight.hover", 9 | "background": "color(var(blue5) alpha(0.15))" 10 | }, 11 | { 12 | "scope": "meta.semantic-token", 13 | "background": "#00000001" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /ColorSchemes/Monokai.sublime-color-scheme: -------------------------------------------------------------------------------- 1 | { 2 | "rules": [ 3 | { 4 | "scope": "markup.highlight", 5 | "background": "color(var(white3) alpha(0.15))" 6 | }, 7 | { 8 | "scope": "markup.highlight.hover", 9 | "background": "color(var(blue) alpha(0.15))" 10 | }, 11 | { 12 | "scope": "meta.semantic-token", 13 | "background": "#00000001" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /ColorSchemes/Sixteen.sublime-color-scheme: -------------------------------------------------------------------------------- 1 | { 2 | "rules": [ 3 | { 4 | "scope": "markup.highlight", 5 | "background": "color(var(grey2) alpha(0.2))" 6 | }, 7 | { 8 | "scope": "markup.highlight.hover", 9 | "background": "color(var(blue2) alpha(0.15))" 10 | }, 11 | { 12 | "scope": "meta.semantic-token", 13 | "background": "#00000001" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /messages/2.1.0.txt: -------------------------------------------------------------------------------- 1 | => 2.1.0 2 | 3 | # Features 4 | 5 | - Add `refactoring_auto_save` setting to save modified files after applying a refactoring (#2433) (Janos Wortmann) 6 | 7 | # Fixes and Improvements 8 | 9 | - Respect semantic token capability when dynamically registered (#2453) (Rafał Chłodnicki) 10 | - Use caret for point to look up symbol (#2440) (Troels Bjørnskov) 11 | - Various typing improvements (#2446, #2450, #2456, #2458, #2460, #2459) (Janos Wortmann, Jack Cherng, deathaxe) -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | .github/ export-ignore 2 | .style.yapf export-ignore 3 | codecov.yml export-ignore 4 | docs/ export-ignore 5 | icons/convert.bat export-ignore 6 | icons/*.svg export-ignore 7 | mypy.ini export-ignore 8 | renovate.json export-ignore 9 | stubs/ export-ignore 10 | tests/ export-ignore 11 | tox.ini export-ignore 12 | unittesting.json export-ignore 13 | 14 | # Release script 15 | .release.json export-ignore 16 | mkdocs.yml export-ignore 17 | scripts export-ignore 18 | VERSION export-ignore 19 | -------------------------------------------------------------------------------- /Context LSP Log Panel.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "Clear", 4 | "command": "lsp_clear_log_panel", 5 | }, 6 | { 7 | "caption": "Limit to 500 Lines", 8 | "command": "lsp_toggle_log_panel_lines_limit", 9 | "checkbox": true 10 | }, 11 | { 12 | "caption": "-" 13 | }, 14 | { 15 | "command": "copy" 16 | }, 17 | { 18 | "caption": "-" 19 | }, 20 | { 21 | "command": "select_all" 22 | }, 23 | ] 24 | -------------------------------------------------------------------------------- /messages/1.3.0.txt: -------------------------------------------------------------------------------- 1 | => 1.3.0 2 | 3 | # Features and Fixes 4 | 5 | - Add plugin APIs: "on_*_capability_async" and "on_session_end_async" (#1638) (Rafał Chłodnicki) 6 | - Lazily update log panel when it's opened (#1636) (Rafał Chłodnicki) 7 | - Various improvements to the server troubleshooting functionality (Rafał Chłodnicki) 8 | - Disable crashed servers in-memory instead of in project data (#1647) (Rafał Chłodnicki) 9 | - Handle all cases where selection is absent thus resulting in a crash (#1646) (Rafał Chłodnicki) 10 | -------------------------------------------------------------------------------- /messages/1.2.8.txt: -------------------------------------------------------------------------------- 1 | => 1.2.8 2 | 3 | Fixes & Features: 4 | 5 | - Fix #1548: when the region is empty, query touching points (Raoul Wols) 6 | - Add support for old plist package (DeathAxe) 7 | - Handle basic editor commands (#1545) (Raoul Wols) 8 | - Implement client-initiated progress reporting (#1535) (Raoul Wols) 9 | - Add more documentation for issues 1529 and 1530 (#1531) (JeremyBois) 10 | - Symbol Tag support, and use QuickPanelItem for workspace symbols (#1541) (Predrag Nikolic) 11 | - make hr less noticeable (Predrag Nikolic) 12 | -------------------------------------------------------------------------------- /messages/2.2.0.txt: -------------------------------------------------------------------------------- 1 | => 2.2.0 2 | 3 | # Features 4 | 5 | - feat: Add `"show_diagnostics_in_hover"` setting (#2489) (@HJX-001) 6 | 7 | # Fixes 8 | 9 | * fix: Unexpected keyword argument 'event' when using mouse keys (2492) (@deathaxe) 10 | - fix: Enum issues (#2484, #2487) (Janos Wortmann) 11 | - fix: Remove logging.basicConfig() and avoid polluting other packages (#2478) (Предраг Николић) 12 | 13 | # Documentation 14 | 15 | - docs: Add racket-langserver (#2481) (omentic) 16 | 17 | # API changes 18 | - Allow plugins to listen for server notification messages (#2496) (Предраг Николић) 19 | -------------------------------------------------------------------------------- /messages/1.2.7.txt: -------------------------------------------------------------------------------- 1 | => 1.2.7 2 | 3 | Fixes & Features: 4 | - Add A File Icon alias scopes to language-ids (DeathAxe) 5 | - Erase diagnostics when the SessionView dies (Raoul Wols) 6 | - Rename auto_show_diagnostics_panel_level to show_diagnostics_panel_on_save (Raoul Wols) 7 | - Rethink the "next/prev diagnostic" (#1533) (Raoul Wols) 8 | As was mentioned in release 1.2.6, the commands lsp_next_diagnostic (F8) and 9 | lsp_previous_diagnostic (shift+F8) are now removed in favor of 10 | the built-in next_result (F4) and prev_result (shift+F4). 11 | Using F4, the hover popup will show diagnostics. 12 | -------------------------------------------------------------------------------- /messages/1.12.0.txt: -------------------------------------------------------------------------------- 1 | => 1.12.0 2 | 3 | # Features and Fixes 4 | 5 | - Add a comment to guide new users how to change the keybind of a command (#1866) (RUSshy) 6 | - Don't allow old nested dict style keys anymore (#1865) (Raoul Wols) 7 | This fixes an issue for LSP-yaml where you couldn't create a mapping 8 | from schema URIs to file patterns. It is now possible to use the 9 | "yaml.schemas" setting. 10 | - [D] dls is not maintained anymore, serve-d took over (RUSshy) 11 | - Add a couple of types here and there (#1862) (Rafał Chłodnicki) 12 | - Support multiple patterns in file_watcher client configuration (#1859) (Rafał Chłodnicki) 13 | -------------------------------------------------------------------------------- /messages/1.16.2.txt: -------------------------------------------------------------------------------- 1 | => 1.16.2 2 | 3 | # Features and Fixes 4 | 5 | - Fix LspShowScopeNameCommand for the new ContextStackFrame type (#1961) (Raoul Wols) 6 | - Skip empty stderr messages, don't break on them (Shane Moore) 7 | - Guard for overflow of the column offset when applying edits (#1952) (Raoul Wols) 8 | - Fix "side by side go-to" if file is already open (#1950) (Rafał Chłodnicki) 9 | - doc: Update Go instructions (Lucas Alber) 10 | - doc: Update documentation for LSP-ltex-ls (Lucas Alber) 11 | - doc: Add documentation for quick-lint-js language server (Matthew "strager" Glazar) 12 | - doc: Reorganize documentation for JavaScript/TypeScript (Matthew "strager" Glazar) 13 | -------------------------------------------------------------------------------- /messages/1.28.0.txt: -------------------------------------------------------------------------------- 1 | => 1.28.0 2 | 3 | ⚠️⚠️⚠️ 4 | To ensure that everything works properly after LSP package is updated, 5 | it's strongly recommended to restart Sublime Text once it finishes updating all packages. 6 | ⚠️⚠️⚠️ 7 | 8 | # New features 9 | 10 | - Support setting cursor position in text edits (https://github.com/sublimelsp/LSP/pull/2389) (Rafał Chłodnicki) 11 | 12 | # Fixes and Improvements 13 | 14 | - Include lsp_utils settings in LSP (https://github.com/sublimelsp/LSP/pull/2395) (Rafał Chłodnicki) 15 | - Add `stimulus-lsp` to docs (https://github.com/sublimelsp/LSP/pull/2391) (Óscar Carretero) 16 | - Add `asm-lsp` to docs (https://github.com/sublimelsp/LSP/pull/2400) (Will Lillis) 17 | -------------------------------------------------------------------------------- /messages/1.9.0.txt: -------------------------------------------------------------------------------- 1 | => 1.9.0 2 | 3 | # Fixes and Features 4 | 5 | - Fix typo in release.py (Brenton Bostick) 6 | - Make code lenses have the same behavior for both variants (#1825) (Raoul Wols) 7 | - Advertise that we only do "adjust indentation" for completions (Raoul Wols) 8 | - Fix inconsistency in indent adjustment behavior when completing (Raoul Wols) 9 | - Update client configuration docs (Raoul Wols) 10 | - Always clear undo stack after a mutation on any panel (#1824) (Raoul Wols) 11 | - Implement labelDetailsSupport (#1827) (Raoul Wols) 12 | - Strip carriage returns in completion insertions as well (Raoul Wols) 13 | - Pass ignore patterns obtained from ST to the file watcher (#1823) (Rafał Chłodnicki) 14 | -------------------------------------------------------------------------------- /messages/1.16.3.txt: -------------------------------------------------------------------------------- 1 | => 1.16.3 2 | 3 | # Features and Fixes 4 | 5 | - Add support for textDocument/documentLink request (#1974) (jwortmann) 6 | - Don't expose disabled code actions (Rafal Chlodnicki) 7 | - Check for : and / while updating nested dict structures in DottedDict (Raoul Wols) 8 | - docs: add info about enabling clangd server (Ilia) 9 | - Fix diagnostic regions being hidden by semantic regions (#1969) (Rafał Chłodnicki) 10 | - Initialize diagnostic tagged regions after non-tagged ones (Rafal Chlodnicki) 11 | - Follow global setting whether to show snippet completions (Janos Wortmann) 12 | - Fix academicmarkdown language id (Lucas Alber) 13 | - Fix WSL path URI decoding under Windows (#1962) (Jack Cherng) 14 | -------------------------------------------------------------------------------- /messages/1.18.0.txt: -------------------------------------------------------------------------------- 1 | => 1.18.0 2 | 3 | # Features and Fixes 4 | 5 | - Implement inlay hints (#2018) (predragnikolic & jwortmann) (documentation: https://lsp.sublimetext.io/features/#inlay-hints) 6 | - Add option to highlight hover range (#2030) (jwortmann) 7 | - Optionally fallback to goto_reference in lsp_symbol_references (#2029) (timfjord) 8 | - Delay re-calculation of code lenses and inlay hints for currently not visible views (#2025) (jwortmann) 9 | - Improve strategy for semantic highlighting requests (#2020) (jwortmann) 10 | - Follow global settings more accurately whether to show snippet completions (#2017) (jwortmann) 11 | - docs: Add ruby steep language server (#2019) (jalkoby) 12 | - docs: Update F# guidance (#2011) (baronfel) 13 | 14 | # API changes 15 | 16 | - Define overridable methods in LspExecuteCommand (#2024) (rchl) 17 | -------------------------------------------------------------------------------- /messages/1.11.0.txt: -------------------------------------------------------------------------------- 1 | => 1.11.0 2 | 3 | # New Helper Packages 4 | 5 | - LSP-volar: https://packagecontrol.io/packages/LSP-volar 6 | - LSP-Bicep: https://packagecontrol.io/packages/LSP-Bicep 7 | - LSP-rust-analyzer: https://packagecontrol.io/packages/LSP-rust-analyzer 8 | 9 | # Fixes and Features 10 | 11 | - Generalize implementation of the transport (#1847) (Rafał Chłodnicki) 12 | - use config_name, because session.config.name can change (Предраг Николић) 13 | - Remove obsolete dependencies (#1853) (deathaxe) 14 | - Ensure lsp_save actually runs when context is active for keybinding (#1852) (jwortmann) 15 | - Ensure the process is killed if it didn't exit within 1 second (Rafal Chlodnicki) 16 | - Send hover request to all sessions that supports hoverProvider (#1845) (Предраг Николић) 17 | - Clear undo stack on every panel mutation (#1841) (Raoul Wols) 18 | -------------------------------------------------------------------------------- /messages/install.txt: -------------------------------------------------------------------------------- 1 | Thanks for trying out the LSP package! 2 | ====================================== 3 | 4 | 5 | This package is a "client" implementation of the "language server protocol". 6 | This is a protocol that defines how (language) servers and clients (editors) 7 | should talk to each other. It enables high-quality code-complete, 8 | signature-help, goto-def, accurate find-references, and more. 9 | 10 | This package doesn't actually contain any language server by itself. It 11 | provides only the glue code to attach language servers to Sublime Text. 12 | 13 | Exactly which concepts from LSP should map to which concepts from Sublime 14 | Text and vice versa is an ongoing research problem. We're certainly still 15 | figuring things out! 16 | 17 | To get up and running for your favorite language, open the documentation at 18 | 19 | https://lsp.sublimetext.io 20 | -------------------------------------------------------------------------------- /messages/1.30.0.txt: -------------------------------------------------------------------------------- 1 | => 1.30.0 2 | 3 | # Breaking changes 4 | 5 | - [godot-editor](https://lsp.sublimetext.io/language_servers/#gdscript-godot-engine) client configuration default `tcp_port` value is now `6005` instead of `6008`. Older versions of Godot(3.x) use port `6008`. (#2447) (Предраг Николић) 6 | 7 | # Fixes and Improvements 8 | 9 | - Keep tab selection and focus when applying WorkspaceEdit (#2431) (Janos Wortmann) 10 | - Enhancements for the rename panel (#2428) (Janos Wortmann) 11 | - Fix URI format for res scheme (#2432) (Janos Wortmann) 12 | - If `"log_debug"` is enabled, `window/logMessage` will be printed to the Sublime Text Console instead of LSP log panel (#2444) (Janos Wortmann) 13 | 14 | # Documentation 15 | 16 | - docs: Add Toit (#2425) (Serjan Nasredin) 17 | - docs: Add Solidity (#2383) (Nikita Kozlov) 18 | 19 | # Plugin API changes 20 | 21 | - Allow plugins to ignore certain views (#2410) (Janos Wortmann) 22 | -------------------------------------------------------------------------------- /lsp_utils.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | // Specifies the type and priority of the Node.js installation that should be used for Node.js-based servers. 3 | // The allowed values are: 4 | // - 'system' - a Node.js runtime found on the PATH 5 | // - 'local' - a Node.js runtime managed by LSP that doesn't affect the system 6 | // The order in which the values are specified determines which one is tried first, 7 | // with the later one being used as a fallback. 8 | // You can also specify just a single value to disable the fallback. 9 | "nodejs_runtime": ["system", "local"], 10 | // Uses Node.js runtime from the Electron package rather than the official distribution. This has the benefit of 11 | // lower memory usage due to it having the pointer compression (https://v8.dev/blog/pointer-compression) enabled. 12 | // Only relevant when using `local` variant of `nodejs_runtime`. 13 | "local_use_electron": false, 14 | } 15 | -------------------------------------------------------------------------------- /plugin/core/css.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | import sublime 3 | 4 | 5 | class CSS: 6 | def __init__(self) -> None: 7 | self.popups = sublime.load_resource("Packages/LSP/popups.css") 8 | self.popups_classname = "lsp_popup" 9 | self.notification = sublime.load_resource("Packages/LSP/notification.css") 10 | self.notification_classname = "notification" 11 | self.sheets = sublime.load_resource("Packages/LSP/sheets.css") 12 | self.sheets_classname = "lsp_sheet" 13 | self.inlay_hints = sublime.load_resource("Packages/LSP/inlay_hints.css") 14 | self.annotations = sublime.load_resource("Packages/LSP/annotations.css") 15 | self.annotations_classname = "lsp_annotation" 16 | 17 | 18 | _css: CSS | None = None 19 | 20 | 21 | def load() -> None: 22 | global _css 23 | _css = CSS() 24 | 25 | 26 | def css() -> CSS: 27 | global _css 28 | assert _css is not None 29 | return _css 30 | -------------------------------------------------------------------------------- /messages/1.25.0.txt: -------------------------------------------------------------------------------- 1 | => 1.25.0 2 | 3 | ⚠️ To ensure that everything works properly after LSP package is updated, it's strongly recommended to restart 4 | Sublime Text once it finishes updating all packages. ⚠️ 5 | 6 | # New features 7 | 8 | - Add option to show diagnostics as annotations (#1702) (Rafał Chłodnicki) 9 | - Add argument "include_declaration" to "lsp_symbol_references" (#2275) (Magnus Karlsson) 10 | 11 | # Fixes 12 | 13 | - Fix rare KeyError (Janos Wortmann) 14 | - fix "Error rewriting command" warning triggered on startup (#2277) (Rafał Chłodnicki) 15 | - fix crash on checking excluded folders with missing project data (#2276) (Rafał Chłodnicki) 16 | - Fix tagged diagnostics flickering on document changes (#2274) (Rafał Chłodnicki) 17 | 18 | # Improvements 19 | 20 | - Show server crashed dialog on unexpected output in server's stdout (Rafal Chlodnicki) 21 | - Only do a single pass on running code actions on save (#2283) (Rafał Chłodnicki) 22 | - Take font style of sighelp active parameter from color scheme (#2279) (jwortmann) 23 | -------------------------------------------------------------------------------- /messages/1.16.0.txt: -------------------------------------------------------------------------------- 1 | => 1.16.0 2 | 3 | # Features and Fixes 4 | 5 | - Remove registration of the legacy LanguageHandler class (#1936) (Raoul Wols) 6 | - Don't use emoji for deprecated completion items icon (#1942) (Janos Wortmann) 7 | - Document the command for showing signature help (#1938) (Seph Soliman) 8 | - Fix "disable server globally" command for LSP packages (#1907) (Предраг Николић) 9 | - Lowercase keys for initializing semantic token regions (Janos Wortmann) 10 | - Show popup with code actions when hovering over lightbulb icon (#1929) (Janos Wortmann) 11 | - Remove more usage of uri_to_filename (#1796) (Raoul Wols) 12 | - Prevent semantic tokens delta request after previous error response (Janos Wortmann) 13 | - Announce in initialize that we augment syntax tokens (Janos Wortmann) 14 | - Ensure code actions lightbulb icon is on top (#1928) (Janos Wortmann) 15 | - Ensure view gets registered when tab is moved to create new window (#1927) (Janos Wortmann) 16 | - Fix lsp_show_scope_name command not working if view was dragged to new window (Janos Wortmann) 17 | -------------------------------------------------------------------------------- /plugin/core/typing.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | import sys 3 | from enum import Enum, IntEnum, IntFlag # noqa: F401 4 | from typing import ( # noqa: F401 5 | IO, 6 | TYPE_CHECKING, 7 | Any, 8 | Callable, 9 | Deque, 10 | Dict, 11 | Generator, 12 | Generic, 13 | Iterable, 14 | Iterator, 15 | List, 16 | Literal, 17 | Mapping, 18 | Optional, 19 | Protocol, 20 | Sequence, 21 | Set, 22 | Tuple, 23 | Type, 24 | TypedDict, 25 | TypeVar, 26 | Union, 27 | cast, 28 | final, 29 | ) 30 | from typing_extensions import ( # noqa: F401 31 | NotRequired, 32 | ParamSpec, 33 | Required, 34 | TypeGuard, 35 | ) 36 | 37 | if sys.version_info >= (3, 11): 38 | from enum import StrEnum # noqa: F401 39 | else: 40 | class StrEnum(str, Enum): 41 | """ 42 | Naive polyfill for Python 3.11's StrEnum. 43 | 44 | See https://docs.python.org/3.11/library/enum.html#enum.StrEnum 45 | """ 46 | 47 | __format__ = str.__format__ 48 | __str__ = str.__str__ 49 | -------------------------------------------------------------------------------- /messages/1.6.1.txt: -------------------------------------------------------------------------------- 1 | => 1.6.1 2 | 3 | # Features and Fixes 4 | 5 | - Add back stippled option for document highlights (#1748) (Janos Wortmann) 6 | - Add command to restart a specific server (#1733) (@husanjun) 7 | You can restart specific servers now with "LSP: Restart Servers". 8 | Note that this changed the name of the command; if you had it bound to a 9 | key binding you should update the key binding. 10 | - When a helper package cannot start the server, continue handling listeners (#1716) (Raoul Wols) 11 | Fixes a subtle bug where if one language server didn't meet the startup 12 | requirements, other queued servers wouldn't be handled any more. 13 | - Special-case env.PATH extending to prepend to the original PATH (#1752) (Rafał Chłodnicki) 14 | In preparation for fixing a subtle bug for helper packages that rely on Node.js 15 | When you've chosen to use a locally-intalled node local to ST, then any 16 | possible subprocess spawned by the language server should use *that* local 17 | node executable as well. 18 | The same problem is also present for pip-based helper packages like LSP-pylsp. 19 | -------------------------------------------------------------------------------- /messages/1.8.0.txt: -------------------------------------------------------------------------------- 1 | => 1.8.0 2 | 3 | # New Client Configurations 4 | 5 | - Add documentation for Phpactor (#1800) (theLine) 6 | 7 | # Features and Fixes 8 | 9 | - Discard diagnostics in a few cases (#1816) (Raoul Wols) 10 | - Allow lists in `env` dict of client configuration (#1815) (Raoul Wols) 11 | - Remember the cwd for plugins (#1814) (Raoul Wols) 12 | - Re-evaluate what langservers to attach when the URI scheme changes (#1813) (Raoul Wols) 13 | - Include the list of key bindings (shortcuts) in the documentation (#1805) (Rafał Chłodnicki) 14 | - remove implementation status from docs as it is only developer related (Predrag Nikolic) 15 | - Rewrite / reorganize the troubleshooting documentation (#1802) (Rafał Chłodnicki) 16 | - Make hover popup also follow the show_diagnostics_severity_level setting (#1798) (Rafał Chłodnicki) 17 | - Improve keyboard shortcuts documentation (#1799) (Rafał Chłodnicki) 18 | - Update client configuration documentation (Rafal Chlodnicki) 19 | - Fix UNC paths on Windows (Raoul Wols) 20 | - Fix mypy type issue on windows (#1786) (Raoul Wols) 21 | - LSP-Serenata doesn't support ST4 (Luca Pomero) 22 | -------------------------------------------------------------------------------- /icons/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 GitHub Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 - 2020 Tom van Ommeren 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /messages/1.17.0.txt: -------------------------------------------------------------------------------- 1 | => 1.17.0 2 | 3 | # Features and Fixes 4 | 5 | - Completions rendering overhaul (expose labelDetails more prominently) (#2010) (jwortmann) 6 | - Prevent failing to load all configs when one fails (Raoul Wols) 7 | - Show previews in side view for Goto commands with side_by_side (#1982) (jwortmann) 8 | - Support side_by_side for "Go to reference" (Rafal Chlodnicki) 9 | - Keep active group when using Goto commands (#1994) (jwortmann) 10 | - Fix bug for symbol action links in hover popup (jwortmann) 11 | - Don't use actual linebreaks in log panel if payload is string literal (#1993) (jwortmann) 12 | - Optionally fallback to goto_definition in lsp_symbol_definition (#1986) (timfjord) 13 | - Restore selections after location picker panel (jwortmann) 14 | - Add preview for resource (res:) files in LocationPicker (jwortmann) 15 | - Tweaks for signature help popup including support for contextSupport and activeParameter (#2006) (jwortmann) 16 | - Enable admonition extension for mdpopups (jwortmann) 17 | - docs: Add Godot (GDScript) LSP instructions (lucypero) 18 | 19 | # API changes 20 | 21 | - Allow plugins to modify server response messages (#1992) (jwortmann) 22 | -------------------------------------------------------------------------------- /third_party/websocket_server/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Johan Hanssen Seferidis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /messages/1.29.0.txt: -------------------------------------------------------------------------------- 1 | => 1.29.0 2 | 3 | ⚠️⚠️⚠️ 4 | To ensure that everything works properly after LSP package is updated, 5 | it's strongly recommended to restart Sublime Text once it finishes updating all packages. 6 | ⚠️⚠️⚠️ 7 | 8 | # New features 9 | 10 | - add commands for opening "find references" in bottom or quick panel (#2409) (Rafał Chłodnicki) 11 | - Add format on paste (#2397) (Предраг Николић) 12 | 13 | # Fixes and Improvements 14 | 15 | - Fix usage of sublime.score_selector (#2427) (Benjamin Schaaf) 16 | - Fix find_open_file incompatibility with older ST versions (Janos Wortmann) 17 | 18 | # Refactoring 19 | 20 | - Remove hover provider count view setting (Janos Wortmann) 21 | - Remove unnecessary argument from lsp_symbol_rename command (Janos Wortmann) 22 | 23 | # Documentation 24 | 25 | - docs: add info about typst-lsp commands (#2424) (Ilia) 26 | - docs: add systemverilog/verible language server setup guide (#2416) (Johnny Martínez) 27 | - docs: rewrite self-help instructions (#2405) (Rafał Chłodnicki) 28 | - docs: rewritten "common problems" section (#2406) (Rafał Chłodnicki) 29 | - A few enhancements for the docs website (#2402) (jwortmann) 30 | - chore: deploy docs preview on docs changes (#2403) (Rafał Chłodnicki) 31 | -------------------------------------------------------------------------------- /messages/1.20.0.txt: -------------------------------------------------------------------------------- 1 | => 1.20.0 2 | 3 | # Features 4 | 5 | - (API) Add `$line` and `$character` expansion variables (#2092) (Matthew Erhard) 6 | 7 | # Fixes and Improvements 8 | 9 | - (Code Action) Filter out non-quickfix actions in view (#2081) (Rafał Chłodnicki) 10 | - (JSON Schema) Fix crash on generating schema with mixed-type enum (#2083) (Rafał Chłodnicki) 11 | - (UI) Update diagnostics gutter icons and change default to "sign" (#2086) (jwortmann) 12 | - (Color Boxes) Fix short color box rendering bug after color presentation change (#2087) (jwortmann) 13 | - Properly handle disabling of the LSP package (#2085) (Rafał Chłodnicki) 14 | - Fix issues with restarting servers (#2088) (Rafał Chłodnicki) 15 | - (Diagnostics) Only enable Goto Diagnostic commands if diagnostic with configured severity exists (#2091) (jwortmann) 16 | - (Document Symbol) Focus symbol closest to selection on showing document symbols (#2094) (Rafał Chłodnicki) 17 | - (Docs) Add LSP-clangd and LSP-terraform to Language Servers (#2096) (Rafał Chłodnicki) 18 | - (Document Link) Adjust the label to "Open in Browser" if the target is not a file uri (#2098) (jwortmann) 19 | - Add Jinja to language ids (#2099) (Предраг Николић) 20 | - Add Elixir to language ids (#2100) (Предраг Николић) 21 | -------------------------------------------------------------------------------- /messages/1.26.0.txt: -------------------------------------------------------------------------------- 1 | => 1.26.0 2 | 3 | ⚠️⚠️⚠️ 4 | To ensure that everything works properly after LSP package is updated, it's strongly recommended 5 | to restart Sublime Text once it finishes updating all packages. 6 | ⚠️⚠️⚠️ 7 | 8 | # New features 9 | 10 | - Add support for remote images in hover popups (#2341) (jwortmann) 11 | - Add kind filter for Goto Symbol command (#2330) (jwortmann) 12 | - Handle multiple formatters (#2328) (jwortmann) 13 | - Add support for folding range request (#2304) (jwortmann) 14 | - Add support for multi-range formatting (#2299) (jwortmann) 15 | 16 | # Improvements 17 | 18 | - Handle custom URI schemes in hover text links (#2339) (Raoul Wols) 19 | - Sort and select closest result for Find References in quick panel (#2337) (jwortmann) 20 | - Improve signature help performance (#2329) (jwortmann) 21 | - Align "Expand Selection" fallback behavior with "Goto Definition" and "Find References" (Janos Wortmann) 22 | - support client config with `tcp_port` but without `command` (#2300) (Marek Budík) 23 | 24 | # Fixes 25 | 26 | - check `codeAction/resolve` capability against session buffer (#2343) (1900 TD Lemon) 27 | - Minor visual tweaks to ShowMessageRequest popup (#2340) (Rafał Chłodnicki) 28 | - fix "formatting on save" potentially running on outdated document state (Rafal Chlodnicki) 29 | -------------------------------------------------------------------------------- /messages/1.6.0.txt: -------------------------------------------------------------------------------- 1 | # Removed Default Configurations 2 | 3 | As was mentioned in the release notes of 1.5, the following default 4 | configurations are now removed: 5 | 6 | - haskell-ide-engine 7 | -> use haskell-language-server, see lsp.sublimetext.io for instructions 8 | - rlang 9 | -> use the LSP-R helper package: https://packagecontrol.io/packages/LSP-R 10 | 11 | # New Helper Packages 12 | 13 | - LSP-R: https://packagecontrol.io/packages/LSP-R 14 | - LSP-Deno: https://packagecontrol.io/packages/LSP-Deno 15 | 16 | # Features and Fixes 17 | 18 | - Fix a bug where code actions would be requested for transient 19 | views (Raoul Wols) 20 | - Clear all status messages when session ends (Janos Wortmann) 21 | This fixes a bug where if you disable the LSP package while 22 | a language server is reporting server-initiated progress, the 23 | progress message would remain visible in the Status Bar. 24 | - CI is once again enabled (#1679) (Rafał Chłodnicki) 25 | - os.path.relpath may throw an exception on Windows (Raoul Wols) 26 | This fixes a bug where if you use Find References on Windows, 27 | the response handler could fail by not catching this exception. 28 | - Rename code_action_on_save_timeout_ms to 29 | on_save_task_timeout_ms (#1728) (Rafał Chłodnicki) 30 | This allows you to adjust the timeout for formatting as well. 31 | -------------------------------------------------------------------------------- /messages/1.14.0.txt: -------------------------------------------------------------------------------- 1 | => 1.14.0 2 | 3 | # Features and Fixes 4 | 5 | - Fix LSP status field missing randomly (#1901) (Rafał Chłodnicki) 6 | 7 | - Fix color boxes not updating in some cases (#1899) (Rafał Chłodnicki) 8 | 9 | - Simplify Vala Language Server instructions (#1895) (Colin Kiama) 10 | 11 | - Include diagnostics when triggering codeAction request on save (#1896) (Rafał Chłodnicki) 12 | 13 | - Use new UnitTesting github actions (#1897) (Rafał Chłodnicki) 14 | 15 | - Remove unused context in "goto definition" keybindings (Rafal Chlodnicki) 16 | 17 | - "Goto Diagnostic" quick panels (#1884) (Hans-Thomas Mueller) 18 | You can now more comfortably browse through the diagnostics in the current 19 | file by pressing F8. Pressing Shift+F8 will allow you to browse through the 20 | diagnostics across the entire project. 21 | This functionality replaces the old behavior of using Sublime's built-in 22 | `next_result` command. 23 | To modify the default key bindings, run "Preferences: LSP Key Bindings" from 24 | the command palette. 25 | 26 | - Don't restart all servers on changing LSP-* plugin settings (#1893) (Rafał Chłodnicki) 27 | 28 | - report didChangeWatchedFiles capability in a correct place (#1892) (Rafał Chłodnicki) 29 | 30 | - Don't resolve code lenses if server doesn't support resolving (#1888) (Rafał Chłodnicki) 31 | -------------------------------------------------------------------------------- /plugin/core/logging.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from .constants import ST_PACKAGES_PATH 3 | from typing import Any 4 | import traceback 5 | import inspect 6 | 7 | 8 | log_debug = False 9 | 10 | 11 | def set_debug_logging(logging_enabled: bool) -> None: 12 | global log_debug 13 | log_debug = logging_enabled 14 | 15 | 16 | def debug(*args: Any) -> None: 17 | """Print args to the console if the "debug" setting is True.""" 18 | if log_debug: 19 | printf(*args) 20 | 21 | 22 | def trace() -> None: 23 | current_frame = inspect.currentframe() 24 | if current_frame is None: 25 | debug("TRACE (unknown frame)") 26 | return 27 | previous_frame = current_frame.f_back 28 | file_name, line_number, function_name, _, _ = inspect.getframeinfo(previous_frame) # type: ignore 29 | file_name = file_name[len(ST_PACKAGES_PATH) + len("/LSP/"):] 30 | debug(f"TRACE {function_name:<32} {file_name}:{line_number}") 31 | 32 | 33 | def exception_log(message: str, ex: Exception) -> None: 34 | print(message) 35 | ex_traceback = ex.__traceback__ 36 | print(''.join(traceback.format_exception(ex.__class__, ex, ex_traceback))) 37 | 38 | 39 | def printf(*args: Any, prefix: str = 'LSP') -> None: 40 | """Print args to the console, prefixed by the plugin name.""" 41 | print(prefix + ":", *args) 42 | -------------------------------------------------------------------------------- /messages/2.0.0.txt: -------------------------------------------------------------------------------- 1 | => 2.0.0 2 | 3 | ⚠️ Sublime Text will need to be restarted **more than once** for things to work properly. ⚠️ 4 | 5 | # Breaking changes 6 | 7 | - LSP and LSP-* packages are transitioning from Python 3.3 to Python 3.8. 8 | 9 | # FAQ 10 | 11 | ### What are the most significant improvements that stem from the 3.3 to 3.8 change? 12 | 13 | There are no new features. 14 | This is an internal LSP codebase change that affects all LSP-* packages. 15 | 16 | ### LSP is broken after the update even if Sublime Text is restarted many times 17 | 18 | If that is the case, follow these steps: 19 | 20 | - ##### Check if `"index_files"` is set to `false` in `Preferences.sublime-settings` 21 | 22 | From the command palette open `Preferences: Settings` and see if `"index_files": false` exists. 23 | If yes, remove that setting. 24 | 25 | - ##### Check if `"LSP"` is put in `"ignored_packages"` in `Preferences.sublime-settings` 26 | 27 | Ensure that LSP hasn't been added to the "ignored_packages" list during the update. If it has, remove it. 28 | 29 | - ##### Reach for help 30 | 31 | Feel free to [open an issue](https://github.com/sublimelsp/LSP/issues/new?assignees=&labels=&projects=&template=bug_report.md&title=) or reach out to us on [Discord](https://discord.gg/TZ5WN8t) if you encounter any problems during the update. 32 | Please provide logs from the Sublime Text console. 33 | -------------------------------------------------------------------------------- /messages/1.4.0.txt: -------------------------------------------------------------------------------- 1 | => 1.4.0 2 | 3 | # Deprecations 4 | 5 | We want to remove the default configurations in LSP.sublime-settings 6 | and replace them by helper packages a la SublimeLinter. 7 | 8 | The following default configurations are **DEPRECATED** and **WILL BE REMOVED** 9 | in a future release: 10 | 11 | - "pyls": Use the LSP-pylsp package instead. 12 | - "dart": Use the LSP-Dart package instead. 13 | - "elixir-ls": Use the LSP-elixir package instead. 14 | - "flow": Use the LSP-flow package instead. 15 | - "jdtls": Use the LSP-jdtls package instead. 16 | - "sourcekit-lsp": Use the LSP-SourceKit package instead. 17 | 18 | # Documentation 19 | 20 | - Add Vala Language Server Instructions (#1693) (Colin Kiama) 21 | - Add information about LSP-pylsp (#1691) (Rafał Chłodnicki) 22 | 23 | # Fixes and Features 24 | 25 | - Raise an exception when neither "command" nor "tcp_port" is given (Raoul Wols) 26 | - Fallback to range formatting if available when formatting on save (Rafal Chlodnicki) 27 | - Allow adjustable extents of the popups (#1689) (Raoul Wols) 28 | - Allow no automatic code actions (Raoul Wols) 29 | - Handle 'file:' URIs in hover content (Raoul Wols) 30 | - Small tweaks for annotations and lightbulb colors (Janos Wortmann) 31 | - Account for out-of-bounds columnn offsets of text edits (Raoul Wols) 32 | - Improve the parser for parsing package.json files (#1664) (Raoul Wols) 33 | -------------------------------------------------------------------------------- /messages/2.7.0.txt: -------------------------------------------------------------------------------- 1 | => 2.6.0 2 | 3 | # Features 4 | 5 | - Add support to open unsaved buffers with VSCode-specific 'untitled' scheme (#2667) (jwortmann) 6 | - Add on_selection_modified_async API method (#2657) (jwortmann) 7 | 8 | # Fixes 9 | 10 | - Purge changes before triggering formatting (#2680) (Rafał Chłodnicki) 11 | - Request each code action kind separately during save (#2674) (Rafał Chłodnicki) 12 | - Fix code lenses with dynamic registration (#2666) (jwortmann) 13 | - Ensure ShowMessage request popup is shown inside the viewport area (#2668) (jwortmann) 14 | - Fix another case of outdated diagnostics (#2654) (Rafał Chłodnicki) 15 | - Consider dynamic registration when checking for capabilities (#2652) (jwortmann) 16 | - Ensure document state is synced before requesting diagnostics (#2648) (Rafał Chłodnicki) 17 | 18 | # Enhancements 19 | 20 | - Add helm language id (#2679) (Предраг Николић) 21 | - Add scope for new semantic token type 'label' (#2659) (jwortmann) 22 | 23 | # Refactors 24 | 25 | - Refactor code lenses (#2677) (jwortmann) 26 | - Move protocol types to separate folder (#2660) (jwortmann) 27 | - Use predefined constants in language ID mapping (#2658) (jwortmann) 28 | 29 | # Documentation 30 | 31 | - Reorganize PostgreSQL section in language_servers.md (#2675) (Предраг Николић) 32 | - Add instructions for PostgresSQL (#2669) (Olivier Le Moal) 33 | - Update instructions for Typst (#2644) (jwortmann) 34 | -------------------------------------------------------------------------------- /Syntaxes/References.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | # [Subl]: https://www.sublimetext.com/docs/3/syntax.html 4 | # [LSP]: https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md 5 | hidden: true 6 | scope: output.lsp.references 7 | 8 | contexts: 9 | main: 10 | - include: references-preamble 11 | - include: references-body 12 | 13 | references-preamble: 14 | - match: ^(\S.*)(:)$ 15 | scope: meta.reference.preamble.lsp 16 | captures: 17 | 1: entity.name.filename.lsp 18 | 2: punctuation.separator.lsp 19 | 20 | references-body: 21 | - match: ^\s+(?=\d+) 22 | push: 23 | - ensure-reference-meta-scope 24 | - code 25 | - expect-line-maybe-column 26 | 27 | code: 28 | - match: '(?=\S)' 29 | set: 30 | - meta_scope: source 31 | - include: pop-at-end 32 | 33 | ensure-reference-meta-scope: 34 | - meta_scope: meta.reference.body.lsp 35 | - match: "" # match the empty string 36 | pop: true 37 | 38 | expect-line-maybe-column: 39 | - include: pop-at-end 40 | - match: (\d+)(?:(:)(\d+))? 41 | captures: 42 | 1: meta.number.integer.decimal.lsp constant.numeric.line-number.lsp 43 | 2: punctuation.separator.lsp 44 | 3: meta.number.integer.decimal.lsp constant.numeric.col-number.lsp 45 | pop: true 46 | 47 | pop-at-end: 48 | - match: $ 49 | pop: true 50 | -------------------------------------------------------------------------------- /messages/1.22.0.txt: -------------------------------------------------------------------------------- 1 | => 1.22.0 2 | 3 | # Breaking changes 4 | 5 | - don't show diagnostics panel on save by default (#2179) (Glauco Custódio) 6 | 7 | # Features 8 | 9 | - Implement type hierarchy request (#2180) (jwortmann) 10 | - Implement call hierarchy request (#2151) (jwortmann) 11 | 12 | # Fixes and Improvements 13 | 14 | - Perform inlay hint action on double instead of single click (#2175) (Предраг Николић) 15 | - support canceling pending completions request (#2177) (Rafał Chłodnicki) 16 | - fix stale state or lack of updates on changing branches (#2182) (Rafał Chłodnicki) 17 | - Add timestamp and request duration in LSP logs (#2181) (Rafał Chłodnicki) 18 | - workaround for View Listeners not being attached for new transient view (#2174) (Rafał Chłodnicki) 19 | - Make Document Symbols behavior more consistent with built-in Goto Symbol (#2166) (jwortmann) 20 | - Allow missing window/workDoneProgress/create request from the server (#2159) (Raoul Wols) 21 | - Use "plaintext" language ID for plain text files (#2164) (Предраг Николић) 22 | - Don't use "escapeall" extension when formatting with mdpopups (#2163) (Rafał Chłodnicki) 23 | - support "force_group" and "group" arguments in "lsp_symbol_references" (#2186) (Rafał Chłodnicki) 24 | 25 | # Plugin API changes 26 | 27 | - trigger "on_server_response_async" also for "initialize" response (#2172) (Rafał Chłodnicki) 28 | - allow setting additional text in permanent server status (#2173) (Rafał Chłodnicki) 29 | -------------------------------------------------------------------------------- /messages/1.27.0.txt: -------------------------------------------------------------------------------- 1 | => 1.27.0 2 | 3 | ⚠️⚠️⚠️ 4 | To ensure that everything works properly after LSP package is updated, 5 | it's strongly recommended to restart Sublime Text once it finishes updating all packages. 6 | ⚠️⚠️⚠️ 7 | 8 | # Breaking changes 9 | 10 | - The default value of `show_references_in_quick_panel` has changed from `false` to `true`. 11 | 12 | # New features 13 | 14 | - Support all arguments of native save in lsp_save command (#2382) (jwortmann) 15 | - Add `only_files` argument for `lsp_save_all` command (#2376) (jwortmann) 16 | - Show diagnostics popup when hovering over gutter icons (#2349) (jwortmann) 17 | - Add menu item to toggle code lenses (#2351) (Rafał Chłodnicki) 18 | - Add menu item to disable/enable hover popups (#2316) (jwortmann) 19 | 20 | # Improvements 21 | 22 | - Workspace Symbols overhaul (#2333) (jwortmann) 23 | - Add NO_UNDO flags to all regions (less memory usage) (#2370) (Rafał Chłodnicki) 24 | - Try to match at least 2 components of base scope with the map (#2361) (Rafał Chłodnicki) 25 | - Small visual tweak for signature help popup (#2358) (jwortmann) 26 | - Prefer active view instead of leftmost one for Goto commands (#2356) (jwortmann) 27 | 28 | # Fixes 29 | 30 | - Empty `command` handling with `tcp_port` (#2378) (Alexey Bondarenko) 31 | - Document state getting out of sync in rare cases (#2375) (Rafał Chłodnicki) 32 | - Use simple error for code lenses that failed to resolve (Rafal Chlodnicki) 33 | - Fix performance regression in Goto Symbol overlay (#2348) (jwortmann) 34 | -------------------------------------------------------------------------------- /messages/1.24.0.txt: -------------------------------------------------------------------------------- 1 | => 1.24.0 2 | 3 | ⚠️ To ensure that everything works properly after LSP package is updated, it's strongly recommended to restart 4 | Sublime Text once it finishes updating all packages. ⚠️ 5 | 6 | # Breaking changes 7 | 8 | - Diagnostics for files that are not within the project folders are no longer ignored. 9 | You can set `"diagnostics_mode": "workspace"` in server-specific configuration to enable old behavior. 10 | 11 | # New features 12 | 13 | - Add support for pull diagnostics (#2221) (jwortmann) 14 | - Add "outline" as an option for "document_highlight_style" (#2234) (Terminal) 15 | - add "show_multiline_document_highlights" setting (#2247) (Tito) 16 | 17 | # Fixes 18 | 19 | - Fix handling of folder_exclude_patterns in projects (#2237) (Rafał Chłodnicki) 20 | - html-escape diagnostic-related strings (#2228) (Rafał Chłodnicki) 21 | - Fix exception for null response id (#2233) (jwortmann) 22 | - Fix some features might not work with dynamical registration (#2222) (jwortmann) 23 | 24 | # Improvements 25 | 26 | - use class for diagnostic info instead of hardcoding color (#2257) (Rafał Chłodnicki) 27 | - Use regular font style in sighelp popup if already highlighted by color scheme (#2259) (jwortmann) 28 | - Add support and mode for workspace pull diagnostics (#2225) (jwortmann) 29 | - don't send params for requests/notifications that don't expect them (#2240) (Rafał Chłodnicki) 30 | - optimize creation of code actions annotation region (#2239) (Rafał Chłodnicki) 31 | - Allow style overrides for inlay_hints.css (#2232) (jwortmann) 32 | - Improve label detail support in completions (#2212) (ryuukk) 33 | - Update clojure-lsp docs (#2226) (Raffael Stein) 34 | -------------------------------------------------------------------------------- /messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "1.10.0": "messages/1.10.0.txt", 3 | "1.10.1": "messages/1.10.1.txt", 4 | "1.11.0": "messages/1.11.0.txt", 5 | "1.12.0": "messages/1.12.0.txt", 6 | "1.12.1": "messages/1.12.1.txt", 7 | "1.13.0": "messages/1.13.0.txt", 8 | "1.14.0": "messages/1.14.0.txt", 9 | "1.15.0": "messages/1.15.0.txt", 10 | "1.16.0": "messages/1.16.0.txt", 11 | "1.16.1": "messages/1.16.1.txt", 12 | "1.16.2": "messages/1.16.2.txt", 13 | "1.16.3": "messages/1.16.3.txt", 14 | "1.17.0": "messages/1.17.0.txt", 15 | "1.18.0": "messages/1.18.0.txt", 16 | "1.19.0": "messages/1.19.0.txt", 17 | "1.2.7": "messages/1.2.7.txt", 18 | "1.2.8": "messages/1.2.8.txt", 19 | "1.2.9": "messages/1.2.9.txt", 20 | "1.20.0": "messages/1.20.0.txt", 21 | "1.21.0": "messages/1.21.0.txt", 22 | "1.22.0": "messages/1.22.0.txt", 23 | "1.23.0": "messages/1.23.0.txt", 24 | "1.24.0": "messages/1.24.0.txt", 25 | "1.25.0": "messages/1.25.0.txt", 26 | "1.26.0": "messages/1.26.0.txt", 27 | "1.27.0": "messages/1.27.0.txt", 28 | "1.28.0": "messages/1.28.0.txt", 29 | "1.29.0": "messages/1.29.0.txt", 30 | "1.3.0": "messages/1.3.0.txt", 31 | "1.3.1": "messages/1.3.1.txt", 32 | "1.30.0": "messages/1.30.0.txt", 33 | "1.4.0": "messages/1.4.0.txt", 34 | "1.5.0": "messages/1.5.0.txt", 35 | "1.6.0": "messages/1.6.0.txt", 36 | "1.6.1": "messages/1.6.1.txt", 37 | "1.7.0": "messages/1.7.0.txt", 38 | "1.8.0": "messages/1.8.0.txt", 39 | "1.9.0": "messages/1.9.0.txt", 40 | "2.0.0": "messages/2.0.0.txt", 41 | "2.1.0": "messages/2.1.0.txt", 42 | "2.2.0": "messages/2.2.0.txt", 43 | "2.3.0": "messages/2.3.0.txt", 44 | "2.4.0": "messages/2.4.0.txt", 45 | "2.5.0": "messages/2.5.0.txt", 46 | "2.6.0": "messages/2.6.0.txt", 47 | "2.7.0": "messages/2.7.0.txt", 48 | "install": "messages/install.txt" 49 | } 50 | -------------------------------------------------------------------------------- /messages/1.15.0.txt: -------------------------------------------------------------------------------- 1 | # Semantic Highlighting (#1839) (jwortmann) 2 | 3 | Semantic highlighting is an advanced language server capability where the server 4 | can communicate token regions to highlight. This allows for accurate 5 | highlighting for project-dependent tokens in languages like C++ and TypeScript. 6 | 7 | There is no official ST API endpoint to communicate this information to ST. As 8 | such, any implementation of this feature for this package will have to resort 9 | to a few workarounds to make it work. For this reason the feature is disabled 10 | by default and should be regarded as **EXPERIMENTAL**. 11 | 12 | To enable the feature, set the "semantic_highlighting" boolean to "true" in your 13 | LSP.sublime-settings file. Moreover, you'll need to add a special scope rule in 14 | your color scheme. The built-in color schemes are overridden in this package to 15 | make that process easier for you. 16 | 17 | If: 18 | 19 | - you're using a color scheme that's not a default one, or 20 | - you want to adjust the colors for semantic tokens by applying a foreground 21 | color to the individual token types, or 22 | - you need to finetune custom token types provided by your language server, 23 | 24 | then see the updated documentation: https://lsp.sublimetext.io/customization/#semantic-highlighting 25 | 26 | # Features and Fixes 27 | 28 | - Allow plugins to specify a custom syntax for code blocks in markdown (#1914) (Raoul Wols) 29 | - Call can_start() and on_pre_start() in the troubleshooting flow (#1916) (Rafał Chłodnicki) 30 | - Don't call the callback if the transport was closed (Rafal Chlodnicki) 31 | - Handle Code Lens Refresh Request (#1918) (Ayoub Benali) 32 | - fix types for protocols to add type safety (#1903) (Rafał Chłodnicki) 33 | - fix crash loop on disabling helper package of a running server (#1906) (Rafał Chłodnicki) 34 | - Send range with textDocument/hover when possible (#1900) (Ayoub Benali) 35 | -------------------------------------------------------------------------------- /Context.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "LSP", 4 | "children": 5 | [ 6 | { 7 | "command": "lsp_symbol_references", 8 | "caption": "Find References" 9 | }, 10 | { 11 | "command": "lsp_symbol_definition", 12 | "caption": "Goto Definition…" 13 | }, 14 | { 15 | "command": "lsp_symbol_type_definition", 16 | "caption": "Goto Type Definition…" 17 | }, 18 | { 19 | "command": "lsp_symbol_declaration", 20 | "caption": "Goto Declaration…" 21 | }, 22 | { 23 | "command": "lsp_symbol_implementation", 24 | "caption": "Goto Implementation…" 25 | }, 26 | { 27 | "command": "lsp_call_hierarchy", 28 | "caption": "Show Call Hierarchy" 29 | }, 30 | { 31 | "command": "lsp_type_hierarchy", 32 | "caption": "Show Type Hierarchy" 33 | }, 34 | { 35 | "command": "lsp_symbol_rename", 36 | "caption": "Rename…" 37 | }, 38 | { 39 | "command": "lsp_code_actions", 40 | "caption": "Code Action…" 41 | }, 42 | { 43 | "command": "lsp_code_actions", 44 | "args": {"only_kinds": ["refactor"]}, 45 | "caption": "Refactor…" 46 | }, 47 | { 48 | "command": "lsp_code_actions", 49 | "args": {"only_kinds": ["source"]}, 50 | "caption": "Source Action…" 51 | }, 52 | { 53 | "command": "lsp_format" 54 | }, 55 | { 56 | "command": "lsp_expand_selection", 57 | "caption": "Expand Selection" 58 | } 59 | ] 60 | } 61 | ] 62 | -------------------------------------------------------------------------------- /messages/1.13.0.txt: -------------------------------------------------------------------------------- 1 | => 1.13.0 2 | 3 | # New Helper Packages 4 | 5 | - LSP-julia: https://packagecontrol.io/packages/LSP-julia 6 | - LSP-gopls: https://packagecontrol.io/packages/LSP-gopls 7 | 8 | # Features and Fixes 9 | 10 | - Allow customizing diagnostics styles (#1856) (Krut Patel) 11 | This adds back the ability to change the style of in-view diagnostics. 12 | See the new settings "diagnostics_highlight_style" and "show_multiline_diagnostics_highlights" 13 | accessible via the command palette by running "Preferences: LSP Settings". 14 | 15 | - Panel output/regex fixes (#1883) (Hans-Thomas Mueller) 16 | 17 | - Document fix for F4 not working (sublimelsp/LSP#1721). (Hans-Thomas Mueller) 18 | 19 | - Add lsp_save_all function (#1876) (Fernando Serboncini) 20 | This allows you to run "code actions on save" and "format on save" for all open 21 | views. See the example key binding accessible via the command palette by 22 | running "Preferences: LSP Key Bindings". 23 | 24 | - Move diagnostic panel contributions from SessionBuffer to Session (#1881) (Hans-Thomas Mueller) 25 | This ensures diagnostics from files not open in Sublime are no longer discarded 26 | in the diagnostics panel. So, for language servers like metals and haskell-language-server 27 | this allows you to get an entire overview of the possible problems in the project. 28 | The diagnostics panel is accessible via the command palette by running 29 | "LSP: Toggle Diagnostics Panel". 30 | 31 | - Ensure timeout messages during code-action-on-save are visible (Rafal Chlodnicki) 32 | 33 | - Fix signature help keybindings after core changes (#1878) (Rafał Chłodnicki) 34 | 35 | - Fix on_query_context returning wrong value for unknown contexts (#1879) (Janos Wortmann) 36 | 37 | - Add documentation for terraform-ls (#1861) (Mike Conigliaro) 38 | 39 | - Add AbstractPlugin.on_session_buffer_changed API (#1873) (Rafał Chłodnicki) 40 | 41 | - Fix console exception with panel logger enabled (#1867) (Rafał Chłodnicki) 42 | 43 | - Update docs for Julia language server (Janos Wortmann) 44 | -------------------------------------------------------------------------------- /messages/1.21.0.txt: -------------------------------------------------------------------------------- 1 | => 1.21.0 2 | 3 | # Features 4 | 5 | - Add "Source Action" entry to the "Edit" main menu (#2149) (jwortmann) 6 | - Add "Refactor" entry to the "Edit" main menu (#2141) (jwortmann) 7 | - Auto-restart on server crashing, up to 5 times (#2145) (Lucas Alber) 8 | 9 | # Fixes and Improvements 10 | 11 | - Fix inlay hint parts wrapping into multiple lines (#2153) (Rafał Chłodnicki) 12 | - Ensure commands triggered from minihtml run on correct view (#2154) (Rafał Chłodnicki) 13 | - Fix completion documentation being parsed as markdown twice (#2146) (Rafał Chłodnicki) 14 | - When going to definition, scroll to start of the region, not end (#2147) (Rafał Chłodnicki) 15 | - Improve performance of completion & signature request on typing (#2148) (Rafał Chłodnicki) 16 | - Fix code lenses not updating after Undo (#2139) (Rafał Chłodnicki) 17 | - Add missing Goto commands to Command Palette (#2140) (Rafał Chłodnicki) 18 | - docs: add missing keyboard shortcuts (#2143) (Rafał Chłodnicki) 19 | - Pass force_group to LocationPicker (#2134) (Rafał Chłodnicki) 20 | - Don't advertise support for disabled code actions (#2137) (Rafał Chłodnicki) 21 | - Add context for lsp_open_link key binding (#2138) (jwortmann) 22 | - Fix prepareRename support (#2127) (Rafał Chłodnicki) 23 | - docs(language_servers): add markmark language server (for Markdown) (#2129) (Nico Rehwaldt) 24 | - Fix plugin overwrite `on_workspace_configuration` (#2132) (Lucas Alber) 25 | - Hide inactive items in context menu (#2124) (jwortmann) 26 | - Don't show a source if diagnostic doesn't have a source (#2119) (Sainan) 27 | - Combine file and range formatting entries in context menu (#2123) (jwortmann) 28 | - Add language id for Django templates (Jannis Vajen) 29 | - Nicer presentation for "find references/definition" quick panel (#2109) (Rafał Chłodnicki) 30 | - Make Goto Diagnostic overlays consistent (#2115) (jwortmann) 31 | - Don't trigger code action requests for background views (#2108) (Rafał Chłodnicki) 32 | - Ignore diagnostics for files in folder_exclude_patterns (#2113) (jwortmann) 33 | - Fix diagnostics underline color priority (#2106) (jwortmann) 34 | - Fix diagnostics_additional_delay_auto_complete_ms not working after split view (#2107) (jwortmann) 35 | -------------------------------------------------------------------------------- /messages/2.4.0.txt: -------------------------------------------------------------------------------- 1 | => 2.4.0 2 | 3 | # Breaking changes 4 | 5 | - Deprecate language IDs configuration file (#2586) (Janos Wortmann) - The language-ids.sublime-settings configuration file has been deprecated and will be removed in the next minor release of LSP. The language IDs are 6 | hardcoded now. Language IDs are used by servers which handle more than one language to avoid 7 | re-interpreting file extensions. If you used the configuration file and think that a language ID 8 | is wrong or missing, please follow this migration guide https://github.com/sublimelsp/LSP/issues/2592. 9 | - Remove `syntax2scope` and `view2scope` (#2594) (Предраг Николић) - They are not used. 10 | - Implement storage path as global constant (#2614) (@deathaxe) - `get_storage_path` was replaced with `ST_STORAGE_PATH`. 11 | 12 | # Features 13 | - Extract more global constants (#2565) (Janos Wortmann) 14 | 15 | # Fixes 16 | - Fix `LspToggleInlayHintsCommand` not initializing (#2571) (Janos Wortmann) 17 | 18 | # Enhancements 19 | - Replace Quick Fix label in hover popup with lightbulb icon (#2567) (Janos Wortmann) 20 | - Implement storage path as global constant (#2614) (@deathaxe) 21 | 22 | # Refactors 23 | - Deprecate `Session.set_window_status_async` (#2570) (Janos Wortmann) 24 | - Deprecate language IDs configuration file (#2586) (Janos Wortmann) 25 | - Remove workaround for `on_post_move_async`listener not triggering (#2582) (Janos Wortmann) 26 | - Update LSP types (#2593) (Предраг Николић) 27 | - Bump actions/checkout from 4 to 5 (#2617) (@dependabot) 28 | 29 | # Documentation 30 | - Update docs and README (#2583, #2590) (Janos Wortmann) 31 | * Add LSP-nimlangserver to the docs (#2602) (Amjad Ben Hedhili) 32 | * Add instructions for helm-ls to the docs (#2589) (@FichteFoll) 33 | * Add instructions for Perl LS to the docs (#2595) (Russ @russsaidwords) 34 | * Add instructions for Marksman to the docs (#2604) (Gavin Wiggins) 35 | * Add instructions for Herb to the docs (#2610, #2613) (Óscar Carretero) 36 | * Add instructions for MediaWiki to the docs (#2611) (@bhsd-harry) 37 | * Add instructions for Pyrefly to the docs (#2612) (Janos Wortmann) 38 | * Fix Sorbet docs to requires a directory be passed to the typecheck command (#2599) (James Hochadel) 39 | -------------------------------------------------------------------------------- /plugin/__init__.py: -------------------------------------------------------------------------------- 1 | from .core.collections import DottedDict 2 | from .core.css import css 3 | from .core.edit import apply_text_edits 4 | from .core.file_watcher import FileWatcher 5 | from .core.file_watcher import FileWatcherEvent 6 | from .core.file_watcher import FileWatcherEventType 7 | from .core.file_watcher import FileWatcherProtocol 8 | from .core.file_watcher import register_file_watcher_implementation 9 | from .core.protocol import Notification 10 | from .core.protocol import Request 11 | from .core.protocol import Response 12 | from .core.registry import LspTextCommand 13 | from .core.registry import LspWindowCommand 14 | from .core.sessions import AbstractPlugin 15 | from .core.sessions import register_plugin 16 | from .core.sessions import Session 17 | from .core.sessions import SessionBufferProtocol 18 | from .core.sessions import SessionViewProtocol 19 | from .core.sessions import unregister_plugin 20 | from .core.types import ClientConfig 21 | from .core.types import DebouncerNonThreadSafe 22 | from .core.types import matches_pattern 23 | from .core.url import filename_to_uri 24 | from .core.url import parse_uri 25 | from .core.url import uri_to_filename # deprecated 26 | from .core.version import __version__ 27 | from .core.views import MarkdownLangMap 28 | from .core.views import uri_from_view 29 | from .core.workspace import WorkspaceFolder 30 | 31 | # This is the public API for LSP-* packages 32 | __all__ = [ 33 | '__version__', 34 | 'AbstractPlugin', 35 | 'apply_text_edits', 36 | 'ClientConfig', 37 | 'css', 38 | 'DebouncerNonThreadSafe', 39 | 'DottedDict', 40 | 'filename_to_uri', 41 | 'FileWatcher', 42 | 'FileWatcherEvent', 43 | 'FileWatcherEventType', 44 | 'FileWatcherProtocol', 45 | 'LspTextCommand', 46 | 'LspWindowCommand', 47 | 'MarkdownLangMap', 48 | 'matches_pattern', 49 | 'Notification', 50 | 'parse_uri', 51 | 'register_file_watcher_implementation', 52 | 'register_plugin', 53 | 'Request', 54 | 'Response', 55 | 'Session', 56 | 'SessionBufferProtocol', 57 | 'SessionViewProtocol', 58 | 'unregister_plugin', 59 | 'uri_from_view', 60 | 'uri_to_filename', # deprecated 61 | 'WorkspaceFolder', 62 | ] 63 | -------------------------------------------------------------------------------- /messages/1.7.0.txt: -------------------------------------------------------------------------------- 1 | => 1.7.0 2 | 3 | # New Helper Packages 4 | 5 | - LSP-tailwindcss: https://packagecontrol.io/packages/LSP-tailwindcss 6 | 7 | # Fixes and Features 8 | 9 | - Fix res URIs for Windows (#1785) (Raoul Wols) 10 | - draw the diagnostics in the view regardless if we have an icon for the gutter (Predrag Nikolic) 11 | - Even more fixes for matching up buffers with URIs (#1782) (Raoul Wols) 12 | - Allow third-party packages to open abstract URIs as well (#1781) (Raoul Wols) 13 | - Open existing views with custom URIs (#1779) (Raoul Wols) 14 | - Fix key errors for log messages when running tests (#1780) (Raoul Wols) 15 | - Fix location_to_encoded_filename function (Raoul Wols) 16 | - Allow styling of code lens phantoms (Janos Wortmann) 17 | - Fix: should reset when uri changes via settings() (Raoul Wols) 18 | - Add API for registering an external file watcher implementation (#1764) (Rafał Chłodnicki) 19 | - You can display Code Lenses as phantoms (#1750) (Danny) 20 | See the setting "show_code_lens". 21 | - Fix file comparisons (#1775) (Raoul Wols) 22 | - Unify mac and linux CI workflows (#1773) (Rafał Chłodnicki) 23 | - Add Windows CI (#1765) (Raoul Wols) 24 | - Normalize drive letter in Windows filepaths to uppercase (#1772) (jwortmann) 25 | - Account for FS case-insensitivity (#1771) (Raoul Wols) 26 | - Update sublime-package.json for 'schemes' (#1769) (Raoul Wols) 27 | - Ignore plugin classes that don't return name() (#1768) (Rafał Chłodnicki) 28 | - Send a code lens request if one hasn't been sent yet (Rapptz) 29 | - Allow attaching to URI schemes other than the 'file' scheme (#1758) (Raoul Wols) 30 | - Document LSP-tailwindcss (#1767) (Предраг Николић / Predrag Nikolic) 31 | - Make language-ids.sublime-settings accessible (Raoul Wols) 32 | See the command LSP: Language ID Mapping Overrides in the command palette. 33 | - Index SessionBuffer weak value dictionary by id(session) (#1762) (Raoul Wols) 34 | - New key binding context: lsp.session_with_name (Raoul Wols) 35 | You can refer to language server configuration names in key binding contexts. 36 | - Remove unneeded abstract methods (Raoul Wols) 37 | - Fix wrong indent (Raoul Wols) 38 | - Prevent infinite loop when server returns empty code lenses (Raoul Wols) 39 | - Refactor code lenses (#1755) (Danny) 40 | -------------------------------------------------------------------------------- /plugin/document_link.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from ..protocol import DocumentLink 3 | from ..protocol import URI 4 | from .core.logging import debug 5 | from .core.open import open_file_uri 6 | from .core.open import open_in_browser 7 | from .core.protocol import Request 8 | from .core.registry import get_position 9 | from .core.registry import LspTextCommand 10 | import sublime 11 | 12 | 13 | class LspOpenLinkCommand(LspTextCommand): 14 | capability = 'documentLinkProvider' 15 | 16 | def is_enabled(self, event: dict | None = None, point: int | None = None) -> bool: 17 | if not super().is_enabled(event, point): 18 | return False 19 | if position := get_position(self.view, event): 20 | if session := self.best_session(self.capability, position): 21 | if sv := session.session_view_for_view_async(self.view): 22 | return sv.session_buffer.get_document_link_at_point(self.view, position) is not None 23 | return False 24 | 25 | def run(self, edit: sublime.Edit, event: dict | None = None) -> None: 26 | if position := get_position(self.view, event): 27 | if session := self.best_session(self.capability, position): 28 | if sv := session.session_view_for_view_async(self.view): 29 | if link := sv.session_buffer.get_document_link_at_point(self.view, position): 30 | if (target := link.get("target")) is not None: 31 | self.open_target(target) 32 | elif session.has_capability("documentLinkProvider.resolveProvider"): 33 | request = Request.resolveDocumentLink(link, self.view) 34 | session.send_request_async(request, self._on_resolved_async) 35 | else: 36 | debug("DocumentLink.target is missing, but the server doesn't support documentLink/resolve") 37 | 38 | def _on_resolved_async(self, response: DocumentLink) -> None: 39 | if target := response.get("target"): 40 | self.open_target(target) 41 | 42 | def open_target(self, target: URI) -> None: 43 | if target.startswith("file:"): 44 | if window := self.view.window(): 45 | open_file_uri(window, target) 46 | else: 47 | open_in_browser(target) 48 | -------------------------------------------------------------------------------- /plugin/diagnostics.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from ..protocol import Diagnostic 3 | from ..protocol import DiagnosticSeverity 4 | from .core.constants import DIAGNOSTIC_KINDS 5 | from .core.constants import REGIONS_INITIALIZE_FLAGS 6 | from .core.settings import userprefs 7 | from .core.views import diagnostic_severity 8 | from .core.views import format_diagnostics_for_annotation 9 | import sublime 10 | 11 | 12 | class DiagnosticsAnnotationsView: 13 | 14 | def __init__(self, view: sublime.View, config_name: str) -> None: 15 | self._view = view 16 | self._config_name = config_name 17 | 18 | def initialize_region_keys(self) -> None: 19 | r = [sublime.Region(0, 0)] 20 | for severity in DIAGNOSTIC_KINDS.keys(): 21 | self._view.add_regions(self._annotation_region_key(severity), r, flags=REGIONS_INITIALIZE_FLAGS) 22 | 23 | def _annotation_region_key(self, severity: DiagnosticSeverity) -> str: 24 | return f'lsp_da-{severity}-{self._config_name}' 25 | 26 | def draw(self, diagnostics: list[tuple[Diagnostic, sublime.Region]]) -> None: 27 | flags = sublime.RegionFlags.DRAW_NO_FILL | sublime.RegionFlags.DRAW_NO_OUTLINE | sublime.RegionFlags.NO_UNDO 28 | max_severity_level = userprefs().show_diagnostics_annotations_severity_level 29 | # To achieve the correct order of annotations (most severe having priority) we have to add regions from the 30 | # most to the least severe. 31 | for severity in DIAGNOSTIC_KINDS.keys(): 32 | if severity <= max_severity_level: 33 | matching_diagnostics: tuple[list[Diagnostic], list[sublime.Region]] = ([], []) 34 | for diagnostic, region in diagnostics: 35 | if diagnostic_severity(diagnostic) != severity: 36 | continue 37 | matching_diagnostics[0].append(diagnostic) 38 | matching_diagnostics[1].append(region) 39 | annotations, color = format_diagnostics_for_annotation(matching_diagnostics[0], severity, self._view) 40 | self._view.add_regions( 41 | self._annotation_region_key(severity), matching_diagnostics[1], flags=flags, 42 | annotations=annotations, annotation_color=color) 43 | else: 44 | self._view.erase_regions(self._annotation_region_key(severity)) 45 | -------------------------------------------------------------------------------- /popups.css: -------------------------------------------------------------------------------- 1 | .lsp_popup { 2 | margin: 0.5rem 0.5rem 0 0.5rem; 3 | font-family: system; 4 | } 5 | .lsp_popup--spacer { 6 | margin-top: 0.5rem; 7 | } 8 | .lsp_popup hr { 9 | margin-top: 0.5rem; 10 | margin-bottom: 0.5rem; 11 | border-color: color(var(--foreground) alpha(0.10)); 12 | } 13 | .lsp_popup h1, 14 | .lsp_popup h2, 15 | .lsp_popup h3, 16 | .lsp_popup h4, 17 | .lsp_popup h5, 18 | .lsp_popup h6 { 19 | font-size: 1rem; 20 | } 21 | .lsp_popup kbd { 22 | font-size: 0.8rem; 23 | line-height: 0.8rem; 24 | color: var(--mdpopups-fg); 25 | background-color: color(var(--mdpopups-bg) lightness(+ 5%)); 26 | border-color: color(var(--mdpopups-fg) alpha(0.25)); 27 | } 28 | .highlight { 29 | border-width: 0; 30 | border-radius: 0; 31 | } 32 | .color-muted { 33 | color: color(var(--foreground) alpha(0.6)); 34 | } 35 | .diagnostics { 36 | margin-bottom: 0.5rem; 37 | font-family: var(--mdpopups-font-mono); 38 | } 39 | .errors { 40 | border-width: 0; 41 | background-color: color(var(--redish) alpha(0.25)); 42 | color: var(--foreground); 43 | padding: 0.5rem; 44 | white-space: pre-wrap; 45 | } 46 | .warnings { 47 | border-width: 0; 48 | background-color: color(var(--yellowish) alpha(0.25)); 49 | color: var(--foreground); 50 | padding: 0.5rem; 51 | } 52 | .info { 53 | border-width: 0; 54 | background-color: color(var(--bluish) alpha(0.25)); 55 | color: var(--foreground); 56 | padding: 0.5rem; 57 | } 58 | .hints { 59 | border-width: 0; 60 | background-color: color(var(--bluish) alpha(0.25)); 61 | color: var(--foreground); 62 | padding: 0.5rem; 63 | } 64 | .actions { 65 | font-family: system; 66 | border-width: 0; 67 | background-color: color(var(--foreground) alpha(0.1)); 68 | color: var(--foreground); 69 | padding: 0.5rem; 70 | } 71 | .actions .lightbulb { 72 | padding-right: 0.3rem; 73 | } 74 | .actions .lightbulb img { 75 | width: 0.9em; 76 | height: 0.9em; 77 | } 78 | .actions a.icon { 79 | text-decoration: none; 80 | } 81 | .link.with-padding { 82 | padding: 0.5rem; 83 | } 84 | pre.related_info { 85 | border-top: 1px solid color(var(--background) alpha(0.25)); 86 | margin-top: 0.7rem; 87 | padding-top: 0.7rem; 88 | } 89 | a.copy-icon { 90 | padding: 0.5rem; 91 | text-decoration: none; 92 | color: color(var(--foreground) alpha(0.6)); 93 | } 94 | -------------------------------------------------------------------------------- /messages/2.3.0.txt: -------------------------------------------------------------------------------- 1 | => 2.3.0 2 | 3 | # Features 4 | 5 | - truncate inlay hint (#2514) (bivashy) 6 | - add variable for VersionedTextDocumentIdentifier to use with lsp_execute (#2516) (jwortmann) 7 | 8 | # Fixes 9 | 10 | - code actions on save not fixing issues if saving quickly (#2540) (Rafał Chłodnicki) 11 | - respect user-preferred flags when canceling completions (#2541) (Rafał Chłodnicki) 12 | - consider priority_selector when using best_session during rename (#2538) (Rafał Chłodnicki) 13 | - fix not considering given point when choosing best session in Goto commands (#2533) (jwortmann) 14 | - ensure ending a session triggers listeners (#2518) (Rafał Chłodnicki) 15 | - use session-unique region keys for semantic tokens (#2517) (Rafał Chłodnicki) 16 | 17 | # Enhancements 18 | 19 | - set completions from the async thread (#2563) (jwortmann) 20 | - cancel pending requests as early as possible (#2543) (Rafał Chłodnicki) 21 | - don't attach LSP to syntax test files (#2531) (jwortmann) 22 | - make DocumentSyncListener more efficient if no server is running (#2532) (jwortmann) 23 | - remove unnecessary parallel debouncing on selection change (#2529) (jwortmann) 24 | - print URI error to status bar instead of error dialog (#2528) (jwortmann) 25 | - don't restart servers when userprefs change (#2448) (jwortmann) 26 | - use orjson to de-/serialize json-rpc messages (#2513) (deathaxe) 27 | 28 | # Documentation 29 | 30 | - add LSP-twiggy to language servers list (#2558) (Ivan Nikolić) 31 | - update vue servers (#2549) (Rafał Chłodnicki) 32 | - tweak badges in Readme (#2544) (jwortmann) 33 | - add LSP-some-sass to language servers list (#2539) (Ivan Nikolić) 34 | - update semantic token types in docs (Janos Wortmann) 35 | - fix phpactor selector (#2512) (Rafał Chłodnicki) 36 | 37 | # Refactors 38 | 39 | - use namespaced enum constants (Janos Wortmann) 40 | - improve custom IntFlag enum and annotations (Janos Wortmann) 41 | - remove deprecated abstractproperty decorator (Janos Wortmann) 42 | - replace literal with enum constant (Janos Wortmann) 43 | - update type stubs for Python 3.8 (#2535) (jwortmann) 44 | - remove unnecessary use of generators for session retrieval (#2524) (Rafał Chłodnicki) 45 | - feat: make DebouncerNonThreadSafe public (#2525) (Rafał Chłodnicki) 46 | - update LSP types and pyright (#2519) (Rafał Chłodnicki) 47 | - use generated types for python 38 (#2500) (Предраг Николић) 48 | - add info about tinymist (#2473) (Ilia) 49 | -------------------------------------------------------------------------------- /messages/1.19.0.txt: -------------------------------------------------------------------------------- 1 | => 1.19.0 2 | 3 | # UPDATE WARNING 4 | 5 | Upgrading to a new LSP release typically requires a Sublime Text restart for everything to work properly 6 | Make sure to do that before reporting any issues. 7 | 8 | # Features 9 | 10 | - (Completions) Add insert-replace support for completions (#1809) (Предраг Николић) 11 | - (Code Actions) Add support for triggerKind in code action requests (#2042) (Rafał Chłodnicki) 12 | - (Code Actions) Add icons and isPreferred support for code actions (#2040) (jwortmann) 13 | - (Color Boxes) Request color presentations when clicking on a color box (#2065) (jwortmann) 14 | - (Diagnostics Panel) Automatically hide the diagnostics panel on save (#2037) (Tristan Daniel) 15 | - (Log Panel) Custom context menu and "Clear log panel" item (#2045) (Предраг Николић) 16 | - (Log Panel) Add context menu entry for toggling lines limit (#2047) (Rafał Chłodnicki) 17 | - (API) Add group argument for LspGotoCommand (#2031) (Justin Lam) 18 | - (API) Add template variable `$text_document_position` in execute command (#2061) (Ayoub Benali) 19 | 20 | # Fixes and Improvements 21 | 22 | - (Signature Help) Improve highlighting of parameters when labels are of type string (#2072) (Предраг Николић) 23 | - (Workspace Symbols) Allow empty string for request (#2071) (Предраг Николић) 24 | - (Code Actions) Ensure "Source Actions..." request includes the "source" kind (#2064) (Rafał Chłodnicki) 25 | - (Diagnostics Panel) Fix issues with toggling on save (#2063) (Rafał Chłodnicki) 26 | - (Diagnostics Panel) Only update content of diagnostics panel when visible (#2054) (jwortmann) 27 | - (JSON Schema) Add json suggestions for disabled capabilities (#2050) (Предраг Николић) 28 | - (Document Link, API) Parse position or selection from link fragment (#2049) (jwortmann) 29 | - (Settings) Change "log_server" default value to ["panel"] (Предраг Николић) 30 | - (Dev) Various improvements for type checking during development (use of NotRequired) (#2058) (Rafał Chłodnicki) 31 | - (Docs) Add code lens image (#2074) (Предраг Николић) 32 | 33 | # Breaking changes for LSP-* developers 34 | 35 | For developers working on LSP-* packages and using LSP-pyright for type-checking, it's now necessary 36 | to tell pyright explicitly which version of Python to check against. 37 | Do that by adding a `pyrightconfig.json` configuration file in the root of the LSP-* package: 38 | 39 | ```json 40 | { 41 | "pythonVersion": "3.11" 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /Syntaxes/Diagnostics.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | # [Subl]: https://www.sublimetext.com/docs/3/syntax.html 4 | # [LSP]: https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md 5 | hidden: true 6 | scope: output.lsp.diagnostics 7 | 8 | contexts: 9 | main: 10 | - match: ^ No diagnostics. Well done!$ 11 | scope: comment.line.placeholder.lsp 12 | - include: file 13 | - include: line 14 | 15 | file: 16 | - match: ^(\S.*)(:)$ 17 | scope: meta.diagnostic.preamble.lsp 18 | captures: 19 | 1: entity.name.filename.lsp 20 | 2: punctuation.separator.lsp 21 | 22 | line: 23 | - match: ^\s+(?=\d) 24 | push: 25 | - ensure-diag-meta-scope 26 | - expect-source-and-code 27 | - expect-message 28 | - expect-severity 29 | - expect-row-col 30 | 31 | ensure-diag-meta-scope: 32 | - meta_scope: meta.diagnostic.body.lsp 33 | - match: "" # match the empty string 34 | pop: true 35 | 36 | expect-message: 37 | # Various server-specific tokens may get special treatment here in the diag message. 38 | - include: pop-at-end 39 | - match: \x{200b} # Zero-width space 40 | pop: true 41 | 42 | expect-severity: 43 | # Implements RFC1036: https://github.com/sublimehq/Packages/issues/1036 44 | - include: pop-at-end 45 | - match: \berror\b 46 | scope: markup.deleted.lsp sublimelinter.mark.error markup.error.lsp 47 | pop: true 48 | - match: \bwarning\b 49 | scope: markup.changed.lsp sublimelinter.mark.warning markup.warning.lsp 50 | pop: true 51 | - match: \binfo\b 52 | scope: markup.inserted.lsp sublimelinter.gutter-mark markup.info.lsp 53 | pop: true 54 | - match: \bhint\b 55 | scope: markup.inserted.lsp sublimelinter.gutter-mark markup.info.hint.lsp 56 | pop: true 57 | 58 | expect-source-and-code: 59 | - include: pop-at-end 60 | - match: ([^:\]]+)((:)(\S+)?)? 61 | captures: 62 | 1: comment.line.source.lsp 63 | 3: punctuation.separator.lsp 64 | 4: comment.line.code.lsp 65 | pop: true 66 | 67 | expect-row-col: 68 | - include: pop-at-end 69 | - match: (\d+)(:)(\d+) 70 | captures: 71 | 1: meta.number.integer.decimal.lsp constant.numeric.line-number.lsp 72 | 2: punctuation.separator.lsp 73 | 3: meta.number.integer.decimal.lsp constant.numeric.col-number.lsp 74 | pop: true 75 | 76 | pop-at-end: 77 | - match: $ 78 | pop: true 79 | -------------------------------------------------------------------------------- /plugin/core/message_request_handler.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from ...protocol import MessageType 3 | from ...protocol import ShowMessageRequestParams 4 | from .protocol import Response 5 | from .sessions import Session 6 | from .views import show_lsp_popup 7 | from .views import text2html 8 | from typing import Any 9 | import sublime 10 | 11 | 12 | ICONS: dict[MessageType, str] = { 13 | MessageType.Error: '❗', 14 | MessageType.Warning: '⚠️', 15 | MessageType.Info: 'ℹ️', 16 | MessageType.Log: '📝', 17 | MessageType.Debug: '🐛' 18 | } 19 | 20 | 21 | class MessageRequestHandler: 22 | def __init__( 23 | self, view: sublime.View, session: Session, request_id: Any, params: ShowMessageRequestParams, source: str 24 | ) -> None: 25 | self.session = session 26 | self.request_id = request_id 27 | self.request_sent = False 28 | self.view = view 29 | self.actions = params.get("actions", []) 30 | self.action_titles = list(action.get("title") for action in self.actions) 31 | self.message = params['message'] 32 | self.message_type = params.get('type', 4) 33 | self.source = source 34 | 35 | def show(self) -> None: 36 | formatted: list[str] = [] 37 | formatted.append(f"

{self.source}

") 38 | icon = ICONS.get(self.message_type, '') 39 | formatted.append(f"
{icon} {text2html(self.message)}
") 40 | if self.action_titles: 41 | buttons: list[str] = [] 42 | for idx, title in enumerate(self.action_titles): 43 | buttons.append(f"{text2html(title)}") 44 | formatted.append("
" + " ".join(buttons) + "
") 45 | show_lsp_popup( 46 | self.view, 47 | "".join(formatted), 48 | location=self.view.layout_to_text(self.view.viewport_position()), 49 | css=sublime.load_resource("Packages/LSP/notification.css"), 50 | wrapper_class='notification', 51 | on_navigate=self._send_user_choice, 52 | on_hide=self._send_user_choice) 53 | 54 | def _send_user_choice(self, href: int = -1) -> None: 55 | if self.request_sent: 56 | return 57 | self.request_sent = True 58 | self.view.hide_popup() 59 | index = int(href) 60 | param = self.actions[index] if index != -1 else None 61 | response = Response(self.request_id, param) 62 | self.session.send_response(response) 63 | -------------------------------------------------------------------------------- /plugin/core/paths.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from ...protocol import DocumentUri 3 | from .sessions import Session 4 | from .views import parse_uri 5 | from pathlib import Path 6 | from typing import Iterable 7 | 8 | 9 | def simple_path(session: Session | None, uri: DocumentUri) -> str: 10 | scheme, path = parse_uri(uri) 11 | if not session or scheme != 'file': 12 | return path 13 | simple_path = simple_project_path([Path(folder.path) for folder in session.get_workspace_folders()], Path(path)) 14 | return str(simple_path) if simple_path else path 15 | 16 | 17 | def project_path(project_folders: Iterable[Path], file_path: Path) -> Path | None: 18 | """ 19 | The project path of `/path/to/project/file` in the project `/path/to/project` is `file`. 20 | """ 21 | folder_path = split_project_path(project_folders, file_path) 22 | if folder_path is None: 23 | return None 24 | _, file = folder_path 25 | return file 26 | 27 | 28 | def simple_project_path(project_folders: Iterable[Path], file_path: Path) -> Path | None: 29 | """ 30 | The simple project path of `/path/to/project/file` in the project `/path/to/project` is `project/file`. 31 | """ 32 | folder_path = split_project_path(project_folders, file_path) 33 | if folder_path is None: 34 | return None 35 | folder, file = folder_path 36 | return folder.name / file 37 | 38 | 39 | def resolve_simple_project_path(project_folders: Iterable[Path], file_path: Path) -> Path | None: 40 | """ 41 | The inverse of `simple_project_path()`. 42 | """ 43 | parts = file_path.parts 44 | folder_name = parts[0] 45 | for folder in project_folders: 46 | if folder.name == folder_name: 47 | return folder / Path(*parts[1:]) 48 | return None 49 | 50 | 51 | def project_base_dir(project_folders: Iterable[Path], file_path: Path) -> Path | None: 52 | """ 53 | The project base dir of `/path/to/project/file` in the project `/path/to/project` is `/path/to`. 54 | """ 55 | folder_path = split_project_path(project_folders, file_path) 56 | if folder_path is None: 57 | return None 58 | folder, _ = folder_path 59 | return folder.parent 60 | 61 | 62 | def split_project_path(project_folders: Iterable[Path], file_path: Path) -> tuple[Path, Path] | None: 63 | abs_path = file_path.resolve() 64 | for folder in project_folders: 65 | try: 66 | rel_path = abs_path.relative_to(folder) 67 | except ValueError: 68 | continue 69 | return folder, rel_path 70 | return None 71 | -------------------------------------------------------------------------------- /messages/1.23.0.txt: -------------------------------------------------------------------------------- 1 | => 1.23.0 2 | 3 | --- WARNING --- 4 | To ensure that everything works properly after LSP package is updated, it's strongly recommended to restart 5 | Sublime Text once it finishes updating all packages. 6 | --- WARNING --- 7 | 8 | # Breaking changes 9 | 10 | - removed bundled `rust-analyzer` client config - install https://packagecontrol.io/packages/LSP-rust-analyzer instead 11 | - removed bundled `clangd` client config - install https://packagecontrol.io/packages/LSP-clangd instead 12 | - removed bundled `gopls` client config - install https://packagecontrol.io/packages/LSP-gopls instead 13 | 14 | If you had one of those configs enabled in your LSP Settings then it will no longer do anything. You can 15 | restore the removed config from https://github.com/sublimelsp/LSP/pull/2206 into your LSP Settings but we 16 | instead recommend installing the relevant helper package which takes care of managing the server dependency 17 | and includes all necessary settings that the server can use. 18 | 19 | # Features 20 | 21 | - (inlay hints) toggle inlay hints command/menu item (#2023) (Предраг Николић) 22 | - (completions) add support for CompletionList.itemDefaults (#2194) (Предраг Николић) 23 | 24 | # Fixes and Improvements 25 | 26 | - (settings) better grouping of default settings (#2206) (Rafał Chłodnicki) 27 | - (general) don't initialize TextChange listeners for widgets (#2213) (Rafał Chłodnicki) 28 | - (general) protect again invalid `auto_complete_triggers` values (#2209) (Rafał Chłodnicki) 29 | - (general) tell if any selection changed in addition to just the first region (#2204) (Rafał Chłodnicki) 30 | - (general) Don't run non-essential requests during save (#2203) (Rafał Chłodnicki) 31 | - (general) add language ID mapping for TailwindCSS syntax (#2198) (Предраг Николић) 32 | - (general) fix hidden code action menu entries sometimes being visible (#2187) (jwortmann) 33 | - (completions) optimize performance of handling huge completion payloads (#2190) (Rafał Chłodnicki) 34 | - (completions) tweak formatting with labelDetails (#2207) (jwortmann) 35 | - (diagnostics) do not wrongly ignore diagnostics when symlinks are involved (#2210) (Rafał Chłodnicki) 36 | - (diagnostics) notify opened files immediately if there are pending diagnostics (#2211) (Rafał Chłodnicki) 37 | - (call hierarchy) highlight call location for incoming calls (#2208) (jwortmann) 38 | - (code actions) check capabilities against buffer when requesting code actions (#2202) (Rafał Chłodnicki) 39 | - (docs) add Digestif server configuration (#2205) (jwortmann) 40 | - (logging) fix log panel not scrolling on initial open (#2188) (Rafał Chłodnicki) 41 | -------------------------------------------------------------------------------- /messages/1.5.0.txt: -------------------------------------------------------------------------------- 1 | => 1.5.0 2 | 3 | # Removed Default Client Configurations 4 | 5 | As was mentioned in the release notes of 1.4.0, the following default 6 | configurations have been removed: 7 | 8 | - pyls 9 | - dart 10 | - elixir-ls 11 | - flow 12 | - jdtls 13 | - sourcekit-lsp 14 | 15 | For all of these, a helper package is available on packagecontrol.io that 16 | automates the boring set up and installation of server binaries. 17 | 18 | # Deprecated Default Client Configurations 19 | 20 | The following default client configs are **DEPRECATED** and **WILL BE REMOVED**: 21 | 22 | - haskell-ide-engine -> set up haskell-language-server instead, see 23 | lsp.sublimetext.io for instructions 24 | - rlang -> use the R-IDE package, which provides an LSP client configuration 25 | under the hood. 26 | 27 | # New Example Configurations 28 | 29 | An example client configuration for "diagnostic-language-server" has been added 30 | to lsp.sublimetext.io. It is a general-purpose language server which allows 31 | you to hook up any command-line linter program or formatter program. It can be 32 | regarded as a replacement of SublimeLinter. There is an example set up for the 33 | shellcheck program to lint your Bash source code. 34 | 35 | # New Settings 36 | 37 | - Use show_code_actions_in_hover to disable code actions in the hover popup. 38 | - Use show_diagnostics_highlights to disable inline diagnostics in the view. 39 | 40 | # Removed Settings 41 | 42 | - diagnostics_highlights_style is replaced by show_diagnostics_highlights. 43 | 44 | # Features and Fixes 45 | 46 | - Draw empty regions as a small horizontal line (Raoul Wols) 47 | - Fix the `LSP Development` command (#1724) (Arnav Jindal) 48 | - Refine diagnostics presentation (#1710) (Raoul Wols) 49 | - Fix crash on showing code actions (Rafal Chlodnicki) 50 | - Handle percent-encoding and leading slash in file URIs (#1722) (jwortmann) 51 | - Add show_code_actions_in_hover boolean setting (#1717) (husanjun) 52 | - Use box-style for multiline doc highlights (#1712) (Raoul Wols) 53 | - Remove some default configs (Raoul Wols) 54 | - Mention font face in customization docs (#1714) (Raoul Wols) 55 | - Improve hover code actions UX (husanjun) 56 | - Improve code action annotation UX (#1707) (husanjun) 57 | - Apply all status messages when a SessionView is opened (#1706) (jwortmann) 58 | - Add instructions for shellcheck (#1703) (Rafał Chłodnicki) 59 | - Don't ascii-escape Unicode (Raoul Wols) 60 | This makes the Flow language server work with non-ascii characters. 61 | - Handle schemes other than the file scheme (#1620) (Raoul Wols) 62 | This makes the Deno language server work for goto-def and find-references. 63 | -------------------------------------------------------------------------------- /plugin/color.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from ..protocol import ColorInformation 3 | from ..protocol import ColorPresentation 4 | from ..protocol import ColorPresentationParams 5 | from .core.edit import apply_text_edits 6 | from .core.protocol import Request 7 | from .core.registry import LspTextCommand 8 | from .core.views import range_to_region 9 | from .core.views import text_document_identifier 10 | import sublime 11 | 12 | 13 | class LspColorPresentationCommand(LspTextCommand): 14 | 15 | capability = 'colorProvider' 16 | 17 | def run(self, edit: sublime.Edit, color_information: ColorInformation) -> None: 18 | if session := self.best_session(self.capability): 19 | self._version = self.view.change_count() 20 | self._range = color_information['range'] 21 | params: ColorPresentationParams = { 22 | 'textDocument': text_document_identifier(self.view), 23 | 'color': color_information['color'], 24 | 'range': self._range 25 | } 26 | session.send_request_async(Request.colorPresentation(params, self.view), self._handle_response_async) 27 | 28 | def want_event(self) -> bool: 29 | return False 30 | 31 | def _handle_response_async(self, response: list[ColorPresentation]) -> None: 32 | if not response: 33 | return 34 | window = self.view.window() 35 | if not window: 36 | return 37 | if self._version != self.view.change_count(): 38 | return 39 | old_text = self.view.substr(range_to_region(self._range, self.view)) 40 | self._filtered_response: list[ColorPresentation] = [] 41 | for item in response: 42 | # Filter out items that would apply no change 43 | if text_edit := item.get('textEdit'): 44 | if text_edit['range'] == self._range and text_edit['newText'] == old_text: 45 | continue 46 | elif item['label'] == old_text: 47 | continue 48 | self._filtered_response.append(item) 49 | if self._filtered_response: 50 | window.show_quick_panel( 51 | [sublime.QuickPanelItem(item['label']) for item in self._filtered_response], 52 | self._on_select, 53 | placeholder="Change color format") 54 | 55 | def _on_select(self, index: int) -> None: 56 | if index > -1: 57 | color_pres = self._filtered_response[index] 58 | text_edit = color_pres.get('textEdit') or {'range': self._range, 'newText': color_pres['label']} 59 | apply_text_edits(self.view, [text_edit], label="Change Color Format", required_view_version=self._version) 60 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributor Guidelines 2 | 3 | ## Before you start 4 | 5 | LSP is a universal language server package. 6 | It is not the place for language-specific logic, features or workarounds. 7 | 8 | Sublime Text is not an IDE. 9 | Sublime Text users may appreciate IDE-like features, but a significant number of users will want to turn these features off due to performance or to keep the package "out of the way". 10 | 11 | Please feel free to create an issue before coding: 12 | 13 | * If you are unsure if your proposal is suitable 14 | * If you are unsure your solution will be technically sound 15 | 16 | The issues also allow you to gather some feedback and help from other contributors. 17 | 18 | ## Coding 19 | 20 | Sublime Text bundles a Python 3.3, please be sure to set up your environment to match. 21 | LSP uses `LSP-pyright`, `flake8` and `mypy` to provide some code quality assurances. 22 | Run `tox` to check your work. 23 | Consider using `LSP-pyright` or `LSP-pylsp` as a language server. 24 | To reload the plugin, save the file boot.py. 25 | Saving any other file does not reload the plugin. 26 | 27 | ## Testing 28 | 29 | Please consider testing your work with other language servers, even if you do not use them. 30 | There is also a test suite in tests/. To run the tests, use the UnitTesting package from randy3k. 31 | The configuration file for the tests is in unittesting.json. 32 | 33 | ## Submitting 34 | 35 | Before you submit your pull request, please review the following: 36 | 37 | * Any unrelated changes in there? 38 | * Is it a bug fix? Please link the issue or attach repro, logs, screenshots etc. 39 | * Is it a feature? Please attach screenshots / GIFs for visual changes. 40 | 41 | We will try to help you get the PR in a mergeable shape within a reasonable time, but it may take a few days. 42 | It is best if you check your GitHub notifications in the meantime! 43 | 44 | ## Releasing a new version (for maintainers) 45 | 46 | * Get a log of commits since the previously released tag with `git log --format="- %s (%an)" ..main` 47 | * Filter out non-relevant and non-important commits (it's not relevant to report fixes for bugs that weren't released yet, for example) 48 | * Optionally group changes into Fixes/Features/etc. 49 | * Create a new file in `messages/` with a file name of the yet-to-be-released version and include the changes. 50 | * Run `./scripts/release.py build` which will bump the version and create a new commit with a new messages file included. 51 | * If something doesn't look right in the newly created commit, delete the newly created tag manually and git reset to the previous commit making sure that you don't lose the newly created messages file. 52 | * Run `GITHUB_TOKEN= ./scripts/release.py publish` to push and create a new Github release. 53 | -------------------------------------------------------------------------------- /plugin/configuration.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from .core.registry import windows 3 | from .core.settings import client_configs 4 | from .core.windows import WindowManager 5 | from functools import partial 6 | import sublime 7 | import sublime_plugin 8 | 9 | 10 | class LspEnableLanguageServerGloballyCommand(sublime_plugin.WindowCommand): 11 | 12 | def run(self) -> None: 13 | self._items = [config.name for config in client_configs.all.values() if not config.enabled] 14 | if len(self._items) > 0: 15 | self.window.show_quick_panel(self._items, self._on_done) 16 | else: 17 | self.window.status_message("No config available to enable") 18 | 19 | def _on_done(self, index: int) -> None: 20 | if index != -1: 21 | config_name = self._items[index] 22 | client_configs.enable(config_name) 23 | 24 | 25 | class LspEnableLanguageServerInProjectCommand(sublime_plugin.WindowCommand): 26 | 27 | def run(self) -> None: 28 | wm = windows.lookup(self.window) 29 | if not wm: 30 | return 31 | self._items = [config.name for config in wm.get_config_manager().all.values() if not config.enabled] 32 | if len(self._items) > 0: 33 | self.window.show_quick_panel(self._items, partial(self._on_done, wm)) 34 | else: 35 | self.window.status_message("No config available to enable") 36 | 37 | def _on_done(self, wm: WindowManager, index: int) -> None: 38 | if index == -1: 39 | return 40 | config_name = self._items[index] 41 | sublime.set_timeout_async(lambda: wm.enable_config_async(config_name)) 42 | 43 | 44 | class LspDisableLanguageServerGloballyCommand(sublime_plugin.WindowCommand): 45 | 46 | def run(self) -> None: 47 | wm = windows.lookup(self.window) 48 | if not wm: 49 | return 50 | self._items = [config.name for config in client_configs.all.values() if config.enabled] 51 | if len(self._items) > 0: 52 | self.window.show_quick_panel(self._items, self._on_done) 53 | else: 54 | self.window.status_message("No config available to disable") 55 | 56 | def _on_done(self, index: int) -> None: 57 | if index == -1: 58 | return 59 | config_name = self._items[index] 60 | client_configs.disable(config_name) 61 | 62 | 63 | class LspDisableLanguageServerInProjectCommand(sublime_plugin.WindowCommand): 64 | 65 | def run(self) -> None: 66 | wm = windows.lookup(self.window) 67 | if not wm: 68 | return 69 | self._items = [config.name for config in wm.get_config_manager().all.values() if config.enabled] 70 | if len(self._items) > 0: 71 | self.window.show_quick_panel(self._items, partial(self._on_done, wm)) 72 | else: 73 | self.window.status_message("No config available to disable") 74 | 75 | def _on_done(self, wm: WindowManager, index: int) -> None: 76 | if index == -1: 77 | return 78 | config_name = self._items[index] 79 | sublime.set_timeout_async(lambda: wm.disable_config_async(config_name)) 80 | -------------------------------------------------------------------------------- /plugin/core/file_watcher.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from ...protocol import FileChangeType 3 | from ...protocol import WatchKind 4 | from abc import ABCMeta 5 | from abc import abstractmethod 6 | from typing import Literal, Protocol, Tuple, Union 7 | 8 | DEFAULT_WATCH_KIND = WatchKind.Create | WatchKind.Change | WatchKind.Delete 9 | 10 | FileWatcherEventType = Union[Literal['create'], Literal['change'], Literal['delete']] 11 | FilePath = str 12 | FileWatcherEvent = Tuple[FileWatcherEventType, FilePath] 13 | 14 | 15 | def lsp_watch_kind_to_file_watcher_event_types(kind: WatchKind) -> list[FileWatcherEventType]: 16 | event_types: list[FileWatcherEventType] = [] 17 | if kind & WatchKind.Create: 18 | event_types.append('create') 19 | if kind & WatchKind.Change: 20 | event_types.append('change') 21 | if kind & WatchKind.Delete: 22 | event_types.append('delete') 23 | return event_types 24 | 25 | 26 | def file_watcher_event_type_to_lsp_file_change_type(kind: FileWatcherEventType) -> FileChangeType: 27 | return { 28 | 'create': FileChangeType.Created, 29 | 'change': FileChangeType.Changed, 30 | 'delete': FileChangeType.Deleted, 31 | }[kind] 32 | 33 | 34 | class FileWatcherProtocol(Protocol): 35 | def on_file_event_async(self, events: list[FileWatcherEvent]) -> None: 36 | """ 37 | Called on file watcher events. 38 | This API must be triggered on async thread. 39 | 40 | :param events: The list of events to notify about. 41 | """ 42 | ... 43 | 44 | 45 | class FileWatcher(metaclass=ABCMeta): 46 | """ 47 | A public interface of a file watcher implementation. 48 | 49 | The interface implements the file watcher and notifies the `handler` (through the `on_file_event_async` method) 50 | on file event changes. 51 | """ 52 | 53 | @classmethod 54 | @abstractmethod 55 | def create( 56 | cls, 57 | root_path: str, 58 | patterns: list[str], 59 | events: list[FileWatcherEventType], 60 | ignores: list[str], 61 | handler: FileWatcherProtocol 62 | ) -> FileWatcher: 63 | """ 64 | Creates a new instance of the file watcher. 65 | 66 | :param patterns: The list of glob pattern to enable watching for. 67 | :param events: The type of events that should be watched. 68 | :param ignores: The list of glob patterns that should excluded from file watching. 69 | 70 | :returns: A new instance of file watcher. 71 | """ 72 | pass 73 | 74 | @abstractmethod 75 | def destroy(self) -> None: 76 | """ 77 | Called before the file watcher is disabled. 78 | """ 79 | pass 80 | 81 | 82 | watcher_implementation: type[FileWatcher] | None = None 83 | 84 | 85 | def register_file_watcher_implementation(file_watcher: type[FileWatcher]) -> None: 86 | global watcher_implementation 87 | if watcher_implementation: 88 | print('LSP: Watcher implementation already registered. Overwriting.') 89 | watcher_implementation = file_watcher 90 | 91 | 92 | def get_file_watcher_implementation() -> type[FileWatcher] | None: 93 | return watcher_implementation 94 | -------------------------------------------------------------------------------- /Syntaxes/ServerLog.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | # [Subl]: https://www.sublimetext.com/docs/3/syntax.html 4 | # [LSP]: https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md 5 | hidden: true 6 | scope: output.lsp.log 7 | 8 | variables: 9 | method: '[[:alnum:]/$#]+' 10 | servername: '[[:alnum:]_-]+' 11 | id: '[^\s():]+' 12 | 13 | contexts: 14 | main: 15 | - match: ^({{servername}})(:) 16 | captures: 17 | 1: variable.function.lsp 18 | 2: punctuation.separator.lsp 19 | push: 20 | - meta_scope: meta.block.lsp 21 | - match: $ 22 | pop: true 23 | - match: '^::' 24 | scope: punctuation.accessor.lsp 25 | push: 26 | - meta_scope: meta.group.lsp 27 | - match: '\[(\d{2}:\d{2}:\d{2}\.\d{3})\]' 28 | captures: 29 | 1: constant.numeric.timestamp.lsp 30 | - match: (?:==|--)> 31 | scope: storage.modifier.lsp 32 | set: [maybe-payload, request, server-name] 33 | - match: -> 34 | scope: storage.modifier.lsp 35 | set: [maybe-payload, notification, server-name] 36 | - match: '>>>' 37 | scope: storage.modifier.lsp 38 | set: [maybe-payload, response, server-name] 39 | - match: '~~>' 40 | scope: invalid.illegal.lsp 41 | set: [maybe-payload, response, server-name] 42 | - match: <-- 43 | scope: storage.modifier.lsp 44 | set: [maybe-payload, request, server-name] 45 | - match: <- 46 | scope: storage.modifier.lsp 47 | set: [maybe-payload, notification, server-name] 48 | - match: <(?:<<|==) 49 | scope: storage.modifier.lsp 50 | set: [maybe-payload, response, server-name] 51 | - match: <~~ 52 | scope: invalid.illegal.lsp 53 | set: [maybe-payload, response, server-name] 54 | - match: <\? 55 | scope: invalid.deprecated.lsp 56 | set: [maybe-payload, notification, server-name] 57 | 58 | server-name: 59 | - match: '{{servername}}' 60 | scope: variable.function.lsp 61 | pop: true 62 | 63 | request: 64 | - match: ({{method}}) (\()({{id}})(\)) 65 | captures: 66 | 1: keyword.control.lsp 67 | 2: punctuation.section.parens.begin.lsp 68 | 3: constant.numeric.id.lsp 69 | 4: punctuation.section.parens.end.lsp 70 | pop: true 71 | 72 | notification: 73 | - match: '{{method}}' 74 | scope: keyword.control.lsp 75 | pop: true 76 | 77 | response: 78 | - match: ' \(({{id}})\) \(duration: (\d+ms|-)\)' 79 | captures: 80 | 1: constant.numeric.id.lsp 81 | 2: constant.numeric.duration.lsp 82 | pop: true 83 | 84 | maybe-payload: 85 | - match: ':' 86 | scope: punctuation.separator.lsp 87 | set: 88 | - match: $ 89 | pop: true 90 | - include: scope:source.python#constants # e.g. shutdown request 91 | - include: scope:source.python#strings 92 | - include: scope:source.python#numbers 93 | - include: scope:source.python#lists 94 | - include: scope:source.python#dictionaries-and-sets 95 | - match: '' 96 | pop: true 97 | ... 98 | -------------------------------------------------------------------------------- /plugin/selection_range.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from ..protocol import SelectionRange 3 | from .core.protocol import Request 4 | from .core.registry import get_position 5 | from .core.registry import LspTextCommand 6 | from .core.views import range_to_region 7 | from .core.views import selection_range_params 8 | from typing import Any 9 | import sublime 10 | 11 | 12 | class LspExpandSelectionCommand(LspTextCommand): 13 | 14 | capability = 'selectionRangeProvider' 15 | 16 | def __init__(self, view: sublime.View) -> None: 17 | super().__init__(view) 18 | self._regions: list[sublime.Region] = [] 19 | self._change_count = 0 20 | 21 | def is_enabled(self, event: dict | None = None, point: int | None = None, fallback: bool = False) -> bool: 22 | return fallback or super().is_enabled(event, point) 23 | 24 | def is_visible(self, event: dict | None = None, point: int | None = None, fallback: bool = False) -> bool: 25 | if self.applies_to_context_menu(event): 26 | return self.is_enabled(event, point, fallback) 27 | return True 28 | 29 | def run(self, edit: sublime.Edit, event: dict | None = None, fallback: bool = False) -> None: 30 | position = get_position(self.view, event) 31 | if position is None: 32 | return 33 | if session := self.best_session(self.capability, position): 34 | params = selection_range_params(self.view) 35 | self._regions.extend(self.view.sel()) 36 | self._change_count = self.view.change_count() 37 | session.send_request(Request.selectionRange(params), self.on_result, self.on_error) 38 | elif fallback: 39 | self._run_builtin_expand_selection(f"No {self.capability} found") 40 | 41 | def on_result(self, params: list[SelectionRange] | None) -> None: 42 | if self._change_count != self.view.change_count(): 43 | return 44 | if params: 45 | self.view.run_command("lsp_selection_set", {"regions": [ 46 | self._smallest_containing(region, param) for region, param in zip(self._regions, params)]}) 47 | else: 48 | self._status_message("Nothing to expand") 49 | self._regions.clear() 50 | 51 | def on_error(self, params: Any) -> None: 52 | self._regions.clear() 53 | self._run_builtin_expand_selection("Error: {}".format(params["message"])) 54 | 55 | def _status_message(self, msg: str) -> None: 56 | if window := self.view.window(): 57 | window.status_message(msg) 58 | 59 | def _run_builtin_expand_selection(self, fallback_reason: str) -> None: 60 | self._status_message(f"{fallback_reason}, reverting to built-in Expand Selection") 61 | self.view.run_command("expand_selection", {"to": "smart"}) 62 | 63 | def _smallest_containing(self, region: sublime.Region, param: SelectionRange) -> tuple[int, int]: 64 | r = range_to_region(param["range"], self.view) 65 | # Test for *strict* containment 66 | if r.contains(region) and (r.a < region.a or r.b > region.b): 67 | return r.a, r.b 68 | if parent := param.get("parent"): 69 | return self._smallest_containing(region, parent) 70 | return region.a, region.b 71 | -------------------------------------------------------------------------------- /plugin/core/progress.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | import sublime 3 | 4 | 5 | class ProgressReporter: 6 | 7 | def __init__(self, title: str) -> None: 8 | self.title = title 9 | self._message: str | None = None 10 | self._percentage: None | int | float = None 11 | 12 | def __del__(self) -> None: 13 | pass 14 | 15 | def _render(self) -> str: 16 | result = self.title 17 | if self._message: 18 | result += ': ' + self._message 19 | if self._percentage: 20 | fmt = ' ({:.1f}%)' if isinstance(self._percentage, float) else ' ({}%)' 21 | result += fmt.format(self._percentage) 22 | return result 23 | 24 | def __call__(self, message: str | None, percentage: None | int | float) -> None: 25 | if percentage is not None: 26 | self._percentage = percentage 27 | if message is not None: 28 | self._message = message 29 | 30 | 31 | class ViewProgressReporter(ProgressReporter): 32 | 33 | def __init__(self, view: sublime.View, key: str, title: str, message: str | None = None, 34 | percentage: None | int | float = None) -> None: 35 | super().__init__(title) 36 | self._view = view 37 | self._key = key 38 | self.__call__(message, percentage) 39 | 40 | def __del__(self) -> None: 41 | self._view.erase_status(self._key) 42 | super().__del__() 43 | 44 | def __call__(self, message: str | None = None, percentage: None | int | float = None) -> None: 45 | super().__call__(message, percentage) 46 | self._view.set_status(self._key, self._render()) 47 | 48 | 49 | class WindowProgressReporter(ProgressReporter): 50 | 51 | def __init__(self, window: sublime.Window, key: str, title: str, message: str | None = None, 52 | percentage: None | int | float = None) -> None: 53 | super().__init__(title) 54 | self._window = window 55 | self._key = key 56 | self.__call__(message, percentage) 57 | 58 | def __del__(self) -> None: 59 | for view in self._window.views(): 60 | view.erase_status(self._key) 61 | super().__del__() 62 | 63 | def __call__(self, message: str | None = None, percentage: None | int | float = None) -> None: 64 | super().__call__(message, percentage) 65 | display = self._render() 66 | for view in self._window.views(): 67 | view.set_status(self._key, display) 68 | 69 | 70 | class ApplicationProgressReporter(ProgressReporter): 71 | 72 | def __init__(self, key: str, title: str, message: str | None = None, 73 | percentage: None | int | float = None) -> None: 74 | super().__init__(title) 75 | self._key = key 76 | self.__call__(message, percentage) 77 | 78 | def __del__(self) -> None: 79 | for window in sublime.windows(): 80 | for view in window.views(): 81 | view.erase_status(self._key) 82 | super().__del__() 83 | 84 | def __call__(self, message: str | None = None, percentage: None | int | float = None) -> None: 85 | super().__call__(message, percentage) 86 | display = self._render() 87 | for window in sublime.windows(): 88 | for view in window.views(): 89 | view.set_status(self._key, display) 90 | -------------------------------------------------------------------------------- /plugin/core/url.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from .constants import ST_INSTALLED_PACKAGES_PATH 3 | from .constants import ST_PACKAGES_PATH 4 | from typing import Any 5 | from typing_extensions import deprecated 6 | from urllib.parse import urljoin 7 | from urllib.parse import urlparse 8 | from urllib.request import pathname2url 9 | from urllib.request import url2pathname 10 | import os 11 | import re 12 | 13 | import sublime 14 | 15 | 16 | def filename_to_uri(file_name: str) -> str: 17 | """ 18 | Convert a file name obtained from view.file_name() into an URI 19 | """ 20 | prefix = ST_INSTALLED_PACKAGES_PATH 21 | if file_name.startswith(prefix): 22 | return _to_resource_uri(file_name, prefix) 23 | prefix = ST_PACKAGES_PATH 24 | if file_name.startswith(prefix) and not os.path.exists(file_name): 25 | return _to_resource_uri(file_name, prefix) 26 | path = pathname2url(file_name) 27 | return urljoin("file:", path) 28 | 29 | 30 | def view_to_uri(view: sublime.View) -> str: 31 | file_name = view.file_name() 32 | if not file_name: 33 | return f"buffer:{view.buffer_id()}" 34 | return filename_to_uri(file_name) 35 | 36 | 37 | @deprecated("Use parse_uri() instead") 38 | def uri_to_filename(uri: str) -> str: 39 | """ 40 | DEPRECATED: An URI associated to a view does not necessarily have a "file:" scheme. 41 | Use parse_uri instead. 42 | """ 43 | scheme, path = parse_uri(uri) 44 | assert scheme == "file" 45 | return path 46 | 47 | 48 | def parse_uri(uri: str) -> tuple[str, str]: 49 | """ 50 | Parses an URI into a tuple where the first element is the URI scheme. The 51 | second element is the local filesystem path if the URI is a file URI, 52 | otherwise the second element is the original URI. 53 | """ 54 | parsed = urlparse(uri) 55 | if parsed.scheme == "file": 56 | path = url2pathname(parsed.path) 57 | if os.name == 'nt': 58 | netloc = url2pathname(parsed.netloc) 59 | path = path.lstrip("\\") 60 | path = re.sub(r"^/([a-zA-Z]:)", r"\1", path) # remove slash preceding drive letter 61 | path = re.sub(r"^([a-z]):", _uppercase_driveletter, path) 62 | if netloc: 63 | # Convert to UNC path 64 | return parsed.scheme, f"\\\\{netloc}\\{path}" 65 | else: 66 | return parsed.scheme, path 67 | return parsed.scheme, path 68 | elif parsed.scheme == '' and ':' in parsed.path.split('/')[0]: 69 | # workaround for bug in urllib.parse.urlparse 70 | return parsed.path.split(':')[0], uri 71 | return parsed.scheme, uri 72 | 73 | 74 | def unparse_uri(parsed_uri: tuple[str, str]) -> str: 75 | """ 76 | Reverse of `parse_uri()`. 77 | """ 78 | scheme, path = parsed_uri 79 | return filename_to_uri(path) if scheme == "file" else path 80 | 81 | 82 | def _to_resource_uri(path: str, prefix: str) -> str: 83 | """ 84 | Terrible hacks from ST core leak into packages as well. 85 | 86 | See: https://github.com/sublimehq/sublime_text/issues/3742 87 | """ 88 | return f"res:/Packages{pathname2url(path[len(prefix):])}" 89 | 90 | 91 | def _uppercase_driveletter(match: Any) -> str: 92 | """ 93 | For compatibility with Sublime's VCS status in the status bar. 94 | """ 95 | return f"{match.group(1).upper()}:" 96 | -------------------------------------------------------------------------------- /plugin/core/edit.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from ...protocol import AnnotatedTextEdit 3 | from ...protocol import Position 4 | from ...protocol import TextEdit 5 | from ...protocol import WorkspaceEdit 6 | from .logging import debug 7 | from .promise import Promise 8 | from .protocol import UINT_MAX 9 | from typing import Dict, List, Optional, Tuple, TypedDict, Union 10 | from typing_extensions import NotRequired 11 | import sublime 12 | 13 | 14 | WorkspaceChanges = Dict[str, Tuple[List[Union[TextEdit, AnnotatedTextEdit]], Optional[str], Optional[int]]] 15 | 16 | 17 | class WorkspaceEditSummary(TypedDict): 18 | total_changes: int 19 | edited_files: int 20 | created_files: NotRequired[int] 21 | renamed_files: NotRequired[int] 22 | deleted_files: NotRequired[int] 23 | 24 | 25 | def parse_workspace_edit(workspace_edit: WorkspaceEdit, label: str | None = None) -> WorkspaceChanges: 26 | changes: WorkspaceChanges = {} 27 | document_changes = workspace_edit.get('documentChanges') 28 | if isinstance(document_changes, list): 29 | change_annotations = workspace_edit.get('changeAnnotations', {}) 30 | for document_change in document_changes: 31 | if 'kind' in document_change: 32 | # TODO: Support resource operations (create/rename/remove) 33 | debug('Ignoring unsupported "resourceOperations" edit type') 34 | continue 35 | text_document = document_change["textDocument"] 36 | uri = text_document['uri'] 37 | version = text_document.get('version') 38 | edits = document_change.get('edits') 39 | for edit in edits: 40 | if 'snippet' in edit: 41 | debug('Ignoring unsupported SnippetTextEdit') 42 | continue 43 | description = change_annotations[id_]['label'] if (id_ := edit.get('annotationId')) else label 44 | # Note that if the WorkspaceEdit contains multiple AnnotatedTextEdit with different labels for the same 45 | # URI, we only show the first label in the undo menu, because all edits are combined into a single 46 | # buffer modification in the lsp_apply_document_edit command. 47 | changes.setdefault(uri, ([], description, version))[0].append(edit) 48 | else: 49 | raw_changes = workspace_edit.get('changes') 50 | if isinstance(raw_changes, dict): 51 | for uri, edits in raw_changes.items(): 52 | changes[uri] = (edits, label, None) 53 | return changes 54 | 55 | 56 | def parse_range(range: Position) -> tuple[int, int]: 57 | return range['line'], min(UINT_MAX, range['character']) 58 | 59 | 60 | def apply_text_edits( 61 | view: sublime.View, 62 | edits: list[TextEdit] | None, 63 | *, 64 | label: str | None = None, 65 | process_placeholders: bool | None = False, 66 | required_view_version: int | None = None 67 | ) -> Promise[sublime.View | None]: 68 | if not edits: 69 | return Promise.resolve(view) 70 | if not view.is_valid(): 71 | print('LSP: ignoring edits due to view not being open') 72 | return Promise.resolve(None) 73 | view.run_command( 74 | 'lsp_apply_document_edit', 75 | { 76 | 'changes': edits, 77 | 'label': label, 78 | 'process_placeholders': process_placeholders, 79 | 'required_view_version': required_view_version, 80 | } 81 | ) 82 | # Resolving from the next message loop iteration guarantees that the edits have already been applied in the main 83 | # thread, and that we’ve received view changes in the asynchronous thread. 84 | return Promise(lambda resolve: sublime.set_timeout_async(lambda: resolve(view if view.is_valid() else None))) 85 | -------------------------------------------------------------------------------- /plugin/core/active_request.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from .sessions import SessionViewProtocol 3 | from .progress import ProgressReporter 4 | from .progress import ViewProgressReporter 5 | from .progress import WindowProgressReporter 6 | from .protocol import Request 7 | from typing import Any 8 | from weakref import ref 9 | import sublime 10 | 11 | 12 | class ActiveRequest: 13 | """ 14 | Holds state per request. 15 | """ 16 | 17 | def __init__(self, sv: SessionViewProtocol, request_id: int, request: Request) -> None: 18 | # sv is the parent object; there is no need to keep it alive explicitly. 19 | self.weaksv = ref(sv) 20 | self.request_id = request_id 21 | self.request = request 22 | self.canceled = False 23 | self.progress: ProgressReporter | None = None 24 | # `request.progress` is either a boolean or a string. If it's a boolean, then that signals that the server does 25 | # not support client-initiated progress. However, for some requests we still want to notify some kind of 26 | # progress to the end-user. This is communicated by the boolean value being "True". 27 | # If `request.progress` is a string, then this string is equal to the workDoneProgress token. In that case, the 28 | # server should start reporting progress for this request. However, even if the server supports workDoneProgress 29 | # then we still don't know for sure whether it will actually start reporting progress. So we still want to 30 | # put a line in the status bar if the request takes a while even if the server promises to report progress. 31 | if request.progress: 32 | # Keep a weak reference because we don't want this delayed function to keep this object alive. 33 | weakself = ref(self) 34 | 35 | def show() -> None: 36 | this = weakself() 37 | # If the server supports client-initiated progress, then it should have sent a "progress begin" 38 | # notification. In that case, `this.progress` should not be None. So if `this.progress` is None 39 | # then the server didn't notify in a timely manner and we will start putting a line in the status bar 40 | # about this request taking a long time (>200ms). 41 | if this is not None and this.progress is None: 42 | # If this object is still alive then that means the request hasn't finished yet after 200ms, 43 | # so put a message in the status bar to notify that this request is still in progress. 44 | this.progress = this._start_progress_reporter_async(this.request.method) 45 | 46 | sublime.set_timeout_async(show, 200) 47 | 48 | def on_request_canceled_async(self) -> None: 49 | self.canceled = True 50 | self.progress = None 51 | 52 | def _start_progress_reporter_async( 53 | self, 54 | title: str, 55 | message: str | None = None, 56 | percentage: float | None = None 57 | ) -> ProgressReporter | None: 58 | sv = self.weaksv() 59 | if not sv: 60 | return None 61 | if self.request.view is not None: 62 | key = f"lspprogressview-{sv.session.config.name}-{self.request.view.id()}-{self.request_id}" 63 | return ViewProgressReporter(self.request.view, key, title, message, percentage) 64 | else: 65 | key = f"lspprogresswindow-{sv.session.config.name}-{sv.session.window.id()}-{self.request_id}" 66 | return WindowProgressReporter(sv.session.window, key, title, message, percentage) 67 | 68 | def update_progress_async(self, params: dict[str, Any]) -> None: 69 | if self.canceled: 70 | return 71 | value = params['value'] 72 | kind = value['kind'] 73 | message = value.get("message") 74 | percentage = value.get("percentage") 75 | if kind == 'begin': 76 | title = value["title"] 77 | # This would potentially overwrite the "manual" progress that activates after 200ms, which is OK. 78 | self.progress = self._start_progress_reporter_async(title, message, percentage) 79 | elif kind == 'report': 80 | if self.progress: 81 | self.progress(message, percentage) 82 | -------------------------------------------------------------------------------- /plugin/execute_command.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from ..protocol import ExecuteCommandParams 3 | from .core.logging import debug 4 | from .core.protocol import Error 5 | from .core.registry import LspTextCommand 6 | from .core.views import first_selection_region 7 | from .core.views import offset_to_point 8 | from .core.views import region_to_range 9 | from .core.views import text_document_identifier 10 | from .core.views import text_document_position_params 11 | from .core.views import uri_from_view 12 | from .core.views import versioned_text_document_identifier 13 | from typing import Any 14 | import sublime 15 | 16 | 17 | class LspExecuteCommand(LspTextCommand): 18 | """ 19 | Helper command for triggering workspace/executeCommand requests. 20 | """ 21 | 22 | def run(self, 23 | edit: sublime.Edit, 24 | command_name: str | None = None, 25 | command_args: list[Any] | None = None, 26 | session_name: str | None = None, 27 | event: dict | None = None) -> None: 28 | session = self.session_by_name(session_name if session_name else self.session_name) 29 | if session and command_name: 30 | params: ExecuteCommandParams = {"command": command_name} 31 | if command_args: 32 | params["arguments"] = self._expand_variables(command_args) 33 | 34 | def handle_response(response: Any) -> None: 35 | assert command_name 36 | if isinstance(response, Error): 37 | self.handle_error_async(response, command_name) 38 | return 39 | self.handle_success_async(response, command_name) 40 | 41 | session.execute_command(params, progress=True, view=self.view).then(handle_response) 42 | 43 | def handle_success_async(self, result: Any, command_name: str) -> None: 44 | """ 45 | Override this method to handle successful response to workspace/executeCommand. 46 | 47 | :param result: The result returned from the server. 48 | :param command_name: The name of the command that was executed. 49 | """ 50 | msg = f"command {command_name} completed" 51 | if window := self.view.window(): 52 | window.status_message(msg) 53 | 54 | def handle_error_async(self, error: Error, command_name: str) -> None: 55 | """ 56 | Override this method to handle failed response to workspace/executeCommand. 57 | 58 | :param error: The Error object. 59 | :param command_name: The name of the command that was executed. 60 | """ 61 | msg = f"command {command_name} failed: {str(error)}" 62 | debug(msg) 63 | if window := self.view.window(): 64 | window.status_message(msg) 65 | 66 | def _expand_variables(self, command_args: list[Any]) -> list[Any]: 67 | view = self.view 68 | region = first_selection_region(view) 69 | for i, arg in enumerate(command_args): 70 | if arg in ["$document_id", "${document_id}"]: 71 | command_args[i] = text_document_identifier(view) 72 | elif arg in ["$versioned_document_id", "${versioned_document_id}"]: 73 | command_args[i] = versioned_text_document_identifier(view, view.change_count()) 74 | elif arg in ["$file_uri", "${file_uri}"]: 75 | command_args[i] = uri_from_view(view) 76 | elif region is not None: 77 | if arg in ["$selection", "${selection}"]: 78 | command_args[i] = view.substr(region) 79 | elif arg in ["$offset", "${offset}"]: 80 | command_args[i] = region.b 81 | elif arg in ["$selection_begin", "${selection_begin}"]: 82 | command_args[i] = region.begin() 83 | elif arg in ["$selection_end", "${selection_end}"]: 84 | command_args[i] = region.end() 85 | elif arg in ["$position", "${position}"]: 86 | command_args[i] = offset_to_point(view, region.b).to_lsp() 87 | elif arg in ["$line", "${line}"]: 88 | command_args[i] = offset_to_point(view, region.b).row 89 | elif arg in ["$character", "${character}"]: 90 | command_args[i] = offset_to_point(view, region.b).col 91 | elif arg in ["$range", "${range}"]: 92 | command_args[i] = region_to_range(view, region) 93 | elif arg in ["$text_document_position", "${text_document_position}"]: 94 | command_args[i] = text_document_position_params(view, region.b) 95 | window = view.window() 96 | window_variables = window.extract_variables() if window else {} 97 | return sublime.expand_variables(command_args, window_variables) 98 | -------------------------------------------------------------------------------- /plugin/core/diagnostics_storage.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from ...protocol import Diagnostic 3 | from ...protocol import DiagnosticSeverity 4 | from ...protocol import DocumentUri 5 | from .url import parse_uri 6 | from .views import diagnostic_severity 7 | from collections import OrderedDict 8 | from typing import Callable, Iterator, Tuple, TypeVar 9 | import functools 10 | 11 | ParsedUri = Tuple[str, str] 12 | T = TypeVar('T') 13 | 14 | 15 | # NOTE: OrderedDict can only be properly typed in Python >=3.8. 16 | class DiagnosticsStorage(OrderedDict): 17 | # From the specs: 18 | # 19 | # When a file changes it is the server’s responsibility to re-compute 20 | # diagnostics and push them to the client. If the computed set is empty 21 | # it has to push the empty array to clear former diagnostics. Newly 22 | # pushed diagnostics always replace previously pushed diagnostics. There 23 | # is no merging that happens on the client side. 24 | # 25 | # https://microsoft.github.io/language-server-protocol/specification#textDocument_publishDiagnostics 26 | 27 | def add_diagnostics_async(self, document_uri: DocumentUri, diagnostics: list[Diagnostic]) -> None: 28 | """ 29 | Add `diagnostics` for `document_uri` to the store, replacing previously received `diagnoscis` 30 | for this `document_uri`. If `diagnostics` is the empty list, `document_uri` is removed from 31 | the store. The item received is moved to the end of the store. 32 | """ 33 | uri = parse_uri(document_uri) 34 | if not diagnostics: 35 | # received "clear diagnostics" message for this uri 36 | self.pop(uri, None) 37 | return 38 | self[uri] = diagnostics 39 | self.move_to_end(uri) # maintain incoming order 40 | 41 | def filter_map_diagnostics_async( 42 | self, pred: Callable[[Diagnostic], bool], f: Callable[[ParsedUri, Diagnostic], T] 43 | ) -> Iterator[tuple[ParsedUri, list[T]]]: 44 | """ 45 | Yields `(uri, results)` items with `results` being a list of `f(diagnostic)` for each 46 | diagnostic for this `uri` with `pred(diagnostic) == True`, filtered by `bool(f(diagnostic))`. 47 | Only `uri`s with non-empty `results` are returned. Each `uri` is guaranteed to be yielded 48 | not more than once. Items and results are ordered as they came in from the server. 49 | """ 50 | for uri, diagnostics in self.items(): 51 | results: list[T] = list(filter(None, map(functools.partial(f, uri), filter(pred, diagnostics)))) 52 | if results: 53 | yield uri, results 54 | 55 | def filter_map_diagnostics_flat_async(self, pred: Callable[[Diagnostic], bool], 56 | f: Callable[[ParsedUri, Diagnostic], T]) -> Iterator[tuple[ParsedUri, T]]: 57 | """ 58 | Flattened variant of `filter_map_diagnostics_async()`. Yields `(uri, result)` items for each 59 | of the `result`s per `uri` instead. Each `uri` can be yielded more than once. Items are 60 | grouped by `uri` and each `uri` group is guaranteed to appear not more than once. Items are 61 | ordered as they came in from the server. 62 | """ 63 | for uri, results in self.filter_map_diagnostics_async(pred, f): 64 | for result in results: 65 | yield uri, result 66 | 67 | def sum_total_errors_and_warnings_async(self) -> tuple[int, int]: 68 | """ 69 | Returns `(total_errors, total_warnings)` count of all diagnostics currently in store. 70 | """ 71 | return ( 72 | sum(map(severity_count(DiagnosticSeverity.Error), self.values())), 73 | sum(map(severity_count(DiagnosticSeverity.Warning), self.values())), 74 | ) 75 | 76 | def diagnostics_by_document_uri(self, document_uri: DocumentUri) -> list[Diagnostic]: 77 | """ 78 | Returns possibly empty list of diagnostic for `document_uri`. 79 | """ 80 | return self.get(parse_uri(document_uri), []) 81 | 82 | def diagnostics_by_parsed_uri(self, uri: ParsedUri) -> list[Diagnostic]: 83 | """ 84 | Returns possibly empty list of diagnostic for `uri`. 85 | """ 86 | return self.get(uri, []) 87 | 88 | 89 | def severity_count(severity: int) -> Callable[[list[Diagnostic]], int]: 90 | def severity_count(diagnostics: list[Diagnostic]) -> int: 91 | return len(list(filter(has_severity(severity), diagnostics))) 92 | 93 | return severity_count 94 | 95 | 96 | def has_severity(severity: int) -> Callable[[Diagnostic], bool]: 97 | def has_severity(diagnostic: Diagnostic) -> bool: 98 | return diagnostic_severity(diagnostic) == severity 99 | 100 | return has_severity 101 | 102 | 103 | def is_severity_included(max_severity: int) -> Callable[[Diagnostic], bool]: 104 | def severity_included(diagnostic: Diagnostic) -> bool: 105 | return diagnostic_severity(diagnostic) <= max_severity 106 | 107 | return severity_included 108 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |

LSP

3 |

4 | 5 |

6 | 7 | 8 | 9 | 10 | 11 |

12 | 13 |

Like an IDE, except it's the good parts. Learn more.

14 | 15 |

TypeScript Server Example

16 | 17 | ## Installation 18 | 19 | ### Stable Version 20 | 21 | Open the command palette and run `Package Control: Install Package`, then select `LSP`. 22 | 23 | ### Development Version 24 | 25 | Clone this repository into your Packages directory. Open the command palette and run `Package Control: Satisfy Libraries`. 26 | 27 | ## Getting started 28 | 29 | Follow the installation steps for a [specific language server](https://lsp.sublimetext.io/language_servers). 30 | 31 | Open a document supported by the language server. LSP should report the language server starting in the status bar. 32 | 33 | See more information in the [documentation](https://lsp.sublimetext.io) :open_book:. 34 | 35 | ## Getting help 36 | 37 | If you have any problems, see the [troubleshooting](https://sublimelsp.github.io/LSP/troubleshooting/) guide for tips and known limitations. If the documentation cannot solve your problem, you can look for help in: 38 | 39 | * The [#lsp](https://discordapp.com/channels/280102180189634562/645268178397560865) channel (join the [SublimeHQ Discord](https://discord.gg/TZ5WN8t) first!) 40 | * By [searching or creating a new issue](https://github.com/sublimelsp/LSP/issues) 41 | -------------------------------------------------------------------------------- /plugin/panels.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from .core.panels import LOG_LINES_LIMIT_SETTING_NAME 3 | from .core.panels import PanelName 4 | from .core.registry import windows 5 | from contextlib import contextmanager 6 | from sublime_plugin import WindowCommand 7 | from typing import Generator 8 | import sublime 9 | import sublime_plugin 10 | 11 | 12 | @contextmanager 13 | def mutable(view: sublime.View) -> Generator: 14 | view.set_read_only(False) 15 | yield 16 | view.set_read_only(True) 17 | 18 | 19 | def clear_undo_stack(view: sublime.View) -> None: 20 | clear_undo_stack = getattr(view, "clear_undo_stack", None) 21 | if not callable(clear_undo_stack): 22 | return 23 | # The clear_undo_stack method cannot be called from within a text command... 24 | sublime.set_timeout(clear_undo_stack) 25 | 26 | 27 | class LspToggleServerPanelCommand(WindowCommand): 28 | def run(self) -> None: 29 | wm = windows.lookup(self.window) 30 | if not wm: 31 | return 32 | panel_manager = wm.panel_manager 33 | if not panel_manager: 34 | return 35 | panel_manager.ensure_log_panel() 36 | panel_manager.toggle_output_panel(PanelName.Log) 37 | 38 | 39 | class LspShowDiagnosticsPanelCommand(WindowCommand): 40 | def run(self) -> None: 41 | wm = windows.lookup(self.window) 42 | if not wm: 43 | return 44 | panel_manager = wm.panel_manager 45 | if not panel_manager: 46 | return 47 | panel_manager.ensure_diagnostics_panel() 48 | panel_manager.toggle_output_panel(PanelName.Diagnostics) 49 | 50 | 51 | class LspToggleLogPanelLinesLimitCommand(sublime_plugin.TextCommand): 52 | @classmethod 53 | def is_limit_enabled(cls, window: sublime.Window | None) -> bool: 54 | wm = windows.lookup(window) 55 | return bool(wm and wm.is_log_lines_limit_enabled()) 56 | 57 | @classmethod 58 | def get_lines_limit(cls, window: sublime.Window | None) -> int: 59 | wm = windows.lookup(window) 60 | return wm.get_log_lines_limit() if wm else 0 61 | 62 | def is_checked(self) -> bool: 63 | return self.is_limit_enabled(self.view.window()) 64 | 65 | def run(self, edit: sublime.Edit) -> None: 66 | wm = windows.lookup(self.view.window()) 67 | if not wm: 68 | return 69 | if panel := wm.panel_manager and wm.panel_manager.get_panel(PanelName.Log): 70 | settings = panel.settings() 71 | settings.set(LOG_LINES_LIMIT_SETTING_NAME, not self.is_limit_enabled(wm.window)) 72 | 73 | 74 | class LspClearPanelCommand(sublime_plugin.TextCommand): 75 | """ 76 | A clear_panel command to clear the error panel. 77 | """ 78 | def run(self, edit: sublime.Edit) -> None: 79 | with mutable(self.view): 80 | self.view.erase(edit, sublime.Region(0, self.view.size())) 81 | 82 | 83 | class LspUpdatePanelCommand(sublime_plugin.TextCommand): 84 | """ 85 | A update_panel command to update the error panel with new text. 86 | """ 87 | 88 | def run(self, edit: sublime.Edit, characters: str | None = "") -> None: 89 | # Clear folds 90 | self.view.unfold(sublime.Region(0, self.view.size())) 91 | 92 | with mutable(self.view): 93 | self.view.replace(edit, sublime.Region(0, self.view.size()), characters or "") 94 | 95 | # Clear the selection 96 | selection = self.view.sel() 97 | selection.clear() 98 | clear_undo_stack(self.view) 99 | 100 | 101 | class LspUpdateLogPanelCommand(sublime_plugin.TextCommand): 102 | 103 | def run(self, edit: sublime.Edit) -> None: 104 | wm = windows.lookup(self.view.window()) 105 | if not wm: 106 | return 107 | with mutable(self.view): 108 | new_lines = [] 109 | for prefix, message in wm.get_and_clear_server_log(): 110 | message = message.replace("\r\n", "\n") # normalize Windows eol 111 | new_lines.append(f"{prefix}: {message}\n") 112 | if new_lines: 113 | self.view.insert(edit, self.view.size(), ''.join(new_lines)) 114 | last_region_end = 0 # Starting from point 0 in the panel ... 115 | total_lines, _ = self.view.rowcol(self.view.size()) 116 | max_lines = LspToggleLogPanelLinesLimitCommand.get_lines_limit(self.view.window()) 117 | for _ in range(0, max(0, total_lines - max_lines)): 118 | # ... collect all regions that span an entire line ... 119 | region = self.view.full_line(last_region_end) 120 | last_region_end = region.b 121 | erase_region = sublime.Region(0, last_region_end) 122 | if not erase_region.empty(): 123 | self.view.erase(edit, erase_region) 124 | clear_undo_stack(self.view) 125 | 126 | 127 | class LspClearLogPanelCommand(sublime_plugin.TextCommand): 128 | def run(self, edit: sublime.Edit) -> None: 129 | wm = windows.lookup(self.view.window()) 130 | if not wm: 131 | return 132 | if panel := wm.panel_manager and wm.panel_manager.ensure_log_panel(): 133 | panel.run_command("lsp_clear_panel") 134 | -------------------------------------------------------------------------------- /plugin/goto.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from ..protocol import Location 3 | from ..protocol import LocationLink 4 | from .core.protocol import Request 5 | from .core.registry import get_position 6 | from .core.registry import LspTextCommand 7 | from .core.sessions import Session, method_to_capability 8 | from .core.views import get_symbol_kind_from_scope 9 | from .core.views import text_document_position_params 10 | from .locationpicker import LocationPicker 11 | from .locationpicker import open_location_async 12 | from functools import partial 13 | import sublime 14 | 15 | 16 | class LspGotoCommand(LspTextCommand): 17 | 18 | method = '' 19 | placeholder_text = '' 20 | fallback_command = '' 21 | 22 | def is_enabled( 23 | self, 24 | event: dict | None = None, 25 | point: int | None = None, 26 | side_by_side: bool = False, 27 | force_group: bool = True, 28 | fallback: bool = False, 29 | group: int = -1 30 | ) -> bool: 31 | return fallback or super().is_enabled(event, point) 32 | 33 | def is_visible( 34 | self, 35 | event: dict | None = None, 36 | point: int | None = None, 37 | side_by_side: bool = False, 38 | force_group: bool = True, 39 | fallback: bool = False, 40 | group: int = -1 41 | ) -> bool: 42 | if self.applies_to_context_menu(event): 43 | return self.is_enabled(event, point, side_by_side, force_group, fallback, group) 44 | return True 45 | 46 | def run( 47 | self, 48 | _: sublime.Edit, 49 | event: dict | None = None, 50 | point: int | None = None, 51 | side_by_side: bool = False, 52 | force_group: bool = True, 53 | fallback: bool = False, 54 | group: int = -1 55 | ) -> None: 56 | position = get_position(self.view, event, point) 57 | session = self.best_session(self.capability, position) 58 | if session and position is not None: 59 | params = text_document_position_params(self.view, position) 60 | request = Request(self.method, params, self.view, progress=True) 61 | session.send_request( 62 | request, 63 | partial(self._handle_response_async, session, side_by_side, force_group, fallback, group, position) 64 | ) 65 | else: 66 | self._handle_no_results(fallback, side_by_side) 67 | 68 | def _handle_response_async( 69 | self, 70 | session: Session, 71 | side_by_side: bool, 72 | force_group: bool, 73 | fallback: bool, 74 | group: int, 75 | position: int, 76 | response: None | Location | list[Location] | list[LocationLink] 77 | ) -> None: 78 | if isinstance(response, dict): 79 | self.view.run_command("add_jump_record", {"selection": [(r.a, r.b) for r in self.view.sel()]}) 80 | open_location_async(session, response, side_by_side, force_group, group) 81 | elif isinstance(response, list): 82 | if len(response) == 0: 83 | self._handle_no_results(fallback, side_by_side) 84 | elif len(response) == 1: 85 | self.view.run_command("add_jump_record", {"selection": [(r.a, r.b) for r in self.view.sel()]}) 86 | open_location_async(session, response[0], side_by_side, force_group, group) 87 | else: 88 | self.view.run_command("add_jump_record", {"selection": [(r.a, r.b) for r in self.view.sel()]}) 89 | placeholder = self.placeholder_text + " " + self.view.substr(self.view.word(position)) 90 | kind = get_symbol_kind_from_scope(self.view.scope_name(position)) 91 | sublime.set_timeout( 92 | partial(LocationPicker, 93 | self.view, session, response, side_by_side, force_group, group, placeholder, kind) 94 | ) 95 | else: 96 | self._handle_no_results(fallback, side_by_side) 97 | 98 | def _handle_no_results(self, fallback: bool = False, side_by_side: bool = False) -> None: 99 | if window := self.view.window(): 100 | if fallback and self.fallback_command: 101 | window.run_command(self.fallback_command, {"side_by_side": side_by_side}) 102 | else: 103 | window.status_message("No results found") 104 | 105 | 106 | class LspSymbolDefinitionCommand(LspGotoCommand): 107 | method = "textDocument/definition" 108 | capability = method_to_capability(method)[0] 109 | placeholder_text = "Definitions of" 110 | fallback_command = "goto_definition" 111 | 112 | 113 | class LspSymbolTypeDefinitionCommand(LspGotoCommand): 114 | method = "textDocument/typeDefinition" 115 | capability = method_to_capability(method)[0] 116 | placeholder_text = "Type Definitions of" 117 | 118 | 119 | class LspSymbolDeclarationCommand(LspGotoCommand): 120 | method = "textDocument/declaration" 121 | capability = method_to_capability(method)[0] 122 | placeholder_text = "Declarations of" 123 | 124 | 125 | class LspSymbolImplementationCommand(LspGotoCommand): 126 | method = "textDocument/implementation" 127 | capability = method_to_capability(method)[0] 128 | placeholder_text = "Implementations of" 129 | -------------------------------------------------------------------------------- /plugin/save_command.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from .core.registry import LspTextCommand 3 | from .core.settings import userprefs 4 | from abc import ABCMeta, abstractmethod 5 | from functools import partial 6 | from typing import Any, Callable 7 | import sublime 8 | import sublime_plugin 9 | 10 | 11 | class SaveTask(metaclass=ABCMeta): 12 | """ 13 | Base class for tasks that run on save. 14 | 15 | Note: The whole task runs on the async thread. 16 | """ 17 | 18 | @classmethod 19 | @abstractmethod 20 | def is_applicable(cls, view: sublime.View) -> bool: 21 | pass 22 | 23 | def __init__(self, task_runner: LspTextCommand, on_done: Callable[[], None]): 24 | self._task_runner = task_runner 25 | self._on_done = on_done 26 | self._completed = False 27 | self._cancelled = False 28 | self._status_key = type(self).__name__ 29 | 30 | def run_async(self) -> None: 31 | self._erase_view_status() 32 | sublime.set_timeout_async(self._on_timeout, userprefs().on_save_task_timeout_ms) 33 | 34 | def _on_timeout(self) -> None: 35 | if not self._completed and not self._cancelled: 36 | self._set_view_status(f'LSP: Timeout processing {self.__class__.__name__}') 37 | self._cancelled = True 38 | self._on_done() 39 | 40 | def cancel(self) -> None: 41 | self._cancelled = True 42 | 43 | def _set_view_status(self, text: str) -> None: 44 | self._task_runner.view.set_status(self._status_key, text) 45 | sublime.set_timeout_async(self._erase_view_status, 5000) 46 | 47 | def _erase_view_status(self) -> None: 48 | self._task_runner.view.erase_status(self._status_key) 49 | 50 | def _on_complete(self) -> None: 51 | assert not self._completed 52 | self._completed = True 53 | if not self._cancelled: 54 | self._on_done() 55 | 56 | def _purge_changes_async(self) -> None: 57 | if listener := self._task_runner.get_listener(): 58 | listener.purge_changes_async() 59 | 60 | 61 | class SaveTasksRunner: 62 | def __init__( 63 | self, text_command: LspTextCommand, tasks: list[type[SaveTask]], on_complete: Callable[[], None] 64 | ) -> None: 65 | self._text_command = text_command 66 | self._tasks = tasks 67 | self._on_tasks_completed = on_complete 68 | self._pending_tasks: list[SaveTask] = [] 69 | self._canceled = False 70 | 71 | def run(self) -> None: 72 | for task in self._tasks: 73 | if task.is_applicable(self._text_command.view): 74 | self._pending_tasks.append(task(self._text_command, self._on_task_completed_async)) 75 | self._process_next_task() 76 | 77 | def cancel(self) -> None: 78 | for task in self._pending_tasks: 79 | task.cancel() 80 | self._pending_tasks = [] 81 | self._canceled = True 82 | 83 | def _process_next_task(self) -> None: 84 | if self._pending_tasks: 85 | # Even though we might be on an async thread already, we want to give ST a chance to notify us about 86 | # potential document changes. 87 | sublime.set_timeout_async(self._run_next_task_async) 88 | else: 89 | self._on_tasks_completed() 90 | 91 | def _run_next_task_async(self) -> None: 92 | if self._canceled: 93 | return 94 | current_task = self._pending_tasks[0] 95 | current_task.run_async() 96 | 97 | def _on_task_completed_async(self) -> None: 98 | self._pending_tasks.pop(0) 99 | self._process_next_task() 100 | 101 | 102 | class LspSaveCommand(LspTextCommand): 103 | """ 104 | A command used as a substitute for native save command. Runs code actions and document 105 | formatting before triggering the native save command. 106 | """ 107 | _tasks: list[type[SaveTask]] = [] 108 | 109 | @classmethod 110 | def register_task(cls, task: type[SaveTask]) -> None: 111 | assert task not in cls._tasks 112 | cls._tasks.append(task) 113 | 114 | def __init__(self, view: sublime.View) -> None: 115 | super().__init__(view) 116 | self._save_tasks_runner: SaveTasksRunner | None = None 117 | 118 | def run(self, edit: sublime.Edit, **kwargs: dict[str, Any]) -> None: 119 | if self._save_tasks_runner: 120 | self._save_tasks_runner.cancel() 121 | sublime.set_timeout_async(self._trigger_on_pre_save_async) 122 | self._save_tasks_runner = SaveTasksRunner(self, self._tasks, partial(self._on_tasks_completed, kwargs)) 123 | self._save_tasks_runner.run() 124 | 125 | def _trigger_on_pre_save_async(self) -> None: 126 | if listener := self.get_listener(): 127 | listener.trigger_on_pre_save_async() 128 | 129 | def _on_tasks_completed(self, kwargs: dict[str, Any]) -> None: 130 | self._save_tasks_runner = None 131 | # Triggered from set_timeout to preserve original semantics of on_pre_save handling 132 | sublime.set_timeout(lambda: self.view.run_command('save', kwargs)) 133 | 134 | 135 | class LspSaveAllCommand(sublime_plugin.WindowCommand): 136 | def run(self, only_files: bool = False) -> None: 137 | done = set() 138 | for view in self.window.views(): 139 | buffer_id = view.buffer_id() 140 | if buffer_id in done: 141 | continue 142 | if not view.is_dirty(): 143 | continue 144 | if only_files and view.file_name() is None: 145 | continue 146 | done.add(buffer_id) 147 | view.run_command("lsp_save", {'async': True}) 148 | -------------------------------------------------------------------------------- /plugin/core/panels.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from .types import PANEL_FILE_REGEX 3 | from .types import PANEL_LINE_REGEX 4 | from typing import Iterable 5 | import sublime 6 | 7 | 8 | LOG_LINES_LIMIT_SETTING_NAME = 'lsp_limit_lines' 9 | MAX_LOG_LINES_LIMIT_ON = 500 10 | MAX_LOG_LINES_LIMIT_OFF = 10000 11 | OUTPUT_PANEL_SETTINGS = { 12 | "auto_indent": False, 13 | "draw_indent_guides": False, 14 | "draw_unicode_white_space": "none", 15 | "draw_white_space": "none", 16 | "fold_buttons": True, 17 | "gutter": True, 18 | "is_widget": True, 19 | "line_numbers": False, 20 | "lsp_active": True, 21 | "margin": 3, 22 | "match_brackets": False, 23 | "rulers": [], 24 | "scroll_past_end": False, 25 | "show_definitions": False, 26 | "tab_size": 4, 27 | "translate_tabs_to_spaces": False, 28 | "word_wrap": False 29 | } 30 | 31 | 32 | class PanelName: 33 | Diagnostics = "diagnostics" 34 | References = "references" 35 | Rename = "rename" 36 | Log = "LSP Log Panel" 37 | 38 | 39 | class PanelManager: 40 | def __init__(self, window: sublime.Window) -> None: 41 | self._window = window 42 | self._rename_panel_buttons: sublime.PhantomSet | None = None 43 | 44 | def destroy_output_panels(self) -> None: 45 | for field in filter(lambda a: not a.startswith('__'), PanelName.__dict__.keys()): 46 | panel_name = getattr(PanelName, field) 47 | panel = self._window.find_output_panel(panel_name) 48 | if panel and panel.is_valid(): 49 | panel.settings().set("syntax", "Packages/Text/Plain text.tmLanguage") 50 | self._window.destroy_output_panel(panel_name) 51 | self._rename_panel_buttons = None 52 | 53 | def toggle_output_panel(self, panel_type: str) -> None: 54 | panel_name = f"output.{panel_type}" 55 | command = "hide_panel" if self.is_panel_open(panel_type) else "show_panel" 56 | self._window.run_command(command, {"panel": panel_name}) 57 | 58 | def is_panel_open(self, panel_name: str) -> bool: 59 | return self._window.is_valid() and self._window.active_panel() == f"output.{panel_name}" 60 | 61 | def update_log_panel(self, scroll_to_selection: bool = False) -> None: 62 | panel = self.ensure_log_panel() 63 | if panel and self.is_panel_open(PanelName.Log): 64 | panel.run_command("lsp_update_log_panel") 65 | if scroll_to_selection: 66 | panel.show(panel.sel(), animate=False) 67 | 68 | def ensure_panel(self, name: str, result_file_regex: str, result_line_regex: str, 69 | syntax: str, context_menu: str | None = None) -> sublime.View | None: 70 | return self.get_panel(name) or \ 71 | self._create_panel(name, result_file_regex, result_line_regex, syntax, context_menu) 72 | 73 | def ensure_diagnostics_panel(self) -> sublime.View | None: 74 | return self.ensure_panel("diagnostics", PANEL_FILE_REGEX, PANEL_LINE_REGEX, 75 | "Packages/LSP/Syntaxes/Diagnostics.sublime-syntax") 76 | 77 | def ensure_log_panel(self) -> sublime.View | None: 78 | return self.ensure_panel(PanelName.Log, "", "", "Packages/LSP/Syntaxes/ServerLog.sublime-syntax", 79 | "Context LSP Log Panel.sublime-menu") 80 | 81 | def ensure_references_panel(self) -> sublime.View | None: 82 | return self.ensure_panel("references", PANEL_FILE_REGEX, PANEL_LINE_REGEX, 83 | "Packages/LSP/Syntaxes/References.sublime-syntax") 84 | 85 | def ensure_rename_panel(self) -> sublime.View | None: 86 | return self.ensure_panel(PanelName.Rename, PANEL_FILE_REGEX, PANEL_LINE_REGEX, 87 | "Packages/LSP/Syntaxes/References.sublime-syntax") 88 | 89 | def get_panel(self, panel_name: str) -> sublime.View | None: 90 | return self._window.find_output_panel(panel_name) 91 | 92 | def _create_panel(self, name: str, result_file_regex: str, result_line_regex: str, 93 | syntax: str, context_menu: str | None = None) -> sublime.View | None: 94 | panel = self.create_output_panel(name) 95 | if not panel: 96 | return None 97 | if name == PanelName.Rename: 98 | self._rename_panel_buttons = sublime.PhantomSet(panel, "lsp_rename_buttons") 99 | settings = panel.settings() 100 | if result_file_regex: 101 | settings.set("result_file_regex", result_file_regex) 102 | if result_line_regex: 103 | settings.set("result_line_regex", result_line_regex) 104 | if context_menu: 105 | settings.set("context_menu", context_menu) 106 | panel.assign_syntax(syntax) 107 | # Call create_output_panel a second time after assigning the above settings, so that it'll be picked up 108 | # as a result buffer. See: Packages/Default/exec.py#L228-L230 109 | panel = self._window.create_output_panel(name) 110 | if panel: 111 | # All our panels are read-only 112 | panel.set_read_only(True) 113 | return panel 114 | 115 | def create_output_panel(self, name: str) -> sublime.View | None: 116 | panel = self._window.create_output_panel(name) 117 | settings = panel.settings() 118 | for key, value in OUTPUT_PANEL_SETTINGS.items(): 119 | settings.set(key, value) 120 | return panel 121 | 122 | def show_diagnostics_panel_async(self) -> None: 123 | if self._window.active_panel() is None: 124 | self.toggle_output_panel(PanelName.Diagnostics) 125 | 126 | def hide_diagnostics_panel_async(self) -> None: 127 | if self.is_panel_open(PanelName.Diagnostics): 128 | self.toggle_output_panel(PanelName.Diagnostics) 129 | 130 | def update_rename_panel_buttons(self, phantoms: Iterable[sublime.Phantom]) -> None: 131 | if self._rename_panel_buttons: 132 | self._rename_panel_buttons.update(phantoms) 133 | -------------------------------------------------------------------------------- /plugin/core/configurations.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from .logging import debug 3 | from .logging import exception_log 4 | from .logging import printf 5 | from .types import ClientConfig 6 | from .url import parse_uri 7 | from .workspace import enable_in_project, disable_in_project 8 | from abc import ABCMeta 9 | from abc import abstractmethod 10 | from collections import deque 11 | from datetime import datetime, timedelta 12 | from typing import Generator 13 | from weakref import WeakSet 14 | import sublime 15 | 16 | 17 | RETRY_MAX_COUNT = 5 18 | RETRY_COUNT_TIMEDELTA = timedelta(minutes=3) 19 | 20 | 21 | class WindowConfigChangeListener(metaclass=ABCMeta): 22 | 23 | @abstractmethod 24 | def on_configs_changed(self, configs: list[ClientConfig]) -> None: 25 | raise NotImplementedError 26 | 27 | 28 | class WindowConfigManager: 29 | def __init__(self, window: sublime.Window, global_configs: dict[str, ClientConfig]) -> None: 30 | self._window = window 31 | self._global_configs = global_configs 32 | self._disabled_for_session: set[str] = set() 33 | self._crashes: dict[str, deque[datetime]] = {} 34 | self.all: dict[str, ClientConfig] = {} 35 | self._change_listeners: WeakSet[WindowConfigChangeListener] = WeakSet() 36 | self._reload_configs(notify_listeners=False) 37 | 38 | def add_change_listener(self, listener: WindowConfigChangeListener) -> None: 39 | self._change_listeners.add(listener) 40 | 41 | def get_configs(self) -> list[ClientConfig]: 42 | return sorted(self.all.values(), key=lambda config: config.name) 43 | 44 | def match_view(self, view: sublime.View, include_disabled: bool = False) -> Generator[ClientConfig, None, None]: 45 | """ 46 | Yields configurations where: 47 | 48 | - the configuration's "selector" matches with the view's base scope, and 49 | - the view's URI scheme is an element of the configuration's "schemes". 50 | """ 51 | try: 52 | uri = view.settings().get("lsp_uri") 53 | if not isinstance(uri, str): 54 | return 55 | scheme = parse_uri(uri)[0] 56 | for config in self.all.values(): 57 | if (config.enabled or include_disabled) and config.match_view(view, scheme): 58 | yield config 59 | except (IndexError, RuntimeError): 60 | pass 61 | 62 | def update(self, updated_config_name: str | None = None) -> None: 63 | self._reload_configs(updated_config_name, notify_listeners=True) 64 | 65 | def _reload_configs(self, updated_config_name: str | None = None, notify_listeners: bool = False) -> None: 66 | project_data = self._window.project_data() 67 | project_settings = project_data.get("settings", {}).get("LSP", {}) if isinstance(project_data, dict) else {} 68 | updated_configs: list[ClientConfig] = [] 69 | if updated_config_name is None: 70 | self.all.clear() 71 | for name, config in self._global_configs.items(): 72 | if updated_config_name and updated_config_name != name: 73 | continue 74 | overrides = project_settings.pop(name, None) 75 | if isinstance(overrides, dict): 76 | debug("applying .sublime-project override for", name) 77 | else: 78 | overrides = {} 79 | if name in self._disabled_for_session: 80 | overrides["enabled"] = False 81 | updated_config = ClientConfig.from_config(config, overrides) 82 | self.all[name] = updated_config 83 | updated_configs.append(updated_config) 84 | for name, c in project_settings.items(): 85 | if updated_config_name and updated_config_name != name: 86 | continue 87 | debug("loading project-only configuration", name) 88 | try: 89 | updated_config = ClientConfig.from_dict(name, c) 90 | self.all[name] = updated_config 91 | updated_configs.append(updated_config) 92 | except Exception as ex: 93 | exception_log(f"failed to load project-only configuration {name}", ex) 94 | if notify_listeners: 95 | for listener in self._change_listeners: 96 | listener.on_configs_changed(updated_configs) 97 | 98 | def enable_config(self, config_name: str) -> None: 99 | if not self._reenable_disabled_for_session(config_name): 100 | enable_in_project(self._window, config_name) 101 | self.update(config_name) 102 | 103 | def disable_config(self, config_name: str, only_for_session: bool = False) -> None: 104 | if only_for_session: 105 | self._disabled_for_session.add(config_name) 106 | else: 107 | disable_in_project(self._window, config_name) 108 | self.update(config_name) 109 | 110 | def record_crash(self, config_name: str, exit_code: int, exception: Exception | None) -> bool: 111 | """ 112 | Signal that a session has crashed. 113 | 114 | Returns True if the session should be restarted automatically. 115 | """ 116 | if config_name not in self._crashes: 117 | self._crashes[config_name] = deque(maxlen=RETRY_MAX_COUNT) 118 | now = datetime.now() 119 | self._crashes[config_name].append(now) 120 | timeout = now - RETRY_COUNT_TIMEDELTA 121 | crash_count = len([crash for crash in self._crashes[config_name] if crash > timeout]) 122 | printf("{} crashed ({} / {} times in the last {} seconds), exit code {}, exception: {}".format( 123 | config_name, crash_count, RETRY_MAX_COUNT, RETRY_COUNT_TIMEDELTA.total_seconds(), exit_code, exception)) 124 | return crash_count < RETRY_MAX_COUNT 125 | 126 | def _reenable_disabled_for_session(self, config_name: str) -> bool: 127 | try: 128 | self._disabled_for_session.remove(config_name) 129 | return True 130 | except KeyError: 131 | return False 132 | -------------------------------------------------------------------------------- /Default.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "Preferences: LSP Settings", 4 | "command": "edit_settings", 5 | "args": { 6 | "base_file": "${packages}/LSP/LSP.sublime-settings", 7 | "default": "// Settings in here override those in \"LSP/LSP.sublime-settings\"\n{\n\t$0\n}\n" 8 | } 9 | }, 10 | { 11 | "caption": "Preferences: LSP Key Bindings", 12 | "command": "edit_settings", 13 | "args": { 14 | "base_file": "${packages}/LSP/Default.sublime-keymap", 15 | "user_file": "${packages}/User/Default ($platform).sublime-keymap", 16 | "default": "[\n\t$0\n]\n", 17 | } 18 | }, 19 | { 20 | "caption": "Preferences: LSP Utils Settings", 21 | "command": "edit_settings", 22 | "args": { 23 | "base_file": "${packages}/LSP/lsp_utils.sublime-settings", 24 | "default": "// Settings in here override those in \"LSP/lsp_utils.sublime-settings\"\n{\n\t$0\n}\n" 25 | } 26 | }, 27 | { 28 | "caption": "LSP Development: Parse VSCode Package JSON From Clipboard", 29 | "command": "lsp_parse_vscode_package_json" 30 | }, 31 | { 32 | "caption": "LSP: Troubleshoot Server", 33 | "command": "lsp_troubleshoot_server" 34 | }, 35 | { 36 | "caption": "LSP: Dump Window Configs", 37 | "command": "lsp_dump_window_configs" 38 | }, 39 | { 40 | "caption": "LSP: Enable Language Server Globally", 41 | "command": "lsp_enable_language_server_globally", 42 | }, 43 | { 44 | "caption": "LSP: Disable Language Server Globally", 45 | "command": "lsp_disable_language_server_globally", 46 | }, 47 | { 48 | "caption": "LSP: Enable Language Server in Project", 49 | "command": "lsp_enable_language_server_in_project", 50 | }, 51 | { 52 | "caption": "LSP: Disable Language Server in Project", 53 | "command": "lsp_disable_language_server_in_project", 54 | }, 55 | { 56 | "caption": "LSP: Format File", 57 | "command": "lsp_format_document", 58 | }, 59 | { 60 | "caption": "LSP: Format File With", 61 | "command": "lsp_format_document", 62 | "args": {"select": true} 63 | }, 64 | { 65 | "caption": "LSP: Format Selection", 66 | "command": "lsp_format_document_range", 67 | }, 68 | { 69 | "caption": "LSP: Expand Selection", 70 | "command": "lsp_expand_selection", 71 | }, 72 | { 73 | "caption": "LSP: Restart Server", 74 | "command": "lsp_restart_server", 75 | }, 76 | { 77 | "caption": "LSP: Goto Symbol", 78 | "command": "lsp_document_symbols", 79 | }, 80 | { 81 | "caption": "LSP: Goto Symbol in Project", 82 | "command": "lsp_workspace_symbols" 83 | }, 84 | { 85 | "caption": "LSP: Goto Definition", 86 | "command": "lsp_symbol_definition" 87 | }, 88 | { 89 | "caption": "LSP: Goto Type Definition", 90 | "command": "lsp_symbol_type_definition" 91 | }, 92 | { 93 | "caption": "LSP: Goto Declaration", 94 | "command": "lsp_symbol_declaration", 95 | }, 96 | { 97 | "caption": "LSP: Goto Implementation", 98 | "command": "lsp_symbol_implementation", 99 | }, 100 | { 101 | "caption": "LSP: Goto Diagnostic", 102 | "command": "lsp_goto_diagnostic", 103 | "args": { 104 | "uri": "$view_uri" 105 | } 106 | }, 107 | { 108 | "caption": "LSP: Goto Diagnostic in Project", 109 | "command": "lsp_goto_diagnostic" 110 | }, 111 | { 112 | "caption": "LSP: Find References", 113 | "command": "lsp_symbol_references" 114 | }, 115 | { 116 | "caption": "LSP: Find References (Output Panel)", 117 | "command": "lsp_symbol_references", 118 | "args": { 119 | "output_mode": "output_panel" 120 | }, 121 | }, 122 | { 123 | "caption": "LSP: Find References (Quick Panel)", 124 | "command": "lsp_symbol_references", 125 | "args": { 126 | "output_mode": "quick_panel" 127 | }, 128 | }, 129 | { 130 | "caption": "LSP: Follow Link", 131 | "command": "lsp_open_link" 132 | }, 133 | { 134 | "caption": "LSP: Toggle Log Panel", 135 | "command": "lsp_toggle_server_panel", 136 | }, 137 | { 138 | "caption": "LSP: Toggle Diagnostics Panel", 139 | "command": "lsp_show_diagnostics_panel" 140 | }, 141 | { 142 | "caption": "LSP: Rename", 143 | "command": "lsp_symbol_rename" 144 | }, 145 | { 146 | "caption": "LSP: Code Action", 147 | "command": "lsp_code_actions" 148 | }, 149 | { 150 | "caption": "LSP: Refactor", 151 | "command": "lsp_code_actions", 152 | "args": { 153 | "only_kinds": [ 154 | "refactor" 155 | ] 156 | }, 157 | }, 158 | { 159 | "caption": "LSP: Source Action", 160 | "command": "lsp_code_actions", 161 | "args": { 162 | "only_kinds": [ 163 | "source" 164 | ] 165 | } 166 | }, 167 | { 168 | "caption": "LSP: Run Code Lens", 169 | "command": "lsp_code_lens" 170 | }, 171 | { 172 | "caption": "LSP: Show Call Hierarchy", 173 | "command": "lsp_call_hierarchy" 174 | }, 175 | { 176 | "caption": "LSP: Show Type Hierarchy", 177 | "command": "lsp_type_hierarchy" 178 | }, 179 | { 180 | "caption": "LSP: Toggle Code Lenses", 181 | "command": "lsp_toggle_code_lenses", 182 | }, 183 | { 184 | "caption": "LSP: Toggle Inlay Hints", 185 | "command": "lsp_toggle_inlay_hints", 186 | }, 187 | // { 188 | // "caption": "LSP: Fold All Comment Blocks", 189 | // "command": "lsp_fold_all", 190 | // "args": {"kind": "comment"} 191 | // }, 192 | ] 193 | -------------------------------------------------------------------------------- /plugin/core/collections.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module with additional collections. 3 | """ 4 | from __future__ import annotations 5 | from copy import deepcopy 6 | from typing import Any, Generator 7 | import sublime 8 | 9 | 10 | class DottedDict: 11 | 12 | __slots__ = ('_d',) 13 | 14 | def __init__(self, d: dict[str, Any] | None = None) -> None: 15 | """ 16 | Construct a DottedDict, optionally from an existing dictionary. 17 | 18 | :param d: An existing dictionary. 19 | """ 20 | self._d: dict[str, Any] = {} 21 | if d is not None: 22 | self.update(d) 23 | 24 | @classmethod 25 | def from_base_and_override(cls, base: DottedDict, override: dict[str, Any] | None) -> DottedDict: 26 | result = DottedDict(base.copy()) 27 | if override: 28 | result.update(override) 29 | return result 30 | 31 | def get(self, path: str | None = None) -> Any: 32 | """ 33 | Get a value from the dictionary. 34 | 35 | :param path: The path, e.g. foo.bar.baz, or None. 36 | 37 | :returns: The value stored at the path, or None if it doesn't exist. 38 | Note that this cannot distinguish between None values and 39 | paths that don't exist. If the path is None, returns the 40 | entire dictionary. 41 | """ 42 | if path is None: 43 | return self._d 44 | current: Any = self._d 45 | keys = path.split('.') 46 | for key in keys: 47 | if isinstance(current, dict): 48 | current = current.get(key) 49 | else: 50 | return None 51 | return current 52 | 53 | def walk(self, path: str) -> Generator[Any, None, None]: 54 | current: Any = self._d 55 | keys = path.split('.') 56 | for key in keys: 57 | if isinstance(current, dict): 58 | current = current.get(key) 59 | yield current 60 | else: 61 | yield None 62 | return 63 | 64 | def set(self, path: str, value: Any) -> None: 65 | """ 66 | Set a value in the dictionary. 67 | 68 | :param path: The path, e.g. foo.bar.baz 69 | :param value: The value 70 | """ 71 | current = self._d 72 | keys = path.split('.') 73 | for i in range(0, len(keys) - 1): 74 | key = keys[i] 75 | next_current = current.get(key) 76 | if not isinstance(next_current, dict): 77 | next_current = {} 78 | current[key] = next_current 79 | current = next_current 80 | current[keys[-1]] = value 81 | 82 | def remove(self, path: str) -> None: 83 | """ 84 | Remove a key from the dictionary. 85 | 86 | :param path: The path, e.g. foo.bar.baz 87 | """ 88 | current = self._d 89 | keys = path.split('.') 90 | for i in range(0, len(keys) - 1): 91 | key = keys[i] 92 | next_current = current.get(key) 93 | if not isinstance(next_current, dict): 94 | return 95 | current = next_current 96 | current.pop(keys[-1], None) 97 | 98 | def copy(self, path: str | None = None) -> Any: 99 | """ 100 | Get a copy of the value from the dictionary or copy of whole dictionary. 101 | 102 | :param path: The path, e.g. foo.bar.baz, or None. 103 | 104 | :returns: A copy of the value stored at the path, or None if it doesn't exist. 105 | Note that this cannot distinguish between None values and 106 | paths that don't exist. If the path is None, returns a copy of the 107 | entire dictionary. 108 | """ 109 | return deepcopy(self.get(path)) 110 | 111 | def __bool__(self) -> bool: 112 | """ 113 | If this collection has at least one key-value pair, return True, else return False. 114 | """ 115 | return bool(self._d) 116 | 117 | def __contains__(self, path: object) -> bool: 118 | if not isinstance(path, str): 119 | return False 120 | value = self.get(path) 121 | return value is not None and value is not False 122 | 123 | def clear(self) -> None: 124 | """ 125 | Remove all key-value pairs. 126 | """ 127 | self._d.clear() 128 | 129 | def assign(self, d: dict[str, Any]) -> None: 130 | """ 131 | Overwrites the old stored dictionary with a fresh new dictionary. 132 | 133 | :param d: The new dictionary to store 134 | """ 135 | self._d = d 136 | 137 | def update(self, d: dict[str, Any]) -> None: 138 | """ 139 | Overwrite and/or add new key-value pairs to the collection. 140 | 141 | :param d: The overriding dictionary. Can contain nested dictionaries. 142 | """ 143 | for key, value in d.items(): 144 | if isinstance(value, dict): 145 | self._update_recursive(value, key) 146 | else: 147 | self.set(key, value) 148 | 149 | def get_resolved(self, variables: dict[str, str]) -> dict[str, Any]: 150 | """ 151 | Resolve a DottedDict that may potentially contain template variables like $folder. 152 | 153 | :param variables: The variables 154 | 155 | :returns: A copy of the underlying dictionary, but with the variables replaced 156 | """ 157 | return sublime.expand_variables(self._d, variables) 158 | 159 | def _update_recursive(self, current: dict[str, Any], prefix: str) -> None: 160 | if not current or any(filter(lambda key: isinstance(key, str) and (":" in key or "/" in key), current.keys())): 161 | return self.set(prefix, current) 162 | for key, value in current.items(): 163 | path = f"{prefix}.{key}" 164 | if isinstance(value, dict): 165 | self._update_recursive(value, path) 166 | else: 167 | self.set(path, value) 168 | 169 | def __repr__(self) -> str: 170 | return f"{self.__class__.__name__}({repr(self._d)})" 171 | 172 | def __eq__(self, other: Any) -> bool: 173 | if not isinstance(other, DottedDict): 174 | return False 175 | return self._d == other._d 176 | -------------------------------------------------------------------------------- /plugin/semantic_highlighting.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from .core.registry import LspTextCommand 3 | from typing import Any, Callable, List, Tuple 4 | from typing import cast 5 | import sublime 6 | import os 7 | 8 | SemanticTokensInfo = Tuple[str, str, str] 9 | 10 | 11 | POPUP_CSS = ''' 12 | h1 { 13 | font-size: 1.1rem; 14 | font-weight: 500; 15 | margin: 0 0 0.5em 0; 16 | font-family: system; 17 | } 18 | p { 19 | margin-top: 0; 20 | } 21 | a { 22 | font-weight: normal; 23 | font-style: italic; 24 | padding-left: 1em; 25 | font-size: 1.0rem; 26 | } 27 | span.nums { 28 | display: inline-block; 29 | text-align: right; 30 | width: %dem; 31 | color: color(var(--foreground) a(0.8)) 32 | } 33 | span.context { 34 | padding-left: 0.5em; 35 | } 36 | .session-name { 37 | color: color(var(--foreground) alpha(0.6)); 38 | font-style: italic; 39 | padding-left: 1em; 40 | } 41 | ''' 42 | 43 | 44 | def copy(view: sublime.View, text: str) -> None: 45 | sublime.set_clipboard(text) 46 | view.hide_popup() 47 | sublime.status_message('Scope name copied to clipboard') 48 | 49 | 50 | class LspShowScopeNameCommand(LspTextCommand): 51 | """ 52 | Like the builtin show_scope_name command from Default/show_scope_name.py, 53 | but will also show semantic tokens if applicable. 54 | """ 55 | 56 | capability = 'semanticTokensProvider' 57 | 58 | def want_event(self) -> bool: 59 | return False 60 | 61 | def run(self, _: sublime.Edit) -> None: 62 | point = self.view.sel()[-1].b 63 | scope = self.view.scope_name(point).rstrip() 64 | scope_list = scope.replace(' ', '
') 65 | stack = self.view.context_backtrace(point) 66 | semantic_info = self._get_semantic_info(point) 67 | if isinstance(stack, list) and len(stack) > 0 and not isinstance(stack[0], str): 68 | self._render_with_fancy_stackframes( 69 | scope, 70 | scope_list, 71 | cast(List[sublime.ContextStackFrame], stack), 72 | semantic_info, 73 | ) 74 | else: 75 | self._render_with_plain_string_stackframes( 76 | scope, 77 | scope_list, 78 | cast(List[str], stack), 79 | semantic_info 80 | ) 81 | 82 | def _get_semantic_info(self, point: int) -> SemanticTokensInfo | None: 83 | if session := self.best_session('semanticTokensProvider', 0): 84 | for sv in session.session_views_async(): 85 | if self.view == sv.view: 86 | for token in sv.session_buffer.get_semantic_tokens(): 87 | if token.region.contains(point) and point < token.region.end(): 88 | token_modifiers = ', '.join(token.modifiers) if token.modifiers else '-' 89 | return (token.type, token_modifiers, session.config.name) 90 | break 91 | 92 | def _render_with_plain_string_stackframes( 93 | self, 94 | scope: str, 95 | scope_list: str, 96 | stack: list[str], 97 | semantic_info: SemanticTokensInfo | None, 98 | ) -> None: 99 | backtrace = '' 100 | digits_len = 1 101 | for i, ctx in enumerate(reversed(stack)): 102 | digits = '%s' % (i + 1) 103 | digits_len = max(len(digits), digits_len) 104 | nums = f'{digits}.' 105 | if ctx.startswith("anonymous context "): 106 | ctx = f'{ctx}' 107 | ctx = f'{ctx}' 108 | if backtrace: 109 | backtrace += '\n' 110 | backtrace += f'
{nums}{ctx}
' 111 | self._show_popup(digits_len, scope, scope_list, backtrace, semantic_info, lambda x: copy(self.view, x)) 112 | 113 | def _render_with_fancy_stackframes( 114 | self, 115 | scope: str, 116 | scope_list: str, 117 | stack: list[Any], 118 | semantic_info: SemanticTokensInfo | None, 119 | ) -> None: 120 | backtrace = '' 121 | digits_len = 1 122 | for i, frame in enumerate(reversed(stack)): 123 | digits = '%s' % (i + 1) 124 | digits_len = max(len(digits), digits_len) 125 | nums = f'{digits}.' 126 | if frame.context_name.startswith("anonymous context "): 127 | context_name = f'{frame.context_name}' 128 | else: 129 | context_name = frame.context_name 130 | ctx = f'{context_name}' 131 | resource_path = frame.source_file 132 | display_path = os.path.splitext(frame.source_file)[0] 133 | if resource_path.startswith('Packages/'): 134 | resource_path = '${packages}/' + resource_path[9:] 135 | display_path = display_path[9:] 136 | if frame.source_location[0] > 0: 137 | href = '%s:%d:%d' % (resource_path, frame.source_location[0], frame.source_location[1]) 138 | location = '%s:%d:%d' % (display_path, frame.source_location[0], frame.source_location[1]) 139 | else: 140 | href = resource_path 141 | location = display_path 142 | link = f'{location}' 143 | if backtrace: 144 | backtrace += '\n' 145 | backtrace += f'
{nums}{ctx}{link}
' 146 | self._show_popup(digits_len, scope, scope_list, backtrace, semantic_info, self.on_navigate) 147 | 148 | def _show_popup( 149 | self, 150 | digits_len: int, 151 | scope: str, 152 | scope_list: str, 153 | backtrace: str, 154 | semantic_info: SemanticTokensInfo | None, 155 | on_navigate: Callable[[str], None] 156 | ) -> None: 157 | semantic_token_html = '' 158 | if semantic_info: 159 | semantic_token_html = f""" 160 |
161 |

Semantic Token {semantic_info[2]}

162 |
Type: {semantic_info[0]}
163 |
Modifiers: {semantic_info[1]}
164 | """ 165 | css = POPUP_CSS % digits_len 166 | html = f""" 167 | 168 | 171 |

Scope Name Copy

172 |

{scope_list}

173 |

Context Backtrace

174 | {backtrace} 175 | {semantic_token_html} 176 | 177 | """ 178 | self.view.show_popup(html, max_width=512, max_height=512, on_navigate=on_navigate) 179 | 180 | def on_navigate(self, link: str) -> None: 181 | if link.startswith('o:'): 182 | if window := self.view.window(): 183 | window.run_command('open_file', {'file': link[2:], 'encoded_position': True}) 184 | else: 185 | copy(self.view, link[2:]) 186 | -------------------------------------------------------------------------------- /plugin/core/workspace.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from ...protocol import WorkspaceFolder as LspWorkspaceFolder 3 | from .types import diff 4 | from .types import matches_pattern 5 | from .types import sublime_pattern_to_glob 6 | from .url import filename_to_uri 7 | from typing import Any 8 | import sublime 9 | import os 10 | 11 | 12 | def is_subpath_of(file_path: str, potential_subpath: str) -> bool: 13 | try: 14 | file_path = os.path.abspath(file_path) 15 | potential_subpath = os.path.abspath(potential_subpath) 16 | return not os.path.relpath(file_path, potential_subpath).startswith("..") 17 | except ValueError: 18 | return False 19 | 20 | 21 | class WorkspaceFolder: 22 | 23 | __slots__ = ('name', 'path') 24 | 25 | def __init__(self, name: str, path: str) -> None: 26 | self.name = name 27 | self.path = path 28 | 29 | @classmethod 30 | def from_path(cls, path: str) -> WorkspaceFolder: 31 | return cls(os.path.basename(path) or path, path) 32 | 33 | def __hash__(self) -> int: 34 | return hash((self.name, self.path)) 35 | 36 | def __repr__(self) -> str: 37 | return f"{self.__class__.__name__}('{self.name}', '{self.path}')" 38 | 39 | def __str__(self) -> str: 40 | return self.path 41 | 42 | def __eq__(self, other: Any) -> bool: 43 | if isinstance(other, WorkspaceFolder): 44 | return self.name == other.name and self.path == other.path 45 | return False 46 | 47 | def to_lsp(self) -> LspWorkspaceFolder: 48 | return {"name": self.name, "uri": self.uri()} 49 | 50 | def uri(self) -> str: 51 | return filename_to_uri(self.path) 52 | 53 | def includes_uri(self, uri: str) -> bool: 54 | return uri.startswith(self.uri()) 55 | 56 | 57 | class ProjectFolders: 58 | 59 | def __init__(self, window: sublime.Window) -> None: 60 | self._window = window 61 | self.folders: list[str] = self._window.folders() 62 | # Per-folder ignore patterns. The list order matches the order of self.folders. 63 | self._folders_exclude_patterns: list[list[str]] = [] 64 | self._update_exclude_patterns(self.folders) 65 | 66 | def _update_exclude_patterns(self, folders: list[str]) -> None: 67 | # Ensure that the number of patterns matches the number of folders so that accessing by index never throws. 68 | self._folders_exclude_patterns = [[]] * len(folders) 69 | project_data = self._window.project_data() 70 | if not isinstance(project_data, dict): 71 | return 72 | for i, folder in enumerate(project_data.get('folders', [])): 73 | exclude_patterns = [] 74 | # Use canoncial path from `window.folders` rather than potentially relative path from project data. 75 | path = folders[i] 76 | for pattern in folder.get('folder_exclude_patterns', []): 77 | if pattern.startswith('//'): 78 | exclude_patterns.append(sublime_pattern_to_glob(pattern, True, path)) 79 | elif pattern.startswith('/'): 80 | exclude_patterns.append(sublime_pattern_to_glob(pattern, True)) 81 | else: 82 | exclude_patterns.append(sublime_pattern_to_glob('//' + pattern, True, path)) 83 | exclude_patterns.append(sublime_pattern_to_glob('//**/' + pattern, True, path)) 84 | self._folders_exclude_patterns[i] = exclude_patterns 85 | 86 | def update(self) -> bool: 87 | new_folders = self._window.folders() 88 | self._update_exclude_patterns(new_folders) 89 | added, removed = diff(self.folders, new_folders) 90 | if added or removed: 91 | self.folders = new_folders 92 | return True 93 | return False 94 | 95 | def includes_path(self, file_path: str) -> bool: 96 | if self.folders: 97 | return any(is_subpath_of(file_path, folder) for folder in self.folders) 98 | return True 99 | 100 | def includes_excluded_path(self, file_path: str) -> bool: 101 | """Path is excluded if it's within one or more workspace folders and in at least one of the folders it's not 102 | excluded using `folder_exclude_patterns`.""" 103 | if not self.folders: 104 | return False 105 | is_excluded = False 106 | for i, folder in enumerate(self.folders): 107 | if not is_subpath_of(file_path, folder): 108 | continue 109 | exclude_patterns = self._folders_exclude_patterns[i] 110 | is_excluded = matches_pattern(file_path, exclude_patterns) 111 | if not is_excluded: 112 | break 113 | return is_excluded 114 | 115 | def contains(self, view_or_file_name: str | sublime.View) -> bool: 116 | file_path = view_or_file_name.file_name() if isinstance(view_or_file_name, sublime.View) else view_or_file_name 117 | return self.includes_path(file_path) if file_path else False 118 | 119 | def get_workspace_folders(self) -> list[WorkspaceFolder]: 120 | return [WorkspaceFolder.from_path(f) for f in self.folders] 121 | 122 | 123 | def sorted_workspace_folders(folders: list[str], file_path: str) -> list[WorkspaceFolder]: 124 | matching_paths: list[str] = [] 125 | other_paths: list[str] = [] 126 | 127 | for folder in folders: 128 | if is_subpath_of(file_path, folder): 129 | if matching_paths and len(folder) > len(matching_paths[0]): 130 | matching_paths.insert(0, folder) 131 | else: 132 | matching_paths.append(folder) 133 | else: 134 | other_paths.append(folder) 135 | 136 | return [WorkspaceFolder.from_path(path) for path in matching_paths + other_paths] 137 | 138 | 139 | def enable_in_project(window: sublime.Window, config_name: str) -> None: 140 | project_data = window.project_data() 141 | if isinstance(project_data, dict): 142 | project_settings = project_data.setdefault('settings', dict()) 143 | project_lsp_settings = project_settings.setdefault('LSP', dict()) 144 | project_client_settings = project_lsp_settings.setdefault(config_name, dict()) 145 | project_client_settings['enabled'] = True 146 | window.set_project_data(project_data) 147 | else: 148 | sublime.message_dialog( 149 | f"Can't enable {config_name} in the current workspace. Ensure that the project is saved first.") 150 | 151 | 152 | def disable_in_project(window: sublime.Window, config_name: str) -> None: 153 | project_data = window.project_data() 154 | if isinstance(project_data, dict): 155 | project_settings = project_data.setdefault('settings', dict()) 156 | project_lsp_settings = project_settings.setdefault('LSP', dict()) 157 | project_client_settings = project_lsp_settings.setdefault(config_name, dict()) 158 | project_client_settings['enabled'] = False 159 | window.set_project_data(project_data) 160 | else: 161 | sublime.message_dialog( 162 | f"Can't disable {config_name} in the current workspace. Ensure that the project is saved first.") 163 | -------------------------------------------------------------------------------- /plugin/edit.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from ..protocol import TextEdit 3 | from ..protocol import WorkspaceEdit 4 | from .core.edit import parse_range 5 | from .core.logging import debug 6 | from .core.registry import LspWindowCommand 7 | from contextlib import contextmanager 8 | from typing import Any, Generator, Iterable, Tuple 9 | import operator 10 | import re 11 | import sublime 12 | import sublime_plugin 13 | 14 | 15 | TextEditTuple = Tuple[Tuple[int, int], Tuple[int, int], str] 16 | 17 | 18 | @contextmanager 19 | def temporary_setting(settings: sublime.Settings, key: str, val: Any) -> Generator[None, None, None]: 20 | prev_val = None 21 | has_prev_val = settings.has(key) 22 | if has_prev_val: 23 | prev_val = settings.get(key) 24 | settings.set(key, val) 25 | yield 26 | settings.erase(key) 27 | if has_prev_val and settings.get(key) != prev_val: 28 | settings.set(key, prev_val) 29 | 30 | 31 | class LspApplyWorkspaceEditCommand(LspWindowCommand): 32 | 33 | def run( 34 | self, session_name: str, edit: WorkspaceEdit, label: str | None = None, is_refactoring: bool = False 35 | ) -> None: 36 | if session := self.session_by_name(session_name): 37 | sublime.set_timeout_async( 38 | lambda: session.apply_workspace_edit_async(edit, label=label, is_refactoring=is_refactoring)) 39 | else: 40 | debug('Could not find session', session_name, 'required to apply WorkspaceEdit') 41 | 42 | 43 | class LspApplyDocumentEditCommand(sublime_plugin.TextCommand): 44 | re_placeholder = re.compile(r'\$(0|\{0:([^}]*)\})') 45 | 46 | def description(self, **kwargs: dict[str, Any]) -> str | None: 47 | return kwargs.get('label') # pyright: ignore[reportReturnType] 48 | 49 | def run( 50 | self, 51 | edit: sublime.Edit, 52 | changes: list[TextEdit], 53 | label: str | None = None, 54 | required_view_version: int | None = None, 55 | process_placeholders: bool = False, 56 | ) -> None: 57 | # Apply the changes in reverse, so that we don't invalidate the range 58 | # of any change that we haven't applied yet. 59 | if not changes: 60 | return 61 | view_version = self.view.change_count() 62 | if required_view_version is not None and required_view_version != view_version: 63 | print('LSP: ignoring edit due to non-matching document version') 64 | return 65 | edits = [_parse_text_edit(change) for change in changes or []] 66 | with temporary_setting(self.view.settings(), "translate_tabs_to_spaces", False): 67 | last_row, _ = self.view.rowcol_utf16(self.view.size()) 68 | placeholder_region_count = 0 69 | for start, end, replacement in reversed(_sort_by_application_order(edits)): 70 | placeholder_region: tuple[tuple[int, int], tuple[int, int]] | None = None 71 | if process_placeholders and replacement: 72 | if parsed := self.parse_snippet(replacement): 73 | replacement, (placeholder_start, placeholder_length) = parsed 74 | # There might be newlines before the placeholder. Find the actual line 75 | # and the character offset of the placeholder. 76 | prefix = replacement[0:placeholder_start] 77 | last_newline_start = prefix.rfind('\n') 78 | start_line = start[0] + prefix.count('\n') 79 | if last_newline_start == -1: 80 | start_column = start[1] + placeholder_start 81 | else: 82 | start_column = len(prefix) - last_newline_start - 1 83 | end_column = start_column + placeholder_length 84 | placeholder_region = ((start_line, start_column), (start_line, end_column)) 85 | region = sublime.Region( 86 | self.view.text_point_utf16(*start, clamp_column=True), 87 | self.view.text_point_utf16(*end, clamp_column=True) 88 | ) 89 | if start[0] > last_row and replacement[0] != '\n': 90 | # Handle when a language server (eg gopls) inserts at a row beyond the document 91 | # some editors create the line automatically, sublime needs to have the newline prepended. 92 | self.apply_change(region, '\n' + replacement, edit) 93 | last_row, _ = self.view.rowcol(self.view.size()) 94 | else: 95 | self.apply_change(region, replacement, edit) 96 | if placeholder_region is not None: 97 | if placeholder_region_count == 0: 98 | self.view.sel().clear() 99 | placeholder_region_count += 1 100 | self.view.sel().add(sublime.Region( 101 | self.view.text_point_utf16(*placeholder_region[0], clamp_column=True), 102 | self.view.text_point_utf16(*placeholder_region[1], clamp_column=True) 103 | )) 104 | if placeholder_region_count == 1: 105 | self.view.show(self.view.sel()) 106 | 107 | def apply_change(self, region: sublime.Region, replacement: str, edit: sublime.Edit) -> None: 108 | if region.empty(): 109 | self.view.insert(edit, region.a, replacement) 110 | elif len(replacement) > 0: 111 | self.view.replace(edit, region, replacement) 112 | else: 113 | self.view.erase(edit, region) 114 | 115 | def parse_snippet(self, replacement: str) -> tuple[str, tuple[int, int]] | None: 116 | if match := re.search(self.re_placeholder, replacement): 117 | placeholder = match.group(2) or '' 118 | new_replacement = replacement.replace(match.group(0), placeholder) 119 | placeholder_start_and_length = (match.start(0), len(placeholder)) 120 | return (new_replacement, placeholder_start_and_length) 121 | return None 122 | 123 | 124 | def _parse_text_edit(text_edit: TextEdit) -> TextEditTuple: 125 | return ( 126 | parse_range(text_edit['range']['start']), 127 | parse_range(text_edit['range']['end']), 128 | # Strip away carriage returns -- SublimeText takes care of that. 129 | text_edit.get('newText', '').replace("\r", "") 130 | ) 131 | 132 | 133 | def _sort_by_application_order(changes: Iterable[TextEditTuple]) -> list[TextEditTuple]: 134 | # The spec reads: 135 | # > However, it is possible that multiple edits have the same start position: multiple 136 | # > inserts, or any number of inserts followed by a single remove or replace edit. If 137 | # > multiple inserts have the same position, the order in the array defines the order in 138 | # > which the inserted strings appear in the resulting text. 139 | # So we sort by start position. But if multiple text edits start at the same position, 140 | # we use the index in the array as the key. 141 | 142 | return list(sorted(changes, key=operator.itemgetter(0))) 143 | -------------------------------------------------------------------------------- /plugin/core/open.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from ...protocol import DocumentUri 3 | from ...protocol import Range 4 | from .constants import ST_PLATFORM 5 | from .constants import ST_VERSION 6 | from .logging import exception_log 7 | from .promise import Promise 8 | from .promise import ResolveFunc 9 | from .protocol import UINT_MAX 10 | from .url import parse_uri 11 | from .views import range_to_region 12 | from urllib.parse import unquote, urlparse 13 | import os 14 | import re 15 | import sublime 16 | import sublime_plugin 17 | import subprocess 18 | import webbrowser 19 | 20 | 21 | opening_files: dict[str, tuple[Promise[sublime.View | None], ResolveFunc[sublime.View | None]]] = {} 22 | FRAGMENT_PATTERN = re.compile(r'^L?(\d+)(?:,(\d+))?(?:-L?(\d+)(?:,(\d+))?)?') 23 | 24 | 25 | def lsp_range_from_uri_fragment(fragment: str) -> Range | None: 26 | if match := FRAGMENT_PATTERN.match(fragment): 27 | selection: Range = {'start': {'line': 0, 'character': 0}, 'end': {'line': 0, 'character': 0}} 28 | # Line and column numbers in the fragment are assumed to be 1-based and need to be converted to 0-based 29 | # numbers for the LSP Position structure. 30 | start_line, start_column, end_line, end_column = (max(0, int(g) - 1) if g else None for g in match.groups()) 31 | if start_line: 32 | selection['start']['line'] = start_line 33 | selection['end']['line'] = start_line 34 | if start_column: 35 | selection['start']['character'] = start_column 36 | selection['end']['character'] = start_column 37 | if end_line: 38 | selection['end']['line'] = end_line 39 | selection['end']['character'] = UINT_MAX 40 | if end_column is not None: 41 | selection['end']['character'] = end_column 42 | return selection 43 | return None 44 | 45 | 46 | def open_file_uri( 47 | window: sublime.Window, uri: DocumentUri, flags: sublime.NewFileFlags = sublime.NewFileFlags.NONE, group: int = -1 48 | ) -> Promise[sublime.View | None]: 49 | 50 | decoded_uri = unquote(uri) # decode percent-encoded characters 51 | open_promise = open_file(window, decoded_uri, flags, group) 52 | if fragment := urlparse(decoded_uri).fragment: 53 | if selection := lsp_range_from_uri_fragment(fragment): 54 | return open_promise.then(lambda view: _select_and_center(view, selection)) 55 | return open_promise 56 | 57 | 58 | def _select_and_center(view: sublime.View | None, r: Range) -> sublime.View | None: 59 | if view: 60 | return center_selection(view, r) 61 | return None 62 | 63 | 64 | def _return_existing_view(flags: int, existing_view_group: int, active_group: int, specified_group: int) -> bool: 65 | if specified_group > -1: 66 | return existing_view_group == specified_group 67 | if bool(flags & (sublime.NewFileFlags.ADD_TO_SELECTION | sublime.NewFileFlags.REPLACE_MRU)): 68 | return False 69 | if existing_view_group == active_group: 70 | return True 71 | return not bool(flags & sublime.NewFileFlags.FORCE_GROUP) 72 | 73 | 74 | def _find_open_file(window: sublime.Window, fname: str, group: int = -1) -> sublime.View | None: 75 | """A replacement for Window.find_open_file that prefers the active view instead of the leftmost one.""" 76 | _group = window.active_group() if group == -1 else group 77 | view = window.active_view_in_group(_group) 78 | if view and fname == view.file_name(): 79 | return view 80 | return window.find_open_file(fname, group) if ST_VERSION >= 4136 else window.find_open_file(fname) 81 | 82 | 83 | def open_file( 84 | window: sublime.Window, uri: DocumentUri, flags: sublime.NewFileFlags = sublime.NewFileFlags.NONE, group: int = -1 85 | ) -> Promise[sublime.View | None]: 86 | """ 87 | Open a file asynchronously. 88 | It is only safe to call this function from the UI thread. 89 | The provided uri MUST be a file URI 90 | """ 91 | file = parse_uri(uri)[1] 92 | # window.open_file brings the file to focus if it's already opened, which we don't want (unless it's supposed 93 | # to open as a separate view). 94 | view = _find_open_file(window, file) 95 | if view and _return_existing_view(flags, window.get_view_index(view)[0], window.active_group(), group): 96 | return Promise.resolve(view) 97 | 98 | was_already_open = view is not None 99 | view = window.open_file(file, flags, group) 100 | if not view.is_loading(): 101 | if was_already_open and (flags & sublime.NewFileFlags.SEMI_TRANSIENT): 102 | # workaround bug https://github.com/sublimehq/sublime_text/issues/2411 where transient view might not get 103 | # its view listeners initialized. 104 | sublime_plugin.check_view_event_listeners(view) # type: ignore 105 | # It's already loaded. Possibly already open in a tab. 106 | return Promise.resolve(view) 107 | 108 | # Is the view opening right now? Then return the associated unresolved promise 109 | for fn, value in opening_files.items(): 110 | if fn == file or os.path.samefile(fn, file): 111 | # Return the unresolved promise. A future on_load event will resolve the promise. 112 | return value[0] 113 | 114 | # Prepare a new promise to be resolved by a future on_load event (see the event listener in main.py) 115 | def fullfill(resolve: ResolveFunc[sublime.View | None]) -> None: 116 | global opening_files 117 | # Save the promise in the first element of the tuple -- except we cannot yet do that here 118 | opening_files[file] = (None, resolve) # type: ignore 119 | 120 | promise = Promise(fullfill) 121 | tup = opening_files[file] 122 | # Save the promise in the first element of the tuple so that the for-loop above can return it 123 | opening_files[file] = (promise, tup[1]) 124 | return promise 125 | 126 | 127 | def center_selection(view: sublime.View, r: Range) -> sublime.View: 128 | selection = range_to_region(r, view) 129 | view.run_command("lsp_selection_set", {"regions": [(selection.a, selection.a)]}) 130 | if window := view.window(): 131 | window.focus_view(view) 132 | view.show_at_center(selection.begin(), animate=False) 133 | return view 134 | 135 | 136 | def open_in_browser(uri: str) -> None: 137 | # NOTE: Remove this check when on py3.8. 138 | if not uri.lower().startswith(("http://", "https://")): 139 | uri = "https://" + uri 140 | if not webbrowser.open(uri): 141 | sublime.status_message("failed to open: " + uri) 142 | 143 | 144 | def open_externally(uri: str, take_focus: bool) -> bool: 145 | """ 146 | A blocking function that invokes the OS's "open with default extension" 147 | """ 148 | try: 149 | # TODO: handle take_focus 150 | if ST_PLATFORM == "windows": 151 | os.startfile(uri) # type: ignore 152 | elif ST_PLATFORM == "osx": 153 | subprocess.check_call(("/usr/bin/open", uri)) 154 | else: # linux 155 | subprocess.check_call(("xdg-open", uri)) 156 | return True 157 | except Exception as ex: 158 | exception_log(f"Failed to open {uri}", ex) 159 | return False 160 | -------------------------------------------------------------------------------- /plugin/code_lens.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from ..protocol import CodeLens 3 | from ..protocol import Command 4 | from ..protocol import Range 5 | from .core.constants import CODE_LENS_ENABLED_KEY 6 | from .core.protocol import Error 7 | from .core.protocol import ResolvedCodeLens 8 | from .core.registry import LspTextCommand 9 | from .core.registry import LspWindowCommand 10 | from .core.registry import windows 11 | from .core.views import range_to_region 12 | from functools import partial 13 | from typing import cast 14 | from typing_extensions import TypeGuard 15 | import itertools 16 | import sublime 17 | 18 | 19 | def is_resolved(code_lens: CodeLens | ResolvedCodeLens) -> TypeGuard[ResolvedCodeLens]: 20 | return 'command' in code_lens 21 | 22 | 23 | class HashableRange: 24 | 25 | def __init__(self, r: Range, /) -> None: 26 | start, end = r['start'], r['end'] 27 | self.data = (start['line'], start['character'], end['line'], end['character']) 28 | 29 | def __hash__(self): 30 | return hash(self.data) 31 | 32 | def __eq__(self, rhs: object) -> bool: 33 | return isinstance(rhs, HashableRange) and self.data == rhs.data 34 | 35 | def __lt__(self, rhs: HashableRange) -> bool: 36 | return self.data < rhs.data 37 | 38 | 39 | class CachedCodeLens: 40 | 41 | __slots__ = ('data', 'range', 'cached_command') 42 | 43 | def __init__(self, data: CodeLens) -> None: 44 | self.data: CodeLens | ResolvedCodeLens = data 45 | self.range = HashableRange(data['range']) 46 | self.cached_command = data.get('command') 47 | 48 | def on_resolve(self, response: CodeLens | Error) -> None: 49 | if isinstance(response, Error): 50 | return 51 | assert is_resolved(response) 52 | self.data = response 53 | self.range = HashableRange(response['range']) 54 | self.cached_command = response['command'] 55 | 56 | 57 | class CodeLensCache: 58 | 59 | def __init__(self) -> None: 60 | self.code_lenses: dict[HashableRange, list[CachedCodeLens]] = {} 61 | 62 | def handle_response_async(self, code_lenses: list[CodeLens]) -> None: 63 | new_code_lenses = [CachedCodeLens(code_lens) for code_lens in code_lenses] 64 | new_code_lenses.sort(key=lambda c: c.range) 65 | grouped_code_lenses = { 66 | range_: list(group) for range_, group in itertools.groupby(new_code_lenses, key=lambda c: c.range) 67 | } 68 | # Fast path: no extra work to do 69 | if not self.code_lenses: 70 | self.code_lenses = grouped_code_lenses 71 | return 72 | # Update new code lenses with cached data to prevent the editor from jumping around due to some code lenses 73 | # going from resolved to unresolved due to a requery of the document data. 74 | for range_, group in grouped_code_lenses.items(): 75 | try: 76 | old_group = self.code_lenses[range_] 77 | except KeyError: 78 | continue 79 | # Use the number of code lenses as a heuristic whether the group still contains the same code lenses. 80 | if len(group) == len(old_group): 81 | for old, new in zip(old_group, group): 82 | if is_resolved(new.data): 83 | continue 84 | # This assignment is only temporary, the resolve call in the future will fill in the actual command 85 | # after resolve() is called on the CachedCodeLens. However reusing the previous command title if the 86 | # code lens groups are the same makes the screen not jump from the phantoms moving around too much. 87 | new.cached_command = old.data['command'] if is_resolved(old.data) else old.cached_command 88 | self.code_lenses = grouped_code_lenses 89 | 90 | def unresolved_visible_code_lenses(self, view: sublime.View) -> list[CachedCodeLens]: 91 | visible_region = view.visible_region() 92 | return [ 93 | cl for cl in itertools.chain.from_iterable(self.code_lenses.values()) 94 | if not is_resolved(cl.data) and range_to_region(cl.data['range'], view).intersects(visible_region) 95 | ] 96 | 97 | def code_lenses_with_command(self) -> list[ResolvedCodeLens]: 98 | """ Returns only the code lenses that are either resolved, or have a cached command. """ 99 | code_lenses: list[ResolvedCodeLens] = [] 100 | for cached_code_lens in itertools.chain.from_iterable(self.code_lenses.values()): 101 | code_lens = cached_code_lens.data.copy() 102 | if is_resolved(code_lens): 103 | code_lenses.append(code_lens) 104 | elif cached_command := cached_code_lens.cached_command: 105 | code_lens['command'] = cached_command 106 | code_lens = cast(ResolvedCodeLens, code_lens) 107 | code_lens['uses_cached_command'] = True 108 | code_lenses.append(code_lens) 109 | return code_lenses 110 | 111 | 112 | class LspToggleCodeLensesCommand(LspWindowCommand): 113 | capability = 'codeLensProvider' 114 | 115 | @classmethod 116 | def are_enabled(cls, window: sublime.Window | None) -> bool: 117 | if not window: 118 | return False 119 | return bool(window.settings().get(CODE_LENS_ENABLED_KEY, True)) 120 | 121 | def is_checked(self) -> bool: 122 | return self.are_enabled(self.window) 123 | 124 | def run(self) -> None: 125 | enable = not self.is_checked() 126 | self.window.settings().set(CODE_LENS_ENABLED_KEY, enable) 127 | sublime.set_timeout_async(partial(self._update_views_async, enable)) 128 | 129 | def _update_views_async(self, enable: bool) -> None: 130 | window_manager = windows.lookup(self.window) 131 | if not window_manager: 132 | return 133 | for session in window_manager.get_sessions(): 134 | for session_view in session.session_views_async(): 135 | if enable: 136 | session_view.session_buffer.do_code_lenses_async(session_view.view) 137 | else: 138 | session_view.clear_code_lenses_async() 139 | 140 | 141 | class LspCodeLensCommand(LspTextCommand): 142 | 143 | capability = 'codeLensProvider' 144 | 145 | def run(self, edit: sublime.Edit) -> None: 146 | listener = windows.listener_for_view(self.view) 147 | if not listener: 148 | return 149 | commands: list[tuple[str, Command]] = [] 150 | for region in self.view.sel(): 151 | for sv in listener.session_views_async(): 152 | session_name = sv.session.config.name 153 | for command in sv.get_code_lenses_for_region(region): 154 | commands.append((session_name, command)) 155 | if not commands: 156 | return 157 | elif len(commands) == 1: 158 | self.on_select(commands, 0) 159 | elif window := self.view.window(): 160 | window.show_quick_panel( 161 | [sublime.QuickPanelItem(cmd["title"], annotation=session_name) for session_name, cmd in commands], 162 | lambda index: self.on_select(commands, index) 163 | ) 164 | 165 | def want_event(self) -> bool: 166 | return False 167 | 168 | def on_select(self, commands: list[tuple[str, Command]], index: int) -> None: 169 | try: 170 | session_name, command = commands[index] 171 | except IndexError: 172 | return 173 | args = { 174 | "session_name": session_name, 175 | "command_name": command["command"], 176 | "command_args": command.get("arguments") 177 | } 178 | self.view.run_command("lsp_execute", args) 179 | --------------------------------------------------------------------------------