├── .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 | 
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 |
--------------------------------------------------------------------------------