on master help sign in
Merge branch 'lsp-async-init'
fd36fbdBlaž Hrastnik
book/book.toml+2−1
@@ -3,8 +3,9 @@ authors = ["Blaž Hrastnik"]
3language = "en"
3language = "en"
4multilingual = false
4multilingual = false
5src = "src"
5src = "src"
6−theme = "colibri"
7edit-url-template = "https://github.com/helix-editor/helix/tree/master/book/{path}?mode=edit"
6edit-url-template = "https://github.com/helix-editor/helix/tree/master/book/{path}?mode=edit"
8
7
9[output.html]
8[output.html]
10cname = "docs.helix-editor.com"
9cname = "docs.helix-editor.com"
10+default-theme = "colibri"
11+preferred-dark-theme = "colibri"
book/src/configuration.md+13
@@ -5,6 +5,19 @@ To override global configuration parameters, create a `config.toml` file located
5* Linux and Mac: `~/.config/helix/config.toml`
5* Linux and Mac: `~/.config/helix/config.toml`
6* Windows: `%AppData%\helix\config.toml`
6* Windows: `%AppData%\helix\config.toml`
7
7
8+## Editor
9+
10+`[editor]` section of the config.
11+
12+| Key | Description | Default |
13+|--|--|---------|
14+| `scrolloff` | Number of lines of padding around the edge of the screen when scrolling. | `3` |
15+| `mouse` | Enable mouse mode. | `true` |
16+| `middle-click-paste` | Middle click paste support. | `true` |
17+| `scroll-lines` | Number of lines to scroll per scroll wheel step. | `3` |
18+| `shell` | Shell to use when running external commands. | Unix: `["sh", "-c"]`<br/>Windows: `["cmd", "/C"]` |
19+| `line-number` | Line number display (`absolute`, `relative`) | `absolute` |
20+
8## LSP
21## LSP
9
22
10To display all language server messages in the status line add the following to your `config.toml`:
23To display all language server messages in the status line add the following to your `config.toml`:
book/src/from-vim.md+2
@@ -7,4 +7,6 @@ going to act on (a word, a paragraph, a line, etc) is selected first and the
7action itself (delete, change, yank, etc) comes second. A cursor is simply a
7action itself (delete, change, yank, etc) comes second. A cursor is simply a
8single width selection.
8single width selection.
9
9
10+See also Kakoune's [Migrating from Vim](https://github.com/mawww/kakoune/wiki/Migrating-from-Vim).
11+
10> TODO: Mention texobjects, surround, registers
12> TODO: Mention texobjects, surround, registers
book/src/install.md+3−1
@@ -23,7 +23,9 @@ shell for working on Helix.
23
23
24### Arch Linux
24### Arch Linux
25
25
26+Releases are available in the `community` repository.
27+
26−Binary packages are available on AUR:
28+Packages are also available on AUR:
27- [helix-bin](https://aur.archlinux.org/packages/helix-bin/) contains the pre-built release
29- [helix-bin](https://aur.archlinux.org/packages/helix-bin/) contains the pre-built release
28- [helix-git](https://aur.archlinux.org/packages/helix-git/) builds the master branch
30- [helix-git](https://aur.archlinux.org/packages/helix-git/) builds the master branch
29
31
book/src/keymap.md+52−48
@@ -4,7 +4,7 @@
4
4
5### Movement
5### Movement
6
6
7−> NOTE: `f`, `F`, `t` and `T` are not confined to the current line.
7+> NOTE: Unlike vim, `f`, `F`, `t` and `T` are not confined to the current line.
8
8
9| Key | Description | Command |
9| Key | Description | Command |
10| ----- | ----------- | ------- |
10| ----- | ----------- | ------- |
@@ -28,14 +28,14 @@
28| `PageDown` | Move page down | `page_down` |
28| `PageDown` | Move page down | `page_down` |
29| `Ctrl-u` | Move half page up | `half_page_up` |
29| `Ctrl-u` | Move half page up | `half_page_up` |
30| `Ctrl-d` | Move half page down | `half_page_down` |
30| `Ctrl-d` | Move half page down | `half_page_down` |
31−| `Ctrl-i` | Jump forward on the jumplist TODO: conflicts tab | `jump_forward` |
31+| `Ctrl-i` | Jump forward on the jumplist | `jump_forward` |
32| `Ctrl-o` | Jump backward on the jumplist | `jump_backward` |
32| `Ctrl-o` | Jump backward on the jumplist | `jump_backward` |
33| `v` | Enter [select (extend) mode](#select--extend-mode) | `select_mode` |
33| `v` | Enter [select (extend) mode](#select--extend-mode) | `select_mode` |
34| `g` | Enter [goto mode](#goto-mode) | N/A |
34| `g` | Enter [goto mode](#goto-mode) | N/A |
35| `m` | Enter [match mode](#match-mode) | N/A |
35| `m` | Enter [match mode](#match-mode) | N/A |
36| `:` | Enter command mode | `command_mode` |
36| `:` | Enter command mode | `command_mode` |
37| `z` | Enter [view mode](#view-mode) | N/A |
37| `z` | Enter [view mode](#view-mode) | N/A |
38−| `Ctrl-w` | Enter [window mode](#window-mode) (maybe will be remove for spc w w later) | N/A |
38+| `Ctrl-w` | Enter [window mode](#window-mode) | N/A |
39| `Space` | Enter [space mode](#space-mode) | N/A |
39| `Space` | Enter [space mode](#space-mode) | N/A |
40| `K` | Show documentation for the item under the cursor | `hover` |
40| `K` | Show documentation for the item under the cursor | `hover` |
41
41
@@ -66,6 +66,16 @@
66| `d` | Delete selection | `delete_selection` |
66| `d` | Delete selection | `delete_selection` |
67| `c` | Change selection (delete and enter insert mode) | `change_selection` |
67| `c` | Change selection (delete and enter insert mode) | `change_selection` |
68
68
69+#### Shell
70+
71+| Key | Description | Command |
72+| ------ | ----------- | ------- |
73+| <code>&#124;</code> | Pipe each selection through shell command, replacing with output | `shell_pipe` |
74+| <code>A-&#124;</code> | Pipe each selection into shell command, ignoring output | `shell_pipe_to` |
75+| `!` | Run shell command, inserting output before each selection | `shell_insert_output` |
76+| `A-!` | Run shell command, appending output after each selection | `shell_append_output` |
77+
78+
69### Selection manipulation
79### Selection manipulation
70
80
71| Key | Description | Command |
81| Key | Description | Command |
@@ -87,17 +97,10 @@
87| | Expand selection to parent syntax node TODO: pick a key | `expand_selection` |
97| | Expand selection to parent syntax node TODO: pick a key | `expand_selection` |
88| `J` | Join lines inside selection | `join_selections` |
98| `J` | Join lines inside selection | `join_selections` |
89| `K` | Keep selections matching the regex TODO: overlapped by hover help | `keep_selections` |
99| `K` | Keep selections matching the regex TODO: overlapped by hover help | `keep_selections` |
100+| `$` | Pipe each selection into shell command, keep selections where command returned 0 | `shell_keep_pipe` |
90| `Space` | Keep only the primary selection TODO: overlapped by space mode | `keep_primary_selection` |
101| `Space` | Keep only the primary selection TODO: overlapped by space mode | `keep_primary_selection` |
91| `Ctrl-c` | Comment/uncomment the selections | `toggle_comments` |
102| `Ctrl-c` | Comment/uncomment the selections | `toggle_comments` |
92
103
93−### Insert Mode
94−
95−| Key | Description | Command |
96−| ----- | ----------- | ------- |
97−| `Escape` | Switch to normal mode | `normal_mode` |
98−| `Ctrl-x` | Autocomplete | `completion` |
99−| `Ctrl-w` | Delete previous word | `delete_word_backward` |
100−
101### Search
104### Search
102
105
103> TODO: The search implementation isn't ideal yet -- we don't support searching
106> TODO: The search implementation isn't ideal yet -- we don't support searching
@@ -110,38 +113,11 @@ in reverse, or searching via smartcase.
110| `N` | Add next search match to selection | `extend_search_next` |
113| `N` | Add next search match to selection | `extend_search_next` |
111| `*` | Use current selection as the search pattern | `search_selection` |
114| `*` | Use current selection as the search pattern | `search_selection` |
112
115
113−### Unimpaired
116+### Minor modes
114−
115−Mappings in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaired)
116−
117−| Key | Description | Command |
118−| ----- | ----------- | ------- |
119−| `[d` | Go to previous diagnostic | `goto_prev_diag` |
120−| `]d` | Go to next diagnostic | `goto_next_diag` |
121−| `[D` | Go to first diagnostic in document | `goto_first_diag` |
122−| `]D` | Go to last diagnostic in document | `goto_last_diag` |
123−| `[space` | Add newline above | `add_newline_above` |
124−| `]space` | Add newline below | `add_newline_below` |
125−
126−### Shell
127
117
128−| Key | Description | Command |
118+These sub-modes are accessible from normal mode and typically switch back to normal mode after a command.
129−| ------ | ----------- | ------- |
130−| `\|` | Pipe each selection through shell command, replacing with output | `shell_pipe` |
131−| `A-\|` | Pipe each selection into shell command, ignoring output | `shell_pipe_to` |
132−| `!` | Run shell command, inserting output before each selection | `shell_insert_output` |
133−| `A-!` | Run shell command, appending output after each selection | `shell_append_output` |
134−| `$` | Pipe each selection into shell command, keep selections where command returned 0 | `shell_keep_pipe` |
135−
136−## Select / extend mode
137−
138−I'm still pondering whether to keep this mode or not. It changes movement
139−commands to extend the existing selection instead of replacing it.
140−
141−> NOTE: It's a bit confusing at the moment because extend hasn't been
142−> implemented for all movement commands yet.
143
119
144−## View mode
120+#### View mode
145
121
146View mode is intended for scrolling and manipulating the view without changing
122View mode is intended for scrolling and manipulating the view without changing
147the selection.
123the selection.
@@ -155,7 +131,7 @@ the selection.
155| `j` | Scroll the view downwards | `scroll_down` |
131| `j` | Scroll the view downwards | `scroll_down` |
156| `k` | Scroll the view upwards | `scroll_up` |
132| `k` | Scroll the view upwards | `scroll_up` |
157
133
158−## Goto mode
134+#### Goto mode
159
135
160Jumps to various locations.
136Jumps to various locations.
161
137
@@ -177,7 +153,7 @@ Jumps to various locations.
177| `i` | Go to implementation | `goto_implementation` |
153| `i` | Go to implementation | `goto_implementation` |
178| `a` | Go to the last accessed/alternate file | `goto_last_accessed_file` |
154| `a` | Go to the last accessed/alternate file | `goto_last_accessed_file` |
179
155
180−## Match mode
156+#### Match mode
181
157
182Enter this mode using `m` from normal mode. See the relavant section
158Enter this mode using `m` from normal mode. See the relavant section
183in [Usage](./usage.md) for an explanation about [surround](./usage.md#surround)
159in [Usage](./usage.md) for an explanation about [surround](./usage.md#surround)
@@ -192,11 +168,9 @@ and [textobject](./usage.md#textobject) usage.
192| `a` `<object>` | Select around textobject | `select_textobject_around` |
168| `a` `<object>` | Select around textobject | `select_textobject_around` |
193| `i` `<object>` | Select inside textobject | `select_textobject_inner` |
169| `i` `<object>` | Select inside textobject | `select_textobject_inner` |
194
170
195−## Object mode
196−
197TODO: Mappings for selecting syntax nodes (a superset of `[`).
171TODO: Mappings for selecting syntax nodes (a superset of `[`).
198
172
199−## Window mode
173+#### Window mode
200
174
201This layer is similar to vim keybindings as kakoune does not support window.
175This layer is similar to vim keybindings as kakoune does not support window.
202
176
@@ -207,9 +181,9 @@ This layer is similar to vim keybindings as kakoune does not support window.
207| `h`, `Ctrl-h` | Horizontal bottom split | `hsplit` |
181| `h`, `Ctrl-h` | Horizontal bottom split | `hsplit` |
208| `q`, `Ctrl-q` | Close current window | `wclose` |
182| `q`, `Ctrl-q` | Close current window | `wclose` |
209
183
210−## Space mode
184+#### Space mode
211
185
212−This layer is a kludge of mappings I had under leader key in neovim.
186+This layer is a kludge of mappings, mostly pickers.
213
187
214| Key | Description | Command |
188| Key | Description | Command |
215| ----- | ----------- | ------- |
189| ----- | ----------- | ------- |
@@ -226,6 +200,36 @@ This layer is a kludge of mappings I had under leader key in neovim.
226| `Y` | Yank main selection to clipboard | `yank_main_selection_to_clipboard` |
200| `Y` | Yank main selection to clipboard | `yank_main_selection_to_clipboard` |
227| `R` | Replace selections by clipboard contents | `replace_selections_with_clipboard` |
201| `R` | Replace selections by clipboard contents | `replace_selections_with_clipboard` |
228
202
203+
204+#### Unimpaired
205+
206+Mappings in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaired).
207+
208+| Key | Description | Command |
209+| ----- | ----------- | ------- |
210+| `[d` | Go to previous diagnostic | `goto_prev_diag` |
211+| `]d` | Go to next diagnostic | `goto_next_diag` |
212+| `[D` | Go to first diagnostic in document | `goto_first_diag` |
213+| `]D` | Go to last diagnostic in document | `goto_last_diag` |
214+| `[space` | Add newline above | `add_newline_above` |
215+| `]space` | Add newline below | `add_newline_below` |
216+
217+## Insert Mode
218+
219+| Key | Description | Command |
220+| ----- | ----------- | ------- |
221+| `Escape` | Switch to normal mode | `normal_mode` |
222+| `Ctrl-x` | Autocomplete | `completion` |
223+| `Ctrl-w` | Delete previous word | `delete_word_backward` |
224+
225+## Select / extend mode
226+
227+I'm still pondering whether to keep this mode or not. It changes movement
228+commands (including goto) to extend the existing selection instead of replacing it.
229+
230+> NOTE: It's a bit confusing at the moment because extend hasn't been
231+> implemented for all movement commands yet.
232+
229# Picker
233# Picker
230
234
231Keys to use within picker. Remapping currently not supported.
235Keys to use within picker. Remapping currently not supported.
book/src/themes.md+124−78
@@ -30,85 +30,9 @@ if the key contains a dot `'.'`, it must be quoted to prevent it being parsed as
30"key.key" = "#ffffff"
30"key.key" = "#ffffff"
31```
31```
32
32
33−Possible modifiers:
33+### Color palettes
34
34
35−| Modifier |
36−| --- |
37−| `bold` |
38−| `dim` |
39−| `italic` |
40−| `underlined` |
41−| `slow\_blink` |
42−| `rapid\_blink` |
43−| `reversed` |
44−| `hidden` |
45−| `crossed\_out` |
46−
47−Possible keys:
48−
49−| Key | Notes |
50−| --- | --- |
51−| `attribute` | |
52−| `keyword` | |
53−| `keyword.directive` | Preprocessor directives (\#if in C) |
54−| `keyword.control` | Control flow |
55−| `namespace` | |
56−| `punctuation` | |
57−| `punctuation.delimiter` | |
58−| `operator` | |
59−| `special` | |
60−| `property` | |
61−| `variable` | |
62−| `variable.parameter` | |
63−| `type` | |
64−| `type.builtin` | |
65−| `type.enum.variant` | Enum variants |
66−| `constructor` | |
67−| `function` | |
68−| `function.macro` | |
69−| `function.builtin` | |
70−| `comment` | |
71−| `variable.builtin` | |
72−| `constant` | |
73−| `constant.builtin` | |
74−| `string` | |
75−| `number` | |
76−| `escape` | Escaped characters |
77−| `label` | For lifetimes |
78−| `module` | |
79−| `ui.background` | |
80−| `ui.cursor` | |
81−| `ui.cursor.insert` | |
82−| `ui.cursor.select` | |
83−| `ui.cursor.match` | Matching bracket etc. |
84−| `ui.cursor.primary` | Cursor with primary selection |
85−| `ui.linenr` | |
86−| `ui.linenr.selected` | |
87−| `ui.statusline` | |
88−| `ui.statusline.inactive` | |
89−| `ui.popup` | |
90−| `ui.window` | |
91−| `ui.help` | |
92−| `ui.text` | |
93−| `ui.text.focus` | |
94−| `ui.info` | |
95−| `ui.info.text` | |
96−| `ui.menu` | |
97−| `ui.menu.selected` | |
98−| `ui.selection` | For selections in the editing area |
99−| `ui.selection.primary` | |
100−| `warning` | LSP warning |
101−| `error` | LSP error |
102−| `info` | LSP info |
103−| `hint` | LSP hint |
104−
105−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.
106−
107−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.
108−
109−## Color palettes
110−
111−You can define a palette of named colors, and refer to them from the
35+It's recommended define a palette of named colors, and refer to them from the
112configuration values in your theme. To do this, add a table called
36configuration values in your theme. To do this, add a table called
113`palette` to your theme file:
37`palette` to your theme file:
114
38
@@ -146,3 +70,125 @@ over it and is merged into the default palette.
146| `light-cyan` |
70| `light-cyan` |
147| `light-gray` |
71| `light-gray` |
148| `white` |
72| `white` |
73+
74+### Modifiers
75+
76+The following values may be used as modifiers.
77+
78+Less common modifiers might not be supported by your terminal emulator.
79+
80+| Modifier |
81+| --- |
82+| `bold` |
83+| `dim` |
84+| `italic` |
85+| `underlined` |
86+| `slow_blink` |
87+| `rapid_blink` |
88+| `reversed` |
89+| `hidden` |
90+| `crossed_out` |
91+
92+### Scopes
93+
94+The following is a list of scopes available to use for styling.
95+
96+#### Syntax highlighting
97+
98+These keys match [tree-sitter scopes](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#theme).
99+
100+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`.
101+
102+We use a similar set of scopes as
103+[SublimeText](https://www.sublimetext.com/docs/scope_naming.html). See also
104+[TextMate](https://macromates.com/manual/en/language_grammars) scopes.
105+
106+- `escape` (TODO: rename to (constant).character.escape)
107+
108+- `type` - Types
109+ - `builtin` - Primitive types provided by the language (`int`, `usize`)
110+
111+- `constant` (TODO: constant.other.placeholder for %v)
112+ - `builtin` Special constants provided by the language (`true`, `false`, `nil` etc)
113+ - `boolean`
114+ - `character`
115+
116+- `number` (TODO: rename to constant.number/.numeric.{integer, float, complex})
117+- `string` (TODO: string.quoted.{single, double}, string.raw/.unquoted)?
118+ - `regexp` - Regular expressions
119+ - `special`
120+ - `path`
121+ - `url`
122+
123+- `comment` - Code comments
124+ - `line` - Single line comments (`//`)
125+ - `block` - Block comments (e.g. (`/* */`)
126+ - `documentation` - Documentation comments (e.g. `///` in Rust)
127+
128+- `variable` - Variables
129+ - `builtin` - Reserved language variables (`self`, `this`, `super`, etc)
130+ - `parameter` - Function parameters
131+ - `property`
132+ - `function` (TODO: ?)
133+
134+- `label`
135+
136+- `punctuation`
137+ - `delimiter` - Commas, colons
138+ - `bracket` - Parentheses, angle brackets, etc.
139+
140+- `keyword`
141+ - `control`
142+ - `conditional` - `if`, `else`
143+ - `repeat` - `for`, `while`, `loop`
144+ - `import` - `import`, `export`
145+ - (TODO: return?)
146+ - `directive` - Preprocessor directives (`#if` in C)
147+ - `function` - `fn`, `func`
148+
149+- `operator` - `||`, `+=`, `>`, `or`
150+
151+- `function`
152+ - `builtin`
153+ - `method`
154+ - `macro`
155+ - `special` (preprocesor in C)
156+
157+- `tag` - Tags (e.g. `<body>` in HTML)
158+
159+- `namespace`
160+
161+#### Interface
162+
163+These scopes are used for theming the editor interface.
164+
165+
166+| Key | Notes |
167+| --- | --- |
168+| `ui.background` | |
169+| `ui.cursor` | |
170+| `ui.cursor.insert` | |
171+| `ui.cursor.select` | |
172+| `ui.cursor.match` | Matching bracket etc. |
173+| `ui.cursor.primary` | Cursor with primary selection |
174+| `ui.linenr` | |
175+| `ui.linenr.selected` | |
176+| `ui.statusline` | Statusline |
177+| `ui.statusline.inactive` | Statusline (unfocused document) |
178+| `ui.popup` | |
179+| `ui.window` | |
180+| `ui.help` | |
181+| `ui.text` | |
182+| `ui.text.focus` | |
183+| `ui.info` | |
184+| `ui.info.text` | |
185+| `ui.menu` | |
186+| `ui.menu.selected` | |
187+| `ui.selection` | For selections in the editing area |
188+| `ui.selection.primary` | |
189+| `warning` | Diagnostics warning |
190+| `error` | Diagnostics error |
191+| `info` | Diagnostics info |
192+| `hint` | Diagnostics hint |
193+
194+
book/theme/css/general.css+13
@@ -114,6 +114,19 @@ h6:target::before {
114 margin-bottom: .875em;
114 margin-bottom: .875em;
115}
115}
116
116
117+.content ul li {
118+margin-bottom: .25rem;
119+}
120+.content ul {
121+ list-style-type: square;
122+}
123+.content ul ul, .content ol ul {
124+ margin-bottom: .5rem;
125+}
126+.content li p {
127+ margin-bottom: .5em;
128+}
129+
117.content p { line-height: 1.45em; }
130.content p { line-height: 1.45em; }
118.content ol { line-height: 1.45em; }
131.content ol { line-height: 1.45em; }
119.content ul { line-height: 1.45em; }
132.content ul { line-height: 1.45em; }
book/theme/css/variables.css+14−10
@@ -69,7 +69,7 @@
69
69
70 --links: #2b79a2;
70 --links: #2b79a2;
71
71
72− --inline-code-color: #c5c8c6;;
72+ --inline-code-color: #c5c8c6;
73
73
74 --theme-popup-bg: #141617;
74 --theme-popup-bg: #141617;
75 --theme-popup-border: #43484d;
75 --theme-popup-border: #43484d;
@@ -110,7 +110,7 @@
110
110
111 --links: #20609f;
111 --links: #20609f;
112
112
113− --inline-code-color: #301900;
113+ --inline-code-color: #a39e9b;
114
114
115 --theme-popup-bg: #fafafa;
115 --theme-popup-bg: #fafafa;
116 --theme-popup-border: #cccccc;
116 --theme-popup-border: #cccccc;
@@ -151,7 +151,7 @@
151
151
152 --links: #2b79a2;
152 --links: #2b79a2;
153
153
154− --inline-code-color: #c5c8c6;;
154+ --inline-code-color: #c5c8c6;
155
155
156 --theme-popup-bg: #161923;
156 --theme-popup-bg: #161923;
157 --theme-popup-border: #737480;
157 --theme-popup-border: #737480;
@@ -192,7 +192,7 @@
192
192
193 --links: #2b79a2;
193 --links: #2b79a2;
194
194
195− --inline-code-color: #6e6b5e;
195+ --inline-code-color: #c5c8c6;
196
196
197 --theme-popup-bg: #e1e1db;
197 --theme-popup-bg: #e1e1db;
198 --theme-popup-border: #b38f6b;
198 --theme-popup-border: #b38f6b;
@@ -234,7 +234,7 @@
234
234
235 --links: #2b79a2;
235 --links: #2b79a2;
236
236
237− --inline-code-color: #c5c8c6;;
237+ --inline-code-color: #6e6b5e;
238
238
239 --theme-popup-bg: #141617;
239 --theme-popup-bg: #141617;
240 --theme-popup-border: #43484d;
240 --theme-popup-border: #43484d;
@@ -261,6 +261,7 @@
261.colibri {
261.colibri {
262 --bg: #3b224c;
262 --bg: #3b224c;
263 --fg: #bcbdd0;
263 --fg: #bcbdd0;
264+ --heading-fg: #fff;
264
265
265 --sidebar-bg: #281733;
266 --sidebar-bg: #281733;
266 --sidebar-fg: #c8c9db;
267 --sidebar-fg: #c8c9db;
@@ -276,18 +277,19 @@
276 /* --links: #a4a0e8; */
277 /* --links: #a4a0e8; */
277 --links: #ECCDBA;
278 --links: #ECCDBA;
278
279
279− --inline-code-color: #c5c8c6;;
280+ --inline-code-color: hsl(48.7, 7.8%, 70%);
280
281
281 --theme-popup-bg: #161923;
282 --theme-popup-bg: #161923;
282 --theme-popup-border: #737480;
283 --theme-popup-border: #737480;
283 --theme-hover: rgba(0,0,0, .2);
284 --theme-hover: rgba(0,0,0, .2);
284
285
285− --quote-bg: hsl(226, 15%, 17%);
286+ --quote-bg: #281733;
286 --quote-border: hsl(226, 15%, 22%);
287 --quote-border: hsl(226, 15%, 22%);
287
288
288− --table-border-color: hsl(226, 23%, 16%);
289+ --table-border-color: hsl(226, 23%, 76%);
289− --table-header-bg: hsl(226, 23%, 31%);
290+ --table-header-bg: hsla(226, 23%, 31%, 0);
290 --table-alternate-bg: hsl(226, 23%, 14%);
291 --table-alternate-bg: hsl(226, 23%, 14%);
292+ --table-border-line: hsla(201deg, 20%, 92%, 0.2);
291
293
292 --searchbar-border-color: #aaa;
294 --searchbar-border-color: #aaa;
293 --searchbar-bg: #aeaec6;
295 --searchbar-bg: #aeaec6;
@@ -300,6 +302,7 @@
300}
302}
301
303
302.colibri {
304.colibri {
305+/*
303 --bg: #ffffff;
306 --bg: #ffffff;
304 --fg: #452859;
307 --fg: #452859;
305 --fg: #5a5977;
308 --fg: #5a5977;
@@ -318,7 +321,7 @@
318
321
319 --links: #6F44F0;
322 --links: #6F44F0;
320
323
321− --inline-code-color: #697C81;
324+ --inline-code-color: #a39e9b;
322
325
323 --theme-popup-bg: #161923;
326 --theme-popup-bg: #161923;
324 --theme-popup-border: #737480;
327 --theme-popup-border: #737480;
@@ -341,4 +344,5 @@
341 --searchresults-border-color: #5c5c68;
344 --searchresults-border-color: #5c5c68;
342 --searchresults-li-bg: #242430;
345 --searchresults-li-bg: #242430;
343 --search-mark-bg: #a2cff5;
346 --search-mark-bg: #a2cff5;
347+*/
344}
348}
book/theme/highlight.css+37−64
@@ -1,83 +1,56 @@
1−/*
1+pre code.hljs {
2− * An increased contrast highlighting scheme loosely based on the
2+ display:block;
3− * "Base16 Atelier Dune Light" theme by Bram de Haan
3+ overflow-x:auto;
4− * (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune)
4+ padding:1em
5− * Original Base16 color scheme by Chris Kempson
5+}
6− * (https://github.com/chriskempson/base16)
6+code.hljs {
7− */
7+ padding:3px 5px
8−
8+}
9−/* Comment */
9+.hljs {
10+ background:#2f1e2e;
11+ color:#a39e9b
12+}
10.hljs-comment,
13.hljs-comment,
11.hljs-quote {
14.hljs-quote {
12− color: #575757;
15+ color:#8d8687
13}
16}
14−
15−/* Red */
16−.hljs-variable,
17−.hljs-template-variable,
18−.hljs-attribute,
19−.hljs-tag,
20−.hljs-name,
21−.hljs-regexp,
22.hljs-link,
17.hljs-link,
18+.hljs-meta,
23.hljs-name,
19.hljs-name,
20+.hljs-regexp,
21+.hljs-selector-class,
24.hljs-selector-id,
22.hljs-selector-id,
25−.hljs-selector-class {
23+.hljs-tag,
24+.hljs-template-variable,
25+.hljs-variable {
26− color: #d70025;
26+ color:#ef6155
27}
27}
28−
29−/* Orange */
30−.hljs-number,
31−.hljs-meta,
32.hljs-built_in,
28.hljs-built_in,
33−.hljs-builtin-name,
29+.hljs-deletion,
34.hljs-literal,
30.hljs-literal,
31+.hljs-number,
32+.hljs-params,
35−.hljs-type,
33+.hljs-type {
36−.hljs-params {
37− color: #b21e00;
34+ color:#f99b15
38}
35}
39−
36+.hljs-attribute,
40−/* Green */
37+.hljs-section,
41−.hljs-string,
42−.hljs-symbol,
43−.hljs-bullet {
38+.hljs-title {
44− color: #008200;
39+ color:#fec418
45}
40}
46−
41+.hljs-addition,
47−/* Blue */
42+.hljs-bullet,
48−.hljs-title,
43+.hljs-string,
49−.hljs-section {
44+.hljs-symbol {
50− color: #0030f2;
45+ color:#48b685
51}
46}
52−
53−/* Purple */
54.hljs-keyword,
47.hljs-keyword,
55.hljs-selector-tag {
48.hljs-selector-tag {
56− color: #9d00ec;
57−}
58−
59−.hljs {
60− display: block;
61− overflow-x: auto;
62− background: #f6f7f6;
63− color: #000;
49+ color:#815ba4
64− padding: 0.5em;
65}
50}
66−
67.hljs-emphasis {
51.hljs-emphasis {
68− font-style: italic;
52+ font-style:italic
69}
53}
70−
71.hljs-strong {
54.hljs-strong {
72− font-weight: bold;
55+ font-weight:700
73−}
74−
75−.hljs-addition {
76− color: #22863a;
77− background-color: #f0fff4;
78−}
79−
80−.hljs-deletion {
81− color: #b31d28;
82− background-color: #ffeef0;
83}
56}
helix-core/src/indent.rs+8−3
@@ -316,8 +316,12 @@ pub fn suggested_indent_for_pos(
316pub fn get_scopes(syntax: Option<&Syntax>, text: RopeSlice, pos: usize) -> Vec<&'static str> {
316pub fn get_scopes(syntax: Option<&Syntax>, text: RopeSlice, pos: usize) -> Vec<&'static str> {
317 let mut scopes = Vec::new();
317 let mut scopes = Vec::new();
318 if let Some(syntax) = syntax {
318 if let Some(syntax) = syntax {
319− let byte_start = text.char_to_byte(pos);
319+ let pos = text.char_to_byte(pos);
320− let node = match get_highest_syntax_node_at_bytepos(syntax, byte_start) {
320+ let mut node = match syntax
321+ .tree()
322+ .root_node()
323+ .descendant_for_byte_range(pos, pos)
324+ {
321 Some(node) => node,
325 Some(node) => node,
322 None => return scopes,
326 None => return scopes,
323 };
327 };
@@ -325,7 +329,8 @@ pub fn get_scopes(syntax: Option<&Syntax>, text: RopeSlice, pos: usize) -> Vec<&
325 scopes.push(node.kind());
329 scopes.push(node.kind());
326
330
327 while let Some(parent) = node.parent() {
331 while let Some(parent) = node.parent() {
328− scopes.push(parent.kind())
332+ scopes.push(parent.kind());
333+ node = parent;
329 }
334 }
330 }
335 }
331
336
helix-core/src/syntax.rs+6−2
@@ -144,8 +144,12 @@ impl LanguageConfiguration {
144 &highlights_query,
144 &highlights_query,
145 &injections_query,
145 &injections_query,
146 &locals_query,
146 &locals_query,
147− )
147+ );
148− .unwrap(); // TODO: no unwrap
148+
149+ let config = match config {
150+ Ok(config) => config,
151+ Err(err) => panic!("{}", err),
152+ }; // TODO: avoid panic
149 config.configure(scopes);
153 config.configure(scopes);
150 Some(Arc::new(config))
154 Some(Arc::new(config))
151 }
155 }
helix-lsp/src/client.rs+49−41
@@ -9,11 +9,17 @@ use lsp_types as lsp;
9use serde_json::Value;
9use serde_json::Value;
10use std::future::Future;
10use std::future::Future;
11use std::process::Stdio;
11use std::process::Stdio;
12+use std::sync::{
12−use std::sync::atomic::{AtomicU64, Ordering};
13+ atomic::{AtomicU64, Ordering},
14+ Arc,
15+};
13use tokio::{
16use tokio::{
14 io::{BufReader, BufWriter},
17 io::{BufReader, BufWriter},
15 process::{Child, Command},
18 process::{Child, Command},
19+ sync::{
16− sync::mpsc::{channel, UnboundedReceiver, UnboundedSender},
20+ mpsc::{channel, UnboundedReceiver, UnboundedSender},
21+ Notify, OnceCell,
22+ },
17};
23};
18
24
19#[derive(Debug)]
25#[derive(Debug)]
@@ -22,18 +28,19 @@ pub struct Client {
22 _process: Child,
28 _process: Child,
23 server_tx: UnboundedSender<Payload>,
29 server_tx: UnboundedSender<Payload>,
24 request_counter: AtomicU64,
30 request_counter: AtomicU64,
25− capabilities: Option<lsp::ServerCapabilities>,
31+ pub(crate) capabilities: OnceCell<lsp::ServerCapabilities>,
26 offset_encoding: OffsetEncoding,
32 offset_encoding: OffsetEncoding,
27 config: Option<Value>,
33 config: Option<Value>,
28}
34}
29
35
30impl Client {
36impl Client {
37+ #[allow(clippy::type_complexity)]
31 pub fn start(
38 pub fn start(
32 cmd: &str,
39 cmd: &str,
33 args: &[String],
40 args: &[String],
34 config: Option<Value>,
41 config: Option<Value>,
35 id: usize,
42 id: usize,
36− ) -> Result<(Self, UnboundedReceiver<(usize, Call)>)> {
43+ ) -> Result<(Self, UnboundedReceiver<(usize, Call)>, Arc<Notify>)> {
37 let process = Command::new(cmd)
44 let process = Command::new(cmd)
38 .args(args)
45 .args(args)
39 .stdin(Stdio::piped())
46 .stdin(Stdio::piped())
@@ -50,22 +57,20 @@ impl Client {
50 let reader = BufReader::new(process.stdout.take().expect("Failed to open stdout"));
57 let reader = BufReader::new(process.stdout.take().expect("Failed to open stdout"));
51 let stderr = BufReader::new(process.stderr.take().expect("Failed to open stderr"));
58 let stderr = BufReader::new(process.stderr.take().expect("Failed to open stderr"));
52
59
60+ let (server_rx, server_tx, initialize_notify) =
53− let (server_rx, server_tx) = Transport::start(reader, writer, stderr, id);
61+ Transport::start(reader, writer, stderr, id);
54
62
55 let client = Self {
63 let client = Self {
56 id,
64 id,
57 _process: process,
65 _process: process,
58 server_tx,
66 server_tx,
59 request_counter: AtomicU64::new(0),
67 request_counter: AtomicU64::new(0),
60− capabilities: None,
68+ capabilities: OnceCell::new(),
61 offset_encoding: OffsetEncoding::Utf8,
69 offset_encoding: OffsetEncoding::Utf8,
62 config,
70 config,
63 };
71 };
64
72
65− // TODO: async client.initialize()
66− // maybe use an arc<atomic> flag
67−
68− Ok((client, server_rx))
73+ Ok((client, server_rx, initialize_notify))
69 }
74 }
70
75
71 pub fn id(&self) -> usize {
76 pub fn id(&self) -> usize {
@@ -88,9 +93,13 @@ impl Client {
88 }
93 }
89 }
94 }
90
95
96+ pub fn is_initialized(&self) -> bool {
97+ self.capabilities.get().is_some()
98+ }
99+
91 pub fn capabilities(&self) -> &lsp::ServerCapabilities {
100 pub fn capabilities(&self) -> &lsp::ServerCapabilities {
92 self.capabilities
101 self.capabilities
93− .as_ref()
102+ .get()
94 .expect("language server not yet initialized!")
103 .expect("language server not yet initialized!")
95 }
104 }
96
105
@@ -143,7 +152,8 @@ impl Client {
143 })
152 })
144 .map_err(|e| Error::Other(e.into()))?;
153 .map_err(|e| Error::Other(e.into()))?;
145
154
155+ // TODO: specifiable timeout, delay other calls until initialize success
146− timeout(Duration::from_secs(2), rx.recv())
156+ timeout(Duration::from_secs(20), rx.recv())
147 .await
157 .await
148 .map_err(|_| Error::Timeout)? // return Timeout
158 .map_err(|_| Error::Timeout)? // return Timeout
149 .ok_or(Error::StreamClosed)?
159 .ok_or(Error::StreamClosed)?
@@ -151,7 +161,7 @@ impl Client {
151 }
161 }
152
162
153 /// Send a RPC notification to the language server.
163 /// Send a RPC notification to the language server.
154− fn notify<R: lsp::notification::Notification>(
164+ pub fn notify<R: lsp::notification::Notification>(
155 &self,
165 &self,
156 params: R::Params,
166 params: R::Params,
157 ) -> impl Future<Output = Result<()>>
167 ) -> impl Future<Output = Result<()>>
@@ -213,7 +223,7 @@ impl Client {
213 // General messages
223 // General messages
214 // -------------------------------------------------------------------------------------------
224 // -------------------------------------------------------------------------------------------
215
225
216− pub(crate) async fn initialize(&mut self) -> Result<()> {
226+ pub(crate) async fn initialize(&self) -> Result<lsp::InitializeResult> {
217 // TODO: delay any requests that are triggered prior to initialize
227 // TODO: delay any requests that are triggered prior to initialize
218 let root = find_root(None).and_then(|root| lsp::Url::from_file_path(root).ok());
228 let root = find_root(None).and_then(|root| lsp::Url::from_file_path(root).ok());
219
229
@@ -281,14 +291,7 @@ impl Client {
281 locale: None, // TODO
291 locale: None, // TODO
282 };
292 };
283
293
284− let response = self.request::<lsp::request::Initialize>(params).await?;
294+ self.request::<lsp::request::Initialize>(params).await
285− self.capabilities = Some(response.capabilities);
286−
287− // next up, notify<initialized>
288− self.notify::<lsp::notification::Initialized>(lsp::InitializedParams {})
289− .await?;
290−
291− Ok(())
292 }
295 }
293
296
294 pub async fn shutdown(&self) -> Result<()> {
297 pub async fn shutdown(&self) -> Result<()> {
@@ -445,7 +448,7 @@ impl Client {
445 ) -> Option<impl Future<Output = Result<()>>> {
448 ) -> Option<impl Future<Output = Result<()>>> {
446 // figure out what kind of sync the server supports
449 // figure out what kind of sync the server supports
447
450
448− let capabilities = self.capabilities.as_ref().unwrap();
451+ let capabilities = self.capabilities.get().unwrap();
449
452
450 let sync_capabilities = match capabilities.text_document_sync {
453 let sync_capabilities = match capabilities.text_document_sync {
451 Some(lsp::TextDocumentSyncCapability::Kind(kind))
454 Some(lsp::TextDocumentSyncCapability::Kind(kind))
@@ -463,7 +466,7 @@ impl Client {
463 // range = None -> whole document
466 // range = None -> whole document
464 range: None, //Some(Range)
467 range: None, //Some(Range)
465 range_length: None, // u64 apparently deprecated
468 range_length: None, // u64 apparently deprecated
466− text: "".to_string(),
469+ text: new_text.to_string(),
467 }]
470 }]
468 }
471 }
469 lsp::TextDocumentSyncKind::Incremental => {
472 lsp::TextDocumentSyncKind::Incremental => {
@@ -491,12 +494,12 @@ impl Client {
491
494
492 // will_save / will_save_wait_until
495 // will_save / will_save_wait_until
493
496
494− pub async fn text_document_did_save(
497+ pub fn text_document_did_save(
495 &self,
498 &self,
496 text_document: lsp::TextDocumentIdentifier,
499 text_document: lsp::TextDocumentIdentifier,
497 text: &Rope,
500 text: &Rope,
498− ) -> Result<()> {
501+ ) -> Option<impl Future<Output = Result<()>>> {
499− let capabilities = self.capabilities.as_ref().unwrap();
502+ let capabilities = self.capabilities.get().unwrap();
500
503
501 let include_text = match &capabilities.text_document_sync {
504 let include_text = match &capabilities.text_document_sync {
502 Some(lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
505 Some(lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
@@ -508,17 +511,18 @@ impl Client {
508 include_text,
511 include_text,
509 }) => include_text.unwrap_or(false),
512 }) => include_text.unwrap_or(false),
510 // Supported(false)
513 // Supported(false)
511− _ => return Ok(()),
514+ _ => return None,
512 },
515 },
513 // unsupported
516 // unsupported
514− _ => return Ok(()),
517+ _ => return None,
515 };
518 };
516
519
517− self.notify::<lsp::notification::DidSaveTextDocument>(lsp::DidSaveTextDocumentParams {
520+ Some(self.notify::<lsp::notification::DidSaveTextDocument>(
521+ lsp::DidSaveTextDocumentParams {
518− text_document,
522+ text_document,
519− text: include_text.then(|| text.into()),
523+ text: include_text.then(|| text.into()),
520− })
524+ },
521− .await
525+ ))
522 }
526 }
523
527
524 pub fn completion(
528 pub fn completion(
@@ -584,19 +588,19 @@ impl Client {
584
588
585 // formatting
589 // formatting
586
590
587− pub async fn text_document_formatting(
591+ pub fn text_document_formatting(
588 &self,
592 &self,
589 text_document: lsp::TextDocumentIdentifier,
593 text_document: lsp::TextDocumentIdentifier,
590 options: lsp::FormattingOptions,
594 options: lsp::FormattingOptions,
591 work_done_token: Option<lsp::ProgressToken>,
595 work_done_token: Option<lsp::ProgressToken>,
592− ) -> anyhow::Result<Vec<lsp::TextEdit>> {
596+ ) -> Option<impl Future<Output = Result<Vec<lsp::TextEdit>>>> {
593− let capabilities = self.capabilities.as_ref().unwrap();
597+ let capabilities = self.capabilities.get().unwrap();
594
598
595 // check if we're able to format
599 // check if we're able to format
596 match capabilities.document_formatting_provider {
600 match capabilities.document_formatting_provider {
597 Some(lsp::OneOf::Left(true)) | Some(lsp::OneOf::Right(_)) => (),
601 Some(lsp::OneOf::Left(true)) | Some(lsp::OneOf::Right(_)) => (),
598 // None | Some(false)
602 // None | Some(false)
599− _ => return Ok(Vec::new()),
603+ _ => return None,
600 };
604 };
601 // TODO: return err::unavailable so we can fall back to tree sitter formatting
605 // TODO: return err::unavailable so we can fall back to tree sitter formatting
602
606
@@ -606,9 +610,13 @@ impl Client {
606 work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
610 work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
607 };
611 };
608
612
609− let response = self.request::<lsp::request::Formatting>(params).await?;
613+ let request = self.call::<lsp::request::Formatting>(params);
610
614
615+ Some(async move {
616+ let json = request.await?;
617+ let response: Vec<lsp::TextEdit> = serde_json::from_value(json)?;
611− Ok(response.unwrap_or_default())
618+ Ok(response)
619+ })
612 }
620 }
613
621
614 pub async fn text_document_range_formatting(
622 pub async fn text_document_range_formatting(
@@ -618,7 +626,7 @@ impl Client {
618 options: lsp::FormattingOptions,
626 options: lsp::FormattingOptions,
619 work_done_token: Option<lsp::ProgressToken>,
627 work_done_token: Option<lsp::ProgressToken>,
620 ) -> anyhow::Result<Vec<lsp::TextEdit>> {
628 ) -> anyhow::Result<Vec<lsp::TextEdit>> {
621− let capabilities = self.capabilities.as_ref().unwrap();
629+ let capabilities = self.capabilities.get().unwrap();
622
630
623 // check if we're able to format
631 // check if we're able to format
624 match capabilities.document_range_formatting_provider {
632 match capabilities.document_range_formatting_provider {
helix-lsp/src/lib.rs+49−53
@@ -226,6 +226,8 @@ impl MethodCall {
226
226
227#[derive(Debug, PartialEq, Clone)]
227#[derive(Debug, PartialEq, Clone)]
228pub enum Notification {
228pub enum Notification {
229+ // we inject this notification to signal the LSP is ready
230+ Initialized,
229 PublishDiagnostics(lsp::PublishDiagnosticsParams),
231 PublishDiagnostics(lsp::PublishDiagnosticsParams),
230 ShowMessage(lsp::ShowMessageParams),
232 ShowMessage(lsp::ShowMessageParams),
231 LogMessage(lsp::LogMessageParams),
233 LogMessage(lsp::LogMessageParams),
@@ -237,6 +239,7 @@ impl Notification {
237 use lsp::notification::Notification as _;
239 use lsp::notification::Notification as _;
238
240
239 let notification = match method {
241 let notification = match method {
242+ lsp::notification::Initialized::METHOD => Self::Initialized,
240 lsp::notification::PublishDiagnostics::METHOD => {
243 lsp::notification::PublishDiagnostics::METHOD => {
241 let params: lsp::PublishDiagnosticsParams = params
244 let params: lsp::PublishDiagnosticsParams = params
242 .parse()
245 .parse()
@@ -294,7 +297,7 @@ impl Registry {
294 }
297 }
295 }
298 }
296
299
297− pub fn get_by_id(&mut self, id: usize) -> Option<&Client> {
300+ pub fn get_by_id(&self, id: usize) -> Option<&Client> {
298 self.inner
301 self.inner
299 .values()
302 .values()
300 .find(|(client_id, _)| client_id == &id)
303 .find(|(client_id, _)| client_id == &id)
@@ -302,33 +305,52 @@ impl Registry {
302 }
305 }
303
306
304 pub fn get(&mut self, language_config: &LanguageConfiguration) -> Result<Arc<Client>> {
307 pub fn get(&mut self, language_config: &LanguageConfiguration) -> Result<Arc<Client>> {
305− if let Some(config) = &language_config.language_server {
308+ let config = match &language_config.language_server {
306− // avoid borrow issues
309+ Some(config) => config,
307− let inner = &mut self.inner;
310+ None => return Err(Error::LspNotDefined),
308− let s_incoming = &mut self.incoming;
311+ };
309−
312+
310− match inner.entry(language_config.scope.clone()) {
313+ match self.inner.entry(language_config.scope.clone()) {
311− Entry::Occupied(entry) => Ok(entry.get().1.clone()),
314+ Entry::Occupied(entry) => Ok(entry.get().1.clone()),
312− Entry::Vacant(entry) => {
315+ Entry::Vacant(entry) => {
313− // initialize a new client
316+ // initialize a new client
314− let id = self.counter.fetch_add(1, Ordering::Relaxed);
317+ let id = self.counter.fetch_add(1, Ordering::Relaxed);
315− let (mut client, incoming) = Client::start(
318+ let (client, incoming, initialize_notify) = Client::start(
316− &config.command,
319+ &config.command,
317− &config.args,
320+ &config.args,
318− serde_json::from_str(language_config.config.as_deref().unwrap_or("")).ok(),
321+ serde_json::from_str(language_config.config.as_deref().unwrap_or("")).ok(),
319− id,
322+ id,
320− )?;
323+ )?;
321− // TODO: run this async without blocking
322− futures_executor::block_on(client.initialize())?;
323− s_incoming.push(UnboundedReceiverStream::new(incoming));
324+ self.incoming.push(UnboundedReceiverStream::new(incoming));
324− let client = Arc::new(client);
325+ let client = Arc::new(client);
325−
326+
327+ // Initialize the client asynchronously
328+ let _client = client.clone();
329+ tokio::spawn(async move {
330+ use futures_util::TryFutureExt;
331+ let value = _client
332+ .capabilities
333+ .get_or_try_init(|| {
334+ _client
335+ .initialize()
336+ .map_ok(|response| response.capabilities)
337+ })
338+ .await;
339+
340+ value.expect("failed to initialize capabilities");
341+
342+ // next up, notify<initialized>
343+ _client
344+ .notify::<lsp::notification::Initialized>(lsp::InitializedParams {})
345+ .await
346+ .unwrap();
347+
348+ initialize_notify.notify_one();
349+ });
350+
326− entry.insert((id, client.clone()));
351+ entry.insert((id, client.clone()));
327− Ok(client)
352+ Ok(client)
328− }
329 }
353 }
330− } else {
331− Err(Error::LspNotDefined)
332 }
354 }
333 }
355 }
334
356
@@ -415,32 +437,6 @@ impl LspProgressMap {
415 }
437 }
416}
438}
417
439
418−// REGISTRY = HashMap<LanguageId, Lazy/OnceCell<Arc<RwLock<Client>>>
419−// spawn one server per language type, need to spawn one per workspace if server doesn't support
420−// workspaces
421−//
422−// could also be a client per root dir
423−//
424−// storing a copy of Option<Arc<RwLock<Client>>> on Document would make the LSP client easily
425−// accessible during edit/save callbacks
426−//
427−// the event loop needs to process all incoming streams, maybe we can just have that be a separate
428−// task that's continually running and store the state on the client, then use read lock to
429−// retrieve data during render
430−// -> PROBLEM: how do you trigger an update on the editor side when data updates?
431−//
432−// -> The data updates should pull all events until we run out so we don't frequently re-render
433−//
434−//
435−// v2:
436−//
437−// there should be a registry of lsp clients, one per language type (or workspace).
438−// the clients should lazy init on first access
439−// the client.initialize() should be called async and we buffer any requests until that completes
440−// there needs to be a way to process incoming lsp messages from all clients.
441−// -> notifications need to be dispatched to wherever
442−// -> requests need to generate a reply and travel back to the same lsp!
443−
444#[cfg(test)]
440#[cfg(test)]
445mod tests {
441mod tests {
446 use super::{lsp, util::*, OffsetEncoding};
442 use super::{lsp, util::*, OffsetEncoding};
helix-lsp/src/transport.rs+115−22
@@ -1,7 +1,7 @@
1−use crate::Result;
1+use crate::{Error, Result};
2use anyhow::Context;
2use anyhow::Context;
3use jsonrpc_core as jsonrpc;
3use jsonrpc_core as jsonrpc;
4−use log::{debug, error, info, warn};
4+use log::{error, info};
5use serde::{Deserialize, Serialize};
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
6use serde_json::Value;
7use std::collections::HashMap;
7use std::collections::HashMap;
@@ -11,7 +11,7 @@ use tokio::{
11 process::{ChildStderr, ChildStdin, ChildStdout},
11 process::{ChildStderr, ChildStdin, ChildStdout},
12 sync::{
12 sync::{
13 mpsc::{unbounded_channel, Sender, UnboundedReceiver, UnboundedSender},
13 mpsc::{unbounded_channel, Sender, UnboundedReceiver, UnboundedSender},
14− Mutex,
14+ Mutex, Notify,
15 },
15 },
16};
16};
17
17
@@ -51,9 +51,11 @@ impl Transport {
51 ) -> (
51 ) -> (
52 UnboundedReceiver<(usize, jsonrpc::Call)>,
52 UnboundedReceiver<(usize, jsonrpc::Call)>,
53 UnboundedSender<Payload>,
53 UnboundedSender<Payload>,
54+ Arc<Notify>,
54 ) {
55 ) {
55 let (client_tx, rx) = unbounded_channel();
56 let (client_tx, rx) = unbounded_channel();
56 let (tx, client_rx) = unbounded_channel();
57 let (tx, client_rx) = unbounded_channel();
58+ let notify = Arc::new(Notify::new());
57
59
58 let transport = Self {
60 let transport = Self {
59 id,
61 id,
@@ -62,11 +64,21 @@ impl Transport {
62
64
63 let transport = Arc::new(transport);
65 let transport = Arc::new(transport);
64
66
65− tokio::spawn(Self::recv(transport.clone(), server_stdout, client_tx));
67+ tokio::spawn(Self::recv(
68+ transport.clone(),
69+ server_stdout,
70+ client_tx.clone(),
71+ ));
66 tokio::spawn(Self::err(transport.clone(), server_stderr));
72 tokio::spawn(Self::err(transport.clone(), server_stderr));
67− tokio::spawn(Self::send(transport, server_stdin, client_rx));
73+ tokio::spawn(Self::send(
68−
74+ transport,
75+ server_stdin,
76+ client_tx,
77+ client_rx,
78+ notify.clone(),
79+ ));
80+
69− (rx, tx)
81+ (rx, tx, notify)
70 }
82 }
71
83
72 async fn recv_server_message(
84 async fn recv_server_message(
@@ -76,14 +88,18 @@ impl Transport {
76 let mut content_length = None;
88 let mut content_length = None;
77 loop {
89 loop {
78 buffer.truncate(0);
90 buffer.truncate(0);
79− reader.read_line(buffer).await?;
91+ if reader.read_line(buffer).await? == 0 {
92+ return Err(Error::StreamClosed);
93+ };
94+
80− let header = buffer.trim();
95+ // debug!("<- header {:?}", buffer);
81
96
82− if header.is_empty() {
97+ if buffer == "\r\n" {
98+ // look for an empty CRLF line
83 break;
99 break;
84 }
100 }
85
101
86− debug!("<- header {}", header);
102+ let header = buffer.trim();
87
103
88 let parts = header.split_once(": ");
104 let parts = header.split_once(": ");
89
105
@@ -96,7 +112,8 @@ impl Transport {
96 // Workaround: Some non-conformant language servers will output logging and other garbage
112 // Workaround: Some non-conformant language servers will output logging and other garbage
97 // into the same stream as JSON-RPC messages. This can also happen from shell scripts that spawn
113 // into the same stream as JSON-RPC messages. This can also happen from shell scripts that spawn
98 // the server. Skip such lines and log a warning.
114 // the server. Skip such lines and log a warning.
115+
99− warn!("Failed to parse header: {:?}", header);
116+ // warn!("Failed to parse header: {:?}", header);
100 }
117 }
101 }
118 }
102 }
119 }
@@ -121,8 +138,10 @@ impl Transport {
121 buffer: &mut String,
138 buffer: &mut String,
122 ) -> Result<()> {
139 ) -> Result<()> {
123 buffer.truncate(0);
140 buffer.truncate(0);
124− err.read_line(buffer).await?;
141+ if err.read_line(buffer).await? == 0 {
142+ return Err(Error::StreamClosed);
143+ };
125− error!("err <- {}", buffer);
144+ error!("err <- {:?}", buffer);
126
145
127 Ok(())
146 Ok(())
128 }
147 }
@@ -255,16 +274,90 @@ impl Transport {
255 async fn send(
274 async fn send(
256 transport: Arc<Self>,
275 transport: Arc<Self>,
257 mut server_stdin: BufWriter<ChildStdin>,
276 mut server_stdin: BufWriter<ChildStdin>,
277+ client_tx: UnboundedSender<(usize, jsonrpc::Call)>,
258 mut client_rx: UnboundedReceiver<Payload>,
278 mut client_rx: UnboundedReceiver<Payload>,
279+ initialize_notify: Arc<Notify>,
259 ) {
280 ) {
281+ let mut pending_messages: Vec<Payload> = Vec::new();
282+ let mut is_pending = true;
283+
284+ // Determine if a message is allowed to be sent early
285+ fn is_initialize(payload: &Payload) -> bool {
286+ use lsp_types::{
287+ notification::{Initialized, Notification},
288+ request::{Initialize, Request},
289+ };
290+ match payload {
291+ Payload::Request {
292+ value: jsonrpc::MethodCall { method, .. },
293+ ..
294+ } if method == Initialize::METHOD => true,
295+ Payload::Notification(jsonrpc::Notification { method, .. })
296+ if method == Initialized::METHOD =>
297+ {
298+ true
299+ }
300+ _ => false,
301+ }
302+ }
303+
304+ // TODO: events that use capabilities need to do the right thing
305+
306+ loop {
307+ tokio::select! {
308+ biased;
309+ _ = initialize_notify.notified() => { // TODO: notified is technically not cancellation safe
310+ // server successfully initialized
311+ is_pending = false;
312+
313+ use lsp_types::notification::Notification;
314+ // Hack: inject an initialized notification so we trigger code that needs to happen after init
315+ let notification = ServerMessage::Call(jsonrpc::Call::Notification(jsonrpc::Notification {
316+ jsonrpc: None,
317+
318+ method: lsp_types::notification::Initialized::METHOD.to_string(),
319+ params: jsonrpc::Params::None,
320+ }));
321+ match transport.process_server_message(&client_tx, notification).await {
322+ Ok(_) => {}
323+ Err(err) => {
324+ error!("err: <- {:?}", err);
325+ }
326+ }
327+
328+ // drain the pending queue and send payloads to server
329+ for msg in pending_messages.drain(..) {
330+ log::info!("Draining pending message {:?}", msg);
331+ match transport.send_payload_to_server(&mut server_stdin, msg).await {
332+ Ok(_) => {}
333+ Err(err) => {
334+ error!("err: <- {:?}", err);
335+ }
336+ }
337+ }
338+ }
260− while let Some(msg) = client_rx.recv().await {
339+ msg = client_rx.recv() => {
261− match transport
340+ if let Some(msg) = msg {
341+ if is_pending && !is_initialize(&msg) {
342+ // ignore notifications
343+ if let Payload::Notification(_) = msg {
344+ continue;
345+ }
346+
347+ log::info!("Language server not initialized, delaying request");
348+ pending_messages.push(msg);
349+ } else {
262− .send_payload_to_server(&mut server_stdin, msg)
350+ match transport.send_payload_to_server(&mut server_stdin, msg).await {
263− .await
264− {
265− Ok(_) => {}
351+ Ok(_) => {}
266− Err(err) => {
352+ Err(err) => {
267− error!("err: <- {:?}", err);
353+ error!("err: <- {:?}", err);
354+ }
355+ }
356+ }
357+ } else {
358+ // channel closed
359+ break;
360+ }
268 }
361 }
269 }
362 }
270 }
363 }
helix-syntax/build.rs+1−2
@@ -158,10 +158,9 @@ fn build_dir(dir: &str, language: &str) {
158 .is_none()
158 .is_none()
159 {
159 {
160 eprintln!(
160 eprintln!(
161− "The directory {} is empty, did you use 'git clone --recursive'?",
161+ "The directory {} is empty, you probably need to use 'git submodule update --init --recursive'?",
162 dir
162 dir
163 );
163 );
164− eprintln!("You can fix in using 'git submodule init && git submodule update --recursive'.");
165 std::process::exit(1);
164 std::process::exit(1);
166 }
165 }
167
166
helix-syntax/languages/tree-sitter-julia+1−1

Submodule changed.

helix-term/src/application.rs+53−54
@@ -4,7 +4,7 @@ use helix_view::{theme, Editor};
4
4
5use crate::{args::Args, compositor::Compositor, config::Config, job::Jobs, ui};
5use crate::{args::Args, compositor::Compositor, config::Config, job::Jobs, ui};
6
6
7−use log::error;
7+use log::{error, warn};
8
8
9use std::{
9use std::{
10 io::{stdout, Write},
10 io::{stdout, Write},
@@ -275,16 +275,42 @@ impl Application {
275 };
275 };
276
276
277 match notification {
277 match notification {
278− Notification::PublishDiagnostics(params) => {
278+ Notification::Initialized => {
279− let path = Some(params.uri.to_file_path().unwrap());
279+ let language_server =
280+ match self.editor.language_servers.get_by_id(server_id) {
281+ Some(language_server) => language_server,
282+ None => {
283+ warn!("can't find language server with id `{}`", server_id);
284+ return;
285+ }
286+ };
287+
288+ let docs = self.editor.documents().filter(|doc| {
289+ doc.language_server().map(|server| server.id()) == Some(server_id)
290+ });
280
291
292+ // trigger textDocument/didOpen for docs that are already open
293+ for doc in docs {
294+ // TODO: extract and share with editor.open
295+ let language_id = doc
296+ .language()
297+ .and_then(|s| s.split('.').last()) // source.rust
298+ .map(ToOwned::to_owned)
299+ .unwrap_or_default();
300+
301+ tokio::spawn(language_server.text_document_did_open(
302+ doc.url().unwrap(),
303+ doc.version(),
304+ doc.text(),
305+ language_id,
306+ ));
307+ }
308+ }
309+ Notification::PublishDiagnostics(params) => {
310+ let path = params.uri.to_file_path().unwrap();
281− let doc = self
311+ let doc = self.editor.document_by_path_mut(&path);
282− .editor
283− .documents
284− .iter_mut()
285− .find(|(_, doc)| doc.path() == path.as_ref());
286
312
287− if let Some((_, doc)) = doc {
313+ if let Some(doc) = doc {
288 let text = doc.text();
314 let text = doc.text();
289
315
290 let diagnostics = params
316 let diagnostics = params
@@ -429,10 +455,27 @@ impl Application {
429 Call::MethodCall(helix_lsp::jsonrpc::MethodCall {
455 Call::MethodCall(helix_lsp::jsonrpc::MethodCall {
430 method, params, id, ..
456 method, params, id, ..
431 }) => {
457 }) => {
458+ let language_server = match self.editor.language_servers.get_by_id(server_id) {
459+ Some(language_server) => language_server,
460+ None => {
461+ warn!("can't find language server with id `{}`", server_id);
462+ return;
463+ }
464+ };
465+
432 let call = match MethodCall::parse(&method, params) {
466 let call = match MethodCall::parse(&method, params) {
433 Some(call) => call,
467 Some(call) => call,
434 None => {
468 None => {
435 error!("Method not found {}", method);
469 error!("Method not found {}", method);
470+ // language_server.reply(
471+ // call.id,
472+ // // TODO: make a Into trait that can cast to Err(jsonrpc::Error)
473+ // Err(helix_lsp::jsonrpc::Error {
474+ // code: helix_lsp::jsonrpc::ErrorCode::MethodNotFound,
475+ // message: "Method not found".to_string(),
476+ // data: None,
477+ // }),
478+ // );
436 return;
479 return;
437 }
480 }
438 };
481 };
@@ -445,53 +488,9 @@ impl Application {
445 if spinner.is_stopped() {
488 if spinner.is_stopped() {
446 spinner.start();
489 spinner.start();
447 }
490 }
448−
449− let doc = self.editor.documents().find(|doc| {
450− doc.language_server()
451− .map(|server| server.id() == server_id)
452− .unwrap_or_default()
453− });
454− match doc {
455− Some(doc) => {
456− // it's ok to unwrap, we check for the language server before
457− let server = doc.language_server().unwrap();
458− tokio::spawn(server.reply(id, Ok(serde_json::Value::Null)));
491+ tokio::spawn(language_server.reply(id, Ok(serde_json::Value::Null)));
459− }
460− None => {
461− if let Some(server) =
462− self.editor.language_servers.get_by_id(server_id)
463− {
464− log::warn!(
465− "missing document with language server id `{}`",
466− server_id
467− );
468− tokio::spawn(server.reply(
469− id,
470− Err(helix_lsp::jsonrpc::Error {
471− code: helix_lsp::jsonrpc::ErrorCode::InternalError,
472− message: "document missing".to_string(),
473− data: None,
474− }),
475− ));
476− } else {
477− log::warn!(
478− "can't find language server with id `{}`",
479− server_id
480− );
481− }
482− }
483− }
484 }
492 }
485 }
493 }
486− // self.language_server.reply(
487− // call.id,
488− // // TODO: make a Into trait that can cast to Err(jsonrpc::Error)
489− // Err(helix_lsp::jsonrpc::Error {
490− // code: helix_lsp::jsonrpc::ErrorCode::MethodNotFound,
491− // message: "Method not found".to_string(),
492− // data: None,
493− // }),
494− // );
495 }
494 }
496 e => unreachable!("{:?}", e),
495 e => unreachable!("{:?}", e),
497 }
496 }
helix-term/src/job.rs+2−2
@@ -61,7 +61,7 @@ impl Jobs {
61 }
61 }
62
62
63 pub fn handle_callback(
63 pub fn handle_callback(
64− &mut self,
64+ &self,
65 editor: &mut Editor,
65 editor: &mut Editor,
66 compositor: &mut Compositor,
66 compositor: &mut Compositor,
67 call: anyhow::Result<Option<Callback>>,
67 call: anyhow::Result<Option<Callback>>,
@@ -84,7 +84,7 @@ impl Jobs {
84 }
84 }
85 }
85 }
86
86
87− pub fn add(&mut self, j: Job) {
87+ pub fn add(&self, j: Job) {
88 if j.wait {
88 if j.wait {
89 self.wait_futures.push(j.future);
89 self.wait_futures.push(j.future);
90 } else {
90 } else {
helix-view/src/document.rs+33−16
@@ -386,21 +386,24 @@ impl Document {
386 /// If supported, returns the changes that should be applied to this document in order
386 /// If supported, returns the changes that should be applied to this document in order
387 /// to format it nicely.
387 /// to format it nicely.
388 pub fn format(&self) -> Option<impl Future<Output = LspFormatting> + 'static> {
388 pub fn format(&self) -> Option<impl Future<Output = LspFormatting> + 'static> {
389− if let Some(language_server) = self.language_server.clone() {
389+ if let Some(language_server) = self.language_server() {
390 let text = self.text.clone();
390 let text = self.text.clone();
391+ let offset_encoding = language_server.offset_encoding();
392+ let request = language_server.text_document_formatting(
391− let id = self.identifier();
393+ self.identifier(),
394+ lsp::FormattingOptions::default(),
395+ None,
396+ )?;
397+
392 let fut = async move {
398 let fut = async move {
393− let edits = language_server
394− .text_document_formatting(id, lsp::FormattingOptions::default(), None)
395− .await
396− .unwrap_or_else(|e| {
399+ let edits = request.await.unwrap_or_else(|e| {
397− log::warn!("LSP formatting failed: {}", e);
400+ log::warn!("LSP formatting failed: {}", e);
398− Default::default()
401+ Default::default()
399− });
402+ });
400 LspFormatting {
403 LspFormatting {
401 doc: text,
404 doc: text,
402 edits,
405 edits,
403− offset_encoding: language_server.offset_encoding(),
406+ offset_encoding,
404 }
407 }
405 };
408 };
406 Some(fut)
409 Some(fut)
@@ -469,9 +472,14 @@ impl Document {
469 to_writer(&mut file, encoding, &text).await?;
472 to_writer(&mut file, encoding, &text).await?;
470
473
471 if let Some(language_server) = language_server {
474 if let Some(language_server) = language_server {
472− language_server
475+ if language_server.is_initialized() {
476+ return Ok(());
477+ }
478+ if let Some(notification) =
473− .text_document_did_save(identifier, &text)
479+ language_server.text_document_did_save(identifier, &text)
480+ {
474− .await?;
481+ notification.await?;
482+ }
475 }
483 }
476
484
477 Ok(())
485 Ok(())
@@ -646,7 +654,7 @@ impl Document {
646 // }
654 // }
647
655
648 // emit lsp notification
656 // emit lsp notification
649− if let Some(language_server) = &self.language_server {
657+ if let Some(language_server) = self.language_server() {
650 let notify = language_server.text_document_did_change(
658 let notify = language_server.text_document_did_change(
651 self.versioned_identifier(),
659 self.versioned_identifier(),
652 &old_doc,
660 &old_doc,
@@ -795,9 +803,18 @@ impl Document {
795 self.version
803 self.version
796 }
804 }
797
805
798− #[inline]
799 pub fn language_server(&self) -> Option<&helix_lsp::Client> {
806 pub fn language_server(&self) -> Option<&helix_lsp::Client> {
800− self.language_server.as_deref()
807+ let server = self.language_server.as_deref();
808+ let initialized = server
809+ .map(|server| server.is_initialized())
810+ .unwrap_or(false);
811+
812+ // only resolve language_server if it's initialized
813+ if initialized {
814+ server
815+ } else {
816+ None
817+ }
801 }
818 }
802
819
803 #[inline]
820 #[inline]
helix-view/src/editor.rs+15−10
@@ -255,20 +255,21 @@ impl Editor {
255 .and_then(|language| self.language_servers.get(language).ok());
255 .and_then(|language| self.language_servers.get(language).ok());
256
256
257 if let Some(language_server) = language_server {
257 if let Some(language_server) = language_server {
258− doc.set_language_server(Some(language_server.clone()));
259−
260 let language_id = doc
258 let language_id = doc
261 .language()
259 .language()
262 .and_then(|s| s.split('.').last()) // source.rust
260 .and_then(|s| s.split('.').last()) // source.rust
263 .map(ToOwned::to_owned)
261 .map(ToOwned::to_owned)
264 .unwrap_or_default();
262 .unwrap_or_default();
265
263
264+ // TODO: this now races with on_init code if the init happens too quickly
266 tokio::spawn(language_server.text_document_did_open(
265 tokio::spawn(language_server.text_document_did_open(
267 doc.url().unwrap(),
266 doc.url().unwrap(),
268 doc.version(),
267 doc.version(),
269 doc.text(),
268 doc.text(),
270 language_id,
269 language_id,
271 ));
270 ));
271+
272+ doc.set_language_server(Some(language_server));
272 }
273 }
273
274
274 let id = self.documents.insert(doc);
275 let id = self.documents.insert(doc);
@@ -287,14 +288,9 @@ impl Editor {
287
288
288 if close_buffer {
289 if close_buffer {
289 // get around borrowck issues
290 // get around borrowck issues
290− let language_servers = &mut self.language_servers;
291 let doc = &self.documents[view.doc];
291 let doc = &self.documents[view.doc];
292
292
293− let language_server = doc
294− .language
295− .as_ref()
296− .and_then(|language| language_servers.get(language).ok());
297− if let Some(language_server) = language_server {
293+ if let Some(language_server) = doc.language_server() {
298 tokio::spawn(language_server.text_document_did_close(doc.identifier()));
294 tokio::spawn(language_server.text_document_did_close(doc.identifier()));
299 }
295 }
300 self.documents.remove(view.doc);
296 self.documents.remove(view.doc);
@@ -324,20 +320,24 @@ impl Editor {
324 view.ensure_cursor_in_view(doc, self.config.scrolloff)
320 view.ensure_cursor_in_view(doc, self.config.scrolloff)
325 }
321 }
326
322
323+ #[inline]
327 pub fn document(&self, id: DocumentId) -> Option<&Document> {
324 pub fn document(&self, id: DocumentId) -> Option<&Document> {
328 self.documents.get(id)
325 self.documents.get(id)
329 }
326 }
330
327
328+ #[inline]
331 pub fn document_mut(&mut self, id: DocumentId) -> Option<&mut Document> {
329 pub fn document_mut(&mut self, id: DocumentId) -> Option<&mut Document> {
332 self.documents.get_mut(id)
330 self.documents.get_mut(id)
333 }
331 }
334
332
333+ #[inline]
335 pub fn documents(&self) -> impl Iterator<Item = &Document> {
334 pub fn documents(&self) -> impl Iterator<Item = &Document> {
336− self.documents.iter().map(|(_id, doc)| doc)
335+ self.documents.values()
337 }
336 }
338
337
338+ #[inline]
339 pub fn documents_mut(&mut self) -> impl Iterator<Item = &mut Document> {
339 pub fn documents_mut(&mut self) -> impl Iterator<Item = &mut Document> {
340− self.documents.iter_mut().map(|(_id, doc)| doc)
340+ self.documents.values_mut()
341 }
341 }
342
342
343 pub fn document_by_path<P: AsRef<Path>>(&self, path: P) -> Option<&Document> {
343 pub fn document_by_path<P: AsRef<Path>>(&self, path: P) -> Option<&Document> {
@@ -345,6 +345,11 @@ impl Editor {
345 .find(|doc| doc.path().map(|p| p == path.as_ref()).unwrap_or(false))
345 .find(|doc| doc.path().map(|p| p == path.as_ref()).unwrap_or(false))
346 }
346 }
347
347
348+ pub fn document_by_path_mut<P: AsRef<Path>>(&mut self, path: P) -> Option<&mut Document> {
349+ self.documents_mut()
350+ .find(|doc| doc.path().map(|p| p == path.as_ref()).unwrap_or(false))
351+ }
352+
348 pub fn cursor(&self) -> (Option<Position>, CursorKind) {
353 pub fn cursor(&self) -> (Option<Position>, CursorKind) {
349 let view = view!(self);
354 let view = view!(self);
350 let doc = &self.documents[view.doc];
355 let doc = &self.documents[view.doc];
languages.toml+27−1
@@ -116,6 +116,17 @@ roots = []
116language-server = { command = "typescript-language-server", args = ["--stdio"] }
116language-server = { command = "typescript-language-server", args = ["--stdio"] }
117indent = { tab-width = 2, unit = " " }
117indent = { tab-width = 2, unit = " " }
118
118
119+[[language]]
120+name = "tsx"
121+scope = "source.tsx"
122+injection-regex = "^(tsx)$" # |typescript
123+file-types = ["tsx"]
124+roots = []
125+# TODO: highlights-jsx, highlights-params
126+
127+language-server = { command = "typescript-language-server", args = ["--stdio"] }
128+indent = { tab-width = 2, unit = " " }
129+
119[[language]]
130[[language]]
120name = "css"
131name = "css"
121scope = "source.css"
132scope = "source.css"
@@ -204,7 +215,22 @@ injection-regex = "julia"
204file-types = ["jl"]
215file-types = ["jl"]
205roots = []
216roots = []
206comment-token = "#"
217comment-token = "#"
207−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);" ] }
218+language-server = { command = "julia", args = [
219+ "--startup-file=no",
220+ "--history-file=no",
221+ "--quiet",
222+ "-e",
223+ """
224+ using LanguageServer;
225+ using Pkg;
226+ import StaticLint;
227+ env_path = dirname(Pkg.Types.Context().env.project_file);
228+
229+ server = LanguageServer.LanguageServerInstance(stdin, stdout, env_path, "");
230+ server.runlinter = true;
231+ run(server);
232+ """,
233+ ] }
208indent = { tab-width = 2, unit = " " }
234indent = { tab-width = 2, unit = " " }
209
235
210[[language]]
236[[language]]
runtime/queries/c/highlights.scm+1−1
@@ -61,7 +61,7 @@
61
61
62(null) @constant
62(null) @constant
63(number_literal) @number
63(number_literal) @number
64−(char_literal) @number
64+(char_literal) @string
65
65
66(call_expression
66(call_expression
67 function: (identifier) @function)
67 function: (identifier) @function)
runtime/queries/go/highlights.scm+35−4
@@ -17,9 +17,18 @@
17
17
18; Identifiers
18; Identifiers
19
19
20+((identifier) @constant (match? @constant "^[A-Z][A-Z\\d_]+$"))
21+(const_spec
22+ name: (identifier) @constant)
23+
24+(parameter_declaration (identifier) @variable.parameter)
25+(variadic_parameter_declaration (identifier) @variable.parameter)
26+
20(type_identifier) @type
27(type_identifier) @type
21(field_identifier) @property
28(field_identifier) @property
22(identifier) @variable
29(identifier) @variable
30+(package_identifier) @variable
31+
23
32
24; Operators
33; Operators
25
34
@@ -79,10 +88,8 @@
79 "go"
88 "go"
80 "goto"
89 "goto"
81 "if"
90 "if"
82− "import"
83 "interface"
91 "interface"
84 "map"
92 "map"
85− "package"
86 "range"
93 "range"
87 "return"
94 "return"
88 "select"
95 "select"
@@ -92,6 +99,29 @@
92 "var"
99 "var"
93] @keyword
100] @keyword
94
101
102+[
103+ "import"
104+ "package"
105+] @keyword.control.import
106+
107+; Delimiters
108+
109+[
110+ ":"
111+ "."
112+ ","
113+ ";"
114+] @punctuation.delimiter
115+
116+[
117+ "("
118+ ")"
119+ "["
120+ "]"
121+ "{"
122+ "}"
123+] @punctuation.bracket
124+
95; Literals
125; Literals
96
126
97[
127[
@@ -111,7 +141,8 @@
111[
141[
112 (true)
142 (true)
113 (false)
143 (false)
114− (nil)
144+] @constant.builtin.boolean
145+
115−] @constant.builtin
146+(nil) @constant.builtin
116
147
117(comment) @comment
148(comment) @comment
runtime/queries/go/locals.scmnew file+30
@@ -0,0 +1,30 @@
1+; Scopes
2+
3+(block) @local.scope
4+
5+; Definitions
6+
7+(parameter_declaration (identifier) @local.definition)
8+(variadic_parameter_declaration (identifier) @local.definition)
9+
10+(short_var_declaration
11+ left: (expression_list
12+ (identifier) @local.definition))
13+
14+(var_spec
15+ name: (identifier) @local.definition)
16+
17+(for_statement
18+ (range_clause
19+ left: (expression_list
20+ (identifier) @local.definition)))
21+
22+(const_declaration
23+ (const_spec
24+ name: (identifier) @local.definition))
25+
26+; References
27+
28+(identifier) @local.reference
29+(field_identifier) @local.reference
30+
runtime/queries/haskell/highlights.scm+8−6
@@ -2,19 +2,19 @@
2(operator) @operator
2(operator) @operator
3(exp_name (constructor) @constructor)
3(exp_name (constructor) @constructor)
4(constructor_operator) @operator
4(constructor_operator) @operator
5−(module) @module_name
5+(module) @namespace
6(type) @type
6(type) @type
7(type) @class
7(type) @class
8(constructor) @constructor
8(constructor) @constructor
9(pragma) @pragma
9(pragma) @pragma
10(comment) @comment
10(comment) @comment
11(signature name: (variable) @fun_type_name)
11(signature name: (variable) @fun_type_name)
12−(function name: (variable) @fun_name)
12+(function name: (variable) @function)
13(constraint class: (class_name (type)) @class)
13(constraint class: (class_name (type)) @class)
14(class (class_head class: (class_name (type)) @class))
14(class (class_head class: (class_name (type)) @class))
15(instance (instance_head class: (class_name (type)) @class))
15(instance (instance_head class: (class_name (type)) @class))
16−(integer) @literal
16+(integer) @number
17−(exp_literal (float)) @literal
17+(exp_literal (float)) @number
18(char) @literal
18(char) @literal
19(con_unit) @literal
19(con_unit) @literal
20(con_list) @literal
20(con_list) @literal
@@ -39,5 +39,7 @@
39"do" @keyword
39"do" @keyword
40"mdo" @keyword
40"mdo" @keyword
41"rec" @keyword
41"rec" @keyword
42+[
42−"(" @paren
43+ "("
43−")" @paren
44+ ")"
45+] @punctuation.bracket
runtime/queries/javascript/highlights.scm+1−1
@@ -87,7 +87,7 @@
87 (template_string)
87 (template_string)
88] @string
88] @string
89
89
90−(regex) @string.special
90+(regex) @string.regexp
91(number) @number
91(number) @number
92
92
93; Tokens
93; Tokens
runtime/queries/julia/highlights.scm+43−39
@@ -1,9 +1,3 @@
1−(identifier) @variable
2−;; In case you want type highlighting based on Julia naming conventions (this might collide with mathematical notation)
3−;((identifier) @type ; exception: mark `A_foo` sort of identifiers as variables
4− ;(match? @type "^[A-Z][^_]"))
5−((identifier) @constant
6− (match? @constant "^[A-Z][A-Z_]{2}[A-Z_]*$"))
7
1
8[
2[
9 (triple_string)
3 (triple_string)
@@ -28,43 +22,43 @@
28(call_expression
22(call_expression
29 (identifier) @function)
23 (identifier) @function)
30(call_expression
24(call_expression
31− (field_expression (identifier) @method .))
25+ (field_expression (identifier) @function.method .))
32(broadcast_call_expression
26(broadcast_call_expression
33 (identifier) @function)
27 (identifier) @function)
34(broadcast_call_expression
28(broadcast_call_expression
35− (field_expression (identifier) @method .))
29+ (field_expression (identifier) @function.method .))
36(parameter_list
30(parameter_list
37− (identifier) @parameter)
31+ (identifier) @variable.parameter)
38(parameter_list
32(parameter_list
39 (optional_parameter .
33 (optional_parameter .
40− (identifier) @parameter))
34+ (identifier) @variable.parameter))
41(typed_parameter
35(typed_parameter
42− (identifier) @parameter
36+ (identifier) @variable.parameter
43 (identifier) @type)
37 (identifier) @type)
44(type_parameter_list
38(type_parameter_list
45 (identifier) @type)
39 (identifier) @type)
46(typed_parameter
40(typed_parameter
47− (identifier) @parameter
41+ (identifier) @variable.parameter
48 (parameterized_identifier) @type)
42 (parameterized_identifier) @type)
49(function_expression
43(function_expression
50− . (identifier) @parameter)
44+ . (identifier) @variable.parameter)
51−(spread_parameter) @parameter
45+(spread_parameter) @variable.parameter
52(spread_parameter
46(spread_parameter
53− (identifier) @parameter)
47+ (identifier) @variable.parameter)
54(named_argument
48(named_argument
55− . (identifier) @parameter)
49+ . (identifier) @variable.parameter)
56(argument_list
50(argument_list
57 (typed_expression
51 (typed_expression
58− (identifier) @parameter
52+ (identifier) @variable.parameter
59 (identifier) @type))
53 (identifier) @type))
60(argument_list
54(argument_list
61 (typed_expression
55 (typed_expression
62− (identifier) @parameter
56+ (identifier) @variable.parameter
63 (parameterized_identifier) @type))
57 (parameterized_identifier) @type))
64
58
65;; Symbol expressions (:my-wanna-be-lisp-keyword)
59;; Symbol expressions (:my-wanna-be-lisp-keyword)
66(quote_expression
60(quote_expression
67− (identifier)) @symbol
61+ (identifier)) @string.special.symbol
68
62
69;; Parsing error! foo (::Type) get's parsed as two quote expressions
63;; Parsing error! foo (::Type) get's parsed as two quote expressions
70(argument_list
64(argument_list
@@ -76,7 +70,7 @@
76 (identifier) @type)
70 (identifier) @type)
77(parameterized_identifier (_)) @type
71(parameterized_identifier (_)) @type
78(argument_list
72(argument_list
79− (typed_expression . (identifier) @parameter))
73+ (typed_expression . (identifier) @variable.parameter))
80
74
81(typed_expression
75(typed_expression
82 (identifier) @type .)
76 (identifier) @type .)
@@ -113,13 +107,13 @@
113"end" @keyword
107"end" @keyword
114
108
115(if_statement
109(if_statement
116− ["if" "end"] @conditional)
110+ ["if" "end"] @keyword.control.conditional)
117(elseif_clause
111(elseif_clause
118− ["elseif"] @conditional)
112+ ["elseif"] @keyword.control.conditional)
119(else_clause
113(else_clause
120− ["else"] @conditional)
114+ ["else"] @keyword.control.conditional)
121(ternary_expression
115(ternary_expression
122− ["?" ":"] @conditional)
116+ ["?" ":"] @keyword.control.conditional)
123
117
124(function_definition ["function" "end"] @keyword.function)
118(function_definition ["function" "end"] @keyword.function)
125
119
@@ -134,47 +128,57 @@
134 "type"
128 "type"
135] @keyword
129] @keyword
136
130
137−((identifier) @keyword (#any-of? @keyword "global" "local"))
131+((identifier) @keyword (match? @keyword "global|local"))
138
132
139(compound_expression
133(compound_expression
140 ["begin" "end"] @keyword)
134 ["begin" "end"] @keyword)
141(try_statement
135(try_statement
142− ["try" "end" ] @exception)
136+ ["try" "end" ] @keyword.control.exception)
143(finally_clause
137(finally_clause
144− "finally" @exception)
138+ "finally" @keyword.control.exception)
145(catch_clause
139(catch_clause
146− "catch" @exception)
140+ "catch" @keyword.control.exception)
147(quote_statement
141(quote_statement
148 ["quote" "end"] @keyword)
142 ["quote" "end"] @keyword)
149(let_statement
143(let_statement
150 ["let" "end"] @keyword)
144 ["let" "end"] @keyword)
151(for_statement
145(for_statement
152− ["for" "end"] @repeat)
146+ ["for" "end"] @keyword.control.repeat)
153(while_statement
147(while_statement
154− ["while" "end"] @repeat)
148+ ["while" "end"] @keyword.control.repeat)
155−(break_statement) @repeat
149+(break_statement) @keyword.control.repeat
156−(continue_statement) @repeat
150+(continue_statement) @keyword.control.repeat
157(for_binding
151(for_binding
158− "in" @repeat)
152+ "in" @keyword.control.repeat)
159(for_clause
153(for_clause
160− "for" @repeat)
154+ "for" @keyword.control.repeat)
161(do_clause
155(do_clause
162 ["do" "end"] @keyword)
156 ["do" "end"] @keyword)
163
157
164(export_statement
158(export_statement
165− ["export"] @include)
159+ ["export"] @keyword.control.import)
166
160
167[
161[
168 "using"
162 "using"
169 "module"
163 "module"
170 "import"
164 "import"
171−] @include
165+] @keyword.control.import
172
166
173−((identifier) @include (#eq? @include "baremodule"))
167+((identifier) @keyword.control.import (#eq? @keyword.control.import "baremodule"))
174
168
175(((identifier) @constant.builtin) (match? @constant.builtin "^(nothing|Inf|NaN)$"))
169(((identifier) @constant.builtin) (match? @constant.builtin "^(nothing|Inf|NaN)$"))
176−(((identifier) @boolean) (eq? @boolean "true"))
170+(((identifier) @constant.builtin.boolean) (#eq? @constant.builtin.boolean "true"))
177−(((identifier) @boolean) (eq? @boolean "false"))
171+(((identifier) @constant.builtin.boolean) (#eq? @constant.builtin.boolean "false"))
172+
178
173
179["::" ":" "." "," "..." "!"] @punctuation.delimiter
174["::" ":" "." "," "..." "!"] @punctuation.delimiter
180["[" "]" "(" ")" "{" "}"] @punctuation.bracket
175["[" "]" "(" ")" "{" "}"] @punctuation.bracket
176+
177+["="] @operator
178+
179+(identifier) @variable
180+;; In case you want type highlighting based on Julia naming conventions (this might collide with mathematical notation)
181+;((identifier) @type ; exception: mark `A_foo` sort of identifiers as variables
182+ ;(match? @type "^[A-Z][^_]"))
183+((identifier) @constant
184+ (match? @constant "^[A-Z][A-Z_]{2}[A-Z_]*$"))
runtime/queries/latex/highlights.scm+2−2
@@ -259,7 +259,7 @@
259
259
260(comment) @comment
260(comment) @comment
261
261
262−(bracket_group) @parameter
262+(bracket_group) @variable.parameter
263
263
264[(math_operator) "="] @operator
264[(math_operator) "="] @operator
265
265
@@ -312,7 +312,7 @@
312 key: (word) @text.reference)
312 key: (word) @text.reference)
313
313
314(key_val_pair
314(key_val_pair
315− key: (_) @parameter
315+ key: (_) @variable.parameter
316 value: (_))
316 value: (_))
317
317
318["[" "]" "{" "}"] @punctuation.bracket ;"(" ")" is has no special meaning in LaTeX
318["[" "]" "{" "}"] @punctuation.bracket ;"(" ")" is has no special meaning in LaTeX
runtime/queries/lua/highlights.scm+9−9
@@ -23,27 +23,27 @@
23 "for"
23 "for"
24 "do"
24 "do"
25 "end"
25 "end"
26−] @keyword.control.loop)
26+] @keyword.control.repeat)
27
27
28(for_in_statement
28(for_in_statement
29[
29[
30 "for"
30 "for"
31 "do"
31 "do"
32 "end"
32 "end"
33−] @keyword.control.loop)
33+] @keyword.control.repeat)
34
34
35(while_statement
35(while_statement
36[
36[
37 "while"
37 "while"
38 "do"
38 "do"
39 "end"
39 "end"
40−] @keyword.control.loop)
40+] @keyword.control.repeat)
41
41
42(repeat_statement
42(repeat_statement
43[
43[
44 "repeat"
44 "repeat"
45 "until"
45 "until"
46−] @keyword.control.loop)
46+] @keyword.control.repeat)
47
47
48(do_statement
48(do_statement
49[
49[
@@ -65,7 +65,7 @@
65 "not"
65 "not"
66 "and"
66 "and"
67 "or"
67 "or"
68−] @keyword.operator
68+] @operator
69
69
70[
70[
71"="
71"="
@@ -108,7 +108,7 @@
108[
108[
109(false)
109(false)
110(true)
110(true)
111−] @boolean
111+] @constant.builtin.boolean
112(nil) @constant.builtin
112(nil) @constant.builtin
113(spread) @constant ;; "..."
113(spread) @constant ;; "..."
114((identifier) @constant
114((identifier) @constant
@@ -116,7 +116,7 @@
116
116
117;; Parameters
117;; Parameters
118(parameters
118(parameters
119− (identifier) @parameter)
119+ (identifier) @variable.parameter)
120
120
121; ;; Functions
121; ;; Functions
122(function [(function_name) (identifier)] @function)
122(function [(function_name) (identifier)] @function)
@@ -139,8 +139,8 @@
139
139
140(function_call
140(function_call
141 [
141 [
142− ((identifier) @variable (method) @method)
142+ ((identifier) @variable (method) @function.method)
143− ((_) (method) @method)
143+ ((_) (method) @function.method)
144 (identifier) @function
144 (identifier) @function
145 (field_expression (property_identifier) @function)
145 (field_expression (property_identifier) @function)
146 ]
146 ]
runtime/queries/ocaml/highlights.scm+4−4
@@ -25,12 +25,12 @@
25
25
26(external (value_name) @function)
26(external (value_name) @function)
27
27
28−(method_name) @method
28+(method_name) @function.method
29
29
30; Variables
30; Variables
31;----------
31;----------
32
32
33−(value_pattern) @parameter
33+(value_pattern) @variable.parameter
34
34
35; Application
35; Application
36;------------
36;------------
@@ -60,7 +60,7 @@
60
60
61[(number) (signed_number)] @number
61[(number) (signed_number)] @number
62
62
63−(character) @character
63+(character) @constant.character
64
64
65(string) @string
65(string) @string
66
66
@@ -92,7 +92,7 @@
92
92
93["include" "open"] @include
93["include" "open"] @include
94
94
95−["for" "to" "downto" "while" "do" "done"] @keyword.control.loop
95+["for" "to" "downto" "while" "do" "done"] @keyword.control.repeat
96
96
97; Macros
97; Macros
98;-------
98;-------
runtime/queries/ruby/highlights.scm+1−1
@@ -100,7 +100,7 @@
100 (bare_symbol)
100 (bare_symbol)
101] @string.special.symbol
101] @string.special.symbol
102
102
103−(regex) @string.special.regex
103+(regex) @string.regexp
104(escape_sequence) @escape
104(escape_sequence) @escape
105
105
106[
106[
runtime/queries/rust/highlights.scm+2−2
@@ -17,7 +17,7 @@
17
17
18(escape_sequence) @escape
18(escape_sequence) @escape
19(primitive_type) @type.builtin
19(primitive_type) @type.builtin
20−(boolean_literal) @constant.builtin
20+(boolean_literal) @constant.builtin.boolean
21[
21[
22 (integer_literal)
22 (integer_literal)
23 (float_literal)
23 (float_literal)
@@ -149,7 +149,7 @@
149
149
150(mutable_specifier) @keyword.mut
150(mutable_specifier) @keyword.mut
151
151
152−
152+; TODO: variable.mut to highlight mutable identifiers via locals.scm
153
153
154; -------
154; -------
155; Guess Other Types
155; Guess Other Types
runtime/queries/rust/locals.scmnew file+17
@@ -0,0 +1,17 @@
1+; Scopes
2+
3+(block) @local.scope
4+
5+; Definitions
6+
7+(parameter
8+ (identifier) @local.definition)
9+
10+(let_declaration
11+ pattern: (identifier) @local.definition)
12+
13+(closure_parameters (identifier)) @local.definition
14+
15+; References
16+(identifier) @local.reference
17+
runtime/queries/tsx/highlights.scmnew file+1
@@ -0,0 +1 @@
1+; inherits: typescript
runtime/queries/yaml/highlights.scm+1−1
@@ -1,6 +1,6 @@
1(block_mapping_pair key: (_) @property)
1(block_mapping_pair key: (_) @property)
2(flow_mapping (_ key: (_) @property))
2(flow_mapping (_ key: (_) @property))
3−(boolean_scalar) @boolean
3+(boolean_scalar) @constant.builtin.boolean
4(null_scalar) @constant.builtin
4(null_scalar) @constant.builtin
5(double_quote_scalar) @string
5(double_quote_scalar) @string
6(single_quote_scalar) @string
6(single_quote_scalar) @string
runtime/themes/dark_plus.toml+1
@@ -34,6 +34,7 @@
34"comment" = { fg = "#6A9955" }
34"comment" = { fg = "#6A9955" }
35
35
36"string" = { fg = "#ce9178" }
36"string" = { fg = "#ce9178" }
37+"string.regexp" = { fg = "regex" }
37"number" = { fg = "#b5cea8" }
38"number" = { fg = "#b5cea8" }
38"escape" = { fg = "#d7ba7d" }
39"escape" = { fg = "#d7ba7d" }
39
40
runtime/themes/monokai.toml+1
@@ -34,6 +34,7 @@
34"comment" = { fg = "#88846F" }
34"comment" = { fg = "#88846F" }
35
35
36"string" = { fg = "#e6db74" }
36"string" = { fg = "#e6db74" }
37+"string.regexp" = { fg = "regex" }
37"number" = { fg = "#ae81ff" }
38"number" = { fg = "#ae81ff" }
38"escape" = { fg = "#ae81ff" }
39"escape" = { fg = "#ae81ff" }
39
40
theme.toml+2−4
@@ -9,7 +9,7 @@ special = "honey"
9property = "white"
9property = "white"
10variable = "lavender"
10variable = "lavender"
11# variable = "almond" # TODO: metavariables only
11# variable = "almond" # TODO: metavariables only
12−"variable.parameter" = "lavender"
12+"variable.parameter" = { fg = "lavender", modifiers = ["underlined"] }
13"variable.builtin" = "mint"
13"variable.builtin" = "mint"
14type = "white"
14type = "white"
15"type.builtin" = "white" # TODO: distinguish?
15"type.builtin" = "white" # TODO: distinguish?
@@ -28,9 +28,7 @@ escape = "honey"
28label = "honey"
28label = "honey"
29
29
30# TODO: diferentiate doc comment
30# TODO: diferentiate doc comment
31−# concat (ERROR) @syntax-error and "MISSING ;" selectors for errors
31+# concat (ERROR) @error.syntax and "MISSING ;" selectors for errors
32−
33−module = "#ff0000"
34
32
35"ui.background" = { bg = "midnight" }
33"ui.background" = { bg = "midnight" }
36"ui.linenr" = { fg = "comet" }
34"ui.linenr" = { fg = "comet" }