mirror of
https://github.com/helix-editor/helix.git
synced 2024-11-22 17:36:19 +04:00
Merge remote-tracking branch 'upstream/master' into steel-event-system
This commit is contained in:
commit
7ebfd90d92
12
.github/workflows/build.yml
vendored
12
.github/workflows/build.yml
vendored
@ -6,7 +6,7 @@ on:
|
||||
- master
|
||||
merge_group:
|
||||
schedule:
|
||||
- cron: '00 01 * * *'
|
||||
- cron: "00 01 * * *"
|
||||
|
||||
jobs:
|
||||
check:
|
||||
@ -16,11 +16,9 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install stable toolchain
|
||||
uses: helix-editor/rust-toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
override: true
|
||||
uses: dtolnay/rust-toolchain@1.70
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
@ -110,6 +108,9 @@ jobs:
|
||||
- name: Validate queries
|
||||
run: cargo xtask query-check
|
||||
|
||||
- name: Validate themes
|
||||
run: cargo xtask theme-check
|
||||
|
||||
- name: Generate docs
|
||||
run: cargo xtask docgen
|
||||
|
||||
@ -119,4 +120,3 @@ jobs:
|
||||
git diff-files --quiet \
|
||||
|| (echo "Run 'cargo xtask docgen', commit the changes and push again" \
|
||||
&& exit 1)
|
||||
|
||||
|
2
.github/workflows/cachix.yml
vendored
2
.github/workflows/cachix.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install nix
|
||||
uses: cachix/install-nix-action@V27
|
||||
uses: cachix/install-nix-action@v30
|
||||
|
||||
- name: Authenticate with Cachix
|
||||
uses: cachix/cachix-action@v15
|
||||
|
@ -16,7 +16,7 @@ # 24.07 (2024-07-14)
|
||||
Commands:
|
||||
|
||||
- `select_all_siblings` (`A-a`) - select all siblings of each selection ([87c4161](https://github.com/helix-editor/helix/commit/87c4161))
|
||||
- `select_all_children` (`A-i`) - select all children of each selection ([fa67c5c](https://github.com/helix-editor/helix/commit/fa67c5c))
|
||||
- `select_all_children` (`A-I`) - select all children of each selection ([fa67c5c](https://github.com/helix-editor/helix/commit/fa67c5c))
|
||||
- `:read` - insert the contents of the given file at each selection ([#10447](https://github.com/helix-editor/helix/pull/10447))
|
||||
|
||||
Usability improvements:
|
||||
|
681
Cargo.lock
generated
681
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,7 @@ members = [
|
||||
"helix-view",
|
||||
"helix-term",
|
||||
"helix-tui",
|
||||
"helix-lsp-types",
|
||||
"helix-lsp",
|
||||
"helix-event",
|
||||
"helix-dap",
|
||||
|
@ -37,8 +37,8 @@ # Features
|
||||
- Built-in language server support
|
||||
- Smart, incremental syntax highlighting and code editing via tree-sitter
|
||||
|
||||
It's a terminal-based editor first, but I'd like to explore a custom renderer
|
||||
(similar to Emacs) in wgpu or skulpin.
|
||||
Although it's primarily a terminal-based editor, I am interested in exploring
|
||||
a custom renderer (similar to Emacs) using wgpu or skulpin.
|
||||
|
||||
Note: Only certain languages have indentation definitions at the moment. Check
|
||||
`runtime/queries/<lang>/` for `indents.scm`.
|
||||
|
@ -10,6 +10,7 @@ # Summary
|
||||
- [Surround](./surround.md)
|
||||
- [Textobjects](./textobjects.md)
|
||||
- [Syntax aware motions](./syntax-aware-motions.md)
|
||||
- [Pickers](./pickers.md)
|
||||
- [Keymap](./keymap.md)
|
||||
- [Commands](./commands.md)
|
||||
- [Language support](./lang-support.md)
|
||||
|
@ -1,6 +1,6 @@
|
||||
| Language | Syntax Highlighting | Treesitter Textobjects | Auto Indent | Default LSP |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| ada | ✓ | ✓ | | `ada_language_server`, `ada_language_server` |
|
||||
| ada | ✓ | ✓ | | `ada_language_server` |
|
||||
| adl | ✓ | ✓ | ✓ | |
|
||||
| agda | ✓ | | | |
|
||||
| astro | ✓ | | | |
|
||||
@ -19,6 +19,7 @@
|
||||
| cairo | ✓ | ✓ | ✓ | `cairo-language-server` |
|
||||
| capnp | ✓ | | ✓ | |
|
||||
| cel | ✓ | | | |
|
||||
| circom | ✓ | | | `circom-lsp` |
|
||||
| clojure | ✓ | | | `clojure-lsp` |
|
||||
| cmake | ✓ | ✓ | ✓ | `cmake-language-server` |
|
||||
| comment | ✓ | | | |
|
||||
@ -34,10 +35,11 @@
|
||||
| devicetree | ✓ | | | |
|
||||
| dhall | ✓ | ✓ | | `dhall-lsp-server` |
|
||||
| diff | ✓ | | | |
|
||||
| docker-compose | ✓ | | ✓ | `docker-compose-langserver`, `yaml-language-server` |
|
||||
| dockerfile | ✓ | | | `docker-langserver` |
|
||||
| docker-compose | ✓ | ✓ | ✓ | `docker-compose-langserver`, `yaml-language-server` |
|
||||
| dockerfile | ✓ | ✓ | | `docker-langserver` |
|
||||
| dot | ✓ | | | `dot-language-server` |
|
||||
| dtd | ✓ | | | |
|
||||
| dune | ✓ | | | |
|
||||
| earthfile | ✓ | ✓ | ✓ | `earthlyls` |
|
||||
| edoc | ✓ | | | |
|
||||
| eex | ✓ | | | |
|
||||
@ -46,9 +48,9 @@
|
||||
| elixir | ✓ | ✓ | ✓ | `elixir-ls` |
|
||||
| elm | ✓ | ✓ | | `elm-language-server` |
|
||||
| elvish | ✓ | | | `elvish` |
|
||||
| env | ✓ | | | |
|
||||
| env | ✓ | ✓ | | |
|
||||
| erb | ✓ | | | |
|
||||
| erlang | ✓ | ✓ | | `erlang_ls` |
|
||||
| erlang | ✓ | ✓ | | `erlang_ls`, `elp` |
|
||||
| esdl | ✓ | | | |
|
||||
| fidl | ✓ | | | |
|
||||
| fish | ✓ | ✓ | ✓ | |
|
||||
@ -58,9 +60,10 @@
|
||||
| gas | ✓ | ✓ | | |
|
||||
| gdscript | ✓ | ✓ | ✓ | |
|
||||
| gemini | ✓ | | | |
|
||||
| gherkin | ✓ | | | |
|
||||
| git-attributes | ✓ | | | |
|
||||
| git-commit | ✓ | ✓ | | |
|
||||
| git-config | ✓ | | | |
|
||||
| git-config | ✓ | ✓ | | |
|
||||
| git-ignore | ✓ | | | |
|
||||
| git-rebase | ✓ | | | |
|
||||
| gjs | ✓ | ✓ | ✓ | `typescript-language-server`, `vscode-eslint-language-server`, `ember-language-server` |
|
||||
@ -69,7 +72,7 @@
|
||||
| glsl | ✓ | ✓ | ✓ | |
|
||||
| gn | ✓ | | | |
|
||||
| go | ✓ | ✓ | ✓ | `gopls`, `golangci-lint-langserver` |
|
||||
| godot-resource | ✓ | | | |
|
||||
| godot-resource | ✓ | ✓ | | |
|
||||
| gomod | ✓ | | | `gopls` |
|
||||
| gotmpl | ✓ | | | `gopls` |
|
||||
| gowork | ✓ | | | `gopls` |
|
||||
@ -82,10 +85,10 @@
|
||||
| hcl | ✓ | ✓ | ✓ | `terraform-ls` |
|
||||
| heex | ✓ | ✓ | | `elixir-ls` |
|
||||
| helm | ✓ | | | `helm_ls` |
|
||||
| hocon | ✓ | | ✓ | |
|
||||
| hocon | ✓ | ✓ | ✓ | |
|
||||
| hoon | ✓ | | | |
|
||||
| hosts | ✓ | | | |
|
||||
| html | ✓ | | | `vscode-html-language-server` |
|
||||
| html | ✓ | | | `vscode-html-language-server`, `superhtml` |
|
||||
| hurl | ✓ | ✓ | ✓ | |
|
||||
| hyprlang | ✓ | | ✓ | |
|
||||
| idris | | | | `idris2-lsp` |
|
||||
@ -96,6 +99,8 @@
|
||||
| java | ✓ | ✓ | ✓ | `jdtls` |
|
||||
| javascript | ✓ | ✓ | ✓ | `typescript-language-server` |
|
||||
| jinja | ✓ | | | |
|
||||
| jjdescription | ✓ | | | |
|
||||
| jq | ✓ | ✓ | | `jq-lsp` |
|
||||
| jsdoc | ✓ | | | |
|
||||
| json | ✓ | ✓ | ✓ | `vscode-json-language-server` |
|
||||
| json5 | ✓ | | | |
|
||||
@ -124,7 +129,7 @@
|
||||
| markdown.inline | ✓ | | | |
|
||||
| matlab | ✓ | ✓ | ✓ | |
|
||||
| mermaid | ✓ | | | |
|
||||
| meson | ✓ | | ✓ | |
|
||||
| meson | ✓ | | ✓ | `mesonlsp` |
|
||||
| mint | | | | `mint` |
|
||||
| mojo | ✓ | ✓ | ✓ | `mojo-lsp-server` |
|
||||
| move | ✓ | | | |
|
||||
@ -132,7 +137,7 @@
|
||||
| nasm | ✓ | ✓ | | |
|
||||
| nickel | ✓ | | ✓ | `nls` |
|
||||
| nim | ✓ | ✓ | ✓ | `nimlangserver` |
|
||||
| nix | ✓ | ✓ | | `nil` |
|
||||
| nix | ✓ | ✓ | | `nil`, `nixd` |
|
||||
| nu | ✓ | | | `nu` |
|
||||
| nunjucks | ✓ | | | |
|
||||
| ocaml | ✓ | | ✓ | `ocamllsp` |
|
||||
@ -155,12 +160,12 @@
|
||||
| pod | ✓ | | | |
|
||||
| ponylang | ✓ | ✓ | ✓ | |
|
||||
| powershell | ✓ | | | |
|
||||
| prisma | ✓ | | | `prisma-language-server` |
|
||||
| prisma | ✓ | ✓ | | `prisma-language-server` |
|
||||
| prolog | | | | `swipl` |
|
||||
| protobuf | ✓ | ✓ | ✓ | `bufls`, `pb` |
|
||||
| prql | ✓ | | | |
|
||||
| purescript | ✓ | ✓ | | `purescript-language-server` |
|
||||
| python | ✓ | ✓ | ✓ | `pylsp` |
|
||||
| python | ✓ | ✓ | ✓ | `ruff`, `jedi-language-server`, `pylsp` |
|
||||
| qml | ✓ | | ✓ | `qmlls` |
|
||||
| r | ✓ | | | `R` |
|
||||
| racket | ✓ | | ✓ | `racket` |
|
||||
@ -183,7 +188,7 @@
|
||||
| sml | ✓ | | | |
|
||||
| solidity | ✓ | ✓ | | `solc` |
|
||||
| spicedb | ✓ | | | |
|
||||
| sql | ✓ | | | |
|
||||
| sql | ✓ | ✓ | | |
|
||||
| sshclientconfig | ✓ | | | |
|
||||
| starlark | ✓ | ✓ | | |
|
||||
| strace | ✓ | | | |
|
||||
@ -198,12 +203,14 @@
|
||||
| tcl | ✓ | | ✓ | |
|
||||
| templ | ✓ | | | `templ` |
|
||||
| tfvars | ✓ | | ✓ | `terraform-ls` |
|
||||
| thrift | ✓ | | | |
|
||||
| todotxt | ✓ | | | |
|
||||
| toml | ✓ | ✓ | | `taplo` |
|
||||
| tsq | ✓ | | | |
|
||||
| tsx | ✓ | ✓ | ✓ | `typescript-language-server` |
|
||||
| twig | ✓ | | | |
|
||||
| typescript | ✓ | ✓ | ✓ | `typescript-language-server` |
|
||||
| typespec | ✓ | ✓ | ✓ | `tsp-server` |
|
||||
| typst | ✓ | | | `tinymist`, `typst-lsp` |
|
||||
| ungrammar | ✓ | | | |
|
||||
| unison | ✓ | | ✓ | |
|
||||
@ -223,6 +230,6 @@
|
||||
| xit | ✓ | | | |
|
||||
| xml | ✓ | | ✓ | |
|
||||
| xtc | ✓ | | | |
|
||||
| yaml | ✓ | | ✓ | `yaml-language-server`, `ansible-language-server` |
|
||||
| yaml | ✓ | ✓ | ✓ | `yaml-language-server`, `ansible-language-server` |
|
||||
| yuck | ✓ | | | |
|
||||
| zig | ✓ | ✓ | ✓ | `zls` |
|
||||
|
@ -2,7 +2,7 @@
|
||||
| --- | --- |
|
||||
| `:quit`, `:q` | Close the current view. |
|
||||
| `:quit!`, `:q!` | Force close the current view, ignoring unsaved changes. |
|
||||
| `:open`, `:o` | Open a file from disk into the current view. |
|
||||
| `:open`, `:o`, `:edit`, `:e` | Open a file from disk into the current view. |
|
||||
| `:buffer-close`, `:bc`, `:bclose` | Close the current buffer. |
|
||||
| `:buffer-close!`, `:bc!`, `:bclose!` | Close the current buffer forcefully, ignoring unsaved changes. |
|
||||
| `:buffer-close-others`, `:bco`, `:bcloseother` | Close all buffers but the currently focused one. |
|
||||
@ -72,7 +72,7 @@
|
||||
| `:sort` | Sort ranges in selection. |
|
||||
| `:rsort` | Sort ranges in selection in reverse order. |
|
||||
| `:reflow` | Hard-wrap the current selection of lines to a given width. |
|
||||
| `:tree-sitter-subtree`, `:ts-subtree` | Display tree sitter subtree under cursor, primarily for debugging queries. |
|
||||
| `:tree-sitter-subtree`, `:ts-subtree` | Display the smallest tree-sitter subtree that spans the primary selection, primarily for debugging queries. |
|
||||
| `:config-reload` | Refresh user config. |
|
||||
| `:config-open` | Open the user config.toml file. |
|
||||
| `:config-open-workspace` | Open the workspace config.toml file. |
|
||||
@ -85,6 +85,6 @@
|
||||
| `:reset-diff-change`, `:diffget`, `:diffg` | Reset the diff change at the cursor position. |
|
||||
| `:clear-register` | Clear given register. If no argument is provided, clear all registers. |
|
||||
| `:redraw` | Clear and re-render the whole UI |
|
||||
| `:move` | Move the current buffer and its corresponding file to a different path |
|
||||
| `:move`, `:mv` | Move the current buffer and its corresponding file to a different path |
|
||||
| `:yank-diagnostic` | Yank diagnostic(s) under primary cursor to register, or clipboard by default |
|
||||
| `:read`, `:r` | Load a file into buffer |
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Adding new languages to Helix
|
||||
## Adding new languages to Helix
|
||||
|
||||
In order to add a new language to Helix, you will need to follow the steps
|
||||
below.
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Adding indent queries
|
||||
## Adding indent queries
|
||||
|
||||
Helix uses tree-sitter to correctly indent new lines. This requires a tree-
|
||||
sitter grammar and an `indent.scm` query file placed in `runtime/queries/
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Adding Injection Queries
|
||||
## Adding Injection Queries
|
||||
|
||||
Writing language injection queries allows one to highlight a specific node as a different language.
|
||||
In addition to the [standard][upstream-docs] language injection options used by tree-sitter, there
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Adding textobject queries
|
||||
## Adding textobject queries
|
||||
|
||||
Helix supports textobjects that are language specific, such as functions, classes, etc.
|
||||
These textobjects require an accompanying tree-sitter grammar and a `textobjects.scm` query file
|
||||
|
@ -14,6 +14,10 @@ # Installing Helix
|
||||
## Pre-built binaries
|
||||
|
||||
Download pre-built binaries from the [GitHub Releases page](https://github.com/helix-editor/helix/releases).
|
||||
Add the `hx` binary to your system's `$PATH` to use it from the command line, and copy the `runtime` directory into the config directory (for example `~/.config/helix/runtime` on Linux/macOS).
|
||||
The runtime location can be overriden via the HELIX_RUNTIME environment variable.
|
||||
The tarball contents include an `hx` binary and a `runtime` directory.
|
||||
To set up Helix:
|
||||
|
||||
1. Add the `hx` binary to your system's `$PATH` to allow it to be used from the command line.
|
||||
2. Copy the `runtime` directory to a location that `hx` searches for runtime files. A typical location on Linux/macOS is `~/.config/helix/runtime`.
|
||||
|
||||
To see the runtime directories that `hx` searches, run `hx --health`. If necessary, you can override the default runtime location by setting the `HELIX_RUNTIME` environment variable.
|
||||
|
@ -145,6 +145,9 @@ ### Selection manipulation
|
||||
| `Alt-i`, `Alt-down` | Shrink syntax tree object selection (**TS**) | `shrink_selection` |
|
||||
| `Alt-p`, `Alt-left` | Select previous sibling node in syntax tree (**TS**) | `select_prev_sibling` |
|
||||
| `Alt-n`, `Alt-right` | Select next sibling node in syntax tree (**TS**) | `select_next_sibling` |
|
||||
| `Alt-a` | Select all sibling nodes in syntax tree (**TS**) | `select_all_siblings` |
|
||||
| `Alt-e` | Move to end of parent node in syntax tree (**TS**) | `move_parent_node_end` |
|
||||
| `Alt-b` | Move to start of parent node in syntax tree (**TS**) | `move_parent_node_start` |
|
||||
|
||||
### Search
|
||||
|
||||
@ -320,10 +323,14 @@ ##### Completion Menu
|
||||
|
||||
Displays documentation for the selected completion item. Remapping currently not supported.
|
||||
|
||||
| Key | Description |
|
||||
| ---- | ----------- |
|
||||
| `Shift-Tab`, `Ctrl-p`, `Up` | Previous entry |
|
||||
| `Tab`, `Ctrl-n`, `Down` | Next entry |
|
||||
| Key | Description |
|
||||
| ---- | ----------- |
|
||||
| `Shift-Tab`, `Ctrl-p`, `Up` | Previous entry |
|
||||
| `Tab`, `Ctrl-n`, `Down` | Next entry |
|
||||
| `Enter` | Close menu and accept completion |
|
||||
| `Ctrl-c` | Close menu and reject completion |
|
||||
|
||||
Any other keypresses result in the completion being accepted.
|
||||
|
||||
##### Signature-help Popup
|
||||
|
||||
@ -436,6 +443,8 @@ ## Select / extend mode
|
||||
## Picker
|
||||
|
||||
Keys to use within picker. Remapping currently not supported.
|
||||
See the documentation page on [pickers](./pickers.md) for more info.
|
||||
[Prompt](#prompt) keybinds also work in pickers, except where they conflict with picker keybinds.
|
||||
|
||||
| Key | Description |
|
||||
| ----- | ------------- |
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Language Support
|
||||
## Language Support
|
||||
|
||||
The following languages and Language Servers are supported. To use
|
||||
Language Server features, you must first [configure][lsp-config-wiki] the
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Languages
|
||||
## Languages
|
||||
|
||||
Language-specific settings and settings for language servers are configured
|
||||
in `languages.toml` files.
|
||||
|
11
book/src/pickers.md
Normal file
11
book/src/pickers.md
Normal file
@ -0,0 +1,11 @@
|
||||
## Using pickers
|
||||
|
||||
Helix has a variety of pickers, which are interactive windows used to select various kinds of items. These include a file picker, global search picker, and more. Most pickers are accessed via keybindings in [space mode](./keymap.md#space-mode). Pickers have their own [keymap](./keymap.md#picker) for navigation.
|
||||
|
||||
### Filtering Picker Results
|
||||
|
||||
Most pickers perform fuzzy matching using [fzf syntax](https://github.com/junegunn/fzf?tab=readme-ov-file#search-syntax). Two exceptions are the global search picker, which uses regex, and the workspace symbol picker, which passes search terms to the LSP. Note that OR operations (`|`) are not currently supported.
|
||||
|
||||
If a picker shows multiple columns, you may apply the filter to a specific column by prefixing the column name with `%`. Column names can be shortened to any prefix, so `%p`, `%pa` or `%pat` all mean the same as `%path`. For example, a query of `helix %p .toml !lang` in the global search picker searches for the term "helix" within files with paths ending in ".toml" but not including "lang".
|
||||
|
||||
You can insert the contents of a [register](./registers.md) using `Ctrl-r` followed by a register name. For example, one could insert the currently selected text using `Ctrl-r`-`.`, or the directory of the current file using `Ctrl-r`-`%` followed by `Ctrl-w` to remove the last path section. The global search picker will use the contents of the [search register](./registers.md#default-registers) if you press `Enter` without typing a filter. For example, pressing `*`-`Space-/`-`Enter` will start a global search for the currently selected text.
|
@ -1,4 +1,4 @@
|
||||
# Key remapping
|
||||
## Key remapping
|
||||
|
||||
Helix currently supports one-way key remapping through a simple TOML configuration
|
||||
file. (More powerful solutions such as rebinding via commands will be
|
||||
@ -75,5 +75,20 @@ ## Special keys and modifiers
|
||||
|
||||
Keys can be disabled by binding them to the `no_op` command.
|
||||
|
||||
A list of commands is available in the [Keymap](https://docs.helix-editor.com/keymap.html) documentation
|
||||
and in the source code at [`helix-term/src/commands.rs`](https://github.com/helix-editor/helix/blob/master/helix-term/src/commands.rs) at the invocation of `static_commands!` macro and the `TypableCommandList`.
|
||||
## Commands
|
||||
|
||||
There are three kinds of commands that can be used in keymaps:
|
||||
|
||||
* Static commands: commands like `move_char_right` which are usually bound to
|
||||
keys and used for movement and editing. A list of static commands is
|
||||
available in the [Keymap](./keymap.html) documentation and in the source code
|
||||
in [`helix-term/src/commands.rs`](https://github.com/helix-editor/helix/blob/master/helix-term/src/commands.rs)
|
||||
at the invocation of `static_commands!` macro and the `TypableCommandList`.
|
||||
* Typable commands: commands that can be executed from command mode (`:`), for
|
||||
example `:write!`. See the [Commands](./commands.html) documentation for a
|
||||
list of available typeable commands.
|
||||
* Macros: sequences of keys that are executed in order. These keybindings
|
||||
start with `@` and then list any number of keys to be executed. For example
|
||||
`@miw` can be used to select the surrounding word. For now, macro keybindings
|
||||
are not allowed in keybinding sequences due to limitations in the way that
|
||||
command sequences are executed.
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Themes
|
||||
## Themes
|
||||
|
||||
To use a theme add `theme = "<name>"` to the top of your [`config.toml`](./configuration.md) file, or select it during runtime using `:theme <name>`.
|
||||
|
||||
@ -293,12 +293,13 @@ #### Interface
|
||||
| `ui.statusline.select` | Statusline mode during select mode ([only if `editor.color-modes` is enabled][editor-section]) |
|
||||
| `ui.statusline.separator` | Separator character in statusline |
|
||||
| `ui.bufferline` | Style for the buffer line |
|
||||
| `ui.bufferline.active` | Style for the active buffer in buffer line |
|
||||
| `ui.bufferline.active` | Style for the active buffer in buffer line |
|
||||
| `ui.bufferline.background` | Style for bufferline background |
|
||||
| `ui.popup` | Documentation popups (e.g. Space + k) |
|
||||
| `ui.popup.info` | Prompt for multiple key options |
|
||||
| `ui.picker.header` | Column names in pickers with multiple columns |
|
||||
| `ui.picker.header.active` | The column name in pickers with multiple columns where the cursor is entering into. |
|
||||
| `ui.picker.header` | Header row area in pickers with multiple columns |
|
||||
| `ui.picker.header.column` | Column names in pickers with multiple columns |
|
||||
| `ui.picker.header.column.active` | The column name in pickers with multiple columns where the cursor is entering into. |
|
||||
| `ui.window` | Borderlines separating splits |
|
||||
| `ui.help` | Description box for commands |
|
||||
| `ui.text` | Default text style, command prompts, popup text, etc. |
|
||||
|
@ -7,3 +7,27 @@ # Using Helix
|
||||
> 💡 Currently, not all functionality is fully documented, please refer to the
|
||||
> [key mappings](./keymap.md) list.
|
||||
|
||||
## Modes
|
||||
|
||||
Helix is a modal editor, meaning it has different modes for different tasks. The main modes are:
|
||||
|
||||
* [Normal mode](./keymap.md#normal-mode): For navigation and editing commands. This is the default mode.
|
||||
* [Insert mode](./keymap.md#insert-mode): For typing text directly into the document. Access by typing `i` in normal mode.
|
||||
* [Select/extend mode](./keymap.md#select--extend-mode): For making selections and performing operations on them. Access by typing `v` in normal mode.
|
||||
|
||||
## Buffers
|
||||
|
||||
Buffers are in-memory representations of files. You can have multiple buffers open at once. Use [pickers](./pickers.md) or commands like `:buffer-next` and `:buffer-previous` to open buffers or switch between them.
|
||||
|
||||
## Selection-first editing
|
||||
|
||||
Inspired by [Kakoune](http://kakoune.org/), Helix follows the `selection → action` model. This means that whatever you are going to act on (a word, a paragraph, a line, etc.) is selected first and the action itself (delete, change, yank, etc.) comes second. A cursor is simply a single width selection.
|
||||
|
||||
## Multiple selections
|
||||
|
||||
Also inspired by Kakoune, multiple selections are a core mode of interaction in Helix. For example, the standard way of replacing multiple instance of a word is to first select all instances (so there is one selection per instance) and then use the change action (`c`) to edit them all at the same time.
|
||||
|
||||
## Motions
|
||||
|
||||
Motions are commands that move the cursor or modify selections. They're used for navigation and text manipulation. Examples include `w` to move to the next word, or `f` to find a character. See the [Movement](./keymap.md#movement) section of the keymap for more motions.
|
||||
|
||||
|
@ -2,23 +2,31 @@
|
||||
# Bash completion script for Helix editor
|
||||
|
||||
_hx() {
|
||||
# $1 command name
|
||||
# $2 word being completed
|
||||
# $3 word preceding
|
||||
local cur prev languages
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
prev="${COMP_WORDS[COMP_CWORD - 1]}"
|
||||
|
||||
case "$3" in
|
||||
-g | --grammar)
|
||||
COMPREPLY="$(compgen -W 'fetch build' -- $2)"
|
||||
;;
|
||||
--health)
|
||||
local languages=$(hx --health |tail -n '+7' |awk '{print $1}' |sed 's/\x1b\[[0-9;]*m//g')
|
||||
COMPREPLY="$(compgen -W """$languages""" -- $2)"
|
||||
;;
|
||||
*)
|
||||
COMPREPLY="$(compgen -fd -W "-h --help --tutor -V --version -v -vv -vvv --health -g --grammar --vsplit --hsplit -c --config --log" -- """$2""")"
|
||||
;;
|
||||
esac
|
||||
case "$prev" in
|
||||
-g | --grammar)
|
||||
COMPREPLY=($(compgen -W 'fetch build' -- "$cur"))
|
||||
return 0
|
||||
;;
|
||||
--health)
|
||||
languages=$(hx --health | tail -n '+7' | awk '{print $1}' | sed 's/\x1b\[[0-9;]*m//g')
|
||||
COMPREPLY=($(compgen -W """$languages""" -- "$cur"))
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
local IFS=$'\n'
|
||||
COMPREPLY=($COMPREPLY)
|
||||
case "$2" in
|
||||
-*)
|
||||
COMPREPLY=($(compgen -W "-h --help --tutor -V --version -v -vv -vvv --health -g --grammar --vsplit --hsplit -c --config --log" -- """$2"""))
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
COMPREPLY=($(compgen -fd -- """$2"""))
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
} && complete -o filenames -F _hx hx
|
||||
|
29
contrib/completion/hx.nu
Normal file
29
contrib/completion/hx.nu
Normal file
@ -0,0 +1,29 @@
|
||||
# Completions for Helix: <https://github.com/helix-editor/helix>
|
||||
#
|
||||
# NOTE: the `+N` syntax is not supported in Nushell (https://github.com/nushell/nushell/issues/13418)
|
||||
# so it has not been specified here and will not be proposed in the autocompletion of Nushell.
|
||||
# The help message won't be overriden though, so it will still be present here
|
||||
|
||||
def health_categories [] {
|
||||
let languages = ^hx --health languages | detect columns | get Language | filter { $in != null }
|
||||
let completions = [ "all", "clipboard", "languages" ] | append $languages
|
||||
return $completions
|
||||
}
|
||||
|
||||
def grammar_categories [] { ["fetch", "build"] }
|
||||
|
||||
# A post-modern text editor.
|
||||
export extern hx [
|
||||
--help(-h), # Prints help information
|
||||
--tutor, # Loads the tutorial
|
||||
--health: string@health_categories, # Checks for potential errors in editor setup
|
||||
--grammar(-g): string@grammar_categories, # Fetches or builds tree-sitter grammars listed in `languages.toml`
|
||||
--config(-c): glob, # Specifies a file to use for configuration
|
||||
-v, # Increases logging verbosity each use for up to 3 times
|
||||
--log: glob, # Specifies a file to use for logging
|
||||
--version(-V), # Prints version information
|
||||
--vsplit, # Splits all given files vertically into different windows
|
||||
--hsplit, # Splits all given files horizontally into different windows
|
||||
--working-dir(-w): glob, # Specify an initial working directory
|
||||
...files: glob, # Sets the input file to use, position can also be specified via file[:row[:col]]
|
||||
]
|
@ -53,6 +53,10 @@ ## Integration tests
|
||||
[helpers.rs][helpers.rs]. The log level can be set with the `HELIX_LOG_LEVEL`
|
||||
environment variable, e.g. `HELIX_LOG_LEVEL=debug cargo integration-test`.
|
||||
|
||||
Contributors using MacOS might encounter `Too many open files (os error 24)`
|
||||
failures while running integration tests. This can be resolved by increasing
|
||||
the default value (e.g. to `10240` from `256`) by running `ulimit -n 10240`.
|
||||
|
||||
## Minimum Stable Rust Version (MSRV) Policy
|
||||
|
||||
Helix follows the MSRV of Firefox.
|
||||
|
@ -1,13 +1,14 @@
|
||||
|
||||
| Crate | Description |
|
||||
| ----------- | ----------- |
|
||||
| helix-core | Core editing primitives, functional. |
|
||||
| helix-lsp | Language server client |
|
||||
| helix-dap | Debug Adapter Protocol (DAP) client |
|
||||
| helix-loader | Functions for building, fetching, and loading external resources |
|
||||
| helix-view | UI abstractions for use in backends, imperative shell. |
|
||||
| helix-term | Terminal UI |
|
||||
| helix-tui | TUI primitives, forked from tui-rs, inspired by Cursive |
|
||||
| Crate | Description |
|
||||
| ----------- | ----------- |
|
||||
| helix-core | Core editing primitives, functional. |
|
||||
| helix-lsp | Language server client |
|
||||
| helix-lsp-types | Language Server Protocol type definitions |
|
||||
| helix-dap | Debug Adapter Protocol (DAP) client |
|
||||
| helix-loader | Functions for building, fetching, and loading external resources |
|
||||
| helix-view | UI abstractions for use in backends, imperative shell. |
|
||||
| helix-term | Terminal UI |
|
||||
| helix-tui | TUI primitives, forked from tui-rs, inspired by Cursive |
|
||||
|
||||
|
||||
This document contains a high-level overview of Helix internals.
|
||||
|
@ -5,19 +5,18 @@ ## Checklist
|
||||
`22.05.1`. In these instructions we'll use `<tag>` as a placeholder for the tag
|
||||
being published.
|
||||
|
||||
* Merge the changelog PR
|
||||
* Add new `<release>` entry in `contrib/Helix.appdata.xml` with release information according to the [AppStream spec](https://www.freedesktop.org/software/appstream/docs/sect-Metadata-Releases.html)
|
||||
* Merge the PR with the release updates. That branch should:
|
||||
* Update the version:
|
||||
* Update the `workspace.package.version` key in `Cargo.toml`. Cargo only accepts
|
||||
SemVer versions so a CalVer version of `22.07` for example must be formatted
|
||||
as `22.7.0`. Patch/bugfix releases should increment the SemVer patch number. A
|
||||
patch release for 22.07 would be `22.7.1`.
|
||||
* Run `cargo check` and commit the resulting change to `Cargo.lock`
|
||||
* Add changelog notes to `CHANGELOG.md`
|
||||
* Add new `<release>` entry in `contrib/Helix.appdata.xml` with release information according to the [AppStream spec](https://www.freedesktop.org/software/appstream/docs/sect-Metadata-Releases.html)
|
||||
* Tag and push
|
||||
* `git tag -s -m "<tag>" -a <tag> && git push`
|
||||
* Make sure to switch to master and pull first
|
||||
* Edit the `Cargo.toml` file and change the date in the `version` field to the next planned release
|
||||
* Due to Cargo having a strict requirement on SemVer with 3 or more version
|
||||
numbers, a `0` is required in the micro version; however, unless we are
|
||||
publishing a patch release after a major release, the `.0` is dropped in
|
||||
the user facing version.
|
||||
* Releases are planned to happen every two months, so `22.05.0` would change to `22.07.0`
|
||||
* If we are pushing a patch/bugfix release in the same month as the previous
|
||||
release, bump the micro version, e.g. `22.07.0` to `22.07.1`
|
||||
* Switch to master and pull
|
||||
* `git tag -s -m "<tag>" -a <tag> && git push` (note the `-s` which signs the tag)
|
||||
* Wait for the Release CI to finish
|
||||
* It will automatically turn the git tag into a GitHub release when it uploads artifacts
|
||||
* Edit the new release
|
||||
|
@ -126,6 +126,7 @@
|
||||
# disable fetching and building of tree-sitter grammars in the helix-term build.rs
|
||||
HELIX_DISABLE_AUTO_GRAMMAR_BUILD = "1";
|
||||
buildInputs = [stdenv.cc.cc.lib];
|
||||
nativeBuildInputs = [pkgs.installShellFiles];
|
||||
# disable tests
|
||||
doCheck = false;
|
||||
meta.mainProgram = "hx";
|
||||
@ -141,6 +142,7 @@
|
||||
cp contrib/Helix.desktop $out/share/applications
|
||||
cp logo.svg $out/share/icons/hicolor/scalable/apps/helix.svg
|
||||
cp contrib/helix.png $out/share/icons/hicolor/256x256/apps
|
||||
installShellCompletion contrib/completion/hx.{bash,fish,zsh}
|
||||
'';
|
||||
});
|
||||
helix = makeOverridableHelix self.packages.${system}.helix-unwrapped {};
|
||||
|
@ -23,12 +23,17 @@ helix-loader = { path = "../helix-loader" }
|
||||
ropey = { version = "1.6.1", default-features = false, features = ["simd"] }
|
||||
smallvec = "1.13"
|
||||
smartstring = "1.0.1"
|
||||
unicode-segmentation = "1.11"
|
||||
unicode-width = "0.1"
|
||||
unicode-segmentation = "1.12"
|
||||
# unicode-width is changing width definitions
|
||||
# that both break our logic and disagree with common
|
||||
# width definitions in terminals, we need to replace it.
|
||||
# For now lets lock the version to avoid rendering glitches
|
||||
# when installing without `--locked`
|
||||
unicode-width = "=0.1.12"
|
||||
unicode-general-category = "0.6"
|
||||
slotmap.workspace = true
|
||||
tree-sitter.workspace = true
|
||||
once_cell = "1.19"
|
||||
once_cell = "1.20"
|
||||
arc-swap = "1"
|
||||
regex = "1"
|
||||
bitflags = "2.6"
|
||||
@ -42,7 +47,7 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
toml = "0.8"
|
||||
|
||||
imara-diff = "0.1.6"
|
||||
imara-diff = "0.1.7"
|
||||
|
||||
encoding_rs = "0.8"
|
||||
|
||||
@ -54,7 +59,7 @@ textwrap = "0.16.1"
|
||||
steel-core = { workspace = true, optional = true }
|
||||
nucleo.workspace = true
|
||||
parking_lot = "0.12"
|
||||
globset = "0.4.14"
|
||||
globset = "0.4.15"
|
||||
|
||||
[dev-dependencies]
|
||||
quickcheck = { version = "1", default-features = false }
|
||||
|
@ -75,9 +75,9 @@ fn from((open, close): (&char, &char)) -> Self {
|
||||
|
||||
impl AutoPairs {
|
||||
/// Make a new AutoPairs set with the given pairs and default conditions.
|
||||
pub fn new<'a, V: 'a, A>(pairs: V) -> Self
|
||||
pub fn new<'a, V, A>(pairs: V) -> Self
|
||||
where
|
||||
V: IntoIterator<Item = A>,
|
||||
V: IntoIterator<Item = A> + 'a,
|
||||
A: Into<Pair>,
|
||||
{
|
||||
let mut auto_pairs = HashMap::new();
|
||||
|
@ -265,7 +265,7 @@ fn is_first_in_line(node: Node, text: RopeSlice, new_line_byte_pos: Option<usize
|
||||
/// This is usually constructed in one of 2 ways:
|
||||
/// - Successively add indent captures to get the (added) indent from a single line
|
||||
/// - Successively add the indent results for each line
|
||||
/// The string that this indentation defines starts with the string contained in the align field (unless it is None), followed by:
|
||||
/// The string that this indentation defines starts with the string contained in the align field (unless it is None), followed by:
|
||||
/// - max(0, indent - outdent) tabs, if tabs are used for indentation
|
||||
/// - max(0, indent - outdent)*indent_width spaces, if spaces are used for indentation
|
||||
#[derive(Default, Debug, PartialEq, Eq, Clone)]
|
||||
@ -457,7 +457,7 @@ fn query_indents<'a>(
|
||||
// Skip matches where not all custom predicates are fulfilled
|
||||
if !query.general_predicates(m.pattern_index).iter().all(|pred| {
|
||||
match pred.operator.as_ref() {
|
||||
"not-kind-eq?" => match (pred.args.get(0), pred.args.get(1)) {
|
||||
"not-kind-eq?" => match (pred.args.first(), pred.args.get(1)) {
|
||||
(
|
||||
Some(QueryPredicateArg::Capture(capture_idx)),
|
||||
Some(QueryPredicateArg::String(kind)),
|
||||
@ -473,7 +473,7 @@ fn query_indents<'a>(
|
||||
}
|
||||
},
|
||||
"same-line?" | "not-same-line?" => {
|
||||
match (pred.args.get(0), pred.args.get(1)) {
|
||||
match (pred.args.first(), pred.args.get(1)) {
|
||||
(
|
||||
Some(QueryPredicateArg::Capture(capt1)),
|
||||
Some(QueryPredicateArg::Capture(capt2))
|
||||
@ -495,7 +495,7 @@ fn query_indents<'a>(
|
||||
}
|
||||
}
|
||||
}
|
||||
"one-line?" | "not-one-line?" => match pred.args.get(0) {
|
||||
"one-line?" | "not-one-line?" => match pred.args.first() {
|
||||
Some(QueryPredicateArg::Capture(capture_idx)) => {
|
||||
let node = m.nodes_for_capture_index(*capture_idx).next();
|
||||
|
||||
@ -786,6 +786,7 @@ fn init_indent_query<'a, 'b>(
|
||||
/// - The line after the node. This is defined by:
|
||||
/// - The scope `tail`.
|
||||
/// - The scope `all` if this node is not the first node on its line.
|
||||
///
|
||||
/// Intuitively, `all` applies to everything contained in this node while `tail` applies to everything except for the first line of the node.
|
||||
/// The indents from different nodes for the same line are then combined.
|
||||
/// The result [Indentation] is simply the sum of the [Indentation] for all lines.
|
||||
|
@ -197,13 +197,31 @@ pub fn move_prev_long_word_end(slice: RopeSlice, range: Range, count: usize) ->
|
||||
word_move(slice, range, count, WordMotionTarget::PrevLongWordEnd)
|
||||
}
|
||||
|
||||
pub fn move_next_sub_word_start(slice: RopeSlice, range: Range, count: usize) -> Range {
|
||||
word_move(slice, range, count, WordMotionTarget::NextSubWordStart)
|
||||
}
|
||||
|
||||
pub fn move_next_sub_word_end(slice: RopeSlice, range: Range, count: usize) -> Range {
|
||||
word_move(slice, range, count, WordMotionTarget::NextSubWordEnd)
|
||||
}
|
||||
|
||||
pub fn move_prev_sub_word_start(slice: RopeSlice, range: Range, count: usize) -> Range {
|
||||
word_move(slice, range, count, WordMotionTarget::PrevSubWordStart)
|
||||
}
|
||||
|
||||
pub fn move_prev_sub_word_end(slice: RopeSlice, range: Range, count: usize) -> Range {
|
||||
word_move(slice, range, count, WordMotionTarget::PrevSubWordEnd)
|
||||
}
|
||||
|
||||
fn word_move(slice: RopeSlice, range: Range, count: usize, target: WordMotionTarget) -> Range {
|
||||
let is_prev = matches!(
|
||||
target,
|
||||
WordMotionTarget::PrevWordStart
|
||||
| WordMotionTarget::PrevLongWordStart
|
||||
| WordMotionTarget::PrevSubWordStart
|
||||
| WordMotionTarget::PrevWordEnd
|
||||
| WordMotionTarget::PrevLongWordEnd
|
||||
| WordMotionTarget::PrevSubWordEnd
|
||||
);
|
||||
|
||||
// Special-case early-out.
|
||||
@ -383,6 +401,12 @@ pub enum WordMotionTarget {
|
||||
NextLongWordEnd,
|
||||
PrevLongWordStart,
|
||||
PrevLongWordEnd,
|
||||
// A sub word is similar to a regular word, except it is also delimited by
|
||||
// underscores and transitions from lowercase to uppercase.
|
||||
NextSubWordStart,
|
||||
NextSubWordEnd,
|
||||
PrevSubWordStart,
|
||||
PrevSubWordEnd,
|
||||
}
|
||||
|
||||
pub trait CharHelpers {
|
||||
@ -398,8 +422,10 @@ fn range_to_target(&mut self, target: WordMotionTarget, origin: Range) -> Range
|
||||
target,
|
||||
WordMotionTarget::PrevWordStart
|
||||
| WordMotionTarget::PrevLongWordStart
|
||||
| WordMotionTarget::PrevSubWordStart
|
||||
| WordMotionTarget::PrevWordEnd
|
||||
| WordMotionTarget::PrevLongWordEnd
|
||||
| WordMotionTarget::PrevSubWordEnd
|
||||
);
|
||||
|
||||
// Reverse the iterator if needed for the motion direction.
|
||||
@ -476,6 +502,25 @@ fn is_long_word_boundary(a: char, b: char) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_sub_word_boundary(a: char, b: char, dir: Direction) -> bool {
|
||||
match (categorize_char(a), categorize_char(b)) {
|
||||
(CharCategory::Word, CharCategory::Word) => {
|
||||
if (a == '_') != (b == '_') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Subword boundaries are directional: in 'fooBar', there is a
|
||||
// boundary between 'o' and 'B', but not between 'B' and 'a'.
|
||||
match dir {
|
||||
Direction::Forward => a.is_lowercase() && b.is_uppercase(),
|
||||
Direction::Backward => a.is_uppercase() && b.is_lowercase(),
|
||||
}
|
||||
}
|
||||
(a, b) if a != b => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn reached_target(target: WordMotionTarget, prev_ch: char, next_ch: char) -> bool {
|
||||
match target {
|
||||
WordMotionTarget::NextWordStart | WordMotionTarget::PrevWordEnd => {
|
||||
@ -494,6 +539,22 @@ fn reached_target(target: WordMotionTarget, prev_ch: char, next_ch: char) -> boo
|
||||
is_long_word_boundary(prev_ch, next_ch)
|
||||
&& (!prev_ch.is_whitespace() || char_is_line_ending(next_ch))
|
||||
}
|
||||
WordMotionTarget::NextSubWordStart => {
|
||||
is_sub_word_boundary(prev_ch, next_ch, Direction::Forward)
|
||||
&& (char_is_line_ending(next_ch) || !(next_ch.is_whitespace() || next_ch == '_'))
|
||||
}
|
||||
WordMotionTarget::PrevSubWordEnd => {
|
||||
is_sub_word_boundary(prev_ch, next_ch, Direction::Backward)
|
||||
&& (char_is_line_ending(next_ch) || !(next_ch.is_whitespace() || next_ch == '_'))
|
||||
}
|
||||
WordMotionTarget::NextSubWordEnd => {
|
||||
is_sub_word_boundary(prev_ch, next_ch, Direction::Forward)
|
||||
&& (!(prev_ch.is_whitespace() || prev_ch == '_') || char_is_line_ending(next_ch))
|
||||
}
|
||||
WordMotionTarget::PrevSubWordStart => {
|
||||
is_sub_word_boundary(prev_ch, next_ch, Direction::Backward)
|
||||
&& (!(prev_ch.is_whitespace() || prev_ch == '_') || char_is_line_ending(next_ch))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1012,6 +1073,178 @@ fn test_behaviour_when_moving_to_start_of_next_words() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_behaviour_when_moving_to_start_of_next_sub_words() {
|
||||
let tests = [
|
||||
(
|
||||
"NextSubwordStart",
|
||||
vec![
|
||||
(1, Range::new(0, 0), Range::new(0, 4)),
|
||||
(1, Range::new(4, 4), Range::new(4, 11)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"next_subword_start",
|
||||
vec![
|
||||
(1, Range::new(0, 0), Range::new(0, 5)),
|
||||
(1, Range::new(4, 4), Range::new(5, 13)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Next_Subword_Start",
|
||||
vec![
|
||||
(1, Range::new(0, 0), Range::new(0, 5)),
|
||||
(1, Range::new(4, 4), Range::new(5, 13)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"NEXT_SUBWORD_START",
|
||||
vec![
|
||||
(1, Range::new(0, 0), Range::new(0, 5)),
|
||||
(1, Range::new(4, 4), Range::new(5, 13)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"next subword start",
|
||||
vec![
|
||||
(1, Range::new(0, 0), Range::new(0, 5)),
|
||||
(1, Range::new(4, 4), Range::new(5, 13)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Next Subword Start",
|
||||
vec![
|
||||
(1, Range::new(0, 0), Range::new(0, 5)),
|
||||
(1, Range::new(4, 4), Range::new(5, 13)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"NEXT SUBWORD START",
|
||||
vec![
|
||||
(1, Range::new(0, 0), Range::new(0, 5)),
|
||||
(1, Range::new(4, 4), Range::new(5, 13)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"next__subword__start",
|
||||
vec![
|
||||
(1, Range::new(0, 0), Range::new(0, 6)),
|
||||
(1, Range::new(4, 4), Range::new(4, 6)),
|
||||
(1, Range::new(5, 5), Range::new(6, 15)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Next__Subword__Start",
|
||||
vec![
|
||||
(1, Range::new(0, 0), Range::new(0, 6)),
|
||||
(1, Range::new(4, 4), Range::new(4, 6)),
|
||||
(1, Range::new(5, 5), Range::new(6, 15)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"NEXT__SUBWORD__START",
|
||||
vec![
|
||||
(1, Range::new(0, 0), Range::new(0, 6)),
|
||||
(1, Range::new(4, 4), Range::new(4, 6)),
|
||||
(1, Range::new(5, 5), Range::new(6, 15)),
|
||||
],
|
||||
),
|
||||
];
|
||||
|
||||
for (sample, scenario) in tests {
|
||||
for (count, begin, expected_end) in scenario.into_iter() {
|
||||
let range = move_next_sub_word_start(Rope::from(sample).slice(..), begin, count);
|
||||
assert_eq!(range, expected_end, "Case failed: [{}]", sample);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_behaviour_when_moving_to_end_of_next_sub_words() {
|
||||
let tests = [
|
||||
(
|
||||
"NextSubwordEnd",
|
||||
vec![
|
||||
(1, Range::new(0, 0), Range::new(0, 4)),
|
||||
(1, Range::new(4, 4), Range::new(4, 11)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"next subword end",
|
||||
vec![
|
||||
(1, Range::new(0, 0), Range::new(0, 4)),
|
||||
(1, Range::new(4, 4), Range::new(4, 12)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Next Subword End",
|
||||
vec![
|
||||
(1, Range::new(0, 0), Range::new(0, 4)),
|
||||
(1, Range::new(4, 4), Range::new(4, 12)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"NEXT SUBWORD END",
|
||||
vec![
|
||||
(1, Range::new(0, 0), Range::new(0, 4)),
|
||||
(1, Range::new(4, 4), Range::new(4, 12)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"next_subword_end",
|
||||
vec![
|
||||
(1, Range::new(0, 0), Range::new(0, 4)),
|
||||
(1, Range::new(4, 4), Range::new(4, 12)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Next_Subword_End",
|
||||
vec![
|
||||
(1, Range::new(0, 0), Range::new(0, 4)),
|
||||
(1, Range::new(4, 4), Range::new(4, 12)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"NEXT_SUBWORD_END",
|
||||
vec![
|
||||
(1, Range::new(0, 0), Range::new(0, 4)),
|
||||
(1, Range::new(4, 4), Range::new(4, 12)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"next__subword__end",
|
||||
vec![
|
||||
(1, Range::new(0, 0), Range::new(0, 4)),
|
||||
(1, Range::new(4, 4), Range::new(4, 13)),
|
||||
(1, Range::new(5, 5), Range::new(5, 13)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Next__Subword__End",
|
||||
vec![
|
||||
(1, Range::new(0, 0), Range::new(0, 4)),
|
||||
(1, Range::new(4, 4), Range::new(4, 13)),
|
||||
(1, Range::new(5, 5), Range::new(5, 13)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"NEXT__SUBWORD__END",
|
||||
vec![
|
||||
(1, Range::new(0, 0), Range::new(0, 4)),
|
||||
(1, Range::new(4, 4), Range::new(4, 13)),
|
||||
(1, Range::new(5, 5), Range::new(5, 13)),
|
||||
],
|
||||
),
|
||||
];
|
||||
|
||||
for (sample, scenario) in tests {
|
||||
for (count, begin, expected_end) in scenario.into_iter() {
|
||||
let range = move_next_sub_word_end(Rope::from(sample).slice(..), begin, count);
|
||||
assert_eq!(range, expected_end, "Case failed: [{}]", sample);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_behaviour_when_moving_to_start_of_next_long_words() {
|
||||
let tests = [
|
||||
@ -1181,6 +1414,92 @@ fn test_behaviour_when_moving_to_start_of_previous_words() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_behaviour_when_moving_to_start_of_previous_sub_words() {
|
||||
let tests = [
|
||||
(
|
||||
"PrevSubwordEnd",
|
||||
vec![
|
||||
(1, Range::new(13, 13), Range::new(14, 11)),
|
||||
(1, Range::new(11, 11), Range::new(11, 4)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"prev subword end",
|
||||
vec![
|
||||
(1, Range::new(15, 15), Range::new(16, 13)),
|
||||
(1, Range::new(12, 12), Range::new(13, 5)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Prev Subword End",
|
||||
vec![
|
||||
(1, Range::new(15, 15), Range::new(16, 13)),
|
||||
(1, Range::new(12, 12), Range::new(13, 5)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"PREV SUBWORD END",
|
||||
vec![
|
||||
(1, Range::new(15, 15), Range::new(16, 13)),
|
||||
(1, Range::new(12, 12), Range::new(13, 5)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"prev_subword_end",
|
||||
vec![
|
||||
(1, Range::new(15, 15), Range::new(16, 13)),
|
||||
(1, Range::new(12, 12), Range::new(13, 5)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Prev_Subword_End",
|
||||
vec![
|
||||
(1, Range::new(15, 15), Range::new(16, 13)),
|
||||
(1, Range::new(12, 12), Range::new(13, 5)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"PREV_SUBWORD_END",
|
||||
vec![
|
||||
(1, Range::new(15, 15), Range::new(16, 13)),
|
||||
(1, Range::new(12, 12), Range::new(13, 5)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"prev__subword__end",
|
||||
vec![
|
||||
(1, Range::new(17, 17), Range::new(18, 15)),
|
||||
(1, Range::new(13, 13), Range::new(14, 6)),
|
||||
(1, Range::new(14, 14), Range::new(15, 6)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Prev__Subword__End",
|
||||
vec![
|
||||
(1, Range::new(17, 17), Range::new(18, 15)),
|
||||
(1, Range::new(13, 13), Range::new(14, 6)),
|
||||
(1, Range::new(14, 14), Range::new(15, 6)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"PREV__SUBWORD__END",
|
||||
vec![
|
||||
(1, Range::new(17, 17), Range::new(18, 15)),
|
||||
(1, Range::new(13, 13), Range::new(14, 6)),
|
||||
(1, Range::new(14, 14), Range::new(15, 6)),
|
||||
],
|
||||
),
|
||||
];
|
||||
|
||||
for (sample, scenario) in tests {
|
||||
for (count, begin, expected_end) in scenario.into_iter() {
|
||||
let range = move_prev_sub_word_start(Rope::from(sample).slice(..), begin, count);
|
||||
assert_eq!(range, expected_end, "Case failed: [{}]", sample);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_behaviour_when_moving_to_start_of_previous_long_words() {
|
||||
let tests = [
|
||||
@ -1444,6 +1763,92 @@ fn test_behaviour_when_moving_to_end_of_previous_words() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_behaviour_when_moving_to_end_of_previous_sub_words() {
|
||||
let tests = [
|
||||
(
|
||||
"PrevSubwordEnd",
|
||||
vec![
|
||||
(1, Range::new(13, 13), Range::new(14, 11)),
|
||||
(1, Range::new(11, 11), Range::new(11, 4)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"prev subword end",
|
||||
vec![
|
||||
(1, Range::new(15, 15), Range::new(16, 12)),
|
||||
(1, Range::new(12, 12), Range::new(12, 4)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Prev Subword End",
|
||||
vec![
|
||||
(1, Range::new(15, 15), Range::new(16, 12)),
|
||||
(1, Range::new(12, 12), Range::new(12, 4)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"PREV SUBWORD END",
|
||||
vec![
|
||||
(1, Range::new(15, 15), Range::new(16, 12)),
|
||||
(1, Range::new(12, 12), Range::new(12, 4)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"prev_subword_end",
|
||||
vec![
|
||||
(1, Range::new(15, 15), Range::new(16, 12)),
|
||||
(1, Range::new(12, 12), Range::new(12, 4)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Prev_Subword_End",
|
||||
vec![
|
||||
(1, Range::new(15, 15), Range::new(16, 12)),
|
||||
(1, Range::new(12, 12), Range::new(12, 4)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"PREV_SUBWORD_END",
|
||||
vec![
|
||||
(1, Range::new(15, 15), Range::new(16, 12)),
|
||||
(1, Range::new(12, 12), Range::new(12, 4)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"prev__subword__end",
|
||||
vec![
|
||||
(1, Range::new(17, 17), Range::new(18, 13)),
|
||||
(1, Range::new(13, 13), Range::new(13, 4)),
|
||||
(1, Range::new(14, 14), Range::new(15, 13)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Prev__Subword__End",
|
||||
vec![
|
||||
(1, Range::new(17, 17), Range::new(18, 13)),
|
||||
(1, Range::new(13, 13), Range::new(13, 4)),
|
||||
(1, Range::new(14, 14), Range::new(15, 13)),
|
||||
],
|
||||
),
|
||||
(
|
||||
"PREV__SUBWORD__END",
|
||||
vec![
|
||||
(1, Range::new(17, 17), Range::new(18, 13)),
|
||||
(1, Range::new(13, 13), Range::new(13, 4)),
|
||||
(1, Range::new(14, 14), Range::new(15, 13)),
|
||||
],
|
||||
),
|
||||
];
|
||||
|
||||
for (sample, scenario) in tests {
|
||||
for (count, begin, expected_end) in scenario.into_iter() {
|
||||
let range = move_prev_sub_word_end(Rope::from(sample).slice(..), begin, count);
|
||||
assert_eq!(range, expected_end, "Case failed: [{}]", sample);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_behaviour_when_moving_to_end_of_next_long_words() {
|
||||
let tests = [
|
||||
|
@ -184,16 +184,16 @@ pub fn map(mut self, changes: &ChangeSet) -> Self {
|
||||
|
||||
let positions_to_map = match self.anchor.cmp(&self.head) {
|
||||
Ordering::Equal => [
|
||||
(&mut self.anchor, Assoc::After),
|
||||
(&mut self.head, Assoc::After),
|
||||
(&mut self.anchor, Assoc::AfterSticky),
|
||||
(&mut self.head, Assoc::AfterSticky),
|
||||
],
|
||||
Ordering::Less => [
|
||||
(&mut self.anchor, Assoc::After),
|
||||
(&mut self.head, Assoc::Before),
|
||||
(&mut self.anchor, Assoc::AfterSticky),
|
||||
(&mut self.head, Assoc::BeforeSticky),
|
||||
],
|
||||
Ordering::Greater => [
|
||||
(&mut self.head, Assoc::After),
|
||||
(&mut self.anchor, Assoc::Before),
|
||||
(&mut self.head, Assoc::AfterSticky),
|
||||
(&mut self.anchor, Assoc::BeforeSticky),
|
||||
],
|
||||
};
|
||||
changes.update_positions(positions_to_map.into_iter());
|
||||
@ -482,16 +482,16 @@ pub fn map_no_normalize(mut self, changes: &ChangeSet) -> Self {
|
||||
range.old_visual_position = None;
|
||||
match range.anchor.cmp(&range.head) {
|
||||
Ordering::Equal => [
|
||||
(&mut range.anchor, Assoc::After),
|
||||
(&mut range.head, Assoc::After),
|
||||
(&mut range.anchor, Assoc::AfterSticky),
|
||||
(&mut range.head, Assoc::AfterSticky),
|
||||
],
|
||||
Ordering::Less => [
|
||||
(&mut range.anchor, Assoc::After),
|
||||
(&mut range.head, Assoc::Before),
|
||||
(&mut range.anchor, Assoc::AfterSticky),
|
||||
(&mut range.head, Assoc::BeforeSticky),
|
||||
],
|
||||
Ordering::Greater => [
|
||||
(&mut range.head, Assoc::After),
|
||||
(&mut range.anchor, Assoc::Before),
|
||||
(&mut range.head, Assoc::AfterSticky),
|
||||
(&mut range.anchor, Assoc::BeforeSticky),
|
||||
],
|
||||
}
|
||||
});
|
||||
|
@ -1025,9 +1025,10 @@ pub fn language_configuration_for_injection_string(
|
||||
match capture {
|
||||
InjectionLanguageMarker::Name(string) => self.language_config_for_name(string),
|
||||
InjectionLanguageMarker::Filename(file) => self.language_config_for_file_name(file),
|
||||
InjectionLanguageMarker::Shebang(shebang) => {
|
||||
self.language_config_for_language_id(shebang)
|
||||
}
|
||||
InjectionLanguageMarker::Shebang(shebang) => self
|
||||
.language_config_ids_by_shebang
|
||||
.get(shebang)
|
||||
.and_then(|&id| self.language_configs.get(id).cloned()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1430,8 +1431,11 @@ pub fn highlight_iter<'a>(
|
||||
// The `captures` iterator borrows the `Tree` and the `QueryCursor`, which
|
||||
// prevents them from being moved. But both of these values are really just
|
||||
// pointers, so it's actually ok to move them.
|
||||
let cursor_ref =
|
||||
unsafe { mem::transmute::<_, &'static mut QueryCursor>(&mut cursor) };
|
||||
let cursor_ref = unsafe {
|
||||
mem::transmute::<&mut tree_sitter::QueryCursor, &mut tree_sitter::QueryCursor>(
|
||||
&mut cursor,
|
||||
)
|
||||
};
|
||||
|
||||
// if reusing cursors & no range this resets to whole range
|
||||
cursor_ref.set_byte_range(range.clone().unwrap_or(0..usize::MAX));
|
||||
@ -1736,7 +1740,7 @@ fn traverse(point: Point, text: &Tendril) -> Point {
|
||||
}
|
||||
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::{iter, mem, ops, str, usize};
|
||||
use std::{iter, mem, ops, str};
|
||||
use tree_sitter::{
|
||||
Language as Grammar, Node, Parser, Point, Query, QueryCaptures, QueryCursor, QueryError,
|
||||
QueryMatch, Range, TextProvider, Tree,
|
||||
@ -2688,6 +2692,8 @@ fn pretty_print_tree_impl<W: fmt::Write>(
|
||||
}
|
||||
|
||||
write!(fmt, "({}", node.kind())?;
|
||||
} else {
|
||||
write!(fmt, " \"{}\"", node.kind())?;
|
||||
}
|
||||
|
||||
// Handle children.
|
||||
@ -2946,7 +2952,7 @@ fn assert_pretty_print(
|
||||
#[test]
|
||||
fn test_pretty_print() {
|
||||
let source = r#"// Hello"#;
|
||||
assert_pretty_print("rust", source, "(line_comment)", 0, source.len());
|
||||
assert_pretty_print("rust", source, "(line_comment \"//\")", 0, source.len());
|
||||
|
||||
// A large tree should be indented with fields:
|
||||
let source = r#"fn main() {
|
||||
@ -2956,16 +2962,16 @@ fn test_pretty_print() {
|
||||
"rust",
|
||||
source,
|
||||
concat!(
|
||||
"(function_item\n",
|
||||
"(function_item \"fn\"\n",
|
||||
" name: (identifier)\n",
|
||||
" parameters: (parameters)\n",
|
||||
" body: (block\n",
|
||||
" parameters: (parameters \"(\" \")\")\n",
|
||||
" body: (block \"{\"\n",
|
||||
" (expression_statement\n",
|
||||
" (macro_invocation\n",
|
||||
" macro: (identifier)\n",
|
||||
" (token_tree\n",
|
||||
" (string_literal\n",
|
||||
" (string_content)))))))",
|
||||
" macro: (identifier) \"!\"\n",
|
||||
" (token_tree \"(\"\n",
|
||||
" (string_literal \"\"\"\n",
|
||||
" (string_content) \"\"\") \")\")) \";\") \"}\"))",
|
||||
),
|
||||
0,
|
||||
source.len(),
|
||||
@ -2977,7 +2983,7 @@ fn test_pretty_print() {
|
||||
|
||||
// Error nodes are printed as errors:
|
||||
let source = r#"}{"#;
|
||||
assert_pretty_print("rust", source, "(ERROR)", 0, source.len());
|
||||
assert_pretty_print("rust", source, "(ERROR \"}\" \"{\")", 0, source.len());
|
||||
|
||||
// Fields broken under unnamed nodes are determined correctly.
|
||||
// In the following source, `object` belongs to the `singleton_method`
|
||||
@ -2992,11 +2998,11 @@ fn test_pretty_print() {
|
||||
"ruby",
|
||||
source,
|
||||
concat!(
|
||||
"(singleton_method\n",
|
||||
" object: (self)\n",
|
||||
"(singleton_method \"def\"\n",
|
||||
" object: (self) \".\"\n",
|
||||
" name: (identifier)\n",
|
||||
" body: (body_statement\n",
|
||||
" (true)))"
|
||||
" (true)) \"end\")"
|
||||
),
|
||||
0,
|
||||
source.len(),
|
||||
|
@ -204,7 +204,7 @@ fn layer_id_containing_byte_range(&self, start: usize, end: usize) -> LayerId {
|
||||
|
||||
self.injection_ranges[start_idx..]
|
||||
.iter()
|
||||
.take_while(|range| range.start < end)
|
||||
.take_while(|range| range.start < end || range.depth > 1)
|
||||
.find_map(|range| (range.start <= start).then_some(range.layer_id))
|
||||
.unwrap_or(self.root)
|
||||
}
|
||||
|
@ -29,6 +29,12 @@ pub enum Assoc {
|
||||
/// Acts like `Before` if a word character is inserted
|
||||
/// before the position, otherwise acts like `After`
|
||||
BeforeWord,
|
||||
/// Acts like `Before` but if the position is within an exact replacement
|
||||
/// (exact size) the offset to the start of the replacement is kept
|
||||
BeforeSticky,
|
||||
/// Acts like `After` but if the position is within an exact replacement
|
||||
/// (exact size) the offset to the start of the replacement is kept
|
||||
AfterSticky,
|
||||
}
|
||||
|
||||
impl Assoc {
|
||||
@ -40,13 +46,17 @@ fn stay_at_gaps(self) -> bool {
|
||||
fn insert_offset(self, s: &str) -> usize {
|
||||
let chars = s.chars().count();
|
||||
match self {
|
||||
Assoc::After => chars,
|
||||
Assoc::After | Assoc::AfterSticky => chars,
|
||||
Assoc::AfterWord => s.chars().take_while(|&c| char_is_word(c)).count(),
|
||||
// return position before inserted text
|
||||
Assoc::Before => 0,
|
||||
Assoc::Before | Assoc::BeforeSticky => 0,
|
||||
Assoc::BeforeWord => chars - s.chars().rev().take_while(|&c| char_is_word(c)).count(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sticky(self) -> bool {
|
||||
matches!(self, Assoc::BeforeSticky | Assoc::AfterSticky)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
@ -456,8 +466,14 @@ macro_rules! map {
|
||||
if pos == old_pos && assoc.stay_at_gaps() {
|
||||
new_pos
|
||||
} else {
|
||||
// place to end of insert
|
||||
new_pos + assoc.insert_offset(s)
|
||||
let ins = assoc.insert_offset(s);
|
||||
// if the deleted and inserted text have the exact same size
|
||||
// keep the relative offset into the new text
|
||||
if *len == ins && assoc.sticky() {
|
||||
new_pos + (pos - old_pos)
|
||||
} else {
|
||||
new_pos + assoc.insert_offset(s)
|
||||
}
|
||||
}
|
||||
}),
|
||||
i
|
||||
|
@ -1,12 +1,18 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{
|
||||
fmt,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
/// A generic pointer to a file location.
|
||||
///
|
||||
/// Currently this type only supports paths to local files.
|
||||
///
|
||||
/// Cloning this type is cheap: the internal representation uses an Arc.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[non_exhaustive]
|
||||
pub enum Uri {
|
||||
File(PathBuf),
|
||||
File(Arc<Path>),
|
||||
}
|
||||
|
||||
impl Uri {
|
||||
@ -23,26 +29,18 @@ pub fn as_path(&self) -> Option<&Path> {
|
||||
Self::File(path) => Some(path),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_path_buf(self) -> Option<PathBuf> {
|
||||
match self {
|
||||
Self::File(path) => Some(path),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PathBuf> for Uri {
|
||||
fn from(path: PathBuf) -> Self {
|
||||
Self::File(path)
|
||||
Self::File(path.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Uri> for PathBuf {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(uri: Uri) -> Result<Self, Self::Error> {
|
||||
match uri {
|
||||
Uri::File(path) => Ok(path),
|
||||
impl fmt::Display for Uri {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::File(path) => write!(f, "{}", path.display()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -59,11 +57,16 @@ pub enum UrlConversionErrorKind {
|
||||
UnableToConvert,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for UrlConversionError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
impl fmt::Display for UrlConversionError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.kind {
|
||||
UrlConversionErrorKind::UnsupportedScheme => {
|
||||
write!(f, "unsupported scheme in URL: {}", self.source.scheme())
|
||||
write!(
|
||||
f,
|
||||
"unsupported scheme '{}' in URL {}",
|
||||
self.source.scheme(),
|
||||
self.source
|
||||
)
|
||||
}
|
||||
UrlConversionErrorKind::UnableToConvert => {
|
||||
write!(f, "unable to convert URL to file path: {}", self.source)
|
||||
@ -77,7 +80,7 @@ impl std::error::Error for UrlConversionError {}
|
||||
fn convert_url_to_uri(url: &url::Url) -> Result<Uri, UrlConversionErrorKind> {
|
||||
if url.scheme() == "file" {
|
||||
url.to_file_path()
|
||||
.map(|path| Uri::File(helix_stdx::path::normalize(path)))
|
||||
.map(|path| Uri::File(helix_stdx::path::normalize(path).into()))
|
||||
.map_err(|_| UrlConversionErrorKind::UnableToConvert)
|
||||
} else {
|
||||
Err(UrlConversionErrorKind::UnsupportedScheme)
|
||||
|
@ -19,7 +19,7 @@ tokio = { version = "1", features = ["rt", "rt-multi-thread", "time", "sync", "p
|
||||
# setup new events on intalization, hardware-lock-elision hugnly benefits this case
|
||||
# as is essentially makes the lock entirely free as long as there is no writes
|
||||
parking_lot = { version = "0.12", features = ["hardware-lock-elision"] }
|
||||
once_cell = "1.18"
|
||||
once_cell = "1.20"
|
||||
|
||||
anyhow = "1"
|
||||
log = "0.4"
|
||||
|
@ -22,7 +22,7 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
toml = "0.8"
|
||||
etcetera = "0.8"
|
||||
tree-sitter.workspace = true
|
||||
once_cell = "1.19"
|
||||
once_cell = "1.20"
|
||||
log = "0.4"
|
||||
|
||||
# TODO: these two should be on !wasm32 only
|
||||
@ -30,8 +30,8 @@ log = "0.4"
|
||||
# cloning/compiling tree-sitter grammars
|
||||
cc = { version = "1" }
|
||||
threadpool = { version = "1.0" }
|
||||
tempfile = "3.10.1"
|
||||
dunce = "1.0.4"
|
||||
tempfile = "3.13.0"
|
||||
dunce = "1.0.5"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
libloading = "0.8"
|
||||
|
@ -225,13 +225,17 @@ fn get_name(v: &Value) -> Option<&str> {
|
||||
/// Used as a ceiling dir for LSP root resolution, the filepicker and potentially as a future filewatching root
|
||||
///
|
||||
/// This function starts searching the FS upward from the CWD
|
||||
/// and returns the first directory that contains either `.git` or `.helix`.
|
||||
/// and returns the first directory that contains either `.git`, `.svn`, `.jj` or `.helix`.
|
||||
/// If no workspace was found returns (CWD, true).
|
||||
/// Otherwise (workspace, false) is returned
|
||||
pub fn find_workspace() -> (PathBuf, bool) {
|
||||
let current_dir = current_working_dir();
|
||||
for ancestor in current_dir.ancestors() {
|
||||
if ancestor.join(".git").exists() || ancestor.join(".helix").exists() {
|
||||
if ancestor.join(".git").exists()
|
||||
|| ancestor.join(".svn").exists()
|
||||
|| ancestor.join(".jj").exists()
|
||||
|| ancestor.join(".helix").exists()
|
||||
{
|
||||
return (ancestor.to_owned(), false);
|
||||
}
|
||||
}
|
||||
|
176
helix-lsp-types/Cargo.lock
generated
Normal file
176
helix-lsp-types/Cargo.lock
generated
Normal file
@ -0,0 +1,176 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
|
||||
|
||||
[[package]]
|
||||
name = "lsp-types"
|
||||
version = "0.95.1"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.145"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.145"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41feea4228a6f1cd09ec7a3593a682276702cd67b5273544757dae23c096f074"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_repr"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
]
|
34
helix-lsp-types/Cargo.toml
Normal file
34
helix-lsp-types/Cargo.toml
Normal file
@ -0,0 +1,34 @@
|
||||
[package]
|
||||
name = "helix-lsp-types"
|
||||
version = "0.95.1"
|
||||
authors = [
|
||||
# Original authors
|
||||
"Markus Westerlind <marwes91@gmail.com>",
|
||||
"Bruno Medeiros <bruno.do.medeiros@gmail.com>",
|
||||
# Since forking
|
||||
"Helix contributors"
|
||||
]
|
||||
edition = "2018"
|
||||
description = "Types for interaction with a language server, using VSCode's Language Server Protocol"
|
||||
|
||||
repository = "https://github.com/gluon-lang/lsp-types"
|
||||
documentation = "https://docs.rs/lsp-types"
|
||||
|
||||
readme = "README.md"
|
||||
|
||||
keywords = ["language", "server", "lsp", "vscode", "lsif"]
|
||||
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2.6.0"
|
||||
serde = { version = "1.0.209", features = ["derive"] }
|
||||
serde_json = "1.0.127"
|
||||
serde_repr = "0.1"
|
||||
url = {version = "2.0.0", features = ["serde"]}
|
||||
|
||||
[features]
|
||||
default = []
|
||||
# Enables proposed LSP extensions.
|
||||
# NOTE: No semver compatibility is guaranteed for types enabled by this feature.
|
||||
proposed = []
|
22
helix-lsp-types/LICENSE
Normal file
22
helix-lsp-types/LICENSE
Normal file
@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Markus Westerlind
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
3
helix-lsp-types/README.md
Normal file
3
helix-lsp-types/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Helix's `lsp-types`
|
||||
|
||||
This is a fork of the [`lsp-types`](https://crates.io/crates/lsp-types) crate ([`gluon-lang/lsp-types`](https://github.com/gluon-lang/lsp-types)) taken at version v0.95.1 (commit [3e6daee](https://github.com/gluon-lang/lsp-types/commit/3e6daee771d14db4094a554b8d03e29c310dfcbe)). This fork focuses usability improvements that make the types easier to work with for the Helix codebase. For example the URL type - the `uri` crate at this version of `lsp-types` - will be replaced with a wrapper around a string.
|
127
helix-lsp-types/src/call_hierarchy.rs
Normal file
127
helix-lsp-types/src/call_hierarchy.rs
Normal file
@ -0,0 +1,127 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
DynamicRegistrationClientCapabilities, PartialResultParams, Range, SymbolKind, SymbolTag,
|
||||
TextDocumentPositionParams, WorkDoneProgressOptions, WorkDoneProgressParams,
|
||||
};
|
||||
|
||||
pub type CallHierarchyClientCapabilities = DynamicRegistrationClientCapabilities;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize, Copy)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CallHierarchyOptions {
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_options: WorkDoneProgressOptions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize, Copy)]
|
||||
#[serde(untagged)]
|
||||
pub enum CallHierarchyServerCapability {
|
||||
Simple(bool),
|
||||
Options(CallHierarchyOptions),
|
||||
}
|
||||
|
||||
impl From<CallHierarchyOptions> for CallHierarchyServerCapability {
|
||||
fn from(from: CallHierarchyOptions) -> Self {
|
||||
Self::Options(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for CallHierarchyServerCapability {
|
||||
fn from(from: bool) -> Self {
|
||||
Self::Simple(from)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CallHierarchyPrepareParams {
|
||||
#[serde(flatten)]
|
||||
pub text_document_position_params: TextDocumentPositionParams,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CallHierarchyItem {
|
||||
/// The name of this item.
|
||||
pub name: String,
|
||||
|
||||
/// The kind of this item.
|
||||
pub kind: SymbolKind,
|
||||
|
||||
/// Tags for this item.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tags: Option<Vec<SymbolTag>>,
|
||||
|
||||
/// More detail for this item, e.g. the signature of a function.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub detail: Option<String>,
|
||||
|
||||
/// The resource identifier of this item.
|
||||
pub uri: Url,
|
||||
|
||||
/// The range enclosing this symbol not including leading/trailing whitespace but everything else, e.g. comments and code.
|
||||
pub range: Range,
|
||||
|
||||
/// The range that should be selected and revealed when this symbol is being picked, e.g. the name of a function.
|
||||
/// Must be contained by the [`range`](#CallHierarchyItem.range).
|
||||
pub selection_range: Range,
|
||||
|
||||
/// A data entry field that is preserved between a call hierarchy prepare and incoming calls or outgoing calls requests.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub data: Option<Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CallHierarchyIncomingCallsParams {
|
||||
pub item: CallHierarchyItem,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub partial_result_params: PartialResultParams,
|
||||
}
|
||||
|
||||
/// Represents an incoming call, e.g. a caller of a method or constructor.
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CallHierarchyIncomingCall {
|
||||
/// The item that makes the call.
|
||||
pub from: CallHierarchyItem,
|
||||
|
||||
/// The range at which at which the calls appears. This is relative to the caller
|
||||
/// denoted by [`this.from`](#CallHierarchyIncomingCall.from).
|
||||
pub from_ranges: Vec<Range>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CallHierarchyOutgoingCallsParams {
|
||||
pub item: CallHierarchyItem,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub partial_result_params: PartialResultParams,
|
||||
}
|
||||
|
||||
/// Represents an outgoing call, e.g. calling a getter from a method or a method from a constructor etc.
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CallHierarchyOutgoingCall {
|
||||
/// The item that is called.
|
||||
pub to: CallHierarchyItem,
|
||||
|
||||
/// The range at which this item is called. This is the range relative to the caller, e.g the item
|
||||
/// passed to [`provideCallHierarchyOutgoingCalls`](#CallHierarchyItemProvider.provideCallHierarchyOutgoingCalls)
|
||||
/// and not [`this.to`](#CallHierarchyOutgoingCall.to).
|
||||
pub from_ranges: Vec<Range>,
|
||||
}
|
395
helix-lsp-types/src/code_action.rs
Normal file
395
helix-lsp-types/src/code_action.rs
Normal file
@ -0,0 +1,395 @@
|
||||
use crate::{
|
||||
Command, Diagnostic, PartialResultParams, Range, TextDocumentIdentifier,
|
||||
WorkDoneProgressOptions, WorkDoneProgressParams, WorkspaceEdit,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
use std::borrow::Cow;
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum CodeActionProviderCapability {
|
||||
Simple(bool),
|
||||
Options(CodeActionOptions),
|
||||
}
|
||||
|
||||
impl From<CodeActionOptions> for CodeActionProviderCapability {
|
||||
fn from(from: CodeActionOptions) -> Self {
|
||||
Self::Options(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for CodeActionProviderCapability {
|
||||
fn from(from: bool) -> Self {
|
||||
Self::Simple(from)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodeActionClientCapabilities {
|
||||
///
|
||||
/// This capability supports dynamic registration.
|
||||
///
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub dynamic_registration: Option<bool>,
|
||||
|
||||
/// The client support code action literals as a valid
|
||||
/// response of the `textDocument/codeAction` request.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub code_action_literal_support: Option<CodeActionLiteralSupport>,
|
||||
|
||||
/// Whether code action supports the `isPreferred` property.
|
||||
///
|
||||
/// @since 3.15.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub is_preferred_support: Option<bool>,
|
||||
|
||||
/// Whether code action supports the `disabled` property.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub disabled_support: Option<bool>,
|
||||
|
||||
/// Whether code action supports the `data` property which is
|
||||
/// preserved between a `textDocument/codeAction` and a
|
||||
/// `codeAction/resolve` request.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub data_support: Option<bool>,
|
||||
|
||||
/// Whether the client supports resolving additional code action
|
||||
/// properties via a separate `codeAction/resolve` request.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub resolve_support: Option<CodeActionCapabilityResolveSupport>,
|
||||
|
||||
/// Whether the client honors the change annotations in
|
||||
/// text edits and resource operations returned via the
|
||||
/// `CodeAction#edit` property by for example presenting
|
||||
/// the workspace edit in the user interface and asking
|
||||
/// for confirmation.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub honors_change_annotations: Option<bool>,
|
||||
}
|
||||
|
||||
/// Whether the client supports resolving additional code action
|
||||
/// properties via a separate `codeAction/resolve` request.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodeActionCapabilityResolveSupport {
|
||||
/// The properties that a client can resolve lazily.
|
||||
pub properties: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodeActionLiteralSupport {
|
||||
/// The code action kind is support with the following value set.
|
||||
pub code_action_kind: CodeActionKindLiteralSupport,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodeActionKindLiteralSupport {
|
||||
/// The code action kind values the client supports. When this
|
||||
/// property exists the client also guarantees that it will
|
||||
/// handle values outside its set gracefully and falls back
|
||||
/// to a default value when unknown.
|
||||
pub value_set: Vec<String>,
|
||||
}
|
||||
|
||||
/// Params for the CodeActionRequest
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodeActionParams {
|
||||
/// The document in which the command was invoked.
|
||||
pub text_document: TextDocumentIdentifier,
|
||||
|
||||
/// The range for which the command was invoked.
|
||||
pub range: Range,
|
||||
|
||||
/// Context carrying additional information.
|
||||
pub context: CodeActionContext,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub partial_result_params: PartialResultParams,
|
||||
}
|
||||
|
||||
/// response for CodeActionRequest
|
||||
pub type CodeActionResponse = Vec<CodeActionOrCommand>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum CodeActionOrCommand {
|
||||
Command(Command),
|
||||
CodeAction(CodeAction),
|
||||
}
|
||||
|
||||
impl From<Command> for CodeActionOrCommand {
|
||||
fn from(command: Command) -> Self {
|
||||
CodeActionOrCommand::Command(command)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CodeAction> for CodeActionOrCommand {
|
||||
fn from(action: CodeAction) -> Self {
|
||||
CodeActionOrCommand::CodeAction(action)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Hash, PartialOrd, Clone, Deserialize, Serialize)]
|
||||
pub struct CodeActionKind(Cow<'static, str>);
|
||||
|
||||
impl CodeActionKind {
|
||||
/// Empty kind.
|
||||
pub const EMPTY: CodeActionKind = CodeActionKind::new("");
|
||||
|
||||
/// Base kind for quickfix actions: 'quickfix'
|
||||
pub const QUICKFIX: CodeActionKind = CodeActionKind::new("quickfix");
|
||||
|
||||
/// Base kind for refactoring actions: 'refactor'
|
||||
pub const REFACTOR: CodeActionKind = CodeActionKind::new("refactor");
|
||||
|
||||
/// Base kind for refactoring extraction actions: 'refactor.extract'
|
||||
///
|
||||
/// Example extract actions:
|
||||
///
|
||||
/// - Extract method
|
||||
/// - Extract function
|
||||
/// - Extract variable
|
||||
/// - Extract interface from class
|
||||
/// - ...
|
||||
pub const REFACTOR_EXTRACT: CodeActionKind = CodeActionKind::new("refactor.extract");
|
||||
|
||||
/// Base kind for refactoring inline actions: 'refactor.inline'
|
||||
///
|
||||
/// Example inline actions:
|
||||
///
|
||||
/// - Inline function
|
||||
/// - Inline variable
|
||||
/// - Inline constant
|
||||
/// - ...
|
||||
pub const REFACTOR_INLINE: CodeActionKind = CodeActionKind::new("refactor.inline");
|
||||
|
||||
/// Base kind for refactoring rewrite actions: 'refactor.rewrite'
|
||||
///
|
||||
/// Example rewrite actions:
|
||||
///
|
||||
/// - Convert JavaScript function to class
|
||||
/// - Add or remove parameter
|
||||
/// - Encapsulate field
|
||||
/// - Make method static
|
||||
/// - Move method to base class
|
||||
/// - ...
|
||||
pub const REFACTOR_REWRITE: CodeActionKind = CodeActionKind::new("refactor.rewrite");
|
||||
|
||||
/// Base kind for source actions: `source`
|
||||
///
|
||||
/// Source code actions apply to the entire file.
|
||||
pub const SOURCE: CodeActionKind = CodeActionKind::new("source");
|
||||
|
||||
/// Base kind for an organize imports source action: `source.organizeImports`
|
||||
pub const SOURCE_ORGANIZE_IMPORTS: CodeActionKind =
|
||||
CodeActionKind::new("source.organizeImports");
|
||||
|
||||
/// Base kind for a 'fix all' source action: `source.fixAll`.
|
||||
///
|
||||
/// 'Fix all' actions automatically fix errors that have a clear fix that
|
||||
/// do not require user input. They should not suppress errors or perform
|
||||
/// unsafe fixes such as generating new types or classes.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
pub const SOURCE_FIX_ALL: CodeActionKind = CodeActionKind::new("source.fixAll");
|
||||
|
||||
pub const fn new(tag: &'static str) -> Self {
|
||||
CodeActionKind(Cow::Borrowed(tag))
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for CodeActionKind {
|
||||
fn from(from: String) -> Self {
|
||||
CodeActionKind(Cow::from(from))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for CodeActionKind {
|
||||
fn from(from: &'static str) -> Self {
|
||||
CodeActionKind::new(from)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodeAction {
|
||||
/// A short, human-readable, title for this code action.
|
||||
pub title: String,
|
||||
|
||||
/// The kind of the code action.
|
||||
/// Used to filter code actions.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub kind: Option<CodeActionKind>,
|
||||
|
||||
/// The diagnostics that this code action resolves.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub diagnostics: Option<Vec<Diagnostic>>,
|
||||
|
||||
/// The workspace edit this code action performs.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub edit: Option<WorkspaceEdit>,
|
||||
|
||||
/// A command this code action executes. If a code action
|
||||
/// provides an edit and a command, first the edit is
|
||||
/// executed and then the command.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub command: Option<Command>,
|
||||
|
||||
/// Marks this as a preferred action. Preferred actions are used by the `auto fix` command and can be targeted
|
||||
/// by keybindings.
|
||||
/// A quick fix should be marked preferred if it properly addresses the underlying error.
|
||||
/// A refactoring should be marked preferred if it is the most reasonable choice of actions to take.
|
||||
///
|
||||
/// @since 3.15.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub is_preferred: Option<bool>,
|
||||
|
||||
/// Marks that the code action cannot currently be applied.
|
||||
///
|
||||
/// Clients should follow the following guidelines regarding disabled code actions:
|
||||
///
|
||||
/// - Disabled code actions are not shown in automatic
|
||||
/// [lightbulb](https://code.visualstudio.com/docs/editor/editingevolved#_code-action)
|
||||
/// code action menu.
|
||||
///
|
||||
/// - Disabled actions are shown as faded out in the code action menu when the user request
|
||||
/// a more specific type of code action, such as refactorings.
|
||||
///
|
||||
/// - If the user has a keybinding that auto applies a code action and only a disabled code
|
||||
/// actions are returned, the client should show the user an error message with `reason`
|
||||
/// in the editor.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub disabled: Option<CodeActionDisabled>,
|
||||
|
||||
/// A data entry field that is preserved on a code action between
|
||||
/// a `textDocument/codeAction` and a `codeAction/resolve` request.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub data: Option<Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodeActionDisabled {
|
||||
/// Human readable description of why the code action is currently disabled.
|
||||
///
|
||||
/// This is displayed in the code actions UI.
|
||||
pub reason: String,
|
||||
}
|
||||
|
||||
/// The reason why code actions were requested.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Eq, PartialEq, Clone, Copy, Deserialize, Serialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct CodeActionTriggerKind(i32);
|
||||
lsp_enum! {
|
||||
impl CodeActionTriggerKind {
|
||||
/// Code actions were explicitly requested by the user or by an extension.
|
||||
pub const INVOKED: CodeActionTriggerKind = CodeActionTriggerKind(1);
|
||||
|
||||
/// Code actions were requested automatically.
|
||||
///
|
||||
/// This typically happens when current selection in a file changes, but can
|
||||
/// also be triggered when file content changes.
|
||||
pub const AUTOMATIC: CodeActionTriggerKind = CodeActionTriggerKind(2);
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains additional diagnostic information about the context in which
|
||||
/// a code action is run.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodeActionContext {
|
||||
/// An array of diagnostics.
|
||||
pub diagnostics: Vec<Diagnostic>,
|
||||
|
||||
/// Requested kind of actions to return.
|
||||
///
|
||||
/// Actions not of this kind are filtered out by the client before being shown. So servers
|
||||
/// can omit computing them.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub only: Option<Vec<CodeActionKind>>,
|
||||
|
||||
/// The reason why code actions were requested.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub trigger_kind: Option<CodeActionTriggerKind>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodeActionOptions {
|
||||
/// CodeActionKinds that this server may return.
|
||||
///
|
||||
/// The list of kinds may be generic, such as `CodeActionKind.Refactor`, or the server
|
||||
/// may list out every specific kind they provide.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub code_action_kinds: Option<Vec<CodeActionKind>>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_options: WorkDoneProgressOptions,
|
||||
|
||||
/// The server provides support to resolve additional
|
||||
/// information for a code action.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub resolve_provider: Option<bool>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tests::test_serialization;
|
||||
|
||||
#[test]
|
||||
fn test_code_action_response() {
|
||||
test_serialization(
|
||||
&vec![
|
||||
CodeActionOrCommand::Command(Command {
|
||||
title: "title".to_string(),
|
||||
command: "command".to_string(),
|
||||
arguments: None,
|
||||
}),
|
||||
CodeActionOrCommand::CodeAction(CodeAction {
|
||||
title: "title".to_string(),
|
||||
kind: Some(CodeActionKind::QUICKFIX),
|
||||
command: None,
|
||||
diagnostics: None,
|
||||
edit: None,
|
||||
is_preferred: None,
|
||||
..CodeAction::default()
|
||||
}),
|
||||
],
|
||||
r#"[{"title":"title","command":"command"},{"title":"title","kind":"quickfix"}]"#,
|
||||
)
|
||||
}
|
||||
}
|
66
helix-lsp-types/src/code_lens.rs
Normal file
66
helix-lsp-types/src/code_lens.rs
Normal file
@ -0,0 +1,66 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
Command, DynamicRegistrationClientCapabilities, PartialResultParams, Range,
|
||||
TextDocumentIdentifier, WorkDoneProgressParams,
|
||||
};
|
||||
|
||||
pub type CodeLensClientCapabilities = DynamicRegistrationClientCapabilities;
|
||||
|
||||
/// Code Lens options.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize, Copy)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodeLensOptions {
|
||||
/// Code lens has a resolve provider as well.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub resolve_provider: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodeLensParams {
|
||||
/// The document to request code lens for.
|
||||
pub text_document: TextDocumentIdentifier,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub partial_result_params: PartialResultParams,
|
||||
}
|
||||
|
||||
/// A code lens represents a command that should be shown along with
|
||||
/// source text, like the number of references, a way to run tests, etc.
|
||||
///
|
||||
/// A code lens is _unresolved_ when no command is associated to it. For performance
|
||||
/// reasons the creation of a code lens and resolving should be done in two stages.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodeLens {
|
||||
/// The range in which this code lens is valid. Should only span a single line.
|
||||
pub range: Range,
|
||||
|
||||
/// The command this code lens represents.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub command: Option<Command>,
|
||||
|
||||
/// A data entry field that is preserved on a code lens item between
|
||||
/// a code lens and a code lens resolve request.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub data: Option<Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodeLensWorkspaceClientCapabilities {
|
||||
/// Whether the client implementation supports a refresh request sent from the
|
||||
/// server to the client.
|
||||
///
|
||||
/// Note that this event is global and will force the client to refresh all
|
||||
/// code lenses currently shown. It should be used with absolute care and is
|
||||
/// useful for situation where a server for example detect a project wide
|
||||
/// change that requires such a calculation.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub refresh_support: Option<bool>,
|
||||
}
|
122
helix-lsp-types/src/color.rs
Normal file
122
helix-lsp-types/src/color.rs
Normal file
@ -0,0 +1,122 @@
|
||||
use crate::{
|
||||
DocumentSelector, DynamicRegistrationClientCapabilities, PartialResultParams, Range,
|
||||
TextDocumentIdentifier, TextEdit, WorkDoneProgressParams,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub type DocumentColorClientCapabilities = DynamicRegistrationClientCapabilities;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ColorProviderOptions {}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StaticTextDocumentColorProviderOptions {
|
||||
/// A document selector to identify the scope of the registration. If set to null
|
||||
/// the document selector provided on the client side will be used.
|
||||
pub document_selector: Option<DocumentSelector>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum ColorProviderCapability {
|
||||
Simple(bool),
|
||||
ColorProvider(ColorProviderOptions),
|
||||
Options(StaticTextDocumentColorProviderOptions),
|
||||
}
|
||||
|
||||
impl From<ColorProviderOptions> for ColorProviderCapability {
|
||||
fn from(from: ColorProviderOptions) -> Self {
|
||||
Self::ColorProvider(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StaticTextDocumentColorProviderOptions> for ColorProviderCapability {
|
||||
fn from(from: StaticTextDocumentColorProviderOptions) -> Self {
|
||||
Self::Options(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for ColorProviderCapability {
|
||||
fn from(from: bool) -> Self {
|
||||
Self::Simple(from)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DocumentColorParams {
|
||||
/// The text document
|
||||
pub text_document: TextDocumentIdentifier,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub partial_result_params: PartialResultParams,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ColorInformation {
|
||||
/// The range in the document where this color appears.
|
||||
pub range: Range,
|
||||
/// The actual color value for this color range.
|
||||
pub color: Color,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Copy)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Color {
|
||||
/// The red component of this color in the range [0-1].
|
||||
pub red: f32,
|
||||
/// The green component of this color in the range [0-1].
|
||||
pub green: f32,
|
||||
/// The blue component of this color in the range [0-1].
|
||||
pub blue: f32,
|
||||
/// The alpha component of this color in the range [0-1].
|
||||
pub alpha: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ColorPresentationParams {
|
||||
/// The text document.
|
||||
pub text_document: TextDocumentIdentifier,
|
||||
|
||||
/// The color information to request presentations for.
|
||||
pub color: Color,
|
||||
|
||||
/// The range where the color would be inserted. Serves as a context.
|
||||
pub range: Range,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub partial_result_params: PartialResultParams,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Deserialize, Serialize, Default, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ColorPresentation {
|
||||
/// The label of this color presentation. It will be shown on the color
|
||||
/// picker header. By default this is also the text that is inserted when selecting
|
||||
/// this color presentation.
|
||||
pub label: String,
|
||||
|
||||
/// An [edit](#TextEdit) which is applied to a document when selecting
|
||||
/// this presentation for the color. When `falsy` the [label](#ColorPresentation.label)
|
||||
/// is used.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub text_edit: Option<TextEdit>,
|
||||
|
||||
/// An optional array of additional [text edits](#TextEdit) that are applied when
|
||||
/// selecting this color presentation. Edits must not overlap with the main [edit](#ColorPresentation.textEdit) nor with themselves.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub additional_text_edits: Option<Vec<TextEdit>>,
|
||||
}
|
622
helix-lsp-types/src/completion.rs
Normal file
622
helix-lsp-types/src/completion.rs
Normal file
@ -0,0 +1,622 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
Command, Documentation, MarkupKind, PartialResultParams, TagSupport,
|
||||
TextDocumentPositionParams, TextDocumentRegistrationOptions, TextEdit, WorkDoneProgressOptions,
|
||||
WorkDoneProgressParams,
|
||||
};
|
||||
|
||||
use crate::Range;
|
||||
use serde_json::Value;
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Defines how to interpret the insert text in a completion item
|
||||
#[derive(Eq, PartialEq, Clone, Copy, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct InsertTextFormat(i32);
|
||||
lsp_enum! {
|
||||
impl InsertTextFormat {
|
||||
pub const PLAIN_TEXT: InsertTextFormat = InsertTextFormat(1);
|
||||
pub const SNIPPET: InsertTextFormat = InsertTextFormat(2);
|
||||
}
|
||||
}
|
||||
|
||||
/// The kind of a completion entry.
|
||||
#[derive(Eq, PartialEq, Clone, Copy, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct CompletionItemKind(i32);
|
||||
lsp_enum! {
|
||||
impl CompletionItemKind {
|
||||
pub const TEXT: CompletionItemKind = CompletionItemKind(1);
|
||||
pub const METHOD: CompletionItemKind = CompletionItemKind(2);
|
||||
pub const FUNCTION: CompletionItemKind = CompletionItemKind(3);
|
||||
pub const CONSTRUCTOR: CompletionItemKind = CompletionItemKind(4);
|
||||
pub const FIELD: CompletionItemKind = CompletionItemKind(5);
|
||||
pub const VARIABLE: CompletionItemKind = CompletionItemKind(6);
|
||||
pub const CLASS: CompletionItemKind = CompletionItemKind(7);
|
||||
pub const INTERFACE: CompletionItemKind = CompletionItemKind(8);
|
||||
pub const MODULE: CompletionItemKind = CompletionItemKind(9);
|
||||
pub const PROPERTY: CompletionItemKind = CompletionItemKind(10);
|
||||
pub const UNIT: CompletionItemKind = CompletionItemKind(11);
|
||||
pub const VALUE: CompletionItemKind = CompletionItemKind(12);
|
||||
pub const ENUM: CompletionItemKind = CompletionItemKind(13);
|
||||
pub const KEYWORD: CompletionItemKind = CompletionItemKind(14);
|
||||
pub const SNIPPET: CompletionItemKind = CompletionItemKind(15);
|
||||
pub const COLOR: CompletionItemKind = CompletionItemKind(16);
|
||||
pub const FILE: CompletionItemKind = CompletionItemKind(17);
|
||||
pub const REFERENCE: CompletionItemKind = CompletionItemKind(18);
|
||||
pub const FOLDER: CompletionItemKind = CompletionItemKind(19);
|
||||
pub const ENUM_MEMBER: CompletionItemKind = CompletionItemKind(20);
|
||||
pub const CONSTANT: CompletionItemKind = CompletionItemKind(21);
|
||||
pub const STRUCT: CompletionItemKind = CompletionItemKind(22);
|
||||
pub const EVENT: CompletionItemKind = CompletionItemKind(23);
|
||||
pub const OPERATOR: CompletionItemKind = CompletionItemKind(24);
|
||||
pub const TYPE_PARAMETER: CompletionItemKind = CompletionItemKind(25);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CompletionItemCapability {
|
||||
/// Client supports snippets as insert text.
|
||||
///
|
||||
/// A snippet can define tab stops and placeholders with `$1`, `$2`
|
||||
/// and `${3:foo}`. `$0` defines the final tab stop, it defaults to
|
||||
/// the end of the snippet. Placeholders with equal identifiers are linked,
|
||||
/// that is typing in one will update others too.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub snippet_support: Option<bool>,
|
||||
|
||||
/// Client supports commit characters on a completion item.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub commit_characters_support: Option<bool>,
|
||||
|
||||
/// Client supports the follow content formats for the documentation
|
||||
/// property. The order describes the preferred format of the client.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub documentation_format: Option<Vec<MarkupKind>>,
|
||||
|
||||
/// Client supports the deprecated property on a completion item.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub deprecated_support: Option<bool>,
|
||||
|
||||
/// Client supports the preselect property on a completion item.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub preselect_support: Option<bool>,
|
||||
|
||||
/// Client supports the tag property on a completion item. Clients supporting
|
||||
/// tags have to handle unknown tags gracefully. Clients especially need to
|
||||
/// preserve unknown tags when sending a completion item back to the server in
|
||||
/// a resolve call.
|
||||
#[serde(
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none",
|
||||
deserialize_with = "TagSupport::deserialize_compat"
|
||||
)]
|
||||
pub tag_support: Option<TagSupport<CompletionItemTag>>,
|
||||
|
||||
/// Client support insert replace edit to control different behavior if a
|
||||
/// completion item is inserted in the text or should replace text.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub insert_replace_support: Option<bool>,
|
||||
|
||||
/// Indicates which properties a client can resolve lazily on a completion
|
||||
/// item. Before version 3.16.0 only the predefined properties `documentation`
|
||||
/// and `details` could be resolved lazily.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub resolve_support: Option<CompletionItemCapabilityResolveSupport>,
|
||||
|
||||
/// The client supports the `insertTextMode` property on
|
||||
/// a completion item to override the whitespace handling mode
|
||||
/// as defined by the client.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub insert_text_mode_support: Option<InsertTextModeSupport>,
|
||||
|
||||
/// The client has support for completion item label
|
||||
/// details (see also `CompletionItemLabelDetails`).
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub label_details_support: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CompletionItemCapabilityResolveSupport {
|
||||
/// The properties that a client can resolve lazily.
|
||||
pub properties: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InsertTextModeSupport {
|
||||
pub value_set: Vec<InsertTextMode>,
|
||||
}
|
||||
|
||||
/// How whitespace and indentation is handled during completion
|
||||
/// item insertion.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[derive(Eq, PartialEq, Clone, Copy, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct InsertTextMode(i32);
|
||||
lsp_enum! {
|
||||
impl InsertTextMode {
|
||||
/// The insertion or replace strings is taken as it is. If the
|
||||
/// value is multi line the lines below the cursor will be
|
||||
/// inserted using the indentation defined in the string value.
|
||||
/// The client will not apply any kind of adjustments to the
|
||||
/// string.
|
||||
pub const AS_IS: InsertTextMode = InsertTextMode(1);
|
||||
|
||||
/// The editor adjusts leading whitespace of new lines so that
|
||||
/// they match the indentation up to the cursor of the line for
|
||||
/// which the item is accepted.
|
||||
///
|
||||
/// Consider a line like this: `<2tabs><cursor><3tabs>foo`. Accepting a
|
||||
/// multi line completion item is indented using 2 tabs all
|
||||
/// following lines inserted will be indented using 2 tabs as well.
|
||||
pub const ADJUST_INDENTATION: InsertTextMode = InsertTextMode(2);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct CompletionItemTag(i32);
|
||||
lsp_enum! {
|
||||
impl CompletionItemTag {
|
||||
pub const DEPRECATED: CompletionItemTag = CompletionItemTag(1);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CompletionItemKindCapability {
|
||||
/// The completion item kind values the client supports. When this
|
||||
/// property exists the client also guarantees that it will
|
||||
/// handle values outside its set gracefully and falls back
|
||||
/// to a default value when unknown.
|
||||
///
|
||||
/// If this property is not present the client only supports
|
||||
/// the completion items kinds from `Text` to `Reference` as defined in
|
||||
/// the initial version of the protocol.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub value_set: Option<Vec<CompletionItemKind>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CompletionListCapability {
|
||||
/// The client supports the following itemDefaults on
|
||||
/// a completion list.
|
||||
///
|
||||
/// The value lists the supported property names of the
|
||||
/// `CompletionList.itemDefaults` object. If omitted
|
||||
/// no properties are supported.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub item_defaults: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CompletionClientCapabilities {
|
||||
/// Whether completion supports dynamic registration.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub dynamic_registration: Option<bool>,
|
||||
|
||||
/// The client supports the following `CompletionItem` specific
|
||||
/// capabilities.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub completion_item: Option<CompletionItemCapability>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub completion_item_kind: Option<CompletionItemKindCapability>,
|
||||
|
||||
/// The client supports to send additional context information for a
|
||||
/// `textDocument/completion` request.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub context_support: Option<bool>,
|
||||
|
||||
/// The client's default when the completion item doesn't provide a
|
||||
/// `insertTextMode` property.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub insert_text_mode: Option<InsertTextMode>,
|
||||
|
||||
/// The client supports the following `CompletionList` specific
|
||||
/// capabilities.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub completion_list: Option<CompletionListCapability>,
|
||||
}
|
||||
|
||||
/// A special text edit to provide an insert and a replace operation.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InsertReplaceEdit {
|
||||
/// The string to be inserted.
|
||||
pub new_text: String,
|
||||
|
||||
/// The range if the insert is requested
|
||||
pub insert: Range,
|
||||
|
||||
/// The range if the replace is requested.
|
||||
pub replace: Range,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum CompletionTextEdit {
|
||||
Edit(TextEdit),
|
||||
InsertAndReplace(InsertReplaceEdit),
|
||||
}
|
||||
|
||||
impl From<TextEdit> for CompletionTextEdit {
|
||||
fn from(edit: TextEdit) -> Self {
|
||||
CompletionTextEdit::Edit(edit)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InsertReplaceEdit> for CompletionTextEdit {
|
||||
fn from(edit: InsertReplaceEdit) -> Self {
|
||||
CompletionTextEdit::InsertAndReplace(edit)
|
||||
}
|
||||
}
|
||||
|
||||
/// Completion options.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CompletionOptions {
|
||||
/// The server provides support to resolve additional information for a completion item.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub resolve_provider: Option<bool>,
|
||||
|
||||
/// Most tools trigger completion request automatically without explicitly
|
||||
/// requesting it using a keyboard shortcut (e.g. Ctrl+Space). Typically they
|
||||
/// do so when the user starts to type an identifier. For example if the user
|
||||
/// types `c` in a JavaScript file code complete will automatically pop up
|
||||
/// present `console` besides others as a completion item. Characters that
|
||||
/// make up identifiers don't need to be listed here.
|
||||
///
|
||||
/// If code complete should automatically be trigger on characters not being
|
||||
/// valid inside an identifier (for example `.` in JavaScript) list them in
|
||||
/// `triggerCharacters`.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub trigger_characters: Option<Vec<String>>,
|
||||
|
||||
/// The list of all possible characters that commit a completion. This field
|
||||
/// can be used if clients don't support individual commit characters per
|
||||
/// completion item. See client capability
|
||||
/// `completion.completionItem.commitCharactersSupport`.
|
||||
///
|
||||
/// If a server provides both `allCommitCharacters` and commit characters on
|
||||
/// an individual completion item the ones on the completion item win.
|
||||
///
|
||||
/// @since 3.2.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub all_commit_characters: Option<Vec<String>>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_options: WorkDoneProgressOptions,
|
||||
|
||||
/// The server supports the following `CompletionItem` specific
|
||||
/// capabilities.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub completion_item: Option<CompletionOptionsCompletionItem>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CompletionOptionsCompletionItem {
|
||||
/// The server has support for completion item label
|
||||
/// details (see also `CompletionItemLabelDetails`) when receiving
|
||||
/// a completion item in a resolve call.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub label_details_support: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub struct CompletionRegistrationOptions {
|
||||
#[serde(flatten)]
|
||||
pub text_document_registration_options: TextDocumentRegistrationOptions,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub completion_options: CompletionOptions,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum CompletionResponse {
|
||||
Array(Vec<CompletionItem>),
|
||||
List(CompletionList),
|
||||
}
|
||||
|
||||
impl From<Vec<CompletionItem>> for CompletionResponse {
|
||||
fn from(items: Vec<CompletionItem>) -> Self {
|
||||
CompletionResponse::Array(items)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CompletionList> for CompletionResponse {
|
||||
fn from(list: CompletionList) -> Self {
|
||||
CompletionResponse::List(list)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CompletionParams {
|
||||
// This field was "mixed-in" from TextDocumentPositionParams
|
||||
#[serde(flatten)]
|
||||
pub text_document_position: TextDocumentPositionParams,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub partial_result_params: PartialResultParams,
|
||||
|
||||
// CompletionParams properties:
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub context: Option<CompletionContext>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CompletionContext {
|
||||
/// How the completion was triggered.
|
||||
pub trigger_kind: CompletionTriggerKind,
|
||||
|
||||
/// The trigger character (a single character) that has trigger code complete.
|
||||
/// Is undefined if `triggerKind !== CompletionTriggerKind.TriggerCharacter`
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub trigger_character: Option<String>,
|
||||
}
|
||||
|
||||
/// How a completion was triggered.
|
||||
#[derive(Eq, PartialEq, Clone, Copy, Deserialize, Serialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct CompletionTriggerKind(i32);
|
||||
lsp_enum! {
|
||||
impl CompletionTriggerKind {
|
||||
pub const INVOKED: CompletionTriggerKind = CompletionTriggerKind(1);
|
||||
pub const TRIGGER_CHARACTER: CompletionTriggerKind = CompletionTriggerKind(2);
|
||||
pub const TRIGGER_FOR_INCOMPLETE_COMPLETIONS: CompletionTriggerKind = CompletionTriggerKind(3);
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a collection of [completion items](#CompletionItem) to be presented
|
||||
/// in the editor.
|
||||
#[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CompletionList {
|
||||
/// This list it not complete. Further typing should result in recomputing
|
||||
/// this list.
|
||||
pub is_incomplete: bool,
|
||||
|
||||
/// The completion items.
|
||||
pub items: Vec<CompletionItem>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Default, Deserialize, Serialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CompletionItem {
|
||||
/// The label of this completion item. By default
|
||||
/// also the text that is inserted when selecting
|
||||
/// this completion.
|
||||
pub label: String,
|
||||
|
||||
/// Additional details for the label
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub label_details: Option<CompletionItemLabelDetails>,
|
||||
|
||||
/// The kind of this completion item. Based of the kind
|
||||
/// an icon is chosen by the editor.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub kind: Option<CompletionItemKind>,
|
||||
|
||||
/// A human-readable string with additional information
|
||||
/// about this item, like type or symbol information.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub detail: Option<String>,
|
||||
|
||||
/// A human-readable string that represents a doc-comment.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub documentation: Option<Documentation>,
|
||||
|
||||
/// Indicates if this item is deprecated.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub deprecated: Option<bool>,
|
||||
|
||||
/// Select this item when showing.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub preselect: Option<bool>,
|
||||
|
||||
/// A string that should be used when comparing this item
|
||||
/// with other items. When `falsy` the label is used
|
||||
/// as the sort text for this item.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub sort_text: Option<String>,
|
||||
|
||||
/// A string that should be used when filtering a set of
|
||||
/// completion items. When `falsy` the label is used as the
|
||||
/// filter text for this item.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub filter_text: Option<String>,
|
||||
|
||||
/// A string that should be inserted into a document when selecting
|
||||
/// this completion. When `falsy` the label is used as the insert text
|
||||
/// for this item.
|
||||
///
|
||||
/// The `insertText` is subject to interpretation by the client side.
|
||||
/// Some tools might not take the string literally. For example
|
||||
/// VS Code when code complete is requested in this example
|
||||
/// `con<cursor position>` and a completion item with an `insertText` of
|
||||
/// `console` is provided it will only insert `sole`. Therefore it is
|
||||
/// recommended to use `textEdit` instead since it avoids additional client
|
||||
/// side interpretation.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub insert_text: Option<String>,
|
||||
|
||||
/// The format of the insert text. The format applies to both the `insertText` property
|
||||
/// and the `newText` property of a provided `textEdit`. If omitted defaults to `InsertTextFormat.PlainText`.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub insert_text_format: Option<InsertTextFormat>,
|
||||
|
||||
/// How whitespace and indentation is handled during completion
|
||||
/// item insertion. If not provided the client's default value depends on
|
||||
/// the `textDocument.completion.insertTextMode` client capability.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
/// @since 3.17.0 - support for `textDocument.completion.insertTextMode`
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub insert_text_mode: Option<InsertTextMode>,
|
||||
|
||||
/// An edit which is applied to a document when selecting
|
||||
/// this completion. When an edit is provided the value of
|
||||
/// insertText is ignored.
|
||||
///
|
||||
/// Most editors support two different operation when accepting a completion item. One is to insert a
|
||||
|
||||
/// completion text and the other is to replace an existing text with a completion text. Since this can
|
||||
/// usually not predetermined by a server it can report both ranges. Clients need to signal support for
|
||||
/// `InsertReplaceEdits` via the `textDocument.completion.insertReplaceSupport` client capability
|
||||
/// property.
|
||||
///
|
||||
/// *Note 1:* The text edit's range as well as both ranges from a insert replace edit must be a
|
||||
/// [single line] and they must contain the position at which completion has been requested.
|
||||
/// *Note 2:* If an `InsertReplaceEdit` is returned the edit's insert range must be a prefix of
|
||||
/// the edit's replace range, that means it must be contained and starting at the same position.
|
||||
///
|
||||
/// @since 3.16.0 additional type `InsertReplaceEdit`
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub text_edit: Option<CompletionTextEdit>,
|
||||
|
||||
/// An optional array of additional text edits that are applied when
|
||||
/// selecting this completion. Edits must not overlap with the main edit
|
||||
/// nor with themselves.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub additional_text_edits: Option<Vec<TextEdit>>,
|
||||
|
||||
/// An optional command that is executed *after* inserting this completion. *Note* that
|
||||
/// additional modifications to the current document should be described with the
|
||||
/// additionalTextEdits-property.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub command: Option<Command>,
|
||||
|
||||
/// An optional set of characters that when pressed while this completion is
|
||||
/// active will accept it first and then type that character. *Note* that all
|
||||
/// commit characters should have `length=1` and that superfluous characters
|
||||
/// will be ignored.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub commit_characters: Option<Vec<String>>,
|
||||
|
||||
/// An data entry field that is preserved on a completion item between
|
||||
/// a completion and a completion resolve request.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub data: Option<Value>,
|
||||
|
||||
/// Tags for this completion item.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tags: Option<Vec<CompletionItemTag>>,
|
||||
}
|
||||
|
||||
impl CompletionItem {
|
||||
/// Create a CompletionItem with the minimum possible info (label and detail).
|
||||
pub fn new_simple(label: String, detail: String) -> CompletionItem {
|
||||
CompletionItem {
|
||||
label,
|
||||
detail: Some(detail),
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional details for a completion item label.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, PartialEq, Default, Deserialize, Serialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CompletionItemLabelDetails {
|
||||
/// An optional string which is rendered less prominently directly after
|
||||
/// {@link CompletionItemLabel.label label}, without any spacing. Should be
|
||||
/// used for function signatures or type annotations.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub detail: Option<String>,
|
||||
|
||||
/// An optional string which is rendered less prominently after
|
||||
/// {@link CompletionItemLabel.detail}. Should be used for fully qualified
|
||||
/// names or file path.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tests::test_deserialization;
|
||||
|
||||
#[test]
|
||||
fn test_tag_support_deserialization() {
|
||||
let empty = CompletionItemCapability {
|
||||
tag_support: None,
|
||||
..CompletionItemCapability::default()
|
||||
};
|
||||
|
||||
test_deserialization(r#"{}"#, &empty);
|
||||
test_deserialization(r#"{"tagSupport": false}"#, &empty);
|
||||
|
||||
let t = CompletionItemCapability {
|
||||
tag_support: Some(TagSupport { value_set: vec![] }),
|
||||
..CompletionItemCapability::default()
|
||||
};
|
||||
test_deserialization(r#"{"tagSupport": true}"#, &t);
|
||||
|
||||
let t = CompletionItemCapability {
|
||||
tag_support: Some(TagSupport {
|
||||
value_set: vec![CompletionItemTag::DEPRECATED],
|
||||
}),
|
||||
..CompletionItemCapability::default()
|
||||
};
|
||||
test_deserialization(r#"{"tagSupport": {"valueSet": [1]}}"#, &t);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_debug_enum() {
|
||||
assert_eq!(format!("{:?}", CompletionItemKind::TEXT), "Text");
|
||||
assert_eq!(
|
||||
format!("{:?}", CompletionItemKind::TYPE_PARAMETER),
|
||||
"TypeParameter"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_try_from_enum() {
|
||||
use std::convert::TryInto;
|
||||
assert_eq!("Text".try_into(), Ok(CompletionItemKind::TEXT));
|
||||
assert_eq!(
|
||||
"TypeParameter".try_into(),
|
||||
Ok(CompletionItemKind::TYPE_PARAMETER)
|
||||
);
|
||||
}
|
||||
}
|
269
helix-lsp-types/src/document_diagnostic.rs
Normal file
269
helix-lsp-types/src/document_diagnostic.rs
Normal file
@ -0,0 +1,269 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
Diagnostic, PartialResultParams, StaticRegistrationOptions, TextDocumentIdentifier,
|
||||
TextDocumentRegistrationOptions, WorkDoneProgressOptions, WorkDoneProgressParams,
|
||||
};
|
||||
|
||||
/// Client capabilities specific to diagnostic pull requests.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DiagnosticClientCapabilities {
|
||||
/// Whether implementation supports dynamic registration.
|
||||
///
|
||||
/// If this is set to `true` the client supports the new `(TextDocumentRegistrationOptions &
|
||||
/// StaticRegistrationOptions)` return value for the corresponding server capability as well.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub dynamic_registration: Option<bool>,
|
||||
|
||||
/// Whether the clients supports related documents for document diagnostic pulls.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub related_document_support: Option<bool>,
|
||||
}
|
||||
|
||||
/// Diagnostic options.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DiagnosticOptions {
|
||||
/// An optional identifier under which the diagnostics are
|
||||
/// managed by the client.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub identifier: Option<String>,
|
||||
|
||||
/// Whether the language has inter file dependencies, meaning that editing code in one file can
|
||||
/// result in a different diagnostic set in another file. Inter file dependencies are common
|
||||
/// for most programming languages and typically uncommon for linters.
|
||||
pub inter_file_dependencies: bool,
|
||||
|
||||
/// The server provides support for workspace diagnostics as well.
|
||||
pub workspace_diagnostics: bool,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_options: WorkDoneProgressOptions,
|
||||
}
|
||||
|
||||
/// Diagnostic registration options.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DiagnosticRegistrationOptions {
|
||||
#[serde(flatten)]
|
||||
pub text_document_registration_options: TextDocumentRegistrationOptions,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub diagnostic_options: DiagnosticOptions,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub static_registration_options: StaticRegistrationOptions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum DiagnosticServerCapabilities {
|
||||
Options(DiagnosticOptions),
|
||||
RegistrationOptions(DiagnosticRegistrationOptions),
|
||||
}
|
||||
|
||||
/// Parameters of the document diagnostic request.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DocumentDiagnosticParams {
|
||||
/// The text document.
|
||||
pub text_document: TextDocumentIdentifier,
|
||||
|
||||
/// The additional identifier provided during registration.
|
||||
pub identifier: Option<String>,
|
||||
|
||||
/// The result ID of a previous response if provided.
|
||||
pub previous_result_id: Option<String>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub partial_result_params: PartialResultParams,
|
||||
}
|
||||
|
||||
/// A diagnostic report with a full set of problems.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, PartialEq, Default, Deserialize, Serialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FullDocumentDiagnosticReport {
|
||||
/// An optional result ID. If provided it will be sent on the next diagnostic request for the
|
||||
/// same document.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub result_id: Option<String>,
|
||||
|
||||
/// The actual items.
|
||||
pub items: Vec<Diagnostic>,
|
||||
}
|
||||
|
||||
/// A diagnostic report indicating that the last returned report is still accurate.
|
||||
///
|
||||
/// A server can only return `unchanged` if result ids are provided.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UnchangedDocumentDiagnosticReport {
|
||||
/// A result ID which will be sent on the next diagnostic request for the same document.
|
||||
pub result_id: String,
|
||||
}
|
||||
|
||||
/// The document diagnostic report kinds.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
|
||||
#[serde(tag = "kind", rename_all = "lowercase")]
|
||||
pub enum DocumentDiagnosticReportKind {
|
||||
/// A diagnostic report with a full set of problems.
|
||||
Full(FullDocumentDiagnosticReport),
|
||||
/// A report indicating that the last returned report is still accurate.
|
||||
Unchanged(UnchangedDocumentDiagnosticReport),
|
||||
}
|
||||
|
||||
impl From<FullDocumentDiagnosticReport> for DocumentDiagnosticReportKind {
|
||||
fn from(from: FullDocumentDiagnosticReport) -> Self {
|
||||
DocumentDiagnosticReportKind::Full(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UnchangedDocumentDiagnosticReport> for DocumentDiagnosticReportKind {
|
||||
fn from(from: UnchangedDocumentDiagnosticReport) -> Self {
|
||||
DocumentDiagnosticReportKind::Unchanged(from)
|
||||
}
|
||||
}
|
||||
|
||||
/// A full diagnostic report with a set of related documents.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, PartialEq, Default, Deserialize, Serialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RelatedFullDocumentDiagnosticReport {
|
||||
/// Diagnostics of related documents.
|
||||
///
|
||||
/// This information is useful in programming languages where code in a file A can generate
|
||||
/// diagnostics in a file B which A depends on. An example of such a language is C/C++ where
|
||||
/// macro definitions in a file `a.cpp` result in errors in a header file `b.hpp`.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[serde(with = "crate::url_map")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(default)]
|
||||
pub related_documents: Option<HashMap<Url, DocumentDiagnosticReportKind>>,
|
||||
// relatedDocuments?: { [uri: string]: FullDocumentDiagnosticReport | UnchangedDocumentDiagnosticReport; };
|
||||
#[serde(flatten)]
|
||||
pub full_document_diagnostic_report: FullDocumentDiagnosticReport,
|
||||
}
|
||||
|
||||
/// An unchanged diagnostic report with a set of related documents.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RelatedUnchangedDocumentDiagnosticReport {
|
||||
/// Diagnostics of related documents.
|
||||
///
|
||||
/// This information is useful in programming languages where code in a file A can generate
|
||||
/// diagnostics in a file B which A depends on. An example of such a language is C/C++ where
|
||||
/// macro definitions in a file `a.cpp` result in errors in a header file `b.hpp`.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[serde(with = "crate::url_map")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(default)]
|
||||
pub related_documents: Option<HashMap<Url, DocumentDiagnosticReportKind>>,
|
||||
// relatedDocuments?: { [uri: string]: FullDocumentDiagnosticReport | UnchangedDocumentDiagnosticReport; };
|
||||
#[serde(flatten)]
|
||||
pub unchanged_document_diagnostic_report: UnchangedDocumentDiagnosticReport,
|
||||
}
|
||||
|
||||
/// The result of a document diagnostic pull request.
|
||||
///
|
||||
/// A report can either be a full report containing all diagnostics for the requested document or
|
||||
/// an unchanged report indicating that nothing has changed in terms of diagnostics in comparison
|
||||
/// to the last pull request.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
|
||||
#[serde(tag = "kind", rename_all = "lowercase")]
|
||||
pub enum DocumentDiagnosticReport {
|
||||
/// A diagnostic report with a full set of problems.
|
||||
Full(RelatedFullDocumentDiagnosticReport),
|
||||
/// A report indicating that the last returned report is still accurate.
|
||||
Unchanged(RelatedUnchangedDocumentDiagnosticReport),
|
||||
}
|
||||
|
||||
impl From<RelatedFullDocumentDiagnosticReport> for DocumentDiagnosticReport {
|
||||
fn from(from: RelatedFullDocumentDiagnosticReport) -> Self {
|
||||
DocumentDiagnosticReport::Full(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RelatedUnchangedDocumentDiagnosticReport> for DocumentDiagnosticReport {
|
||||
fn from(from: RelatedUnchangedDocumentDiagnosticReport) -> Self {
|
||||
DocumentDiagnosticReport::Unchanged(from)
|
||||
}
|
||||
}
|
||||
|
||||
/// A partial result for a document diagnostic report.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, PartialEq, Default, Deserialize, Serialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DocumentDiagnosticReportPartialResult {
|
||||
#[serde(with = "crate::url_map")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(default)]
|
||||
pub related_documents: Option<HashMap<Url, DocumentDiagnosticReportKind>>,
|
||||
// relatedDocuments?: { [uri: string]: FullDocumentDiagnosticReport | UnchangedDocumentDiagnosticReport; };
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
|
||||
#[serde(untagged)]
|
||||
pub enum DocumentDiagnosticReportResult {
|
||||
Report(DocumentDiagnosticReport),
|
||||
Partial(DocumentDiagnosticReportPartialResult),
|
||||
}
|
||||
|
||||
impl From<DocumentDiagnosticReport> for DocumentDiagnosticReportResult {
|
||||
fn from(from: DocumentDiagnosticReport) -> Self {
|
||||
DocumentDiagnosticReportResult::Report(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DocumentDiagnosticReportPartialResult> for DocumentDiagnosticReportResult {
|
||||
fn from(from: DocumentDiagnosticReportPartialResult) -> Self {
|
||||
DocumentDiagnosticReportResult::Partial(from)
|
||||
}
|
||||
}
|
||||
|
||||
/// Cancellation data returned from a diagnostic request.
|
||||
///
|
||||
/// If no data is provided, it defaults to `{ retrigger_request: true }`.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DiagnosticServerCancellationData {
|
||||
pub retrigger_request: bool,
|
||||
}
|
||||
|
||||
impl Default for DiagnosticServerCancellationData {
|
||||
fn default() -> Self {
|
||||
DiagnosticServerCancellationData {
|
||||
retrigger_request: true,
|
||||
}
|
||||
}
|
||||
}
|
51
helix-lsp-types/src/document_highlight.rs
Normal file
51
helix-lsp-types/src/document_highlight.rs
Normal file
@ -0,0 +1,51 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
DynamicRegistrationClientCapabilities, PartialResultParams, Range, TextDocumentPositionParams,
|
||||
WorkDoneProgressParams,
|
||||
};
|
||||
|
||||
pub type DocumentHighlightClientCapabilities = DynamicRegistrationClientCapabilities;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DocumentHighlightParams {
|
||||
#[serde(flatten)]
|
||||
pub text_document_position_params: TextDocumentPositionParams,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub partial_result_params: PartialResultParams,
|
||||
}
|
||||
|
||||
/// A document highlight is a range inside a text document which deserves
|
||||
/// special attention. Usually a document highlight is visualized by changing
|
||||
/// the background color of its range.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
pub struct DocumentHighlight {
|
||||
/// The range this highlight applies to.
|
||||
pub range: Range,
|
||||
|
||||
/// The highlight kind, default is DocumentHighlightKind.Text.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub kind: Option<DocumentHighlightKind>,
|
||||
}
|
||||
|
||||
/// A document highlight kind.
|
||||
#[derive(Eq, PartialEq, Copy, Clone, Deserialize, Serialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct DocumentHighlightKind(i32);
|
||||
lsp_enum! {
|
||||
impl DocumentHighlightKind {
|
||||
/// A textual occurrence.
|
||||
pub const TEXT: DocumentHighlightKind = DocumentHighlightKind(1);
|
||||
|
||||
/// Read-access of a symbol, like reading a variable.
|
||||
pub const READ: DocumentHighlightKind = DocumentHighlightKind(2);
|
||||
|
||||
/// Write-access of a symbol, like writing to a variable.
|
||||
pub const WRITE: DocumentHighlightKind = DocumentHighlightKind(3);
|
||||
}
|
||||
}
|
67
helix-lsp-types/src/document_link.rs
Normal file
67
helix-lsp-types/src/document_link.rs
Normal file
@ -0,0 +1,67 @@
|
||||
use crate::{
|
||||
PartialResultParams, Range, TextDocumentIdentifier, WorkDoneProgressOptions,
|
||||
WorkDoneProgressParams,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DocumentLinkClientCapabilities {
|
||||
/// Whether document link supports dynamic registration.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub dynamic_registration: Option<bool>,
|
||||
|
||||
/// Whether the client support the `tooltip` property on `DocumentLink`.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tooltip_support: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DocumentLinkOptions {
|
||||
/// Document links have a resolve provider as well.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub resolve_provider: Option<bool>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_options: WorkDoneProgressOptions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DocumentLinkParams {
|
||||
/// The document to provide document links for.
|
||||
pub text_document: TextDocumentIdentifier,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub partial_result_params: PartialResultParams,
|
||||
}
|
||||
|
||||
/// A document link is a range in a text document that links to an internal or external resource, like another
|
||||
/// text document or a web site.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
pub struct DocumentLink {
|
||||
/// The range this link applies to.
|
||||
pub range: Range,
|
||||
/// The uri this link points to.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub target: Option<Url>,
|
||||
|
||||
/// The tooltip text when you hover over this link.
|
||||
///
|
||||
/// If a tooltip is provided, is will be displayed in a string that includes instructions on how to
|
||||
/// trigger the link, such as `{0} (ctrl + click)`. The specific instructions vary depending on OS,
|
||||
/// user settings, and localization.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tooltip: Option<String>,
|
||||
|
||||
/// A data entry field that is preserved on a document link between a DocumentLinkRequest
|
||||
/// and a DocumentLinkResolveRequest.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub data: Option<Value>,
|
||||
}
|
134
helix-lsp-types/src/document_symbols.rs
Normal file
134
helix-lsp-types/src/document_symbols.rs
Normal file
@ -0,0 +1,134 @@
|
||||
use crate::{
|
||||
Location, PartialResultParams, Range, SymbolKind, SymbolKindCapability, TextDocumentIdentifier,
|
||||
WorkDoneProgressParams,
|
||||
};
|
||||
|
||||
use crate::{SymbolTag, TagSupport};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DocumentSymbolClientCapabilities {
|
||||
/// This capability supports dynamic registration.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub dynamic_registration: Option<bool>,
|
||||
|
||||
/// Specific capabilities for the `SymbolKind`.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub symbol_kind: Option<SymbolKindCapability>,
|
||||
|
||||
/// The client support hierarchical document symbols.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub hierarchical_document_symbol_support: Option<bool>,
|
||||
|
||||
/// The client supports tags on `SymbolInformation`. Tags are supported on
|
||||
/// `DocumentSymbol` if `hierarchicalDocumentSymbolSupport` is set to true.
|
||||
/// Clients supporting tags have to handle unknown tags gracefully.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[serde(
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none",
|
||||
deserialize_with = "TagSupport::deserialize_compat"
|
||||
)]
|
||||
pub tag_support: Option<TagSupport<SymbolTag>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum DocumentSymbolResponse {
|
||||
Flat(Vec<SymbolInformation>),
|
||||
Nested(Vec<DocumentSymbol>),
|
||||
}
|
||||
|
||||
impl From<Vec<SymbolInformation>> for DocumentSymbolResponse {
|
||||
fn from(info: Vec<SymbolInformation>) -> Self {
|
||||
DocumentSymbolResponse::Flat(info)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<DocumentSymbol>> for DocumentSymbolResponse {
|
||||
fn from(symbols: Vec<DocumentSymbol>) -> Self {
|
||||
DocumentSymbolResponse::Nested(symbols)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DocumentSymbolParams {
|
||||
/// The text document.
|
||||
pub text_document: TextDocumentIdentifier,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub partial_result_params: PartialResultParams,
|
||||
}
|
||||
|
||||
/// Represents programming constructs like variables, classes, interfaces etc.
|
||||
/// that appear in a document. Document symbols can be hierarchical and they have two ranges:
|
||||
/// one that encloses its definition and one that points to its most interesting range,
|
||||
/// e.g. the range of an identifier.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DocumentSymbol {
|
||||
/// The name of this symbol.
|
||||
pub name: String,
|
||||
/// More detail for this symbol, e.g the signature of a function. If not provided the
|
||||
/// name is used.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub detail: Option<String>,
|
||||
/// The kind of this symbol.
|
||||
pub kind: SymbolKind,
|
||||
/// Tags for this completion item.
|
||||
///
|
||||
/// @since 3.15.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tags: Option<Vec<SymbolTag>>,
|
||||
/// Indicates if this symbol is deprecated.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[deprecated(note = "Use tags instead")]
|
||||
pub deprecated: Option<bool>,
|
||||
/// The range enclosing this symbol not including leading/trailing whitespace but everything else
|
||||
/// like comments. This information is typically used to determine if the the clients cursor is
|
||||
/// inside the symbol to reveal in the symbol in the UI.
|
||||
pub range: Range,
|
||||
/// The range that should be selected and revealed when this symbol is being picked, e.g the name of a function.
|
||||
/// Must be contained by the the `range`.
|
||||
pub selection_range: Range,
|
||||
/// Children of this symbol, e.g. properties of a class.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub children: Option<Vec<DocumentSymbol>>,
|
||||
}
|
||||
|
||||
/// Represents information about programming constructs like variables, classes,
|
||||
/// interfaces etc.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SymbolInformation {
|
||||
/// The name of this symbol.
|
||||
pub name: String,
|
||||
|
||||
/// The kind of this symbol.
|
||||
pub kind: SymbolKind,
|
||||
|
||||
/// Tags for this completion item.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tags: Option<Vec<SymbolTag>>,
|
||||
|
||||
/// Indicates if this symbol is deprecated.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[deprecated(note = "Use tags instead")]
|
||||
pub deprecated: Option<bool>,
|
||||
|
||||
/// The location of this symbol.
|
||||
pub location: Location,
|
||||
|
||||
/// The name of the symbol containing this symbol.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub container_name: Option<String>,
|
||||
}
|
54
helix-lsp-types/src/error_codes.rs
Normal file
54
helix-lsp-types/src/error_codes.rs
Normal file
@ -0,0 +1,54 @@
|
||||
//! In this module we only define constants for lsp specific error codes.
|
||||
//! There are other error codes that are defined in the
|
||||
//! [JSON RPC specification](https://www.jsonrpc.org/specification#error_object).
|
||||
|
||||
/// Defined in the LSP specification but in the range reserved for JSON-RPC error codes,
|
||||
/// namely the -32099 to -32000 "Reserved for implementation-defined server-errors." range.
|
||||
/// The code has, nonetheless, been left in this range for backwards compatibility reasons.
|
||||
pub const SERVER_NOT_INITIALIZED: i64 = -32002;
|
||||
|
||||
/// Defined in the LSP specification but in the range reserved for JSON-RPC error codes,
|
||||
/// namely the -32099 to -32000 "Reserved for implementation-defined server-errors." range.
|
||||
/// The code has, nonetheless, left in this range for backwards compatibility reasons.
|
||||
pub const UNKNOWN_ERROR_CODE: i64 = -32001;
|
||||
|
||||
/// This is the start range of LSP reserved error codes.
|
||||
/// It doesn't denote a real error code.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
pub const LSP_RESERVED_ERROR_RANGE_START: i64 = -32899;
|
||||
|
||||
/// A request failed but it was syntactically correct, e.g the
|
||||
/// method name was known and the parameters were valid. The error
|
||||
/// message should contain human readable information about why
|
||||
/// the request failed.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
pub const REQUEST_FAILED: i64 = -32803;
|
||||
|
||||
/// The server cancelled the request. This error code should
|
||||
/// only be used for requests that explicitly support being
|
||||
/// server cancellable.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
pub const SERVER_CANCELLED: i64 = -32802;
|
||||
|
||||
/// The server detected that the content of a document got
|
||||
/// modified outside normal conditions. A server should
|
||||
/// NOT send this error code if it detects a content change
|
||||
/// in it unprocessed messages. The result even computed
|
||||
/// on an older state might still be useful for the client.
|
||||
///
|
||||
/// If a client decides that a result is not of any use anymore
|
||||
/// the client should cancel the request.
|
||||
pub const CONTENT_MODIFIED: i64 = -32801;
|
||||
|
||||
/// The client has canceled a request and a server as detected
|
||||
/// the cancel.
|
||||
pub const REQUEST_CANCELLED: i64 = -32800;
|
||||
|
||||
/// This is the end range of LSP reserved error codes.
|
||||
/// It doesn't denote a real error code.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
pub const LSP_RESERVED_ERROR_RANGE_END: i64 = -32800;
|
213
helix-lsp-types/src/file_operations.rs
Normal file
213
helix-lsp-types/src/file_operations.rs
Normal file
@ -0,0 +1,213 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WorkspaceFileOperationsClientCapabilities {
|
||||
/// Whether the client supports dynamic registration for file
|
||||
/// requests/notifications.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub dynamic_registration: Option<bool>,
|
||||
|
||||
/// The client has support for sending didCreateFiles notifications.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub did_create: Option<bool>,
|
||||
|
||||
/// The server is interested in receiving willCreateFiles requests.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub will_create: Option<bool>,
|
||||
|
||||
/// The server is interested in receiving didRenameFiles requests.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub did_rename: Option<bool>,
|
||||
|
||||
/// The server is interested in receiving willRenameFiles requests.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub will_rename: Option<bool>,
|
||||
|
||||
/// The server is interested in receiving didDeleteFiles requests.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub did_delete: Option<bool>,
|
||||
|
||||
/// The server is interested in receiving willDeleteFiles requests.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub will_delete: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WorkspaceFileOperationsServerCapabilities {
|
||||
/// The server is interested in receiving didCreateFiles
|
||||
/// notifications.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub did_create: Option<FileOperationRegistrationOptions>,
|
||||
|
||||
/// The server is interested in receiving willCreateFiles requests.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub will_create: Option<FileOperationRegistrationOptions>,
|
||||
|
||||
/// The server is interested in receiving didRenameFiles
|
||||
/// notifications.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub did_rename: Option<FileOperationRegistrationOptions>,
|
||||
|
||||
/// The server is interested in receiving willRenameFiles requests.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub will_rename: Option<FileOperationRegistrationOptions>,
|
||||
|
||||
/// The server is interested in receiving didDeleteFiles file
|
||||
/// notifications.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub did_delete: Option<FileOperationRegistrationOptions>,
|
||||
|
||||
/// The server is interested in receiving willDeleteFiles file
|
||||
/// requests.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub will_delete: Option<FileOperationRegistrationOptions>,
|
||||
}
|
||||
|
||||
/// The options to register for file operations.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FileOperationRegistrationOptions {
|
||||
/// The actual filters.
|
||||
pub filters: Vec<FileOperationFilter>,
|
||||
}
|
||||
|
||||
/// A filter to describe in which file operation requests or notifications
|
||||
/// the server is interested in.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FileOperationFilter {
|
||||
/// A Uri like `file` or `untitled`.
|
||||
pub scheme: Option<String>,
|
||||
|
||||
/// The actual file operation pattern.
|
||||
pub pattern: FileOperationPattern,
|
||||
}
|
||||
|
||||
/// A pattern kind describing if a glob pattern matches a file a folder or
|
||||
/// both.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[derive(Debug, Eq, PartialEq, Deserialize, Serialize, Clone)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum FileOperationPatternKind {
|
||||
/// The pattern matches a file only.
|
||||
File,
|
||||
|
||||
/// The pattern matches a folder only.
|
||||
Folder,
|
||||
}
|
||||
|
||||
/// Matching options for the file operation pattern.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
///
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FileOperationPatternOptions {
|
||||
/// The pattern should be matched ignoring casing.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ignore_case: Option<bool>,
|
||||
}
|
||||
|
||||
/// A pattern to describe in which file operation requests or notifications
|
||||
/// the server is interested in.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FileOperationPattern {
|
||||
/// The glob pattern to match. Glob patterns can have the following syntax:
|
||||
/// - `*` to match one or more characters in a path segment
|
||||
/// - `?` to match on one character in a path segment
|
||||
/// - `**` to match any number of path segments, including none
|
||||
/// - `{}` to group conditions (e.g. `**/*.{ts,js}` matches all TypeScript
|
||||
/// and JavaScript files)
|
||||
/// - `[]` to declare a range of characters to match in a path segment
|
||||
/// (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …)
|
||||
/// - `[!...]` to negate a range of characters to match in a path segment
|
||||
/// (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but
|
||||
/// not `example.0`)
|
||||
pub glob: String,
|
||||
|
||||
/// Whether to match files or folders with this pattern.
|
||||
///
|
||||
/// Matches both if undefined.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub matches: Option<FileOperationPatternKind>,
|
||||
|
||||
/// Additional options used during matching.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub options: Option<FileOperationPatternOptions>,
|
||||
}
|
||||
|
||||
/// The parameters sent in notifications/requests for user-initiated creation
|
||||
/// of files.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CreateFilesParams {
|
||||
/// An array of all files/folders created in this operation.
|
||||
pub files: Vec<FileCreate>,
|
||||
}
|
||||
/// Represents information on a file/folder create.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FileCreate {
|
||||
/// A file:// URI for the location of the file/folder being created.
|
||||
pub uri: String,
|
||||
}
|
||||
|
||||
/// The parameters sent in notifications/requests for user-initiated renames
|
||||
/// of files.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RenameFilesParams {
|
||||
/// An array of all files/folders renamed in this operation. When a folder
|
||||
/// is renamed, only the folder will be included, and not its children.
|
||||
pub files: Vec<FileRename>,
|
||||
}
|
||||
|
||||
/// Represents information on a file/folder rename.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FileRename {
|
||||
/// A file:// URI for the original location of the file/folder being renamed.
|
||||
pub old_uri: String,
|
||||
|
||||
/// A file:// URI for the new location of the file/folder being renamed.
|
||||
pub new_uri: String,
|
||||
}
|
||||
|
||||
/// The parameters sent in notifications/requests for user-initiated deletes
|
||||
/// of files.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DeleteFilesParams {
|
||||
/// An array of all files/folders deleted in this operation.
|
||||
pub files: Vec<FileDelete>,
|
||||
}
|
||||
|
||||
/// Represents information on a file/folder delete.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FileDelete {
|
||||
/// A file:// URI for the location of the file/folder being deleted.
|
||||
pub uri: String,
|
||||
}
|
145
helix-lsp-types/src/folding_range.rs
Normal file
145
helix-lsp-types/src/folding_range.rs
Normal file
@ -0,0 +1,145 @@
|
||||
use crate::{
|
||||
PartialResultParams, StaticTextDocumentColorProviderOptions, TextDocumentIdentifier,
|
||||
WorkDoneProgressParams,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FoldingRangeParams {
|
||||
/// The text document.
|
||||
pub text_document: TextDocumentIdentifier,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub partial_result_params: PartialResultParams,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum FoldingRangeProviderCapability {
|
||||
Simple(bool),
|
||||
FoldingProvider(FoldingProviderOptions),
|
||||
Options(StaticTextDocumentColorProviderOptions),
|
||||
}
|
||||
|
||||
impl From<StaticTextDocumentColorProviderOptions> for FoldingRangeProviderCapability {
|
||||
fn from(from: StaticTextDocumentColorProviderOptions) -> Self {
|
||||
Self::Options(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FoldingProviderOptions> for FoldingRangeProviderCapability {
|
||||
fn from(from: FoldingProviderOptions) -> Self {
|
||||
Self::FoldingProvider(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for FoldingRangeProviderCapability {
|
||||
fn from(from: bool) -> Self {
|
||||
Self::Simple(from)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
pub struct FoldingProviderOptions {}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FoldingRangeKindCapability {
|
||||
/// The folding range kind values the client supports. When this
|
||||
/// property exists the client also guarantees that it will
|
||||
/// handle values outside its set gracefully and falls back
|
||||
/// to a default value when unknown.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub value_set: Option<Vec<FoldingRangeKind>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FoldingRangeCapability {
|
||||
/// If set, the client signals that it supports setting collapsedText on
|
||||
/// folding ranges to display custom labels instead of the default text.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub collapsed_text: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FoldingRangeClientCapabilities {
|
||||
/// Whether implementation supports dynamic registration for folding range providers. If this is set to `true`
|
||||
/// the client supports the new `(FoldingRangeProviderOptions & TextDocumentRegistrationOptions & StaticRegistrationOptions)`
|
||||
/// return value for the corresponding server capability as well.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub dynamic_registration: Option<bool>,
|
||||
|
||||
/// The maximum number of folding ranges that the client prefers to receive per document. The value serves as a
|
||||
/// hint, servers are free to follow the limit.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub range_limit: Option<u32>,
|
||||
|
||||
/// If set, the client signals that it only supports folding complete lines. If set, client will
|
||||
/// ignore specified `startCharacter` and `endCharacter` properties in a FoldingRange.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub line_folding_only: Option<bool>,
|
||||
|
||||
/// Specific options for the folding range kind.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub folding_range_kind: Option<FoldingRangeKindCapability>,
|
||||
|
||||
/// Specific options for the folding range.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub folding_range: Option<FoldingRangeCapability>,
|
||||
}
|
||||
|
||||
/// Enum of known range kinds
|
||||
#[derive(Debug, Eq, PartialEq, Deserialize, Serialize, Clone)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum FoldingRangeKind {
|
||||
/// Folding range for a comment
|
||||
Comment,
|
||||
/// Folding range for a imports or includes
|
||||
Imports,
|
||||
/// Folding range for a region (e.g. `#region`)
|
||||
Region,
|
||||
}
|
||||
|
||||
/// Represents a folding range.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FoldingRange {
|
||||
/// The zero-based line number from where the folded range starts.
|
||||
pub start_line: u32,
|
||||
|
||||
/// The zero-based character offset from where the folded range starts. If not defined, defaults to the length of the start line.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub start_character: Option<u32>,
|
||||
|
||||
/// The zero-based line number where the folded range ends.
|
||||
pub end_line: u32,
|
||||
|
||||
/// The zero-based character offset before the folded range ends. If not defined, defaults to the length of the end line.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub end_character: Option<u32>,
|
||||
|
||||
/// Describes the kind of the folding range such as `comment' or 'region'. The kind
|
||||
/// is used to categorize folding ranges and used by commands like 'Fold all comments'. See
|
||||
/// [FoldingRangeKind](#FoldingRangeKind) for an enumeration of standardized kinds.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub kind: Option<FoldingRangeKind>,
|
||||
|
||||
/// The text that the client should show when the specified range is
|
||||
/// collapsed. If not defined or not supported by the client, a default
|
||||
/// will be chosen by the client.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub collapsed_text: Option<String>,
|
||||
}
|
153
helix-lsp-types/src/formatting.rs
Normal file
153
helix-lsp-types/src/formatting.rs
Normal file
@ -0,0 +1,153 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
DocumentSelector, DynamicRegistrationClientCapabilities, Range, TextDocumentIdentifier,
|
||||
TextDocumentPositionParams, WorkDoneProgressParams,
|
||||
};
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub type DocumentFormattingClientCapabilities = DynamicRegistrationClientCapabilities;
|
||||
pub type DocumentRangeFormattingClientCapabilities = DynamicRegistrationClientCapabilities;
|
||||
pub type DocumentOnTypeFormattingClientCapabilities = DynamicRegistrationClientCapabilities;
|
||||
|
||||
/// Format document on type options
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DocumentOnTypeFormattingOptions {
|
||||
/// A character on which formatting should be triggered, like `}`.
|
||||
pub first_trigger_character: String,
|
||||
|
||||
/// More trigger characters.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub more_trigger_character: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DocumentFormattingParams {
|
||||
/// The document to format.
|
||||
pub text_document: TextDocumentIdentifier,
|
||||
|
||||
/// The format options.
|
||||
pub options: FormattingOptions,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
}
|
||||
|
||||
/// Value-object describing what options formatting should use.
|
||||
#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FormattingOptions {
|
||||
/// Size of a tab in spaces.
|
||||
pub tab_size: u32,
|
||||
|
||||
/// Prefer spaces over tabs.
|
||||
pub insert_spaces: bool,
|
||||
|
||||
/// Signature for further properties.
|
||||
#[serde(flatten)]
|
||||
pub properties: HashMap<String, FormattingProperty>,
|
||||
|
||||
/// Trim trailing whitespace on a line.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub trim_trailing_whitespace: Option<bool>,
|
||||
|
||||
/// Insert a newline character at the end of the file if one does not exist.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub insert_final_newline: Option<bool>,
|
||||
|
||||
/// Trim all newlines after the final newline at the end of the file.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub trim_final_newlines: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum FormattingProperty {
|
||||
Bool(bool),
|
||||
Number(i32),
|
||||
String(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DocumentRangeFormattingParams {
|
||||
/// The document to format.
|
||||
pub text_document: TextDocumentIdentifier,
|
||||
|
||||
/// The range to format
|
||||
pub range: Range,
|
||||
|
||||
/// The format options
|
||||
pub options: FormattingOptions,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DocumentOnTypeFormattingParams {
|
||||
/// Text Document and Position fields.
|
||||
#[serde(flatten)]
|
||||
pub text_document_position: TextDocumentPositionParams,
|
||||
|
||||
/// The character that has been typed.
|
||||
pub ch: String,
|
||||
|
||||
/// The format options.
|
||||
pub options: FormattingOptions,
|
||||
}
|
||||
|
||||
/// Extends TextDocumentRegistrationOptions
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DocumentOnTypeFormattingRegistrationOptions {
|
||||
/// A document selector to identify the scope of the registration. If set to null
|
||||
/// the document selector provided on the client side will be used.
|
||||
pub document_selector: Option<DocumentSelector>,
|
||||
|
||||
/// A character on which formatting should be triggered, like `}`.
|
||||
pub first_trigger_character: String,
|
||||
|
||||
/// More trigger characters.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub more_trigger_character: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tests::test_serialization;
|
||||
|
||||
#[test]
|
||||
fn formatting_options() {
|
||||
test_serialization(
|
||||
&FormattingOptions {
|
||||
tab_size: 123,
|
||||
insert_spaces: true,
|
||||
properties: HashMap::new(),
|
||||
trim_trailing_whitespace: None,
|
||||
insert_final_newline: None,
|
||||
trim_final_newlines: None,
|
||||
},
|
||||
r#"{"tabSize":123,"insertSpaces":true}"#,
|
||||
);
|
||||
|
||||
test_serialization(
|
||||
&FormattingOptions {
|
||||
tab_size: 123,
|
||||
insert_spaces: true,
|
||||
properties: vec![("prop".to_string(), FormattingProperty::Number(1))]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
trim_trailing_whitespace: None,
|
||||
insert_final_newline: None,
|
||||
trim_final_newlines: None,
|
||||
},
|
||||
r#"{"tabSize":123,"insertSpaces":true,"prop":1}"#,
|
||||
);
|
||||
}
|
||||
}
|
86
helix-lsp-types/src/hover.rs
Normal file
86
helix-lsp-types/src/hover.rs
Normal file
@ -0,0 +1,86 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
MarkedString, MarkupContent, MarkupKind, Range, TextDocumentPositionParams,
|
||||
TextDocumentRegistrationOptions, WorkDoneProgressOptions, WorkDoneProgressParams,
|
||||
};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct HoverClientCapabilities {
|
||||
/// Whether completion supports dynamic registration.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub dynamic_registration: Option<bool>,
|
||||
|
||||
/// Client supports the follow content formats for the content
|
||||
/// property. The order describes the preferred format of the client.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub content_format: Option<Vec<MarkupKind>>,
|
||||
}
|
||||
|
||||
/// Hover options.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct HoverOptions {
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_options: WorkDoneProgressOptions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct HoverRegistrationOptions {
|
||||
#[serde(flatten)]
|
||||
pub text_document_registration_options: TextDocumentRegistrationOptions,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub hover_options: HoverOptions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum HoverProviderCapability {
|
||||
Simple(bool),
|
||||
Options(HoverOptions),
|
||||
}
|
||||
|
||||
impl From<HoverOptions> for HoverProviderCapability {
|
||||
fn from(from: HoverOptions) -> Self {
|
||||
Self::Options(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for HoverProviderCapability {
|
||||
fn from(from: bool) -> Self {
|
||||
Self::Simple(from)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct HoverParams {
|
||||
#[serde(flatten)]
|
||||
pub text_document_position_params: TextDocumentPositionParams,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
}
|
||||
|
||||
/// The result of a hover request.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
pub struct Hover {
|
||||
/// The hover's content
|
||||
pub contents: HoverContents,
|
||||
/// An optional range is a range inside a text document
|
||||
/// that is used to visualize a hover, e.g. by changing the background color.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub range: Option<Range>,
|
||||
}
|
||||
|
||||
/// Hover contents could be single entry or multiple entries.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum HoverContents {
|
||||
Scalar(MarkedString),
|
||||
Array(Vec<MarkedString>),
|
||||
Markup(MarkupContent),
|
||||
}
|
281
helix-lsp-types/src/inlay_hint.rs
Normal file
281
helix-lsp-types/src/inlay_hint.rs
Normal file
@ -0,0 +1,281 @@
|
||||
use crate::{
|
||||
Command, LSPAny, Location, MarkupContent, Position, Range, StaticRegistrationOptions,
|
||||
TextDocumentIdentifier, TextDocumentRegistrationOptions, TextEdit, WorkDoneProgressOptions,
|
||||
WorkDoneProgressParams,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(untagged)]
|
||||
pub enum InlayHintServerCapabilities {
|
||||
Options(InlayHintOptions),
|
||||
RegistrationOptions(InlayHintRegistrationOptions),
|
||||
}
|
||||
|
||||
/// Inlay hint client capabilities.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InlayHintClientCapabilities {
|
||||
/// Whether inlay hints support dynamic registration.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub dynamic_registration: Option<bool>,
|
||||
|
||||
/// Indicates which properties a client can resolve lazily on a inlay
|
||||
/// hint.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub resolve_support: Option<InlayHintResolveClientCapabilities>,
|
||||
}
|
||||
|
||||
/// Inlay hint options used during static registration.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InlayHintOptions {
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_options: WorkDoneProgressOptions,
|
||||
|
||||
/// The server provides support to resolve additional
|
||||
/// information for an inlay hint item.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub resolve_provider: Option<bool>,
|
||||
}
|
||||
|
||||
/// Inlay hint options used during static or dynamic registration.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InlayHintRegistrationOptions {
|
||||
#[serde(flatten)]
|
||||
pub inlay_hint_options: InlayHintOptions,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub text_document_registration_options: TextDocumentRegistrationOptions,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub static_registration_options: StaticRegistrationOptions,
|
||||
}
|
||||
|
||||
/// A parameter literal used in inlay hint requests.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InlayHintParams {
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
|
||||
/// The text document.
|
||||
pub text_document: TextDocumentIdentifier,
|
||||
|
||||
/// The visible document range for which inlay hints should be computed.
|
||||
pub range: Range,
|
||||
}
|
||||
|
||||
/// Inlay hint information.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InlayHint {
|
||||
/// The position of this hint.
|
||||
pub position: Position,
|
||||
|
||||
/// The label of this hint. A human readable string or an array of
|
||||
/// InlayHintLabelPart label parts.
|
||||
///
|
||||
/// *Note* that neither the string nor the label part can be empty.
|
||||
pub label: InlayHintLabel,
|
||||
|
||||
/// The kind of this hint. Can be omitted in which case the client
|
||||
/// should fall back to a reasonable default.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub kind: Option<InlayHintKind>,
|
||||
|
||||
/// Optional text edits that are performed when accepting this inlay hint.
|
||||
///
|
||||
/// *Note* that edits are expected to change the document so that the inlay
|
||||
/// hint (or its nearest variant) is now part of the document and the inlay
|
||||
/// hint itself is now obsolete.
|
||||
///
|
||||
/// Depending on the client capability `inlayHint.resolveSupport` clients
|
||||
/// might resolve this property late using the resolve request.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub text_edits: Option<Vec<TextEdit>>,
|
||||
|
||||
/// The tooltip text when you hover over this item.
|
||||
///
|
||||
/// Depending on the client capability `inlayHint.resolveSupport` clients
|
||||
/// might resolve this property late using the resolve request.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tooltip: Option<InlayHintTooltip>,
|
||||
|
||||
/// Render padding before the hint.
|
||||
///
|
||||
/// Note: Padding should use the editor's background color, not the
|
||||
/// background color of the hint itself. That means padding can be used
|
||||
/// to visually align/separate an inlay hint.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub padding_left: Option<bool>,
|
||||
|
||||
/// Render padding after the hint.
|
||||
///
|
||||
/// Note: Padding should use the editor's background color, not the
|
||||
/// background color of the hint itself. That means padding can be used
|
||||
/// to visually align/separate an inlay hint.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub padding_right: Option<bool>,
|
||||
|
||||
/// A data entry field that is preserved on a inlay hint between
|
||||
/// a `textDocument/inlayHint` and a `inlayHint/resolve` request.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub data: Option<LSPAny>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum InlayHintLabel {
|
||||
String(String),
|
||||
LabelParts(Vec<InlayHintLabelPart>),
|
||||
}
|
||||
|
||||
impl From<String> for InlayHintLabel {
|
||||
#[inline]
|
||||
fn from(from: String) -> Self {
|
||||
Self::String(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<InlayHintLabelPart>> for InlayHintLabel {
|
||||
#[inline]
|
||||
fn from(from: Vec<InlayHintLabelPart>) -> Self {
|
||||
Self::LabelParts(from)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum InlayHintTooltip {
|
||||
String(String),
|
||||
MarkupContent(MarkupContent),
|
||||
}
|
||||
|
||||
impl From<String> for InlayHintTooltip {
|
||||
#[inline]
|
||||
fn from(from: String) -> Self {
|
||||
Self::String(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MarkupContent> for InlayHintTooltip {
|
||||
#[inline]
|
||||
fn from(from: MarkupContent) -> Self {
|
||||
Self::MarkupContent(from)
|
||||
}
|
||||
}
|
||||
|
||||
/// An inlay hint label part allows for interactive and composite labels
|
||||
/// of inlay hints.
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InlayHintLabelPart {
|
||||
/// The value of this label part.
|
||||
pub value: String,
|
||||
|
||||
/// The tooltip text when you hover over this label part. Depending on
|
||||
/// the client capability `inlayHint.resolveSupport` clients might resolve
|
||||
/// this property late using the resolve request.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tooltip: Option<InlayHintLabelPartTooltip>,
|
||||
|
||||
/// An optional source code location that represents this
|
||||
/// label part.
|
||||
///
|
||||
/// The editor will use this location for the hover and for code navigation
|
||||
/// features: This part will become a clickable link that resolves to the
|
||||
/// definition of the symbol at the given location (not necessarily the
|
||||
/// location itself), it shows the hover that shows at the given location,
|
||||
/// and it shows a context menu with further code navigation commands.
|
||||
///
|
||||
/// Depending on the client capability `inlayHint.resolveSupport` clients
|
||||
/// might resolve this property late using the resolve request.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub location: Option<Location>,
|
||||
|
||||
/// An optional command for this label part.
|
||||
///
|
||||
/// Depending on the client capability `inlayHint.resolveSupport` clients
|
||||
/// might resolve this property late using the resolve request.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub command: Option<Command>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum InlayHintLabelPartTooltip {
|
||||
String(String),
|
||||
MarkupContent(MarkupContent),
|
||||
}
|
||||
|
||||
impl From<String> for InlayHintLabelPartTooltip {
|
||||
#[inline]
|
||||
fn from(from: String) -> Self {
|
||||
Self::String(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MarkupContent> for InlayHintLabelPartTooltip {
|
||||
#[inline]
|
||||
fn from(from: MarkupContent) -> Self {
|
||||
Self::MarkupContent(from)
|
||||
}
|
||||
}
|
||||
|
||||
/// Inlay hint kinds.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Eq, PartialEq, Copy, Clone, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct InlayHintKind(i32);
|
||||
lsp_enum! {
|
||||
impl InlayHintKind {
|
||||
/// An inlay hint that for a type annotation.
|
||||
pub const TYPE: InlayHintKind = InlayHintKind(1);
|
||||
|
||||
/// An inlay hint that is for a parameter.
|
||||
pub const PARAMETER: InlayHintKind = InlayHintKind(2);
|
||||
}
|
||||
}
|
||||
|
||||
/// Inlay hint client capabilities.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InlayHintResolveClientCapabilities {
|
||||
/// The properties that a client can resolve lazily.
|
||||
pub properties: Vec<String>,
|
||||
}
|
||||
|
||||
/// Client workspace capabilities specific to inlay hints.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InlayHintWorkspaceClientCapabilities {
|
||||
/// Whether the client implementation supports a refresh request sent from
|
||||
/// the server to the client.
|
||||
///
|
||||
/// Note that this event is global and will force the client to refresh all
|
||||
/// inlay hints currently shown. It should be used with absolute care and
|
||||
/// is useful for situation where a server for example detects a project wide
|
||||
/// change that requires such a calculation.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub refresh_support: Option<bool>,
|
||||
}
|
||||
|
||||
// TODO(sno2): add tests once stabilized
|
162
helix-lsp-types/src/inline_completion.rs
Normal file
162
helix-lsp-types/src/inline_completion.rs
Normal file
@ -0,0 +1,162 @@
|
||||
use crate::{
|
||||
Command, InsertTextFormat, Range, StaticRegistrationOptions, TextDocumentPositionParams,
|
||||
TextDocumentRegistrationOptions, WorkDoneProgressOptions, WorkDoneProgressParams,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Client capabilities specific to inline completions.
|
||||
///
|
||||
/// @since 3.18.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InlineCompletionClientCapabilities {
|
||||
/// Whether implementation supports dynamic registration for inline completion providers.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub dynamic_registration: Option<bool>,
|
||||
}
|
||||
|
||||
/// Inline completion options used during static registration.
|
||||
///
|
||||
/// @since 3.18.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
pub struct InlineCompletionOptions {
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_options: WorkDoneProgressOptions,
|
||||
}
|
||||
|
||||
/// Inline completion options used during static or dynamic registration.
|
||||
///
|
||||
// @since 3.18.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
pub struct InlineCompletionRegistrationOptions {
|
||||
#[serde(flatten)]
|
||||
pub inline_completion_options: InlineCompletionOptions,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub text_document_registration_options: TextDocumentRegistrationOptions,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub static_registration_options: StaticRegistrationOptions,
|
||||
}
|
||||
|
||||
/// A parameter literal used in inline completion requests.
|
||||
///
|
||||
/// @since 3.18.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InlineCompletionParams {
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub text_document_position: TextDocumentPositionParams,
|
||||
|
||||
/// Additional information about the context in which inline completions were requested.
|
||||
pub context: InlineCompletionContext,
|
||||
}
|
||||
|
||||
/// Describes how an [`InlineCompletionItemProvider`] was triggered.
|
||||
///
|
||||
/// @since 3.18.0
|
||||
#[derive(Eq, PartialEq, Clone, Copy, Deserialize, Serialize)]
|
||||
pub struct InlineCompletionTriggerKind(i32);
|
||||
lsp_enum! {
|
||||
impl InlineCompletionTriggerKind {
|
||||
/// Completion was triggered explicitly by a user gesture.
|
||||
/// Return multiple completion items to enable cycling through them.
|
||||
pub const Invoked: InlineCompletionTriggerKind = InlineCompletionTriggerKind(1);
|
||||
|
||||
/// Completion was triggered automatically while editing.
|
||||
/// It is sufficient to return a single completion item in this case.
|
||||
pub const Automatic: InlineCompletionTriggerKind = InlineCompletionTriggerKind(2);
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes the currently selected completion item.
|
||||
///
|
||||
/// @since 3.18.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
pub struct SelectedCompletionInfo {
|
||||
/// The range that will be replaced if this completion item is accepted.
|
||||
pub range: Range,
|
||||
/// The text the range will be replaced with if this completion is
|
||||
/// accepted.
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
/// Provides information about the context in which an inline completion was
|
||||
/// requested.
|
||||
///
|
||||
/// @since 3.18.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InlineCompletionContext {
|
||||
/// Describes how the inline completion was triggered.
|
||||
pub trigger_kind: InlineCompletionTriggerKind,
|
||||
/// Provides information about the currently selected item in the
|
||||
/// autocomplete widget if it is visible.
|
||||
///
|
||||
/// If set, provided inline completions must extend the text of the
|
||||
/// selected item and use the same range, otherwise they are not shown as
|
||||
/// preview.
|
||||
/// As an example, if the document text is `console.` and the selected item
|
||||
/// is `.log` replacing the `.` in the document, the inline completion must
|
||||
/// also replace `.` and start with `.log`, for example `.log()`.
|
||||
///
|
||||
/// Inline completion providers are requested again whenever the selected
|
||||
/// item changes.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub selected_completion_info: Option<SelectedCompletionInfo>,
|
||||
}
|
||||
|
||||
/// InlineCompletion response can be multiple completion items, or a list of completion items
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum InlineCompletionResponse {
|
||||
Array(Vec<InlineCompletionItem>),
|
||||
List(InlineCompletionList),
|
||||
}
|
||||
|
||||
/// Represents a collection of [`InlineCompletionItem`] to be presented in the editor.
|
||||
///
|
||||
/// @since 3.18.0
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub struct InlineCompletionList {
|
||||
/// The inline completion items
|
||||
pub items: Vec<InlineCompletionItem>,
|
||||
}
|
||||
|
||||
/// An inline completion item represents a text snippet that is proposed inline
|
||||
/// to complete text that is being typed.
|
||||
///
|
||||
/// @since 3.18.0
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InlineCompletionItem {
|
||||
/// The text to replace the range with. Must be set.
|
||||
/// Is used both for the preview and the accept operation.
|
||||
pub insert_text: String,
|
||||
/// A text that is used to decide if this inline completion should be
|
||||
/// shown. When `falsy` the [`InlineCompletionItem::insertText`] is
|
||||
/// used.
|
||||
///
|
||||
/// An inline completion is shown if the text to replace is a prefix of the
|
||||
/// filter text.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub filter_text: Option<String>,
|
||||
/// The range to replace.
|
||||
/// Must begin and end on the same line.
|
||||
///
|
||||
/// Prefer replacements over insertions to provide a better experience when
|
||||
/// the user deletes typed text.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub range: Option<Range>,
|
||||
/// An optional command that is executed *after* inserting this
|
||||
/// completion.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub command: Option<Command>,
|
||||
/// The format of the insert text. The format applies to the `insertText`.
|
||||
/// If omitted defaults to `InsertTextFormat.PlainText`.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub insert_text_format: Option<InsertTextFormat>,
|
||||
}
|
218
helix-lsp-types/src/inline_value.rs
Normal file
218
helix-lsp-types/src/inline_value.rs
Normal file
@ -0,0 +1,218 @@
|
||||
use crate::{
|
||||
DynamicRegistrationClientCapabilities, Range, StaticRegistrationOptions,
|
||||
TextDocumentIdentifier, TextDocumentRegistrationOptions, WorkDoneProgressOptions,
|
||||
WorkDoneProgressParams,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub type InlineValueClientCapabilities = DynamicRegistrationClientCapabilities;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum InlineValueServerCapabilities {
|
||||
Options(InlineValueOptions),
|
||||
RegistrationOptions(InlineValueRegistrationOptions),
|
||||
}
|
||||
|
||||
/// Inline value options used during static registration.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
pub struct InlineValueOptions {
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_options: WorkDoneProgressOptions,
|
||||
}
|
||||
|
||||
/// Inline value options used during static or dynamic registration.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
pub struct InlineValueRegistrationOptions {
|
||||
#[serde(flatten)]
|
||||
pub inline_value_options: InlineValueOptions,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub text_document_registration_options: TextDocumentRegistrationOptions,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub static_registration_options: StaticRegistrationOptions,
|
||||
}
|
||||
|
||||
/// A parameter literal used in inline value requests.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InlineValueParams {
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
|
||||
/// The text document.
|
||||
pub text_document: TextDocumentIdentifier,
|
||||
|
||||
/// The document range for which inline values should be computed.
|
||||
pub range: Range,
|
||||
|
||||
/// Additional information about the context in which inline values were
|
||||
/// requested.
|
||||
pub context: InlineValueContext,
|
||||
}
|
||||
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InlineValueContext {
|
||||
/// The stack frame (as a DAP Id) where the execution has stopped.
|
||||
pub frame_id: i32,
|
||||
|
||||
/// The document range where execution has stopped.
|
||||
/// Typically the end position of the range denotes the line where the
|
||||
/// inline values are shown.
|
||||
pub stopped_location: Range,
|
||||
}
|
||||
|
||||
/// Provide inline value as text.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
pub struct InlineValueText {
|
||||
/// The document range for which the inline value applies.
|
||||
pub range: Range,
|
||||
|
||||
/// The text of the inline value.
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
/// Provide inline value through a variable lookup.
|
||||
///
|
||||
/// If only a range is specified, the variable name will be extracted from
|
||||
/// the underlying document.
|
||||
///
|
||||
/// An optional variable name can be used to override the extracted name.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InlineValueVariableLookup {
|
||||
/// The document range for which the inline value applies.
|
||||
/// The range is used to extract the variable name from the underlying
|
||||
/// document.
|
||||
pub range: Range,
|
||||
|
||||
/// If specified the name of the variable to look up.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub variable_name: Option<String>,
|
||||
|
||||
/// How to perform the lookup.
|
||||
pub case_sensitive_lookup: bool,
|
||||
}
|
||||
|
||||
/// Provide an inline value through an expression evaluation.
|
||||
///
|
||||
/// If only a range is specified, the expression will be extracted from the
|
||||
/// underlying document.
|
||||
///
|
||||
/// An optional expression can be used to override the extracted expression.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InlineValueEvaluatableExpression {
|
||||
/// The document range for which the inline value applies.
|
||||
/// The range is used to extract the evaluatable expression from the
|
||||
/// underlying document.
|
||||
pub range: Range,
|
||||
|
||||
/// If specified the expression overrides the extracted expression.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub expression: Option<String>,
|
||||
}
|
||||
|
||||
/// Inline value information can be provided by different means:
|
||||
/// - directly as a text value (class InlineValueText).
|
||||
/// - as a name to use for a variable lookup (class InlineValueVariableLookup)
|
||||
/// - as an evaluatable expression (class InlineValueEvaluatableExpression)
|
||||
///
|
||||
/// The InlineValue types combines all inline value types into one type.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum InlineValue {
|
||||
Text(InlineValueText),
|
||||
VariableLookup(InlineValueVariableLookup),
|
||||
EvaluatableExpression(InlineValueEvaluatableExpression),
|
||||
}
|
||||
|
||||
impl From<InlineValueText> for InlineValue {
|
||||
#[inline]
|
||||
fn from(from: InlineValueText) -> Self {
|
||||
Self::Text(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InlineValueVariableLookup> for InlineValue {
|
||||
#[inline]
|
||||
fn from(from: InlineValueVariableLookup) -> Self {
|
||||
Self::VariableLookup(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InlineValueEvaluatableExpression> for InlineValue {
|
||||
#[inline]
|
||||
fn from(from: InlineValueEvaluatableExpression) -> Self {
|
||||
Self::EvaluatableExpression(from)
|
||||
}
|
||||
}
|
||||
|
||||
/// Client workspace capabilities specific to inline values.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InlineValueWorkspaceClientCapabilities {
|
||||
/// Whether the client implementation supports a refresh request sent from
|
||||
/// the server to the client.
|
||||
///
|
||||
/// Note that this event is global and will force the client to refresh all
|
||||
/// inline values currently shown. It should be used with absolute care and
|
||||
/// is useful for situation where a server for example detect a project wide
|
||||
/// change that requires such a calculation.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub refresh_support: Option<bool>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tests::test_serialization;
|
||||
use crate::Position;
|
||||
|
||||
#[test]
|
||||
fn inline_values() {
|
||||
test_serialization(
|
||||
&InlineValueText {
|
||||
range: Range::new(Position::new(0, 0), Position::new(0, 4)),
|
||||
text: "one".to_owned(),
|
||||
},
|
||||
r#"{"range":{"start":{"line":0,"character":0},"end":{"line":0,"character":4}},"text":"one"}"#,
|
||||
);
|
||||
|
||||
test_serialization(
|
||||
&InlineValue::VariableLookup(InlineValueVariableLookup {
|
||||
range: Range::new(Position::new(1, 0), Position::new(1, 4)),
|
||||
variable_name: None,
|
||||
case_sensitive_lookup: false,
|
||||
}),
|
||||
r#"{"range":{"start":{"line":1,"character":0},"end":{"line":1,"character":4}},"caseSensitiveLookup":false}"#,
|
||||
);
|
||||
|
||||
test_serialization(
|
||||
&InlineValue::EvaluatableExpression(InlineValueEvaluatableExpression {
|
||||
range: Range::new(Position::new(2, 0), Position::new(2, 4)),
|
||||
expression: None,
|
||||
}),
|
||||
r#"{"range":{"start":{"line":2,"character":0},"end":{"line":2,"character":4}}}"#,
|
||||
);
|
||||
}
|
||||
}
|
2883
helix-lsp-types/src/lib.rs
Normal file
2883
helix-lsp-types/src/lib.rs
Normal file
File diff suppressed because it is too large
Load Diff
61
helix-lsp-types/src/linked_editing.rs
Normal file
61
helix-lsp-types/src/linked_editing.rs
Normal file
@ -0,0 +1,61 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
DynamicRegistrationClientCapabilities, Range, StaticRegistrationOptions,
|
||||
TextDocumentPositionParams, TextDocumentRegistrationOptions, WorkDoneProgressOptions,
|
||||
WorkDoneProgressParams,
|
||||
};
|
||||
|
||||
pub type LinkedEditingRangeClientCapabilities = DynamicRegistrationClientCapabilities;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LinkedEditingRangeOptions {
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_options: WorkDoneProgressOptions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LinkedEditingRangeRegistrationOptions {
|
||||
#[serde(flatten)]
|
||||
pub text_document_registration_options: TextDocumentRegistrationOptions,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub linked_editing_range_options: LinkedEditingRangeOptions,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub static_registration_options: StaticRegistrationOptions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum LinkedEditingRangeServerCapabilities {
|
||||
Simple(bool),
|
||||
Options(LinkedEditingRangeOptions),
|
||||
RegistrationOptions(LinkedEditingRangeRegistrationOptions),
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LinkedEditingRangeParams {
|
||||
#[serde(flatten)]
|
||||
pub text_document_position_params: TextDocumentPositionParams,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LinkedEditingRanges {
|
||||
/// A list of ranges that can be renamed together. The ranges must have
|
||||
/// identical length and contain identical text content. The ranges cannot overlap.
|
||||
pub ranges: Vec<Range>,
|
||||
|
||||
/// An optional word pattern (regular expression) that describes valid contents for
|
||||
/// the given ranges. If no pattern is provided, the client configuration's word
|
||||
/// pattern will be used.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub word_pattern: Option<String>,
|
||||
}
|
336
helix-lsp-types/src/lsif.rs
Normal file
336
helix-lsp-types/src/lsif.rs
Normal file
@ -0,0 +1,336 @@
|
||||
//! Types of Language Server Index Format (LSIF). LSIF is a standard format
|
||||
//! for language servers or other programming tools to dump their knowledge
|
||||
//! about a workspace.
|
||||
//!
|
||||
//! Based on <https://microsoft.github.io/language-server-protocol/specifications/lsif/0.6.0/specification/>
|
||||
|
||||
use crate::{Range, Url};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub type Id = crate::NumberOrString;
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum LocationOrRangeId {
|
||||
Location(crate::Location),
|
||||
RangeId(Id),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Entry {
|
||||
pub id: Id,
|
||||
#[serde(flatten)]
|
||||
pub data: Element,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(tag = "type")]
|
||||
pub enum Element {
|
||||
Vertex(Vertex),
|
||||
Edge(Edge),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ToolInfo {
|
||||
pub name: String,
|
||||
#[serde(default = "Default::default")]
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub args: Vec<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub version: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Copy)]
|
||||
pub enum Encoding {
|
||||
/// Currently only 'utf-16' is supported due to the limitations in LSP.
|
||||
#[serde(rename = "utf-16")]
|
||||
Utf16,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RangeBasedDocumentSymbol {
|
||||
pub id: Id,
|
||||
#[serde(default = "Default::default")]
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub children: Vec<RangeBasedDocumentSymbol>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(untagged)]
|
||||
pub enum DocumentSymbolOrRangeBasedVec {
|
||||
DocumentSymbol(Vec<crate::DocumentSymbol>),
|
||||
RangeBased(Vec<RangeBasedDocumentSymbol>),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DefinitionTag {
|
||||
/// The text covered by the range
|
||||
text: String,
|
||||
/// The symbol kind.
|
||||
kind: crate::SymbolKind,
|
||||
/// Indicates if this symbol is deprecated.
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "std::ops::Not::not")]
|
||||
deprecated: bool,
|
||||
/// The full range of the definition not including leading/trailing whitespace but everything else, e.g comments and code.
|
||||
/// The range must be included in fullRange.
|
||||
full_range: Range,
|
||||
/// Optional detail information for the definition.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
detail: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DeclarationTag {
|
||||
/// The text covered by the range
|
||||
text: String,
|
||||
/// The symbol kind.
|
||||
kind: crate::SymbolKind,
|
||||
/// Indicates if this symbol is deprecated.
|
||||
#[serde(default)]
|
||||
deprecated: bool,
|
||||
/// The full range of the definition not including leading/trailing whitespace but everything else, e.g comments and code.
|
||||
/// The range must be included in fullRange.
|
||||
full_range: Range,
|
||||
/// Optional detail information for the definition.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
detail: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ReferenceTag {
|
||||
text: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UnknownTag {
|
||||
text: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(tag = "type")]
|
||||
pub enum RangeTag {
|
||||
Definition(DefinitionTag),
|
||||
Declaration(DeclarationTag),
|
||||
Reference(ReferenceTag),
|
||||
Unknown(UnknownTag),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(tag = "label")]
|
||||
pub enum Vertex {
|
||||
MetaData(MetaData),
|
||||
/// <https://github.com/Microsoft/language-server-protocol/blob/master/indexFormat/specification.md#the-project-vertex>
|
||||
Project(Project),
|
||||
Document(Document),
|
||||
/// <https://github.com/Microsoft/language-server-protocol/blob/master/indexFormat/specification.md#ranges>
|
||||
Range {
|
||||
#[serde(flatten)]
|
||||
range: Range,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
tag: Option<RangeTag>,
|
||||
},
|
||||
/// <https://github.com/Microsoft/language-server-protocol/blob/master/indexFormat/specification.md#result-set>
|
||||
ResultSet(ResultSet),
|
||||
Moniker(crate::Moniker),
|
||||
PackageInformation(PackageInformation),
|
||||
|
||||
#[serde(rename = "$event")]
|
||||
Event(Event),
|
||||
|
||||
DefinitionResult,
|
||||
DeclarationResult,
|
||||
TypeDefinitionResult,
|
||||
ReferenceResult,
|
||||
ImplementationResult,
|
||||
FoldingRangeResult {
|
||||
result: Vec<crate::FoldingRange>,
|
||||
},
|
||||
HoverResult {
|
||||
result: crate::Hover,
|
||||
},
|
||||
DocumentSymbolResult {
|
||||
result: DocumentSymbolOrRangeBasedVec,
|
||||
},
|
||||
DocumentLinkResult {
|
||||
result: Vec<crate::DocumentLink>,
|
||||
},
|
||||
DiagnosticResult {
|
||||
result: Vec<crate::Diagnostic>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum EventKind {
|
||||
Begin,
|
||||
End,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum EventScope {
|
||||
Document,
|
||||
Project,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Event {
|
||||
pub kind: EventKind,
|
||||
pub scope: EventScope,
|
||||
pub data: Id,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(tag = "label")]
|
||||
pub enum Edge {
|
||||
Contains(EdgeDataMultiIn),
|
||||
Moniker(EdgeData),
|
||||
NextMoniker(EdgeData),
|
||||
Next(EdgeData),
|
||||
PackageInformation(EdgeData),
|
||||
Item(Item),
|
||||
|
||||
// Methods
|
||||
#[serde(rename = "textDocument/definition")]
|
||||
Definition(EdgeData),
|
||||
#[serde(rename = "textDocument/declaration")]
|
||||
Declaration(EdgeData),
|
||||
#[serde(rename = "textDocument/hover")]
|
||||
Hover(EdgeData),
|
||||
#[serde(rename = "textDocument/references")]
|
||||
References(EdgeData),
|
||||
#[serde(rename = "textDocument/implementation")]
|
||||
Implementation(EdgeData),
|
||||
#[serde(rename = "textDocument/typeDefinition")]
|
||||
TypeDefinition(EdgeData),
|
||||
#[serde(rename = "textDocument/foldingRange")]
|
||||
FoldingRange(EdgeData),
|
||||
#[serde(rename = "textDocument/documentLink")]
|
||||
DocumentLink(EdgeData),
|
||||
#[serde(rename = "textDocument/documentSymbol")]
|
||||
DocumentSymbol(EdgeData),
|
||||
#[serde(rename = "textDocument/diagnostic")]
|
||||
Diagnostic(EdgeData),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EdgeData {
|
||||
pub in_v: Id,
|
||||
pub out_v: Id,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EdgeDataMultiIn {
|
||||
pub in_vs: Vec<Id>,
|
||||
pub out_v: Id,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum DefinitionResultType {
|
||||
Scalar(LocationOrRangeId),
|
||||
Array(LocationOrRangeId),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum ItemKind {
|
||||
Declarations,
|
||||
Definitions,
|
||||
References,
|
||||
ReferenceResults,
|
||||
ImplementationResults,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Item {
|
||||
pub document: Id,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub property: Option<ItemKind>,
|
||||
#[serde(flatten)]
|
||||
pub edge_data: EdgeDataMultiIn,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Document {
|
||||
pub uri: Url,
|
||||
pub language_id: String,
|
||||
}
|
||||
|
||||
/// <https://github.com/Microsoft/language-server-protocol/blob/master/indexFormat/specification.md#result-set>
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ResultSet {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub key: Option<String>,
|
||||
}
|
||||
|
||||
/// <https://github.com/Microsoft/language-server-protocol/blob/master/indexFormat/specification.md#the-project-vertex>
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Project {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub resource: Option<Url>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub content: Option<String>,
|
||||
pub kind: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MetaData {
|
||||
/// The version of the LSIF format using semver notation. See <https://semver.org/>. Please note
|
||||
/// the version numbers starting with 0 don't adhere to semver and adopters have to assume
|
||||
/// that each new version is breaking.
|
||||
pub version: String,
|
||||
|
||||
/// The project root (in form of an URI) used to compute this dump.
|
||||
pub project_root: Url,
|
||||
|
||||
/// The string encoding used to compute line and character values in
|
||||
/// positions and ranges.
|
||||
pub position_encoding: Encoding,
|
||||
|
||||
/// Information about the tool that created the dump
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tool_info: Option<ToolInfo>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Repository {
|
||||
pub r#type: String,
|
||||
pub url: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub commit_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PackageInformation {
|
||||
pub name: String,
|
||||
pub manager: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub uri: Option<Url>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub content: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub repository: Option<Repository>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub version: Option<String>,
|
||||
}
|
92
helix-lsp-types/src/moniker.rs
Normal file
92
helix-lsp-types/src/moniker.rs
Normal file
@ -0,0 +1,92 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
DynamicRegistrationClientCapabilities, PartialResultParams, TextDocumentPositionParams,
|
||||
TextDocumentRegistrationOptions, WorkDoneProgressOptions, WorkDoneProgressParams,
|
||||
};
|
||||
|
||||
pub type MonikerClientCapabilities = DynamicRegistrationClientCapabilities;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum MonikerServerCapabilities {
|
||||
Options(MonikerOptions),
|
||||
RegistrationOptions(MonikerRegistrationOptions),
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
pub struct MonikerOptions {
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_options: WorkDoneProgressOptions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MonikerRegistrationOptions {
|
||||
#[serde(flatten)]
|
||||
pub text_document_registration_options: TextDocumentRegistrationOptions,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub moniker_options: MonikerOptions,
|
||||
}
|
||||
|
||||
/// Moniker uniqueness level to define scope of the moniker.
|
||||
#[derive(Debug, Eq, PartialEq, Deserialize, Serialize, Copy, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum UniquenessLevel {
|
||||
/// The moniker is only unique inside a document
|
||||
Document,
|
||||
/// The moniker is unique inside a project for which a dump got created
|
||||
Project,
|
||||
/// The moniker is unique inside the group to which a project belongs
|
||||
Group,
|
||||
/// The moniker is unique inside the moniker scheme.
|
||||
Scheme,
|
||||
/// The moniker is globally unique
|
||||
Global,
|
||||
}
|
||||
|
||||
/// The moniker kind.
|
||||
#[derive(Debug, Eq, PartialEq, Deserialize, Serialize, Copy, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum MonikerKind {
|
||||
/// The moniker represent a symbol that is imported into a project
|
||||
Import,
|
||||
/// The moniker represent a symbol that is exported into a project
|
||||
Export,
|
||||
/// The moniker represents a symbol that is local to a project (e.g. a local
|
||||
/// variable of a function, a class not visible outside the project, ...)
|
||||
Local,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MonikerParams {
|
||||
#[serde(flatten)]
|
||||
pub text_document_position_params: TextDocumentPositionParams,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub partial_result_params: PartialResultParams,
|
||||
}
|
||||
|
||||
/// Moniker definition to match LSIF 0.5 moniker definition.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Moniker {
|
||||
/// The scheme of the moniker. For example tsc or .Net
|
||||
pub scheme: String,
|
||||
|
||||
/// The identifier of the moniker. The value is opaque in LSIF however
|
||||
/// schema owners are allowed to define the structure if they want.
|
||||
pub identifier: String,
|
||||
|
||||
/// The scope in which the moniker is unique
|
||||
pub unique: UniquenessLevel,
|
||||
|
||||
/// The moniker kind if known.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub kind: Option<MonikerKind>,
|
||||
}
|
361
helix-lsp-types/src/notification.rs
Normal file
361
helix-lsp-types/src/notification.rs
Normal file
@ -0,0 +1,361 @@
|
||||
use super::*;
|
||||
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
|
||||
pub trait Notification {
|
||||
type Params: DeserializeOwned + Serialize + Send + Sync + 'static;
|
||||
const METHOD: &'static str;
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! lsp_notification {
|
||||
("$/cancelRequest") => {
|
||||
$crate::notification::Cancel
|
||||
};
|
||||
("$/setTrace") => {
|
||||
$crate::notification::SetTrace
|
||||
};
|
||||
("$/logTrace") => {
|
||||
$crate::notification::LogTrace
|
||||
};
|
||||
("initialized") => {
|
||||
$crate::notification::Initialized
|
||||
};
|
||||
("exit") => {
|
||||
$crate::notification::Exit
|
||||
};
|
||||
|
||||
("window/showMessage") => {
|
||||
$crate::notification::ShowMessage
|
||||
};
|
||||
("window/logMessage") => {
|
||||
$crate::notification::LogMessage
|
||||
};
|
||||
("window/workDoneProgress/cancel") => {
|
||||
$crate::notification::WorkDoneProgressCancel
|
||||
};
|
||||
|
||||
("telemetry/event") => {
|
||||
$crate::notification::TelemetryEvent
|
||||
};
|
||||
|
||||
("textDocument/didOpen") => {
|
||||
$crate::notification::DidOpenTextDocument
|
||||
};
|
||||
("textDocument/didChange") => {
|
||||
$crate::notification::DidChangeTextDocument
|
||||
};
|
||||
("textDocument/willSave") => {
|
||||
$crate::notification::WillSaveTextDocument
|
||||
};
|
||||
("textDocument/didSave") => {
|
||||
$crate::notification::DidSaveTextDocument
|
||||
};
|
||||
("textDocument/didClose") => {
|
||||
$crate::notification::DidCloseTextDocument
|
||||
};
|
||||
("textDocument/publishDiagnostics") => {
|
||||
$crate::notification::PublishDiagnostics
|
||||
};
|
||||
|
||||
("workspace/didChangeConfiguration") => {
|
||||
$crate::notification::DidChangeConfiguration
|
||||
};
|
||||
("workspace/didChangeWatchedFiles") => {
|
||||
$crate::notification::DidChangeWatchedFiles
|
||||
};
|
||||
("workspace/didChangeWorkspaceFolders") => {
|
||||
$crate::notification::DidChangeWorkspaceFolders
|
||||
};
|
||||
("$/progress") => {
|
||||
$crate::notification::Progress
|
||||
};
|
||||
("workspace/didCreateFiles") => {
|
||||
$crate::notification::DidCreateFiles
|
||||
};
|
||||
("workspace/didRenameFiles") => {
|
||||
$crate::notification::DidRenameFiles
|
||||
};
|
||||
("workspace/didDeleteFiles") => {
|
||||
$crate::notification::DidDeleteFiles
|
||||
};
|
||||
}
|
||||
|
||||
/// The base protocol now offers support for request cancellation. To cancel a request,
|
||||
/// a notification message with the following properties is sent:
|
||||
///
|
||||
/// A request that got canceled still needs to return from the server and send a response back.
|
||||
/// It can not be left open / hanging. This is in line with the JSON RPC protocol that requires
|
||||
/// that every request sends a response back. In addition it allows for returning partial results on cancel.
|
||||
#[derive(Debug)]
|
||||
pub enum Cancel {}
|
||||
|
||||
impl Notification for Cancel {
|
||||
type Params = CancelParams;
|
||||
const METHOD: &'static str = "$/cancelRequest";
|
||||
}
|
||||
|
||||
/// A notification that should be used by the client to modify the trace
|
||||
/// setting of the server.
|
||||
#[derive(Debug)]
|
||||
pub enum SetTrace {}
|
||||
|
||||
impl Notification for SetTrace {
|
||||
type Params = SetTraceParams;
|
||||
const METHOD: &'static str = "$/setTrace";
|
||||
}
|
||||
|
||||
/// A notification to log the trace of the server’s execution.
|
||||
/// The amount and content of these notifications depends on the current trace configuration.
|
||||
///
|
||||
/// `LogTrace` should be used for systematic trace reporting. For single debugging messages,
|
||||
/// the server should send `LogMessage` notifications.
|
||||
#[derive(Debug)]
|
||||
pub enum LogTrace {}
|
||||
|
||||
impl Notification for LogTrace {
|
||||
type Params = LogTraceParams;
|
||||
const METHOD: &'static str = "$/logTrace";
|
||||
}
|
||||
|
||||
/// The initialized notification is sent from the client to the server after the client received
|
||||
/// the result of the initialize request but before the client is sending any other request or
|
||||
/// notification to the server. The server can use the initialized notification for example to
|
||||
/// dynamically register capabilities.
|
||||
#[derive(Debug)]
|
||||
pub enum Initialized {}
|
||||
|
||||
impl Notification for Initialized {
|
||||
type Params = InitializedParams;
|
||||
const METHOD: &'static str = "initialized";
|
||||
}
|
||||
|
||||
/// A notification to ask the server to exit its process.
|
||||
/// The server should exit with success code 0 if the shutdown request has been received before;
|
||||
/// otherwise with error code 1.
|
||||
#[derive(Debug)]
|
||||
pub enum Exit {}
|
||||
|
||||
impl Notification for Exit {
|
||||
type Params = ();
|
||||
const METHOD: &'static str = "exit";
|
||||
}
|
||||
|
||||
/// The show message notification is sent from a server to a client to ask the client to display a particular message
|
||||
/// in the user interface.
|
||||
#[derive(Debug)]
|
||||
pub enum ShowMessage {}
|
||||
|
||||
impl Notification for ShowMessage {
|
||||
type Params = ShowMessageParams;
|
||||
const METHOD: &'static str = "window/showMessage";
|
||||
}
|
||||
|
||||
/// The log message notification is sent from the server to the client to ask the client to log a particular message.
|
||||
#[derive(Debug)]
|
||||
pub enum LogMessage {}
|
||||
|
||||
impl Notification for LogMessage {
|
||||
type Params = LogMessageParams;
|
||||
const METHOD: &'static str = "window/logMessage";
|
||||
}
|
||||
|
||||
/// The telemetry notification is sent from the server to the client to ask the client to log a telemetry event.
|
||||
/// The protocol doesn't specify the payload since no interpretation of the data happens in the protocol. Most clients even don't handle
|
||||
/// the event directly but forward them to the extensions owning the corresponding server issuing the event.
|
||||
#[derive(Debug)]
|
||||
pub enum TelemetryEvent {}
|
||||
|
||||
impl Notification for TelemetryEvent {
|
||||
type Params = OneOf<LSPObject, LSPArray>;
|
||||
const METHOD: &'static str = "telemetry/event";
|
||||
}
|
||||
|
||||
/// A notification sent from the client to the server to signal the change of configuration settings.
|
||||
#[derive(Debug)]
|
||||
pub enum DidChangeConfiguration {}
|
||||
|
||||
impl Notification for DidChangeConfiguration {
|
||||
type Params = DidChangeConfigurationParams;
|
||||
const METHOD: &'static str = "workspace/didChangeConfiguration";
|
||||
}
|
||||
|
||||
/// The document open notification is sent from the client to the server to signal newly opened text documents.
|
||||
/// The document's truth is now managed by the client and the server must not try to read the document's truth
|
||||
/// using the document's uri.
|
||||
#[derive(Debug)]
|
||||
pub enum DidOpenTextDocument {}
|
||||
|
||||
impl Notification for DidOpenTextDocument {
|
||||
type Params = DidOpenTextDocumentParams;
|
||||
const METHOD: &'static str = "textDocument/didOpen";
|
||||
}
|
||||
|
||||
/// The document change notification is sent from the client to the server to signal changes to a text document.
|
||||
/// In 2.0 the shape of the params has changed to include proper version numbers and language ids.
|
||||
#[derive(Debug)]
|
||||
pub enum DidChangeTextDocument {}
|
||||
|
||||
impl Notification for DidChangeTextDocument {
|
||||
type Params = DidChangeTextDocumentParams;
|
||||
const METHOD: &'static str = "textDocument/didChange";
|
||||
}
|
||||
|
||||
/// The document will save notification is sent from the client to the server before the document
|
||||
/// is actually saved.
|
||||
#[derive(Debug)]
|
||||
pub enum WillSaveTextDocument {}
|
||||
|
||||
impl Notification for WillSaveTextDocument {
|
||||
type Params = WillSaveTextDocumentParams;
|
||||
const METHOD: &'static str = "textDocument/willSave";
|
||||
}
|
||||
|
||||
/// The document close notification is sent from the client to the server when the document got closed in the client.
|
||||
/// The document's truth now exists where the document's uri points to (e.g. if the document's uri is a file uri
|
||||
/// the truth now exists on disk).
|
||||
#[derive(Debug)]
|
||||
pub enum DidCloseTextDocument {}
|
||||
|
||||
impl Notification for DidCloseTextDocument {
|
||||
type Params = DidCloseTextDocumentParams;
|
||||
const METHOD: &'static str = "textDocument/didClose";
|
||||
}
|
||||
|
||||
/// The document save notification is sent from the client to the server when the document was saved in the client.
|
||||
#[derive(Debug)]
|
||||
pub enum DidSaveTextDocument {}
|
||||
|
||||
impl Notification for DidSaveTextDocument {
|
||||
type Params = DidSaveTextDocumentParams;
|
||||
const METHOD: &'static str = "textDocument/didSave";
|
||||
}
|
||||
|
||||
/// The watched files notification is sent from the client to the server when the client detects changes to files and folders
|
||||
/// watched by the language client (note although the name suggest that only file events are sent it is about file system events which include folders as well).
|
||||
/// It is recommended that servers register for these file system events using the registration mechanism.
|
||||
/// In former implementations clients pushed file events without the server actively asking for it.
|
||||
#[derive(Debug)]
|
||||
pub enum DidChangeWatchedFiles {}
|
||||
|
||||
impl Notification for DidChangeWatchedFiles {
|
||||
type Params = DidChangeWatchedFilesParams;
|
||||
const METHOD: &'static str = "workspace/didChangeWatchedFiles";
|
||||
}
|
||||
|
||||
/// The workspace/didChangeWorkspaceFolders notification is sent from the client to the server to inform the server
|
||||
/// about workspace folder configuration changes
|
||||
#[derive(Debug)]
|
||||
pub enum DidChangeWorkspaceFolders {}
|
||||
|
||||
impl Notification for DidChangeWorkspaceFolders {
|
||||
type Params = DidChangeWorkspaceFoldersParams;
|
||||
const METHOD: &'static str = "workspace/didChangeWorkspaceFolders";
|
||||
}
|
||||
|
||||
/// Diagnostics notification are sent from the server to the client to signal results of validation runs.
|
||||
#[derive(Debug)]
|
||||
pub enum PublishDiagnostics {}
|
||||
|
||||
impl Notification for PublishDiagnostics {
|
||||
type Params = PublishDiagnosticsParams;
|
||||
const METHOD: &'static str = "textDocument/publishDiagnostics";
|
||||
}
|
||||
|
||||
/// The progress notification is sent from the server to the client to ask
|
||||
/// the client to indicate progress.
|
||||
#[derive(Debug)]
|
||||
pub enum Progress {}
|
||||
|
||||
impl Notification for Progress {
|
||||
type Params = ProgressParams;
|
||||
const METHOD: &'static str = "$/progress";
|
||||
}
|
||||
|
||||
/// The `window/workDoneProgress/cancel` notification is sent from the client
|
||||
/// to the server to cancel a progress initiated on the server side using the `window/workDoneProgress/create`.
|
||||
#[derive(Debug)]
|
||||
pub enum WorkDoneProgressCancel {}
|
||||
|
||||
impl Notification for WorkDoneProgressCancel {
|
||||
type Params = WorkDoneProgressCancelParams;
|
||||
const METHOD: &'static str = "window/workDoneProgress/cancel";
|
||||
}
|
||||
|
||||
/// The did create files notification is sent from the client to the server when files were created from within the client.
|
||||
#[derive(Debug)]
|
||||
pub enum DidCreateFiles {}
|
||||
|
||||
impl Notification for DidCreateFiles {
|
||||
type Params = CreateFilesParams;
|
||||
const METHOD: &'static str = "workspace/didCreateFiles";
|
||||
}
|
||||
|
||||
/// The did rename files notification is sent from the client to the server when files were renamed from within the client.
|
||||
#[derive(Debug)]
|
||||
pub enum DidRenameFiles {}
|
||||
|
||||
impl Notification for DidRenameFiles {
|
||||
type Params = RenameFilesParams;
|
||||
const METHOD: &'static str = "workspace/didRenameFiles";
|
||||
}
|
||||
|
||||
/// The did delete files notification is sent from the client to the server when files were deleted from within the client.
|
||||
#[derive(Debug)]
|
||||
pub enum DidDeleteFiles {}
|
||||
|
||||
impl Notification for DidDeleteFiles {
|
||||
type Params = DeleteFilesParams;
|
||||
const METHOD: &'static str = "workspace/didDeleteFiles";
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
fn fake_call<N>()
|
||||
where
|
||||
N: Notification,
|
||||
N::Params: serde::Serialize,
|
||||
{
|
||||
}
|
||||
|
||||
macro_rules! check_macro {
|
||||
($name:tt) => {
|
||||
// check whether the macro name matches the method
|
||||
assert_eq!(<lsp_notification!($name) as Notification>::METHOD, $name);
|
||||
// test whether type checking passes for each component
|
||||
fake_call::<lsp_notification!($name)>();
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_macro_definitions() {
|
||||
check_macro!("$/cancelRequest");
|
||||
check_macro!("$/progress");
|
||||
check_macro!("$/logTrace");
|
||||
check_macro!("$/setTrace");
|
||||
check_macro!("initialized");
|
||||
check_macro!("exit");
|
||||
check_macro!("window/showMessage");
|
||||
check_macro!("window/logMessage");
|
||||
check_macro!("window/workDoneProgress/cancel");
|
||||
check_macro!("telemetry/event");
|
||||
check_macro!("textDocument/didOpen");
|
||||
check_macro!("textDocument/didChange");
|
||||
check_macro!("textDocument/willSave");
|
||||
check_macro!("textDocument/didSave");
|
||||
check_macro!("textDocument/didClose");
|
||||
check_macro!("textDocument/publishDiagnostics");
|
||||
check_macro!("workspace/didChangeConfiguration");
|
||||
check_macro!("workspace/didChangeWatchedFiles");
|
||||
check_macro!("workspace/didChangeWorkspaceFolders");
|
||||
check_macro!("workspace/didCreateFiles");
|
||||
check_macro!("workspace/didRenameFiles");
|
||||
check_macro!("workspace/didDeleteFiles");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "proposed")]
|
||||
fn check_proposed_macro_definitions() {}
|
||||
}
|
134
helix-lsp-types/src/progress.rs
Normal file
134
helix-lsp-types/src/progress.rs
Normal file
@ -0,0 +1,134 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::NumberOrString;
|
||||
|
||||
pub type ProgressToken = NumberOrString;
|
||||
|
||||
/// The progress notification is sent from the server to the client to ask
|
||||
/// the client to indicate progress.
|
||||
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProgressParams {
|
||||
/// The progress token provided by the client.
|
||||
pub token: ProgressToken,
|
||||
|
||||
/// The progress data.
|
||||
pub value: ProgressParamsValue,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
|
||||
#[serde(untagged)]
|
||||
pub enum ProgressParamsValue {
|
||||
WorkDone(WorkDoneProgress),
|
||||
}
|
||||
|
||||
/// The `window/workDoneProgress/create` request is sent
|
||||
/// from the server to the client to ask the client to create a work done progress.
|
||||
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WorkDoneProgressCreateParams {
|
||||
/// The token to be used to report progress.
|
||||
pub token: ProgressToken,
|
||||
}
|
||||
|
||||
/// The `window/workDoneProgress/cancel` notification is sent from the client
|
||||
/// to the server to cancel a progress initiated on the server side using the `window/workDoneProgress/create`.
|
||||
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WorkDoneProgressCancelParams {
|
||||
/// The token to be used to report progress.
|
||||
pub token: ProgressToken,
|
||||
}
|
||||
|
||||
/// Options to signal work done progress support in server capabilities.
|
||||
#[derive(Debug, Eq, PartialEq, Default, Deserialize, Serialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WorkDoneProgressOptions {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub work_done_progress: Option<bool>,
|
||||
}
|
||||
|
||||
/// An optional token that a server can use to report work done progress
|
||||
#[derive(Debug, Eq, PartialEq, Default, Deserialize, Serialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WorkDoneProgressParams {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub work_done_token: Option<ProgressToken>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Default, Deserialize, Serialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WorkDoneProgressBegin {
|
||||
/// Mandatory title of the progress operation. Used to briefly inform
|
||||
/// about the kind of operation being performed.
|
||||
/// Examples: "Indexing" or "Linking dependencies".
|
||||
pub title: String,
|
||||
|
||||
/// Controls if a cancel button should show to allow the user to cancel the
|
||||
/// long running operation. Clients that don't support cancellation are allowed
|
||||
/// to ignore the setting.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub cancellable: Option<bool>,
|
||||
|
||||
/// Optional, more detailed associated progress message. Contains
|
||||
/// complementary information to the `title`.
|
||||
///
|
||||
/// Examples: "3/25 files", "project/src/module2", "node_modules/some_dep".
|
||||
/// If unset, the previous progress message (if any) is still valid.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub message: Option<String>,
|
||||
|
||||
/// Optional progress percentage to display (value 100 is considered 100%).
|
||||
/// If not provided infinite progress is assumed and clients are allowed
|
||||
/// to ignore the `percentage` value in subsequent in report notifications.
|
||||
///
|
||||
/// The value should be steadily rising. Clients are free to ignore values
|
||||
/// that are not following this rule. The value range is [0, 100]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub percentage: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Default, Deserialize, Serialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WorkDoneProgressReport {
|
||||
/// Controls if a cancel button should show to allow the user to cancel the
|
||||
/// long running operation. Clients that don't support cancellation are allowed
|
||||
/// to ignore the setting.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub cancellable: Option<bool>,
|
||||
|
||||
/// Optional, more detailed associated progress message. Contains
|
||||
/// complementary information to the `title`.
|
||||
/// Examples: "3/25 files", "project/src/module2", "node_modules/some_dep".
|
||||
/// If unset, the previous progress message (if any) is still valid.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub message: Option<String>,
|
||||
|
||||
/// Optional progress percentage to display (value 100 is considered 100%).
|
||||
/// If not provided infinite progress is assumed and clients are allowed
|
||||
/// to ignore the `percentage` value in subsequent in report notifications.
|
||||
///
|
||||
/// The value should be steadily rising. Clients are free to ignore values
|
||||
/// that are not following this rule. The value range is [0, 100]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub percentage: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Default, Deserialize, Serialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WorkDoneProgressEnd {
|
||||
/// Optional, more detailed associated progress message. Contains
|
||||
/// complementary information to the `title`.
|
||||
/// Examples: "3/25 files", "project/src/module2", "node_modules/some_dep".
|
||||
/// If unset, the previous progress message (if any) is still valid.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub message: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
|
||||
#[serde(tag = "kind", rename_all = "lowercase")]
|
||||
pub enum WorkDoneProgress {
|
||||
Begin(WorkDoneProgressBegin),
|
||||
Report(WorkDoneProgressReport),
|
||||
End(WorkDoneProgressEnd),
|
||||
}
|
30
helix-lsp-types/src/references.rs
Normal file
30
helix-lsp-types/src/references.rs
Normal file
@ -0,0 +1,30 @@
|
||||
use crate::{
|
||||
DynamicRegistrationClientCapabilities, PartialResultParams, TextDocumentPositionParams,
|
||||
WorkDoneProgressParams,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub type ReferenceClientCapabilities = DynamicRegistrationClientCapabilities;
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ReferenceContext {
|
||||
/// Include the declaration of the current symbol.
|
||||
pub include_declaration: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ReferenceParams {
|
||||
// Text Document and Position fields
|
||||
#[serde(flatten)]
|
||||
pub text_document_position: TextDocumentPositionParams,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub partial_result_params: PartialResultParams,
|
||||
|
||||
// ReferenceParams properties:
|
||||
pub context: ReferenceContext,
|
||||
}
|
88
helix-lsp-types/src/rename.rs
Normal file
88
helix-lsp-types/src/rename.rs
Normal file
@ -0,0 +1,88 @@
|
||||
use crate::{Range, TextDocumentPositionParams, WorkDoneProgressOptions, WorkDoneProgressParams};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RenameParams {
|
||||
/// Text Document and Position fields
|
||||
#[serde(flatten)]
|
||||
pub text_document_position: TextDocumentPositionParams,
|
||||
|
||||
/// The new name of the symbol. If the given name is not valid the
|
||||
/// request must return a [ResponseError](#ResponseError) with an
|
||||
/// appropriate message set.
|
||||
pub new_name: String,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RenameOptions {
|
||||
/// Renames should be checked and tested before being executed.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub prepare_provider: Option<bool>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_options: WorkDoneProgressOptions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RenameClientCapabilities {
|
||||
/// Whether rename supports dynamic registration.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub dynamic_registration: Option<bool>,
|
||||
|
||||
/// Client supports testing for validity of rename operations before execution.
|
||||
///
|
||||
/// @since 3.12.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub prepare_support: Option<bool>,
|
||||
|
||||
/// Client supports the default behavior result.
|
||||
///
|
||||
/// The value indicates the default behavior used by the
|
||||
/// client.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub prepare_support_default_behavior: Option<PrepareSupportDefaultBehavior>,
|
||||
|
||||
/// Whether the client honors the change annotations in
|
||||
/// text edits and resource operations returned via the
|
||||
/// rename request's workspace edit by for example presenting
|
||||
/// the workspace edit in the user interface and asking
|
||||
/// for confirmation.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub honors_change_annotations: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Copy, Clone, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct PrepareSupportDefaultBehavior(i32);
|
||||
lsp_enum! {
|
||||
impl PrepareSupportDefaultBehavior {
|
||||
/// The client's default behavior is to select the identifier
|
||||
/// according the to language's syntax rule
|
||||
pub const IDENTIFIER: PrepareSupportDefaultBehavior = PrepareSupportDefaultBehavior(1);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(untagged)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum PrepareRenameResponse {
|
||||
Range(Range),
|
||||
RangeWithPlaceholder {
|
||||
range: Range,
|
||||
placeholder: String,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
DefaultBehavior {
|
||||
default_behavior: bool,
|
||||
},
|
||||
}
|
1068
helix-lsp-types/src/request.rs
Normal file
1068
helix-lsp-types/src/request.rs
Normal file
File diff suppressed because it is too large
Load Diff
86
helix-lsp-types/src/selection_range.rs
Normal file
86
helix-lsp-types/src/selection_range.rs
Normal file
@ -0,0 +1,86 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
PartialResultParams, Position, Range, StaticTextDocumentRegistrationOptions,
|
||||
TextDocumentIdentifier, WorkDoneProgressOptions, WorkDoneProgressParams,
|
||||
};
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SelectionRangeClientCapabilities {
|
||||
/// Whether implementation supports dynamic registration for selection range
|
||||
/// providers. If this is set to `true` the client supports the new
|
||||
/// `SelectionRangeRegistrationOptions` return value for the corresponding
|
||||
/// server capability as well.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub dynamic_registration: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
pub struct SelectionRangeOptions {
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_options: WorkDoneProgressOptions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
pub struct SelectionRangeRegistrationOptions {
|
||||
#[serde(flatten)]
|
||||
pub selection_range_options: SelectionRangeOptions,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub registration_options: StaticTextDocumentRegistrationOptions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum SelectionRangeProviderCapability {
|
||||
Simple(bool),
|
||||
Options(SelectionRangeOptions),
|
||||
RegistrationOptions(SelectionRangeRegistrationOptions),
|
||||
}
|
||||
|
||||
impl From<SelectionRangeRegistrationOptions> for SelectionRangeProviderCapability {
|
||||
fn from(from: SelectionRangeRegistrationOptions) -> Self {
|
||||
Self::RegistrationOptions(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SelectionRangeOptions> for SelectionRangeProviderCapability {
|
||||
fn from(from: SelectionRangeOptions) -> Self {
|
||||
Self::Options(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for SelectionRangeProviderCapability {
|
||||
fn from(from: bool) -> Self {
|
||||
Self::Simple(from)
|
||||
}
|
||||
}
|
||||
|
||||
/// A parameter literal used in selection range requests.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SelectionRangeParams {
|
||||
/// The text document.
|
||||
pub text_document: TextDocumentIdentifier,
|
||||
|
||||
/// The positions inside the text document.
|
||||
pub positions: Vec<Position>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub partial_result_params: PartialResultParams,
|
||||
}
|
||||
|
||||
/// Represents a selection range.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SelectionRange {
|
||||
/// Range of the selection.
|
||||
pub range: Range,
|
||||
|
||||
/// The parent selection range containing this range.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub parent: Option<Box<SelectionRange>>,
|
||||
}
|
734
helix-lsp-types/src/semantic_tokens.rs
Normal file
734
helix-lsp-types/src/semantic_tokens.rs
Normal file
@ -0,0 +1,734 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use serde::ser::SerializeSeq;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
PartialResultParams, Range, StaticRegistrationOptions, TextDocumentIdentifier,
|
||||
TextDocumentRegistrationOptions, WorkDoneProgressOptions, WorkDoneProgressParams,
|
||||
};
|
||||
/// A set of predefined token types. This set is not fixed
|
||||
/// and clients can specify additional token types via the
|
||||
/// corresponding client capabilities.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[derive(Debug, Eq, PartialEq, Hash, PartialOrd, Clone, Deserialize, Serialize)]
|
||||
pub struct SemanticTokenType(Cow<'static, str>);
|
||||
|
||||
impl SemanticTokenType {
|
||||
pub const NAMESPACE: SemanticTokenType = SemanticTokenType::new("namespace");
|
||||
pub const TYPE: SemanticTokenType = SemanticTokenType::new("type");
|
||||
pub const CLASS: SemanticTokenType = SemanticTokenType::new("class");
|
||||
pub const ENUM: SemanticTokenType = SemanticTokenType::new("enum");
|
||||
pub const INTERFACE: SemanticTokenType = SemanticTokenType::new("interface");
|
||||
pub const STRUCT: SemanticTokenType = SemanticTokenType::new("struct");
|
||||
pub const TYPE_PARAMETER: SemanticTokenType = SemanticTokenType::new("typeParameter");
|
||||
pub const PARAMETER: SemanticTokenType = SemanticTokenType::new("parameter");
|
||||
pub const VARIABLE: SemanticTokenType = SemanticTokenType::new("variable");
|
||||
pub const PROPERTY: SemanticTokenType = SemanticTokenType::new("property");
|
||||
pub const ENUM_MEMBER: SemanticTokenType = SemanticTokenType::new("enumMember");
|
||||
pub const EVENT: SemanticTokenType = SemanticTokenType::new("event");
|
||||
pub const FUNCTION: SemanticTokenType = SemanticTokenType::new("function");
|
||||
pub const METHOD: SemanticTokenType = SemanticTokenType::new("method");
|
||||
pub const MACRO: SemanticTokenType = SemanticTokenType::new("macro");
|
||||
pub const KEYWORD: SemanticTokenType = SemanticTokenType::new("keyword");
|
||||
pub const MODIFIER: SemanticTokenType = SemanticTokenType::new("modifier");
|
||||
pub const COMMENT: SemanticTokenType = SemanticTokenType::new("comment");
|
||||
pub const STRING: SemanticTokenType = SemanticTokenType::new("string");
|
||||
pub const NUMBER: SemanticTokenType = SemanticTokenType::new("number");
|
||||
pub const REGEXP: SemanticTokenType = SemanticTokenType::new("regexp");
|
||||
pub const OPERATOR: SemanticTokenType = SemanticTokenType::new("operator");
|
||||
|
||||
/// @since 3.17.0
|
||||
pub const DECORATOR: SemanticTokenType = SemanticTokenType::new("decorator");
|
||||
|
||||
pub const fn new(tag: &'static str) -> Self {
|
||||
SemanticTokenType(Cow::Borrowed(tag))
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for SemanticTokenType {
|
||||
fn from(from: String) -> Self {
|
||||
SemanticTokenType(Cow::from(from))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for SemanticTokenType {
|
||||
fn from(from: &'static str) -> Self {
|
||||
SemanticTokenType::new(from)
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of predefined token modifiers. This set is not fixed
|
||||
/// and clients can specify additional token types via the
|
||||
/// corresponding client capabilities.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[derive(Debug, Eq, PartialEq, Hash, PartialOrd, Clone, Deserialize, Serialize)]
|
||||
pub struct SemanticTokenModifier(Cow<'static, str>);
|
||||
|
||||
impl SemanticTokenModifier {
|
||||
pub const DECLARATION: SemanticTokenModifier = SemanticTokenModifier::new("declaration");
|
||||
pub const DEFINITION: SemanticTokenModifier = SemanticTokenModifier::new("definition");
|
||||
pub const READONLY: SemanticTokenModifier = SemanticTokenModifier::new("readonly");
|
||||
pub const STATIC: SemanticTokenModifier = SemanticTokenModifier::new("static");
|
||||
pub const DEPRECATED: SemanticTokenModifier = SemanticTokenModifier::new("deprecated");
|
||||
pub const ABSTRACT: SemanticTokenModifier = SemanticTokenModifier::new("abstract");
|
||||
pub const ASYNC: SemanticTokenModifier = SemanticTokenModifier::new("async");
|
||||
pub const MODIFICATION: SemanticTokenModifier = SemanticTokenModifier::new("modification");
|
||||
pub const DOCUMENTATION: SemanticTokenModifier = SemanticTokenModifier::new("documentation");
|
||||
pub const DEFAULT_LIBRARY: SemanticTokenModifier = SemanticTokenModifier::new("defaultLibrary");
|
||||
|
||||
pub const fn new(tag: &'static str) -> Self {
|
||||
SemanticTokenModifier(Cow::Borrowed(tag))
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for SemanticTokenModifier {
|
||||
fn from(from: String) -> Self {
|
||||
SemanticTokenModifier(Cow::from(from))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for SemanticTokenModifier {
|
||||
fn from(from: &'static str) -> Self {
|
||||
SemanticTokenModifier::new(from)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Hash, PartialOrd, Clone, Deserialize, Serialize)]
|
||||
pub struct TokenFormat(Cow<'static, str>);
|
||||
|
||||
impl TokenFormat {
|
||||
pub const RELATIVE: TokenFormat = TokenFormat::new("relative");
|
||||
|
||||
pub const fn new(tag: &'static str) -> Self {
|
||||
TokenFormat(Cow::Borrowed(tag))
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for TokenFormat {
|
||||
fn from(from: String) -> Self {
|
||||
TokenFormat(Cow::from(from))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for TokenFormat {
|
||||
fn from(from: &'static str) -> Self {
|
||||
TokenFormat::new(from)
|
||||
}
|
||||
}
|
||||
|
||||
/// @since 3.16.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SemanticTokensLegend {
|
||||
/// The token types a server uses.
|
||||
pub token_types: Vec<SemanticTokenType>,
|
||||
|
||||
/// The token modifiers a server uses.
|
||||
pub token_modifiers: Vec<SemanticTokenModifier>,
|
||||
}
|
||||
|
||||
/// The actual tokens.
|
||||
#[derive(Debug, Eq, PartialEq, Copy, Clone, Default)]
|
||||
pub struct SemanticToken {
|
||||
pub delta_line: u32,
|
||||
pub delta_start: u32,
|
||||
pub length: u32,
|
||||
pub token_type: u32,
|
||||
pub token_modifiers_bitset: u32,
|
||||
}
|
||||
|
||||
impl SemanticToken {
|
||||
fn deserialize_tokens<'de, D>(deserializer: D) -> Result<Vec<SemanticToken>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let data = Vec::<u32>::deserialize(deserializer)?;
|
||||
let chunks = data.chunks_exact(5);
|
||||
|
||||
if !chunks.remainder().is_empty() {
|
||||
return Result::Err(serde::de::Error::custom("Length is not divisible by 5"));
|
||||
}
|
||||
|
||||
Result::Ok(
|
||||
chunks
|
||||
.map(|chunk| SemanticToken {
|
||||
delta_line: chunk[0],
|
||||
delta_start: chunk[1],
|
||||
length: chunk[2],
|
||||
token_type: chunk[3],
|
||||
token_modifiers_bitset: chunk[4],
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
fn serialize_tokens<S>(tokens: &[SemanticToken], serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let mut seq = serializer.serialize_seq(Some(tokens.len() * 5))?;
|
||||
for token in tokens.iter() {
|
||||
seq.serialize_element(&token.delta_line)?;
|
||||
seq.serialize_element(&token.delta_start)?;
|
||||
seq.serialize_element(&token.length)?;
|
||||
seq.serialize_element(&token.token_type)?;
|
||||
seq.serialize_element(&token.token_modifiers_bitset)?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
|
||||
fn deserialize_tokens_opt<'de, D>(
|
||||
deserializer: D,
|
||||
) -> Result<Option<Vec<SemanticToken>>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
#[serde(transparent)]
|
||||
struct Wrapper {
|
||||
#[serde(deserialize_with = "SemanticToken::deserialize_tokens")]
|
||||
tokens: Vec<SemanticToken>,
|
||||
}
|
||||
|
||||
Ok(Option::<Wrapper>::deserialize(deserializer)?.map(|wrapper| wrapper.tokens))
|
||||
}
|
||||
|
||||
fn serialize_tokens_opt<S>(
|
||||
data: &Option<Vec<SemanticToken>>,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
#[derive(Serialize)]
|
||||
#[serde(transparent)]
|
||||
struct Wrapper {
|
||||
#[serde(serialize_with = "SemanticToken::serialize_tokens")]
|
||||
tokens: Vec<SemanticToken>,
|
||||
}
|
||||
|
||||
let opt = data.as_ref().map(|t| Wrapper { tokens: t.to_vec() });
|
||||
|
||||
opt.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
/// @since 3.16.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SemanticTokens {
|
||||
/// An optional result id. If provided and clients support delta updating
|
||||
/// the client will include the result id in the next semantic token request.
|
||||
/// A server can then instead of computing all semantic tokens again simply
|
||||
/// send a delta.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub result_id: Option<String>,
|
||||
|
||||
/// The actual tokens. For a detailed description about how the data is
|
||||
/// structured please see
|
||||
/// <https://github.com/microsoft/vscode-extension-samples/blob/5ae1f7787122812dcc84e37427ca90af5ee09f14/semantic-tokens-sample/vscode.proposed.d.ts#L71>
|
||||
#[serde(
|
||||
deserialize_with = "SemanticToken::deserialize_tokens",
|
||||
serialize_with = "SemanticToken::serialize_tokens"
|
||||
)]
|
||||
pub data: Vec<SemanticToken>,
|
||||
}
|
||||
|
||||
/// @since 3.16.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SemanticTokensPartialResult {
|
||||
#[serde(
|
||||
deserialize_with = "SemanticToken::deserialize_tokens",
|
||||
serialize_with = "SemanticToken::serialize_tokens"
|
||||
)]
|
||||
pub data: Vec<SemanticToken>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(untagged)]
|
||||
pub enum SemanticTokensResult {
|
||||
Tokens(SemanticTokens),
|
||||
Partial(SemanticTokensPartialResult),
|
||||
}
|
||||
|
||||
impl From<SemanticTokens> for SemanticTokensResult {
|
||||
fn from(from: SemanticTokens) -> Self {
|
||||
SemanticTokensResult::Tokens(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SemanticTokensPartialResult> for SemanticTokensResult {
|
||||
fn from(from: SemanticTokensPartialResult) -> Self {
|
||||
SemanticTokensResult::Partial(from)
|
||||
}
|
||||
}
|
||||
|
||||
/// @since 3.16.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SemanticTokensEdit {
|
||||
pub start: u32,
|
||||
pub delete_count: u32,
|
||||
|
||||
#[serde(
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none",
|
||||
deserialize_with = "SemanticToken::deserialize_tokens_opt",
|
||||
serialize_with = "SemanticToken::serialize_tokens_opt"
|
||||
)]
|
||||
pub data: Option<Vec<SemanticToken>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(untagged)]
|
||||
pub enum SemanticTokensFullDeltaResult {
|
||||
Tokens(SemanticTokens),
|
||||
TokensDelta(SemanticTokensDelta),
|
||||
PartialTokensDelta { edits: Vec<SemanticTokensEdit> },
|
||||
}
|
||||
|
||||
impl From<SemanticTokens> for SemanticTokensFullDeltaResult {
|
||||
fn from(from: SemanticTokens) -> Self {
|
||||
SemanticTokensFullDeltaResult::Tokens(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SemanticTokensDelta> for SemanticTokensFullDeltaResult {
|
||||
fn from(from: SemanticTokensDelta) -> Self {
|
||||
SemanticTokensFullDeltaResult::TokensDelta(from)
|
||||
}
|
||||
}
|
||||
|
||||
/// @since 3.16.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SemanticTokensDelta {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub result_id: Option<String>,
|
||||
/// For a detailed description how these edits are structured please see
|
||||
/// <https://github.com/microsoft/vscode-extension-samples/blob/5ae1f7787122812dcc84e37427ca90af5ee09f14/semantic-tokens-sample/vscode.proposed.d.ts#L131>
|
||||
pub edits: Vec<SemanticTokensEdit>,
|
||||
}
|
||||
|
||||
/// Capabilities specific to the `textDocument/semanticTokens/*` requests.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SemanticTokensClientCapabilities {
|
||||
/// Whether implementation supports dynamic registration. If this is set to `true`
|
||||
/// the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)`
|
||||
/// return value for the corresponding server capability as well.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub dynamic_registration: Option<bool>,
|
||||
|
||||
/// Which requests the client supports and might send to the server
|
||||
/// depending on the server's capability. Please note that clients might not
|
||||
/// show semantic tokens or degrade some of the user experience if a range
|
||||
/// or full request is advertised by the client but not provided by the
|
||||
/// server. If for example the client capability `requests.full` and
|
||||
/// `request.range` are both set to true but the server only provides a
|
||||
/// range provider the client might not render a minimap correctly or might
|
||||
/// even decide to not show any semantic tokens at all.
|
||||
pub requests: SemanticTokensClientCapabilitiesRequests,
|
||||
|
||||
/// The token types that the client supports.
|
||||
pub token_types: Vec<SemanticTokenType>,
|
||||
|
||||
/// The token modifiers that the client supports.
|
||||
pub token_modifiers: Vec<SemanticTokenModifier>,
|
||||
|
||||
/// The token formats the clients supports.
|
||||
pub formats: Vec<TokenFormat>,
|
||||
|
||||
/// Whether the client supports tokens that can overlap each other.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub overlapping_token_support: Option<bool>,
|
||||
|
||||
/// Whether the client supports tokens that can span multiple lines.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub multiline_token_support: Option<bool>,
|
||||
|
||||
/// Whether the client allows the server to actively cancel a
|
||||
/// semantic token request, e.g. supports returning
|
||||
/// ErrorCodes.ServerCancelled. If a server does the client
|
||||
/// needs to retrigger the request.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub server_cancel_support: Option<bool>,
|
||||
|
||||
/// Whether the client uses semantic tokens to augment existing
|
||||
/// syntax tokens. If set to `true` client side created syntax
|
||||
/// tokens and semantic tokens are both used for colorization. If
|
||||
/// set to `false` the client only uses the returned semantic tokens
|
||||
/// for colorization.
|
||||
///
|
||||
/// If the value is `undefined` then the client behavior is not
|
||||
/// specified.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub augments_syntax_tokens: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SemanticTokensClientCapabilitiesRequests {
|
||||
/// The client will send the `textDocument/semanticTokens/range` request if the server provides a corresponding handler.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub range: Option<bool>,
|
||||
|
||||
/// The client will send the `textDocument/semanticTokens/full` request if the server provides a corresponding handler.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub full: Option<SemanticTokensFullOptions>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(untagged)]
|
||||
pub enum SemanticTokensFullOptions {
|
||||
Bool(bool),
|
||||
Delta {
|
||||
/// The client will send the `textDocument/semanticTokens/full/delta` request if the server provides a corresponding handler.
|
||||
/// The server supports deltas for full documents.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
delta: Option<bool>,
|
||||
},
|
||||
}
|
||||
|
||||
/// @since 3.16.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SemanticTokensOptions {
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_options: WorkDoneProgressOptions,
|
||||
|
||||
/// The legend used by the server
|
||||
pub legend: SemanticTokensLegend,
|
||||
|
||||
/// Server supports providing semantic tokens for a specific range
|
||||
/// of a document.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub range: Option<bool>,
|
||||
|
||||
/// Server supports providing semantic tokens for a full document.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub full: Option<SemanticTokensFullOptions>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SemanticTokensRegistrationOptions {
|
||||
#[serde(flatten)]
|
||||
pub text_document_registration_options: TextDocumentRegistrationOptions,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub semantic_tokens_options: SemanticTokensOptions,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub static_registration_options: StaticRegistrationOptions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(untagged)]
|
||||
pub enum SemanticTokensServerCapabilities {
|
||||
SemanticTokensOptions(SemanticTokensOptions),
|
||||
SemanticTokensRegistrationOptions(SemanticTokensRegistrationOptions),
|
||||
}
|
||||
|
||||
impl From<SemanticTokensOptions> for SemanticTokensServerCapabilities {
|
||||
fn from(from: SemanticTokensOptions) -> Self {
|
||||
SemanticTokensServerCapabilities::SemanticTokensOptions(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SemanticTokensRegistrationOptions> for SemanticTokensServerCapabilities {
|
||||
fn from(from: SemanticTokensRegistrationOptions) -> Self {
|
||||
SemanticTokensServerCapabilities::SemanticTokensRegistrationOptions(from)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SemanticTokensWorkspaceClientCapabilities {
|
||||
/// Whether the client implementation supports a refresh request sent from
|
||||
/// the server to the client.
|
||||
///
|
||||
/// Note that this event is global and will force the client to refresh all
|
||||
/// semantic tokens currently shown. It should be used with absolute care
|
||||
/// and is useful for situation where a server for example detect a project
|
||||
/// wide change that requires such a calculation.
|
||||
pub refresh_support: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SemanticTokensParams {
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub partial_result_params: PartialResultParams,
|
||||
|
||||
/// The text document.
|
||||
pub text_document: TextDocumentIdentifier,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SemanticTokensDeltaParams {
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub partial_result_params: PartialResultParams,
|
||||
|
||||
/// The text document.
|
||||
pub text_document: TextDocumentIdentifier,
|
||||
|
||||
/// The result id of a previous response. The result Id can either point to a full response
|
||||
/// or a delta response depending on what was received last.
|
||||
pub previous_result_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SemanticTokensRangeParams {
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub partial_result_params: PartialResultParams,
|
||||
|
||||
/// The text document.
|
||||
pub text_document: TextDocumentIdentifier,
|
||||
|
||||
/// The range the semantic tokens are requested for.
|
||||
pub range: Range,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(untagged)]
|
||||
pub enum SemanticTokensRangeResult {
|
||||
Tokens(SemanticTokens),
|
||||
Partial(SemanticTokensPartialResult),
|
||||
}
|
||||
|
||||
impl From<SemanticTokens> for SemanticTokensRangeResult {
|
||||
fn from(tokens: SemanticTokens) -> Self {
|
||||
SemanticTokensRangeResult::Tokens(tokens)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SemanticTokensPartialResult> for SemanticTokensRangeResult {
|
||||
fn from(partial: SemanticTokensPartialResult) -> Self {
|
||||
SemanticTokensRangeResult::Partial(partial)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tests::{test_deserialization, test_serialization};
|
||||
|
||||
#[test]
|
||||
fn test_semantic_tokens_support_serialization() {
|
||||
test_serialization(
|
||||
&SemanticTokens {
|
||||
result_id: None,
|
||||
data: vec![],
|
||||
},
|
||||
r#"{"data":[]}"#,
|
||||
);
|
||||
|
||||
test_serialization(
|
||||
&SemanticTokens {
|
||||
result_id: None,
|
||||
data: vec![SemanticToken {
|
||||
delta_line: 2,
|
||||
delta_start: 5,
|
||||
length: 3,
|
||||
token_type: 0,
|
||||
token_modifiers_bitset: 3,
|
||||
}],
|
||||
},
|
||||
r#"{"data":[2,5,3,0,3]}"#,
|
||||
);
|
||||
|
||||
test_serialization(
|
||||
&SemanticTokens {
|
||||
result_id: None,
|
||||
data: vec![
|
||||
SemanticToken {
|
||||
delta_line: 2,
|
||||
delta_start: 5,
|
||||
length: 3,
|
||||
token_type: 0,
|
||||
token_modifiers_bitset: 3,
|
||||
},
|
||||
SemanticToken {
|
||||
delta_line: 0,
|
||||
delta_start: 5,
|
||||
length: 4,
|
||||
token_type: 1,
|
||||
token_modifiers_bitset: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
r#"{"data":[2,5,3,0,3,0,5,4,1,0]}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_semantic_tokens_support_deserialization() {
|
||||
test_deserialization(
|
||||
r#"{"data":[]}"#,
|
||||
&SemanticTokens {
|
||||
result_id: None,
|
||||
data: vec![],
|
||||
},
|
||||
);
|
||||
|
||||
test_deserialization(
|
||||
r#"{"data":[2,5,3,0,3]}"#,
|
||||
&SemanticTokens {
|
||||
result_id: None,
|
||||
data: vec![SemanticToken {
|
||||
delta_line: 2,
|
||||
delta_start: 5,
|
||||
length: 3,
|
||||
token_type: 0,
|
||||
token_modifiers_bitset: 3,
|
||||
}],
|
||||
},
|
||||
);
|
||||
|
||||
test_deserialization(
|
||||
r#"{"data":[2,5,3,0,3,0,5,4,1,0]}"#,
|
||||
&SemanticTokens {
|
||||
result_id: None,
|
||||
data: vec![
|
||||
SemanticToken {
|
||||
delta_line: 2,
|
||||
delta_start: 5,
|
||||
length: 3,
|
||||
token_type: 0,
|
||||
token_modifiers_bitset: 3,
|
||||
},
|
||||
SemanticToken {
|
||||
delta_line: 0,
|
||||
delta_start: 5,
|
||||
length: 4,
|
||||
token_type: 1,
|
||||
token_modifiers_bitset: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_semantic_tokens_support_deserialization_err() {
|
||||
test_deserialization(
|
||||
r#"{"data":[1]}"#,
|
||||
&SemanticTokens {
|
||||
result_id: None,
|
||||
data: vec![],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_semantic_tokens_edit_support_deserialization() {
|
||||
test_deserialization(
|
||||
r#"{"start":0,"deleteCount":1,"data":[2,5,3,0,3,0,5,4,1,0]}"#,
|
||||
&SemanticTokensEdit {
|
||||
start: 0,
|
||||
delete_count: 1,
|
||||
data: Some(vec![
|
||||
SemanticToken {
|
||||
delta_line: 2,
|
||||
delta_start: 5,
|
||||
length: 3,
|
||||
token_type: 0,
|
||||
token_modifiers_bitset: 3,
|
||||
},
|
||||
SemanticToken {
|
||||
delta_line: 0,
|
||||
delta_start: 5,
|
||||
length: 4,
|
||||
token_type: 1,
|
||||
token_modifiers_bitset: 0,
|
||||
},
|
||||
]),
|
||||
},
|
||||
);
|
||||
|
||||
test_deserialization(
|
||||
r#"{"start":0,"deleteCount":1}"#,
|
||||
&SemanticTokensEdit {
|
||||
start: 0,
|
||||
delete_count: 1,
|
||||
data: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_semantic_tokens_edit_support_serialization() {
|
||||
test_serialization(
|
||||
&SemanticTokensEdit {
|
||||
start: 0,
|
||||
delete_count: 1,
|
||||
data: Some(vec![
|
||||
SemanticToken {
|
||||
delta_line: 2,
|
||||
delta_start: 5,
|
||||
length: 3,
|
||||
token_type: 0,
|
||||
token_modifiers_bitset: 3,
|
||||
},
|
||||
SemanticToken {
|
||||
delta_line: 0,
|
||||
delta_start: 5,
|
||||
length: 4,
|
||||
token_type: 1,
|
||||
token_modifiers_bitset: 0,
|
||||
},
|
||||
]),
|
||||
},
|
||||
r#"{"start":0,"deleteCount":1,"data":[2,5,3,0,3,0,5,4,1,0]}"#,
|
||||
);
|
||||
|
||||
test_serialization(
|
||||
&SemanticTokensEdit {
|
||||
start: 0,
|
||||
delete_count: 1,
|
||||
data: None,
|
||||
},
|
||||
r#"{"start":0,"deleteCount":1}"#,
|
||||
);
|
||||
}
|
||||
}
|
207
helix-lsp-types/src/signature_help.rs
Normal file
207
helix-lsp-types/src/signature_help.rs
Normal file
@ -0,0 +1,207 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
Documentation, MarkupKind, TextDocumentPositionParams, TextDocumentRegistrationOptions,
|
||||
WorkDoneProgressOptions, WorkDoneProgressParams,
|
||||
};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SignatureInformationSettings {
|
||||
/// Client supports the follow content formats for the documentation
|
||||
/// property. The order describes the preferred format of the client.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub documentation_format: Option<Vec<MarkupKind>>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub parameter_information: Option<ParameterInformationSettings>,
|
||||
|
||||
/// The client support the `activeParameter` property on `SignatureInformation`
|
||||
/// literal.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub active_parameter_support: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ParameterInformationSettings {
|
||||
/// The client supports processing label offsets instead of a
|
||||
/// simple label string.
|
||||
///
|
||||
/// @since 3.14.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub label_offset_support: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SignatureHelpClientCapabilities {
|
||||
/// Whether completion supports dynamic registration.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub dynamic_registration: Option<bool>,
|
||||
|
||||
/// The client supports the following `SignatureInformation`
|
||||
/// specific properties.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub signature_information: Option<SignatureInformationSettings>,
|
||||
|
||||
/// The client supports to send additional context information for a
|
||||
/// `textDocument/signatureHelp` request. A client that opts into
|
||||
/// contextSupport will also support the `retriggerCharacters` on
|
||||
/// `SignatureHelpOptions`.
|
||||
///
|
||||
/// @since 3.15.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub context_support: Option<bool>,
|
||||
}
|
||||
|
||||
/// Signature help options.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SignatureHelpOptions {
|
||||
/// The characters that trigger signature help automatically.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub trigger_characters: Option<Vec<String>>,
|
||||
|
||||
/// List of characters that re-trigger signature help.
|
||||
/// These trigger characters are only active when signature help is already showing. All trigger characters
|
||||
/// are also counted as re-trigger characters.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub retrigger_characters: Option<Vec<String>>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_options: WorkDoneProgressOptions,
|
||||
}
|
||||
|
||||
/// Signature help options.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
pub struct SignatureHelpRegistrationOptions {
|
||||
#[serde(flatten)]
|
||||
pub text_document_registration_options: TextDocumentRegistrationOptions,
|
||||
}
|
||||
|
||||
/// Signature help options.
|
||||
#[derive(Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct SignatureHelpTriggerKind(i32);
|
||||
lsp_enum! {
|
||||
impl SignatureHelpTriggerKind {
|
||||
/// Signature help was invoked manually by the user or by a command.
|
||||
pub const INVOKED: SignatureHelpTriggerKind = SignatureHelpTriggerKind(1);
|
||||
/// Signature help was triggered by a trigger character.
|
||||
pub const TRIGGER_CHARACTER: SignatureHelpTriggerKind = SignatureHelpTriggerKind(2);
|
||||
/// Signature help was triggered by the cursor moving or by the document content changing.
|
||||
pub const CONTENT_CHANGE: SignatureHelpTriggerKind = SignatureHelpTriggerKind(3);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SignatureHelpParams {
|
||||
/// The signature help context. This is only available if the client specifies
|
||||
/// to send this using the client capability `textDocument.signatureHelp.contextSupport === true`
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub context: Option<SignatureHelpContext>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub text_document_position_params: TextDocumentPositionParams,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SignatureHelpContext {
|
||||
/// Action that caused signature help to be triggered.
|
||||
pub trigger_kind: SignatureHelpTriggerKind,
|
||||
|
||||
/// Character that caused signature help to be triggered.
|
||||
/// This is undefined when `triggerKind !== SignatureHelpTriggerKind.TriggerCharacter`
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub trigger_character: Option<String>,
|
||||
|
||||
/// `true` if signature help was already showing when it was triggered.
|
||||
/// Retriggers occur when the signature help is already active and can be caused by actions such as
|
||||
/// typing a trigger character, a cursor move, or document content changes.
|
||||
pub is_retrigger: bool,
|
||||
|
||||
/// The currently active `SignatureHelp`.
|
||||
/// The `activeSignatureHelp` has its `SignatureHelp.activeSignature` field updated based on
|
||||
/// the user navigating through available signatures.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub active_signature_help: Option<SignatureHelp>,
|
||||
}
|
||||
|
||||
/// Signature help represents the signature of something
|
||||
/// callable. There can be multiple signature but only one
|
||||
/// active and only one active parameter.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SignatureHelp {
|
||||
/// One or more signatures.
|
||||
pub signatures: Vec<SignatureInformation>,
|
||||
|
||||
/// The active signature.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub active_signature: Option<u32>,
|
||||
|
||||
/// The active parameter of the active signature.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub active_parameter: Option<u32>,
|
||||
}
|
||||
|
||||
/// Represents the signature of something callable. A signature
|
||||
/// can have a label, like a function-name, a doc-comment, and
|
||||
/// a set of parameters.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SignatureInformation {
|
||||
/// The label of this signature. Will be shown in
|
||||
/// the UI.
|
||||
pub label: String,
|
||||
|
||||
/// The human-readable doc-comment of this signature. Will be shown
|
||||
/// in the UI but can be omitted.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub documentation: Option<Documentation>,
|
||||
|
||||
/// The parameters of this signature.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub parameters: Option<Vec<ParameterInformation>>,
|
||||
|
||||
/// The index of the active parameter.
|
||||
///
|
||||
/// If provided, this is used in place of `SignatureHelp.activeParameter`.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub active_parameter: Option<u32>,
|
||||
}
|
||||
|
||||
/// Represents a parameter of a callable-signature. A parameter can
|
||||
/// have a label and a doc-comment.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ParameterInformation {
|
||||
/// The label of this parameter information.
|
||||
///
|
||||
/// Either a string or an inclusive start and exclusive end offsets within its containing
|
||||
/// signature label. (see SignatureInformation.label). *Note*: A label of type string must be
|
||||
/// a substring of its containing signature label.
|
||||
pub label: ParameterLabel,
|
||||
|
||||
/// The human-readable doc-comment of this parameter. Will be shown
|
||||
/// in the UI but can be omitted.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub documentation: Option<Documentation>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum ParameterLabel {
|
||||
Simple(String),
|
||||
LabelOffsets([u32; 2]),
|
||||
}
|
77
helix-lsp-types/src/trace.rs
Normal file
77
helix-lsp-types/src/trace.rs
Normal file
@ -0,0 +1,77 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
pub struct SetTraceParams {
|
||||
/// The new value that should be assigned to the trace setting.
|
||||
pub value: TraceValue,
|
||||
}
|
||||
|
||||
/// A TraceValue represents the level of verbosity with which the server systematically
|
||||
/// reports its execution trace using `LogTrace` notifications.
|
||||
///
|
||||
/// The initial trace value is set by the client at initialization and can be modified
|
||||
/// later using the `SetTrace` notification.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy, Deserialize, Serialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum TraceValue {
|
||||
/// The server should not send any `$/logTrace` notification
|
||||
#[default]
|
||||
Off,
|
||||
/// The server should not add the 'verbose' field in the `LogTraceParams`
|
||||
Messages,
|
||||
Verbose,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LogTraceParams {
|
||||
/// The message to be logged.
|
||||
pub message: String,
|
||||
/// Additional information that can be computed if the `trace` configuration
|
||||
/// is set to `'verbose'`
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub verbose: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tests::test_serialization;
|
||||
|
||||
#[test]
|
||||
fn test_set_trace_params() {
|
||||
test_serialization(
|
||||
&SetTraceParams {
|
||||
value: TraceValue::Off,
|
||||
},
|
||||
r#"{"value":"off"}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_log_trace_params() {
|
||||
test_serialization(
|
||||
&LogTraceParams {
|
||||
message: "message".into(),
|
||||
verbose: None,
|
||||
},
|
||||
r#"{"message":"message"}"#,
|
||||
);
|
||||
|
||||
test_serialization(
|
||||
&LogTraceParams {
|
||||
message: "message".into(),
|
||||
verbose: Some("verbose".into()),
|
||||
},
|
||||
r#"{"message":"message","verbose":"verbose"}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trace_value() {
|
||||
test_serialization(
|
||||
&vec![TraceValue::Off, TraceValue::Messages, TraceValue::Verbose],
|
||||
r#"["off","messages","verbose"]"#,
|
||||
);
|
||||
}
|
||||
}
|
90
helix-lsp-types/src/type_hierarchy.rs
Normal file
90
helix-lsp-types/src/type_hierarchy.rs
Normal file
@ -0,0 +1,90 @@
|
||||
use crate::{
|
||||
DynamicRegistrationClientCapabilities, LSPAny, PartialResultParams, Range,
|
||||
StaticRegistrationOptions, SymbolKind, SymbolTag, TextDocumentPositionParams,
|
||||
TextDocumentRegistrationOptions, Url, WorkDoneProgressOptions, WorkDoneProgressParams,
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub type TypeHierarchyClientCapabilities = DynamicRegistrationClientCapabilities;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
pub struct TypeHierarchyOptions {
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_options: WorkDoneProgressOptions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
pub struct TypeHierarchyRegistrationOptions {
|
||||
#[serde(flatten)]
|
||||
pub text_document_registration_options: TextDocumentRegistrationOptions,
|
||||
#[serde(flatten)]
|
||||
pub type_hierarchy_options: TypeHierarchyOptions,
|
||||
#[serde(flatten)]
|
||||
pub static_registration_options: StaticRegistrationOptions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
pub struct TypeHierarchyPrepareParams {
|
||||
#[serde(flatten)]
|
||||
pub text_document_position_params: TextDocumentPositionParams,
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
pub struct TypeHierarchySupertypesParams {
|
||||
pub item: TypeHierarchyItem,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
#[serde(flatten)]
|
||||
pub partial_result_params: PartialResultParams,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
pub struct TypeHierarchySubtypesParams {
|
||||
pub item: TypeHierarchyItem,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
#[serde(flatten)]
|
||||
pub partial_result_params: PartialResultParams,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TypeHierarchyItem {
|
||||
/// The name of this item.
|
||||
pub name: String,
|
||||
|
||||
/// The kind of this item.
|
||||
pub kind: SymbolKind,
|
||||
|
||||
/// Tags for this item.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tags: Option<SymbolTag>,
|
||||
|
||||
/// More detail for this item, e.g. the signature of a function.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub detail: Option<String>,
|
||||
|
||||
/// The resource identifier of this item.
|
||||
pub uri: Url,
|
||||
|
||||
/// The range enclosing this symbol not including leading/trailing whitespace
|
||||
/// but everything else, e.g. comments and code.
|
||||
pub range: Range,
|
||||
|
||||
/// The range that should be selected and revealed when this symbol is being
|
||||
/// picked, e.g. the name of a function. Must be contained by the
|
||||
/// [`range`](#TypeHierarchyItem.range).
|
||||
pub selection_range: Range,
|
||||
|
||||
/// A data entry field that is preserved between a type hierarchy prepare and
|
||||
/// supertypes or subtypes requests. It could also be used to identify the
|
||||
/// type hierarchy in the server, helping improve the performance on
|
||||
/// resolving supertypes and subtypes.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub data: Option<LSPAny>,
|
||||
}
|
173
helix-lsp-types/src/window.rs
Normal file
173
helix-lsp-types/src/window.rs
Normal file
@ -0,0 +1,173 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
use url::Url;
|
||||
|
||||
use crate::Range;
|
||||
|
||||
#[derive(Eq, PartialEq, Clone, Copy, Deserialize, Serialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct MessageType(i32);
|
||||
lsp_enum! {
|
||||
impl MessageType {
|
||||
/// An error message.
|
||||
pub const ERROR: MessageType = MessageType(1);
|
||||
/// A warning message.
|
||||
pub const WARNING: MessageType = MessageType(2);
|
||||
/// An information message;
|
||||
pub const INFO: MessageType = MessageType(3);
|
||||
/// A log message.
|
||||
pub const LOG: MessageType = MessageType(4);
|
||||
}
|
||||
}
|
||||
|
||||
/// Window specific client capabilities.
|
||||
#[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WindowClientCapabilities {
|
||||
/// Whether client supports handling progress notifications. If set
|
||||
/// servers are allowed to report in `workDoneProgress` property in the
|
||||
/// request specific server capabilities.
|
||||
///
|
||||
/// @since 3.15.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub work_done_progress: Option<bool>,
|
||||
|
||||
/// Capabilities specific to the showMessage request.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub show_message: Option<ShowMessageRequestClientCapabilities>,
|
||||
|
||||
/// Client capabilities for the show document request.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub show_document: Option<ShowDocumentClientCapabilities>,
|
||||
}
|
||||
|
||||
/// Show message request client capabilities
|
||||
#[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ShowMessageRequestClientCapabilities {
|
||||
/// Capabilities specific to the `MessageActionItem` type.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub message_action_item: Option<MessageActionItemCapabilities>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MessageActionItemCapabilities {
|
||||
/// Whether the client supports additional attributes which
|
||||
/// are preserved and send back to the server in the
|
||||
/// request's response.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub additional_properties_support: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MessageActionItem {
|
||||
/// A short title like 'Retry', 'Open Log' etc.
|
||||
pub title: String,
|
||||
|
||||
/// Additional attributes that the client preserves and
|
||||
/// sends back to the server. This depends on the client
|
||||
/// capability window.messageActionItem.additionalPropertiesSupport
|
||||
#[serde(flatten)]
|
||||
pub properties: HashMap<String, MessageActionItemProperty>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum MessageActionItemProperty {
|
||||
String(String),
|
||||
Boolean(bool),
|
||||
Integer(i32),
|
||||
Object(Value),
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
pub struct LogMessageParams {
|
||||
/// The message type. See {@link MessageType}
|
||||
#[serde(rename = "type")]
|
||||
pub typ: MessageType,
|
||||
|
||||
/// The actual message
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
pub struct ShowMessageParams {
|
||||
/// The message type. See {@link MessageType}.
|
||||
#[serde(rename = "type")]
|
||||
pub typ: MessageType,
|
||||
|
||||
/// The actual message.
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
pub struct ShowMessageRequestParams {
|
||||
/// The message type. See {@link MessageType}
|
||||
#[serde(rename = "type")]
|
||||
pub typ: MessageType,
|
||||
|
||||
/// The actual message
|
||||
pub message: String,
|
||||
|
||||
/// The message action items to present.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub actions: Option<Vec<MessageActionItem>>,
|
||||
}
|
||||
|
||||
/// Client capabilities for the show document request.
|
||||
#[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ShowDocumentClientCapabilities {
|
||||
/// The client has support for the show document request.
|
||||
pub support: bool,
|
||||
}
|
||||
|
||||
/// Params to show a document.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ShowDocumentParams {
|
||||
/// The document uri to show.
|
||||
pub uri: Url,
|
||||
|
||||
/// Indicates to show the resource in an external program.
|
||||
/// To show for example `https://code.visualstudio.com/`
|
||||
/// in the default WEB browser set `external` to `true`.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub external: Option<bool>,
|
||||
|
||||
/// An optional property to indicate whether the editor
|
||||
/// showing the document should take focus or not.
|
||||
/// Clients might ignore this property if an external
|
||||
/// program in started.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub take_focus: Option<bool>,
|
||||
|
||||
/// An optional selection range if the document is a text
|
||||
/// document. Clients might ignore the property if an
|
||||
/// external program is started or the file is not a text
|
||||
/// file.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub selection: Option<Range>,
|
||||
}
|
||||
|
||||
/// The result of an show document request.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ShowDocumentResult {
|
||||
/// A boolean indicating if the show was successful.
|
||||
pub success: bool,
|
||||
}
|
149
helix-lsp-types/src/workspace_diagnostic.rs
Normal file
149
helix-lsp-types/src/workspace_diagnostic.rs
Normal file
@ -0,0 +1,149 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
FullDocumentDiagnosticReport, PartialResultParams, UnchangedDocumentDiagnosticReport,
|
||||
WorkDoneProgressParams,
|
||||
};
|
||||
|
||||
/// Workspace client capabilities specific to diagnostic pull requests.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DiagnosticWorkspaceClientCapabilities {
|
||||
/// Whether the client implementation supports a refresh request sent from
|
||||
/// the server to the client.
|
||||
///
|
||||
/// Note that this event is global and will force the client to refresh all
|
||||
/// pulled diagnostics currently shown. It should be used with absolute care
|
||||
/// and is useful for situation where a server for example detects a project
|
||||
/// wide change that requires such a calculation.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub refresh_support: Option<bool>,
|
||||
}
|
||||
|
||||
/// A previous result ID in a workspace pull request.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
pub struct PreviousResultId {
|
||||
/// The URI for which the client knows a result ID.
|
||||
pub uri: Url,
|
||||
|
||||
/// The value of the previous result ID.
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
/// Parameters of the workspace diagnostic request.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WorkspaceDiagnosticParams {
|
||||
/// The additional identifier provided during registration.
|
||||
pub identifier: Option<String>,
|
||||
|
||||
/// The currently known diagnostic reports with their
|
||||
/// previous result ids.
|
||||
pub previous_result_ids: Vec<PreviousResultId>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub partial_result_params: PartialResultParams,
|
||||
}
|
||||
|
||||
/// A full document diagnostic report for a workspace diagnostic result.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WorkspaceFullDocumentDiagnosticReport {
|
||||
/// The URI for which diagnostic information is reported.
|
||||
pub uri: Url,
|
||||
|
||||
/// The version number for which the diagnostics are reported.
|
||||
///
|
||||
/// If the document is not marked as open, `None` can be provided.
|
||||
pub version: Option<i64>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub full_document_diagnostic_report: FullDocumentDiagnosticReport,
|
||||
}
|
||||
|
||||
/// An unchanged document diagnostic report for a workspace diagnostic result.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WorkspaceUnchangedDocumentDiagnosticReport {
|
||||
/// The URI for which diagnostic information is reported.
|
||||
pub uri: Url,
|
||||
|
||||
/// The version number for which the diagnostics are reported.
|
||||
///
|
||||
/// If the document is not marked as open, `None` can be provided.
|
||||
pub version: Option<i64>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub unchanged_document_diagnostic_report: UnchangedDocumentDiagnosticReport,
|
||||
}
|
||||
|
||||
/// A workspace diagnostic document report.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
|
||||
#[serde(tag = "kind", rename_all = "lowercase")]
|
||||
pub enum WorkspaceDocumentDiagnosticReport {
|
||||
Full(WorkspaceFullDocumentDiagnosticReport),
|
||||
Unchanged(WorkspaceUnchangedDocumentDiagnosticReport),
|
||||
}
|
||||
|
||||
impl From<WorkspaceFullDocumentDiagnosticReport> for WorkspaceDocumentDiagnosticReport {
|
||||
fn from(from: WorkspaceFullDocumentDiagnosticReport) -> Self {
|
||||
WorkspaceDocumentDiagnosticReport::Full(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WorkspaceUnchangedDocumentDiagnosticReport> for WorkspaceDocumentDiagnosticReport {
|
||||
fn from(from: WorkspaceUnchangedDocumentDiagnosticReport) -> Self {
|
||||
WorkspaceDocumentDiagnosticReport::Unchanged(from)
|
||||
}
|
||||
}
|
||||
|
||||
/// A workspace diagnostic report.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, PartialEq, Default, Deserialize, Serialize, Clone)]
|
||||
pub struct WorkspaceDiagnosticReport {
|
||||
pub items: Vec<WorkspaceDocumentDiagnosticReport>,
|
||||
}
|
||||
|
||||
/// A partial result for a workspace diagnostic report.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, PartialEq, Default, Deserialize, Serialize, Clone)]
|
||||
pub struct WorkspaceDiagnosticReportPartialResult {
|
||||
pub items: Vec<WorkspaceDocumentDiagnosticReport>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone)]
|
||||
#[serde(untagged)]
|
||||
pub enum WorkspaceDiagnosticReportResult {
|
||||
Report(WorkspaceDiagnosticReport),
|
||||
Partial(WorkspaceDiagnosticReportPartialResult),
|
||||
}
|
||||
|
||||
impl From<WorkspaceDiagnosticReport> for WorkspaceDiagnosticReportResult {
|
||||
fn from(from: WorkspaceDiagnosticReport) -> Self {
|
||||
WorkspaceDiagnosticReportResult::Report(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WorkspaceDiagnosticReportPartialResult> for WorkspaceDiagnosticReportResult {
|
||||
fn from(from: WorkspaceDiagnosticReportPartialResult) -> Self {
|
||||
WorkspaceDiagnosticReportResult::Partial(from)
|
||||
}
|
||||
}
|
49
helix-lsp-types/src/workspace_folders.rs
Normal file
49
helix-lsp-types/src/workspace_folders.rs
Normal file
@ -0,0 +1,49 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
use crate::OneOf;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WorkspaceFoldersServerCapabilities {
|
||||
/// The server has support for workspace folders
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub supported: Option<bool>,
|
||||
|
||||
/// Whether the server wants to receive workspace folder
|
||||
/// change notifications.
|
||||
///
|
||||
/// If a string is provided, the string is treated as an ID
|
||||
/// under which the notification is registered on the client
|
||||
/// side. The ID can be used to unregister for these events
|
||||
/// using the `client/unregisterCapability` request.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub change_notifications: Option<OneOf<bool, String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WorkspaceFolder {
|
||||
/// The associated URI for this workspace folder.
|
||||
pub uri: Url,
|
||||
/// The name of the workspace folder. Defaults to the uri's basename.
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DidChangeWorkspaceFoldersParams {
|
||||
/// The actual workspace folder change event.
|
||||
pub event: WorkspaceFoldersChangeEvent,
|
||||
}
|
||||
|
||||
/// The workspace folder change event.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WorkspaceFoldersChangeEvent {
|
||||
/// The array of added workspace folders
|
||||
pub added: Vec<WorkspaceFolder>,
|
||||
|
||||
/// The array of the removed workspace folders
|
||||
pub removed: Vec<WorkspaceFolder>,
|
||||
}
|
105
helix-lsp-types/src/workspace_symbols.rs
Normal file
105
helix-lsp-types/src/workspace_symbols.rs
Normal file
@ -0,0 +1,105 @@
|
||||
use crate::{
|
||||
LSPAny, Location, OneOf, PartialResultParams, SymbolInformation, SymbolKind,
|
||||
SymbolKindCapability, SymbolTag, TagSupport, Url, WorkDoneProgressParams,
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WorkspaceSymbolClientCapabilities {
|
||||
/// This capability supports dynamic registration.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub dynamic_registration: Option<bool>,
|
||||
|
||||
/// Specific capabilities for the `SymbolKind` in the `workspace/symbol` request.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub symbol_kind: Option<SymbolKindCapability>,
|
||||
|
||||
/// The client supports tags on `SymbolInformation`.
|
||||
/// Clients supporting tags have to handle unknown tags gracefully.
|
||||
///
|
||||
/// @since 3.16.0
|
||||
#[serde(
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none",
|
||||
deserialize_with = "TagSupport::deserialize_compat"
|
||||
)]
|
||||
pub tag_support: Option<TagSupport<SymbolTag>>,
|
||||
|
||||
/// The client support partial workspace symbols. The client will send the
|
||||
/// request `workspaceSymbol/resolve` to the server to resolve additional
|
||||
/// properties.
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub resolve_support: Option<WorkspaceSymbolResolveSupportCapability>,
|
||||
}
|
||||
|
||||
/// The parameters of a Workspace Symbol Request.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
pub struct WorkspaceSymbolParams {
|
||||
#[serde(flatten)]
|
||||
pub partial_result_params: PartialResultParams,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub work_done_progress_params: WorkDoneProgressParams,
|
||||
|
||||
/// A non-empty query string
|
||||
pub query: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
|
||||
pub struct WorkspaceSymbolResolveSupportCapability {
|
||||
/// The properties that a client can resolve lazily. Usually
|
||||
/// `location.range`
|
||||
pub properties: Vec<String>,
|
||||
}
|
||||
|
||||
/// A special workspace symbol that supports locations without a range
|
||||
///
|
||||
/// @since 3.17.0
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WorkspaceSymbol {
|
||||
/// The name of this symbol.
|
||||
pub name: String,
|
||||
|
||||
/// The kind of this symbol.
|
||||
pub kind: SymbolKind,
|
||||
|
||||
/// Tags for this completion item.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tags: Option<Vec<SymbolTag>>,
|
||||
|
||||
/// The name of the symbol containing this symbol. This information is for
|
||||
/// user interface purposes (e.g. to render a qualifier in the user interface
|
||||
/// if necessary). It can't be used to re-infer a hierarchy for the document
|
||||
/// symbols.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub container_name: Option<String>,
|
||||
|
||||
/// The location of this symbol. Whether a server is allowed to
|
||||
/// return a location without a range depends on the client
|
||||
/// capability `workspace.symbol.resolveSupport`.
|
||||
///
|
||||
/// See also `SymbolInformation.location`.
|
||||
pub location: OneOf<Location, WorkspaceLocation>,
|
||||
|
||||
/// A data entry field that is preserved on a workspace symbol between a
|
||||
/// workspace symbol request and a workspace symbol resolve request.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub data: Option<LSPAny>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
pub struct WorkspaceLocation {
|
||||
pub uri: Url,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum WorkspaceSymbolResponse {
|
||||
Flat(Vec<SymbolInformation>),
|
||||
Nested(Vec<WorkspaceSymbol>),
|
||||
}
|
@ -17,16 +17,16 @@ helix-stdx = { path = "../helix-stdx" }
|
||||
helix-core = { path = "../helix-core" }
|
||||
helix-loader = { path = "../helix-loader" }
|
||||
helix-parsec = { path = "../helix-parsec" }
|
||||
helix-lsp-types = { path = "../helix-lsp-types" }
|
||||
|
||||
anyhow = "1.0"
|
||||
futures-executor = "0.3"
|
||||
futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
|
||||
globset = "0.4.14"
|
||||
globset = "0.4.15"
|
||||
log = "0.4"
|
||||
lsp-types = { version = "0.95" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
tokio = { version = "1.38", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] }
|
||||
tokio = { version = "1.40", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] }
|
||||
tokio-stream = "0.1.15"
|
||||
parking_lot = "0.12.3"
|
||||
arc-swap = "1"
|
||||
|
@ -5,15 +5,14 @@
|
||||
Call, Error, LanguageServerId, OffsetEncoding, Result,
|
||||
};
|
||||
|
||||
use helix_core::{find_workspace, syntax::LanguageServerFeature, ChangeSet, Rope};
|
||||
use helix_loader::VERSION_AND_GIT_HASH;
|
||||
use helix_stdx::path;
|
||||
use lsp::{
|
||||
notification::DidChangeWorkspaceFolders, CodeActionCapabilityResolveSupport,
|
||||
use crate::lsp::{
|
||||
self, notification::DidChangeWorkspaceFolders, CodeActionCapabilityResolveSupport,
|
||||
DidChangeWorkspaceFoldersParams, OneOf, PositionEncodingKind, SignatureHelp, Url,
|
||||
WorkspaceFolder, WorkspaceFoldersChangeEvent,
|
||||
};
|
||||
use lsp_types as lsp;
|
||||
use helix_core::{find_workspace, syntax::LanguageServerFeature, ChangeSet, Rope};
|
||||
use helix_loader::VERSION_AND_GIT_HASH;
|
||||
use helix_stdx::path;
|
||||
use parking_lot::Mutex;
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
@ -994,7 +993,7 @@ pub fn text_document_did_save(
|
||||
..
|
||||
}) => match options.as_ref()? {
|
||||
lsp::TextDocumentSyncSaveOptions::Supported(true) => false,
|
||||
lsp::TextDocumentSyncSaveOptions::SaveOptions(lsp_types::SaveOptions {
|
||||
lsp::TextDocumentSyncSaveOptions::SaveOptions(lsp::SaveOptions {
|
||||
include_text,
|
||||
}) => include_text.unwrap_or(false),
|
||||
lsp::TextDocumentSyncSaveOptions::Supported(false) => return None,
|
||||
|
@ -8,9 +8,9 @@
|
||||
use arc_swap::ArcSwap;
|
||||
pub use client::Client;
|
||||
pub use futures_executor::block_on;
|
||||
pub use helix_lsp_types as lsp;
|
||||
pub use jsonrpc::Call;
|
||||
pub use lsp::{Position, Url};
|
||||
pub use lsp_types as lsp;
|
||||
|
||||
use futures_util::stream::select_all::SelectAll;
|
||||
use helix_core::syntax::{
|
||||
@ -284,10 +284,8 @@ fn find_completion_range(text: RopeSlice, replace_mode: bool, cursor: usize) ->
|
||||
if replace_mode {
|
||||
end += text
|
||||
.chars_at(cursor)
|
||||
.skip(1)
|
||||
.take_while(|ch| chars::char_is_word(*ch))
|
||||
.count()
|
||||
+ 1;
|
||||
.count();
|
||||
}
|
||||
(start, end)
|
||||
}
|
||||
@ -505,7 +503,7 @@ pub fn generate_transaction_from_edits(
|
||||
) -> Transaction {
|
||||
// Sort edits by start range, since some LSPs (Omnisharp) send them
|
||||
// in reverse order.
|
||||
edits.sort_unstable_by_key(|edit| edit.range.start);
|
||||
edits.sort_by_key(|edit| edit.range.start);
|
||||
|
||||
// Generate a diff if the edit is a full document replacement.
|
||||
#[allow(clippy::collapsible_if)]
|
||||
@ -677,7 +675,7 @@ pub fn get_by_id(&self, id: LanguageServerId) -> Option<&Arc<Client>> {
|
||||
|
||||
pub fn remove_by_id(&mut self, id: LanguageServerId) {
|
||||
let Some(client) = self.inner.remove(id) else {
|
||||
log::error!("client was already removed");
|
||||
log::debug!("client was already removed");
|
||||
return;
|
||||
};
|
||||
self.file_event_handler.remove_client(id);
|
||||
@ -737,6 +735,11 @@ pub fn restart(
|
||||
.iter()
|
||||
.filter_map(|LanguageServerFeatures { name, .. }| {
|
||||
if let Some(old_clients) = self.inner_by_name.remove(name) {
|
||||
if old_clients.is_empty() {
|
||||
log::info!("restarting client for '{name}' which was manually stopped");
|
||||
} else {
|
||||
log::info!("stopping existing clients for '{name}'");
|
||||
}
|
||||
for old_client in old_clients {
|
||||
self.file_event_handler.remove_client(old_client.id());
|
||||
self.inner.remove(old_client.id());
|
||||
@ -765,8 +768,13 @@ pub fn restart(
|
||||
}
|
||||
|
||||
pub fn stop(&mut self, name: &str) {
|
||||
if let Some(clients) = self.inner_by_name.remove(name) {
|
||||
for client in clients {
|
||||
if let Some(clients) = self.inner_by_name.get_mut(name) {
|
||||
// Drain the clients vec so that the entry in `inner_by_name` remains
|
||||
// empty. We use the empty vec as a "tombstone" to mean that a server
|
||||
// has been manually stopped with :lsp-stop and shouldn't be automatically
|
||||
// restarted by `get`. :lsp-restart can be used to restart the server
|
||||
// manually.
|
||||
for client in clients.drain(..) {
|
||||
self.file_event_handler.remove_client(client.id());
|
||||
self.inner.remove(client.id());
|
||||
tokio::spawn(async move {
|
||||
@ -786,6 +794,14 @@ pub fn get<'a>(
|
||||
language_config.language_servers.iter().filter_map(
|
||||
move |LanguageServerFeatures { name, .. }| {
|
||||
if let Some(clients) = self.inner_by_name.get(name) {
|
||||
// If the clients vec is empty, do not automatically start a client
|
||||
// for this server. The empty vec is a tombstone left to mean that a
|
||||
// server has been manually stopped and shouldn't be started automatically.
|
||||
// See `stop`.
|
||||
if clients.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some((_, client)) = clients.iter().enumerate().find(|(i, client)| {
|
||||
client.try_add_doc(&language_config.roots, root_dirs, doc_path, *i == 0)
|
||||
}) {
|
||||
@ -1097,7 +1113,7 @@ macro_rules! test_case {
|
||||
|
||||
#[test]
|
||||
fn emoji_format_gh_4791() {
|
||||
use lsp_types::{Position, Range, TextEdit};
|
||||
use lsp::{Position, Range, TextEdit};
|
||||
|
||||
let edits = vec![
|
||||
TextEdit {
|
||||
|
@ -1,4 +1,8 @@
|
||||
use crate::{jsonrpc, Error, LanguageServerId, Result};
|
||||
use crate::{
|
||||
jsonrpc,
|
||||
lsp::{self, notification::Notification as _},
|
||||
Error, LanguageServerId, Result,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use log::{error, info};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -289,11 +293,10 @@ async fn recv(
|
||||
}
|
||||
|
||||
// Hack: inject a terminated notification so we trigger code that needs to happen after exit
|
||||
use lsp_types::notification::Notification as _;
|
||||
let notification =
|
||||
ServerMessage::Call(jsonrpc::Call::Notification(jsonrpc::Notification {
|
||||
jsonrpc: None,
|
||||
method: lsp_types::notification::Exit::METHOD.to_string(),
|
||||
method: lsp::notification::Exit::METHOD.to_string(),
|
||||
params: jsonrpc::Params::None,
|
||||
}));
|
||||
match transport
|
||||
@ -338,8 +341,8 @@ async fn send(
|
||||
|
||||
// Determine if a message is allowed to be sent early
|
||||
fn is_initialize(payload: &Payload) -> bool {
|
||||
use lsp_types::{
|
||||
notification::{Initialized, Notification},
|
||||
use lsp::{
|
||||
notification::Initialized,
|
||||
request::{Initialize, Request},
|
||||
};
|
||||
match payload {
|
||||
@ -357,7 +360,7 @@ fn is_initialize(payload: &Payload) -> bool {
|
||||
}
|
||||
|
||||
fn is_shutdown(payload: &Payload) -> bool {
|
||||
use lsp_types::request::{Request, Shutdown};
|
||||
use lsp::request::{Request, Shutdown};
|
||||
matches!(payload, Payload::Request { value: jsonrpc::MethodCall { method, .. }, .. } if method == Shutdown::METHOD)
|
||||
}
|
||||
|
||||
@ -370,12 +373,11 @@ fn is_shutdown(payload: &Payload) -> bool {
|
||||
// server successfully initialized
|
||||
is_pending = false;
|
||||
|
||||
use lsp_types::notification::Notification;
|
||||
// Hack: inject an initialized notification so we trigger code that needs to happen after init
|
||||
let notification = ServerMessage::Call(jsonrpc::Call::Notification(jsonrpc::Notification {
|
||||
jsonrpc: None,
|
||||
|
||||
method: lsp_types::notification::Initialized::METHOD.to_string(),
|
||||
method: lsp::notification::Initialized::METHOD.to_string(),
|
||||
params: jsonrpc::Params::None,
|
||||
}));
|
||||
let language_server_name = &transport.name;
|
||||
|
@ -20,10 +20,10 @@ regex-cursor = "0.1.4"
|
||||
bitflags = "2.6"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows-sys = { version = "0.52", features = ["Win32_Security", "Win32_Security_Authorization", "Win32_System_Threading"] }
|
||||
windows-sys = { version = "0.59", features = ["Win32_Foundation", "Win32_Security", "Win32_Security_Authorization", "Win32_Storage_FileSystem", "Win32_System_Threading"] }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
rustix = { version = "0.38", features = ["fs"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.10"
|
||||
tempfile = "3.13"
|
||||
|
@ -74,13 +74,18 @@ pub fn copy_metadata(from: &Path, to: &Path) -> io::Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn hardlink_count(p: &Path) -> std::io::Result<u64> {
|
||||
let metadata = p.metadata()?;
|
||||
Ok(metadata.nlink())
|
||||
}
|
||||
}
|
||||
|
||||
// Licensed under MIT from faccess except for `chown`, `copy_metadata` and `is_acl_inherited`
|
||||
#[cfg(windows)]
|
||||
mod imp {
|
||||
|
||||
use windows_sys::Win32::Foundation::{CloseHandle, LocalFree, ERROR_SUCCESS, HANDLE, PSID};
|
||||
use windows_sys::Win32::Foundation::{CloseHandle, LocalFree, ERROR_SUCCESS, HANDLE};
|
||||
use windows_sys::Win32::Security::Authorization::{
|
||||
GetNamedSecurityInfoW, SetNamedSecurityInfoW, SE_FILE_OBJECT,
|
||||
};
|
||||
@ -90,12 +95,12 @@ mod imp {
|
||||
SecurityImpersonation, ACCESS_ALLOWED_CALLBACK_ACE, ACL, ACL_SIZE_INFORMATION,
|
||||
DACL_SECURITY_INFORMATION, GENERIC_MAPPING, GROUP_SECURITY_INFORMATION, INHERITED_ACE,
|
||||
LABEL_SECURITY_INFORMATION, OBJECT_SECURITY_INFORMATION, OWNER_SECURITY_INFORMATION,
|
||||
PRIVILEGE_SET, PROTECTED_DACL_SECURITY_INFORMATION, PSECURITY_DESCRIPTOR,
|
||||
PRIVILEGE_SET, PROTECTED_DACL_SECURITY_INFORMATION, PSECURITY_DESCRIPTOR, PSID,
|
||||
SID_IDENTIFIER_AUTHORITY, TOKEN_DUPLICATE, TOKEN_QUERY,
|
||||
};
|
||||
use windows_sys::Win32::Storage::FileSystem::{
|
||||
FILE_ACCESS_RIGHTS, FILE_ALL_ACCESS, FILE_GENERIC_EXECUTE, FILE_GENERIC_READ,
|
||||
FILE_GENERIC_WRITE,
|
||||
GetFileInformationByHandle, BY_HANDLE_FILE_INFORMATION, FILE_ACCESS_RIGHTS,
|
||||
FILE_ALL_ACCESS, FILE_GENERIC_EXECUTE, FILE_GENERIC_READ, FILE_GENERIC_WRITE,
|
||||
};
|
||||
use windows_sys::Win32::System::Threading::{GetCurrentThread, OpenThreadToken};
|
||||
|
||||
@ -103,7 +108,7 @@ mod imp {
|
||||
|
||||
use std::ffi::c_void;
|
||||
|
||||
use std::os::windows::{ffi::OsStrExt, fs::OpenOptionsExt};
|
||||
use std::os::windows::{ffi::OsStrExt, fs::OpenOptionsExt, io::AsRawHandle};
|
||||
|
||||
struct SecurityDescriptor {
|
||||
sd: PSECURITY_DESCRIPTOR,
|
||||
@ -290,21 +295,21 @@ fn eaccess(p: &Path, mut mode: FILE_ACCESS_RIGHTS) -> io::Result<()> {
|
||||
let mut privileges_length = std::mem::size_of::<PRIVILEGE_SET>() as u32;
|
||||
let mut result = 0;
|
||||
|
||||
let mut mapping = GENERIC_MAPPING {
|
||||
let mapping = GENERIC_MAPPING {
|
||||
GenericRead: FILE_GENERIC_READ,
|
||||
GenericWrite: FILE_GENERIC_WRITE,
|
||||
GenericExecute: FILE_GENERIC_EXECUTE,
|
||||
GenericAll: FILE_ALL_ACCESS,
|
||||
};
|
||||
|
||||
unsafe { MapGenericMask(&mut mode, &mut mapping) };
|
||||
unsafe { MapGenericMask(&mut mode, &mapping) };
|
||||
|
||||
if unsafe {
|
||||
AccessCheck(
|
||||
*sd.descriptor(),
|
||||
*token.as_handle(),
|
||||
mode,
|
||||
&mut mapping,
|
||||
&mapping,
|
||||
&mut privileges,
|
||||
&mut privileges_length,
|
||||
&mut granted_access,
|
||||
@ -411,6 +416,18 @@ pub fn copy_metadata(from: &Path, to: &Path) -> io::Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn hardlink_count(p: &Path) -> std::io::Result<u64> {
|
||||
let file = std::fs::File::open(p)?;
|
||||
let handle = file.as_raw_handle();
|
||||
let mut info: BY_HANDLE_FILE_INFORMATION = unsafe { std::mem::zeroed() };
|
||||
|
||||
if unsafe { GetFileInformationByHandle(handle, &mut info) } == 0 {
|
||||
Err(std::io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(info.nNumberOfLinks as u64)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Licensed under MIT from faccess except for `copy_metadata`
|
||||
@ -457,3 +474,7 @@ pub fn readonly(p: &Path) -> bool {
|
||||
pub fn copy_metadata(from: &Path, to: &Path) -> io::Result<()> {
|
||||
imp::copy_metadata(from, to)
|
||||
}
|
||||
|
||||
pub fn hardlink_count(p: &Path) -> io::Result<u64> {
|
||||
imp::hardlink_count(p)
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ fn starts_with(self, text: &str) -> bool {
|
||||
if len < text.len() {
|
||||
return false;
|
||||
}
|
||||
self.get_byte_slice(..len - text.len())
|
||||
self.get_byte_slice(..text.len())
|
||||
.map_or(false, |start| start == text)
|
||||
}
|
||||
|
||||
@ -137,4 +137,14 @@ fn next_char_at_byte() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn starts_with() {
|
||||
assert!(RopeSlice::from("asdf").starts_with("a"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ends_with() {
|
||||
assert!(RopeSlice::from("asdf").ends_with("f"));
|
||||
}
|
||||
}
|
||||
|
@ -34,11 +34,11 @@ helix-vcs = { path = "../helix-vcs" }
|
||||
helix-loader = { path = "../helix-loader" }
|
||||
|
||||
anyhow = "1"
|
||||
once_cell = "1.19"
|
||||
once_cell = "1.20"
|
||||
|
||||
tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
|
||||
tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] }
|
||||
crossterm = { version = "0.27", features = ["event-stream"] }
|
||||
crossterm = { version = "0.28", features = ["event-stream"] }
|
||||
signal-hook = "0.3"
|
||||
tokio-stream = "0.1"
|
||||
futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
|
||||
@ -54,13 +54,13 @@ log = "0.4"
|
||||
nucleo.workspace = true
|
||||
ignore = "0.4"
|
||||
# markdown doc rendering
|
||||
pulldown-cmark = { version = "0.11", default-features = false }
|
||||
pulldown-cmark = { version = "0.12", default-features = false }
|
||||
# file type detection
|
||||
content_inspector = "0.2.4"
|
||||
thiserror = "1.0"
|
||||
|
||||
# opening URLs
|
||||
open = "5.2.0"
|
||||
open = "5.3.0"
|
||||
url = "2.5.2"
|
||||
|
||||
# config
|
||||
@ -70,8 +70,8 @@ serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
# ripgrep for global search
|
||||
grep-regex = "0.1.12"
|
||||
grep-searcher = "0.1.13"
|
||||
grep-regex = "0.1.13"
|
||||
grep-searcher = "0.1.14"
|
||||
|
||||
# plugin support
|
||||
steel-core = { workspace = true, optional = true }
|
||||
@ -79,10 +79,10 @@ steel-doc = { git = "https://github.com/mattwparas/steel.git", version = "0.6.0"
|
||||
|
||||
[target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100
|
||||
signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] }
|
||||
libc = "0.2.155"
|
||||
libc = "0.2.159"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
crossterm = { version = "0.27", features = ["event-stream", "use-dev-tty"] }
|
||||
crossterm = { version = "0.28", features = ["event-stream", "use-dev-tty", "libc"] }
|
||||
|
||||
[build-dependencies]
|
||||
helix-loader = { path = "../helix-loader" }
|
||||
@ -90,4 +90,5 @@ helix-loader = { path = "../helix-loader" }
|
||||
[dev-dependencies]
|
||||
smallvec = "1.13"
|
||||
indoc = "2.0.5"
|
||||
tempfile = "3.10.1"
|
||||
tempfile = "3.13.0"
|
||||
same-file = "1.0.1"
|
||||
|
@ -66,18 +66,16 @@ fn find_rc_exe() -> io::Result<PathBuf> {
|
||||
.output();
|
||||
|
||||
match find_reg_key {
|
||||
Err(find_reg_key) => {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("Failed to run registry query: {}", find_reg_key),
|
||||
))
|
||||
}
|
||||
Err(find_reg_key) => Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("Failed to run registry query: {}", find_reg_key),
|
||||
)),
|
||||
Ok(find_reg_key) => {
|
||||
if find_reg_key.status.code().unwrap() != 0 {
|
||||
return Err(io::Error::new(
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Can not find Windows SDK",
|
||||
));
|
||||
))
|
||||
} else {
|
||||
let lines = String::from_utf8(find_reg_key.stdout)
|
||||
.expect("Should be able to parse the output");
|
||||
|
@ -35,7 +35,9 @@
|
||||
use std::io::stdout;
|
||||
use std::{collections::btree_map::Entry, io::stdin, path::Path, sync::Arc};
|
||||
|
||||
use anyhow::{Context, Error};
|
||||
#[cfg(not(windows))]
|
||||
use anyhow::Context;
|
||||
use anyhow::Error;
|
||||
|
||||
use crossterm::{event::Event as CrosstermEvent, tty::IsTty};
|
||||
#[cfg(not(windows))]
|
||||
@ -416,9 +418,9 @@ pub fn handle_config_events(&mut self, config_event: ConfigEvent) {
|
||||
|
||||
// reset view position in case softwrap was enabled/disabled
|
||||
let scrolloff = self.editor.config().scrolloff;
|
||||
for (view, _) in self.editor.tree.views_mut() {
|
||||
let doc = &self.editor.documents[&view.doc];
|
||||
view.ensure_cursor_in_view(doc, scrolloff)
|
||||
for (view, _) in self.editor.tree.views() {
|
||||
let doc = doc_mut!(self.editor, &view.doc);
|
||||
view.ensure_cursor_in_view(doc, scrolloff);
|
||||
}
|
||||
}
|
||||
|
||||
@ -596,7 +598,7 @@ pub fn handle_document_write(&mut self, doc_save_event: DocumentSavedEventResult
|
||||
doc_save_event.revision
|
||||
);
|
||||
|
||||
doc.set_last_saved_revision(doc_save_event.revision);
|
||||
doc.set_last_saved_revision(doc_save_event.revision, doc_save_event.save_time);
|
||||
|
||||
let lines = doc_save_event.text.len_lines();
|
||||
let bytes = doc_save_event.text.len_bytes();
|
||||
@ -865,7 +867,15 @@ macro_rules! language_server {
|
||||
}
|
||||
}
|
||||
Notification::ShowMessage(params) => {
|
||||
log::warn!("unhandled window/showMessage: {:?}", params);
|
||||
if self.config.load().editor.lsp.display_messages {
|
||||
match params.typ {
|
||||
lsp::MessageType::ERROR => self.editor.set_error(params.message),
|
||||
lsp::MessageType::WARNING => {
|
||||
self.editor.set_warning(params.message)
|
||||
}
|
||||
_ => self.editor.set_status(params.message),
|
||||
}
|
||||
}
|
||||
}
|
||||
Notification::LogMessage(params) => {
|
||||
log::info!("window/logMessage: {:?}", params);
|
||||
@ -949,7 +959,7 @@ macro_rules! language_server {
|
||||
self.lsp_progress.update(server_id, token, work);
|
||||
}
|
||||
|
||||
if self.config.load().editor.lsp.display_messages {
|
||||
if self.config.load().editor.lsp.display_progress_messages {
|
||||
self.editor.set_status(status);
|
||||
}
|
||||
}
|
||||
|
@ -186,9 +186,16 @@ fn make_job_callback<T, F>(
|
||||
|
||||
use helix_view::{align_view, Align};
|
||||
|
||||
/// A MappableCommand is either a static command like "jump_view_up" or a Typable command like
|
||||
/// :format. It causes a side-effect on the state (usually by creating and applying a transaction).
|
||||
/// Both of these types of commands can be mapped with keybindings in the config.toml.
|
||||
/// MappableCommands are commands that can be bound to keys, executable in
|
||||
/// normal, insert or select mode.
|
||||
///
|
||||
/// There are three kinds:
|
||||
///
|
||||
/// * Static: commands usually bound to keys and used for editing, movement,
|
||||
/// etc., for example `move_char_left`.
|
||||
/// * Typable: commands executable from command mode, prefixed with a `:`,
|
||||
/// for example `:write!`.
|
||||
/// * Macro: a sequence of keys to execute, for example `@miw`.
|
||||
#[derive(Clone)]
|
||||
pub enum MappableCommand {
|
||||
Typable {
|
||||
@ -201,6 +208,10 @@ pub enum MappableCommand {
|
||||
fun: fn(cx: &mut Context),
|
||||
doc: &'static str,
|
||||
},
|
||||
Macro {
|
||||
name: String,
|
||||
keys: Vec<KeyEvent>,
|
||||
},
|
||||
}
|
||||
|
||||
macro_rules! static_commands {
|
||||
@ -240,6 +251,23 @@ pub fn execute(&self, cx: &mut Context) {
|
||||
}
|
||||
}
|
||||
Self::Static { fun, .. } => (fun)(cx),
|
||||
Self::Macro { keys, .. } => {
|
||||
// Protect against recursive macros.
|
||||
if cx.editor.macro_replaying.contains(&'@') {
|
||||
cx.editor.set_error(
|
||||
"Cannot execute macro because the [@] register is already playing a macro",
|
||||
);
|
||||
return;
|
||||
}
|
||||
cx.editor.macro_replaying.push('@');
|
||||
let keys = keys.clone();
|
||||
cx.callback.push(Box::new(move |compositor, cx| {
|
||||
for key in keys.into_iter() {
|
||||
compositor.handle_event(&compositor::Event::Key(key), cx);
|
||||
}
|
||||
cx.editor.macro_replaying.pop();
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -247,6 +275,7 @@ pub fn name(&self) -> &str {
|
||||
match &self {
|
||||
Self::Typable { name, .. } => name,
|
||||
Self::Static { name, .. } => name,
|
||||
Self::Macro { name, .. } => name,
|
||||
}
|
||||
}
|
||||
|
||||
@ -254,6 +283,7 @@ pub fn doc(&self) -> &str {
|
||||
match &self {
|
||||
Self::Typable { doc, .. } => doc,
|
||||
Self::Static { doc, .. } => doc,
|
||||
Self::Macro { name, .. } => name,
|
||||
}
|
||||
}
|
||||
|
||||
@ -282,6 +312,10 @@ pub fn doc(&self) -> &str {
|
||||
move_prev_long_word_start, "Move to start of previous long word",
|
||||
move_next_long_word_end, "Move to end of next long word",
|
||||
move_prev_long_word_end, "Move to end of previous long word",
|
||||
move_next_sub_word_start, "Move to start of next sub word",
|
||||
move_prev_sub_word_start, "Move to start of previous sub word",
|
||||
move_next_sub_word_end, "Move to end of next sub word",
|
||||
move_prev_sub_word_end, "Move to end of previous sub word",
|
||||
move_parent_node_end, "Move to end of the parent node",
|
||||
move_parent_node_start, "Move to beginning of the parent node",
|
||||
extend_next_word_start, "Extend to start of next word",
|
||||
@ -292,6 +326,10 @@ pub fn doc(&self) -> &str {
|
||||
extend_prev_long_word_start, "Extend to start of previous long word",
|
||||
extend_next_long_word_end, "Extend to end of next long word",
|
||||
extend_prev_long_word_end, "Extend to end of prev long word",
|
||||
extend_next_sub_word_start, "Extend to start of next sub word",
|
||||
extend_prev_sub_word_start, "Extend to start of previous sub word",
|
||||
extend_next_sub_word_end, "Extend to end of next sub word",
|
||||
extend_prev_sub_word_end, "Extend to end of prev sub word",
|
||||
extend_parent_node_end, "Extend to end of the parent node",
|
||||
extend_parent_node_start, "Extend to beginning of the parent node",
|
||||
find_till_char, "Move till next occurrence of char",
|
||||
@ -556,6 +594,11 @@ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
.field(name)
|
||||
.field(args)
|
||||
.finish(),
|
||||
MappableCommand::Macro { name, keys, .. } => f
|
||||
.debug_tuple("MappableCommand")
|
||||
.field(name)
|
||||
.field(keys)
|
||||
.finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -597,6 +640,11 @@ fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
}
|
||||
})
|
||||
.ok_or_else(|| anyhow!("No TypableCommand named '{}'", s))
|
||||
} else if let Some(suffix) = s.strip_prefix('@') {
|
||||
helix_view::input::parse_macro(suffix).map(|keys| Self::Macro {
|
||||
name: s.to_string(),
|
||||
keys,
|
||||
})
|
||||
} else {
|
||||
MappableCommand::STATIC_COMMAND_LIST
|
||||
.iter()
|
||||
@ -1056,6 +1104,7 @@ fn goto_window(cx: &mut Context, align: Align) {
|
||||
let count = cx.count() - 1;
|
||||
let config = cx.editor.config();
|
||||
let (view, doc) = current!(cx.editor);
|
||||
let view_offset = doc.view_offset(view.id);
|
||||
|
||||
let height = view.inner_height();
|
||||
|
||||
@ -1068,15 +1117,15 @@ fn goto_window(cx: &mut Context, align: Align) {
|
||||
let last_visual_line = view.last_visual_line(doc);
|
||||
|
||||
let visual_line = match align {
|
||||
Align::Top => view.offset.vertical_offset + scrolloff + count,
|
||||
Align::Center => view.offset.vertical_offset + (last_visual_line / 2),
|
||||
Align::Top => view_offset.vertical_offset + scrolloff + count,
|
||||
Align::Center => view_offset.vertical_offset + (last_visual_line / 2),
|
||||
Align::Bottom => {
|
||||
view.offset.vertical_offset + last_visual_line.saturating_sub(scrolloff + count)
|
||||
view_offset.vertical_offset + last_visual_line.saturating_sub(scrolloff + count)
|
||||
}
|
||||
};
|
||||
let visual_line = visual_line
|
||||
.max(view.offset.vertical_offset + scrolloff)
|
||||
.min(view.offset.vertical_offset + last_visual_line.saturating_sub(scrolloff));
|
||||
.max(view_offset.vertical_offset + scrolloff)
|
||||
.min(view_offset.vertical_offset + last_visual_line.saturating_sub(scrolloff));
|
||||
|
||||
let pos = view
|
||||
.pos_at_visual_coords(doc, visual_line as u16, 0, false)
|
||||
@ -1149,6 +1198,22 @@ fn move_next_long_word_end(cx: &mut Context) {
|
||||
move_word_impl(cx, movement::move_next_long_word_end)
|
||||
}
|
||||
|
||||
fn move_next_sub_word_start(cx: &mut Context) {
|
||||
move_word_impl(cx, movement::move_next_sub_word_start)
|
||||
}
|
||||
|
||||
fn move_prev_sub_word_start(cx: &mut Context) {
|
||||
move_word_impl(cx, movement::move_prev_sub_word_start)
|
||||
}
|
||||
|
||||
fn move_prev_sub_word_end(cx: &mut Context) {
|
||||
move_word_impl(cx, movement::move_prev_sub_word_end)
|
||||
}
|
||||
|
||||
fn move_next_sub_word_end(cx: &mut Context) {
|
||||
move_word_impl(cx, movement::move_next_sub_word_end)
|
||||
}
|
||||
|
||||
fn goto_para_impl<F>(cx: &mut Context, move_fn: F)
|
||||
where
|
||||
F: Fn(RopeSlice, Range, usize, Movement) -> Range + 'static,
|
||||
@ -1385,6 +1450,22 @@ fn extend_next_long_word_end(cx: &mut Context) {
|
||||
extend_word_impl(cx, movement::move_next_long_word_end)
|
||||
}
|
||||
|
||||
fn extend_next_sub_word_start(cx: &mut Context) {
|
||||
extend_word_impl(cx, movement::move_next_sub_word_start)
|
||||
}
|
||||
|
||||
fn extend_prev_sub_word_start(cx: &mut Context) {
|
||||
extend_word_impl(cx, movement::move_prev_sub_word_start)
|
||||
}
|
||||
|
||||
fn extend_prev_sub_word_end(cx: &mut Context) {
|
||||
extend_word_impl(cx, movement::move_prev_sub_word_end)
|
||||
}
|
||||
|
||||
fn extend_next_sub_word_end(cx: &mut Context) {
|
||||
extend_word_impl(cx, movement::move_next_sub_word_end)
|
||||
}
|
||||
|
||||
/// Separate branch to find_char designed only for `<ret>` char.
|
||||
//
|
||||
// This is necessary because the one document can have different line endings inside. And we
|
||||
@ -1689,6 +1770,7 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction, sync_cursor
|
||||
use Direction::*;
|
||||
let config = cx.editor.config();
|
||||
let (view, doc) = current!(cx.editor);
|
||||
let mut view_offset = doc.view_offset(view.id);
|
||||
|
||||
let range = doc.selection(view.id).primary();
|
||||
let text = doc.text().slice(..);
|
||||
@ -1705,15 +1787,19 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction, sync_cursor
|
||||
let doc_text = doc.text().slice(..);
|
||||
let viewport = view.inner_area(doc);
|
||||
let text_fmt = doc.text_format(viewport.width, None);
|
||||
let mut annotations = view.text_annotations(&*doc, None);
|
||||
(view.offset.anchor, view.offset.vertical_offset) = char_idx_at_visual_offset(
|
||||
(view_offset.anchor, view_offset.vertical_offset) = char_idx_at_visual_offset(
|
||||
doc_text,
|
||||
view.offset.anchor,
|
||||
view.offset.vertical_offset as isize + offset,
|
||||
view_offset.anchor,
|
||||
view_offset.vertical_offset as isize + offset,
|
||||
0,
|
||||
&text_fmt,
|
||||
&annotations,
|
||||
// &annotations,
|
||||
&view.text_annotations(&*doc, None),
|
||||
);
|
||||
doc.set_view_offset(view.id, view_offset);
|
||||
|
||||
let doc_text = doc.text().slice(..);
|
||||
let mut annotations = view.text_annotations(&*doc, None);
|
||||
|
||||
if sync_cursor {
|
||||
let movement = match cx.editor.mode {
|
||||
@ -1740,14 +1826,16 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction, sync_cursor
|
||||
return;
|
||||
}
|
||||
|
||||
let view_offset = doc.view_offset(view.id);
|
||||
|
||||
let mut head;
|
||||
match direction {
|
||||
Forward => {
|
||||
let off;
|
||||
(head, off) = char_idx_at_visual_offset(
|
||||
doc_text,
|
||||
view.offset.anchor,
|
||||
(view.offset.vertical_offset + scrolloff) as isize,
|
||||
view_offset.anchor,
|
||||
(view_offset.vertical_offset + scrolloff) as isize,
|
||||
0,
|
||||
&text_fmt,
|
||||
&annotations,
|
||||
@ -1760,8 +1848,8 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction, sync_cursor
|
||||
Backward => {
|
||||
head = char_idx_at_visual_offset(
|
||||
doc_text,
|
||||
view.offset.anchor,
|
||||
(view.offset.vertical_offset + height - scrolloff - 1) as isize,
|
||||
view_offset.anchor,
|
||||
(view_offset.vertical_offset + height - scrolloff - 1) as isize,
|
||||
0,
|
||||
&text_fmt,
|
||||
&annotations,
|
||||
@ -1955,6 +2043,8 @@ fn select_regex(cx: &mut Context) {
|
||||
selection::select_on_matches(text, doc.selection(view.id), ®ex)
|
||||
{
|
||||
doc.set_selection(view.id, selection);
|
||||
} else {
|
||||
cx.editor.set_error("nothing selected");
|
||||
}
|
||||
},
|
||||
);
|
||||
@ -3161,6 +3251,9 @@ pub fn command_palette(cx: &mut Context) {
|
||||
ui::PickerColumn::new("name", |item, _| match item {
|
||||
MappableCommand::Typable { name, .. } => format!(":{name}").into(),
|
||||
MappableCommand::Static { name, .. } => (*name).into(),
|
||||
MappableCommand::Macro { .. } => {
|
||||
unreachable!("macros aren't included in the command palette")
|
||||
}
|
||||
}),
|
||||
ui::PickerColumn::new(
|
||||
"bindings",
|
||||
@ -4571,6 +4664,14 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
|
||||
let text = doc.text();
|
||||
let slice = text.slice(..);
|
||||
|
||||
let comment_tokens = doc
|
||||
.language_config()
|
||||
.and_then(|config| config.comment_tokens.as_deref())
|
||||
.unwrap_or(&[]);
|
||||
// Sort by length to handle Rust's /// vs //
|
||||
let mut comment_tokens: Vec<&str> = comment_tokens.iter().map(|x| x.as_str()).collect();
|
||||
comment_tokens.sort_unstable_by_key(|x| std::cmp::Reverse(x.len()));
|
||||
|
||||
let mut changes = Vec::new();
|
||||
|
||||
for selection in doc.selection(view.id) {
|
||||
@ -4582,10 +4683,31 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
|
||||
|
||||
changes.reserve(lines.len());
|
||||
|
||||
let first_line_idx = slice.line_to_char(start);
|
||||
let first_line_idx = skip_while(slice, first_line_idx, |ch| matches!(ch, ' ' | 't'))
|
||||
.unwrap_or(first_line_idx);
|
||||
let first_line = slice.slice(first_line_idx..);
|
||||
let mut current_comment_token = comment_tokens
|
||||
.iter()
|
||||
.find(|token| first_line.starts_with(token));
|
||||
|
||||
for line in lines {
|
||||
let start = line_end_char_index(&slice, line);
|
||||
let mut end = text.line_to_char(line + 1);
|
||||
end = skip_while(slice, end, |ch| matches!(ch, ' ' | '\t')).unwrap_or(end);
|
||||
let slice_from_end = slice.slice(end..);
|
||||
if let Some(token) = comment_tokens
|
||||
.iter()
|
||||
.find(|token| slice_from_end.starts_with(token))
|
||||
{
|
||||
if Some(token) == current_comment_token {
|
||||
end += token.chars().count();
|
||||
end = skip_while(slice, end, |ch| matches!(ch, ' ' | '\t')).unwrap_or(end);
|
||||
} else {
|
||||
// update current token, but don't delete this one.
|
||||
current_comment_token = Some(token);
|
||||
}
|
||||
}
|
||||
|
||||
let separator = if end == line_end_char_index(&slice, line + 1) {
|
||||
// the joining line contains only space-characters => don't include a whitespace when joining
|
||||
@ -4654,6 +4776,8 @@ fn keep_or_remove_selections_impl(cx: &mut Context, remove: bool) {
|
||||
selection::keep_or_remove_matches(text, doc.selection(view.id), ®ex, remove)
|
||||
{
|
||||
doc.set_selection(view.id, selection);
|
||||
} else {
|
||||
cx.editor.set_error("no selections remaining");
|
||||
}
|
||||
},
|
||||
)
|
||||
@ -5081,6 +5205,8 @@ fn jump_forward(cx: &mut Context) {
|
||||
}
|
||||
|
||||
doc.set_selection(view.id, selection);
|
||||
// Document we switch to might not have been opened in the view before
|
||||
doc.ensure_view_init(view.id);
|
||||
view.ensure_cursor_in_view_center(doc, config.scrolloff);
|
||||
};
|
||||
}
|
||||
@ -5101,6 +5227,8 @@ fn jump_backward(cx: &mut Context) {
|
||||
}
|
||||
|
||||
doc.set_selection(view.id, selection);
|
||||
// Document we switch to might not have been opened in the view before
|
||||
doc.ensure_view_init(view.id);
|
||||
view.ensure_cursor_in_view_center(doc, config.scrolloff);
|
||||
};
|
||||
}
|
||||
@ -5162,7 +5290,7 @@ fn split(editor: &mut Editor, action: Action) {
|
||||
let (view, doc) = current!(editor);
|
||||
let id = doc.id();
|
||||
let selection = doc.selection(view.id).clone();
|
||||
let offset = view.offset;
|
||||
let offset = doc.view_offset(view.id);
|
||||
|
||||
editor.switch(id, action);
|
||||
|
||||
@ -5171,7 +5299,7 @@ fn split(editor: &mut Editor, action: Action) {
|
||||
doc.set_selection(view.id, selection);
|
||||
// match the view scroll offset (switch doesn't handle this fully
|
||||
// since the selection is only matched after the split)
|
||||
view.offset = offset;
|
||||
doc.set_view_offset(view.id, offset);
|
||||
}
|
||||
|
||||
fn hsplit(cx: &mut Context) {
|
||||
@ -5266,14 +5394,21 @@ fn align_view_middle(cx: &mut Context) {
|
||||
return;
|
||||
}
|
||||
let doc_text = doc.text().slice(..);
|
||||
let annotations = view.text_annotations(doc, None);
|
||||
let pos = doc.selection(view.id).primary().cursor(doc_text);
|
||||
let pos =
|
||||
visual_offset_from_block(doc_text, view.offset.anchor, pos, &text_fmt, &annotations).0;
|
||||
let pos = visual_offset_from_block(
|
||||
doc_text,
|
||||
doc.view_offset(view.id).anchor,
|
||||
pos,
|
||||
&text_fmt,
|
||||
&view.text_annotations(doc, None),
|
||||
)
|
||||
.0;
|
||||
|
||||
view.offset.horizontal_offset = pos
|
||||
let mut offset = doc.view_offset(view.id);
|
||||
offset.horizontal_offset = pos
|
||||
.col
|
||||
.saturating_sub((view.inner_area(doc).width as usize) / 2);
|
||||
doc.set_view_offset(view.id, offset);
|
||||
}
|
||||
|
||||
fn scroll_up(cx: &mut Context) {
|
||||
@ -5739,27 +5874,24 @@ async fn shell_impl_async(
|
||||
process.wait_with_output().await?
|
||||
};
|
||||
|
||||
if !output.status.success() {
|
||||
if !output.stderr.is_empty() {
|
||||
let err = String::from_utf8_lossy(&output.stderr).to_string();
|
||||
log::error!("Shell error: {}", err);
|
||||
bail!("Shell error: {}", err);
|
||||
}
|
||||
match output.status.code() {
|
||||
Some(exit_code) => bail!("Shell command failed: status {}", exit_code),
|
||||
None => bail!("Shell command failed"),
|
||||
let output = if !output.status.success() {
|
||||
if output.stderr.is_empty() {
|
||||
match output.status.code() {
|
||||
Some(exit_code) => bail!("Shell command failed: status {}", exit_code),
|
||||
None => bail!("Shell command failed"),
|
||||
}
|
||||
}
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
// Prioritize `stderr` output over `stdout`
|
||||
} else if !output.stderr.is_empty() {
|
||||
log::debug!(
|
||||
"Command printed to stderr: {}",
|
||||
String::from_utf8_lossy(&output.stderr).to_string()
|
||||
);
|
||||
}
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
log::debug!("Command printed to stderr: {stderr}");
|
||||
stderr
|
||||
} else {
|
||||
String::from_utf8_lossy(&output.stdout)
|
||||
};
|
||||
|
||||
let str = std::str::from_utf8(&output.stdout)
|
||||
.map_err(|_| anyhow!("Process did not output valid UTF-8"))?;
|
||||
let tendril = Tendril::from(str);
|
||||
Ok(tendril)
|
||||
Ok(Tendril::from(output))
|
||||
}
|
||||
|
||||
fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) {
|
||||
@ -5783,14 +5915,20 @@ fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) {
|
||||
let output = if let Some(output) = shell_output.as_ref() {
|
||||
output.clone()
|
||||
} else {
|
||||
let fragment = range.slice(text);
|
||||
match shell_impl(shell, cmd, pipe.then(|| fragment.into())) {
|
||||
Ok(result) => {
|
||||
let result = Tendril::from(result.trim_end());
|
||||
if !pipe {
|
||||
shell_output = Some(result.clone());
|
||||
let input = range.slice(text);
|
||||
match shell_impl(shell, cmd, pipe.then(|| input.into())) {
|
||||
Ok(mut output) => {
|
||||
if !input.ends_with("\n") && !output.is_empty() && output.ends_with('\n') {
|
||||
output.pop();
|
||||
if output.ends_with('\r') {
|
||||
output.pop();
|
||||
}
|
||||
}
|
||||
result
|
||||
|
||||
if !pipe {
|
||||
shell_output = Some(output.clone());
|
||||
}
|
||||
output
|
||||
}
|
||||
Err(err) => {
|
||||
cx.editor.set_error(err.to_string());
|
||||
@ -6149,7 +6287,7 @@ fn jump_to_word(cx: &mut Context, behaviour: Movement) {
|
||||
|
||||
// This is not necessarily exact if there is virtual text like soft wrap.
|
||||
// It's ok though because the extra jump labels will not be rendered.
|
||||
let start = text.line_to_char(text.char_to_line(view.offset.anchor));
|
||||
let start = text.line_to_char(text.char_to_line(doc.view_offset(view.id).anchor));
|
||||
let end = text.line_to_char(view.estimate_last_doc_line(doc) + 1);
|
||||
|
||||
let primary_selection = doc.selection(view.id).primary();
|
||||
|
@ -2164,7 +2164,7 @@ fn configure_engine_impl(mut engine: Engine) -> Engine {
|
||||
cx.editor.set_error(err);
|
||||
}
|
||||
})
|
||||
.with_preview(|_editor, path| Some((PathOrId::Path(path.clone().into()), None)));
|
||||
.with_preview(|_editor, path| Some((PathOrId::Path(path), None)));
|
||||
|
||||
let injector = picker.injector();
|
||||
|
||||
|
@ -34,7 +34,7 @@
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::{BTreeMap, HashSet},
|
||||
fmt::Write,
|
||||
fmt::Display,
|
||||
future::Future,
|
||||
path::Path,
|
||||
};
|
||||
@ -61,10 +61,31 @@ macro_rules! language_server_with_feature {
|
||||
}};
|
||||
}
|
||||
|
||||
/// A wrapper around `lsp::Location` that swaps out the LSP URI for `helix_core::Uri`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct Location {
|
||||
uri: Uri,
|
||||
range: lsp::Range,
|
||||
}
|
||||
|
||||
fn lsp_location_to_location(location: lsp::Location) -> Option<Location> {
|
||||
let uri = match location.uri.try_into() {
|
||||
Ok(uri) => uri,
|
||||
Err(err) => {
|
||||
log::warn!("discarding invalid or unsupported URI: {err}");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
Some(Location {
|
||||
uri,
|
||||
range: location.range,
|
||||
})
|
||||
}
|
||||
|
||||
struct SymbolInformationItem {
|
||||
location: Location,
|
||||
symbol: lsp::SymbolInformation,
|
||||
offset_encoding: OffsetEncoding,
|
||||
uri: Uri,
|
||||
}
|
||||
|
||||
struct DiagnosticStyles {
|
||||
@ -75,35 +96,35 @@ struct DiagnosticStyles {
|
||||
}
|
||||
|
||||
struct PickerDiagnostic {
|
||||
uri: Uri,
|
||||
location: Location,
|
||||
diag: lsp::Diagnostic,
|
||||
offset_encoding: OffsetEncoding,
|
||||
}
|
||||
|
||||
fn uri_to_file_location<'a>(uri: &'a Uri, range: &lsp::Range) -> Option<FileLocation<'a>> {
|
||||
let path = uri.as_path()?;
|
||||
let line = Some((range.start.line as usize, range.end.line as usize));
|
||||
fn location_to_file_location(location: &Location) -> Option<FileLocation> {
|
||||
let path = location.uri.as_path()?;
|
||||
let line = Some((
|
||||
location.range.start.line as usize,
|
||||
location.range.end.line as usize,
|
||||
));
|
||||
Some((path.into(), line))
|
||||
}
|
||||
|
||||
fn jump_to_location(
|
||||
editor: &mut Editor,
|
||||
location: &lsp::Location,
|
||||
location: &Location,
|
||||
offset_encoding: OffsetEncoding,
|
||||
action: Action,
|
||||
) {
|
||||
let (view, doc) = current!(editor);
|
||||
push_jump(view, doc);
|
||||
|
||||
let path = match location.uri.to_file_path() {
|
||||
Ok(path) => path,
|
||||
Err(_) => {
|
||||
let err = format!("unable to convert URI to filepath: {}", location.uri);
|
||||
editor.set_error(err);
|
||||
return;
|
||||
}
|
||||
let Some(path) = location.uri.as_path() else {
|
||||
let err = format!("unable to convert URI to filepath: {:?}", location.uri);
|
||||
editor.set_error(err);
|
||||
return;
|
||||
};
|
||||
jump_to_position(editor, &path, location.range, offset_encoding, action);
|
||||
jump_to_position(editor, path, location.range, offset_encoding, action);
|
||||
}
|
||||
|
||||
fn jump_to_position(
|
||||
@ -196,7 +217,10 @@ fn diag_picker(
|
||||
for (diag, ls) in diags {
|
||||
if let Some(ls) = cx.editor.language_server_by_id(ls) {
|
||||
flat_diag.push(PickerDiagnostic {
|
||||
uri: uri.clone(),
|
||||
location: Location {
|
||||
uri: uri.clone(),
|
||||
range: diag.range,
|
||||
},
|
||||
diag,
|
||||
offset_encoding: ls.offset_encoding(),
|
||||
});
|
||||
@ -243,7 +267,7 @@ fn diag_picker(
|
||||
// between message code and message
|
||||
2,
|
||||
ui::PickerColumn::new("path", |item: &PickerDiagnostic, _| {
|
||||
if let Some(path) = item.uri.as_path() {
|
||||
if let Some(path) = item.location.uri.as_path() {
|
||||
path::get_truncated_path(path)
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
@ -261,26 +285,14 @@ fn diag_picker(
|
||||
primary_column,
|
||||
flat_diag,
|
||||
styles,
|
||||
move |cx,
|
||||
PickerDiagnostic {
|
||||
uri,
|
||||
diag,
|
||||
offset_encoding,
|
||||
},
|
||||
action| {
|
||||
let Some(path) = uri.as_path() else {
|
||||
return;
|
||||
};
|
||||
jump_to_position(cx.editor, path, diag.range, *offset_encoding, action);
|
||||
move |cx, diag, action| {
|
||||
jump_to_location(cx.editor, &diag.location, diag.offset_encoding, action);
|
||||
let (view, doc) = current!(cx.editor);
|
||||
view.diagnostics_handler
|
||||
.immediately_show_diagnostic(doc, view.id);
|
||||
},
|
||||
)
|
||||
.with_preview(move |_editor, PickerDiagnostic { uri, diag, .. }| {
|
||||
let line = Some((diag.range.start.line as usize, diag.range.end.line as usize));
|
||||
Some((uri.as_path()?.into(), line))
|
||||
})
|
||||
.with_preview(move |_editor, diag| location_to_file_location(&diag.location))
|
||||
.truncate_start(false)
|
||||
}
|
||||
|
||||
@ -303,7 +315,10 @@ fn nested_to_flat(
|
||||
container_name: None,
|
||||
},
|
||||
offset_encoding,
|
||||
uri: uri.clone(),
|
||||
location: Location {
|
||||
uri: uri.clone(),
|
||||
range: symbol.selection_range,
|
||||
},
|
||||
});
|
||||
for child in symbol.children.into_iter().flatten() {
|
||||
nested_to_flat(list, file, uri, child, offset_encoding);
|
||||
@ -337,7 +352,10 @@ fn nested_to_flat(
|
||||
lsp::DocumentSymbolResponse::Flat(symbols) => symbols
|
||||
.into_iter()
|
||||
.map(|symbol| SymbolInformationItem {
|
||||
uri: doc_uri.clone(),
|
||||
location: Location {
|
||||
uri: doc_uri.clone(),
|
||||
range: symbol.location.range,
|
||||
},
|
||||
symbol,
|
||||
offset_encoding,
|
||||
})
|
||||
@ -392,17 +410,10 @@ fn nested_to_flat(
|
||||
symbols,
|
||||
(),
|
||||
move |cx, item, action| {
|
||||
jump_to_location(
|
||||
cx.editor,
|
||||
&item.symbol.location,
|
||||
item.offset_encoding,
|
||||
action,
|
||||
);
|
||||
jump_to_location(cx.editor, &item.location, item.offset_encoding, action);
|
||||
},
|
||||
)
|
||||
.with_preview(move |_editor, item| {
|
||||
uri_to_file_location(&item.uri, &item.symbol.location.range)
|
||||
})
|
||||
.with_preview(move |_editor, item| location_to_file_location(&item.location))
|
||||
.truncate_start(false);
|
||||
|
||||
compositor.push(Box::new(overlaid(picker)))
|
||||
@ -453,8 +464,11 @@ pub fn workspace_symbol_picker(cx: &mut Context) {
|
||||
}
|
||||
};
|
||||
Some(SymbolInformationItem {
|
||||
location: Location {
|
||||
uri,
|
||||
range: symbol.location.range,
|
||||
},
|
||||
symbol,
|
||||
uri,
|
||||
offset_encoding,
|
||||
})
|
||||
})
|
||||
@ -490,7 +504,7 @@ pub fn workspace_symbol_picker(cx: &mut Context) {
|
||||
})
|
||||
.without_filtering(),
|
||||
ui::PickerColumn::new("path", |item: &SymbolInformationItem, _| {
|
||||
if let Some(path) = item.uri.as_path() {
|
||||
if let Some(path) = item.location.uri.as_path() {
|
||||
path::get_relative_path(path)
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
@ -507,15 +521,10 @@ pub fn workspace_symbol_picker(cx: &mut Context) {
|
||||
[],
|
||||
(),
|
||||
move |cx, item, action| {
|
||||
jump_to_location(
|
||||
cx.editor,
|
||||
&item.symbol.location,
|
||||
item.offset_encoding,
|
||||
action,
|
||||
);
|
||||
jump_to_location(cx.editor, &item.location, item.offset_encoding, action);
|
||||
},
|
||||
)
|
||||
.with_preview(|_editor, item| uri_to_file_location(&item.uri, &item.symbol.location.range))
|
||||
.with_preview(|_editor, item| location_to_file_location(&item.location))
|
||||
.with_dynamic_query(get_symbols, None)
|
||||
.truncate_start(false);
|
||||
|
||||
@ -832,13 +841,13 @@ pub enum ApplyEditErrorKind {
|
||||
// InvalidEdit,
|
||||
}
|
||||
|
||||
impl ToString for ApplyEditErrorKind {
|
||||
fn to_string(&self) -> String {
|
||||
impl Display for ApplyEditErrorKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ApplyEditErrorKind::DocumentChanged => "document has changed".to_string(),
|
||||
ApplyEditErrorKind::FileNotFound => "file not found".to_string(),
|
||||
ApplyEditErrorKind::UnknownURISchema => "URI schema not supported".to_string(),
|
||||
ApplyEditErrorKind::IoError(err) => err.to_string(),
|
||||
ApplyEditErrorKind::DocumentChanged => f.write_str("document has changed"),
|
||||
ApplyEditErrorKind::FileNotFound => f.write_str("file not found"),
|
||||
ApplyEditErrorKind::UnknownURISchema => f.write_str("URI schema not supported"),
|
||||
ApplyEditErrorKind::IoError(err) => f.write_str(&format!("{err}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -847,7 +856,7 @@ fn to_string(&self) -> String {
|
||||
fn goto_impl(
|
||||
editor: &mut Editor,
|
||||
compositor: &mut Compositor,
|
||||
locations: Vec<lsp::Location>,
|
||||
locations: Vec<Location>,
|
||||
offset_encoding: OffsetEncoding,
|
||||
) {
|
||||
let cwdir = helix_stdx::env::current_working_dir();
|
||||
@ -860,80 +869,41 @@ fn goto_impl(
|
||||
_locations => {
|
||||
let columns = [ui::PickerColumn::new(
|
||||
"location",
|
||||
|item: &lsp::Location, cwdir: &std::path::PathBuf| {
|
||||
// The preallocation here will overallocate a few characters since it will account for the
|
||||
// URL's scheme, which is not used most of the time since that scheme will be "file://".
|
||||
// Those extra chars will be used to avoid allocating when writing the line number (in the
|
||||
// common case where it has 5 digits or less, which should be enough for a cast majority
|
||||
// of usages).
|
||||
let mut res = String::with_capacity(item.uri.as_str().len());
|
||||
|
||||
if item.uri.scheme() == "file" {
|
||||
// With the preallocation above and UTF-8 paths already, this closure will do one (1)
|
||||
// allocation, for `to_file_path`, else there will be two (2), with `to_string_lossy`.
|
||||
if let Ok(path) = item.uri.to_file_path() {
|
||||
// We don't convert to a `helix_core::Uri` here because we've already checked the scheme.
|
||||
// This path won't be normalized but it's only used for display.
|
||||
res.push_str(
|
||||
&path.strip_prefix(cwdir).unwrap_or(&path).to_string_lossy(),
|
||||
);
|
||||
}
|
||||
|item: &Location, cwdir: &std::path::PathBuf| {
|
||||
let path = if let Some(path) = item.uri.as_path() {
|
||||
path.strip_prefix(cwdir).unwrap_or(path).to_string_lossy()
|
||||
} else {
|
||||
// Never allocates since we declared the string with this capacity already.
|
||||
res.push_str(item.uri.as_str());
|
||||
}
|
||||
item.uri.to_string().into()
|
||||
};
|
||||
|
||||
// Most commonly, this will not allocate, especially on Unix systems where the root prefix
|
||||
// is a simple `/` and not `C:\` (with whatever drive letter)
|
||||
write!(&mut res, ":{}", item.range.start.line + 1)
|
||||
.expect("Will only failed if allocating fail");
|
||||
res.into()
|
||||
format!("{path}:{}", item.range.start.line + 1).into()
|
||||
},
|
||||
)];
|
||||
|
||||
let picker = Picker::new(columns, 0, locations, cwdir, move |cx, location, action| {
|
||||
jump_to_location(cx.editor, location, offset_encoding, action)
|
||||
})
|
||||
.with_preview(move |_editor, location| {
|
||||
use crate::ui::picker::PathOrId;
|
||||
|
||||
let lines = Some((
|
||||
location.range.start.line as usize,
|
||||
location.range.end.line as usize,
|
||||
));
|
||||
|
||||
// TODO: we should avoid allocating by doing the Uri conversion ahead of time.
|
||||
//
|
||||
// To do this, introduce a `Location` type in `helix-core` that reuses the core
|
||||
// `Uri` type instead of the LSP `Url` type and replaces the LSP `Range` type.
|
||||
// Refactor the callers of `goto_impl` to pass iterators that translate the
|
||||
// LSP location type to the custom one in core, or have them collect and pass
|
||||
// `Vec<Location>`s. Replace the `uri_to_file_location` function with
|
||||
// `location_to_file_location` that takes only `&helix_core::Location` as
|
||||
// parameters.
|
||||
//
|
||||
// By doing this we can also eliminate the duplicated URI info in the
|
||||
// `SymbolInformationItem` type and introduce a custom Symbol type in `helix-core`
|
||||
// which will be reused in the future for tree-sitter based symbol pickers.
|
||||
let path = Uri::try_from(&location.uri).ok()?.as_path_buf()?;
|
||||
#[allow(deprecated)]
|
||||
Some((PathOrId::from_path_buf(path), lines))
|
||||
});
|
||||
.with_preview(move |_editor, location| location_to_file_location(location));
|
||||
compositor.push(Box::new(overlaid(picker)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn to_locations(definitions: Option<lsp::GotoDefinitionResponse>) -> Vec<lsp::Location> {
|
||||
fn to_locations(definitions: Option<lsp::GotoDefinitionResponse>) -> Vec<Location> {
|
||||
match definitions {
|
||||
Some(lsp::GotoDefinitionResponse::Scalar(location)) => vec![location],
|
||||
Some(lsp::GotoDefinitionResponse::Array(locations)) => locations,
|
||||
Some(lsp::GotoDefinitionResponse::Scalar(location)) => {
|
||||
lsp_location_to_location(location).into_iter().collect()
|
||||
}
|
||||
Some(lsp::GotoDefinitionResponse::Array(locations)) => locations
|
||||
.into_iter()
|
||||
.flat_map(lsp_location_to_location)
|
||||
.collect(),
|
||||
Some(lsp::GotoDefinitionResponse::Link(locations)) => locations
|
||||
.into_iter()
|
||||
.map(|location_link| lsp::Location {
|
||||
uri: location_link.target_uri,
|
||||
range: location_link.target_range,
|
||||
.map(|location_link| {
|
||||
lsp::Location::new(location_link.target_uri, location_link.target_range)
|
||||
})
|
||||
.flat_map(lsp_location_to_location)
|
||||
.collect(),
|
||||
None => Vec::new(),
|
||||
}
|
||||
@ -1018,7 +988,11 @@ pub fn goto_reference(cx: &mut Context) {
|
||||
cx.callback(
|
||||
future,
|
||||
move |editor, compositor, response: Option<Vec<lsp::Location>>| {
|
||||
let items = response.unwrap_or_default();
|
||||
let items: Vec<Location> = response
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.flat_map(lsp_location_to_location)
|
||||
.collect();
|
||||
if items.is_empty() {
|
||||
editor.set_error("No references found.");
|
||||
} else {
|
||||
@ -1299,7 +1273,8 @@ fn compute_inlay_hints_for_view(
|
||||
// than computing all the hints for the full file (which could be dozens of time
|
||||
// longer than the view is).
|
||||
let view_height = view.inner_height();
|
||||
let first_visible_line = doc_text.char_to_line(view.offset.anchor.min(doc_text.len_chars()));
|
||||
let first_visible_line =
|
||||
doc_text.char_to_line(doc.view_offset(view_id).anchor.min(doc_text.len_chars()));
|
||||
let first_line = first_visible_line.saturating_sub(view_height);
|
||||
let last_line = first_visible_line
|
||||
.saturating_add(view_height.saturating_mul(2))
|
||||
@ -1359,7 +1334,7 @@ fn compute_inlay_hints_for_view(
|
||||
|
||||
// Most language servers will already send them sorted but ensure this is the case to
|
||||
// avoid errors on our end.
|
||||
hints.sort_unstable_by_key(|inlay_hint| inlay_hint.position);
|
||||
hints.sort_by_key(|inlay_hint| inlay_hint.position);
|
||||
|
||||
let mut padding_before_inlay_hints = Vec::new();
|
||||
let mut type_inlay_hints = Vec::new();
|
||||
|
@ -1596,7 +1596,7 @@ fn find_highlight_at_cursor(
|
||||
// Query the same range as the one used in syntax highlighting.
|
||||
let range = {
|
||||
// Calculate viewport byte ranges:
|
||||
let row = text.char_to_line(view.offset.anchor.min(text.len_chars()));
|
||||
let row = text.char_to_line(doc.view_offset(view.id).anchor.min(text.len_chars()));
|
||||
// Saturating subs to make it inclusive zero indexing.
|
||||
let last_line = text.len_lines().saturating_sub(1);
|
||||
let height = view.inner_area(doc).height;
|
||||
@ -2342,7 +2342,7 @@ fn run_shell_command(
|
||||
move |editor: &mut Editor, compositor: &mut Compositor| {
|
||||
if !output.is_empty() {
|
||||
let contents = ui::Markdown::new(
|
||||
format!("```sh\n{}\n```", output),
|
||||
format!("```sh\n{}\n```", output.trim_end()),
|
||||
editor.syn_loader.clone(),
|
||||
);
|
||||
let popup = Popup::new("shell", contents).position(Some(
|
||||
@ -2350,7 +2350,7 @@ fn run_shell_command(
|
||||
));
|
||||
compositor.replace_or_push("shell", popup);
|
||||
}
|
||||
editor.set_status("Command succeeded");
|
||||
editor.set_status("Command run");
|
||||
},
|
||||
));
|
||||
Ok(call)
|
||||
@ -2540,7 +2540,7 @@ fn read(cx: &mut compositor::Context, args: &[Cow<str>], event: PromptEvent) ->
|
||||
ensure!(!args.is_empty(), "file name is expected");
|
||||
ensure!(args.len() == 1, "only the file name is expected");
|
||||
|
||||
let filename = args.get(0).unwrap();
|
||||
let filename = args.first().unwrap();
|
||||
let path = PathBuf::from(filename.to_string());
|
||||
ensure!(
|
||||
path.exists() && path.is_file(),
|
||||
@ -3076,7 +3076,7 @@ fn read(cx: &mut compositor::Context, args: &[Cow<str>], event: PromptEvent) ->
|
||||
TypableCommand {
|
||||
name: "tree-sitter-subtree",
|
||||
aliases: &["ts-subtree"],
|
||||
doc: "Display tree sitter subtree under cursor, primarily for debugging queries.",
|
||||
doc: "Display the smallest tree-sitter subtree that spans the primary selection, primarily for debugging queries.",
|
||||
fun: tree_sitter_subtree,
|
||||
signature: CommandSignature::none(),
|
||||
},
|
||||
@ -3173,7 +3173,7 @@ fn read(cx: &mut compositor::Context, args: &[Cow<str>], event: PromptEvent) ->
|
||||
},
|
||||
TypableCommand {
|
||||
name: "move",
|
||||
aliases: &[],
|
||||
aliases: &["mv"],
|
||||
doc: "Move the current buffer and its corresponding file to a different path",
|
||||
fun: move_buffer,
|
||||
signature: CommandSignature::positional(&[completers::filename]),
|
||||
|
@ -177,6 +177,19 @@ fn visit_seq<S>(self, mut seq: S) -> Result<Self::Value, S::Error>
|
||||
.map_err(serde::de::Error::custom)?,
|
||||
)
|
||||
}
|
||||
|
||||
// Prevent macro keybindings from being used in command sequences.
|
||||
// This is meant to be a temporary restriction pending a larger
|
||||
// refactor of how command sequences are executed.
|
||||
if commands
|
||||
.iter()
|
||||
.any(|cmd| matches!(cmd, MappableCommand::Macro { .. }))
|
||||
{
|
||||
return Err(serde::de::Error::custom(
|
||||
"macro keybindings may not be used in command sequences",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(KeyTrie::Sequence(commands))
|
||||
}
|
||||
|
||||
@ -199,6 +212,7 @@ pub fn reverse_map(&self) -> ReverseKeymap {
|
||||
// recursively visit all nodes in keymap
|
||||
fn map_node(cmd_map: &mut ReverseKeymap, node: &KeyTrie, keys: &mut Vec<KeyEvent>) {
|
||||
match node {
|
||||
KeyTrie::MappableCommand(MappableCommand::Macro { .. }) => {}
|
||||
KeyTrie::MappableCommand(cmd) => {
|
||||
let name = cmd.name();
|
||||
if name != "no_op" {
|
||||
|
@ -120,10 +120,9 @@ async fn main_impl() -> Result<i32> {
|
||||
helix_term::commands::ScriptingEngine::initialize();
|
||||
|
||||
// Before setting the working directory, resolve all the paths in args.files
|
||||
for (path, _) in args.files.iter_mut() {
|
||||
*path = helix_stdx::path::canonicalize(&path);
|
||||
for (path, _) in &mut args.files {
|
||||
*path = helix_stdx::path::canonicalize(&*path);
|
||||
}
|
||||
|
||||
// NOTE: Set the working directory early so the correct configuration is loaded. Be aware that
|
||||
// Application::new() depends on this logic so it must be updated if this changes.
|
||||
if let Some(path) = &args.working_directory {
|
||||
|
@ -435,7 +435,7 @@ pub fn draw_grapheme(
|
||||
Grapheme::Newline => &self.newline,
|
||||
};
|
||||
|
||||
let in_bounds = self.column_in_bounds(position.col + width - 1);
|
||||
let in_bounds = self.column_in_bounds(position.col, width);
|
||||
|
||||
if in_bounds {
|
||||
self.surface.set_string(
|
||||
@ -454,7 +454,6 @@ pub fn draw_grapheme(
|
||||
);
|
||||
self.surface.set_style(rect, style);
|
||||
}
|
||||
|
||||
if *is_in_indent_area && !is_whitespace {
|
||||
*last_indent_level = position.col;
|
||||
*is_in_indent_area = false;
|
||||
@ -463,8 +462,8 @@ pub fn draw_grapheme(
|
||||
width
|
||||
}
|
||||
|
||||
pub fn column_in_bounds(&self, colum: usize) -> bool {
|
||||
self.offset.col <= colum && colum < self.viewport.width as usize + self.offset.col
|
||||
pub fn column_in_bounds(&self, colum: usize, width: usize) -> bool {
|
||||
self.offset.col <= colum && colum + width <= self.offset.col + self.viewport.width as usize
|
||||
}
|
||||
|
||||
/// Overlay indentation guides ontop of a rendered line
|
||||
|
@ -93,6 +93,8 @@ pub fn render_view(
|
||||
let theme = &editor.theme;
|
||||
let config = editor.config();
|
||||
|
||||
let view_offset = doc.view_offset(view.id);
|
||||
|
||||
let text_annotations = view.text_annotations(doc, Some(theme));
|
||||
let mut decorations = DecorationManager::default();
|
||||
|
||||
@ -119,13 +121,13 @@ pub fn render_view(
|
||||
}
|
||||
|
||||
let syntax_highlights =
|
||||
Self::doc_syntax_highlights(doc, view.offset.anchor, inner.height, theme);
|
||||
Self::doc_syntax_highlights(doc, view_offset.anchor, inner.height, theme);
|
||||
|
||||
let mut overlay_highlights =
|
||||
Self::empty_highlight_iter(doc, view.offset.anchor, inner.height);
|
||||
Self::empty_highlight_iter(doc, view_offset.anchor, inner.height);
|
||||
let overlay_syntax_highlights = Self::overlay_syntax_highlights(
|
||||
doc,
|
||||
view.offset.anchor,
|
||||
view_offset.anchor,
|
||||
inner.height,
|
||||
&text_annotations,
|
||||
);
|
||||
@ -203,7 +205,7 @@ pub fn render_view(
|
||||
surface,
|
||||
inner,
|
||||
doc,
|
||||
view.offset,
|
||||
view_offset,
|
||||
&text_annotations,
|
||||
syntax_highlights,
|
||||
overlay_highlights,
|
||||
@ -259,11 +261,13 @@ pub fn render_rulers(
|
||||
.and_then(|config| config.rulers.as_ref())
|
||||
.unwrap_or(editor_rulers);
|
||||
|
||||
let view_offset = doc.view_offset(view.id);
|
||||
|
||||
rulers
|
||||
.iter()
|
||||
// View might be horizontally scrolled, convert from absolute distance
|
||||
// from the 1st column to relative distance from left of viewport
|
||||
.filter_map(|ruler| ruler.checked_sub(1 + view.offset.horizontal_offset as u16))
|
||||
.filter_map(|ruler| ruler.checked_sub(1 + view_offset.horizontal_offset as u16))
|
||||
.filter(|ruler| ruler < &viewport.width)
|
||||
.map(|ruler| viewport.clip_left(ruler).with_width(1))
|
||||
.for_each(|area| surface.set_style(area, ruler_theme))
|
||||
@ -825,6 +829,7 @@ pub fn highlight_cursorcolumn(
|
||||
let inner_area = view.inner_area(doc);
|
||||
|
||||
let selection = doc.selection(view.id);
|
||||
let view_offset = doc.view_offset(view.id);
|
||||
let primary = selection.primary();
|
||||
let text_format = doc.text_format(viewport.width, None);
|
||||
for range in selection.iter() {
|
||||
@ -835,11 +840,11 @@ pub fn highlight_cursorcolumn(
|
||||
visual_offset_from_block(text, cursor, cursor, &text_format, text_annotations).0;
|
||||
|
||||
// if the cursor is horizontally in the view
|
||||
if col >= view.offset.horizontal_offset
|
||||
&& inner_area.width > (col - view.offset.horizontal_offset) as u16
|
||||
if col >= view_offset.horizontal_offset
|
||||
&& inner_area.width > (col - view_offset.horizontal_offset) as u16
|
||||
{
|
||||
let area = Rect::new(
|
||||
inner_area.x + (col - view.offset.horizontal_offset) as u16,
|
||||
inner_area.x + (col - view_offset.horizontal_offset) as u16,
|
||||
view.area.y,
|
||||
1,
|
||||
view.area.height,
|
||||
|
@ -96,7 +96,10 @@ fn handle_event(&mut self, event: &Event, _cx: &mut Context) -> EventResult {
|
||||
fn render(&mut self, area: Rect, surface: &mut Buffer, cx: &mut Context) {
|
||||
let margin = Margin::horizontal(1);
|
||||
|
||||
let signature = &self.signatures[self.active_signature];
|
||||
let signature = self
|
||||
.signatures
|
||||
.get(self.active_signature)
|
||||
.unwrap_or_else(|| &self.signatures[0]);
|
||||
|
||||
let active_param_span = signature.active_param_range.map(|(start, end)| {
|
||||
vec![(
|
||||
@ -108,9 +111,13 @@ fn render(&mut self, area: Rect, surface: &mut Buffer, cx: &mut Context) {
|
||||
)]
|
||||
});
|
||||
|
||||
let sig = &self.signatures[self.active_signature];
|
||||
let signature = self
|
||||
.signatures
|
||||
.get(self.active_signature)
|
||||
.unwrap_or_else(|| &self.signatures[0]);
|
||||
|
||||
let sig_text = crate::ui::markdown::highlighted_code_block(
|
||||
sig.signature.as_str(),
|
||||
signature.signature.as_str(),
|
||||
&self.language,
|
||||
Some(&cx.editor.theme),
|
||||
Arc::clone(&self.config_loader),
|
||||
@ -130,7 +137,7 @@ fn render(&mut self, area: Rect, surface: &mut Buffer, cx: &mut Context) {
|
||||
let sig_text_para = Paragraph::new(&sig_text).wrap(Wrap { trim: false });
|
||||
sig_text_para.render(sig_text_area, surface);
|
||||
|
||||
if sig.signature_doc.is_none() {
|
||||
if signature.signature_doc.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -142,7 +149,7 @@ fn render(&mut self, area: Rect, surface: &mut Buffer, cx: &mut Context) {
|
||||
}
|
||||
}
|
||||
|
||||
let sig_doc = match &sig.signature_doc {
|
||||
let sig_doc = match &signature.signature_doc {
|
||||
None => return,
|
||||
Some(doc) => Markdown::new(doc.clone(), Arc::clone(&self.config_loader)),
|
||||
};
|
||||
@ -160,12 +167,15 @@ fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
|
||||
const PADDING: u16 = 2;
|
||||
const SEPARATOR_HEIGHT: u16 = 1;
|
||||
|
||||
let sig = &self.signatures[self.active_signature];
|
||||
let signature = self
|
||||
.signatures
|
||||
.get(self.active_signature)
|
||||
.unwrap_or_else(|| &self.signatures[0]);
|
||||
|
||||
let max_text_width = viewport.0.saturating_sub(PADDING).clamp(10, 120);
|
||||
|
||||
let signature_text = crate::ui::markdown::highlighted_code_block(
|
||||
sig.signature.as_str(),
|
||||
signature.signature.as_str(),
|
||||
&self.language,
|
||||
None,
|
||||
Arc::clone(&self.config_loader),
|
||||
@ -174,7 +184,7 @@ fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
|
||||
let (sig_width, sig_height) =
|
||||
crate::ui::text::required_size(&signature_text, max_text_width);
|
||||
|
||||
let (width, height) = match sig.signature_doc {
|
||||
let (width, height) = match signature.signature_doc {
|
||||
Some(ref doc) => {
|
||||
let doc_md = Markdown::new(doc.clone(), Arc::clone(&self.config_loader));
|
||||
let doc_text = doc_md.parse(None);
|
||||
|
@ -84,7 +84,7 @@ pub fn raw_regex_prompt(
|
||||
let (view, doc) = current!(cx.editor);
|
||||
let doc_id = view.doc;
|
||||
let snapshot = doc.selection(view.id).clone();
|
||||
let offset_snapshot = view.offset;
|
||||
let offset_snapshot = doc.view_offset(view.id);
|
||||
let config = cx.editor.config();
|
||||
|
||||
let mut prompt = Prompt::new(
|
||||
@ -96,7 +96,7 @@ pub fn raw_regex_prompt(
|
||||
PromptEvent::Abort => {
|
||||
let (view, doc) = current!(cx.editor);
|
||||
doc.set_selection(view.id, snapshot.clone());
|
||||
view.offset = offset_snapshot;
|
||||
doc.set_view_offset(view.id, offset_snapshot);
|
||||
}
|
||||
PromptEvent::Update | PromptEvent::Validate => {
|
||||
// skip empty input
|
||||
@ -137,7 +137,7 @@ pub fn raw_regex_prompt(
|
||||
Err(err) => {
|
||||
let (view, doc) = current!(cx.editor);
|
||||
doc.set_selection(view.id, snapshot.clone());
|
||||
view.offset = offset_snapshot;
|
||||
doc.set_view_offset(view.id, offset_snapshot);
|
||||
|
||||
if event == PromptEvent::Validate {
|
||||
let callback = async move {
|
||||
|
@ -32,7 +32,7 @@
|
||||
borrow::Cow,
|
||||
collections::HashMap,
|
||||
io::Read,
|
||||
path::{Path, PathBuf},
|
||||
path::Path,
|
||||
sync::{
|
||||
atomic::{self, AtomicUsize},
|
||||
Arc,
|
||||
@ -52,7 +52,7 @@
|
||||
Document, DocumentId, Editor,
|
||||
};
|
||||
|
||||
use self::handlers::{DynamicQueryHandler, PreviewHighlightHandler};
|
||||
use self::handlers::{DynamicQueryChange, DynamicQueryHandler, PreviewHighlightHandler};
|
||||
|
||||
pub const ID: &str = "picker";
|
||||
|
||||
@ -63,26 +63,12 @@
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
pub enum PathOrId<'a> {
|
||||
Id(DocumentId),
|
||||
// See [PathOrId::from_path_buf]: this will eventually become `Path(&Path)`.
|
||||
Path(Cow<'a, Path>),
|
||||
}
|
||||
|
||||
impl<'a> PathOrId<'a> {
|
||||
/// Creates a [PathOrId] from a PathBuf
|
||||
///
|
||||
/// # Deprecated
|
||||
/// The owned version of PathOrId will be removed in a future refactor
|
||||
/// and replaced with `&'a Path`. See the caller of this function for
|
||||
/// more details on its removal.
|
||||
#[deprecated]
|
||||
pub fn from_path_buf(path_buf: PathBuf) -> Self {
|
||||
Self::Path(Cow::Owned(path_buf))
|
||||
}
|
||||
Path(&'a Path),
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Path> for PathOrId<'a> {
|
||||
fn from(path: &'a Path) -> Self {
|
||||
Self::Path(Cow::Borrowed(path))
|
||||
Self::Path(path)
|
||||
}
|
||||
}
|
||||
|
||||
@ -272,7 +258,7 @@ pub struct Picker<T: 'static + Send + Sync, D: 'static> {
|
||||
file_fn: Option<FileCallback<T>>,
|
||||
/// An event handler for syntax highlighting the currently previewed file.
|
||||
preview_highlight_handler: Sender<Arc<Path>>,
|
||||
dynamic_query_handler: Option<Sender<Arc<str>>>,
|
||||
dynamic_query_handler: Option<Sender<DynamicQueryChange>>,
|
||||
}
|
||||
|
||||
impl<T: 'static + Send + Sync, D: 'static + Send + Sync> Picker<T, D> {
|
||||
@ -435,7 +421,12 @@ pub fn with_dynamic_query(
|
||||
debounce_ms: Option<u64>,
|
||||
) -> Self {
|
||||
let handler = DynamicQueryHandler::new(callback, debounce_ms).spawn();
|
||||
helix_event::send_blocking(&handler, self.primary_query());
|
||||
let event = DynamicQueryChange {
|
||||
query: self.primary_query(),
|
||||
// Treat the initial query as a paste.
|
||||
is_paste: true,
|
||||
};
|
||||
helix_event::send_blocking(&handler, event);
|
||||
self.dynamic_query_handler = Some(handler);
|
||||
self
|
||||
}
|
||||
@ -511,12 +502,12 @@ pub fn toggle_preview(&mut self) {
|
||||
|
||||
fn prompt_handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult {
|
||||
if let EventResult::Consumed(_) = self.prompt.handle_event(event, cx) {
|
||||
self.handle_prompt_change();
|
||||
self.handle_prompt_change(matches!(event, Event::Paste(_)));
|
||||
}
|
||||
EventResult::Consumed(None)
|
||||
}
|
||||
|
||||
fn handle_prompt_change(&mut self) {
|
||||
fn handle_prompt_change(&mut self, is_paste: bool) {
|
||||
// TODO: better track how the pattern has changed
|
||||
let line = self.prompt.line();
|
||||
let old_query = self.query.parse(line);
|
||||
@ -557,7 +548,11 @@ fn handle_prompt_change(&mut self) {
|
||||
// If this is a dynamic picker, notify the query hook that the primary
|
||||
// query might have been updated.
|
||||
if let Some(handler) = &self.dynamic_query_handler {
|
||||
helix_event::send_blocking(handler, self.primary_query());
|
||||
let event = DynamicQueryChange {
|
||||
query: self.primary_query(),
|
||||
is_paste,
|
||||
};
|
||||
helix_event::send_blocking(handler, event);
|
||||
}
|
||||
}
|
||||
|
||||
@ -572,7 +567,6 @@ fn get_preview<'picker, 'editor>(
|
||||
|
||||
match path_or_id {
|
||||
PathOrId::Path(path) => {
|
||||
let path = path.as_ref();
|
||||
if let Some(doc) = editor.document_by_path(path) {
|
||||
return Some((Preview::EditorDocument(doc), range));
|
||||
}
|
||||
@ -790,21 +784,25 @@ fn render_picker(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context)
|
||||
if self.columns.len() > 1 {
|
||||
let active_column = self.query.active_column(self.prompt.position());
|
||||
let header_style = cx.editor.theme.get("ui.picker.header");
|
||||
let header_column_style = cx.editor.theme.get("ui.picker.header.column");
|
||||
|
||||
table = table.header(Row::new(self.columns.iter().map(|column| {
|
||||
if column.hidden {
|
||||
Cell::default()
|
||||
} else {
|
||||
let style = if active_column.is_some_and(|name| Arc::ptr_eq(name, &column.name))
|
||||
{
|
||||
cx.editor.theme.get("ui.picker.header.active")
|
||||
table = table.header(
|
||||
Row::new(self.columns.iter().map(|column| {
|
||||
if column.hidden {
|
||||
Cell::default()
|
||||
} else {
|
||||
header_style
|
||||
};
|
||||
let style =
|
||||
if active_column.is_some_and(|name| Arc::ptr_eq(name, &column.name)) {
|
||||
cx.editor.theme.get("ui.picker.header.column.active")
|
||||
} else {
|
||||
header_column_style
|
||||
};
|
||||
|
||||
Cell::from(Span::styled(Cow::from(&*column.name), style))
|
||||
}
|
||||
})));
|
||||
Cell::from(Span::styled(Cow::from(&*column.name), style))
|
||||
}
|
||||
}))
|
||||
.style(header_style),
|
||||
);
|
||||
}
|
||||
|
||||
use tui::widgets::TableState;
|
||||
@ -1028,11 +1026,21 @@ fn handle_event(&mut self, event: &Event, ctx: &mut Context) -> EventResult {
|
||||
.filter(|_| self.prompt.line().is_empty())
|
||||
{
|
||||
self.prompt.set_line(completion.to_string(), ctx.editor);
|
||||
self.handle_prompt_change();
|
||||
// Inserting from the history register is a paste.
|
||||
self.handle_prompt_change(true);
|
||||
} else {
|
||||
if let Some(option) = self.selection() {
|
||||
(self.callback_fn)(ctx, option, Action::Replace);
|
||||
}
|
||||
if let Some(history_register) = self.prompt.history_register() {
|
||||
if let Err(err) = ctx
|
||||
.editor
|
||||
.registers
|
||||
.push(history_register, self.primary_query().to_string())
|
||||
{
|
||||
ctx.editor.set_error(err.to_string());
|
||||
}
|
||||
}
|
||||
return close_fn(self);
|
||||
}
|
||||
}
|
||||
|
@ -115,6 +115,11 @@ fn finish_debounce(&mut self) {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct DynamicQueryChange {
|
||||
pub query: Arc<str>,
|
||||
pub is_paste: bool,
|
||||
}
|
||||
|
||||
pub(super) struct DynamicQueryHandler<T: 'static + Send + Sync, D: 'static + Send + Sync> {
|
||||
callback: Arc<DynQueryCallback<T, D>>,
|
||||
// Duration used as a debounce.
|
||||
@ -137,9 +142,10 @@ pub(super) fn new(callback: DynQueryCallback<T, D>, duration_ms: Option<u64>) ->
|
||||
}
|
||||
|
||||
impl<T: 'static + Send + Sync, D: 'static + Send + Sync> AsyncHook for DynamicQueryHandler<T, D> {
|
||||
type Event = Arc<str>;
|
||||
type Event = DynamicQueryChange;
|
||||
|
||||
fn handle_event(&mut self, query: Self::Event, _timeout: Option<Instant>) -> Option<Instant> {
|
||||
fn handle_event(&mut self, change: Self::Event, _timeout: Option<Instant>) -> Option<Instant> {
|
||||
let DynamicQueryChange { query, is_paste } = change;
|
||||
if query == self.last_query {
|
||||
// If the search query reverts to the last one we requested, no need to
|
||||
// make a new request.
|
||||
@ -147,7 +153,12 @@ fn handle_event(&mut self, query: Self::Event, _timeout: Option<Instant>) -> Opt
|
||||
None
|
||||
} else {
|
||||
self.query = Some(query);
|
||||
Some(Instant::now() + self.debounce)
|
||||
if is_paste {
|
||||
self.finish_debounce();
|
||||
None
|
||||
} else {
|
||||
Some(Instant::now() + self.debounce)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user