aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--book/book.toml3
-rw-r--r--book/src/configuration.md13
-rw-r--r--book/src/from-vim.md2
-rw-r--r--book/src/install.md4
-rw-r--r--book/src/keymap.md100
-rw-r--r--book/src/themes.md202
-rw-r--r--book/theme/css/general.css13
-rw-r--r--book/theme/css/variables.css24
-rw-r--r--book/theme/highlight.css101
-rw-r--r--helix-core/src/indent.rs11
-rw-r--r--helix-core/src/syntax.rs8
-rw-r--r--helix-lsp/src/client.rs90
-rw-r--r--helix-lsp/src/lib.rs102
-rw-r--r--helix-lsp/src/transport.rs137
-rw-r--r--helix-syntax/build.rs3
m---------helix-syntax/languages/tree-sitter-julia0
-rw-r--r--helix-term/src/application.rs107
-rw-r--r--helix-term/src/job.rs4
-rw-r--r--helix-view/src/document.rs49
-rw-r--r--helix-view/src/editor.rs25
-rw-r--r--languages.toml28
-rw-r--r--runtime/queries/c/highlights.scm2
-rw-r--r--runtime/queries/go/highlights.scm39
-rw-r--r--runtime/queries/go/locals.scm30
-rw-r--r--runtime/queries/haskell/highlights.scm14
-rw-r--r--runtime/queries/javascript/highlights.scm2
-rw-r--r--runtime/queries/julia/highlights.scm82
-rw-r--r--runtime/queries/latex/highlights.scm4
-rw-r--r--runtime/queries/lua/highlights.scm18
-rw-r--r--runtime/queries/ocaml/highlights.scm8
-rw-r--r--runtime/queries/ruby/highlights.scm2
-rw-r--r--runtime/queries/rust/highlights.scm4
-rw-r--r--runtime/queries/rust/locals.scm17
-rw-r--r--runtime/queries/tsx/highlights.scm1
-rw-r--r--runtime/queries/yaml/highlights.scm2
-rw-r--r--runtime/themes/dark_plus.toml1
-rw-r--r--runtime/themes/monokai.toml1
-rw-r--r--theme.toml6
38 files changed, 777 insertions, 482 deletions
diff --git a/book/book.toml b/book/book.toml
index 3ccaf71e..2277a0bd 100644
--- a/book/book.toml
+++ b/book/book.toml
@@ -3,8 +3,9 @@ authors = ["Blaž Hrastnik"]
language = "en"
multilingual = false
src = "src"
-theme = "colibri"
edit-url-template = "https://github.com/helix-editor/helix/tree/master/book/{path}?mode=edit"
[output.html]
cname = "docs.helix-editor.com"
+default-theme = "colibri"
+preferred-dark-theme = "colibri"
diff --git a/book/src/configuration.md b/book/src/configuration.md
index 00dfbbd8..5a28362d 100644
--- a/book/src/configuration.md
+++ b/book/src/configuration.md
@@ -5,6 +5,19 @@ To override global configuration parameters, create a `config.toml` file located
* Linux and Mac: `~/.config/helix/config.toml`
* Windows: `%AppData%\helix\config.toml`
+## Editor
+
+`[editor]` section of the config.
+
+| Key | Description | Default |
+|--|--|---------|
+| `scrolloff` | Number of lines of padding around the edge of the screen when scrolling. | `3` |
+| `mouse` | Enable mouse mode. | `true` |
+| `middle-click-paste` | Middle click paste support. | `true` |
+| `scroll-lines` | Number of lines to scroll per scroll wheel step. | `3` |
+| `shell` | Shell to use when running external commands. | Unix: `["sh", "-c"]`<br/>Windows: `["cmd", "/C"]` |
+| `line-number` | Line number display (`absolute`, `relative`) | `absolute` |
+
## LSP
To display all language server messages in the status line add the following to your `config.toml`:
diff --git a/book/src/from-vim.md b/book/src/from-vim.md
index 8e9bbac3..09f33386 100644
--- a/book/src/from-vim.md
+++ b/book/src/from-vim.md
@@ -7,4 +7,6 @@ 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.
+See also Kakoune's [Migrating from Vim](https://github.com/mawww/kakoune/wiki/Migrating-from-Vim).
+
> TODO: Mention texobjects, surround, registers
diff --git a/book/src/install.md b/book/src/install.md
index cd9c980e..b9febbcc 100644
--- a/book/src/install.md
+++ b/book/src/install.md
@@ -23,7 +23,9 @@ shell for working on Helix.
### Arch Linux
-Binary packages are available on AUR:
+Releases are available in the `community` repository.
+
+Packages are also available on AUR:
- [helix-bin](https://aur.archlinux.org/packages/helix-bin/) contains the pre-built release
- [helix-git](https://aur.archlinux.org/packages/helix-git/) builds the master branch
diff --git a/book/src/keymap.md b/book/src/keymap.md
index 861e46ac..51e56eaa 100644
--- a/book/src/keymap.md
+++ b/book/src/keymap.md
@@ -4,7 +4,7 @@
### Movement
-> NOTE: `f`, `F`, `t` and `T` are not confined to the current line.
+> NOTE: Unlike vim, `f`, `F`, `t` and `T` are not confined to the current line.
| Key | Description | Command |
| ----- | ----------- | ------- |
@@ -28,14 +28,14 @@
| `PageDown` | Move page down | `page_down` |
| `Ctrl-u` | Move half page up | `half_page_up` |
| `Ctrl-d` | Move half page down | `half_page_down` |
-| `Ctrl-i` | Jump forward on the jumplist TODO: conflicts tab | `jump_forward` |
+| `Ctrl-i` | Jump forward on the jumplist | `jump_forward` |
| `Ctrl-o` | Jump backward on the jumplist | `jump_backward` |
| `v` | Enter [select (extend) mode](#select--extend-mode) | `select_mode` |
| `g` | Enter [goto mode](#goto-mode) | N/A |
| `m` | Enter [match mode](#match-mode) | N/A |
| `:` | Enter command mode | `command_mode` |
| `z` | Enter [view mode](#view-mode) | N/A |
-| `Ctrl-w` | Enter [window mode](#window-mode) (maybe will be remove for spc w w later) | N/A |
+| `Ctrl-w` | Enter [window mode](#window-mode) | N/A |
| `Space` | Enter [space mode](#space-mode) | N/A |
| `K` | Show documentation for the item under the cursor | `hover` |
@@ -66,6 +66,16 @@
| `d` | Delete selection | `delete_selection` |
| `c` | Change selection (delete and enter insert mode) | `change_selection` |
+#### Shell
+
+| Key | Description | Command |
+| ------ | ----------- | ------- |
+| <code>&#124;</code> | Pipe each selection through shell command, replacing with output | `shell_pipe` |
+| <code>A-&#124;</code> | Pipe each selection into shell command, ignoring output | `shell_pipe_to` |
+| `!` | Run shell command, inserting output before each selection | `shell_insert_output` |
+| `A-!` | Run shell command, appending output after each selection | `shell_append_output` |
+
+
### Selection manipulation
| Key | Description | Command |
@@ -87,17 +97,10 @@
| | Expand selection to parent syntax node TODO: pick a key | `expand_selection` |
| `J` | Join lines inside selection | `join_selections` |
| `K` | Keep selections matching the regex TODO: overlapped by hover help | `keep_selections` |
+| `$` | Pipe each selection into shell command, keep selections where command returned 0 | `shell_keep_pipe` |
| `Space` | Keep only the primary selection TODO: overlapped by space mode | `keep_primary_selection` |
| `Ctrl-c` | Comment/uncomment the selections | `toggle_comments` |
-### Insert Mode
-
-| Key | Description | Command |
-| ----- | ----------- | ------- |
-| `Escape` | Switch to normal mode | `normal_mode` |
-| `Ctrl-x` | Autocomplete | `completion` |
-| `Ctrl-w` | Delete previous word | `delete_word_backward` |
-
### Search
> TODO: The search implementation isn't ideal yet -- we don't support searching
@@ -110,38 +113,11 @@ in reverse, or searching via smartcase.
| `N` | Add next search match to selection | `extend_search_next` |
| `*` | Use current selection as the search pattern | `search_selection` |
-### Unimpaired
-
-Mappings in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaired)
-
-| Key | Description | Command |
-| ----- | ----------- | ------- |
-| `[d` | Go to previous diagnostic | `goto_prev_diag` |
-| `]d` | Go to next diagnostic | `goto_next_diag` |
-| `[D` | Go to first diagnostic in document | `goto_first_diag` |
-| `]D` | Go to last diagnostic in document | `goto_last_diag` |
-| `[space` | Add newline above | `add_newline_above` |
-| `]space` | Add newline below | `add_newline_below` |
-
-### Shell
+### Minor modes
-| Key | Description | Command |
-| ------ | ----------- | ------- |
-| `\|` | Pipe each selection through shell command, replacing with output | `shell_pipe` |
-| `A-\|` | Pipe each selection into shell command, ignoring output | `shell_pipe_to` |
-| `!` | Run shell command, inserting output before each selection | `shell_insert_output` |
-| `A-!` | Run shell command, appending output after each selection | `shell_append_output` |
-| `$` | Pipe each selection into shell command, keep selections where command returned 0 | `shell_keep_pipe` |
-
-## Select / extend mode
-
-I'm still pondering whether to keep this mode or not. It changes movement
-commands to extend the existing selection instead of replacing it.
-
-> NOTE: It's a bit confusing at the moment because extend hasn't been
-> implemented for all movement commands yet.
+These sub-modes are accessible from normal mode and typically switch back to normal mode after a command.
-## View mode
+#### View mode
View mode is intended for scrolling and manipulating the view without changing
the selection.
@@ -155,7 +131,7 @@ the selection.
| `j` | Scroll the view downwards | `scroll_down` |
| `k` | Scroll the view upwards | `scroll_up` |
-## Goto mode
+#### Goto mode
Jumps to various locations.
@@ -177,7 +153,7 @@ Jumps to various locations.
| `i` | Go to implementation | `goto_implementation` |
| `a` | Go to the last accessed/alternate file | `goto_last_accessed_file` |
-## Match mode
+#### Match mode
Enter this mode using `m` from normal mode. See the relavant section
in [Usage](./usage.md) for an explanation about [surround](./usage.md#surround)
@@ -192,11 +168,9 @@ and [textobject](./usage.md#textobject) usage.
| `a` `<object>` | Select around textobject | `select_textobject_around` |
| `i` `<object>` | Select inside textobject | `select_textobject_inner` |
-## Object mode
-
TODO: Mappings for selecting syntax nodes (a superset of `[`).
-## Window mode
+#### Window mode
This layer is similar to vim keybindings as kakoune does not support window.
@@ -207,9 +181,9 @@ This layer is similar to vim keybindings as kakoune does not support window.
| `h`, `Ctrl-h` | Horizontal bottom split | `hsplit` |
| `q`, `Ctrl-q` | Close current window | `wclose` |
-## Space mode
+#### Space mode
-This layer is a kludge of mappings I had under leader key in neovim.
+This layer is a kludge of mappings, mostly pickers.
| Key | Description | Command |
| ----- | ----------- | ------- |
@@ -226,6 +200,36 @@ This layer is a kludge of mappings I had under leader key in neovim.
| `Y` | Yank main selection to clipboard | `yank_main_selection_to_clipboard` |
| `R` | Replace selections by clipboard contents | `replace_selections_with_clipboard` |
+
+#### Unimpaired
+
+Mappings in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaired).
+
+| Key | Description | Command |
+| ----- | ----------- | ------- |
+| `[d` | Go to previous diagnostic | `goto_prev_diag` |
+| `]d` | Go to next diagnostic | `goto_next_diag` |
+| `[D` | Go to first diagnostic in document | `goto_first_diag` |
+| `]D` | Go to last diagnostic in document | `goto_last_diag` |
+| `[space` | Add newline above | `add_newline_above` |
+| `]space` | Add newline below | `add_newline_below` |
+
+## Insert Mode
+
+| Key | Description | Command |
+| ----- | ----------- | ------- |
+| `Escape` | Switch to normal mode | `normal_mode` |
+| `Ctrl-x` | Autocomplete | `completion` |
+| `Ctrl-w` | Delete previous word | `delete_word_backward` |
+
+## Select / extend mode
+
+I'm still pondering whether to keep this mode or not. It changes movement
+commands (including goto) to extend the existing selection instead of replacing it.
+
+> NOTE: It's a bit confusing at the moment because extend hasn't been
+> implemented for all movement commands yet.
+
# Picker
Keys to use within picker. Remapping currently not supported.
diff --git a/book/src/themes.md b/book/src/themes.md
index 804baa1c..fe5259d5 100644
--- a/book/src/themes.md
+++ b/book/src/themes.md
@@ -30,85 +30,9 @@ if the key contains a dot `'.'`, it must be quoted to prevent it being parsed as
"key.key" = "#ffffff"
```
-Possible modifiers:
+### Color palettes
-| Modifier |
-| --- |
-| `bold` |
-| `dim` |
-| `italic` |
-| `underlined` |
-| `slow\_blink` |
-| `rapid\_blink` |
-| `reversed` |
-| `hidden` |
-| `crossed\_out` |
-
-Possible keys:
-
-| Key | Notes |
-| --- | --- |
-| `attribute` | |
-| `keyword` | |
-| `keyword.directive` | Preprocessor directives (\#if in C) |
-| `keyword.control` | Control flow |
-| `namespace` | |
-| `punctuation` | |
-| `punctuation.delimiter` | |
-| `operator` | |
-| `special` | |
-| `property` | |
-| `variable` | |
-| `variable.parameter` | |
-| `type` | |
-| `type.builtin` | |
-| `type.enum.variant` | Enum variants |
-| `constructor` | |
-| `function` | |
-| `function.macro` | |
-| `function.builtin` | |
-| `comment` | |
-| `variable.builtin` | |
-| `constant` | |
-| `constant.builtin` | |
-| `string` | |
-| `number` | |
-| `escape` | Escaped characters |
-| `label` | For lifetimes |
-| `module` | |
-| `ui.background` | |
-| `ui.cursor` | |
-| `ui.cursor.insert` | |
-| `ui.cursor.select` | |
-| `ui.cursor.match` | Matching bracket etc. |
-| `ui.cursor.primary` | Cursor with primary selection |
-| `ui.linenr` | |
-| `ui.linenr.selected` | |
-| `ui.statusline` | |
-| `ui.statusline.inactive` | |
-| `ui.popup` | |
-| `ui.window` | |
-| `ui.help` | |
-| `ui.text` | |
-| `ui.text.focus` | |
-| `ui.info` | |
-| `ui.info.text` | |
-| `ui.menu` | |
-| `ui.menu.selected` | |
-| `ui.selection` | For selections in the editing area |
-| `ui.selection.primary` | |
-| `warning` | LSP warning |
-| `error` | LSP error |
-| `info` | LSP info |
-| `hint` | LSP hint |
-
-These keys match [tree-sitter scopes](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#theme). We half-follow the common scopes from [macromates language grammars](https://macromates.com/manual/en/language_grammars) with some differences.
-
-For a given highlight produced, styling will be determined based on the longest matching theme key. So it's enough to provide function to highlight `function.macro` and `function.builtin` as well, but you can use more specific scopes to highlight specific cases differently.
-
-## Color palettes
-
-You can define a palette of named colors, and refer to them from the
+It's recommended define a palette of named colors, and refer to them from the
configuration values in your theme. To do this, add a table called
`palette` to your theme file:
@@ -146,3 +70,125 @@ over it and is merged into the default palette.
| `light-cyan` |
| `light-gray` |
| `white` |
+
+### Modifiers
+
+The following values may be used as modifiers.
+
+Less common modifiers might not be supported by your terminal emulator.
+
+| Modifier |
+| --- |
+| `bold` |
+| `dim` |
+| `italic` |
+| `underlined` |
+| `slow_blink` |
+| `rapid_blink` |
+| `reversed` |
+| `hidden` |
+| `crossed_out` |
+
+### Scopes
+
+The following is a list of scopes available to use for styling.
+
+#### Syntax highlighting
+
+These keys match [tree-sitter scopes](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#theme).
+
+For a given highlight produced, styling will be determined based on the longest matching theme key. For example, the highlight `function.builtin.static` would match the key `function.builtin` rather than `function`.
+
+We use a similar set of scopes as
+[SublimeText](https://www.sublimetext.com/docs/scope_naming.html). See also
+[TextMate](https://macromates.com/manual/en/language_grammars) scopes.
+
+- `escape` (TODO: rename to (constant).character.escape)
+
+- `type` - Types
+ - `builtin` - Primitive types provided by the language (`int`, `usize`)
+
+- `constant` (TODO: constant.other.placeholder for %v)
+ - `builtin` Special constants provided by the language (`true`, `false`, `nil` etc)
+ - `boolean`
+ - `character`
+
+- `number` (TODO: rename to constant.number/.numeric.{integer, float, complex})
+- `string` (TODO: string.quoted.{single, double}, string.raw/.unquoted)?
+ - `regexp` - Regular expressions
+ - `special`
+ - `path`
+ - `url`
+
+- `comment` - Code comments
+ - `line` - Single line comments (`//`)
+ - `block` - Block comments (e.g. (`/* */`)
+ - `documentation` - Documentation comments (e.g. `///` in Rust)
+
+- `variable` - Variables
+ - `builtin` - Reserved language variables (`self`, `this`, `super`, etc)
+ - `parameter` - Function parameters
+ - `property`
+ - `function` (TODO: ?)
+
+- `label`
+
+- `punctuation`
+ - `delimiter` - Commas, colons
+ - `bracket` - Parentheses, angle brackets, etc.
+
+- `keyword`
+ - `control`
+ - `conditional` - `if`, `else`
+ - `repeat` - `for`, `while`, `loop`
+ - `import` - `import`, `export`
+ - (TODO: return?)
+ - `directive` - Preprocessor directives (`#if` in C)
+ - `function` - `fn`, `func`
+
+- `operator` - `||`, `+=`, `>`, `or`
+
+- `function`
+ - `builtin`
+ - `method`
+ - `macro`
+ - `special` (preprocesor in C)
+
+- `tag` - Tags (e.g. `<body>` in HTML)
+
+- `namespace`
+
+#### Interface
+
+These scopes are used for theming the editor interface.
+
+
+| Key | Notes |
+| --- | --- |
+| `ui.background` | |
+| `ui.cursor` | |
+| `ui.cursor.insert` | |
+| `ui.cursor.select` | |
+| `ui.cursor.match` | Matching bracket etc. |
+| `ui.cursor.primary` | Cursor with primary selection |
+| `ui.linenr` | |
+| `ui.linenr.selected` | |
+| `ui.statusline` | Statusline |
+| `ui.statusline.inactive` | Statusline (unfocused document) |
+| `ui.popup` | |
+| `ui.window` | |
+| `ui.help` | |
+| `ui.text` | |
+| `ui.text.focus` | |
+| `ui.info` | |
+| `ui.info.text` | |
+| `ui.menu` | |
+| `ui.menu.selected` | |
+| `ui.selection` | For selections in the editing area |
+| `ui.selection.primary` | |
+| `warning` | Diagnostics warning |
+| `error` | Diagnostics error |
+| `info` | Diagnostics info |
+| `hint` | Diagnostics hint |
+
+
diff --git a/book/theme/css/general.css b/book/theme/css/general.css
index 7749bded..ddc2387a 100644
--- a/book/theme/css/general.css
+++ b/book/theme/css/general.css
@@ -114,6 +114,19 @@ h6:target::before {
margin-bottom: .875em;
}
+.content ul li {
+margin-bottom: .25rem;
+}
+.content ul {
+ list-style-type: square;
+}
+.content ul ul, .content ol ul {
+ margin-bottom: .5rem;
+}
+.content li p {
+ margin-bottom: .5em;
+}
+
.content p { line-height: 1.45em; }
.content ol { line-height: 1.45em; }
.content ul { line-height: 1.45em; }
diff --git a/book/theme/css/variables.css b/book/theme/css/variables.css
index a49d6794..db1a11b8 100644
--- a/book/theme/css/variables.css
+++ b/book/theme/css/variables.css
@@ -69,7 +69,7 @@
--links: #2b79a2;
- --inline-code-color: #c5c8c6;;
+ --inline-code-color: #c5c8c6;
--theme-popup-bg: #141617;
--theme-popup-border: #43484d;
@@ -110,7 +110,7 @@
--links: #20609f;
- --inline-code-color: #301900;
+ --inline-code-color: #a39e9b;
--theme-popup-bg: #fafafa;
--theme-popup-border: #cccccc;
@@ -151,7 +151,7 @@
--links: #2b79a2;
- --inline-code-color: #c5c8c6;;
+ --inline-code-color: #c5c8c6;
--theme-popup-bg: #161923;
--theme-popup-border: #737480;
@@ -192,7 +192,7 @@
--links: #2b79a2;
- --inline-code-color: #6e6b5e;
+ --inline-code-color: #c5c8c6;
--theme-popup-bg: #e1e1db;
--theme-popup-border: #b38f6b;
@@ -234,7 +234,7 @@
--links: #2b79a2;
- --inline-code-color: #c5c8c6;;
+ --inline-code-color: #6e6b5e;
--theme-popup-bg: #141617;
--theme-popup-border: #43484d;
@@ -261,6 +261,7 @@
.colibri {
--bg: #3b224c;
--fg: #bcbdd0;
+ --heading-fg: #fff;
--sidebar-bg: #281733;
--sidebar-fg: #c8c9db;
@@ -276,18 +277,19 @@
/* --links: #a4a0e8; */
--links: #ECCDBA;
- --inline-code-color: #c5c8c6;;
+ --inline-code-color: hsl(48.7, 7.8%, 70%);
--theme-popup-bg: #161923;
--theme-popup-border: #737480;
--theme-hover: rgba(0,0,0, .2);
- --quote-bg: hsl(226, 15%, 17%);
+ --quote-bg: #281733;
--quote-border: hsl(226, 15%, 22%);
- --table-border-color: hsl(226, 23%, 16%);
- --table-header-bg: hsl(226, 23%, 31%);
+ --table-border-color: hsl(226, 23%, 76%);
+ --table-header-bg: hsla(226, 23%, 31%, 0);
--table-alternate-bg: hsl(226, 23%, 14%);
+ --table-border-line: hsla(201deg, 20%, 92%, 0.2);
--searchbar-border-color: #aaa;
--searchbar-bg: #aeaec6;
@@ -300,6 +302,7 @@
}
.colibri {
+/*
--bg: #ffffff;
--fg: #452859;
--fg: #5a5977;
@@ -318,7 +321,7 @@
--links: #6F44F0;
- --inline-code-color: #697C81;
+ --inline-code-color: #a39e9b;
--theme-popup-bg: #161923;
--theme-popup-border: #737480;
@@ -341,4 +344,5 @@
--searchresults-border-color: #5c5c68;
--searchresults-li-bg: #242430;
--search-mark-bg: #a2cff5;
+*/
}
diff --git a/book/theme/highlight.css b/book/theme/highlight.css
index c2343227..8dce7d65 100644
--- a/book/theme/highlight.css
+++ b/book/theme/highlight.css
@@ -1,83 +1,56 @@
-/*
- * An increased contrast highlighting scheme loosely based on the
- * "Base16 Atelier Dune Light" theme by Bram de Haan
- * (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune)
- * Original Base16 color scheme by Chris Kempson
- * (https://github.com/chriskempson/base16)
- */
-
-/* Comment */
+pre code.hljs {
+ display:block;
+ overflow-x:auto;
+ padding:1em
+}
+code.hljs {
+ padding:3px 5px
+}
+.hljs {
+ background:#2f1e2e;
+ color:#a39e9b
+}
.hljs-comment,
.hljs-quote {
- color: #575757;
+ color:#8d8687
}
-
-/* Red */
-.hljs-variable,
-.hljs-template-variable,
-.hljs-attribute,
-.hljs-tag,
-.hljs-name,
-.hljs-regexp,
.hljs-link,
+.hljs-meta,
.hljs-name,
+.hljs-regexp,
+.hljs-selector-class,
.hljs-selector-id,
-.hljs-selector-class {
- color: #d70025;
+.hljs-tag,
+.hljs-template-variable,
+.hljs-variable {
+ color:#ef6155
}
-
-/* Orange */
-.hljs-number,
-.hljs-meta,
.hljs-built_in,
-.hljs-builtin-name,
+.hljs-deletion,
.hljs-literal,
-.hljs-type,
-.hljs-params {
- color: #b21e00;
+.hljs-number,
+.hljs-params,
+.hljs-type {
+ color:#f99b15
}
-
-/* Green */
-.hljs-string,
-.hljs-symbol,
-.hljs-bullet {
- color: #008200;
+.hljs-attribute,
+.hljs-section,
+.hljs-title {
+ color:#fec418
}
-
-/* Blue */
-.hljs-title,
-.hljs-section {
- color: #0030f2;
+.hljs-addition,
+.hljs-bullet,
+.hljs-string,
+.hljs-symbol {
+ color:#48b685
}
-
-/* Purple */
.hljs-keyword,
.hljs-selector-tag {
- color: #9d00ec;
-}
-
-.hljs {
- display: block;
- overflow-x: auto;
- background: #f6f7f6;
- color: #000;
- padding: 0.5em;
+ color:#815ba4
}
-
.hljs-emphasis {
- font-style: italic;
+ font-style:italic
}
-
.hljs-strong {
- font-weight: bold;
-}
-
-.hljs-addition {
- color: #22863a;
- background-color: #f0fff4;
-}
-
-.hljs-deletion {
- color: #b31d28;
- background-color: #ffeef0;
+ font-weight:700
}
diff --git a/helix-core/src/indent.rs b/helix-core/src/indent.rs
index f5f36aca..55802059 100644
--- a/helix-core/src/indent.rs
+++ b/helix-core/src/indent.rs
@@ -316,8 +316,12 @@ pub fn suggested_indent_for_pos(
pub fn get_scopes(syntax: Option<&Syntax>, text: RopeSlice, pos: usize) -> Vec<&'static str> {
let mut scopes = Vec::new();
if let Some(syntax) = syntax {
- let byte_start = text.char_to_byte(pos);
- let node = match get_highest_syntax_node_at_bytepos(syntax, byte_start) {
+ let pos = text.char_to_byte(pos);
+ let mut node = match syntax
+ .tree()
+ .root_node()
+ .descendant_for_byte_range(pos, pos)
+ {
Some(node) => node,
None => return scopes,
};
@@ -325,7 +329,8 @@ pub fn get_scopes(syntax: Option<&Syntax>, text: RopeSlice, pos: usize) -> Vec<&
scopes.push(node.kind());
while let Some(parent) = node.parent() {
- scopes.push(parent.kind())
+ scopes.push(parent.kind());
+ node = parent;
}
}
diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs
index a7a5d022..1afe0e25 100644
--- a/helix-core/src/syntax.rs
+++ b/helix-core/src/syntax.rs
@@ -144,8 +144,12 @@ impl LanguageConfiguration {
&highlights_query,
&injections_query,
&locals_query,
- )
- .unwrap(); // TODO: no unwrap
+ );
+
+ let config = match config {
+ Ok(config) => config,
+ Err(err) => panic!("{}", err),
+ }; // TODO: avoid panic
config.configure(scopes);
Some(Arc::new(config))
}
diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs
index d0a8183f..f2bb0059 100644
--- a/helix-lsp/src/client.rs
+++ b/helix-lsp/src/client.rs
@@ -9,11 +9,17 @@ use lsp_types as lsp;
use serde_json::Value;
use std::future::Future;
use std::process::Stdio;
-use std::sync::atomic::{AtomicU64, Ordering};
+use std::sync::{
+ atomic::{AtomicU64, Ordering},
+ Arc,
+};
use tokio::{
io::{BufReader, BufWriter},
process::{Child, Command},
- sync::mpsc::{channel, UnboundedReceiver, UnboundedSender},
+ sync::{
+ mpsc::{channel, UnboundedReceiver, UnboundedSender},
+ Notify, OnceCell,
+ },
};
#[derive(Debug)]
@@ -22,18 +28,19 @@ pub struct Client {
_process: Child,
server_tx: UnboundedSender<Payload>,
request_counter: AtomicU64,
- capabilities: Option<lsp::ServerCapabilities>,
+ pub(crate) capabilities: OnceCell<lsp::ServerCapabilities>,
offset_encoding: OffsetEncoding,
config: Option<Value>,
}
impl Client {
+ #[allow(clippy::type_complexity)]
pub fn start(
cmd: &str,
args: &[String],
config: Option<Value>,
id: usize,
- ) -> Result<(Self, UnboundedReceiver<(usize, Call)>)> {
+ ) -> Result<(Self, UnboundedReceiver<(usize, Call)>, Arc<Notify>)> {
let process = Command::new(cmd)
.args(args)
.stdin(Stdio::piped())
@@ -50,22 +57,20 @@ impl Client {
let reader = BufReader::new(process.stdout.take().expect("Failed to open stdout"));
let stderr = BufReader::new(process.stderr.take().expect("Failed to open stderr"));
- let (server_rx, server_tx) = Transport::start(reader, writer, stderr, id);
+ let (server_rx, server_tx, initialize_notify) =
+ Transport::start(reader, writer, stderr, id);
let client = Self {
id,
_process: process,
server_tx,
request_counter: AtomicU64::new(0),
- capabilities: None,
+ capabilities: OnceCell::new(),
offset_encoding: OffsetEncoding::Utf8,
config,
};
- // TODO: async client.initialize()
- // maybe use an arc<atomic> flag
-
- Ok((client, server_rx))
+ Ok((client, server_rx, initialize_notify))
}
pub fn id(&self) -> usize {
@@ -88,9 +93,13 @@ impl Client {
}
}
+ pub fn is_initialized(&self) -> bool {
+ self.capabilities.get().is_some()
+ }
+
pub fn capabilities(&self) -> &lsp::ServerCapabilities {
self.capabilities
- .as_ref()
+ .get()
.expect("language server not yet initialized!")
}
@@ -143,7 +152,8 @@ impl Client {
})
.map_err(|e| Error::Other(e.into()))?;
- timeout(Duration::from_secs(2), rx.recv())
+ // TODO: specifiable timeout, delay other calls until initialize success
+ timeout(Duration::from_secs(20), rx.recv())
.await
.map_err(|_| Error::Timeout)? // return Timeout
.ok_or(Error::StreamClosed)?
@@ -151,7 +161,7 @@ impl Client {
}
/// Send a RPC notification to the language server.
- fn notify<R: lsp::notification::Notification>(
+ pub fn notify<R: lsp::notification::Notification>(
&self,
params: R::Params,
) -> impl Future<Output = Result<()>>
@@ -213,7 +223,7 @@ impl Client {
// General messages
// -------------------------------------------------------------------------------------------
- pub(crate) async fn initialize(&mut self) -> Result<()> {
+ pub(crate) async fn initialize(&self) -> Result<lsp::InitializeResult> {
// TODO: delay any requests that are triggered prior to initialize
let root = find_root(None).and_then(|root| lsp::Url::from_file_path(root).ok());
@@ -281,14 +291,7 @@ impl Client {
locale: None, // TODO
};
- let response = self.request::<lsp::request::Initialize>(params).await?;
- self.capabilities = Some(response.capabilities);
-
- // next up, notify<initialized>
- self.notify::<lsp::notification::Initialized>(lsp::InitializedParams {})
- .await?;
-
- Ok(())
+ self.request::<lsp::request::Initialize>(params).await
}
pub async fn shutdown(&self) -> Result<()> {
@@ -445,7 +448,7 @@ impl Client {
) -> Option<impl Future<Output = Result<()>>> {
// figure out what kind of sync the server supports
- let capabilities = self.capabilities.as_ref().unwrap();
+ let capabilities = self.capabilities.get().unwrap();
let sync_capabilities = match capabilities.text_document_sync {
Some(lsp::TextDocumentSyncCapability::Kind(kind))
@@ -463,7 +466,7 @@ impl Client {
// range = None -> whole document
range: None, //Some(Range)
range_length: None, // u64 apparently deprecated
- text: "".to_string(),
+ text: new_text.to_string(),
}]
}
lsp::TextDocumentSyncKind::Incremental => {
@@ -491,12 +494,12 @@ impl Client {
// will_save / will_save_wait_until
- pub async fn text_document_did_save(
+ pub fn text_document_did_save(
&self,
text_document: lsp::TextDocumentIdentifier,
text: &Rope,
- ) -> Result<()> {
- let capabilities = self.capabilities.as_ref().unwrap();
+ ) -> Option<impl Future<Output = Result<()>>> {
+ let capabilities = self.capabilities.get().unwrap();
let include_text = match &capabilities.text_document_sync {
Some(lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
@@ -508,17 +511,18 @@ impl Client {
include_text,
}) => include_text.unwrap_or(false),
// Supported(false)
- _ => return Ok(()),
+ _ => return None,
},
// unsupported
- _ => return Ok(()),
+ _ => return None,
};
- self.notify::<lsp::notification::DidSaveTextDocument>(lsp::DidSaveTextDocumentParams {
- text_document,
- text: include_text.then(|| text.into()),
- })
- .await
+ Some(self.notify::<lsp::notification::DidSaveTextDocument>(
+ lsp::DidSaveTextDocumentParams {
+ text_document,
+ text: include_text.then(|| text.into()),
+ },
+ ))
}
pub fn completion(
@@ -584,19 +588,19 @@ impl Client {
// formatting
- pub async fn text_document_formatting(
+ pub fn text_document_formatting(
&self,
text_document: lsp::TextDocumentIdentifier,
options: lsp::FormattingOptions,
work_done_token: Option<lsp::ProgressToken>,
- ) -> anyhow::Result<Vec<lsp::TextEdit>> {
- let capabilities = self.capabilities.as_ref().unwrap();
+ ) -> Option<impl Future<Output = Result<Vec<lsp::TextEdit>>>> {
+ let capabilities = self.capabilities.get().unwrap();
// check if we're able to format
match capabilities.document_formatting_provider {
Some(lsp::OneOf::Left(true)) | Some(lsp::OneOf::Right(_)) => (),
// None | Some(false)
- _ => return Ok(Vec::new()),
+ _ => return None,
};
// TODO: return err::unavailable so we can fall back to tree sitter formatting
@@ -606,9 +610,13 @@ impl Client {
work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
};
- let response = self.request::<lsp::request::Formatting>(params).await?;
+ let request = self.call::<lsp::request::Formatting>(params);
- Ok(response.unwrap_or_default())
+ Some(async move {
+ let json = request.await?;
+ let response: Vec<lsp::TextEdit> = serde_json::from_value(json)?;
+ Ok(response)
+ })
}
pub async fn text_document_range_formatting(
@@ -618,7 +626,7 @@ impl Client {
options: lsp::FormattingOptions,
work_done_token: Option<lsp::ProgressToken>,
) -> anyhow::Result<Vec<lsp::TextEdit>> {
- let capabilities = self.capabilities.as_ref().unwrap();
+ let capabilities = self.capabilities.get().unwrap();
// check if we're able to format
match capabilities.document_range_formatting_provider {
diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs
index 72606b70..7357c885 100644
--- a/helix-lsp/src/lib.rs
+++ b/helix-lsp/src/lib.rs
@@ -226,6 +226,8 @@ impl MethodCall {
#[derive(Debug, PartialEq, Clone)]
pub enum Notification {
+ // we inject this notification to signal the LSP is ready
+ Initialized,
PublishDiagnostics(lsp::PublishDiagnosticsParams),
ShowMessage(lsp::ShowMessageParams),
LogMessage(lsp::LogMessageParams),
@@ -237,6 +239,7 @@ impl Notification {
use lsp::notification::Notification as _;
let notification = match method {
+ lsp::notification::Initialized::METHOD => Self::Initialized,
lsp::notification::PublishDiagnostics::METHOD => {
let params: lsp::PublishDiagnosticsParams = params
.parse()
@@ -294,7 +297,7 @@ impl Registry {
}
}
- pub fn get_by_id(&mut self, id: usize) -> Option<&Client> {
+ pub fn get_by_id(&self, id: usize) -> Option<&Client> {
self.inner
.values()
.find(|(client_id, _)| client_id == &id)
@@ -302,33 +305,52 @@ impl Registry {
}
pub fn get(&mut self, language_config: &LanguageConfiguration) -> Result<Arc<Client>> {
- if let Some(config) = &language_config.language_server {
- // avoid borrow issues
- let inner = &mut self.inner;
- let s_incoming = &mut self.incoming;
-
- match inner.entry(language_config.scope.clone()) {
- Entry::Occupied(entry) => Ok(entry.get().1.clone()),
- Entry::Vacant(entry) => {
- // initialize a new client
- let id = self.counter.fetch_add(1, Ordering::Relaxed);
- let (mut client, incoming) = Client::start(
- &config.command,
- &config.args,
- serde_json::from_str(language_config.config.as_deref().unwrap_or("")).ok(),
- id,
- )?;
- // TODO: run this async without blocking
- futures_executor::block_on(client.initialize())?;
- s_incoming.push(UnboundedReceiverStream::new(incoming));
- let client = Arc::new(client);
-
- entry.insert((id, client.clone()));
- Ok(client)
- }
+ let config = match &language_config.language_server {
+ Some(config) => config,
+ None => return Err(Error::LspNotDefined),
+ };
+
+ match self.inner.entry(language_config.scope.clone()) {
+ Entry::Occupied(entry) => Ok(entry.get().1.clone()),
+ Entry::Vacant(entry) => {
+ // initialize a new client
+ let id = self.counter.fetch_add(1, Ordering::Relaxed);
+ let (client, incoming, initialize_notify) = Client::start(
+ &config.command,
+ &config.args,
+ serde_json::from_str(language_config.config.as_deref().unwrap_or("")).ok(),
+ id,
+ )?;
+ self.incoming.push(UnboundedReceiverStream::new(incoming));
+ let client = Arc::new(client);
+
+ // Initialize the client asynchronously
+ let _client = client.clone();
+ tokio::spawn(async move {
+ use futures_util::TryFutureExt;
+ let value = _client
+ .capabilities
+ .get_or_try_init(|| {
+ _client
+ .initialize()
+ .map_ok(|response| response.capabilities)
+ })
+ .await;
+
+ value.expect("failed to initialize capabilities");
+
+ // next up, notify<initialized>
+ _client
+ .notify::<lsp::notification::Initialized>(lsp::InitializedParams {})
+ .await
+ .unwrap();
+
+ initialize_notify.notify_one();
+ });
+
+ entry.insert((id, client.clone()));
+ Ok(client)
}
- } else {
- Err(Error::LspNotDefined)
}
}
@@ -415,32 +437,6 @@ impl LspProgressMap {
}
}
-// REGISTRY = HashMap<LanguageId, Lazy/OnceCell<Arc<RwLock<Client>>>
-// spawn one server per language type, need to spawn one per workspace if server doesn't support
-// workspaces
-//
-// could also be a client per root dir
-//
-// storing a copy of Option<Arc<RwLock<Client>>> on Document would make the LSP client easily
-// accessible during edit/save callbacks
-//
-// the event loop needs to process all incoming streams, maybe we can just have that be a separate
-// task that's continually running and store the state on the client, then use read lock to
-// retrieve data during render
-// -> PROBLEM: how do you trigger an update on the editor side when data updates?
-//
-// -> The data updates should pull all events until we run out so we don't frequently re-render
-//
-//
-// v2:
-//
-// there should be a registry of lsp clients, one per language type (or workspace).
-// the clients should lazy init on first access
-// the client.initialize() should be called async and we buffer any requests until that completes
-// there needs to be a way to process incoming lsp messages from all clients.
-// -> notifications need to be dispatched to wherever
-// -> requests need to generate a reply and travel back to the same lsp!
-
#[cfg(test)]
mod tests {
use super::{lsp, util::*, OffsetEncoding};
diff --git a/helix-lsp/src/transport.rs b/helix-lsp/src/transport.rs
index 67b7b48f..6e28094d 100644
--- a/helix-lsp/src/transport.rs
+++ b/helix-lsp/src/transport.rs
@@ -1,7 +1,7 @@
-use crate::Result;
+use crate::{Error, Result};
use anyhow::Context;
use jsonrpc_core as jsonrpc;
-use log::{debug, error, info, warn};
+use log::{error, info};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
@@ -11,7 +11,7 @@ use tokio::{
process::{ChildStderr, ChildStdin, ChildStdout},
sync::{
mpsc::{unbounded_channel, Sender, UnboundedReceiver, UnboundedSender},
- Mutex,
+ Mutex, Notify,
},
};
@@ -51,9 +51,11 @@ impl Transport {
) -> (
UnboundedReceiver<(usize, jsonrpc::Call)>,
UnboundedSender<Payload>,
+ Arc<Notify>,
) {
let (client_tx, rx) = unbounded_channel();
let (tx, client_rx) = unbounded_channel();
+ let notify = Arc::new(Notify::new());
let transport = Self {
id,
@@ -62,11 +64,21 @@ impl Transport {
let transport = Arc::new(transport);
- tokio::spawn(Self::recv(transport.clone(), server_stdout, client_tx));
+ tokio::spawn(Self::recv(
+ transport.clone(),
+ server_stdout,
+ client_tx.clone(),
+ ));
tokio::spawn(Self::err(transport.clone(), server_stderr));
- tokio::spawn(Self::send(transport, server_stdin, client_rx));
-
- (rx, tx)
+ tokio::spawn(Self::send(
+ transport,
+ server_stdin,
+ client_tx,
+ client_rx,
+ notify.clone(),
+ ));
+
+ (rx, tx, notify)
}
async fn recv_server_message(
@@ -76,14 +88,18 @@ impl Transport {
let mut content_length = None;
loop {
buffer.truncate(0);
- reader.read_line(buffer).await?;
- let header = buffer.trim();
+ if reader.read_line(buffer).await? == 0 {
+ return Err(Error::StreamClosed);
+ };
+
+ // debug!("<- header {:?}", buffer);
- if header.is_empty() {
+ if buffer == "\r\n" {
+ // look for an empty CRLF line
break;
}
- debug!("<- header {}", header);
+ let header = buffer.trim();
let parts = header.split_once(": ");
@@ -96,7 +112,8 @@ impl Transport {
// Workaround: Some non-conformant language servers will output logging and other garbage
// into the same stream as JSON-RPC messages. This can also happen from shell scripts that spawn
// the server. Skip such lines and log a warning.
- warn!("Failed to parse header: {:?}", header);
+
+ // warn!("Failed to parse header: {:?}", header);
}
}
}
@@ -121,8 +138,10 @@ impl Transport {
buffer: &mut String,
) -> Result<()> {
buffer.truncate(0);
- err.read_line(buffer).await?;
- error!("err <- {}", buffer);
+ if err.read_line(buffer).await? == 0 {
+ return Err(Error::StreamClosed);
+ };
+ error!("err <- {:?}", buffer);
Ok(())
}
@@ -255,16 +274,90 @@ impl Transport {
async fn send(
transport: Arc<Self>,
mut server_stdin: BufWriter<ChildStdin>,
+ client_tx: UnboundedSender<(usize, jsonrpc::Call)>,
mut client_rx: UnboundedReceiver<Payload>,
+ initialize_notify: Arc<Notify>,
) {
- while let Some(msg) = client_rx.recv().await {
- match transport
- .send_payload_to_server(&mut server_stdin, msg)
- .await
- {
- Ok(_) => {}
- Err(err) => {
- error!("err: <- {:?}", err);
+ let mut pending_messages: Vec<Payload> = Vec::new();
+ let mut is_pending = true;
+
+ // Determine if a message is allowed to be sent early
+ fn is_initialize(payload: &Payload) -> bool {
+ use lsp_types::{
+ notification::{Initialized, Notification},
+ request::{Initialize, Request},
+ };
+ match payload {
+ Payload::Request {
+ value: jsonrpc::MethodCall { method, .. },
+ ..
+ } if method == Initialize::METHOD => true,
+ Payload::Notification(jsonrpc::Notification { method, .. })
+ if method == Initialized::METHOD =>
+ {
+ true
+ }
+ _ => false,
+ }
+ }
+
+ // TODO: events that use capabilities need to do the right thing
+
+ loop {
+ tokio::select! {
+ biased;
+ _ = initialize_notify.notified() => { // TODO: notified is technically not cancellation safe
+ // 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(),
+ params: jsonrpc::Params::None,
+ }));
+ match transport.process_server_message(&client_tx, notification).await {
+ Ok(_) => {}
+ Err(err) => {
+ error!("err: <- {:?}", err);
+ }
+ }
+
+ // drain the pending queue and send payloads to server
+ for msg in pending_messages.drain(..) {
+ log::info!("Draining pending message {:?}", msg);
+ match transport.send_payload_to_server(&mut server_stdin, msg).await {
+ Ok(_) => {}
+ Err(err) => {
+ error!("err: <- {:?}", err);
+ }
+ }
+ }
+ }
+ msg = client_rx.recv() => {
+ if let Some(msg) = msg {
+ if is_pending && !is_initialize(&msg) {
+ // ignore notifications
+ if let Payload::Notification(_) = msg {
+ continue;
+ }
+
+ log::info!("Language server not initialized, delaying request");
+ pending_messages.push(msg);
+ } else {
+ match transport.send_payload_to_server(&mut server_stdin, msg).await {
+ Ok(_) => {}
+ Err(err) => {
+ error!("err: <- {:?}", err);
+ }
+ }
+ }
+ } else {
+ // channel closed
+ break;
+ }
}
}
}
diff --git a/helix-syntax/build.rs b/helix-syntax/build.rs
index 473646fd..28f85e74 100644
--- a/helix-syntax/build.rs
+++ b/helix-syntax/build.rs
@@ -158,10 +158,9 @@ fn build_dir(dir: &str, language: &str) {
.is_none()
{
eprintln!(
- "The directory {} is empty, did you use 'git clone --recursive'?",
+ "The directory {} is empty, you probably need to use 'git submodule update --init --recursive'?",
dir
);
- eprintln!("You can fix in using 'git submodule init && git submodule update --recursive'.");
std::process::exit(1);
}
diff --git a/helix-syntax/languages/tree-sitter-julia b/helix-syntax/languages/tree-sitter-julia
-Subproject 0ba7a24b062b671263ae08e707e9e94383b25bb
+Subproject 12ea597262125fc22fd2e91aa953ac69b19c26c
diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs
index 1fcca681..e21c5504 100644
--- a/helix-term/src/application.rs
+++ b/helix-term/src/application.rs
@@ -4,7 +4,7 @@ use helix_view::{theme, Editor};
use crate::{args::Args, compositor::Compositor, config::Config, job::Jobs, ui};
-use log::error;
+use log::{error, warn};
use std::{
io::{stdout, Write},
@@ -275,16 +275,42 @@ impl Application {
};
match notification {
- Notification::PublishDiagnostics(params) => {
- let path = Some(params.uri.to_file_path().unwrap());
+ Notification::Initialized => {
+ let language_server =
+ match self.editor.language_servers.get_by_id(server_id) {
+ Some(language_server) => language_server,
+ None => {
+ warn!("can't find language server with id `{}`", server_id);
+ return;
+ }
+ };
+
+ let docs = self.editor.documents().filter(|doc| {
+ doc.language_server().map(|server| server.id()) == Some(server_id)
+ });
- let doc = self
- .editor
- .documents
- .iter_mut()
- .find(|(_, doc)| doc.path() == path.as_ref());
+ // trigger textDocument/didOpen for docs that are already open
+ for doc in docs {
+ // TODO: extract and share with editor.open
+ let language_id = doc
+ .language()
+ .and_then(|s| s.split('.').last()) // source.rust
+ .map(ToOwned::to_owned)
+ .unwrap_or_default();
+
+ tokio::spawn(language_server.text_document_did_open(
+ doc.url().unwrap(),
+ doc.version(),
+ doc.text(),
+ language_id,
+ ));
+ }
+ }
+ Notification::PublishDiagnostics(params) => {
+ let path = params.uri.to_file_path().unwrap();
+ let doc = self.editor.document_by_path_mut(&path);
- if let Some((_, doc)) = doc {
+ if let Some(doc) = doc {
let text = doc.text();
let diagnostics = params
@@ -429,10 +455,27 @@ impl Application {
Call::MethodCall(helix_lsp::jsonrpc::MethodCall {
method, params, id, ..
}) => {
+ let language_server = match self.editor.language_servers.get_by_id(server_id) {
+ Some(language_server) => language_server,
+ None => {
+ warn!("can't find language server with id `{}`", server_id);
+ return;
+ }
+ };
+
let call = match MethodCall::parse(&method, params) {
Some(call) => call,
None => {
error!("Method not found {}", method);
+ // language_server.reply(
+ // call.id,
+ // // TODO: make a Into trait that can cast to Err(jsonrpc::Error)
+ // Err(helix_lsp::jsonrpc::Error {
+ // code: helix_lsp::jsonrpc::ErrorCode::MethodNotFound,
+ // message: "Method not found".to_string(),
+ // data: None,
+ // }),
+ // );
return;
}
};
@@ -445,53 +488,9 @@ impl Application {
if spinner.is_stopped() {
spinner.start();
}
-
- let doc = self.editor.documents().find(|doc| {
- doc.language_server()
- .map(|server| server.id() == server_id)
- .unwrap_or_default()
- });
- match doc {
- Some(doc) => {
- // it's ok to unwrap, we check for the language server before
- let server = doc.language_server().unwrap();
- tokio::spawn(server.reply(id, Ok(serde_json::Value::Null)));
- }
- None => {
- if let Some(server) =
- self.editor.language_servers.get_by_id(server_id)
- {
- log::warn!(
- "missing document with language server id `{}`",
- server_id
- );
- tokio::spawn(server.reply(
- id,
- Err(helix_lsp::jsonrpc::Error {
- code: helix_lsp::jsonrpc::ErrorCode::InternalError,
- message: "document missing".to_string(),
- data: None,
- }),
- ));
- } else {
- log::warn!(
- "can't find language server with id `{}`",
- server_id
- );
- }
- }
- }
+ tokio::spawn(language_server.reply(id, Ok(serde_json::Value::Null)));
}
}
- // self.language_server.reply(
- // call.id,
- // // TODO: make a Into trait that can cast to Err(jsonrpc::Error)
- // Err(helix_lsp::jsonrpc::Error {
- // code: helix_lsp::jsonrpc::ErrorCode::MethodNotFound,
- // message: "Method not found".to_string(),
- // data: None,
- // }),
- // );
}
e => unreachable!("{:?}", e),
}
diff --git a/helix-term/src/job.rs b/helix-term/src/job.rs
index 2ac41926..4fa38174 100644
--- a/helix-term/src/job.rs
+++ b/helix-term/src/job.rs
@@ -61,7 +61,7 @@ impl Jobs {
}
pub fn handle_callback(
- &mut self,
+ &self,
editor: &mut Editor,
compositor: &mut Compositor,
call: anyhow::Result<Option<Callback>>,
@@ -84,7 +84,7 @@ impl Jobs {
}
}
- pub fn add(&mut self, j: Job) {
+ pub fn add(&self, j: Job) {
if j.wait {
self.wait_futures.push(j.future);
} else {
diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs
index b2c02927..6de60995 100644
--- a/helix-view/src/document.rs
+++ b/helix-view/src/document.rs
@@ -386,21 +386,24 @@ impl Document {
/// If supported, returns the changes that should be applied to this document in order
/// to format it nicely.
pub fn format(&self) -> Option<impl Future<Output = LspFormatting> + 'static> {
- if let Some(language_server) = self.language_server.clone() {
+ if let Some(language_server) = self.language_server() {
let text = self.text.clone();
- let id = self.identifier();
+ let offset_encoding = language_server.offset_encoding();
+ let request = language_server.text_document_formatting(
+ self.identifier(),
+ lsp::FormattingOptions::default(),
+ None,
+ )?;
+
let fut = async move {
- let edits = language_server
- .text_document_formatting(id, lsp::FormattingOptions::default(), None)
- .await
- .unwrap_or_else(|e| {
- log::warn!("LSP formatting failed: {}", e);
- Default::default()
- });
+ let edits = request.await.unwrap_or_else(|e| {
+ log::warn!("LSP formatting failed: {}", e);
+ Default::default()
+ });
LspFormatting {
doc: text,
edits,
- offset_encoding: language_server.offset_encoding(),
+ offset_encoding,
}
};
Some(fut)
@@ -469,9 +472,14 @@ impl Document {
to_writer(&mut file, encoding, &text).await?;
if let Some(language_server) = language_server {
- language_server
- .text_document_did_save(identifier, &text)
- .await?;
+ if language_server.is_initialized() {
+ return Ok(());
+ }
+ if let Some(notification) =
+ language_server.text_document_did_save(identifier, &text)
+ {
+ notification.await?;
+ }
}
Ok(())
@@ -646,7 +654,7 @@ impl Document {
// }
// emit lsp notification
- if let Some(language_server) = &self.language_server {
+ if let Some(language_server) = self.language_server() {
let notify = language_server.text_document_did_change(
self.versioned_identifier(),
&old_doc,
@@ -795,9 +803,18 @@ impl Document {
self.version
}
- #[inline]
pub fn language_server(&self) -> Option<&helix_lsp::Client> {
- self.language_server.as_deref()
+ let server = self.language_server.as_deref();
+ let initialized = server
+ .map(|server| server.is_initialized())
+ .unwrap_or(false);
+
+ // only resolve language_server if it's initialized
+ if initialized {
+ server
+ } else {
+ None
+ }
}
#[inline]
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index 562c3c60..3d2d4a87 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -255,20 +255,21 @@ impl Editor {
.and_then(|language| self.language_servers.get(language).ok());
if let Some(language_server) = language_server {
- doc.set_language_server(Some(language_server.clone()));
-
let language_id = doc
.language()
.and_then(|s| s.split('.').last()) // source.rust
.map(ToOwned::to_owned)
.unwrap_or_default();
+ // TODO: this now races with on_init code if the init happens too quickly
tokio::spawn(language_server.text_document_did_open(
doc.url().unwrap(),
doc.version(),
doc.text(),
language_id,
));
+
+ doc.set_language_server(Some(language_server));
}
let id = self.documents.insert(doc);
@@ -287,14 +288,9 @@ impl Editor {
if close_buffer {
// get around borrowck issues
- let language_servers = &mut self.language_servers;
let doc = &self.documents[view.doc];
- let language_server = doc
- .language
- .as_ref()
- .and_then(|language| language_servers.get(language).ok());
- if let Some(language_server) = language_server {
+ if let Some(language_server) = doc.language_server() {
tokio::spawn(language_server.text_document_did_close(doc.identifier()));
}
self.documents.remove(view.doc);
@@ -324,20 +320,24 @@ impl Editor {
view.ensure_cursor_in_view(doc, self.config.scrolloff)
}
+ #[inline]
pub fn document(&self, id: DocumentId) -> Option<&Document> {
self.documents.get(id)
}
+ #[inline]
pub fn document_mut(&mut self, id: DocumentId) -> Option<&mut Document> {
self.documents.get_mut(id)
}
+ #[inline]
pub fn documents(&self) -> impl Iterator<Item = &Document> {
- self.documents.iter().map(|(_id, doc)| doc)
+ self.documents.values()
}
+ #[inline]
pub fn documents_mut(&mut self) -> impl Iterator<Item = &mut Document> {
- self.documents.iter_mut().map(|(_id, doc)| doc)
+ self.documents.values_mut()
}
pub fn document_by_path<P: AsRef<Path>>(&self, path: P) -> Option<&Document> {
@@ -345,6 +345,11 @@ impl Editor {
.find(|doc| doc.path().map(|p| p == path.as_ref()).unwrap_or(false))
}
+ pub fn document_by_path_mut<P: AsRef<Path>>(&mut self, path: P) -> Option<&mut Document> {
+ self.documents_mut()
+ .find(|doc| doc.path().map(|p| p == path.as_ref()).unwrap_or(false))
+ }
+
pub fn cursor(&self) -> (Option<Position>, CursorKind) {
let view = view!(self);
let doc = &self.documents[view.doc];
diff --git a/languages.toml b/languages.toml
index c04435fe..a29dd38d 100644
--- a/languages.toml
+++ b/languages.toml
@@ -117,6 +117,17 @@ language-server = { command = "typescript-language-server", args = ["--stdio"] }
indent = { tab-width = 2, unit = " " }
[[language]]
+name = "tsx"
+scope = "source.tsx"
+injection-regex = "^(tsx)$" # |typescript
+file-types = ["tsx"]
+roots = []
+# TODO: highlights-jsx, highlights-params
+
+language-server = { command = "typescript-language-server", args = ["--stdio"] }
+indent = { tab-width = 2, unit = " " }
+
+[[language]]
name = "css"
scope = "source.css"
injection-regex = "css"
@@ -204,7 +215,22 @@ injection-regex = "julia"
file-types = ["jl"]
roots = []
comment-token = "#"
-language-server = { command = "julia", args = [ "--startup-file=no", "--history-file=no", "-e", "using LanguageServer;using Pkg;import StaticLint;import SymbolServer;env_path = dirname(Pkg.Types.Context().env.project_file);server = LanguageServer.LanguageServerInstance(stdin, stdout, env_path, \"\");server.runlinter = true;run(server);" ] }
+language-server = { command = "julia", args = [
+ "--startup-file=no",
+ "--history-file=no",
+ "--quiet",
+ "-e",
+ """
+ using LanguageServer;
+ using Pkg;
+ import StaticLint;
+ env_path = dirname(Pkg.Types.Context().env.project_file);
+
+ server = LanguageServer.LanguageServerInstance(stdin, stdout, env_path, "");
+ server.runlinter = true;
+ run(server);
+ """,
+ ] }
indent = { tab-width = 2, unit = " " }
[[language]]
diff --git a/runtime/queries/c/highlights.scm b/runtime/queries/c/highlights.scm
index 258e07e7..2c42710f 100644
--- a/runtime/queries/c/highlights.scm
+++ b/runtime/queries/c/highlights.scm
@@ -61,7 +61,7 @@
(null) @constant
(number_literal) @number
-(char_literal) @number
+(char_literal) @string
(call_expression
function: (identifier) @function)
diff --git a/runtime/queries/go/highlights.scm b/runtime/queries/go/highlights.scm
index 224c8b78..3129c4b2 100644
--- a/runtime/queries/go/highlights.scm
+++ b/runtime/queries/go/highlights.scm
@@ -17,9 +17,18 @@
; Identifiers
+((identifier) @constant (match? @constant "^[A-Z][A-Z\\d_]+$"))
+(const_spec
+ name: (identifier) @constant)
+
+(parameter_declaration (identifier) @variable.parameter)
+(variadic_parameter_declaration (identifier) @variable.parameter)
+
(type_identifier) @type
(field_identifier) @property
(identifier) @variable
+(package_identifier) @variable
+
; Operators
@@ -79,10 +88,8 @@
"go"
"goto"
"if"
- "import"
"interface"
"map"
- "package"
"range"
"return"
"select"
@@ -92,6 +99,29 @@
"var"
] @keyword
+[
+ "import"
+ "package"
+] @keyword.control.import
+
+; Delimiters
+
+[
+ ":"
+ "."
+ ","
+ ";"
+] @punctuation.delimiter
+
+[
+ "("
+ ")"
+ "["
+ "]"
+ "{"
+ "}"
+] @punctuation.bracket
+
; Literals
[
@@ -111,7 +141,8 @@
[
(true)
(false)
- (nil)
-] @constant.builtin
+] @constant.builtin.boolean
+
+(nil) @constant.builtin
(comment) @comment
diff --git a/runtime/queries/go/locals.scm b/runtime/queries/go/locals.scm
new file mode 100644
index 00000000..d240e2b7
--- /dev/null
+++ b/runtime/queries/go/locals.scm
@@ -0,0 +1,30 @@
+; Scopes
+
+(block) @local.scope
+
+; Definitions
+
+(parameter_declaration (identifier) @local.definition)
+(variadic_parameter_declaration (identifier) @local.definition)
+
+(short_var_declaration
+ left: (expression_list
+ (identifier) @local.definition))
+
+(var_spec
+ name: (identifier) @local.definition)
+
+(for_statement
+ (range_clause
+ left: (expression_list
+ (identifier) @local.definition)))
+
+(const_declaration
+ (const_spec
+ name: (identifier) @local.definition))
+
+; References
+
+(identifier) @local.reference
+(field_identifier) @local.reference
+
diff --git a/runtime/queries/haskell/highlights.scm b/runtime/queries/haskell/highlights.scm
index ecaa2d2c..dada80b6 100644
--- a/runtime/queries/haskell/highlights.scm
+++ b/runtime/queries/haskell/highlights.scm
@@ -2,19 +2,19 @@
(operator) @operator
(exp_name (constructor) @constructor)
(constructor_operator) @operator
-(module) @module_name
+(module) @namespace
(type) @type
(type) @class
(constructor) @constructor
(pragma) @pragma
(comment) @comment
(signature name: (variable) @fun_type_name)
-(function name: (variable) @fun_name)
+(function name: (variable) @function)
(constraint class: (class_name (type)) @class)
(class (class_head class: (class_name (type)) @class))
(instance (instance_head class: (class_name (type)) @class))
-(integer) @literal
-(exp_literal (float)) @literal
+(integer) @number
+(exp_literal (float)) @number
(char) @literal
(con_unit) @literal
(con_list) @literal
@@ -39,5 +39,7 @@
"do" @keyword
"mdo" @keyword
"rec" @keyword
-"(" @paren
-")" @paren
+[
+ "("
+ ")"
+] @punctuation.bracket
diff --git a/runtime/queries/javascript/highlights.scm b/runtime/queries/javascript/highlights.scm
index a18c38d9..e29829bf 100644
--- a/runtime/queries/javascript/highlights.scm
+++ b/runtime/queries/javascript/highlights.scm
@@ -87,7 +87,7 @@
(template_string)
] @string
-(regex) @string.special
+(regex) @string.regexp
(number) @number
; Tokens
diff --git a/runtime/queries/julia/highlights.scm b/runtime/queries/julia/highlights.scm
index a53dabe5..7b7d426c 100644
--- a/runtime/queries/julia/highlights.scm
+++ b/runtime/queries/julia/highlights.scm
@@ -1,9 +1,3 @@
-(identifier) @variable
-;; In case you want type highlighting based on Julia naming conventions (this might collide with mathematical notation)
-;((identifier) @type ; exception: mark `A_foo` sort of identifiers as variables
- ;(match? @type "^[A-Z][^_]"))
-((identifier) @constant
- (match? @constant "^[A-Z][A-Z_]{2}[A-Z_]*$"))
[
(triple_string)
@@ -28,43 +22,43 @@
(call_expression
(identifier) @function)
(call_expression
- (field_expression (identifier) @method .))
+ (field_expression (identifier) @function.method .))
(broadcast_call_expression
(identifier) @function)
(broadcast_call_expression
- (field_expression (identifier) @method .))
+ (field_expression (identifier) @function.method .))
(parameter_list
- (identifier) @parameter)
+ (identifier) @variable.parameter)
(parameter_list
(optional_parameter .
- (identifier) @parameter))
+ (identifier) @variable.parameter))
(typed_parameter
- (identifier) @parameter
+ (identifier) @variable.parameter
(identifier) @type)
(type_parameter_list
(identifier) @type)
(typed_parameter
- (identifier) @parameter
+ (identifier) @variable.parameter
(parameterized_identifier) @type)
(function_expression
- . (identifier) @parameter)
-(spread_parameter) @parameter
+ . (identifier) @variable.parameter)
+(spread_parameter) @variable.parameter
(spread_parameter
- (identifier) @parameter)
+ (identifier) @variable.parameter)
(named_argument
- . (identifier) @parameter)
+ . (identifier) @variable.parameter)
(argument_list
(typed_expression
- (identifier) @parameter
+ (identifier) @variable.parameter
(identifier) @type))
(argument_list
(typed_expression
- (identifier) @parameter
+ (identifier) @variable.parameter
(parameterized_identifier) @type))
;; Symbol expressions (:my-wanna-be-lisp-keyword)
(quote_expression
- (identifier)) @symbol
+ (identifier)) @string.special.symbol
;; Parsing error! foo (::Type) get's parsed as two quote expressions
(argument_list
@@ -76,7 +70,7 @@
(identifier) @type)
(parameterized_identifier (_)) @type
(argument_list
- (typed_expression . (identifier) @parameter))
+ (typed_expression . (identifier) @variable.parameter))
(typed_expression
(identifier) @type .)
@@ -113,13 +107,13 @@
"end" @keyword
(if_statement
- ["if" "end"] @conditional)
+ ["if" "end"] @keyword.control.conditional)
(elseif_clause
- ["elseif"] @conditional)
+ ["elseif"] @keyword.control.conditional)
(else_clause
- ["else"] @conditional)
+ ["else"] @keyword.control.conditional)
(ternary_expression
- ["?" ":"] @conditional)
+ ["?" ":"] @keyword.control.conditional)
(function_definition ["function" "end"] @keyword.function)
@@ -134,47 +128,57 @@
"type"
] @keyword
-((identifier) @keyword (#any-of? @keyword "global" "local"))
+((identifier) @keyword (match? @keyword "global|local"))
(compound_expression
["begin" "end"] @keyword)
(try_statement
- ["try" "end" ] @exception)
+ ["try" "end" ] @keyword.control.exception)
(finally_clause
- "finally" @exception)
+ "finally" @keyword.control.exception)
(catch_clause
- "catch" @exception)
+ "catch" @keyword.control.exception)
(quote_statement
["quote" "end"] @keyword)
(let_statement
["let" "end"] @keyword)
(for_statement
- ["for" "end"] @repeat)
+ ["for" "end"] @keyword.control.repeat)
(while_statement
- ["while" "end"] @repeat)
-(break_statement) @repeat
-(continue_statement) @repeat
+ ["while" "end"] @keyword.control.repeat)
+(break_statement) @keyword.control.repeat
+(continue_statement) @keyword.control.repeat
(for_binding
- "in" @repeat)
+ "in" @keyword.control.repeat)
(for_clause
- "for" @repeat)
+ "for" @keyword.control.repeat)
(do_clause
["do" "end"] @keyword)
(export_statement
- ["export"] @include)
+ ["export"] @keyword.control.import)
[
"using"
"module"
"import"
-] @include
+] @keyword.control.import
-((identifier) @include (#eq? @include "baremodule"))
+((identifier) @keyword.control.import (#eq? @keyword.control.import "baremodule"))
(((identifier) @constant.builtin) (match? @constant.builtin "^(nothing|Inf|NaN)$"))
-(((identifier) @boolean) (eq? @boolean "true"))
-(((identifier) @boolean) (eq? @boolean "false"))
+(((identifier) @constant.builtin.boolean) (#eq? @constant.builtin.boolean "true"))
+(((identifier) @constant.builtin.boolean) (#eq? @constant.builtin.boolean "false"))
+
["::" ":" "." "," "..." "!"] @punctuation.delimiter
["[" "]" "(" ")" "{" "}"] @punctuation.bracket
+
+["="] @operator
+
+(identifier) @variable
+;; In case you want type highlighting based on Julia naming conventions (this might collide with mathematical notation)
+;((identifier) @type ; exception: mark `A_foo` sort of identifiers as variables
+ ;(match? @type "^[A-Z][^_]"))
+((identifier) @constant
+ (match? @constant "^[A-Z][A-Z_]{2}[A-Z_]*$"))
diff --git a/runtime/queries/latex/highlights.scm b/runtime/queries/latex/highlights.scm
index cd04a62c..f045c82d 100644
--- a/runtime/queries/latex/highlights.scm
+++ b/runtime/queries/latex/highlights.scm
@@ -259,7 +259,7 @@
(comment) @comment
-(bracket_group) @parameter
+(bracket_group) @variable.parameter
[(math_operator) "="] @operator
@@ -312,7 +312,7 @@
key: (word) @text.reference)
(key_val_pair
- key: (_) @parameter
+ key: (_) @variable.parameter
value: (_))
["[" "]" "{" "}"] @punctuation.bracket ;"(" ")" is has no special meaning in LaTeX
diff --git a/runtime/queries/lua/highlights.scm b/runtime/queries/lua/highlights.scm
index 8e27a39a..40c2be70 100644
--- a/runtime/queries/lua/highlights.scm
+++ b/runtime/queries/lua/highlights.scm
@@ -23,27 +23,27 @@
"for"
"do"
"end"
-] @keyword.control.loop)
+] @keyword.control.repeat)
(for_in_statement
[
"for"
"do"
"end"
-] @keyword.control.loop)
+] @keyword.control.repeat)
(while_statement
[
"while"
"do"
"end"
-] @keyword.control.loop)
+] @keyword.control.repeat)
(repeat_statement
[
"repeat"
"until"
-] @keyword.control.loop)
+] @keyword.control.repeat)
(do_statement
[
@@ -65,7 +65,7 @@
"not"
"and"
"or"
-] @keyword.operator
+] @operator
[
"="
@@ -108,7 +108,7 @@
[
(false)
(true)
-] @boolean
+] @constant.builtin.boolean
(nil) @constant.builtin
(spread) @constant ;; "..."
((identifier) @constant
@@ -116,7 +116,7 @@
;; Parameters
(parameters
- (identifier) @parameter)
+ (identifier) @variable.parameter)
; ;; Functions
(function [(function_name) (identifier)] @function)
@@ -139,8 +139,8 @@
(function_call
[
- ((identifier) @variable (method) @method)
- ((_) (method) @method)
+ ((identifier) @variable (method) @function.method)
+ ((_) (method) @function.method)
(identifier) @function
(field_expression (property_identifier) @function)
]
diff --git a/runtime/queries/ocaml/highlights.scm b/runtime/queries/ocaml/highlights.scm
index 093b3cce..160f2cb4 100644
--- a/runtime/queries/ocaml/highlights.scm
+++ b/runtime/queries/ocaml/highlights.scm
@@ -25,12 +25,12 @@
(external (value_name) @function)
-(method_name) @method
+(method_name) @function.method
; Variables
;----------
-(value_pattern) @parameter
+(value_pattern) @variable.parameter
; Application
;------------
@@ -60,7 +60,7 @@
[(number) (signed_number)] @number
-(character) @character
+(character) @constant.character
(string) @string
@@ -92,7 +92,7 @@
["include" "open"] @include
-["for" "to" "downto" "while" "do" "done"] @keyword.control.loop
+["for" "to" "downto" "while" "do" "done"] @keyword.control.repeat
; Macros
;-------
diff --git a/runtime/queries/ruby/highlights.scm b/runtime/queries/ruby/highlights.scm
index 7f296f3b..8617d6f0 100644
--- a/runtime/queries/ruby/highlights.scm
+++ b/runtime/queries/ruby/highlights.scm
@@ -100,7 +100,7 @@
(bare_symbol)
] @string.special.symbol
-(regex) @string.special.regex
+(regex) @string.regexp
(escape_sequence) @escape
[
diff --git a/runtime/queries/rust/highlights.scm b/runtime/queries/rust/highlights.scm
index 6b14d74d..956a5dac 100644
--- a/runtime/queries/rust/highlights.scm
+++ b/runtime/queries/rust/highlights.scm
@@ -17,7 +17,7 @@
(escape_sequence) @escape
(primitive_type) @type.builtin
-(boolean_literal) @constant.builtin
+(boolean_literal) @constant.builtin.boolean
[
(integer_literal)
(float_literal)
@@ -149,7 +149,7 @@
(mutable_specifier) @keyword.mut
-
+; TODO: variable.mut to highlight mutable identifiers via locals.scm
; -------
; Guess Other Types
diff --git a/runtime/queries/rust/locals.scm b/runtime/queries/rust/locals.scm
new file mode 100644
index 00000000..6428f9b4
--- /dev/null
+++ b/runtime/queries/rust/locals.scm
@@ -0,0 +1,17 @@
+; Scopes
+
+(block) @local.scope
+
+; Definitions
+
+(parameter
+ (identifier) @local.definition)
+
+(let_declaration
+ pattern: (identifier) @local.definition)
+
+(closure_parameters (identifier)) @local.definition
+
+; References
+(identifier) @local.reference
+
diff --git a/runtime/queries/tsx/highlights.scm b/runtime/queries/tsx/highlights.scm
new file mode 100644
index 00000000..1b61e36d
--- /dev/null
+++ b/runtime/queries/tsx/highlights.scm
@@ -0,0 +1 @@
+; inherits: typescript
diff --git a/runtime/queries/yaml/highlights.scm b/runtime/queries/yaml/highlights.scm
index 4ebb4440..2955a4ce 100644
--- a/runtime/queries/yaml/highlights.scm
+++ b/runtime/queries/yaml/highlights.scm
@@ -1,6 +1,6 @@
(block_mapping_pair key: (_) @property)
(flow_mapping (_ key: (_) @property))
-(boolean_scalar) @boolean
+(boolean_scalar) @constant.builtin.boolean
(null_scalar) @constant.builtin
(double_quote_scalar) @string
(single_quote_scalar) @string
diff --git a/runtime/themes/dark_plus.toml b/runtime/themes/dark_plus.toml
index c105d52b..7eeb3f95 100644
--- a/runtime/themes/dark_plus.toml
+++ b/runtime/themes/dark_plus.toml
@@ -34,6 +34,7 @@
"comment" = { fg = "#6A9955" }
"string" = { fg = "#ce9178" }
+"string.regexp" = { fg = "regex" }
"number" = { fg = "#b5cea8" }
"escape" = { fg = "#d7ba7d" }
diff --git a/runtime/themes/monokai.toml b/runtime/themes/monokai.toml
index 2407591a..a8f03ff3 100644
--- a/runtime/themes/monokai.toml
+++ b/runtime/themes/monokai.toml
@@ -34,6 +34,7 @@
"comment" = { fg = "#88846F" }
"string" = { fg = "#e6db74" }
+"string.regexp" = { fg = "regex" }
"number" = { fg = "#ae81ff" }
"escape" = { fg = "#ae81ff" }
diff --git a/theme.toml b/theme.toml
index 3166b2d6..49f46b0b 100644
--- a/theme.toml
+++ b/theme.toml
@@ -9,7 +9,7 @@ special = "honey"
property = "white"
variable = "lavender"
# variable = "almond" # TODO: metavariables only
-"variable.parameter" = "lavender"
+"variable.parameter" = { fg = "lavender", modifiers = ["underlined"] }
"variable.builtin" = "mint"
type = "white"
"type.builtin" = "white" # TODO: distinguish?
@@ -28,9 +28,7 @@ escape = "honey"
label = "honey"
# TODO: diferentiate doc comment
-# concat (ERROR) @syntax-error and "MISSING ;" selectors for errors
-
-module = "#ff0000"
+# concat (ERROR) @error.syntax and "MISSING ;" selectors for errors
"ui.background" = { bg = "midnight" }
"ui.linenr" = { fg = "comet" }