Merge branch 'lsp-async-init'

pull/724/head
Blaž Hrastnik 3 years ago
commit fd36fbdebf

@ -3,8 +3,9 @@ authors = ["Blaž Hrastnik"]
language = "en" language = "en"
multilingual = false multilingual = false
src = "src" src = "src"
theme = "colibri"
edit-url-template = "https://github.com/helix-editor/helix/tree/master/book/{path}?mode=edit" edit-url-template = "https://github.com/helix-editor/helix/tree/master/book/{path}?mode=edit"
[output.html] [output.html]
cname = "docs.helix-editor.com" cname = "docs.helix-editor.com"
default-theme = "colibri"
preferred-dark-theme = "colibri"

@ -5,6 +5,19 @@ To override global configuration parameters, create a `config.toml` file located
* Linux and Mac: `~/.config/helix/config.toml` * Linux and Mac: `~/.config/helix/config.toml`
* Windows: `%AppData%\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 ## LSP
To display all language server messages in the status line add the following to your `config.toml`: To display all language server messages in the status line add the following to your `config.toml`:

@ -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 action itself (delete, change, yank, etc) comes second. A cursor is simply a
single width selection. single width selection.
See also Kakoune's [Migrating from Vim](https://github.com/mawww/kakoune/wiki/Migrating-from-Vim).
> TODO: Mention texobjects, surround, registers > TODO: Mention texobjects, surround, registers

@ -23,7 +23,9 @@ shell for working on Helix.
### Arch Linux ### 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-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 - [helix-git](https://aur.archlinux.org/packages/helix-git/) builds the master branch

@ -4,7 +4,7 @@
### Movement ### 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 | | Key | Description | Command |
| ----- | ----------- | ------- | | ----- | ----------- | ------- |
@ -28,14 +28,14 @@
| `PageDown` | Move page down | `page_down` | | `PageDown` | Move page down | `page_down` |
| `Ctrl-u` | Move half page up | `half_page_up` | | `Ctrl-u` | Move half page up | `half_page_up` |
| `Ctrl-d` | Move half page down | `half_page_down` | | `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` | | `Ctrl-o` | Jump backward on the jumplist | `jump_backward` |
| `v` | Enter [select (extend) mode](#select--extend-mode) | `select_mode` | | `v` | Enter [select (extend) mode](#select--extend-mode) | `select_mode` |
| `g` | Enter [goto mode](#goto-mode) | N/A | | `g` | Enter [goto mode](#goto-mode) | N/A |
| `m` | Enter [match mode](#match-mode) | N/A | | `m` | Enter [match mode](#match-mode) | N/A |
| `:` | Enter command mode | `command_mode` | | `:` | Enter command mode | `command_mode` |
| `z` | Enter [view mode](#view-mode) | N/A | | `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 | | `Space` | Enter [space mode](#space-mode) | N/A |
| `K` | Show documentation for the item under the cursor | `hover` | | `K` | Show documentation for the item under the cursor | `hover` |
@ -66,6 +66,16 @@
| `d` | Delete selection | `delete_selection` | | `d` | Delete selection | `delete_selection` |
| `c` | Change selection (delete and enter insert mode) | `change_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 ### Selection manipulation
| Key | Description | Command | | Key | Description | Command |
@ -87,17 +97,10 @@
| | Expand selection to parent syntax node TODO: pick a key | `expand_selection` | | | Expand selection to parent syntax node TODO: pick a key | `expand_selection` |
| `J` | Join lines inside selection | `join_selections` | | `J` | Join lines inside selection | `join_selections` |
| `K` | Keep selections matching the regex TODO: overlapped by hover help | `keep_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` | | `Space` | Keep only the primary selection TODO: overlapped by space mode | `keep_primary_selection` |
| `Ctrl-c` | Comment/uncomment the selections | `toggle_comments` | | `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 ### Search
> TODO: The search implementation isn't ideal yet -- we don't support searching > 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` | | `N` | Add next search match to selection | `extend_search_next` |
| `*` | Use current selection as the search pattern | `search_selection` | | `*` | Use current selection as the search pattern | `search_selection` |
### Unimpaired ### Minor modes
Mappings in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaired) These sub-modes are accessible from normal mode and typically switch back to normal mode after a command.
| Key | Description | Command | #### View mode
| ----- | ----------- | ------- |
| `[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
| 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.
## View mode
View mode is intended for scrolling and manipulating the view without changing View mode is intended for scrolling and manipulating the view without changing
the selection. the selection.
@ -155,7 +131,7 @@ the selection.
| `j` | Scroll the view downwards | `scroll_down` | | `j` | Scroll the view downwards | `scroll_down` |
| `k` | Scroll the view upwards | `scroll_up` | | `k` | Scroll the view upwards | `scroll_up` |
## Goto mode #### Goto mode
Jumps to various locations. Jumps to various locations.
@ -177,7 +153,7 @@ Jumps to various locations.
| `i` | Go to implementation | `goto_implementation` | | `i` | Go to implementation | `goto_implementation` |
| `a` | Go to the last accessed/alternate file | `goto_last_accessed_file` | | `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 Enter this mode using `m` from normal mode. See the relavant section
in [Usage](./usage.md) for an explanation about [surround](./usage.md#surround) 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` | | `a` `<object>` | Select around textobject | `select_textobject_around` |
| `i` `<object>` | Select inside textobject | `select_textobject_inner` | | `i` `<object>` | Select inside textobject | `select_textobject_inner` |
## Object mode
TODO: Mappings for selecting syntax nodes (a superset of `[`). 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. 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` | | `h`, `Ctrl-h` | Horizontal bottom split | `hsplit` |
| `q`, `Ctrl-q` | Close current window | `wclose` | | `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 | | 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` | | `Y` | Yank main selection to clipboard | `yank_main_selection_to_clipboard` |
| `R` | Replace selections by clipboard contents | `replace_selections_with_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 # Picker
Keys to use within picker. Remapping currently not supported. Keys to use within picker. Remapping currently not supported.

@ -30,85 +30,9 @@ if the key contains a dot `'.'`, it must be quoted to prevent it being parsed as
"key.key" = "#ffffff" "key.key" = "#ffffff"
``` ```
Possible modifiers: ### Color palettes
| Modifier | It's recommended define a palette of named colors, and refer to them from the
| --- |
| `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
configuration values in your theme. To do this, add a table called configuration values in your theme. To do this, add a table called
`palette` to your theme file: `palette` to your theme file:
@ -146,3 +70,125 @@ over it and is merged into the default palette.
| `light-cyan` | | `light-cyan` |
| `light-gray` | | `light-gray` |
| `white` | | `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 |

@ -114,6 +114,19 @@ h6:target::before {
margin-bottom: .875em; 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 p { line-height: 1.45em; }
.content ol { line-height: 1.45em; } .content ol { line-height: 1.45em; }
.content ul { line-height: 1.45em; } .content ul { line-height: 1.45em; }

@ -69,7 +69,7 @@
--links: #2b79a2; --links: #2b79a2;
--inline-code-color: #c5c8c6;; --inline-code-color: #c5c8c6;
--theme-popup-bg: #141617; --theme-popup-bg: #141617;
--theme-popup-border: #43484d; --theme-popup-border: #43484d;
@ -110,7 +110,7 @@
--links: #20609f; --links: #20609f;
--inline-code-color: #301900; --inline-code-color: #a39e9b;
--theme-popup-bg: #fafafa; --theme-popup-bg: #fafafa;
--theme-popup-border: #cccccc; --theme-popup-border: #cccccc;
@ -151,7 +151,7 @@
--links: #2b79a2; --links: #2b79a2;
--inline-code-color: #c5c8c6;; --inline-code-color: #c5c8c6;
--theme-popup-bg: #161923; --theme-popup-bg: #161923;
--theme-popup-border: #737480; --theme-popup-border: #737480;
@ -192,7 +192,7 @@
--links: #2b79a2; --links: #2b79a2;
--inline-code-color: #6e6b5e; --inline-code-color: #c5c8c6;
--theme-popup-bg: #e1e1db; --theme-popup-bg: #e1e1db;
--theme-popup-border: #b38f6b; --theme-popup-border: #b38f6b;
@ -234,7 +234,7 @@
--links: #2b79a2; --links: #2b79a2;
--inline-code-color: #c5c8c6;; --inline-code-color: #6e6b5e;
--theme-popup-bg: #141617; --theme-popup-bg: #141617;
--theme-popup-border: #43484d; --theme-popup-border: #43484d;
@ -261,6 +261,7 @@
.colibri { .colibri {
--bg: #3b224c; --bg: #3b224c;
--fg: #bcbdd0; --fg: #bcbdd0;
--heading-fg: #fff;
--sidebar-bg: #281733; --sidebar-bg: #281733;
--sidebar-fg: #c8c9db; --sidebar-fg: #c8c9db;
@ -276,18 +277,19 @@
/* --links: #a4a0e8; */ /* --links: #a4a0e8; */
--links: #ECCDBA; --links: #ECCDBA;
--inline-code-color: #c5c8c6;; --inline-code-color: hsl(48.7, 7.8%, 70%);
--theme-popup-bg: #161923; --theme-popup-bg: #161923;
--theme-popup-border: #737480; --theme-popup-border: #737480;
--theme-hover: rgba(0,0,0, .2); --theme-hover: rgba(0,0,0, .2);
--quote-bg: hsl(226, 15%, 17%); --quote-bg: #281733;
--quote-border: hsl(226, 15%, 22%); --quote-border: hsl(226, 15%, 22%);
--table-border-color: hsl(226, 23%, 16%); --table-border-color: hsl(226, 23%, 76%);
--table-header-bg: hsl(226, 23%, 31%); --table-header-bg: hsla(226, 23%, 31%, 0);
--table-alternate-bg: hsl(226, 23%, 14%); --table-alternate-bg: hsl(226, 23%, 14%);
--table-border-line: hsla(201deg, 20%, 92%, 0.2);
--searchbar-border-color: #aaa; --searchbar-border-color: #aaa;
--searchbar-bg: #aeaec6; --searchbar-bg: #aeaec6;
@ -300,6 +302,7 @@
} }
.colibri { .colibri {
/*
--bg: #ffffff; --bg: #ffffff;
--fg: #452859; --fg: #452859;
--fg: #5a5977; --fg: #5a5977;
@ -318,7 +321,7 @@
--links: #6F44F0; --links: #6F44F0;
--inline-code-color: #697C81; --inline-code-color: #a39e9b;
--theme-popup-bg: #161923; --theme-popup-bg: #161923;
--theme-popup-border: #737480; --theme-popup-border: #737480;
@ -341,4 +344,5 @@
--searchresults-border-color: #5c5c68; --searchresults-border-color: #5c5c68;
--searchresults-li-bg: #242430; --searchresults-li-bg: #242430;
--search-mark-bg: #a2cff5; --search-mark-bg: #a2cff5;
*/
} }

@ -1,83 +1,56 @@
/* pre code.hljs {
* An increased contrast highlighting scheme loosely based on the display:block;
* "Base16 Atelier Dune Light" theme by Bram de Haan overflow-x:auto;
* (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune) padding:1em
* Original Base16 color scheme by Chris Kempson }
* (https://github.com/chriskempson/base16) code.hljs {
*/ padding:3px 5px
}
/* Comment */ .hljs {
background:#2f1e2e;
color:#a39e9b
}
.hljs-comment, .hljs-comment,
.hljs-quote { .hljs-quote {
color: #575757; color:#8d8687
} }
/* Red */
.hljs-variable,
.hljs-template-variable,
.hljs-attribute,
.hljs-tag,
.hljs-name,
.hljs-regexp,
.hljs-link, .hljs-link,
.hljs-meta,
.hljs-name, .hljs-name,
.hljs-regexp,
.hljs-selector-class,
.hljs-selector-id, .hljs-selector-id,
.hljs-selector-class { .hljs-tag,
color: #d70025; .hljs-template-variable,
.hljs-variable {
color:#ef6155
} }
/* Orange */
.hljs-number,
.hljs-meta,
.hljs-built_in, .hljs-built_in,
.hljs-builtin-name, .hljs-deletion,
.hljs-literal, .hljs-literal,
.hljs-type, .hljs-number,
.hljs-params { .hljs-params,
color: #b21e00; .hljs-type {
color:#f99b15
} }
.hljs-attribute,
/* Green */ .hljs-section,
.hljs-string, .hljs-title {
.hljs-symbol, color:#fec418
.hljs-bullet {
color: #008200;
} }
.hljs-addition,
/* Blue */ .hljs-bullet,
.hljs-title, .hljs-string,
.hljs-section { .hljs-symbol {
color: #0030f2; color:#48b685
} }
/* Purple */
.hljs-keyword, .hljs-keyword,
.hljs-selector-tag { .hljs-selector-tag {
color: #9d00ec; color:#815ba4
} }
.hljs {
display: block;
overflow-x: auto;
background: #f6f7f6;
color: #000;
padding: 0.5em;
}
.hljs-emphasis { .hljs-emphasis {
font-style: italic; font-style:italic
} }
.hljs-strong { .hljs-strong {
font-weight: bold; font-weight:700
}
.hljs-addition {
color: #22863a;
background-color: #f0fff4;
}
.hljs-deletion {
color: #b31d28;
background-color: #ffeef0;
} }

@ -316,8 +316,12 @@ pub fn suggested_indent_for_pos(
pub fn get_scopes(syntax: Option<&Syntax>, text: RopeSlice, pos: usize) -> Vec<&'static str> { pub fn get_scopes(syntax: Option<&Syntax>, text: RopeSlice, pos: usize) -> Vec<&'static str> {
let mut scopes = Vec::new(); let mut scopes = Vec::new();
if let Some(syntax) = syntax { if let Some(syntax) = syntax {
let byte_start = text.char_to_byte(pos); let pos = text.char_to_byte(pos);
let node = match get_highest_syntax_node_at_bytepos(syntax, byte_start) { let mut node = match syntax
.tree()
.root_node()
.descendant_for_byte_range(pos, pos)
{
Some(node) => node, Some(node) => node,
None => return scopes, None => return scopes,
}; };
@ -325,7 +329,8 @@ pub fn get_scopes(syntax: Option<&Syntax>, text: RopeSlice, pos: usize) -> Vec<&
scopes.push(node.kind()); scopes.push(node.kind());
while let Some(parent) = node.parent() { while let Some(parent) = node.parent() {
scopes.push(parent.kind()) scopes.push(parent.kind());
node = parent;
} }
} }

@ -144,8 +144,12 @@ impl LanguageConfiguration {
&highlights_query, &highlights_query,
&injections_query, &injections_query,
&locals_query, &locals_query,
) );
.unwrap(); // TODO: no unwrap
let config = match config {
Ok(config) => config,
Err(err) => panic!("{}", err),
}; // TODO: avoid panic
config.configure(scopes); config.configure(scopes);
Some(Arc::new(config)) Some(Arc::new(config))
} }

@ -9,11 +9,17 @@ use lsp_types as lsp;
use serde_json::Value; use serde_json::Value;
use std::future::Future; use std::future::Future;
use std::process::Stdio; use std::process::Stdio;
use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::{
atomic::{AtomicU64, Ordering},
Arc,
};
use tokio::{ use tokio::{
io::{BufReader, BufWriter}, io::{BufReader, BufWriter},
process::{Child, Command}, process::{Child, Command},
sync::mpsc::{channel, UnboundedReceiver, UnboundedSender}, sync::{
mpsc::{channel, UnboundedReceiver, UnboundedSender},
Notify, OnceCell,
},
}; };
#[derive(Debug)] #[derive(Debug)]
@ -22,18 +28,19 @@ pub struct Client {
_process: Child, _process: Child,
server_tx: UnboundedSender<Payload>, server_tx: UnboundedSender<Payload>,
request_counter: AtomicU64, request_counter: AtomicU64,
capabilities: Option<lsp::ServerCapabilities>, pub(crate) capabilities: OnceCell<lsp::ServerCapabilities>,
offset_encoding: OffsetEncoding, offset_encoding: OffsetEncoding,
config: Option<Value>, config: Option<Value>,
} }
impl Client { impl Client {
#[allow(clippy::type_complexity)]
pub fn start( pub fn start(
cmd: &str, cmd: &str,
args: &[String], args: &[String],
config: Option<Value>, config: Option<Value>,
id: usize, id: usize,
) -> Result<(Self, UnboundedReceiver<(usize, Call)>)> { ) -> Result<(Self, UnboundedReceiver<(usize, Call)>, Arc<Notify>)> {
let process = Command::new(cmd) let process = Command::new(cmd)
.args(args) .args(args)
.stdin(Stdio::piped()) .stdin(Stdio::piped())
@ -50,22 +57,20 @@ impl Client {
let reader = BufReader::new(process.stdout.take().expect("Failed to open stdout")); 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 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 { let client = Self {
id, id,
_process: process, _process: process,
server_tx, server_tx,
request_counter: AtomicU64::new(0), request_counter: AtomicU64::new(0),
capabilities: None, capabilities: OnceCell::new(),
offset_encoding: OffsetEncoding::Utf8, offset_encoding: OffsetEncoding::Utf8,
config, config,
}; };
// TODO: async client.initialize() Ok((client, server_rx, initialize_notify))
// maybe use an arc<atomic> flag
Ok((client, server_rx))
} }
pub fn id(&self) -> usize { 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 { pub fn capabilities(&self) -> &lsp::ServerCapabilities {
self.capabilities self.capabilities
.as_ref() .get()
.expect("language server not yet initialized!") .expect("language server not yet initialized!")
} }
@ -143,7 +152,8 @@ impl Client {
}) })
.map_err(|e| Error::Other(e.into()))?; .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 .await
.map_err(|_| Error::Timeout)? // return Timeout .map_err(|_| Error::Timeout)? // return Timeout
.ok_or(Error::StreamClosed)? .ok_or(Error::StreamClosed)?
@ -151,7 +161,7 @@ impl Client {
} }
/// Send a RPC notification to the language server. /// Send a RPC notification to the language server.
fn notify<R: lsp::notification::Notification>( pub fn notify<R: lsp::notification::Notification>(
&self, &self,
params: R::Params, params: R::Params,
) -> impl Future<Output = Result<()>> ) -> impl Future<Output = Result<()>>
@ -213,7 +223,7 @@ impl Client {
// General messages // 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 // 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()); let root = find_root(None).and_then(|root| lsp::Url::from_file_path(root).ok());
@ -281,14 +291,7 @@ impl Client {
locale: None, // TODO locale: None, // TODO
}; };
let response = self.request::<lsp::request::Initialize>(params).await?; 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(())
} }
pub async fn shutdown(&self) -> Result<()> { pub async fn shutdown(&self) -> Result<()> {
@ -445,7 +448,7 @@ impl Client {
) -> Option<impl Future<Output = Result<()>>> { ) -> Option<impl Future<Output = Result<()>>> {
// figure out what kind of sync the server supports // 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 { let sync_capabilities = match capabilities.text_document_sync {
Some(lsp::TextDocumentSyncCapability::Kind(kind)) Some(lsp::TextDocumentSyncCapability::Kind(kind))
@ -463,7 +466,7 @@ impl Client {
// range = None -> whole document // range = None -> whole document
range: None, //Some(Range) range: None, //Some(Range)
range_length: None, // u64 apparently deprecated range_length: None, // u64 apparently deprecated
text: "".to_string(), text: new_text.to_string(),
}] }]
} }
lsp::TextDocumentSyncKind::Incremental => { lsp::TextDocumentSyncKind::Incremental => {
@ -491,12 +494,12 @@ impl Client {
// will_save / will_save_wait_until // will_save / will_save_wait_until
pub async fn text_document_did_save( pub fn text_document_did_save(
&self, &self,
text_document: lsp::TextDocumentIdentifier, text_document: lsp::TextDocumentIdentifier,
text: &Rope, text: &Rope,
) -> Result<()> { ) -> Option<impl Future<Output = Result<()>>> {
let capabilities = self.capabilities.as_ref().unwrap(); let capabilities = self.capabilities.get().unwrap();
let include_text = match &capabilities.text_document_sync { let include_text = match &capabilities.text_document_sync {
Some(lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions { Some(lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
@ -508,17 +511,18 @@ impl Client {
include_text, include_text,
}) => include_text.unwrap_or(false), }) => include_text.unwrap_or(false),
// Supported(false) // Supported(false)
_ => return Ok(()), _ => return None,
}, },
// unsupported // unsupported
_ => return Ok(()), _ => return None,
}; };
self.notify::<lsp::notification::DidSaveTextDocument>(lsp::DidSaveTextDocumentParams { Some(self.notify::<lsp::notification::DidSaveTextDocument>(
lsp::DidSaveTextDocumentParams {
text_document, text_document,
text: include_text.then(|| text.into()), text: include_text.then(|| text.into()),
}) },
.await ))
} }
pub fn completion( pub fn completion(
@ -584,19 +588,19 @@ impl Client {
// formatting // formatting
pub async fn text_document_formatting( pub fn text_document_formatting(
&self, &self,
text_document: lsp::TextDocumentIdentifier, text_document: lsp::TextDocumentIdentifier,
options: lsp::FormattingOptions, options: lsp::FormattingOptions,
work_done_token: Option<lsp::ProgressToken>, work_done_token: Option<lsp::ProgressToken>,
) -> anyhow::Result<Vec<lsp::TextEdit>> { ) -> Option<impl Future<Output = Result<Vec<lsp::TextEdit>>>> {
let capabilities = self.capabilities.as_ref().unwrap(); let capabilities = self.capabilities.get().unwrap();
// check if we're able to format // check if we're able to format
match capabilities.document_formatting_provider { match capabilities.document_formatting_provider {
Some(lsp::OneOf::Left(true)) | Some(lsp::OneOf::Right(_)) => (), Some(lsp::OneOf::Left(true)) | Some(lsp::OneOf::Right(_)) => (),
// None | Some(false) // None | Some(false)
_ => return Ok(Vec::new()), _ => return None,
}; };
// TODO: return err::unavailable so we can fall back to tree sitter formatting // 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 }, 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( pub async fn text_document_range_formatting(
@ -618,7 +626,7 @@ impl Client {
options: lsp::FormattingOptions, options: lsp::FormattingOptions,
work_done_token: Option<lsp::ProgressToken>, work_done_token: Option<lsp::ProgressToken>,
) -> anyhow::Result<Vec<lsp::TextEdit>> { ) -> 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 // check if we're able to format
match capabilities.document_range_formatting_provider { match capabilities.document_range_formatting_provider {

@ -226,6 +226,8 @@ impl MethodCall {
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub enum Notification { pub enum Notification {
// we inject this notification to signal the LSP is ready
Initialized,
PublishDiagnostics(lsp::PublishDiagnosticsParams), PublishDiagnostics(lsp::PublishDiagnosticsParams),
ShowMessage(lsp::ShowMessageParams), ShowMessage(lsp::ShowMessageParams),
LogMessage(lsp::LogMessageParams), LogMessage(lsp::LogMessageParams),
@ -237,6 +239,7 @@ impl Notification {
use lsp::notification::Notification as _; use lsp::notification::Notification as _;
let notification = match method { let notification = match method {
lsp::notification::Initialized::METHOD => Self::Initialized,
lsp::notification::PublishDiagnostics::METHOD => { lsp::notification::PublishDiagnostics::METHOD => {
let params: lsp::PublishDiagnosticsParams = params let params: lsp::PublishDiagnosticsParams = params
.parse() .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 self.inner
.values() .values()
.find(|(client_id, _)| client_id == &id) .find(|(client_id, _)| client_id == &id)
@ -302,34 +305,53 @@ impl Registry {
} }
pub fn get(&mut self, language_config: &LanguageConfiguration) -> Result<Arc<Client>> { pub fn get(&mut self, language_config: &LanguageConfiguration) -> Result<Arc<Client>> {
if let Some(config) = &language_config.language_server { let config = match &language_config.language_server {
// avoid borrow issues Some(config) => config,
let inner = &mut self.inner; None => return Err(Error::LspNotDefined),
let s_incoming = &mut self.incoming; };
match inner.entry(language_config.scope.clone()) { match self.inner.entry(language_config.scope.clone()) {
Entry::Occupied(entry) => Ok(entry.get().1.clone()), Entry::Occupied(entry) => Ok(entry.get().1.clone()),
Entry::Vacant(entry) => { Entry::Vacant(entry) => {
// initialize a new client // initialize a new client
let id = self.counter.fetch_add(1, Ordering::Relaxed); let id = self.counter.fetch_add(1, Ordering::Relaxed);
let (mut client, incoming) = Client::start( let (client, incoming, initialize_notify) = Client::start(
&config.command, &config.command,
&config.args, &config.args,
serde_json::from_str(language_config.config.as_deref().unwrap_or("")).ok(), serde_json::from_str(language_config.config.as_deref().unwrap_or("")).ok(),
id, id,
)?; )?;
// TODO: run this async without blocking self.incoming.push(UnboundedReceiverStream::new(incoming));
futures_executor::block_on(client.initialize())?;
s_incoming.push(UnboundedReceiverStream::new(incoming));
let client = Arc::new(client); 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())); entry.insert((id, client.clone()));
Ok(client) Ok(client)
} }
} }
} else {
Err(Error::LspNotDefined)
}
} }
pub fn iter_clients(&self) -> impl Iterator<Item = &Arc<Client>> { pub fn iter_clients(&self) -> impl Iterator<Item = &Arc<Client>> {
@ -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)] #[cfg(test)]
mod tests { mod tests {
use super::{lsp, util::*, OffsetEncoding}; use super::{lsp, util::*, OffsetEncoding};

@ -1,7 +1,7 @@
use crate::Result; use crate::{Error, Result};
use anyhow::Context; use anyhow::Context;
use jsonrpc_core as jsonrpc; use jsonrpc_core as jsonrpc;
use log::{debug, error, info, warn}; use log::{error, info};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
use std::collections::HashMap; use std::collections::HashMap;
@ -11,7 +11,7 @@ use tokio::{
process::{ChildStderr, ChildStdin, ChildStdout}, process::{ChildStderr, ChildStdin, ChildStdout},
sync::{ sync::{
mpsc::{unbounded_channel, Sender, UnboundedReceiver, UnboundedSender}, mpsc::{unbounded_channel, Sender, UnboundedReceiver, UnboundedSender},
Mutex, Mutex, Notify,
}, },
}; };
@ -51,9 +51,11 @@ impl Transport {
) -> ( ) -> (
UnboundedReceiver<(usize, jsonrpc::Call)>, UnboundedReceiver<(usize, jsonrpc::Call)>,
UnboundedSender<Payload>, UnboundedSender<Payload>,
Arc<Notify>,
) { ) {
let (client_tx, rx) = unbounded_channel(); let (client_tx, rx) = unbounded_channel();
let (tx, client_rx) = unbounded_channel(); let (tx, client_rx) = unbounded_channel();
let notify = Arc::new(Notify::new());
let transport = Self { let transport = Self {
id, id,
@ -62,11 +64,21 @@ impl Transport {
let transport = Arc::new(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::err(transport.clone(), server_stderr));
tokio::spawn(Self::send(transport, server_stdin, client_rx)); tokio::spawn(Self::send(
transport,
server_stdin,
client_tx,
client_rx,
notify.clone(),
));
(rx, tx) (rx, tx, notify)
} }
async fn recv_server_message( async fn recv_server_message(
@ -76,14 +88,18 @@ impl Transport {
let mut content_length = None; let mut content_length = None;
loop { loop {
buffer.truncate(0); buffer.truncate(0);
reader.read_line(buffer).await?; if reader.read_line(buffer).await? == 0 {
let header = buffer.trim(); return Err(Error::StreamClosed);
};
// debug!("<- header {:?}", buffer);
if header.is_empty() { if buffer == "\r\n" {
// look for an empty CRLF line
break; break;
} }
debug!("<- header {}", header); let header = buffer.trim();
let parts = header.split_once(": "); let parts = header.split_once(": ");
@ -96,7 +112,8 @@ impl Transport {
// Workaround: Some non-conformant language servers will output logging and other garbage // 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 // 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. // 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, buffer: &mut String,
) -> Result<()> { ) -> Result<()> {
buffer.truncate(0); buffer.truncate(0);
err.read_line(buffer).await?; if err.read_line(buffer).await? == 0 {
error!("err <- {}", buffer); return Err(Error::StreamClosed);
};
error!("err <- {:?}", buffer);
Ok(()) Ok(())
} }
@ -255,18 +274,92 @@ impl Transport {
async fn send( async fn send(
transport: Arc<Self>, transport: Arc<Self>,
mut server_stdin: BufWriter<ChildStdin>, mut server_stdin: BufWriter<ChildStdin>,
client_tx: UnboundedSender<(usize, jsonrpc::Call)>,
mut client_rx: UnboundedReceiver<Payload>, mut client_rx: UnboundedReceiver<Payload>,
initialize_notify: Arc<Notify>,
) { ) {
while let Some(msg) = client_rx.recv().await { let mut pending_messages: Vec<Payload> = Vec::new();
match transport let mut is_pending = true;
.send_payload_to_server(&mut server_stdin, msg)
.await // 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(_) => {} Ok(_) => {}
Err(err) => { Err(err) => {
error!("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;
}
}
}
} }
} }
} }

@ -158,10 +158,9 @@ fn build_dir(dir: &str, language: &str) {
.is_none() .is_none()
{ {
eprintln!( 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 dir
); );
eprintln!("You can fix in using 'git submodule init && git submodule update --recursive'.");
std::process::exit(1); std::process::exit(1);
} }

@ -1 +1 @@
Subproject commit 0ba7a24b062b671263ae08e707e9e94383b25bb7 Subproject commit 12ea597262125fc22fd2e91aa953ac69b19c26ca

@ -4,7 +4,7 @@ use helix_view::{theme, Editor};
use crate::{args::Args, compositor::Compositor, config::Config, job::Jobs, ui}; use crate::{args::Args, compositor::Compositor, config::Config, job::Jobs, ui};
use log::error; use log::{error, warn};
use std::{ use std::{
io::{stdout, Write}, io::{stdout, Write},
@ -275,16 +275,42 @@ impl Application {
}; };
match notification { match notification {
Notification::PublishDiagnostics(params) => { Notification::Initialized => {
let path = Some(params.uri.to_file_path().unwrap()); 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 doc = self let docs = self.editor.documents().filter(|doc| {
.editor doc.language_server().map(|server| server.id()) == Some(server_id)
.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 text = doc.text();
let diagnostics = params let diagnostics = params
@ -429,10 +455,27 @@ impl Application {
Call::MethodCall(helix_lsp::jsonrpc::MethodCall { Call::MethodCall(helix_lsp::jsonrpc::MethodCall {
method, params, id, .. 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) { let call = match MethodCall::parse(&method, params) {
Some(call) => call, Some(call) => call,
None => { None => {
error!("Method not found {}", method); 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; return;
} }
}; };
@ -445,54 +488,10 @@ impl Application {
if spinner.is_stopped() { if spinner.is_stopped() {
spinner.start(); spinner.start();
} }
tokio::spawn(language_server.reply(id, Ok(serde_json::Value::Null)));
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
);
}
} }
} }
} }
}
// 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), e => unreachable!("{:?}", e),
} }
} }

@ -61,7 +61,7 @@ impl Jobs {
} }
pub fn handle_callback( pub fn handle_callback(
&mut self, &self,
editor: &mut Editor, editor: &mut Editor,
compositor: &mut Compositor, compositor: &mut Compositor,
call: anyhow::Result<Option<Callback>>, 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 { if j.wait {
self.wait_futures.push(j.future); self.wait_futures.push(j.future);
} else { } else {

@ -386,21 +386,24 @@ impl Document {
/// If supported, returns the changes that should be applied to this document in order /// If supported, returns the changes that should be applied to this document in order
/// to format it nicely. /// to format it nicely.
pub fn format(&self) -> Option<impl Future<Output = LspFormatting> + 'static> { 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 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 fut = async move {
let edits = language_server let edits = request.await.unwrap_or_else(|e| {
.text_document_formatting(id, lsp::FormattingOptions::default(), None)
.await
.unwrap_or_else(|e| {
log::warn!("LSP formatting failed: {}", e); log::warn!("LSP formatting failed: {}", e);
Default::default() Default::default()
}); });
LspFormatting { LspFormatting {
doc: text, doc: text,
edits, edits,
offset_encoding: language_server.offset_encoding(), offset_encoding,
} }
}; };
Some(fut) Some(fut)
@ -469,9 +472,14 @@ impl Document {
to_writer(&mut file, encoding, &text).await?; to_writer(&mut file, encoding, &text).await?;
if let Some(language_server) = language_server { if let Some(language_server) = language_server {
language_server if language_server.is_initialized() {
.text_document_did_save(identifier, &text) return Ok(());
.await?; }
if let Some(notification) =
language_server.text_document_did_save(identifier, &text)
{
notification.await?;
}
} }
Ok(()) Ok(())
@ -646,7 +654,7 @@ impl Document {
// } // }
// emit lsp notification // 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( let notify = language_server.text_document_did_change(
self.versioned_identifier(), self.versioned_identifier(),
&old_doc, &old_doc,
@ -795,9 +803,18 @@ impl Document {
self.version self.version
} }
#[inline]
pub fn language_server(&self) -> Option<&helix_lsp::Client> { 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] #[inline]

@ -255,20 +255,21 @@ impl Editor {
.and_then(|language| self.language_servers.get(language).ok()); .and_then(|language| self.language_servers.get(language).ok());
if let Some(language_server) = language_server { if let Some(language_server) = language_server {
doc.set_language_server(Some(language_server.clone()));
let language_id = doc let language_id = doc
.language() .language()
.and_then(|s| s.split('.').last()) // source.rust .and_then(|s| s.split('.').last()) // source.rust
.map(ToOwned::to_owned) .map(ToOwned::to_owned)
.unwrap_or_default(); .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( tokio::spawn(language_server.text_document_did_open(
doc.url().unwrap(), doc.url().unwrap(),
doc.version(), doc.version(),
doc.text(), doc.text(),
language_id, language_id,
)); ));
doc.set_language_server(Some(language_server));
} }
let id = self.documents.insert(doc); let id = self.documents.insert(doc);
@ -287,14 +288,9 @@ impl Editor {
if close_buffer { if close_buffer {
// get around borrowck issues // get around borrowck issues
let language_servers = &mut self.language_servers;
let doc = &self.documents[view.doc]; let doc = &self.documents[view.doc];
let language_server = doc if let Some(language_server) = doc.language_server() {
.language
.as_ref()
.and_then(|language| language_servers.get(language).ok());
if let Some(language_server) = language_server {
tokio::spawn(language_server.text_document_did_close(doc.identifier())); tokio::spawn(language_server.text_document_did_close(doc.identifier()));
} }
self.documents.remove(view.doc); self.documents.remove(view.doc);
@ -324,20 +320,24 @@ impl Editor {
view.ensure_cursor_in_view(doc, self.config.scrolloff) view.ensure_cursor_in_view(doc, self.config.scrolloff)
} }
#[inline]
pub fn document(&self, id: DocumentId) -> Option<&Document> { pub fn document(&self, id: DocumentId) -> Option<&Document> {
self.documents.get(id) self.documents.get(id)
} }
#[inline]
pub fn document_mut(&mut self, id: DocumentId) -> Option<&mut Document> { pub fn document_mut(&mut self, id: DocumentId) -> Option<&mut Document> {
self.documents.get_mut(id) self.documents.get_mut(id)
} }
#[inline]
pub fn documents(&self) -> impl Iterator<Item = &Document> { 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> { 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> { 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)) .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) { pub fn cursor(&self) -> (Option<Position>, CursorKind) {
let view = view!(self); let view = view!(self);
let doc = &self.documents[view.doc]; let doc = &self.documents[view.doc];

@ -116,6 +116,17 @@ roots = []
language-server = { command = "typescript-language-server", args = ["--stdio"] } language-server = { command = "typescript-language-server", args = ["--stdio"] }
indent = { tab-width = 2, unit = " " } 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]] [[language]]
name = "css" name = "css"
scope = "source.css" scope = "source.css"
@ -204,7 +215,22 @@ injection-regex = "julia"
file-types = ["jl"] file-types = ["jl"]
roots = [] roots = []
comment-token = "#" 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 = " " } indent = { tab-width = 2, unit = " " }
[[language]] [[language]]

@ -61,7 +61,7 @@
(null) @constant (null) @constant
(number_literal) @number (number_literal) @number
(char_literal) @number (char_literal) @string
(call_expression (call_expression
function: (identifier) @function) function: (identifier) @function)

@ -17,9 +17,18 @@
; Identifiers ; 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 (type_identifier) @type
(field_identifier) @property (field_identifier) @property
(identifier) @variable (identifier) @variable
(package_identifier) @variable
; Operators ; Operators
@ -79,10 +88,8 @@
"go" "go"
"goto" "goto"
"if" "if"
"import"
"interface" "interface"
"map" "map"
"package"
"range" "range"
"return" "return"
"select" "select"
@ -92,6 +99,29 @@
"var" "var"
] @keyword ] @keyword
[
"import"
"package"
] @keyword.control.import
; Delimiters
[
":"
"."
","
";"
] @punctuation.delimiter
[
"("
")"
"["
"]"
"{"
"}"
] @punctuation.bracket
; Literals ; Literals
[ [
@ -111,7 +141,8 @@
[ [
(true) (true)
(false) (false)
(nil) ] @constant.builtin.boolean
] @constant.builtin
(nil) @constant.builtin
(comment) @comment (comment) @comment

@ -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

@ -2,19 +2,19 @@
(operator) @operator (operator) @operator
(exp_name (constructor) @constructor) (exp_name (constructor) @constructor)
(constructor_operator) @operator (constructor_operator) @operator
(module) @module_name (module) @namespace
(type) @type (type) @type
(type) @class (type) @class
(constructor) @constructor (constructor) @constructor
(pragma) @pragma (pragma) @pragma
(comment) @comment (comment) @comment
(signature name: (variable) @fun_type_name) (signature name: (variable) @fun_type_name)
(function name: (variable) @fun_name) (function name: (variable) @function)
(constraint class: (class_name (type)) @class) (constraint class: (class_name (type)) @class)
(class (class_head class: (class_name (type)) @class)) (class (class_head class: (class_name (type)) @class))
(instance (instance_head class: (class_name (type)) @class)) (instance (instance_head class: (class_name (type)) @class))
(integer) @literal (integer) @number
(exp_literal (float)) @literal (exp_literal (float)) @number
(char) @literal (char) @literal
(con_unit) @literal (con_unit) @literal
(con_list) @literal (con_list) @literal
@ -39,5 +39,7 @@
"do" @keyword "do" @keyword
"mdo" @keyword "mdo" @keyword
"rec" @keyword "rec" @keyword
"(" @paren [
")" @paren "("
")"
] @punctuation.bracket

@ -87,7 +87,7 @@
(template_string) (template_string)
] @string ] @string
(regex) @string.special (regex) @string.regexp
(number) @number (number) @number
; Tokens ; Tokens

@ -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) (triple_string)
@ -28,43 +22,43 @@
(call_expression (call_expression
(identifier) @function) (identifier) @function)
(call_expression (call_expression
(field_expression (identifier) @method .)) (field_expression (identifier) @function.method .))
(broadcast_call_expression (broadcast_call_expression
(identifier) @function) (identifier) @function)
(broadcast_call_expression (broadcast_call_expression
(field_expression (identifier) @method .)) (field_expression (identifier) @function.method .))
(parameter_list (parameter_list
(identifier) @parameter) (identifier) @variable.parameter)
(parameter_list (parameter_list
(optional_parameter . (optional_parameter .
(identifier) @parameter)) (identifier) @variable.parameter))
(typed_parameter (typed_parameter
(identifier) @parameter (identifier) @variable.parameter
(identifier) @type) (identifier) @type)
(type_parameter_list (type_parameter_list
(identifier) @type) (identifier) @type)
(typed_parameter (typed_parameter
(identifier) @parameter (identifier) @variable.parameter
(parameterized_identifier) @type) (parameterized_identifier) @type)
(function_expression (function_expression
. (identifier) @parameter) . (identifier) @variable.parameter)
(spread_parameter) @parameter (spread_parameter) @variable.parameter
(spread_parameter (spread_parameter
(identifier) @parameter) (identifier) @variable.parameter)
(named_argument (named_argument
. (identifier) @parameter) . (identifier) @variable.parameter)
(argument_list (argument_list
(typed_expression (typed_expression
(identifier) @parameter (identifier) @variable.parameter
(identifier) @type)) (identifier) @type))
(argument_list (argument_list
(typed_expression (typed_expression
(identifier) @parameter (identifier) @variable.parameter
(parameterized_identifier) @type)) (parameterized_identifier) @type))
;; Symbol expressions (:my-wanna-be-lisp-keyword) ;; Symbol expressions (:my-wanna-be-lisp-keyword)
(quote_expression (quote_expression
(identifier)) @symbol (identifier)) @string.special.symbol
;; Parsing error! foo (::Type) get's parsed as two quote expressions ;; Parsing error! foo (::Type) get's parsed as two quote expressions
(argument_list (argument_list
@ -76,7 +70,7 @@
(identifier) @type) (identifier) @type)
(parameterized_identifier (_)) @type (parameterized_identifier (_)) @type
(argument_list (argument_list
(typed_expression . (identifier) @parameter)) (typed_expression . (identifier) @variable.parameter))
(typed_expression (typed_expression
(identifier) @type .) (identifier) @type .)
@ -113,13 +107,13 @@
"end" @keyword "end" @keyword
(if_statement (if_statement
["if" "end"] @conditional) ["if" "end"] @keyword.control.conditional)
(elseif_clause (elseif_clause
["elseif"] @conditional) ["elseif"] @keyword.control.conditional)
(else_clause (else_clause
["else"] @conditional) ["else"] @keyword.control.conditional)
(ternary_expression (ternary_expression
["?" ":"] @conditional) ["?" ":"] @keyword.control.conditional)
(function_definition ["function" "end"] @keyword.function) (function_definition ["function" "end"] @keyword.function)
@ -134,47 +128,57 @@
"type" "type"
] @keyword ] @keyword
((identifier) @keyword (#any-of? @keyword "global" "local")) ((identifier) @keyword (match? @keyword "global|local"))
(compound_expression (compound_expression
["begin" "end"] @keyword) ["begin" "end"] @keyword)
(try_statement (try_statement
["try" "end" ] @exception) ["try" "end" ] @keyword.control.exception)
(finally_clause (finally_clause
"finally" @exception) "finally" @keyword.control.exception)
(catch_clause (catch_clause
"catch" @exception) "catch" @keyword.control.exception)
(quote_statement (quote_statement
["quote" "end"] @keyword) ["quote" "end"] @keyword)
(let_statement (let_statement
["let" "end"] @keyword) ["let" "end"] @keyword)
(for_statement (for_statement
["for" "end"] @repeat) ["for" "end"] @keyword.control.repeat)
(while_statement (while_statement
["while" "end"] @repeat) ["while" "end"] @keyword.control.repeat)
(break_statement) @repeat (break_statement) @keyword.control.repeat
(continue_statement) @repeat (continue_statement) @keyword.control.repeat
(for_binding (for_binding
"in" @repeat) "in" @keyword.control.repeat)
(for_clause (for_clause
"for" @repeat) "for" @keyword.control.repeat)
(do_clause (do_clause
["do" "end"] @keyword) ["do" "end"] @keyword)
(export_statement (export_statement
["export"] @include) ["export"] @keyword.control.import)
[ [
"using" "using"
"module" "module"
"import" "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) @constant.builtin) (match? @constant.builtin "^(nothing|Inf|NaN)$"))
(((identifier) @boolean) (eq? @boolean "true")) (((identifier) @constant.builtin.boolean) (#eq? @constant.builtin.boolean "true"))
(((identifier) @boolean) (eq? @boolean "false")) (((identifier) @constant.builtin.boolean) (#eq? @constant.builtin.boolean "false"))
["::" ":" "." "," "..." "!"] @punctuation.delimiter ["::" ":" "." "," "..." "!"] @punctuation.delimiter
["[" "]" "(" ")" "{" "}"] @punctuation.bracket ["[" "]" "(" ")" "{" "}"] @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_]*$"))

@ -259,7 +259,7 @@
(comment) @comment (comment) @comment
(bracket_group) @parameter (bracket_group) @variable.parameter
[(math_operator) "="] @operator [(math_operator) "="] @operator
@ -312,7 +312,7 @@
key: (word) @text.reference) key: (word) @text.reference)
(key_val_pair (key_val_pair
key: (_) @parameter key: (_) @variable.parameter
value: (_)) value: (_))
["[" "]" "{" "}"] @punctuation.bracket ;"(" ")" is has no special meaning in LaTeX ["[" "]" "{" "}"] @punctuation.bracket ;"(" ")" is has no special meaning in LaTeX

@ -23,27 +23,27 @@
"for" "for"
"do" "do"
"end" "end"
] @keyword.control.loop) ] @keyword.control.repeat)
(for_in_statement (for_in_statement
[ [
"for" "for"
"do" "do"
"end" "end"
] @keyword.control.loop) ] @keyword.control.repeat)
(while_statement (while_statement
[ [
"while" "while"
"do" "do"
"end" "end"
] @keyword.control.loop) ] @keyword.control.repeat)
(repeat_statement (repeat_statement
[ [
"repeat" "repeat"
"until" "until"
] @keyword.control.loop) ] @keyword.control.repeat)
(do_statement (do_statement
[ [
@ -65,7 +65,7 @@
"not" "not"
"and" "and"
"or" "or"
] @keyword.operator ] @operator
[ [
"=" "="
@ -108,7 +108,7 @@
[ [
(false) (false)
(true) (true)
] @boolean ] @constant.builtin.boolean
(nil) @constant.builtin (nil) @constant.builtin
(spread) @constant ;; "..." (spread) @constant ;; "..."
((identifier) @constant ((identifier) @constant
@ -116,7 +116,7 @@
;; Parameters ;; Parameters
(parameters (parameters
(identifier) @parameter) (identifier) @variable.parameter)
; ;; Functions ; ;; Functions
(function [(function_name) (identifier)] @function) (function [(function_name) (identifier)] @function)
@ -139,8 +139,8 @@
(function_call (function_call
[ [
((identifier) @variable (method) @method) ((identifier) @variable (method) @function.method)
((_) (method) @method) ((_) (method) @function.method)
(identifier) @function (identifier) @function
(field_expression (property_identifier) @function) (field_expression (property_identifier) @function)
] ]

@ -25,12 +25,12 @@
(external (value_name) @function) (external (value_name) @function)
(method_name) @method (method_name) @function.method
; Variables ; Variables
;---------- ;----------
(value_pattern) @parameter (value_pattern) @variable.parameter
; Application ; Application
;------------ ;------------
@ -60,7 +60,7 @@
[(number) (signed_number)] @number [(number) (signed_number)] @number
(character) @character (character) @constant.character
(string) @string (string) @string
@ -92,7 +92,7 @@
["include" "open"] @include ["include" "open"] @include
["for" "to" "downto" "while" "do" "done"] @keyword.control.loop ["for" "to" "downto" "while" "do" "done"] @keyword.control.repeat
; Macros ; Macros
;------- ;-------

@ -100,7 +100,7 @@
(bare_symbol) (bare_symbol)
] @string.special.symbol ] @string.special.symbol
(regex) @string.special.regex (regex) @string.regexp
(escape_sequence) @escape (escape_sequence) @escape
[ [

@ -17,7 +17,7 @@
(escape_sequence) @escape (escape_sequence) @escape
(primitive_type) @type.builtin (primitive_type) @type.builtin
(boolean_literal) @constant.builtin (boolean_literal) @constant.builtin.boolean
[ [
(integer_literal) (integer_literal)
(float_literal) (float_literal)
@ -149,7 +149,7 @@
(mutable_specifier) @keyword.mut (mutable_specifier) @keyword.mut
; TODO: variable.mut to highlight mutable identifiers via locals.scm
; ------- ; -------
; Guess Other Types ; Guess Other Types

@ -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

@ -0,0 +1 @@
; inherits: typescript

@ -1,6 +1,6 @@
(block_mapping_pair key: (_) @property) (block_mapping_pair key: (_) @property)
(flow_mapping (_ key: (_) @property)) (flow_mapping (_ key: (_) @property))
(boolean_scalar) @boolean (boolean_scalar) @constant.builtin.boolean
(null_scalar) @constant.builtin (null_scalar) @constant.builtin
(double_quote_scalar) @string (double_quote_scalar) @string
(single_quote_scalar) @string (single_quote_scalar) @string

@ -34,6 +34,7 @@
"comment" = { fg = "#6A9955" } "comment" = { fg = "#6A9955" }
"string" = { fg = "#ce9178" } "string" = { fg = "#ce9178" }
"string.regexp" = { fg = "regex" }
"number" = { fg = "#b5cea8" } "number" = { fg = "#b5cea8" }
"escape" = { fg = "#d7ba7d" } "escape" = { fg = "#d7ba7d" }

@ -34,6 +34,7 @@
"comment" = { fg = "#88846F" } "comment" = { fg = "#88846F" }
"string" = { fg = "#e6db74" } "string" = { fg = "#e6db74" }
"string.regexp" = { fg = "regex" }
"number" = { fg = "#ae81ff" } "number" = { fg = "#ae81ff" }
"escape" = { fg = "#ae81ff" } "escape" = { fg = "#ae81ff" }

@ -9,7 +9,7 @@ special = "honey"
property = "white" property = "white"
variable = "lavender" variable = "lavender"
# variable = "almond" # TODO: metavariables only # variable = "almond" # TODO: metavariables only
"variable.parameter" = "lavender" "variable.parameter" = { fg = "lavender", modifiers = ["underlined"] }
"variable.builtin" = "mint" "variable.builtin" = "mint"
type = "white" type = "white"
"type.builtin" = "white" # TODO: distinguish? "type.builtin" = "white" # TODO: distinguish?
@ -28,9 +28,7 @@ escape = "honey"
label = "honey" label = "honey"
# TODO: diferentiate doc comment # TODO: diferentiate doc comment
# concat (ERROR) @syntax-error and "MISSING ;" selectors for errors # concat (ERROR) @error.syntax and "MISSING ;" selectors for errors
module = "#ff0000"
"ui.background" = { bg = "midnight" } "ui.background" = { bg = "midnight" }
"ui.linenr" = { fg = "comet" } "ui.linenr" = { fg = "comet" }

Loading…
Cancel
Save