on master help sign in
Merge remote-tracking branch 'origin/master' into line_ending_detection
cdd9347Jan Hrastnik
Cargo.lock+3−2
@@ -342,6 +342,7 @@ version = "0.2.0"
342dependencies = [
342dependencies = [
343 "anyhow",
343 "anyhow",
344 "crossterm",
344 "crossterm",
345+ "futures-util",
345 "helix-core",
346 "helix-core",
346 "helix-lsp",
347 "helix-lsp",
347 "helix-tui",
348 "helix-tui",
@@ -672,9 +673,9 @@ checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
672
673
673[[package]]
674[[package]]
674name = "ropey"
675name = "ropey"
675−version = "1.2.0"
676+version = "1.3.0"
676source = "registry+https://github.com/rust-lang/crates.io-index"
677source = "registry+https://github.com/rust-lang/crates.io-index"
677−checksum = "f0f3ef16589fdbb3e8fbce3dca944c08e61f39c7f16064b21a257d68ea911a83"
678+checksum = "e4b1996ad82fb6b8ac0711329432d80aa1ad5d70a0fe6edd1a9cf6fd2375fbb2"
678dependencies = [
679dependencies = [
679 "smallvec",
680 "smallvec",
680]
681]
book/src/SUMMARY.md+1
@@ -4,4 +4,5 @@
4- [Usage](./usage.md)
4- [Usage](./usage.md)
5- [Configuration](./configuration.md)
5- [Configuration](./configuration.md)
6 - [Keymap](./keymap.md)
6 - [Keymap](./keymap.md)
7+ - [Key Remapping](./remapping.md)
7 - [Hooks](./hooks.md)
8 - [Hooks](./hooks.md)
book/src/configuration.md+11−3
@@ -1,5 +1,12 @@
1# Configuration
1# Configuration
2
2
3+## LSP
4+
5+To disable language server progress report from being displayed in the status bar add this option to your `config.toml`:
6+```toml
7+lsp-progress = false
8+```
9+
3## Theme
10## Theme
4
11
5Use a custom theme by placing a theme.toml in your config directory (i.e ~/.config/helix/theme.toml). The default theme.toml can be found [here](https://github.com/helix-editor/helix/blob/master/theme.toml), and user submitted themes [here](https://github.com/helix-editor/helix/blob/master/contrib/themes).
12Use a custom theme by placing a theme.toml in your config directory (i.e ~/.config/helix/theme.toml). The default theme.toml can be found [here](https://github.com/helix-editor/helix/blob/master/theme.toml), and user submitted themes [here](https://github.com/helix-editor/helix/blob/master/contrib/themes).
@@ -32,11 +39,11 @@ Possible modifiers:
32| `dim` |
39| `dim` |
33| `italic` |
40| `italic` |
34| `underlined` |
41| `underlined` |
35−| `slow\_blink` |
42+| `slow_blink` |
36−| `rapid\_blink` |
43+| `rapid_blink` |
37| `reversed` |
44| `reversed` |
38| `hidden` |
45| `hidden` |
39−| `crossed\_out` |
46+| `crossed_out` |
40
47
41Possible keys:
48Possible keys:
42
49
@@ -87,3 +94,4 @@ Possible keys:
87These 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.
94These 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.
88
95
89For 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.
96For 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.
97+
book/src/install.md+5−2
@@ -4,9 +4,12 @@ We provide pre-built binaries on the [GitHub Releases page](https://github.com/h
4
4
5## OSX
5## OSX
6
6
7−TODO: brew tap
7+A Homebrew tap is available:
8
8
9−Please use a pre-built binary release for the time being.
9+```
10+brew tap helix-editor/helix
11+brew install helix
12+```
10
13
11## Linux
14## Linux
12
15
book/src/remapping.mdnew file+48
@@ -0,0 +1,48 @@
1+# Key Remapping
2+
3+One-way key remapping is temporarily supported via a simple TOML configuration
4+file. (More powerful solutions such as rebinding via commands will be
5+available in the feature).
6+
7+To remap keys, write a `config.toml` file in your `helix` configuration
8+directory (default `~/.config/helix` in Linux systems) with a structure like
9+this:
10+
11+```toml
12+# At most one section each of 'keys.normal', 'keys.insert' and 'keys.select'
13+[keys.normal]
14+a = "move_char_left" # Maps the 'a' key to the move_char_left command
15+w = "move_line_up" # Maps the 'w' key move_line_up
16+C-S-esc = "select_line" # Maps Control-Shift-Escape to select_line
17+
18+[keys.insert]
19+A-x = "normal_mode" # Maps Alt-X to enter normal mode
20+```
21+
22+Control, Shift and Alt modifiers are encoded respectively with the prefixes
23+`C-`, `S-` and `A-`. Special keys are encoded as follows:
24+
25+* Backspace => "backspace"
26+* Space => "space"
27+* Return/Enter => "ret"
28+* < => "lt"
29+* \> => "gt"
30+* \+ => "plus"
31+* \- => "minus"
32+* ; => "semicolon"
33+* % => "percent"
34+* Left => "left"
35+* Right => "right"
36+* Up => "up"
37+* Home => "home"
38+* End => "end"
39+* Page Up => "pageup"
40+* Page Down => "pagedown"
41+* Tab => "tab"
42+* Back Tab => "backtab"
43+* Delete => "del"
44+* Insert => "ins"
45+* Null => "null"
46+* Escape => "esc"
47+
48+Commands can be found in the source code at `../../helix-term/src/commands.rs`
contrib/themes/bogster.toml+3−2
@@ -31,7 +31,8 @@
31"ui.background" = { bg = "#161c23" }
31"ui.background" = { bg = "#161c23" }
32"ui.linenr" = { fg = "#415367" }
32"ui.linenr" = { fg = "#415367" }
33"ui.linenr.selected" = { fg = "#e5ded6" } # TODO
33"ui.linenr.selected" = { fg = "#e5ded6" } # TODO
34−"ui.statusline" = { bg = "#232d38" }
34+"ui.statusline" = { fg = "#e5ded6", bg = "#232d38" }
35+"ui.statusline.inactive" = { fg = "#c6b8ad", bg = "#232d38" }
35"ui.popup" = { bg = "#232d38" }
36"ui.popup" = { bg = "#232d38" }
36"ui.window" = { bg = "#232d38" }
37"ui.window" = { bg = "#232d38" }
37"ui.help" = { bg = "#232d38", fg = "#e5ded6" }
38"ui.help" = { bg = "#232d38", fg = "#e5ded6" }
@@ -39,7 +40,7 @@
39"ui.text" = { fg = "#e5ded6" }
40"ui.text" = { fg = "#e5ded6" }
40"ui.text.focus" = { fg = "#e5ded6", modifiers= ["bold"] }
41"ui.text.focus" = { fg = "#e5ded6", modifiers= ["bold"] }
41
42
42−"ui.selection" = { bg = "#540099" }
43+"ui.selection" = { bg = "#313f4e" }
43"ui.menu.selected" = { fg = "#e5ded6", bg = "#313f4e" }
44"ui.menu.selected" = { fg = "#e5ded6", bg = "#313f4e" }
44
45
45"warning" = "#dc7759"
46"warning" = "#dc7759"
contrib/themes/ingrid.toml+2−1
@@ -31,7 +31,8 @@
31"ui.background" = { bg = "#FFFCFD" }
31"ui.background" = { bg = "#FFFCFD" }
32"ui.linenr" = { fg = "#bbbbbb" }
32"ui.linenr" = { fg = "#bbbbbb" }
33"ui.linenr.selected" = { fg = "#F3EAE9" } # TODO
33"ui.linenr.selected" = { fg = "#F3EAE9" } # TODO
34−"ui.statusline" = { bg = "#F3EAE9" }
34+"ui.statusline" = { fg = "#250E07", bg = "#F3EAE9" }
35+"ui.statusline.inactive" = { fg = "#7b91b3", bg = "#F3EAE9" }
35"ui.popup" = { bg = "#F3EAE9" }
36"ui.popup" = { bg = "#F3EAE9" }
36"ui.window" = { bg = "#D8B8B3" }
37"ui.window" = { bg = "#D8B8B3" }
37"ui.help" = { bg = "#D8B8B3", fg = "#250E07" }
38"ui.help" = { bg = "#D8B8B3", fg = "#250E07" }
contrib/themes/onedark.toml+1
@@ -36,6 +36,7 @@
36"ui.linenr.selected" = { fg = "#ABB2BF" }
36"ui.linenr.selected" = { fg = "#ABB2BF" }
37"ui.popup" = { bg = "#3E4452" }
37"ui.popup" = { bg = "#3E4452" }
38"ui.statusline" = { fg = "#ABB2BF", bg = "#2C323C" }
38"ui.statusline" = { fg = "#ABB2BF", bg = "#2C323C" }
39+"ui.statusline.inactive" = { fg = "#ABB2Bf", bg = "#2C323C" }
39"ui.selection" = { bg = "#3E4452" }
40"ui.selection" = { bg = "#3E4452" }
40"ui.text" = { fg = "#ABB2BF", bg = "#282C34" }
41"ui.text" = { fg = "#ABB2BF", bg = "#282C34" }
41"ui.text.focus" = { fg = "#ABB2BF", bg = "#2C323C", modifiers = ['bold'] }
42"ui.text.focus" = { fg = "#ABB2BF", bg = "#2C323C", modifiers = ['bold'] }
flake.lock+13−13
@@ -34,11 +34,11 @@
34 "helix": {
34 "helix": {
35 "flake": false,
35 "flake": false,
36 "locked": {
36 "locked": {
37− "lastModified": 1623545930,
37+ "lastModified": 1624077450,
38− "narHash": "sha256-14ASoYbxXHU/qPGctiUymb4fMRCoih9c7YujjxqEkdU=",
38+ "narHash": "sha256-gcL519tetuEv0N+oBP8X3U4bPjwcAgzbPdgwW19qeVI=",
39 "ref": "master",
39 "ref": "master",
40− "rev": "9640ed1425f2db904fb42cd0c54dc6fbc05ca292",
40+ "rev": "1c2585202145467f0fde7ad9c571e462081c3656",
41− "revCount": 821,
41+ "revCount": 894,
42 "submodules": true,
42 "submodules": true,
43 "type": "git",
43 "type": "git",
44 "url": "https://github.com/helix-editor/helix.git"
44 "url": "https://github.com/helix-editor/helix.git"
@@ -58,11 +58,11 @@
58 "rustOverlay": "rustOverlay"
58 "rustOverlay": "rustOverlay"
59 },
59 },
60 "locked": {
60 "locked": {
61− "lastModified": 1623591988,
61+ "lastModified": 1624070370,
62− "narHash": "sha256-a8E5LYKxYjHmBWZsFxKnCBVGsWHFEWrKjeAJkplWrfI=",
62+ "narHash": "sha256-sfFqfmerCYvk0jDeP1gfuskz7AaqDsgV8aiQrEUGdsc=",
63 "owner": "yusdacra",
63 "owner": "yusdacra",
64 "repo": "nix-cargo-integration",
64 "repo": "nix-cargo-integration",
65− "rev": "8254b71eddd4e85173eddc189174b873fad85360",
65+ "rev": "85e6c1ba4c0e3e6dec5a7d1f65bcc036d2ea6ae3",
66 "type": "github"
66 "type": "github"
67 },
67 },
68 "original": {
68 "original": {
@@ -73,11 +73,11 @@
73 },
73 },
74 "nixpkgs": {
74 "nixpkgs": {
75 "locked": {
75 "locked": {
76− "lastModified": 1623324058,
76+ "lastModified": 1623580589,
77− "narHash": "sha256-Jm9GUTXdjXz56gWDKy++EpFfjrBaxqXlLvTLfgEi8lo=",
77+ "narHash": "sha256-Ayp1cjXpwFCkAiWUE46rj9APTltsiEBdIs2+cj+U7+c=",
78 "owner": "nixos",
78 "owner": "nixos",
79 "repo": "nixpkgs",
79 "repo": "nixpkgs",
80− "rev": "432fc2d9a67f92e05438dff5fdc2b39d33f77997",
80+ "rev": "fa0326ce5233f7d592271df52c9d0812bec47b84",
81 "type": "github"
81 "type": "github"
82 },
82 },
83 "original": {
83 "original": {
@@ -98,11 +98,11 @@
98 "rustOverlay": {
98 "rustOverlay": {
99 "flake": false,
99 "flake": false,
100 "locked": {
100 "locked": {
101− "lastModified": 1623550815,
101+ "lastModified": 1624069337,
102− "narHash": "sha256-RumRrkE6OTJDndHV4qZNZv8kUGnzwRHZQSyzx29r6/g=",
102+ "narHash": "sha256-9mTcx7osE4biF2Hm/GU19s1T3+KvphWj4QaUcJh39lU=",
103 "owner": "oxalica",
103 "owner": "oxalica",
104 "repo": "rust-overlay",
104 "repo": "rust-overlay",
105− "rev": "9824f142cbd7bc3e2a92eefbb79addfff8704cd3",
105+ "rev": "67dc2a9543a7c24591e6cb102ad0121c3a704aab",
106 "type": "github"
106 "type": "github"
107 },
107 },
108 "original": {
108 "original": {
helix-core/Cargo.toml+7−5
@@ -4,17 +4,19 @@ version = "0.2.0"
4authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
4authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5edition = "2018"
5edition = "2018"
6license = "MPL-2.0"
6license = "MPL-2.0"
7−
7+description = "Helix editor core editing primitives"
8−# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+categories = ["editor"]
9−
9+repository = "https://github.com/helix-editor/helix"
10+homepage = "https://helix-editor.com"
11+include = ["src/**/*", "README.md"]
10
12
11[features]
13[features]
12embed_runtime = ["rust-embed"]
14embed_runtime = ["rust-embed"]
13
15
14[dependencies]
16[dependencies]
15−helix-syntax = { path = "../helix-syntax" }
17+helix-syntax = { version = "0.2", path = "../helix-syntax" }
16
18
17−ropey = "1.2"
19+ropey = "1.3"
18smallvec = "1.4"
20smallvec = "1.4"
19tendril = "0.4.2"
21tendril = "0.4.2"
20unicode-segmentation = "1.7.1"
22unicode-segmentation = "1.7.1"
helix-core/src/lib.rs+2
@@ -90,6 +90,8 @@ pub fn cache_dir() -> std::path::PathBuf {
90 path
90 path
91}
91}
92
92
93+pub use etcetera::home_dir;
94+
93use etcetera::base_strategy::{choose_base_strategy, BaseStrategy};
95use etcetera::base_strategy::{choose_base_strategy, BaseStrategy};
94
96
95pub use ropey::{Rope, RopeSlice};
97pub use ropey::{Rope, RopeSlice};
helix-lsp/Cargo.toml+6−2
@@ -4,11 +4,15 @@ version = "0.2.0"
4authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
4authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5edition = "2018"
5edition = "2018"
6license = "MPL-2.0"
6license = "MPL-2.0"
7+description = "LSP client implementation for Helix project"
8+categories = ["editor"]
9+repository = "https://github.com/helix-editor/helix"
10+homepage = "https://helix-editor.com"
7
11
8# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
12# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
9
13
10[dependencies]
14[dependencies]
11−helix-core = { path = "../helix-core" }
15+helix-core = { version = "0.2", path = "../helix-core" }
12
16
13anyhow = "1.0"
17anyhow = "1.0"
14futures-executor = "0.3"
18futures-executor = "0.3"
@@ -20,4 +24,4 @@ serde = { version = "1.0", features = ["derive"] }
20serde_json = "1.0"
24serde_json = "1.0"
21thiserror = "1.0"
25thiserror = "1.0"
22tokio = { version = "1.6", features = ["full"] }
26tokio = { version = "1.6", features = ["full"] }
23−tokio-stream = "0.1.6"
27+tokio-stream = "0.1.6"
helix-lsp/src/client.rs+78−46
@@ -18,6 +18,7 @@ use tokio::{
18
18
19#[derive(Debug)]
19#[derive(Debug)]
20pub struct Client {
20pub struct Client {
21+ id: usize,
21 _process: Child,
22 _process: Child,
22 server_tx: UnboundedSender<Payload>,
23 server_tx: UnboundedSender<Payload>,
23 request_counter: AtomicU64,
24 request_counter: AtomicU64,
@@ -26,7 +27,11 @@ pub struct Client {
26}
27}
27
28
28impl Client {
29impl Client {
30+ pub fn start(
31+ cmd: &str,
32+ args: &[String],
33+ id: usize,
29− pub fn start(cmd: &str, args: &[String]) -> Result<(Self, UnboundedReceiver<Call>)> {
34+ ) -> Result<(Self, UnboundedReceiver<(usize, Call)>)> {
30 let process = Command::new(cmd)
35 let process = Command::new(cmd)
31 .args(args)
36 .args(args)
32 .stdin(Stdio::piped())
37 .stdin(Stdio::piped())
@@ -43,9 +48,10 @@ impl Client {
43 let reader = BufReader::new(process.stdout.take().expect("Failed to open stdout"));
48 let reader = BufReader::new(process.stdout.take().expect("Failed to open stdout"));
44 let stderr = BufReader::new(process.stderr.take().expect("Failed to open stderr"));
49 let stderr = BufReader::new(process.stderr.take().expect("Failed to open stderr"));
45
50
46− let (server_rx, server_tx) = Transport::start(reader, writer, stderr);
51+ let (server_rx, server_tx) = Transport::start(reader, writer, stderr, id);
47
52
48 let client = Self {
53 let client = Self {
54+ id,
49 _process: process,
55 _process: process,
50 server_tx,
56 server_tx,
51 request_counter: AtomicU64::new(0),
57 request_counter: AtomicU64::new(0),
@@ -59,6 +65,10 @@ impl Client {
59 Ok((client, server_rx))
65 Ok((client, server_rx))
60 }
66 }
61
67
68+ pub fn id(&self) -> usize {
69+ self.id
70+ }
71+
62 fn next_request_id(&self) -> jsonrpc::Id {
72 fn next_request_id(&self) -> jsonrpc::Id {
63 let id = self.request_counter.fetch_add(1, Ordering::Relaxed);
73 let id = self.request_counter.fetch_add(1, Ordering::Relaxed);
64 jsonrpc::Id::Num(id)
74 jsonrpc::Id::Num(id)
@@ -165,31 +175,35 @@ impl Client {
165 }
175 }
166
176
167 /// Reply to a language server RPC call.
177 /// Reply to a language server RPC call.
168− pub async fn reply(
178+ pub fn reply(
169 &self,
179 &self,
170 id: jsonrpc::Id,
180 id: jsonrpc::Id,
171 result: core::result::Result<Value, jsonrpc::Error>,
181 result: core::result::Result<Value, jsonrpc::Error>,
172− ) -> Result<()> {
182+ ) -> impl Future<Output = Result<()>> {
173 use jsonrpc::{Failure, Output, Success, Version};
183 use jsonrpc::{Failure, Output, Success, Version};
174
184
175− let output = match result {
185+ let server_tx = self.server_tx.clone();
176− Ok(result) => Output::Success(Success {
177− jsonrpc: Some(Version::V2),
178− id,
179− result,
180− }),
181− Err(error) => Output::Failure(Failure {
182− jsonrpc: Some(Version::V2),
183− id,
184− error,
185− }),
186− };
187
186
188− self.server_tx
187+ async move {
189− .send(Payload::Response(output))
188+ let output = match result {
190− .map_err(|e| Error::Other(e.into()))?;
189+ Ok(result) => Output::Success(Success {
190+ jsonrpc: Some(Version::V2),
191+ id,
192+ result,
193+ }),
194+ Err(error) => Output::Failure(Failure {
195+ jsonrpc: Some(Version::V2),
196+ id,
197+ error,
198+ }),
199+ };
191
200
201+ server_tx
202+ .send(Payload::Response(output))
203+ .map_err(|e| Error::Other(e.into()))?;
204+
192− Ok(())
205+ Ok(())
206+ }
193 }
207 }
194
208
195 // -------------------------------------------------------------------------------------------
209 // -------------------------------------------------------------------------------------------
@@ -229,8 +243,7 @@ impl Client {
229 ..Default::default()
243 ..Default::default()
230 }),
244 }),
231 window: Some(lsp::WindowClientCapabilities {
245 window: Some(lsp::WindowClientCapabilities {
232− // TODO: temporarily disabled until we implement handling for window/workDoneProgress/create
233− // work_done_progress: Some(true),
246+ work_done_progress: Some(true),
234 ..Default::default()
247 ..Default::default()
235 }),
248 }),
236 ..Default::default()
249 ..Default::default()
@@ -259,6 +272,21 @@ impl Client {
259 self.notify::<lsp::notification::Exit>(())
272 self.notify::<lsp::notification::Exit>(())
260 }
273 }
261
274
275+ /// Tries to shut down the language server but returns
276+ /// early if server responds with an error.
277+ pub async fn shutdown_and_exit(&self) -> Result<()> {
278+ self.shutdown().await?;
279+ self.exit().await
280+ }
281+
282+ /// Forcefully shuts down the language server ignoring any errors.
283+ pub async fn force_shutdown(&self) -> Result<()> {
284+ if let Err(e) = self.shutdown().await {
285+ log::warn!("language server failed to terminate gracefully - {}", e);
286+ }
287+ self.exit().await
288+ }
289+
262 // -------------------------------------------------------------------------------------------
290 // -------------------------------------------------------------------------------------------
263 // Text document
291 // Text document
264 // -------------------------------------------------------------------------------------------
292 // -------------------------------------------------------------------------------------------
@@ -465,6 +493,7 @@ impl Client {
465 &self,
493 &self,
466 text_document: lsp::TextDocumentIdentifier,
494 text_document: lsp::TextDocumentIdentifier,
467 position: lsp::Position,
495 position: lsp::Position,
496+ work_done_token: Option<lsp::ProgressToken>,
468 ) -> impl Future<Output = Result<Value>> {
497 ) -> impl Future<Output = Result<Value>> {
469 // ) -> Result<Vec<lsp::CompletionItem>> {
498 // ) -> Result<Vec<lsp::CompletionItem>> {
470 let params = lsp::CompletionParams {
499 let params = lsp::CompletionParams {
@@ -473,9 +502,7 @@ impl Client {
473 position,
502 position,
474 },
503 },
475 // TODO: support these tokens by async receiving and updating the choice list
504 // TODO: support these tokens by async receiving and updating the choice list
476− work_done_progress_params: lsp::WorkDoneProgressParams {
505+ work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
477− work_done_token: None,
478− },
479 partial_result_params: lsp::PartialResultParams {
506 partial_result_params: lsp::PartialResultParams {
480 partial_result_token: None,
507 partial_result_token: None,
481 },
508 },
@@ -490,15 +517,14 @@ impl Client {
490 &self,
517 &self,
491 text_document: lsp::TextDocumentIdentifier,
518 text_document: lsp::TextDocumentIdentifier,
492 position: lsp::Position,
519 position: lsp::Position,
520+ work_done_token: Option<lsp::ProgressToken>,
493 ) -> impl Future<Output = Result<Value>> {
521 ) -> impl Future<Output = Result<Value>> {
494 let params = lsp::SignatureHelpParams {
522 let params = lsp::SignatureHelpParams {
495 text_document_position_params: lsp::TextDocumentPositionParams {
523 text_document_position_params: lsp::TextDocumentPositionParams {
496 text_document,
524 text_document,
497 position,
525 position,
498 },
526 },
499− work_done_progress_params: lsp::WorkDoneProgressParams {
527+ work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
500− work_done_token: None,
501− },
502 context: None,
528 context: None,
503 // lsp::SignatureHelpContext
529 // lsp::SignatureHelpContext
504 };
530 };
@@ -510,15 +536,14 @@ impl Client {
510 &self,
536 &self,
511 text_document: lsp::TextDocumentIdentifier,
537 text_document: lsp::TextDocumentIdentifier,
512 position: lsp::Position,
538 position: lsp::Position,
539+ work_done_token: Option<lsp::ProgressToken>,
513 ) -> impl Future<Output = Result<Value>> {
540 ) -> impl Future<Output = Result<Value>> {
514 let params = lsp::HoverParams {
541 let params = lsp::HoverParams {
515 text_document_position_params: lsp::TextDocumentPositionParams {
542 text_document_position_params: lsp::TextDocumentPositionParams {
516 text_document,
543 text_document,
517 position,
544 position,
518 },
545 },
519− work_done_progress_params: lsp::WorkDoneProgressParams {
546+ work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
520− work_done_token: None,
521− },
522 // lsp::SignatureHelpContext
547 // lsp::SignatureHelpContext
523 };
548 };
524
549
@@ -531,6 +556,7 @@ impl Client {
531 &self,
556 &self,
532 text_document: lsp::TextDocumentIdentifier,
557 text_document: lsp::TextDocumentIdentifier,
533 options: lsp::FormattingOptions,
558 options: lsp::FormattingOptions,
559+ work_done_token: Option<lsp::ProgressToken>,
534 ) -> anyhow::Result<Vec<lsp::TextEdit>> {
560 ) -> anyhow::Result<Vec<lsp::TextEdit>> {
535 let capabilities = self.capabilities.as_ref().unwrap();
561 let capabilities = self.capabilities.as_ref().unwrap();
536
562
@@ -545,9 +571,7 @@ impl Client {
545 let params = lsp::DocumentFormattingParams {
571 let params = lsp::DocumentFormattingParams {
546 text_document,
572 text_document,
547 options,
573 options,
548− work_done_progress_params: lsp::WorkDoneProgressParams {
574+ work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
549− work_done_token: None,
550− },
551 };
575 };
552
576
553 let response = self.request::<lsp::request::Formatting>(params).await?;
577 let response = self.request::<lsp::request::Formatting>(params).await?;
@@ -560,6 +584,7 @@ impl Client {
560 text_document: lsp::TextDocumentIdentifier,
584 text_document: lsp::TextDocumentIdentifier,
561 range: lsp::Range,
585 range: lsp::Range,
562 options: lsp::FormattingOptions,
586 options: lsp::FormattingOptions,
587+ work_done_token: Option<lsp::ProgressToken>,
563 ) -> anyhow::Result<Vec<lsp::TextEdit>> {
588 ) -> anyhow::Result<Vec<lsp::TextEdit>> {
564 let capabilities = self.capabilities.as_ref().unwrap();
589 let capabilities = self.capabilities.as_ref().unwrap();
565
590
@@ -575,9 +600,7 @@ impl Client {
575 text_document,
600 text_document,
576 range,
601 range,
577 options,
602 options,
578− work_done_progress_params: lsp::WorkDoneProgressParams {
603+ work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
579− work_done_token: None,
580− },
581 };
604 };
582
605
583 let response = self
606 let response = self
@@ -596,15 +619,14 @@ impl Client {
596 &self,
619 &self,
597 text_document: lsp::TextDocumentIdentifier,
620 text_document: lsp::TextDocumentIdentifier,
598 position: lsp::Position,
621 position: lsp::Position,
622+ work_done_token: Option<lsp::ProgressToken>,
599 ) -> impl Future<Output = Result<Value>> {
623 ) -> impl Future<Output = Result<Value>> {
600 let params = lsp::GotoDefinitionParams {
624 let params = lsp::GotoDefinitionParams {
601 text_document_position_params: lsp::TextDocumentPositionParams {
625 text_document_position_params: lsp::TextDocumentPositionParams {
602 text_document,
626 text_document,
603 position,
627 position,
604 },
628 },
605− work_done_progress_params: lsp::WorkDoneProgressParams {
629+ work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
606− work_done_token: None,
607− },
608 partial_result_params: lsp::PartialResultParams {
630 partial_result_params: lsp::PartialResultParams {
609 partial_result_token: None,
631 partial_result_token: None,
610 },
632 },
@@ -617,30 +639,42 @@ impl Client {
617 &self,
639 &self,
618 text_document: lsp::TextDocumentIdentifier,
640 text_document: lsp::TextDocumentIdentifier,
619 position: lsp::Position,
641 position: lsp::Position,
642+ work_done_token: Option<lsp::ProgressToken>,
620 ) -> impl Future<Output = Result<Value>> {
643 ) -> impl Future<Output = Result<Value>> {
621− self.goto_request::<lsp::request::GotoDefinition>(text_document, position)
644+ self.goto_request::<lsp::request::GotoDefinition>(text_document, position, work_done_token)
622 }
645 }
623
646
624 pub fn goto_type_definition(
647 pub fn goto_type_definition(
625 &self,
648 &self,
626 text_document: lsp::TextDocumentIdentifier,
649 text_document: lsp::TextDocumentIdentifier,
627 position: lsp::Position,
650 position: lsp::Position,
651+ work_done_token: Option<lsp::ProgressToken>,
628 ) -> impl Future<Output = Result<Value>> {
652 ) -> impl Future<Output = Result<Value>> {
629− self.goto_request::<lsp::request::GotoTypeDefinition>(text_document, position)
653+ self.goto_request::<lsp::request::GotoTypeDefinition>(
654+ text_document,
655+ position,
656+ work_done_token,
657+ )
630 }
658 }
631
659
632 pub fn goto_implementation(
660 pub fn goto_implementation(
633 &self,
661 &self,
634 text_document: lsp::TextDocumentIdentifier,
662 text_document: lsp::TextDocumentIdentifier,
635 position: lsp::Position,
663 position: lsp::Position,
664+ work_done_token: Option<lsp::ProgressToken>,
636 ) -> impl Future<Output = Result<Value>> {
665 ) -> impl Future<Output = Result<Value>> {
637− self.goto_request::<lsp::request::GotoImplementation>(text_document, position)
666+ self.goto_request::<lsp::request::GotoImplementation>(
667+ text_document,
668+ position,
669+ work_done_token,
670+ )
638 }
671 }
639
672
640 pub fn goto_reference(
673 pub fn goto_reference(
641 &self,
674 &self,
642 text_document: lsp::TextDocumentIdentifier,
675 text_document: lsp::TextDocumentIdentifier,
643 position: lsp::Position,
676 position: lsp::Position,
677+ work_done_token: Option<lsp::ProgressToken>,
644 ) -> impl Future<Output = Result<Value>> {
678 ) -> impl Future<Output = Result<Value>> {
645 let params = lsp::ReferenceParams {
679 let params = lsp::ReferenceParams {
646 text_document_position: lsp::TextDocumentPositionParams {
680 text_document_position: lsp::TextDocumentPositionParams {
@@ -650,9 +684,7 @@ impl Client {
650 context: lsp::ReferenceContext {
684 context: lsp::ReferenceContext {
651 include_declaration: true,
685 include_declaration: true,
652 },
686 },
653− work_done_progress_params: lsp::WorkDoneProgressParams {
687+ work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
654− work_done_token: None,
655− },
656 partial_result_params: lsp::PartialResultParams {
688 partial_result_params: lsp::PartialResultParams {
657 partial_result_token: None,
689 partial_result_token: None,
658 },
690 },
helix-lsp/src/lib.rs+121−6
@@ -13,7 +13,10 @@ use helix_core::syntax::LanguageConfiguration;
13
13
14use std::{
14use std::{
15 collections::{hash_map::Entry, HashMap},
15 collections::{hash_map::Entry, HashMap},
16− sync::Arc,
16+ sync::{
17+ atomic::{AtomicUsize, Ordering},
18+ Arc,
19+ },
17};
20};
18
21
19use serde::{Deserialize, Serialize};
22use serde::{Deserialize, Serialize};
@@ -181,6 +184,30 @@ pub mod util {
181 }
184 }
182}
185}
183
186
187+#[derive(Debug, PartialEq, Clone)]
188+pub enum MethodCall {
189+ WorkDoneProgressCreate(lsp::WorkDoneProgressCreateParams),
190+}
191+
192+impl MethodCall {
193+ pub fn parse(method: &str, params: jsonrpc::Params) -> Option<MethodCall> {
194+ use lsp::request::Request;
195+ let request = match method {
196+ lsp::request::WorkDoneProgressCreate::METHOD => {
197+ let params: lsp::WorkDoneProgressCreateParams = params
198+ .parse()
199+ .expect("Failed to parse WorkDoneCreate params");
200+ Self::WorkDoneProgressCreate(params)
201+ }
202+ _ => {
203+ log::warn!("unhandled lsp request: {}", method);
204+ return None;
205+ }
206+ };
207+ Some(request)
208+ }
209+}
210+
184#[derive(Debug, PartialEq, Clone)]
211#[derive(Debug, PartialEq, Clone)]
185pub enum Notification {
212pub enum Notification {
186 PublishDiagnostics(lsp::PublishDiagnosticsParams),
213 PublishDiagnostics(lsp::PublishDiagnosticsParams),
@@ -230,9 +257,10 @@ impl Notification {
230
257
231#[derive(Debug)]
258#[derive(Debug)]
232pub struct Registry {
259pub struct Registry {
233− inner: HashMap<LanguageId, Arc<Client>>,
260+ inner: HashMap<LanguageId, (usize, Arc<Client>)>,
234
261
262+ counter: AtomicUsize,
235− pub incoming: SelectAll<UnboundedReceiverStream<Call>>,
263+ pub incoming: SelectAll<UnboundedReceiverStream<(usize, Call)>>,
236}
264}
237
265
238impl Default for Registry {
266impl Default for Registry {
@@ -245,10 +273,18 @@ impl Registry {
245 pub fn new() -> Self {
273 pub fn new() -> Self {
246 Self {
274 Self {
247 inner: HashMap::new(),
275 inner: HashMap::new(),
276+ counter: AtomicUsize::new(0),
248 incoming: SelectAll::new(),
277 incoming: SelectAll::new(),
249 }
278 }
250 }
279 }
251
280
281+ pub fn get_by_id(&mut self, id: usize) -> Option<&Client> {
282+ self.inner
283+ .values()
284+ .find(|(client_id, _)| client_id == &id)
285+ .map(|(_, client)| client.as_ref())
286+ }
287+
252 pub fn get(&mut self, language_config: &LanguageConfiguration) -> Result<Arc<Client>> {
288 pub fn get(&mut self, language_config: &LanguageConfiguration) -> Result<Arc<Client>> {
253 if let Some(config) = &language_config.language_server {
289 if let Some(config) = &language_config.language_server {
254 // avoid borrow issues
290 // avoid borrow issues
@@ -256,16 +292,17 @@ impl Registry {
256 let s_incoming = &mut self.incoming;
292 let s_incoming = &mut self.incoming;
257
293
258 match inner.entry(language_config.scope.clone()) {
294 match inner.entry(language_config.scope.clone()) {
259− Entry::Occupied(language_server) => Ok(language_server.get().clone()),
295+ Entry::Occupied(entry) => Ok(entry.get().1.clone()),
260 Entry::Vacant(entry) => {
296 Entry::Vacant(entry) => {
261 // initialize a new client
297 // initialize a new client
298+ let id = self.counter.fetch_add(1, Ordering::Relaxed);
262− let (mut client, incoming) = Client::start(&config.command, &config.args)?;
299+ let (mut client, incoming) = Client::start(&config.command, &config.args, id)?;
263 // TODO: run this async without blocking
300 // TODO: run this async without blocking
264 futures_executor::block_on(client.initialize())?;
301 futures_executor::block_on(client.initialize())?;
265 s_incoming.push(UnboundedReceiverStream::new(incoming));
302 s_incoming.push(UnboundedReceiverStream::new(incoming));
266 let client = Arc::new(client);
303 let client = Arc::new(client);
267
304
268− entry.insert(client.clone());
305+ entry.insert((id, client.clone()));
269 Ok(client)
306 Ok(client)
270 }
307 }
271 }
308 }
@@ -273,6 +310,84 @@ impl Registry {
273 Err(Error::LspNotDefined)
310 Err(Error::LspNotDefined)
274 }
311 }
275 }
312 }
313+
314+ pub fn iter_clients(&self) -> impl Iterator<Item = &Arc<Client>> {
315+ self.inner.values().map(|(_, client)| client)
316+ }
317+}
318+
319+#[derive(Debug)]
320+pub enum ProgressStatus {
321+ Created,
322+ Started(lsp::WorkDoneProgress),
323+}
324+
325+impl ProgressStatus {
326+ pub fn progress(&self) -> Option<&lsp::WorkDoneProgress> {
327+ match &self {
328+ ProgressStatus::Created => None,
329+ ProgressStatus::Started(progress) => Some(&progress),
330+ }
331+ }
332+}
333+
334+#[derive(Default, Debug)]
335+/// Acts as a container for progress reported by language servers. Each server
336+/// has a unique id assigned at creation through [`Registry`]. This id is then used
337+/// to store the progress in this map.
338+pub struct LspProgressMap(HashMap<usize, HashMap<lsp::ProgressToken, ProgressStatus>>);
339+
340+impl LspProgressMap {
341+ pub fn new() -> Self {
342+ Self::default()
343+ }
344+
345+ /// Returns a map of all tokens coresponding to the lanaguage server with `id`.
346+ pub fn progress_map(&self, id: usize) -> Option<&HashMap<lsp::ProgressToken, ProgressStatus>> {
347+ self.0.get(&id)
348+ }
349+
350+ /// Returns last progress status for a given server with `id` and `token`.
351+ pub fn progress(&self, id: usize, token: &lsp::ProgressToken) -> Option<&ProgressStatus> {
352+ self.0.get(&id).and_then(|values| values.get(token))
353+ }
354+
355+ /// Checks if progress `token` for server with `id` is created.
356+ pub fn is_created(&mut self, id: usize, token: &lsp::ProgressToken) -> bool {
357+ self.0
358+ .get(&id)
359+ .map(|values| values.get(token).is_some())
360+ .unwrap_or_default()
361+ }
362+
363+ pub fn create(&mut self, id: usize, token: lsp::ProgressToken) {
364+ self.0
365+ .entry(id)
366+ .or_default()
367+ .insert(token, ProgressStatus::Created);
368+ }
369+
370+ /// Ends the progress by removing the `token` from server with `id`, if removed returns the value.
371+ pub fn end_progress(
372+ &mut self,
373+ id: usize,
374+ token: &lsp::ProgressToken,
375+ ) -> Option<ProgressStatus> {
376+ self.0.get_mut(&id).and_then(|vals| vals.remove(token))
377+ }
378+
379+ /// Updates the progess of `token` for server with `id` to `status`, returns the value replaced or `None`.
380+ pub fn update(
381+ &mut self,
382+ id: usize,
383+ token: lsp::ProgressToken,
384+ status: lsp::WorkDoneProgress,
385+ ) -> Option<ProgressStatus> {
386+ self.0
387+ .entry(id)
388+ .or_default()
389+ .insert(token, ProgressStatus::Started(status))
390+ }
276}
391}
277
392
278// REGISTRY = HashMap<LanguageId, Lazy/OnceCell<Arc<RwLock<Client>>>
393// REGISTRY = HashMap<LanguageId, Lazy/OnceCell<Arc<RwLock<Client>>>
helix-lsp/src/transport.rs+15−6
@@ -1,4 +1,5 @@
1use crate::Result;
1use crate::Result;
2+use anyhow::Context;
2use jsonrpc_core as jsonrpc;
3use jsonrpc_core as jsonrpc;
3use log::{error, info};
4use log::{error, info};
4use serde::{Deserialize, Serialize};
5use serde::{Deserialize, Serialize};
@@ -33,7 +34,8 @@ enum ServerMessage {
33
34
34#[derive(Debug)]
35#[derive(Debug)]
35pub struct Transport {
36pub struct Transport {
37+ id: usize,
36− client_tx: UnboundedSender<jsonrpc::Call>,
38+ client_tx: UnboundedSender<(usize, jsonrpc::Call)>,
37 client_rx: UnboundedReceiver<Payload>,
39 client_rx: UnboundedReceiver<Payload>,
38
40
39 pending_requests: HashMap<jsonrpc::Id, Sender<Result<Value>>>,
41 pending_requests: HashMap<jsonrpc::Id, Sender<Result<Value>>>,
@@ -48,11 +50,16 @@ impl Transport {
48 server_stdout: BufReader<ChildStdout>,
50 server_stdout: BufReader<ChildStdout>,
49 server_stdin: BufWriter<ChildStdin>,
51 server_stdin: BufWriter<ChildStdin>,
50 server_stderr: BufReader<ChildStderr>,
52 server_stderr: BufReader<ChildStderr>,
53+ id: usize,
54+ ) -> (
51− ) -> (UnboundedReceiver<jsonrpc::Call>, UnboundedSender<Payload>) {
55+ UnboundedReceiver<(usize, jsonrpc::Call)>,
56+ UnboundedSender<Payload>,
57+ ) {
52 let (client_tx, rx) = unbounded_channel();
58 let (client_tx, rx) = unbounded_channel();
53 let (tx, client_rx) = unbounded_channel();
59 let (tx, client_rx) = unbounded_channel();
54
60
55 let transport = Self {
61 let transport = Self {
62+ id,
56 server_stdout,
63 server_stdout,
57 server_stdin,
64 server_stdin,
58 server_stderr,
65 server_stderr,
@@ -84,7 +91,7 @@ impl Transport {
84
91
85 match (parts.next(), parts.next(), parts.next()) {
92 match (parts.next(), parts.next(), parts.next()) {
86 (Some("Content-Length"), Some(value), None) => {
93 (Some("Content-Length"), Some(value), None) => {
87− content_length = Some(value.parse().unwrap());
94+ content_length = Some(value.parse().context("invalid content length")?);
88 }
95 }
89 (Some(_), Some(_), None) => {}
96 (Some(_), Some(_), None) => {}
90 _ => {
97 _ => {
@@ -97,12 +104,12 @@ impl Transport {
97 }
104 }
98 }
105 }
99
106
100− let content_length = content_length.unwrap();
107+ let content_length = content_length.context("missing content length")?;
101
108
102 //TODO: reuse vector
109 //TODO: reuse vector
103 let mut content = vec![0; content_length];
110 let mut content = vec![0; content_length];
104 reader.read_exact(&mut content).await?;
111 reader.read_exact(&mut content).await?;
105− let msg = String::from_utf8(content).unwrap();
112+ let msg = String::from_utf8(content).context("invalid utf8 from server")?;
106
113
107 info!("<- {}", msg);
114 info!("<- {}", msg);
108
115
@@ -156,7 +163,9 @@ impl Transport {
156 match msg {
163 match msg {
157 ServerMessage::Output(output) => self.process_request_response(output).await?,
164 ServerMessage::Output(output) => self.process_request_response(output).await?,
158 ServerMessage::Call(call) => {
165 ServerMessage::Call(call) => {
159− self.client_tx.send(call).unwrap();
166+ self.client_tx
167+ .send((self.id, call))
168+ .context("failed to send a message to server")?;
160 // let notification = Notification::parse(&method, params);
169 // let notification = Notification::parse(&method, params);
161 }
170 }
162 };
171 };
helix-syntax/Cargo.toml+5−2
@@ -4,8 +4,11 @@ version = "0.2.0"
4authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
4authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5edition = "2018"
5edition = "2018"
6license = "MPL-2.0"
6license = "MPL-2.0"
7−
7+description = "Tree-sitter grammars support"
8−# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+categories = ["editor"]
9+repository = "https://github.com/helix-editor/helix"
10+homepage = "https://helix-editor.com"
11+include = ["src/**/*", "languages/**/*", "build.rs", "!**/docs/**/*", "!**/test/**/*", "!**/examples/**/*", "!**/build/**/*"]
9
12
10[dependencies]
13[dependencies]
11tree-sitter = "0.19"
14tree-sitter = "0.19"
helix-term/Cargo.toml+7−3
@@ -5,6 +5,10 @@ description = "A post-modern text editor."
5authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
6edition = "2018"
6edition = "2018"
7license = "MPL-2.0"
7license = "MPL-2.0"
8+categories = ["editor", "command-line-utilities"]
9+repository = "https://github.com/helix-editor/helix"
10+homepage = "https://helix-editor.com"
11+include = ["src/**/*", "README.md"]
8
12
9[package.metadata.nix]
13[package.metadata.nix]
10build = true
14build = true
@@ -18,9 +22,9 @@ name = "hx"
18path = "src/main.rs"
22path = "src/main.rs"
19
23
20[dependencies]
24[dependencies]
21−helix-core = { path = "../helix-core" }
25+helix-core = { version = "0.2", path = "../helix-core" }
22−helix-view = { path = "../helix-view", features = ["term"]}
26+helix-view = { version = "0.2", path = "../helix-view", features = ["term"]}
23−helix-lsp = { path = "../helix-lsp"}
27+helix-lsp = { version = "0.2", path = "../helix-lsp"}
24
28
25anyhow = "1"
29anyhow = "1"
26once_cell = "1.8"
30once_cell = "1.8"
helix-term/src/application.rs+137−59
@@ -1,7 +1,7 @@
1−use helix_lsp::lsp;
1+use helix_lsp::{lsp, LspProgressMap};
2use helix_view::{document::Mode, Document, Editor, Theme, View};
2use helix_view::{document::Mode, Document, Editor, Theme, View};
3
3
4−use crate::{args::Args, compositor::Compositor, ui};
4+use crate::{args::Args, compositor::Compositor, config::Config, ui};
5
5
6use log::{error, info};
6use log::{error, info};
7
7
@@ -9,6 +9,7 @@ use std::{
9 future::Future,
9 future::Future,
10 io::{self, stdout, Stdout, Write},
10 io::{self, stdout, Stdout, Write},
11 path::PathBuf,
11 path::PathBuf,
12+ pin::Pin,
12 sync::Arc,
13 sync::Arc,
13 time::Duration,
14 time::Duration,
14};
15};
@@ -22,8 +23,7 @@ use crossterm::{
22
23
23use tui::layout::Rect;
24use tui::layout::Rect;
24
25
25−use futures_util::stream::FuturesUnordered;
26+use futures_util::{future, stream::FuturesUnordered};
26−use std::pin::Pin;
27
27
28type BoxFuture<T> = Pin<Box<dyn Future<Output = T> + Send>>;
28type BoxFuture<T> = Pin<Box<dyn Future<Output = T> + Send>>;
29pub type LspCallback =
29pub type LspCallback =
@@ -37,16 +37,20 @@ pub struct Application {
37 editor: Editor,
37 editor: Editor,
38
38
39 callbacks: LspCallbacks,
39 callbacks: LspCallbacks,
40+
41+ lsp_progress: LspProgressMap,
42+ lsp_progress_enabled: bool,
40}
43}
41
44
42impl Application {
45impl Application {
43− pub fn new(mut args: Args) -> Result<Self, Error> {
46+ pub fn new(mut args: Args, config: Config) -> Result<Self, Error> {
44 use helix_view::editor::Action;
47 use helix_view::editor::Action;
45 let mut compositor = Compositor::new()?;
48 let mut compositor = Compositor::new()?;
46 let size = compositor.size();
49 let size = compositor.size();
47 let mut editor = Editor::new(size);
50 let mut editor = Editor::new(size);
48
51
49− compositor.push(Box::new(ui::EditorView::new()));
52+ let mut editor_view = Box::new(ui::EditorView::new(config.keys));
53+ compositor.push(editor_view);
50
54
51 if !args.files.is_empty() {
55 if !args.files.is_empty() {
52 let first = &args.files[0]; // we know it's not empty
56 let first = &args.files[0]; // we know it's not empty
@@ -73,6 +77,8 @@ impl Application {
73 editor,
77 editor,
74
78
75 callbacks: FuturesUnordered::new(),
79 callbacks: FuturesUnordered::new(),
80+ lsp_progress: LspProgressMap::new(),
81+ lsp_progress_enabled: config.global.lsp_progress,
76 };
82 };
77
83
78 Ok(app)
84 Ok(app)
@@ -108,8 +114,8 @@ impl Application {
108 event = reader.next() => {
114 event = reader.next() => {
109 self.handle_terminal_events(event)
115 self.handle_terminal_events(event)
110 }
116 }
111− Some(call) = self.editor.language_servers.incoming.next() => {
117+ Some((id, call)) = self.editor.language_servers.incoming.next() => {
112− self.handle_language_server_message(call).await
118+ self.handle_language_server_message(call, id).await
113 }
119 }
114 Some(callback) = &mut self.callbacks.next() => {
120 Some(callback) = &mut self.callbacks.next() => {
115 self.handle_language_server_callback(callback)
121 self.handle_language_server_callback(callback)
@@ -152,8 +158,12 @@ impl Application {
152 }
158 }
153 }
159 }
154
160
155− pub async fn handle_language_server_message(&mut self, call: helix_lsp::Call) {
161+ pub async fn handle_language_server_message(
162+ &mut self,
163+ call: helix_lsp::Call,
164+ server_id: usize,
165+ ) {
156− use helix_lsp::{Call, Notification};
166+ use helix_lsp::{Call, MethodCall, Notification};
157 match call {
167 match call {
158 Call::Notification(helix_lsp::jsonrpc::Notification { method, params, .. }) => {
168 Call::Notification(helix_lsp::jsonrpc::Notification { method, params, .. }) => {
159 let notification = match Notification::parse(&method, params) {
169 let notification = match Notification::parse(&method, params) {
@@ -241,64 +251,130 @@ impl Application {
241 log::warn!("unhandled window/logMessage: {:?}", params);
251 log::warn!("unhandled window/logMessage: {:?}", params);
242 }
252 }
243 Notification::ProgressMessage(params) => {
253 Notification::ProgressMessage(params) => {
244− let token = match params.token {
254+ let lsp::ProgressParams { token, value } = params;
255+
256+ let lsp::ProgressParamsValue::WorkDone(work) = value;
257+ let parts = match &work {
258+ lsp::WorkDoneProgress::Begin(lsp::WorkDoneProgressBegin {
259+ title,
260+ message,
261+ percentage,
262+ ..
263+ }) => (Some(title), message, percentage),
264+ lsp::WorkDoneProgress::Report(lsp::WorkDoneProgressReport {
265+ message,
266+ percentage,
267+ ..
268+ }) => (None, message, percentage),
269+ lsp::WorkDoneProgress::End(lsp::WorkDoneProgressEnd { message }) => {
270+ if message.is_some() {
271+ (None, message, &None)
272+ } else {
273+ self.lsp_progress.end_progress(server_id, &token);
274+ self.editor.clear_status();
275+ return;
276+ }
277+ }
278+ };
279+ let token_d: &dyn std::fmt::Display = match &token {
245− lsp::NumberOrString::Number(n) => n.to_string(),
280+ lsp::NumberOrString::Number(n) => n,
246 lsp::NumberOrString::String(s) => s,
281 lsp::NumberOrString::String(s) => s,
247 };
282 };
248− let msg = {
283+
249− let lsp::ProgressParamsValue::WorkDone(work) = params.value;
284+ let status = match parts {
250− let parts = match work {
251− lsp::WorkDoneProgress::Begin(lsp::WorkDoneProgressBegin {
252− title,
253− message,
254− percentage,
255− ..
256− }) => (Some(title), message, percentage.map(|n| n.to_string())),
257− lsp::WorkDoneProgress::Report(lsp::WorkDoneProgressReport {
258− message,
259− percentage,
260− ..
261− }) => (None, message, percentage.map(|n| n.to_string())),
262− lsp::WorkDoneProgress::End(lsp::WorkDoneProgressEnd {
263− message,
264− }) => {
265− if let Some(message) = message {
266− (None, Some(message), None)
267− } else {
268− self.editor.clear_status();
269− return;
270− }
271− }
272− };
273− match parts {
274− (Some(title), Some(message), Some(percentage)) => {
285+ (Some(title), Some(message), Some(percentage)) => {
275− format!("{}% {} - {}", percentage, title, message)
286+ format!("[{}] {}% {} - {}", token_d, percentage, title, message)
276− }
287+ }
277− (Some(title), None, Some(percentage)) => {
288+ (Some(title), None, Some(percentage)) => {
278− format!("{}% {}", percentage, title)
289+ format!("[{}] {}% {}", token_d, percentage, title)
279− }
290+ }
280− (Some(title), Some(message), None) => {
291+ (Some(title), Some(message), None) => {
281− format!("{} - {}", title, message)
292+ format!("[{}] {} - {}", token_d, title, message)
282− }
293+ }
283− (None, Some(message), Some(percentage)) => {
294+ (None, Some(message), Some(percentage)) => {
284− format!("{}% {}", percentage, message)
295+ format!("[{}] {}% {}", token_d, percentage, message)
285− }
296+ }
286− (Some(title), None, None) => title,
297+ (Some(title), None, None) => {
287− (None, Some(message), None) => message,
298+ format!("[{}] {}", token_d, title)
288− (None, None, Some(percentage)) => format!("{}%", percentage),
289− (None, None, None) => "".into(),
290 }
299 }
300+ (None, Some(message), None) => {
301+ format!("[{}] {}", token_d, message)
302+ }
303+ (None, None, Some(percentage)) => {
304+ format!("[{}] {}%", token_d, percentage)
305+ }
306+ (None, None, None) => format!("[{}]", token_d),
291 };
307 };
292− let status = format!("[{}] {}", token, msg);
308+
309+ if let lsp::WorkDoneProgress::End(_) = work {
310+ self.lsp_progress.end_progress(server_id, &token);
311+ } else {
312+ self.lsp_progress.update(server_id, token, work);
313+ }
314+
315+ if self.lsp_progress_enabled {
293− self.editor.set_status(status);
316+ self.editor.set_status(status);
294− self.render();
317+ self.render();
318+ }
295 }
319 }
296 _ => unreachable!(),
320 _ => unreachable!(),
297 }
321 }
298 }
322 }
299− Call::MethodCall(call) => {
323+ Call::MethodCall(helix_lsp::jsonrpc::MethodCall {
324+ method,
325+ params,
326+ jsonrpc,
327+ id,
328+ }) => {
329+ let call = match MethodCall::parse(&method, params) {
330+ Some(call) => call,
331+ None => {
300− error!("Method not found {}", call.method);
332+ error!("Method not found {}", method);
333+ return;
334+ }
335+ };
301
336
337+ match call {
338+ MethodCall::WorkDoneProgressCreate(params) => {
339+ self.lsp_progress.create(server_id, params.token);
340+
341+ let doc = self.editor.documents().find(|doc| {
342+ doc.language_server()
343+ .map(|server| server.id() == server_id)
344+ .unwrap_or_default()
345+ });
346+ match doc {
347+ Some(doc) => {
348+ // it's ok to unwrap, we check for the language server before
349+ let server = doc.language_server().unwrap();
350+ tokio::spawn(server.reply(id, Ok(serde_json::Value::Null)));
351+ }
352+ None => {
353+ if let Some(server) =
354+ self.editor.language_servers.get_by_id(server_id)
355+ {
356+ log::warn!(
357+ "missing document with language server id `{}`",
358+ server_id
359+ );
360+ tokio::spawn(server.reply(
361+ id,
362+ Err(helix_lsp::jsonrpc::Error {
363+ code: helix_lsp::jsonrpc::ErrorCode::InternalError,
364+ message: "document missing".to_string(),
365+ data: None,
366+ }),
367+ ));
368+ } else {
369+ log::warn!(
370+ "can't find language server with id `{}`",
371+ server_id
372+ );
373+ }
374+ }
375+ }
376+ }
377+ }
302 // self.language_server.reply(
378 // self.language_server.reply(
303 // call.id,
379 // call.id,
304 // // TODO: make a Into trait that can cast to Err(jsonrpc::Error)
380 // // TODO: make a Into trait that can cast to Err(jsonrpc::Error)
@@ -330,6 +406,8 @@ impl Application {
330
406
331 self.event_loop().await;
407 self.event_loop().await;
332
408
409+ self.editor.close_language_servers(None).await;
410+
333 // reset cursor shape
411 // reset cursor shape
334 write!(stdout, "\x1B[2 q");
412 write!(stdout, "\x1B[2 q");
335
413
helix-term/src/commands.rs+488−313
@@ -11,15 +11,18 @@ use helix_core::{
11
11
12use helix_view::{
12use helix_view::{
13 document::{IndentStyle, Mode},
13 document::{IndentStyle, Mode},
14+ input::{KeyCode, KeyEvent},
14 view::{View, PADDING},
15 view::{View, PADDING},
15 Document, DocumentId, Editor, ViewId,
16 Document, DocumentId, Editor, ViewId,
16};
17};
17
18
19+use anyhow::anyhow;
18use helix_lsp::{
20use helix_lsp::{
19 lsp,
21 lsp,
20 util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range},
22 util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range},
21 OffsetEncoding,
23 OffsetEncoding,
22};
24};
25+use insert::*;
23use movement::Movement;
26use movement::Movement;
24
27
25use crate::{
28use crate::{
@@ -29,15 +32,15 @@ use crate::{
29
32
30use crate::application::{LspCallbackWrapper, LspCallbacks};
33use crate::application::{LspCallbackWrapper, LspCallbacks};
31use futures_util::FutureExt;
34use futures_util::FutureExt;
32−use std::future::Future;
35+use std::{fmt, future::Future, path::Display, str::FromStr};
33
36
34use std::{
37use std::{
35 borrow::Cow,
38 borrow::Cow,
36 path::{Path, PathBuf},
39 path::{Path, PathBuf},
37};
40};
38
41
39−use crossterm::event::{KeyCode, KeyEvent};
40use once_cell::sync::Lazy;
42use once_cell::sync::Lazy;
43+use serde::de::{self, Deserialize, Deserializer};
41
44
42pub struct Context<'a> {
45pub struct Context<'a> {
43 pub selected_register: helix_view::RegisterSelection,
46 pub selected_register: helix_view::RegisterSelection,
@@ -50,27 +53,6 @@ pub struct Context<'a> {
50}
53}
51
54
52impl<'a> Context<'a> {
55impl<'a> Context<'a> {
53− #[inline]
54− pub fn view(&mut self) -> &mut View {
55− self.editor.view_mut()
56− }
57−
58− #[inline]
59− pub fn doc(&mut self) -> &mut Document {
60− let id = self.editor.view().doc;
61− &mut self.editor.documents[id]
62− }
63−
64− #[inline]
65− pub fn current(&mut self) -> (&mut View, &mut Document) {
66− self.editor.current()
67− }
68−
69− #[inline]
70− pub fn current_with_registers(&mut self) -> (&mut View, &mut Document, &mut Registers) {
71− self.editor.current_with_registers()
72− }
73−
74 /// Push a new component onto the compositor.
56 /// Push a new component onto the compositor.
75 pub fn push_layer(&mut self, mut component: Box<dyn Component>) {
57 pub fn push_layer(&mut self, mut component: Box<dyn Component>) {
76 self.callback = Some(Box::new(|compositor: &mut Compositor| {
58 self.callback = Some(Box::new(|compositor: &mut Compositor| {
@@ -107,6 +89,7 @@ impl<'a> Context<'a> {
107 self.callbacks.push(callback);
89 self.callbacks.push(callback);
108 }
90 }
109
91
92+ /// Returns 1 if no explicit count was provided
110 #[inline]
93 #[inline]
111 pub fn count(&self) -> usize {
94 pub fn count(&self) -> usize {
112 self.count.map_or(1, |v| v.get())
95 self.count.map_or(1, |v| v.get())
@@ -132,13 +115,189 @@ fn align_view(doc: &Document, view: &mut View, align: Align) {
132 view.first_line = line.saturating_sub(relative);
115 view.first_line = line.saturating_sub(relative);
133}
116}
134
117
135−/// A command is a function that takes the current state and a count, and does a side-effect on the
118+/// A command is composed of a static name, and a function that takes the current state plus a count,
136−/// state (usually by creating and applying a transaction).
119+/// and does a side-effect on the state (usually by creating and applying a transaction).
137−pub type Command = fn(cx: &mut Context);
120+#[derive(Copy, Clone)]
121+pub struct Command(&'static str, fn(cx: &mut Context));
122+
123+macro_rules! commands {
124+ ( $($name:ident),* ) => {
125+ $(
126+ #[allow(non_upper_case_globals)]
127+ pub const $name: Self = Self(stringify!($name), $name);
128+ )*
129+
130+ pub const COMMAND_LIST: &'static [Self] = &[
131+ $( Self::$name, )*
132+ ];
133+ }
134+}
135+
136+impl Command {
137+ pub fn execute(&self, cx: &mut Context) {
138+ (self.1)(cx);
139+ }
140+
141+ pub fn name(&self) -> &'static str {
142+ self.0
143+ }
144+
145+ commands!(
146+ move_char_left,
147+ move_char_right,
148+ move_line_up,
149+ move_line_down,
150+ move_line_end,
151+ move_line_start,
152+ move_first_nonwhitespace,
153+ move_next_word_start,
154+ move_prev_word_start,
155+ move_next_word_end,
156+ move_file_start,
157+ move_file_end,
158+ extend_next_word_start,
159+ extend_prev_word_start,
160+ extend_next_word_end,
161+ find_till_char,
162+ find_next_char,
163+ extend_till_char,
164+ extend_next_char,
165+ till_prev_char,
166+ find_prev_char,
167+ extend_till_prev_char,
168+ extend_prev_char,
169+ extend_first_nonwhitespace,
170+ replace,
171+ page_up,
172+ page_down,
173+ half_page_up,
174+ half_page_down,
175+ extend_char_left,
176+ extend_char_right,
177+ extend_line_up,
178+ extend_line_down,
179+ extend_line_end,
180+ extend_line_start,
181+ select_all,
182+ select_regex,
183+ split_selection,
184+ split_selection_on_newline,
185+ search,
186+ search_next,
187+ extend_search_next,
188+ search_selection,
189+ select_line,
190+ extend_line,
191+ delete_selection,
192+ change_selection,
193+ collapse_selection,
194+ flip_selections,
195+ insert_mode,
196+ append_mode,
197+ command_mode,
198+ file_picker,
199+ buffer_picker,
200+ symbol_picker,
201+ prepend_to_line,
202+ append_to_line,
203+ open_below,
204+ open_above,
205+ normal_mode,
206+ goto_mode,
207+ select_mode,
208+ exit_select_mode,
209+ goto_definition,
210+ goto_type_definition,
211+ goto_implementation,
212+ goto_reference,
213+ goto_first_diag,
214+ goto_last_diag,
215+ goto_next_diag,
216+ goto_prev_diag,
217+ signature_help,
218+ insert_tab,
219+ insert_newline,
220+ delete_char_backward,
221+ delete_char_forward,
222+ delete_word_backward,
223+ undo,
224+ redo,
225+ yank,
226+ replace_with_yanked,
227+ paste_after,
228+ paste_before,
229+ indent,
230+ unindent,
231+ format_selections,
232+ join_selections,
233+ keep_selections,
234+ keep_primary_selection,
235+ save,
236+ completion,
237+ hover,
238+ toggle_comments,
239+ expand_selection,
240+ match_brackets,
241+ jump_forward,
242+ jump_backward,
243+ window_mode,
244+ rotate_view,
245+ hsplit,
246+ vsplit,
247+ wclose,
248+ select_register,
249+ space_mode,
250+ view_mode,
251+ left_bracket_mode,
252+ right_bracket_mode
253+ );
254+}
255+
256+impl fmt::Debug for Command {
257+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
258+ let Command(name, _) = self;
259+ f.debug_tuple("Command").field(name).finish()
260+ }
261+}
262+
263+impl fmt::Display for Command {
264+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
265+ let Command(name, _) = self;
266+ f.write_str(name)
267+ }
268+}
269+
270+impl std::str::FromStr for Command {
271+ type Err = anyhow::Error;
272+
273+ fn from_str(s: &str) -> Result<Self, Self::Err> {
274+ Command::COMMAND_LIST
275+ .iter()
276+ .copied()
277+ .find(|cmd| cmd.0 == s)
278+ .ok_or_else(|| anyhow!("No command named '{}'", s))
279+ }
280+}
138
281
282+impl<'de> Deserialize<'de> for Command {
283+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
284+ where
285+ D: Deserializer<'de>,
286+ {
287+ let s = String::deserialize(deserializer)?;
288+ s.parse().map_err(de::Error::custom)
289+ }
290+}
291+
292+impl PartialEq for Command {
293+ fn eq(&self, other: &Self) -> bool {
294+ self.name() == other.name()
295+ }
296+}
297+
139−pub fn move_char_left(cx: &mut Context) {
298+fn move_char_left(cx: &mut Context) {
140 let count = cx.count();
299 let count = cx.count();
141− let (view, doc) = cx.current();
300+ let (view, doc) = current!(cx.editor);
142 let text = doc.text().slice(..);
301 let text = doc.text().slice(..);
143 let selection = doc.selection(view.id).transform(|range| {
302 let selection = doc.selection(view.id).transform(|range| {
144 movement::move_horizontally(text, range, Direction::Backward, count, Movement::Move)
303 movement::move_horizontally(text, range, Direction::Backward, count, Movement::Move)
@@ -146,9 +305,9 @@ pub fn move_char_left(cx: &mut Context) {
146 doc.set_selection(view.id, selection);
305 doc.set_selection(view.id, selection);
147}
306}
148
307
149−pub fn move_char_right(cx: &mut Context) {
308+fn move_char_right(cx: &mut Context) {
150 let count = cx.count();
309 let count = cx.count();
151− let (view, doc) = cx.current();
310+ let (view, doc) = current!(cx.editor);
152 let text = doc.text().slice(..);
311 let text = doc.text().slice(..);
153 let selection = doc.selection(view.id).transform(|range| {
312 let selection = doc.selection(view.id).transform(|range| {
154 movement::move_horizontally(text, range, Direction::Forward, count, Movement::Move)
313 movement::move_horizontally(text, range, Direction::Forward, count, Movement::Move)
@@ -156,9 +315,9 @@ pub fn move_char_right(cx: &mut Context) {
156 doc.set_selection(view.id, selection);
315 doc.set_selection(view.id, selection);
157}
316}
158
317
159−pub fn move_line_up(cx: &mut Context) {
318+fn move_line_up(cx: &mut Context) {
160 let count = cx.count();
319 let count = cx.count();
161− let (view, doc) = cx.current();
320+ let (view, doc) = current!(cx.editor);
162 let text = doc.text().slice(..);
321 let text = doc.text().slice(..);
163 let selection = doc.selection(view.id).transform(|range| {
322 let selection = doc.selection(view.id).transform(|range| {
164 movement::move_vertically(text, range, Direction::Backward, count, Movement::Move)
323 movement::move_vertically(text, range, Direction::Backward, count, Movement::Move)
@@ -166,9 +325,9 @@ pub fn move_line_up(cx: &mut Context) {
166 doc.set_selection(view.id, selection);
325 doc.set_selection(view.id, selection);
167}
326}
168
327
169−pub fn move_line_down(cx: &mut Context) {
328+fn move_line_down(cx: &mut Context) {
170 let count = cx.count();
329 let count = cx.count();
171− let (view, doc) = cx.current();
330+ let (view, doc) = current!(cx.editor);
172 let text = doc.text().slice(..);
331 let text = doc.text().slice(..);
173 let selection = doc.selection(view.id).transform(|range| {
332 let selection = doc.selection(view.id).transform(|range| {
174 movement::move_vertically(text, range, Direction::Forward, count, Movement::Move)
333 movement::move_vertically(text, range, Direction::Forward, count, Movement::Move)
@@ -176,8 +335,8 @@ pub fn move_line_down(cx: &mut Context) {
176 doc.set_selection(view.id, selection);
335 doc.set_selection(view.id, selection);
177}
336}
178
337
179−pub fn move_line_end(cx: &mut Context) {
338+fn move_line_end(cx: &mut Context) {
180− let (view, doc) = cx.current();
339+ let (view, doc) = current!(cx.editor);
181
340
182 let selection = doc.selection(view.id).transform(|range| {
341 let selection = doc.selection(view.id).transform(|range| {
183 let text = doc.text();
342 let text = doc.text();
@@ -195,8 +354,8 @@ pub fn move_line_end(cx: &mut Context) {
195 doc.set_selection(view.id, selection);
354 doc.set_selection(view.id, selection);
196}
355}
197
356
198−pub fn move_line_start(cx: &mut Context) {
357+fn move_line_start(cx: &mut Context) {
199− let (view, doc) = cx.current();
358+ let (view, doc) = current!(cx.editor);
200
359
201 let selection = doc.selection(view.id).transform(|range| {
360 let selection = doc.selection(view.id).transform(|range| {
202 let text = doc.text();
361 let text = doc.text();
@@ -210,8 +369,8 @@ pub fn move_line_start(cx: &mut Context) {
210 doc.set_selection(view.id, selection);
369 doc.set_selection(view.id, selection);
211}
370}
212
371
213−pub fn move_first_nonwhitespace(cx: &mut Context) {
372+fn move_first_nonwhitespace(cx: &mut Context) {
214− let (view, doc) = cx.current();
373+ let (view, doc) = current!(cx.editor);
215
374
216 let selection = doc.selection(view.id).transform(|range| {
375 let selection = doc.selection(view.id).transform(|range| {
217 let text = doc.text();
376 let text = doc.text();
@@ -232,9 +391,9 @@ pub fn move_first_nonwhitespace(cx: &mut Context) {
232// Range::new(if Move { pos } if Extend { range.anchor }, pos)
391// Range::new(if Move { pos } if Extend { range.anchor }, pos)
233// since these all really do the same thing
392// since these all really do the same thing
234
393
235−pub fn move_next_word_start(cx: &mut Context) {
394+fn move_next_word_start(cx: &mut Context) {
236 let count = cx.count();
395 let count = cx.count();
237− let (view, doc) = cx.current();
396+ let (view, doc) = current!(cx.editor);
238 let text = doc.text().slice(..);
397 let text = doc.text().slice(..);
239
398
240 let selection = doc
399 let selection = doc
@@ -244,9 +403,9 @@ pub fn move_next_word_start(cx: &mut Context) {
244 doc.set_selection(view.id, selection);
403 doc.set_selection(view.id, selection);
245}
404}
246
405
247−pub fn move_prev_word_start(cx: &mut Context) {
406+fn move_prev_word_start(cx: &mut Context) {
248 let count = cx.count();
407 let count = cx.count();
249− let (view, doc) = cx.current();
408+ let (view, doc) = current!(cx.editor);
250 let text = doc.text().slice(..);
409 let text = doc.text().slice(..);
251
410
252 let selection = doc
411 let selection = doc
@@ -256,9 +415,9 @@ pub fn move_prev_word_start(cx: &mut Context) {
256 doc.set_selection(view.id, selection);
415 doc.set_selection(view.id, selection);
257}
416}
258
417
259−pub fn move_next_word_end(cx: &mut Context) {
418+fn move_next_word_end(cx: &mut Context) {
260 let count = cx.count();
419 let count = cx.count();
261− let (view, doc) = cx.current();
420+ let (view, doc) = current!(cx.editor);
262 let text = doc.text().slice(..);
421 let text = doc.text().slice(..);
263
422
264 let selection = doc
423 let selection = doc
@@ -268,23 +427,23 @@ pub fn move_next_word_end(cx: &mut Context) {
268 doc.set_selection(view.id, selection);
427 doc.set_selection(view.id, selection);
269}
428}
270
429
271−pub fn move_file_start(cx: &mut Context) {
430+fn move_file_start(cx: &mut Context) {
272 push_jump(cx.editor);
431 push_jump(cx.editor);
273− let (view, doc) = cx.current();
432+ let (view, doc) = current!(cx.editor);
274 doc.set_selection(view.id, Selection::point(0));
433 doc.set_selection(view.id, Selection::point(0));
275}
434}
276
435
277−pub fn move_file_end(cx: &mut Context) {
436+fn move_file_end(cx: &mut Context) {
278 push_jump(cx.editor);
437 push_jump(cx.editor);
279− let (view, doc) = cx.current();
438+ let (view, doc) = current!(cx.editor);
280 let text = doc.text();
439 let text = doc.text();
281 let last_line = text.line_to_char(text.len_lines().saturating_sub(2));
440 let last_line = text.line_to_char(text.len_lines().saturating_sub(2));
282 doc.set_selection(view.id, Selection::point(last_line));
441 doc.set_selection(view.id, Selection::point(last_line));
283}
442}
284
443
285−pub fn extend_next_word_start(cx: &mut Context) {
444+fn extend_next_word_start(cx: &mut Context) {
286 let count = cx.count();
445 let count = cx.count();
287− let (view, doc) = cx.current();
446+ let (view, doc) = current!(cx.editor);
288 let text = doc.text().slice(..);
447 let text = doc.text().slice(..);
289
448
290 let selection = doc.selection(view.id).transform(|mut range| {
449 let selection = doc.selection(view.id).transform(|mut range| {
@@ -296,9 +455,9 @@ pub fn extend_next_word_start(cx: &mut Context) {
296 doc.set_selection(view.id, selection);
455 doc.set_selection(view.id, selection);
297}
456}
298
457
299−pub fn extend_prev_word_start(cx: &mut Context) {
458+fn extend_prev_word_start(cx: &mut Context) {
300 let count = cx.count();
459 let count = cx.count();
301− let (view, doc) = cx.current();
460+ let (view, doc) = current!(cx.editor);
302 let text = doc.text().slice(..);
461 let text = doc.text().slice(..);
303
462
304 let selection = doc.selection(view.id).transform(|mut range| {
463 let selection = doc.selection(view.id).transform(|mut range| {
@@ -309,9 +468,9 @@ pub fn extend_prev_word_start(cx: &mut Context) {
309 doc.set_selection(view.id, selection);
468 doc.set_selection(view.id, selection);
310}
469}
311
470
312−pub fn extend_next_word_end(cx: &mut Context) {
471+fn extend_next_word_end(cx: &mut Context) {
313 let count = cx.count();
472 let count = cx.count();
314− let (view, doc) = cx.current();
473+ let (view, doc) = current!(cx.editor);
315 let text = doc.text().slice(..);
474 let text = doc.text().slice(..);
316
475
317 let selection = doc.selection(view.id).transform(|mut range| {
476 let selection = doc.selection(view.id).transform(|mut range| {
@@ -348,7 +507,7 @@ where
348 _ => return,
507 _ => return,
349 };
508 };
350
509
351− let (view, doc) = cx.current();
510+ let (view, doc) = current!(cx.editor);
352 let text = doc.text().slice(..);
511 let text = doc.text().slice(..);
353
512
354 let selection = doc.selection(view.id).transform(|mut range| {
513 let selection = doc.selection(view.id).transform(|mut range| {
@@ -367,7 +526,7 @@ where
367 })
526 })
368}
527}
369
528
370−pub fn find_till_char(cx: &mut Context) {
529+fn find_till_char(cx: &mut Context) {
371 find_char_impl(
530 find_char_impl(
372 cx,
531 cx,
373 search::find_nth_next,
532 search::find_nth_next,
@@ -376,7 +535,7 @@ pub fn find_till_char(cx: &mut Context) {
376 )
535 )
377}
536}
378
537
379−pub fn find_next_char(cx: &mut Context) {
538+fn find_next_char(cx: &mut Context) {
380 find_char_impl(
539 find_char_impl(
381 cx,
540 cx,
382 search::find_nth_next,
541 search::find_nth_next,
@@ -385,7 +544,7 @@ pub fn find_next_char(cx: &mut Context) {
385 )
544 )
386}
545}
387
546
388−pub fn extend_till_char(cx: &mut Context) {
547+fn extend_till_char(cx: &mut Context) {
389 find_char_impl(
548 find_char_impl(
390 cx,
549 cx,
391 search::find_nth_next,
550 search::find_nth_next,
@@ -394,7 +553,7 @@ pub fn extend_till_char(cx: &mut Context) {
394 )
553 )
395}
554}
396
555
397−pub fn extend_next_char(cx: &mut Context) {
556+fn extend_next_char(cx: &mut Context) {
398 find_char_impl(
557 find_char_impl(
399 cx,
558 cx,
400 search::find_nth_next,
559 search::find_nth_next,
@@ -403,7 +562,7 @@ pub fn extend_next_char(cx: &mut Context) {
403 )
562 )
404}
563}
405
564
406−pub fn till_prev_char(cx: &mut Context) {
565+fn till_prev_char(cx: &mut Context) {
407 find_char_impl(
566 find_char_impl(
408 cx,
567 cx,
409 search::find_nth_prev,
568 search::find_nth_prev,
@@ -412,7 +571,7 @@ pub fn till_prev_char(cx: &mut Context) {
412 )
571 )
413}
572}
414
573
415−pub fn find_prev_char(cx: &mut Context) {
574+fn find_prev_char(cx: &mut Context) {
416 find_char_impl(
575 find_char_impl(
417 cx,
576 cx,
418 search::find_nth_prev,
577 search::find_nth_prev,
@@ -421,7 +580,7 @@ pub fn find_prev_char(cx: &mut Context) {
421 )
580 )
422}
581}
423
582
424−pub fn extend_till_prev_char(cx: &mut Context) {
583+fn extend_till_prev_char(cx: &mut Context) {
425 find_char_impl(
584 find_char_impl(
426 cx,
585 cx,
427 search::find_nth_prev,
586 search::find_nth_prev,
@@ -430,7 +589,7 @@ pub fn extend_till_prev_char(cx: &mut Context) {
430 )
589 )
431}
590}
432
591
433−pub fn extend_prev_char(cx: &mut Context) {
592+fn extend_prev_char(cx: &mut Context) {
434 find_char_impl(
593 find_char_impl(
435 cx,
594 cx,
436 search::find_nth_prev,
595 search::find_nth_prev,
@@ -439,8 +598,8 @@ pub fn extend_prev_char(cx: &mut Context) {
439 )
598 )
440}
599}
441
600
442−pub fn extend_first_nonwhitespace(cx: &mut Context) {
601+fn extend_first_nonwhitespace(cx: &mut Context) {
443− let (view, doc) = cx.current();
602+ let (view, doc) = current!(cx.editor);
444
603
445 let selection = doc.selection(view.id).transform(|range| {
604 let selection = doc.selection(view.id).transform(|range| {
446 let text = doc.text();
605 let text = doc.text();
@@ -457,7 +616,7 @@ pub fn extend_first_nonwhitespace(cx: &mut Context) {
457 doc.set_selection(view.id, selection);
616 doc.set_selection(view.id, selection);
458}
617}
459
618
460−pub fn replace(cx: &mut Context) {
619+fn replace(cx: &mut Context) {
461 // need to wait for next key
620 // need to wait for next key
462 cx.on_next_key(move |cx, event| {
621 cx.on_next_key(move |cx, event| {
463 let ch = match event {
622 let ch = match event {
@@ -473,7 +632,7 @@ pub fn replace(cx: &mut Context) {
473 };
632 };
474
633
475 if let Some(ch) = ch {
634 if let Some(ch) = ch {
476− let (view, doc) = cx.current();
635+ let (view, doc) = current!(cx.editor);
477
636
478 let transaction =
637 let transaction =
479 Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
638 Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
@@ -497,7 +656,7 @@ pub fn replace(cx: &mut Context) {
497
656
498fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
657fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
499 use Direction::*;
658 use Direction::*;
500− let (view, doc) = cx.current();
659+ let (view, doc) = current!(cx.editor);
501 let cursor = coords_at_pos(doc.text().slice(..), doc.selection(view.id).cursor());
660 let cursor = coords_at_pos(doc.text().slice(..), doc.selection(view.id).cursor());
502 let doc_last_line = doc.text().len_lines() - 1;
661 let doc_last_line = doc.text().len_lines() - 1;
503
662
@@ -537,33 +696,33 @@ fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
537 doc.set_selection(view.id, Selection::point(pos));
696 doc.set_selection(view.id, Selection::point(pos));
538}
697}
539
698
540−pub fn page_up(cx: &mut Context) {
699+fn page_up(cx: &mut Context) {
541− let view = cx.view();
700+ let view = view!(cx.editor);
542 let offset = view.area.height as usize;
701 let offset = view.area.height as usize;
543 scroll(cx, offset, Direction::Backward);
702 scroll(cx, offset, Direction::Backward);
544}
703}
545
704
546−pub fn page_down(cx: &mut Context) {
705+fn page_down(cx: &mut Context) {
547− let view = cx.view();
706+ let view = view!(cx.editor);
548 let offset = view.area.height as usize;
707 let offset = view.area.height as usize;
549 scroll(cx, offset, Direction::Forward);
708 scroll(cx, offset, Direction::Forward);
550}
709}
551
710
552−pub fn half_page_up(cx: &mut Context) {
711+fn half_page_up(cx: &mut Context) {
553− let view = cx.view();
712+ let view = view!(cx.editor);
554 let offset = view.area.height as usize / 2;
713 let offset = view.area.height as usize / 2;
555 scroll(cx, offset, Direction::Backward);
714 scroll(cx, offset, Direction::Backward);
556}
715}
557
716
558−pub fn half_page_down(cx: &mut Context) {
717+fn half_page_down(cx: &mut Context) {
559− let view = cx.view();
718+ let view = view!(cx.editor);
560 let offset = view.area.height as usize / 2;
719 let offset = view.area.height as usize / 2;
561 scroll(cx, offset, Direction::Forward);
720 scroll(cx, offset, Direction::Forward);
562}
721}
563
722
564−pub fn extend_char_left(cx: &mut Context) {
723+fn extend_char_left(cx: &mut Context) {
565 let count = cx.count();
724 let count = cx.count();
566− let (view, doc) = cx.current();
725+ let (view, doc) = current!(cx.editor);
567 let text = doc.text().slice(..);
726 let text = doc.text().slice(..);
568 let selection = doc.selection(view.id).transform(|range| {
727 let selection = doc.selection(view.id).transform(|range| {
569 movement::move_horizontally(text, range, Direction::Backward, count, Movement::Extend)
728 movement::move_horizontally(text, range, Direction::Backward, count, Movement::Extend)
@@ -571,9 +730,9 @@ pub fn extend_char_left(cx: &mut Context) {
571 doc.set_selection(view.id, selection);
730 doc.set_selection(view.id, selection);
572}
731}
573
732
574−pub fn extend_char_right(cx: &mut Context) {
733+fn extend_char_right(cx: &mut Context) {
575 let count = cx.count();
734 let count = cx.count();
576− let (view, doc) = cx.current();
735+ let (view, doc) = current!(cx.editor);
577 let text = doc.text().slice(..);
736 let text = doc.text().slice(..);
578 let selection = doc.selection(view.id).transform(|range| {
737 let selection = doc.selection(view.id).transform(|range| {
579 movement::move_horizontally(text, range, Direction::Forward, count, Movement::Extend)
738 movement::move_horizontally(text, range, Direction::Forward, count, Movement::Extend)
@@ -581,9 +740,9 @@ pub fn extend_char_right(cx: &mut Context) {
581 doc.set_selection(view.id, selection);
740 doc.set_selection(view.id, selection);
582}
741}
583
742
584−pub fn extend_line_up(cx: &mut Context) {
743+fn extend_line_up(cx: &mut Context) {
585 let count = cx.count();
744 let count = cx.count();
586− let (view, doc) = cx.current();
745+ let (view, doc) = current!(cx.editor);
587 let text = doc.text().slice(..);
746 let text = doc.text().slice(..);
588 let selection = doc.selection(view.id).transform(|range| {
747 let selection = doc.selection(view.id).transform(|range| {
589 movement::move_vertically(text, range, Direction::Backward, count, Movement::Extend)
748 movement::move_vertically(text, range, Direction::Backward, count, Movement::Extend)
@@ -591,9 +750,9 @@ pub fn extend_line_up(cx: &mut Context) {
591 doc.set_selection(view.id, selection);
750 doc.set_selection(view.id, selection);
592}
751}
593
752
594−pub fn extend_line_down(cx: &mut Context) {
753+fn extend_line_down(cx: &mut Context) {
595 let count = cx.count();
754 let count = cx.count();
596− let (view, doc) = cx.current();
755+ let (view, doc) = current!(cx.editor);
597 let text = doc.text().slice(..);
756 let text = doc.text().slice(..);
598 let selection = doc.selection(view.id).transform(|range| {
757 let selection = doc.selection(view.id).transform(|range| {
599 movement::move_vertically(text, range, Direction::Forward, count, Movement::Extend)
758 movement::move_vertically(text, range, Direction::Forward, count, Movement::Extend)
@@ -601,8 +760,8 @@ pub fn extend_line_down(cx: &mut Context) {
601 doc.set_selection(view.id, selection);
760 doc.set_selection(view.id, selection);
602}
761}
603
762
604−pub fn extend_line_end(cx: &mut Context) {
763+fn extend_line_end(cx: &mut Context) {
605− let (view, doc) = cx.current();
764+ let (view, doc) = current!(cx.editor);
606
765
607 let selection = doc.selection(view.id).transform(|range| {
766 let selection = doc.selection(view.id).transform(|range| {
608 let text = doc.text();
767 let text = doc.text();
@@ -620,8 +779,8 @@ pub fn extend_line_end(cx: &mut Context) {
620 doc.set_selection(view.id, selection);
779 doc.set_selection(view.id, selection);
621}
780}
622
781
623−pub fn extend_line_start(cx: &mut Context) {
782+fn extend_line_start(cx: &mut Context) {
624− let (view, doc) = cx.current();
783+ let (view, doc) = current!(cx.editor);
625
784
626 let selection = doc.selection(view.id).transform(|range| {
785 let selection = doc.selection(view.id).transform(|range| {
627 let text = doc.text();
786 let text = doc.text();
@@ -635,14 +794,14 @@ pub fn extend_line_start(cx: &mut Context) {
635 doc.set_selection(view.id, selection);
794 doc.set_selection(view.id, selection);
636}
795}
637
796
638−pub fn select_all(cx: &mut Context) {
797+fn select_all(cx: &mut Context) {
639− let (view, doc) = cx.current();
798+ let (view, doc) = current!(cx.editor);
640
799
641 let end = doc.text().len_chars().saturating_sub(1);
800 let end = doc.text().len_chars().saturating_sub(1);
642 doc.set_selection(view.id, Selection::single(0, end))
801 doc.set_selection(view.id, Selection::single(0, end))
643}
802}
644
803
645−pub fn select_regex(cx: &mut Context) {
804+fn select_regex(cx: &mut Context) {
646 let prompt = ui::regex_prompt(cx, "select:".to_string(), move |view, doc, _, regex| {
805 let prompt = ui::regex_prompt(cx, "select:".to_string(), move |view, doc, _, regex| {
647 let text = doc.text().slice(..);
806 let text = doc.text().slice(..);
648 if let Some(selection) = selection::select_on_matches(text, doc.selection(view.id), &regex)
807 if let Some(selection) = selection::select_on_matches(text, doc.selection(view.id), &regex)
@@ -654,7 +813,7 @@ pub fn select_regex(cx: &mut Context) {
654 cx.push_layer(Box::new(prompt));
813 cx.push_layer(Box::new(prompt));
655}
814}
656
815
657−pub fn split_selection(cx: &mut Context) {
816+fn split_selection(cx: &mut Context) {
658 let prompt = ui::regex_prompt(cx, "split:".to_string(), move |view, doc, _, regex| {
817 let prompt = ui::regex_prompt(cx, "split:".to_string(), move |view, doc, _, regex| {
659 let text = doc.text().slice(..);
818 let text = doc.text().slice(..);
660 let selection = selection::split_on_matches(text, doc.selection(view.id), &regex);
819 let selection = selection::split_on_matches(text, doc.selection(view.id), &regex);
@@ -664,8 +823,8 @@ pub fn split_selection(cx: &mut Context) {
664 cx.push_layer(Box::new(prompt));
823 cx.push_layer(Box::new(prompt));
665}
824}
666
825
667−pub fn split_selection_on_newline(cx: &mut Context) {
826+fn split_selection_on_newline(cx: &mut Context) {
668− let (view, doc) = cx.current();
827+ let (view, doc) = current!(cx.editor);
669 let text = doc.text().slice(..);
828 let text = doc.text().slice(..);
670 // only compile the regex once
829 // only compile the regex once
671 #[allow(clippy::trivial_regex)]
830 #[allow(clippy::trivial_regex)]
@@ -716,8 +875,8 @@ fn search_impl(doc: &mut Document, view: &mut View, contents: &str, regex: &Rege
716}
875}
717
876
718// TODO: use one function for search vs extend
877// TODO: use one function for search vs extend
719−pub fn search(cx: &mut Context) {
878+fn search(cx: &mut Context) {
720− let (view, doc) = cx.current();
879+ let (view, doc) = current!(cx.editor);
721
880
722 // TODO: could probably share with select_on_matches?
881 // TODO: could probably share with select_on_matches?
723
882
@@ -738,10 +897,10 @@ pub fn search(cx: &mut Context) {
738
897
739 cx.push_layer(Box::new(prompt));
898 cx.push_layer(Box::new(prompt));
740}
899}
741−// can't search next for ""compose"" for some reason
742
900
743−pub fn search_next_impl(cx: &mut Context, extend: bool) {
901+fn search_next_impl(cx: &mut Context, extend: bool) {
744− let (view, doc, registers) = cx.current_with_registers();
902+ let (view, doc) = current!(cx.editor);
903+ let registers = &mut cx.editor.registers;
745 if let Some(query) = registers.read('\\') {
904 if let Some(query) = registers.read('\\') {
746 let query = query.first().unwrap();
905 let query = query.first().unwrap();
747 let contents = doc.text().slice(..).to_string();
906 let contents = doc.text().slice(..).to_string();
@@ -750,16 +909,16 @@ pub fn search_next_impl(cx: &mut Context, extend: bool) {
750 }
909 }
751}
910}
752
911
753−pub fn search_next(cx: &mut Context) {
912+fn search_next(cx: &mut Context) {
754 search_next_impl(cx, false);
913 search_next_impl(cx, false);
755}
914}
756
915
757−pub fn extend_search_next(cx: &mut Context) {
916+fn extend_search_next(cx: &mut Context) {
758 search_next_impl(cx, true);
917 search_next_impl(cx, true);
759}
918}
760
919
761−pub fn search_selection(cx: &mut Context) {
920+fn search_selection(cx: &mut Context) {
762− let (view, doc) = cx.current();
921+ let (view, doc) = current!(cx.editor);
763 let contents = doc.text().slice(..);
922 let contents = doc.text().slice(..);
764 let query = doc.selection(view.id).primary().fragment(contents);
923 let query = doc.selection(view.id).primary().fragment(contents);
765 let regex = regex::escape(&query);
924 let regex = regex::escape(&query);
@@ -773,9 +932,9 @@ pub fn search_selection(cx: &mut Context) {
773
932
774//
933//
775
934
776−pub fn select_line(cx: &mut Context) {
935+fn select_line(cx: &mut Context) {
777 let count = cx.count();
936 let count = cx.count();
778− let (view, doc) = cx.current();
937+ let (view, doc) = current!(cx.editor);
779
938
780 let pos = doc.selection(view.id).primary();
939 let pos = doc.selection(view.id).primary();
781 let text = doc.text();
940 let text = doc.text();
@@ -788,9 +947,9 @@ pub fn select_line(cx: &mut Context) {
788
947
789 doc.set_selection(view.id, Selection::single(start, end));
948 doc.set_selection(view.id, Selection::single(start, end));
790}
949}
791−pub fn extend_line(cx: &mut Context) {
950+fn extend_line(cx: &mut Context) {
792 let count = cx.count();
951 let count = cx.count();
793− let (view, doc) = cx.current();
952+ let (view, doc) = current!(cx.editor);
794
953
795 let pos = doc.selection(view.id).primary();
954 let pos = doc.selection(view.id).primary();
796 let text = doc.text();
955 let text = doc.text();
@@ -830,9 +989,10 @@ fn delete_selection_impl(reg: &mut Register, doc: &mut Document, view_id: ViewId
830 doc.apply(&transaction, view_id);
989 doc.apply(&transaction, view_id);
831}
990}
832
991
833−pub fn delete_selection(cx: &mut Context) {
992+fn delete_selection(cx: &mut Context) {
834 let reg_name = cx.selected_register.name();
993 let reg_name = cx.selected_register.name();
835− let (view, doc, registers) = cx.current_with_registers();
994+ let (view, doc) = current!(cx.editor);
995+ let registers = &mut cx.editor.registers;
836 let reg = registers.get_or_insert(reg_name);
996 let reg = registers.get_or_insert(reg_name);
837 delete_selection_impl(reg, doc, view.id);
997 delete_selection_impl(reg, doc, view.id);
838
998
@@ -842,16 +1002,17 @@ pub fn delete_selection(cx: &mut Context) {
842 exit_select_mode(cx);
1002 exit_select_mode(cx);
843}
1003}
844
1004
845−pub fn change_selection(cx: &mut Context) {
1005+fn change_selection(cx: &mut Context) {
846 let reg_name = cx.selected_register.name();
1006 let reg_name = cx.selected_register.name();
847− let (view, doc, registers) = cx.current_with_registers();
1007+ let (view, doc) = current!(cx.editor);
1008+ let registers = &mut cx.editor.registers;
848 let reg = registers.get_or_insert(reg_name);
1009 let reg = registers.get_or_insert(reg_name);
849 delete_selection_impl(reg, doc, view.id);
1010 delete_selection_impl(reg, doc, view.id);
850 enter_insert_mode(doc);
1011 enter_insert_mode(doc);
851}
1012}
852
1013
853−pub fn collapse_selection(cx: &mut Context) {
1014+fn collapse_selection(cx: &mut Context) {
854− let (view, doc) = cx.current();
1015+ let (view, doc) = current!(cx.editor);
855 let selection = doc
1016 let selection = doc
856 .selection(view.id)
1017 .selection(view.id)
857 .transform(|range| Range::new(range.head, range.head));
1018 .transform(|range| Range::new(range.head, range.head));
@@ -859,8 +1020,8 @@ pub fn collapse_selection(cx: &mut Context) {
859 doc.set_selection(view.id, selection);
1020 doc.set_selection(view.id, selection);
860}
1021}
861
1022
862−pub fn flip_selections(cx: &mut Context) {
1023+fn flip_selections(cx: &mut Context) {
863− let (view, doc) = cx.current();
1024+ let (view, doc) = current!(cx.editor);
864 let selection = doc
1025 let selection = doc
865 .selection(view.id)
1026 .selection(view.id)
866 .transform(|range| Range::new(range.head, range.anchor));
1027 .transform(|range| Range::new(range.head, range.anchor));
@@ -873,8 +1034,8 @@ fn enter_insert_mode(doc: &mut Document) {
873}
1034}
874
1035
875// inserts at the start of each selection
1036// inserts at the start of each selection
876−pub fn insert_mode(cx: &mut Context) {
1037+fn insert_mode(cx: &mut Context) {
877− let (view, doc) = cx.current();
1038+ let (view, doc) = current!(cx.editor);
878 enter_insert_mode(doc);
1039 enter_insert_mode(doc);
879
1040
880 let selection = doc
1041 let selection = doc
@@ -884,8 +1045,8 @@ pub fn insert_mode(cx: &mut Context) {
884}
1045}
885
1046
886// inserts at the end of each selection
1047// inserts at the end of each selection
887−pub fn append_mode(cx: &mut Context) {
1048+fn append_mode(cx: &mut Context) {
888− let (view, doc) = cx.current();
1049+ let (view, doc) = current!(cx.editor);
889 enter_insert_mode(doc);
1050 enter_insert_mode(doc);
890 doc.restore_cursor = true;
1051 doc.restore_cursor = true;
891
1052
@@ -918,7 +1079,7 @@ mod cmd {
918 use ui::completers::{self, Completer};
1079 use ui::completers::{self, Completer};
919
1080
920 #[derive(Clone)]
1081 #[derive(Clone)]
921− pub struct Command {
1082+ pub struct TypableCommand {
922 pub name: &'static str,
1083 pub name: &'static str,
923 pub alias: Option<&'static str>,
1084 pub alias: Option<&'static str>,
924 pub doc: &'static str,
1085 pub doc: &'static str,
@@ -932,11 +1093,11 @@ mod cmd {
932 if editor.tree.views().count() == 1 && buffers_remaining_impl(editor) {
1093 if editor.tree.views().count() == 1 && buffers_remaining_impl(editor) {
933 return;
1094 return;
934 }
1095 }
935− editor.close(editor.view().id, /* close_buffer */ false);
1096+ editor.close(view!(editor).id, /* close_buffer */ false);
936 }
1097 }
937
1098
938 fn force_quit(editor: &mut Editor, args: &[&str], event: PromptEvent) {
1099 fn force_quit(editor: &mut Editor, args: &[&str], event: PromptEvent) {
939− editor.close(editor.view().id, /* close_buffer */ false);
1100+ editor.close(view!(editor).id, /* close_buffer */ false);
940 }
1101 }
941
1102
942 fn open(editor: &mut Editor, args: &[&str], event: PromptEvent) {
1103 fn open(editor: &mut Editor, args: &[&str], event: PromptEvent) {
@@ -978,7 +1139,7 @@ mod cmd {
978 }
1139 }
979
1140
980 fn write(editor: &mut Editor, args: &[&str], event: PromptEvent) {
1141 fn write(editor: &mut Editor, args: &[&str], event: PromptEvent) {
981− let (view, doc) = editor.current();
1142+ let (view, doc) = current!(editor);
982 if let Err(e) = write_impl(view, doc, args.first()) {
1143 if let Err(e) = write_impl(view, doc, args.first()) {
983 editor.set_error(e.to_string());
1144 editor.set_error(e.to_string());
984 };
1145 };
@@ -989,7 +1150,7 @@ mod cmd {
989 }
1150 }
990
1151
991 fn format(editor: &mut Editor, args: &[&str], event: PromptEvent) {
1152 fn format(editor: &mut Editor, args: &[&str], event: PromptEvent) {
992− let (view, doc) = editor.current();
1153+ let (view, doc) = current!(editor);
993
1154
994 doc.format(view.id)
1155 doc.format(view.id)
995 }
1156 }
@@ -999,7 +1160,7 @@ mod cmd {
999
1160
1000 // If no argument, report current indent style.
1161 // If no argument, report current indent style.
1001 if args.is_empty() {
1162 if args.is_empty() {
1002− let style = editor.current().1.indent_style;
1163+ let style = current!(editor).1.indent_style;
1003 editor.set_status(match style {
1164 editor.set_status(match style {
1004 Tabs => "tabs".into(),
1165 Tabs => "tabs".into(),
1005 Spaces(1) => "1 space".into(),
1166 Spaces(1) => "1 space".into(),
@@ -1022,7 +1183,7 @@ mod cmd {
1022 };
1183 };
1023
1184
1024 if let Some(s) = style {
1185 if let Some(s) = style {
1025− let (_, doc) = editor.current();
1186+ let doc = doc_mut!(editor);
1026 doc.indent_style = s;
1187 doc.indent_style = s;
1027 } else {
1188 } else {
1028 // Invalid argument.
1189 // Invalid argument.
@@ -1038,7 +1199,7 @@ mod cmd {
1038 return;
1199 return;
1039 }
1200 }
1040 };
1201 };
1041− let (view, doc) = editor.current();
1202+ let (view, doc) = current!(editor);
1042 doc.earlier(view.id, uk)
1203 doc.earlier(view.id, uk)
1043 }
1204 }
1044
1205
@@ -1050,12 +1211,12 @@ mod cmd {
1050 return;
1211 return;
1051 }
1212 }
1052 };
1213 };
1053− let (view, doc) = editor.current();
1214+ let (view, doc) = current!(editor);
1054 doc.later(view.id, uk)
1215 doc.later(view.id, uk)
1055 }
1216 }
1056
1217
1057 fn write_quit(editor: &mut Editor, args: &[&str], event: PromptEvent) {
1218 fn write_quit(editor: &mut Editor, args: &[&str], event: PromptEvent) {
1058− let (view, doc) = editor.current();
1219+ let (view, doc) = current!(editor);
1059 if let Err(e) = write_impl(view, doc, args.first()) {
1220 if let Err(e) = write_impl(view, doc, args.first()) {
1060 editor.set_error(e.to_string());
1221 editor.set_error(e.to_string());
1061 return;
1222 return;
@@ -1076,8 +1237,8 @@ mod cmd {
1076 .filter(|doc| doc.is_modified())
1237 .filter(|doc| doc.is_modified())
1077 .map(|doc| {
1238 .map(|doc| {
1078 doc.relative_path()
1239 doc.relative_path()
1079− .and_then(|path| path.to_str())
1240+ .map(|path| path.to_string_lossy().to_string())
1080− .unwrap_or("[scratch]")
1241+ .unwrap_or_else(|| "[scratch]".into())
1081 })
1242 })
1082 .collect();
1243 .collect();
1083 if !modified.is_empty() {
1244 if !modified.is_empty() {
@@ -1157,113 +1318,113 @@ mod cmd {
1157 quit_all_impl(editor, args, event, true)
1318 quit_all_impl(editor, args, event, true)
1158 }
1319 }
1159
1320
1160− pub const COMMAND_LIST: &[Command] = &[
1321+ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
1161− Command {
1322+ TypableCommand {
1162 name: "quit",
1323 name: "quit",
1163 alias: Some("q"),
1324 alias: Some("q"),
1164 doc: "Close the current view.",
1325 doc: "Close the current view.",
1165 fun: quit,
1326 fun: quit,
1166 completer: None,
1327 completer: None,
1167 },
1328 },
1168− Command {
1329+ TypableCommand {
1169 name: "quit!",
1330 name: "quit!",
1170 alias: Some("q!"),
1331 alias: Some("q!"),
1171 doc: "Close the current view.",
1332 doc: "Close the current view.",
1172 fun: force_quit,
1333 fun: force_quit,
1173 completer: None,
1334 completer: None,
1174 },
1335 },
1175− Command {
1336+ TypableCommand {
1176 name: "open",
1337 name: "open",
1177 alias: Some("o"),
1338 alias: Some("o"),
1178 doc: "Open a file from disk into the current view.",
1339 doc: "Open a file from disk into the current view.",
1179 fun: open,
1340 fun: open,
1180 completer: Some(completers::filename),
1341 completer: Some(completers::filename),
1181 },
1342 },
1182− Command {
1343+ TypableCommand {
1183 name: "write",
1344 name: "write",
1184 alias: Some("w"),
1345 alias: Some("w"),
1185 doc: "Write changes to disk. Accepts an optional path (:write some/path.txt)",
1346 doc: "Write changes to disk. Accepts an optional path (:write some/path.txt)",
1186 fun: write,
1347 fun: write,
1187 completer: Some(completers::filename),
1348 completer: Some(completers::filename),
1188 },
1349 },
1189− Command {
1350+ TypableCommand {
1190 name: "new",
1351 name: "new",
1191 alias: Some("n"),
1352 alias: Some("n"),
1192 doc: "Create a new scratch buffer.",
1353 doc: "Create a new scratch buffer.",
1193 fun: new_file,
1354 fun: new_file,
1194 completer: Some(completers::filename),
1355 completer: Some(completers::filename),
1195 },
1356 },
1196− Command {
1357+ TypableCommand {
1197 name: "format",
1358 name: "format",
1198 alias: Some("fmt"),
1359 alias: Some("fmt"),
1199 doc: "Format the file using a formatter.",
1360 doc: "Format the file using a formatter.",
1200 fun: format,
1361 fun: format,
1201 completer: None,
1362 completer: None,
1202 },
1363 },
1203− Command {
1364+ TypableCommand {
1204 name: "indent-style",
1365 name: "indent-style",
1205 alias: None,
1366 alias: None,
1206 doc: "Set the indentation style for editing. ('t' for tabs or 1-8 for number of spaces.)",
1367 doc: "Set the indentation style for editing. ('t' for tabs or 1-8 for number of spaces.)",
1207 fun: set_indent_style,
1368 fun: set_indent_style,
1208 completer: None,
1369 completer: None,
1209 },
1370 },
1210− Command {
1371+ TypableCommand {
1211 name: "earlier",
1372 name: "earlier",
1212 alias: Some("ear"),
1373 alias: Some("ear"),
1213 doc: "Jump back to an earlier point in edit history. Accepts a number of steps or a time span.",
1374 doc: "Jump back to an earlier point in edit history. Accepts a number of steps or a time span.",
1214 fun: earlier,
1375 fun: earlier,
1215 completer: None,
1376 completer: None,
1216 },
1377 },
1217− Command {
1378+ TypableCommand {
1218 name: "later",
1379 name: "later",
1219 alias: Some("lat"),
1380 alias: Some("lat"),
1220 doc: "Jump to a later point in edit history. Accepts a number of steps or a time span.",
1381 doc: "Jump to a later point in edit history. Accepts a number of steps or a time span.",
1221 fun: later,
1382 fun: later,
1222 completer: None,
1383 completer: None,
1223 },
1384 },
1224− Command {
1385+ TypableCommand {
1225 name: "write-quit",
1386 name: "write-quit",
1226 alias: Some("wq"),
1387 alias: Some("wq"),
1227 doc: "Writes changes to disk and closes the current view. Accepts an optional path (:wq some/path.txt)",
1388 doc: "Writes changes to disk and closes the current view. Accepts an optional path (:wq some/path.txt)",
1228 fun: write_quit,
1389 fun: write_quit,
1229 completer: Some(completers::filename),
1390 completer: Some(completers::filename),
1230 },
1391 },
1231− Command {
1392+ TypableCommand {
1232 name: "write-quit!",
1393 name: "write-quit!",
1233 alias: Some("wq!"),
1394 alias: Some("wq!"),
1234 doc: "Writes changes to disk and closes the current view forcefully. Accepts an optional path (:wq! some/path.txt)",
1395 doc: "Writes changes to disk and closes the current view forcefully. Accepts an optional path (:wq! some/path.txt)",
1235 fun: force_write_quit,
1396 fun: force_write_quit,
1236 completer: Some(completers::filename),
1397 completer: Some(completers::filename),
1237 },
1398 },
1238− Command {
1399+ TypableCommand {
1239 name: "write-all",
1400 name: "write-all",
1240 alias: Some("wa"),
1401 alias: Some("wa"),
1241 doc: "Writes changes from all views to disk.",
1402 doc: "Writes changes from all views to disk.",
1242 fun: write_all,
1403 fun: write_all,
1243 completer: None,
1404 completer: None,
1244 },
1405 },
1245− Command {
1406+ TypableCommand {
1246 name: "write-quit-all",
1407 name: "write-quit-all",
1247 alias: Some("wqa"),
1408 alias: Some("wqa"),
1248 doc: "Writes changes from all views to disk and close all views.",
1409 doc: "Writes changes from all views to disk and close all views.",
1249 fun: write_all_quit,
1410 fun: write_all_quit,
1250 completer: None,
1411 completer: None,
1251 },
1412 },
1252− Command {
1413+ TypableCommand {
1253 name: "write-quit-all!",
1414 name: "write-quit-all!",
1254 alias: Some("wqa!"),
1415 alias: Some("wqa!"),
1255 doc: "Writes changes from all views to disk and close all views forcefully (ignoring unsaved changes).",
1416 doc: "Writes changes from all views to disk and close all views forcefully (ignoring unsaved changes).",
1256 fun: force_write_all_quit,
1417 fun: force_write_all_quit,
1257 completer: None,
1418 completer: None,
1258 },
1419 },
1259− Command {
1420+ TypableCommand {
1260 name: "quit-all",
1421 name: "quit-all",
1261 alias: Some("qa"),
1422 alias: Some("qa"),
1262 doc: "Close all views.",
1423 doc: "Close all views.",
1263 fun: quit_all,
1424 fun: quit_all,
1264 completer: None,
1425 completer: None,
1265 },
1426 },
1266− Command {
1427+ TypableCommand {
1267 name: "quit-all!",
1428 name: "quit-all!",
1268 alias: Some("qa!"),
1429 alias: Some("qa!"),
1269 doc: "Close all views forcefully (ignoring unsaved changes).",
1430 doc: "Close all views forcefully (ignoring unsaved changes).",
@@ -1273,10 +1434,10 @@ mod cmd {
1273
1434
1274 ];
1435 ];
1275
1436
1276− pub static COMMANDS: Lazy<HashMap<&'static str, &'static Command>> = Lazy::new(|| {
1437+ pub static COMMANDS: Lazy<HashMap<&'static str, &'static TypableCommand>> = Lazy::new(|| {
1277 let mut map = HashMap::new();
1438 let mut map = HashMap::new();
1278
1439
1279− for cmd in COMMAND_LIST {
1440+ for cmd in TYPABLE_COMMAND_LIST {
1280 map.insert(cmd.name, cmd);
1441 map.insert(cmd.name, cmd);
1281 if let Some(alias) = cmd.alias {
1442 if let Some(alias) = cmd.alias {
1282 map.insert(alias, cmd);
1443 map.insert(alias, cmd);
@@ -1287,7 +1448,7 @@ mod cmd {
1287 });
1448 });
1288}
1449}
1289
1450
1290−pub fn command_mode(cx: &mut Context) {
1451+fn command_mode(cx: &mut Context) {
1291 // TODO: completion items should have a info section that would get displayed in
1452 // TODO: completion items should have a info section that would get displayed in
1292 // a popup above the prompt when items are tabbed over
1453 // a popup above the prompt when items are tabbed over
1293
1454
@@ -1302,7 +1463,7 @@ pub fn command_mode(cx: &mut Context) {
1302 if parts.len() <= 1 {
1463 if parts.len() <= 1 {
1303 use std::{borrow::Cow, ops::Range};
1464 use std::{borrow::Cow, ops::Range};
1304 let end = 0..;
1465 let end = 0..;
1305− cmd::COMMAND_LIST
1466+ cmd::TYPABLE_COMMAND_LIST
1306 .iter()
1467 .iter()
1307 .filter(|command| command.name.contains(input))
1468 .filter(|command| command.name.contains(input))
1308 .map(|command| (end.clone(), Cow::Borrowed(command.name)))
1469 .map(|command| (end.clone(), Cow::Borrowed(command.name)))
@@ -1310,7 +1471,7 @@ pub fn command_mode(cx: &mut Context) {
1310 } else {
1471 } else {
1311 let part = parts.last().unwrap();
1472 let part = parts.last().unwrap();
1312
1473
1313− if let Some(cmd::Command {
1474+ if let Some(cmd::TypableCommand {
1314 completer: Some(completer),
1475 completer: Some(completer),
1315 ..
1476 ..
1316 }) = cmd::COMMANDS.get(parts[0])
1477 }) = cmd::COMMANDS.get(parts[0])
@@ -1351,7 +1512,7 @@ pub fn command_mode(cx: &mut Context) {
1351 prompt.doc_fn = Box::new(|input: &str| {
1512 prompt.doc_fn = Box::new(|input: &str| {
1352 let part = input.split(' ').next().unwrap_or_default();
1513 let part = input.split(' ').next().unwrap_or_default();
1353
1514
1354− if let Some(cmd::Command { doc, .. }) = cmd::COMMANDS.get(part) {
1515+ if let Some(cmd::TypableCommand { doc, .. }) = cmd::COMMANDS.get(part) {
1355 return Some(doc);
1516 return Some(doc);
1356 }
1517 }
1357
1518
@@ -1361,21 +1522,21 @@ pub fn command_mode(cx: &mut Context) {
1361 cx.push_layer(Box::new(prompt));
1522 cx.push_layer(Box::new(prompt));
1362}
1523}
1363
1524
1364−pub fn file_picker(cx: &mut Context) {
1525+fn file_picker(cx: &mut Context) {
1365 let root = find_root(None).unwrap_or_else(|| PathBuf::from("./"));
1526 let root = find_root(None).unwrap_or_else(|| PathBuf::from("./"));
1366 let picker = ui::file_picker(root);
1527 let picker = ui::file_picker(root);
1367 cx.push_layer(Box::new(picker));
1528 cx.push_layer(Box::new(picker));
1368}
1529}
1369
1530
1370−pub fn buffer_picker(cx: &mut Context) {
1531+fn buffer_picker(cx: &mut Context) {
1371 use std::path::{Path, PathBuf};
1532 use std::path::{Path, PathBuf};
1372− let current = cx.editor.view().doc;
1533+ let current = view!(cx.editor).doc;
1373
1534
1374 let picker = Picker::new(
1535 let picker = Picker::new(
1375 cx.editor
1536 cx.editor
1376 .documents
1537 .documents
1377 .iter()
1538 .iter()
1378− .map(|(id, doc)| (id, doc.relative_path().map(Path::to_path_buf)))
1539+ .map(|(id, doc)| (id, doc.relative_path()))
1379 .collect(),
1540 .collect(),
1380 move |(id, path): &(DocumentId, Option<PathBuf>)| {
1541 move |(id, path): &(DocumentId, Option<PathBuf>)| {
1381 // format_fn
1542 // format_fn
@@ -1398,7 +1559,7 @@ pub fn buffer_picker(cx: &mut Context) {
1398 cx.push_layer(Box::new(picker));
1559 cx.push_layer(Box::new(picker));
1399}
1560}
1400
1561
1401−pub fn symbol_picker(cx: &mut Context) {
1562+fn symbol_picker(cx: &mut Context) {
1402 fn nested_to_flat(
1563 fn nested_to_flat(
1403 list: &mut Vec<lsp::SymbolInformation>,
1564 list: &mut Vec<lsp::SymbolInformation>,
1404 file: &lsp::TextDocumentIdentifier,
1565 file: &lsp::TextDocumentIdentifier,
@@ -1417,7 +1578,7 @@ pub fn symbol_picker(cx: &mut Context) {
1417 nested_to_flat(list, file, child);
1578 nested_to_flat(list, file, child);
1418 }
1579 }
1419 }
1580 }
1420− let (view, doc) = cx.current();
1581+ let (view, doc) = current!(cx.editor);
1421
1582
1422 let language_server = match doc.language_server() {
1583 let language_server = match doc.language_server() {
1423 Some(language_server) => language_server,
1584 Some(language_server) => language_server,
@@ -1438,7 +1599,7 @@ pub fn symbol_picker(cx: &mut Context) {
1438 let symbols = match symbols {
1599 let symbols = match symbols {
1439 lsp::DocumentSymbolResponse::Flat(symbols) => symbols,
1600 lsp::DocumentSymbolResponse::Flat(symbols) => symbols,
1440 lsp::DocumentSymbolResponse::Nested(symbols) => {
1601 lsp::DocumentSymbolResponse::Nested(symbols) => {
1441− let (_view, doc) = editor.current();
1602+ let (_view, doc) = current!(editor);
1442 let mut flat_symbols = Vec::new();
1603 let mut flat_symbols = Vec::new();
1443 for symbol in symbols {
1604 for symbol in symbols {
1444 nested_to_flat(&mut flat_symbols, &doc.identifier(), symbol)
1605 nested_to_flat(&mut flat_symbols, &doc.identifier(), symbol)
@@ -1452,7 +1613,7 @@ pub fn symbol_picker(cx: &mut Context) {
1452 |symbol| (&symbol.name).into(),
1613 |symbol| (&symbol.name).into(),
1453 move |editor: &mut Editor, symbol, _action| {
1614 move |editor: &mut Editor, symbol, _action| {
1454 push_jump(editor);
1615 push_jump(editor);
1455− let (view, doc) = editor.current();
1616+ let (view, doc) = current!(editor);
1456
1617
1457 if let Some(range) =
1618 if let Some(range) =
1458 lsp_range_to_range(doc.text(), symbol.location.range, offset_encoding)
1619 lsp_range_to_range(doc.text(), symbol.location.range, offset_encoding)
@@ -1469,15 +1630,15 @@ pub fn symbol_picker(cx: &mut Context) {
1469}
1630}
1470
1631
1471// I inserts at the first nonwhitespace character of each line with a selection
1632// I inserts at the first nonwhitespace character of each line with a selection
1472−pub fn prepend_to_line(cx: &mut Context) {
1633+fn prepend_to_line(cx: &mut Context) {
1473 move_first_nonwhitespace(cx);
1634 move_first_nonwhitespace(cx);
1474− let doc = cx.doc();
1635+ let doc = doc_mut!(cx.editor);
1475 enter_insert_mode(doc);
1636 enter_insert_mode(doc);
1476}
1637}
1477
1638
1478// A inserts at the end of each line with a selection
1639// A inserts at the end of each line with a selection
1479−pub fn append_to_line(cx: &mut Context) {
1640+fn append_to_line(cx: &mut Context) {
1480− let (view, doc) = cx.current();
1641+ let (view, doc) = current!(cx.editor);
1481 enter_insert_mode(doc);
1642 enter_insert_mode(doc);
1482
1643
1483 let selection = doc.selection(view.id).transform(|range| {
1644 let selection = doc.selection(view.id).transform(|range| {
@@ -1497,73 +1658,79 @@ enum Open {
1497
1658
1498fn open(cx: &mut Context, open: Open) {
1659fn open(cx: &mut Context, open: Open) {
1499 let count = cx.count();
1660 let count = cx.count();
1500− let (view, doc) = cx.current();
1661+ let (view, doc) = current!(cx.editor);
1501 enter_insert_mode(doc);
1662 enter_insert_mode(doc);
1502
1663
1503 let text = doc.text().slice(..);
1664 let text = doc.text().slice(..);
1665+ let contents = doc.text();
1504 let selection = doc.selection(view.id);
1666 let selection = doc.selection(view.id);
1505
1667
1506 let mut ranges = SmallVec::with_capacity(selection.len());
1668 let mut ranges = SmallVec::with_capacity(selection.len());
1669+ let mut offs = 0;
1507
1670
1508− let changes: Vec<Change> = selection
1509− .iter()
1510− .map(|range| {
1511− let line = text.char_to_line(range.head);
1512−
1513− let line = match open {
1671+ let line = match open {
1514− // adjust position to the end of the line (next line - 1)
1672+ // adjust position to the end of the line (next line - 1)
1515− Open::Below => line + 1,
1673+ Open::Below => line + 1,
1516− // adjust position to the end of the previous line (current line - 1)
1674+ // adjust position to the end of the previous line (current line - 1)
1517− Open::Above => line,
1675+ Open::Above => line,
1518− };
1676+ };
1519−
1520− let index = doc.text().line_to_char(line).saturating_sub(1);
1521
1677
1522− // TODO: share logic with insert_newline for indentation
1678+ let mut transaction = Transaction::change_by_selection(contents, selection, |range| {
1523− let indent_level = indent::suggested_indent_for_pos(
1679+ let line = text.char_to_line(range.head);
1524− doc.language_config(),
1525− doc.syntax(),
1526− text,
1527− index,
1528− true,
1529− );
1530− let indent = doc.indent_unit().repeat(indent_level);
1531− let mut text = String::with_capacity(1 + indent.len());
1532− text.push_str(doc.line_ending().as_str());
1533− text.push_str(&indent);
1534− let text = text.repeat(count);
1535
1680
1536− // calculate new selection range
1681+ // insert newlines after this index for both Above and Below variants
1537− let pos = index + text.chars().count();
1682+ let linend_index = doc.text().line_to_char(line).saturating_sub(1);
1538− ranges.push(Range::new(pos, pos));
1539
1683
1540− (index, index, Some(text.into()))
1684+ // TODO: share logic with insert_newline for indentation
1541− })
1685+ let indent_level = indent::suggested_indent_for_pos(
1542− .collect();
1686+ doc.language_config(),
1687+ doc.syntax(),
1688+ text,
1689+ linend_index,
1690+ true,
1691+ );
1692+ let indent = doc.indent_unit().repeat(indent_level);
1693+ let indent_len = indent.len();
1694+ let mut text = String::with_capacity(1 + indent_len);
1695+ text.push('\n');
1696+ text.push_str(&indent);
1697+ let text = text.repeat(count);
1698+
1699+ // calculate new selection ranges
1700+ let pos = if line == 0 {
1701+ 0 // Required since text will have a min len of 1 (\n)
1702+ } else {
1703+ offs + linend_index + 1
1704+ };
1705+ for i in 0..count {
1706+ // pos -> beginning of reference line,
1707+ // + (i * (1+indent_len)) -> beginning of i'th line from pos
1708+ // + indent_len -> -> indent for i'th line
1709+ ranges.push(Range::point(pos + (i * (1 + indent_len)) + indent_len));
1710+ }
1543
1711
1544− // TODO: count actually inserts "n" new lines and starts editing on all of them.
1712+ offs += text.chars().count();
1545− // TODO: append "count" newlines and modify cursors to those lines
1546
1713
1547− let selection = Selection::new(ranges, 0);
1714+ (linend_index, linend_index, Some(text.into()))
1715+ });
1548
1716
1549− let transaction =
1717+ transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index()));
1550− Transaction::change(doc.text(), changes.into_iter()).with_selection(selection);
1551
1718
1552 doc.apply(&transaction, view.id);
1719 doc.apply(&transaction, view.id);
1553}
1720}
1554
1721
1555// o inserts a new line after each line with a selection
1722// o inserts a new line after each line with a selection
1556−pub fn open_below(cx: &mut Context) {
1723+fn open_below(cx: &mut Context) {
1557 open(cx, Open::Below)
1724 open(cx, Open::Below)
1558}
1725}
1559
1726
1560// O inserts a new line before each line with a selection
1727// O inserts a new line before each line with a selection
1561−pub fn open_above(cx: &mut Context) {
1728+fn open_above(cx: &mut Context) {
1562 open(cx, Open::Above)
1729 open(cx, Open::Above)
1563}
1730}
1564
1731
1565−pub fn normal_mode(cx: &mut Context) {
1732+fn normal_mode(cx: &mut Context) {
1566− let (view, doc) = cx.current();
1733+ let (view, doc) = current!(cx.editor);
1567
1734
1568 doc.mode = Mode::Normal;
1735 doc.mode = Mode::Normal;
1569
1736
@@ -1586,13 +1753,13 @@ pub fn normal_mode(cx: &mut Context) {
1586
1753
1587// Store a jump on the jumplist.
1754// Store a jump on the jumplist.
1588fn push_jump(editor: &mut Editor) {
1755fn push_jump(editor: &mut Editor) {
1589− let (view, doc) = editor.current();
1756+ let (view, doc) = current!(editor);
1590 let jump = (doc.id(), doc.selection(view.id).clone());
1757 let jump = (doc.id(), doc.selection(view.id).clone());
1591 view.jumps.push(jump);
1758 view.jumps.push(jump);
1592}
1759}
1593
1760
1594fn switch_to_last_accessed_file(cx: &mut Context) {
1761fn switch_to_last_accessed_file(cx: &mut Context) {
1595− let alternate_file = cx.view().last_accessed_doc;
1762+ let alternate_file = view!(cx.editor).last_accessed_doc;
1596 if let Some(alt) = alternate_file {
1763 if let Some(alt) = alternate_file {
1597 cx.editor.switch(alt, Action::Replace);
1764 cx.editor.switch(alt, Action::Replace);
1598 } else {
1765 } else {
@@ -1600,11 +1767,11 @@ fn switch_to_last_accessed_file(cx: &mut Context) {
1600 }
1767 }
1601}
1768}
1602
1769
1603−pub fn goto_mode(cx: &mut Context) {
1770+fn goto_mode(cx: &mut Context) {
1604 if let Some(count) = cx.count {
1771 if let Some(count) = cx.count {
1605 push_jump(cx.editor);
1772 push_jump(cx.editor);
1606
1773
1607− let (view, doc) = cx.current();
1774+ let (view, doc) = current!(cx.editor);
1608 let line_idx = std::cmp::min(count.get() - 1, doc.text().len_lines().saturating_sub(2));
1775 let line_idx = std::cmp::min(count.get() - 1, doc.text().len_lines().saturating_sub(2));
1609 let pos = doc.text().line_to_char(line_idx);
1776 let pos = doc.text().line_to_char(line_idx);
1610 doc.set_selection(view.id, Selection::point(pos));
1777 doc.set_selection(view.id, Selection::point(pos));
@@ -1618,7 +1785,8 @@ pub fn goto_mode(cx: &mut Context) {
1618 } = event
1785 } = event
1619 {
1786 {
1620 // TODO: temporarily show GOTO in the mode list
1787 // TODO: temporarily show GOTO in the mode list
1788+ let doc = doc_mut!(cx.editor);
1621− match (cx.doc().mode, ch) {
1789+ match (doc.mode, ch) {
1622 (_, 'g') => move_file_start(cx),
1790 (_, 'g') => move_file_start(cx),
1623 (_, 'e') => move_file_end(cx),
1791 (_, 'e') => move_file_end(cx),
1624 (_, 'a') => switch_to_last_accessed_file(cx),
1792 (_, 'a') => switch_to_last_accessed_file(cx),
@@ -1634,7 +1802,7 @@ pub fn goto_mode(cx: &mut Context) {
1634 (Mode::Select, 's') => extend_first_nonwhitespace(cx),
1802 (Mode::Select, 's') => extend_first_nonwhitespace(cx),
1635
1803
1636 (_, 't') | (_, 'm') | (_, 'b') => {
1804 (_, 't') | (_, 'm') | (_, 'b') => {
1637− let (view, doc) = cx.current();
1805+ let (view, doc) = current!(cx.editor);
1638
1806
1639 let pos = doc.selection(view.id).cursor();
1807 let pos = doc.selection(view.id).cursor();
1640 let line = doc.text().char_to_line(pos);
1808 let line = doc.text().char_to_line(pos);
@@ -1661,12 +1829,12 @@ pub fn goto_mode(cx: &mut Context) {
1661 })
1829 })
1662}
1830}
1663
1831
1664−pub fn select_mode(cx: &mut Context) {
1832+fn select_mode(cx: &mut Context) {
1665− cx.doc().mode = Mode::Select;
1833+ doc_mut!(cx.editor).mode = Mode::Select;
1666}
1834}
1667
1835
1668−pub fn exit_select_mode(cx: &mut Context) {
1836+fn exit_select_mode(cx: &mut Context) {
1669− cx.doc().mode = Mode::Normal;
1837+ doc_mut!(cx.editor).mode = Mode::Normal;
1670}
1838}
1671
1839
1672fn goto_impl(
1840fn goto_impl(
@@ -1688,7 +1856,7 @@ fn goto_impl(
1688 let id = editor
1856 let id = editor
1689 .open(PathBuf::from(location.uri.path()), action)
1857 .open(PathBuf::from(location.uri.path()), action)
1690 .expect("editor.open failed");
1858 .expect("editor.open failed");
1691− let (view, doc) = editor.current();
1859+ let (view, doc) = current!(editor);
1692 let definition_pos = location.range.start;
1860 let definition_pos = location.range.start;
1693 // TODO: convert inside server
1861 // TODO: convert inside server
1694 let new_pos =
1862 let new_pos =
@@ -1725,8 +1893,8 @@ fn goto_impl(
1725 }
1893 }
1726}
1894}
1727
1895
1728−pub fn goto_definition(cx: &mut Context) {
1896+fn goto_definition(cx: &mut Context) {
1729− let (view, doc) = cx.current();
1897+ let (view, doc) = current!(cx.editor);
1730 let language_server = match doc.language_server() {
1898 let language_server = match doc.language_server() {
1731 Some(language_server) => language_server,
1899 Some(language_server) => language_server,
1732 None => return,
1900 None => return,
@@ -1737,7 +1905,7 @@ pub fn goto_definition(cx: &mut Context) {
1737 let pos = pos_to_lsp_pos(doc.text(), doc.selection(view.id).cursor(), offset_encoding);
1905 let pos = pos_to_lsp_pos(doc.text(), doc.selection(view.id).cursor(), offset_encoding);
1738
1906
1739 // TODO: handle fails
1907 // TODO: handle fails
1740− let future = language_server.goto_definition(doc.identifier(), pos);
1908+ let future = language_server.goto_definition(doc.identifier(), pos, None);
1741
1909
1742 cx.callback(
1910 cx.callback(
1743 future,
1911 future,
@@ -1762,8 +1930,8 @@ pub fn goto_definition(cx: &mut Context) {
1762 );
1930 );
1763}
1931}
1764
1932
1765−pub fn goto_type_definition(cx: &mut Context) {
1933+fn goto_type_definition(cx: &mut Context) {
1766− let (view, doc) = cx.current();
1934+ let (view, doc) = current!(cx.editor);
1767 let language_server = match doc.language_server() {
1935 let language_server = match doc.language_server() {
1768 Some(language_server) => language_server,
1936 Some(language_server) => language_server,
1769 None => return,
1937 None => return,
@@ -1774,7 +1942,7 @@ pub fn goto_type_definition(cx: &mut Context) {
1774 let pos = pos_to_lsp_pos(doc.text(), doc.selection(view.id).cursor(), offset_encoding);
1942 let pos = pos_to_lsp_pos(doc.text(), doc.selection(view.id).cursor(), offset_encoding);
1775
1943
1776 // TODO: handle fails
1944 // TODO: handle fails
1777− let future = language_server.goto_type_definition(doc.identifier(), pos);
1945+ let future = language_server.goto_type_definition(doc.identifier(), pos, None);
1778
1946
1779 cx.callback(
1947 cx.callback(
1780 future,
1948 future,
@@ -1799,8 +1967,8 @@ pub fn goto_type_definition(cx: &mut Context) {
1799 );
1967 );
1800}
1968}
1801
1969
1802−pub fn goto_implementation(cx: &mut Context) {
1970+fn goto_implementation(cx: &mut Context) {
1803− let (view, doc) = cx.current();
1971+ let (view, doc) = current!(cx.editor);
1804 let language_server = match doc.language_server() {
1972 let language_server = match doc.language_server() {
1805 Some(language_server) => language_server,
1973 Some(language_server) => language_server,
1806 None => return,
1974 None => return,
@@ -1811,7 +1979,7 @@ pub fn goto_implementation(cx: &mut Context) {
1811 let pos = pos_to_lsp_pos(doc.text(), doc.selection(view.id).cursor(), offset_encoding);
1979 let pos = pos_to_lsp_pos(doc.text(), doc.selection(view.id).cursor(), offset_encoding);
1812
1980
1813 // TODO: handle fails
1981 // TODO: handle fails
1814− let future = language_server.goto_implementation(doc.identifier(), pos);
1982+ let future = language_server.goto_implementation(doc.identifier(), pos, None);
1815
1983
1816 cx.callback(
1984 cx.callback(
1817 future,
1985 future,
@@ -1836,8 +2004,8 @@ pub fn goto_implementation(cx: &mut Context) {
1836 );
2004 );
1837}
2005}
1838
2006
1839−pub fn goto_reference(cx: &mut Context) {
2007+fn goto_reference(cx: &mut Context) {
1840− let (view, doc) = cx.current();
2008+ let (view, doc) = current!(cx.editor);
1841 let language_server = match doc.language_server() {
2009 let language_server = match doc.language_server() {
1842 Some(language_server) => language_server,
2010 Some(language_server) => language_server,
1843 None => return,
2011 None => return,
@@ -1848,7 +2016,7 @@ pub fn goto_reference(cx: &mut Context) {
1848 let pos = pos_to_lsp_pos(doc.text(), doc.selection(view.id).cursor(), offset_encoding);
2016 let pos = pos_to_lsp_pos(doc.text(), doc.selection(view.id).cursor(), offset_encoding);
1849
2017
1850 // TODO: handle fails
2018 // TODO: handle fails
1851− let future = language_server.goto_reference(doc.identifier(), pos);
2019+ let future = language_server.goto_reference(doc.identifier(), pos, None);
1852
2020
1853 cx.callback(
2021 cx.callback(
1854 future,
2022 future,
@@ -1868,15 +2036,15 @@ pub fn goto_reference(cx: &mut Context) {
1868fn goto_pos(editor: &mut Editor, pos: usize) {
2036fn goto_pos(editor: &mut Editor, pos: usize) {
1869 push_jump(editor);
2037 push_jump(editor);
1870
2038
1871− let (view, doc) = editor.current();
2039+ let (view, doc) = current!(editor);
1872
2040
1873 doc.set_selection(view.id, Selection::point(pos));
2041 doc.set_selection(view.id, Selection::point(pos));
1874 align_view(doc, view, Align::Center);
2042 align_view(doc, view, Align::Center);
1875}
2043}
1876
2044
1877−pub fn goto_first_diag(cx: &mut Context) {
2045+fn goto_first_diag(cx: &mut Context) {
1878 let editor = &mut cx.editor;
2046 let editor = &mut cx.editor;
1879− let (view, doc) = editor.current();
2047+ let (view, doc) = current!(editor);
1880
2048
1881 let cursor_pos = doc.selection(view.id).cursor();
2049 let cursor_pos = doc.selection(view.id).cursor();
1882 let diag = if let Some(diag) = doc.diagnostics().first() {
2050 let diag = if let Some(diag) = doc.diagnostics().first() {
@@ -1888,9 +2056,9 @@ pub fn goto_first_diag(cx: &mut Context) {
1888 goto_pos(editor, diag);
2056 goto_pos(editor, diag);
1889}
2057}
1890
2058
1891−pub fn goto_last_diag(cx: &mut Context) {
2059+fn goto_last_diag(cx: &mut Context) {
1892 let editor = &mut cx.editor;
2060 let editor = &mut cx.editor;
1893− let (view, doc) = editor.current();
2061+ let (view, doc) = current!(editor);
1894
2062
1895 let cursor_pos = doc.selection(view.id).cursor();
2063 let cursor_pos = doc.selection(view.id).cursor();
1896 let diag = if let Some(diag) = doc.diagnostics().last() {
2064 let diag = if let Some(diag) = doc.diagnostics().last() {
@@ -1902,9 +2070,9 @@ pub fn goto_last_diag(cx: &mut Context) {
1902 goto_pos(editor, diag);
2070 goto_pos(editor, diag);
1903}
2071}
1904
2072
1905−pub fn goto_next_diag(cx: &mut Context) {
2073+fn goto_next_diag(cx: &mut Context) {
1906 let editor = &mut cx.editor;
2074 let editor = &mut cx.editor;
1907− let (view, doc) = editor.current();
2075+ let (view, doc) = current!(editor);
1908
2076
1909 let cursor_pos = doc.selection(view.id).cursor();
2077 let cursor_pos = doc.selection(view.id).cursor();
1910 let diag = if let Some(diag) = doc
2078 let diag = if let Some(diag) = doc
@@ -1923,9 +2091,9 @@ pub fn goto_next_diag(cx: &mut Context) {
1923 goto_pos(editor, diag);
2091 goto_pos(editor, diag);
1924}
2092}
1925
2093
1926−pub fn goto_prev_diag(cx: &mut Context) {
2094+fn goto_prev_diag(cx: &mut Context) {
1927 let editor = &mut cx.editor;
2095 let editor = &mut cx.editor;
1928− let (view, doc) = editor.current();
2096+ let (view, doc) = current!(editor);
1929
2097
1930 let cursor_pos = doc.selection(view.id).cursor();
2098 let cursor_pos = doc.selection(view.id).cursor();
1931 let diag = if let Some(diag) = doc
2099 let diag = if let Some(diag) = doc
@@ -1945,8 +2113,8 @@ pub fn goto_prev_diag(cx: &mut Context) {
1945 goto_pos(editor, diag);
2113 goto_pos(editor, diag);
1946}
2114}
1947
2115
1948−pub fn signature_help(cx: &mut Context) {
2116+fn signature_help(cx: &mut Context) {
1949− let (view, doc) = cx.current();
2117+ let (view, doc) = current!(cx.editor);
1950
2118
1951 let language_server = match doc.language_server() {
2119 let language_server = match doc.language_server() {
1952 Some(language_server) => language_server,
2120 Some(language_server) => language_server,
@@ -1960,7 +2128,7 @@ pub fn signature_help(cx: &mut Context) {
1960 );
2128 );
1961
2129
1962 // TODO: handle fails
2130 // TODO: handle fails
1963− let future = language_server.text_document_signature_help(doc.identifier(), pos);
2131+ let future = language_server.text_document_signature_help(doc.identifier(), pos, None);
1964
2132
1965 cx.callback(
2133 cx.callback(
1966 future,
2134 future,
@@ -1992,7 +2160,7 @@ pub mod insert {
1992
2160
1993 fn completion(cx: &mut Context, ch: char) {
2161 fn completion(cx: &mut Context, ch: char) {
1994 // if ch matches completion char, trigger completion
2162 // if ch matches completion char, trigger completion
1995− let doc = cx.doc();
2163+ let doc = doc_mut!(cx.editor);
1996 let language_server = match doc.language_server() {
2164 let language_server = match doc.language_server() {
1997 Some(language_server) => language_server,
2165 Some(language_server) => language_server,
1998 None => return,
2166 None => return,
@@ -2020,7 +2188,7 @@ pub mod insert {
2020
2188
2021 fn signature_help(cx: &mut Context, ch: char) {
2189 fn signature_help(cx: &mut Context, ch: char) {
2022 // if ch matches signature_help char, trigger
2190 // if ch matches signature_help char, trigger
2023− let doc = cx.doc();
2191+ let doc = doc_mut!(cx.editor);
2024 let language_server = match doc.language_server() {
2192 let language_server = match doc.language_server() {
2025 Some(language_server) => language_server,
2193 Some(language_server) => language_server,
2026 None => return,
2194 None => return,
@@ -2075,7 +2243,7 @@ pub mod insert {
2075 const POST_HOOKS: &[PostHook] = &[completion, signature_help];
2243 const POST_HOOKS: &[PostHook] = &[completion, signature_help];
2076
2244
2077 pub fn insert_char(cx: &mut Context, c: char) {
2245 pub fn insert_char(cx: &mut Context, c: char) {
2078− let (view, doc) = cx.current();
2246+ let (view, doc) = current!(cx.editor);
2079
2247
2080 let text = doc.text();
2248 let text = doc.text();
2081 let selection = doc.selection(view.id);
2249 let selection = doc.selection(view.id);
@@ -2097,7 +2265,7 @@ pub mod insert {
2097 }
2265 }
2098
2266
2099 pub fn insert_tab(cx: &mut Context) {
2267 pub fn insert_tab(cx: &mut Context) {
2100− let (view, doc) = cx.current();
2268+ let (view, doc) = current!(cx.editor);
2101 // TODO: round out to nearest indentation level (for example a line with 3 spaces should
2269 // TODO: round out to nearest indentation level (for example a line with 3 spaces should
2102 // indent by one to reach 4 spaces).
2270 // indent by one to reach 4 spaces).
2103
2271
@@ -2107,7 +2275,7 @@ pub mod insert {
2107 }
2275 }
2108
2276
2109 pub fn insert_newline(cx: &mut Context) {
2277 pub fn insert_newline(cx: &mut Context) {
2110− let (view, doc) = cx.current();
2278+ let (view, doc) = current!(cx.editor);
2111 let text = doc.text().slice(..);
2279 let text = doc.text().slice(..);
2112
2280
2113 let contents = doc.text();
2281 let contents = doc.text();
@@ -2176,7 +2344,7 @@ pub mod insert {
2176 // TODO: handle indent-aware delete
2344 // TODO: handle indent-aware delete
2177 pub fn delete_char_backward(cx: &mut Context) {
2345 pub fn delete_char_backward(cx: &mut Context) {
2178 let count = cx.count();
2346 let count = cx.count();
2179− let (view, doc) = cx.current();
2347+ let (view, doc) = current!(cx.editor);
2180 let text = doc.text().slice(..);
2348 let text = doc.text().slice(..);
2181 let transaction =
2349 let transaction =
2182 Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
2350 Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
@@ -2191,7 +2359,7 @@ pub mod insert {
2191
2359
2192 pub fn delete_char_forward(cx: &mut Context) {
2360 pub fn delete_char_forward(cx: &mut Context) {
2193 let count = cx.count();
2361 let count = cx.count();
2194− let (view, doc) = cx.current();
2362+ let (view, doc) = current!(cx.editor);
2195 let text = doc.text().slice(..);
2363 let text = doc.text().slice(..);
2196 let transaction =
2364 let transaction =
2197 Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
2365 Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
@@ -2206,7 +2374,7 @@ pub mod insert {
2206
2374
2207 pub fn delete_word_backward(cx: &mut Context) {
2375 pub fn delete_word_backward(cx: &mut Context) {
2208 let count = cx.count();
2376 let count = cx.count();
2209− let (view, doc) = cx.current();
2377+ let (view, doc) = current!(cx.editor);
2210 let text = doc.text().slice(..);
2378 let text = doc.text().slice(..);
2211 let selection = doc
2379 let selection = doc
2212 .selection(view.id)
2380 .selection(view.id)
@@ -2221,21 +2389,23 @@ pub mod insert {
2221// TODO: each command could simply return a Option<transaction>, then the higher level handles
2389// TODO: each command could simply return a Option<transaction>, then the higher level handles
2222// storing it?
2390// storing it?
2223
2391
2224−pub fn undo(cx: &mut Context) {
2392+fn undo(cx: &mut Context) {
2225− let view_id = cx.view().id;
2393+ let (view, doc) = current!(cx.editor);
2394+ let view_id = view.id;
2226− cx.doc().undo(view_id);
2395+ doc.undo(view_id);
2227}
2396}
2228
2397
2229−pub fn redo(cx: &mut Context) {
2398+fn redo(cx: &mut Context) {
2230− let view_id = cx.view().id;
2399+ let (view, doc) = current!(cx.editor);
2400+ let view_id = view.id;
2231− cx.doc().redo(view_id);
2401+ doc.redo(view_id);
2232}
2402}
2233
2403
2234// Yank / Paste
2404// Yank / Paste
2235
2405
2236−pub fn yank(cx: &mut Context) {
2406+fn yank(cx: &mut Context) {
2237 // TODO: should selections be made end inclusive?
2407 // TODO: should selections be made end inclusive?
2238− let (view, doc) = cx.current();
2408+ let (view, doc) = current!(cx.editor);
2239 let values: Vec<String> = doc
2409 let values: Vec<String> = doc
2240 .selection(view.id)
2410 .selection(view.id)
2241 .fragments(doc.text().slice(..))
2411 .fragments(doc.text().slice(..))
@@ -2300,9 +2470,10 @@ fn paste_impl(
2300 Some(transaction)
2470 Some(transaction)
2301}
2471}
2302
2472
2303−pub fn replace_with_yanked(cx: &mut Context) {
2473+fn replace_with_yanked(cx: &mut Context) {
2304 let reg_name = cx.selected_register.name();
2474 let reg_name = cx.selected_register.name();
2305− let (view, doc, registers) = cx.current_with_registers();
2475+ let (view, doc) = current!(cx.editor);
2476+ let registers = &mut cx.editor.registers;
2306
2477
2307 if let Some(values) = registers.read(reg_name) {
2478 if let Some(values) = registers.read(reg_name) {
2308 if let Some(yank) = values.first() {
2479 if let Some(yank) = values.first() {
@@ -2329,9 +2500,10 @@ pub fn replace_with_yanked(cx: &mut Context) {
2329// replace => replace
2500// replace => replace
2330// default insert
2501// default insert
2331
2502
2332−pub fn paste_after(cx: &mut Context) {
2503+fn paste_after(cx: &mut Context) {
2333 let reg_name = cx.selected_register.name();
2504 let reg_name = cx.selected_register.name();
2334− let (view, doc, registers) = cx.current_with_registers();
2505+ let (view, doc) = current!(cx.editor);
2506+ let registers = &mut cx.editor.registers;
2335
2507
2336 if let Some(transaction) = registers
2508 if let Some(transaction) = registers
2337 .read(reg_name)
2509 .read(reg_name)
@@ -2342,9 +2514,10 @@ pub fn paste_after(cx: &mut Context) {
2342 }
2514 }
2343}
2515}
2344
2516
2345−pub fn paste_before(cx: &mut Context) {
2517+fn paste_before(cx: &mut Context) {
2346 let reg_name = cx.selected_register.name();
2518 let reg_name = cx.selected_register.name();
2347− let (view, doc, registers) = cx.current_with_registers();
2519+ let (view, doc) = current!(cx.editor);
2520+ let registers = &mut cx.editor.registers;
2348
2521
2349 if let Some(transaction) = registers
2522 if let Some(transaction) = registers
2350 .read(reg_name)
2523 .read(reg_name)
@@ -2372,9 +2545,9 @@ fn get_lines(doc: &Document, view_id: ViewId) -> Vec<usize> {
2372 lines
2545 lines
2373}
2546}
2374
2547
2375−pub fn indent(cx: &mut Context) {
2548+fn indent(cx: &mut Context) {
2376 let count = cx.count();
2549 let count = cx.count();
2377− let (view, doc) = cx.current();
2550+ let (view, doc) = current!(cx.editor);
2378 let lines = get_lines(doc, view.id);
2551 let lines = get_lines(doc, view.id);
2379
2552
2380 // Indent by one level
2553 // Indent by one level
@@ -2391,9 +2564,9 @@ pub fn indent(cx: &mut Context) {
2391 doc.append_changes_to_history(view.id);
2564 doc.append_changes_to_history(view.id);
2392}
2565}
2393
2566
2394−pub fn unindent(cx: &mut Context) {
2567+fn unindent(cx: &mut Context) {
2395 let count = cx.count();
2568 let count = cx.count();
2396− let (view, doc) = cx.current();
2569+ let (view, doc) = current!(cx.editor);
2397 let lines = get_lines(doc, view.id);
2570 let lines = get_lines(doc, view.id);
2398 let mut changes = Vec::with_capacity(lines.len());
2571 let mut changes = Vec::with_capacity(lines.len());
2399 let tab_width = doc.tab_width();
2572 let tab_width = doc.tab_width();
@@ -2431,8 +2604,8 @@ pub fn unindent(cx: &mut Context) {
2431 doc.append_changes_to_history(view.id);
2604 doc.append_changes_to_history(view.id);
2432}
2605}
2433
2606
2434−pub fn format_selections(cx: &mut Context) {
2607+fn format_selections(cx: &mut Context) {
2435− let (view, doc) = cx.current();
2608+ let (view, doc) = current!(cx.editor);
2436
2609
2437 // via lsp if available
2610 // via lsp if available
2438 // else via tree-sitter indentation calculations
2611 // else via tree-sitter indentation calculations
@@ -2477,9 +2650,9 @@ pub fn format_selections(cx: &mut Context) {
2477 doc.append_changes_to_history(view.id);
2650 doc.append_changes_to_history(view.id);
2478}
2651}
2479
2652
2480−pub fn join_selections(cx: &mut Context) {
2653+fn join_selections(cx: &mut Context) {
2481 use movement::skip_while;
2654 use movement::skip_while;
2482− let (view, doc) = cx.current();
2655+ let (view, doc) = current!(cx.editor);
2483 let text = doc.text();
2656 let text = doc.text();
2484 let slice = doc.text().slice(..);
2657 let slice = doc.text().slice(..);
2485
2658
@@ -2521,7 +2694,7 @@ pub fn join_selections(cx: &mut Context) {
2521 doc.append_changes_to_history(view.id);
2694 doc.append_changes_to_history(view.id);
2522}
2695}
2523
2696
2524−pub fn keep_selections(cx: &mut Context) {
2697+fn keep_selections(cx: &mut Context) {
2525 // keep selections matching regex
2698 // keep selections matching regex
2526 let prompt = ui::regex_prompt(cx, "keep:".to_string(), move |view, doc, _, regex| {
2699 let prompt = ui::regex_prompt(cx, "keep:".to_string(), move |view, doc, _, regex| {
2527 let text = doc.text().slice(..);
2700 let text = doc.text().slice(..);
@@ -2534,8 +2707,8 @@ pub fn keep_selections(cx: &mut Context) {
2534 cx.push_layer(Box::new(prompt));
2707 cx.push_layer(Box::new(prompt));
2535}
2708}
2536
2709
2537−pub fn keep_primary_selection(cx: &mut Context) {
2710+fn keep_primary_selection(cx: &mut Context) {
2538− let (view, doc) = cx.current();
2711+ let (view, doc) = current!(cx.editor);
2539
2712
2540 let range = doc.selection(view.id).primary();
2713 let range = doc.selection(view.id).primary();
2541 let selection = Selection::single(range.anchor, range.head);
2714 let selection = Selection::single(range.anchor, range.head);
@@ -2544,14 +2717,14 @@ pub fn keep_primary_selection(cx: &mut Context) {
2544
2717
2545//
2718//
2546
2719
2547−pub fn save(cx: &mut Context) {
2720+fn save(cx: &mut Context) {
2548 // Spawns an async task to actually do the saving. This way we prevent blocking.
2721 // Spawns an async task to actually do the saving. This way we prevent blocking.
2549
2722
2550 // TODO: handle save errors somehow?
2723 // TODO: handle save errors somehow?
2551− tokio::spawn(cx.doc().save());
2724+ tokio::spawn(doc_mut!(cx.editor).save());
2552}
2725}
2553
2726
2554−pub fn completion(cx: &mut Context) {
2727+fn completion(cx: &mut Context) {
2555 // trigger on trigger char, or if user calls it
2728 // trigger on trigger char, or if user calls it
2556 // (or on word char typing??)
2729 // (or on word char typing??)
2557 // after it's triggered, if response marked is_incomplete, update on every subsequent keypress
2730 // after it's triggered, if response marked is_incomplete, update on every subsequent keypress
@@ -2588,7 +2761,7 @@ pub fn completion(cx: &mut Context) {
2588 // The prefix still has to satisfy `company-minimum-prefix-length' before that
2761 // The prefix still has to satisfy `company-minimum-prefix-length' before that
2589 // happens. The value of nil means no idle completion."
2762 // happens. The value of nil means no idle completion."
2590
2763
2591− let (view, doc) = cx.current();
2764+ let (view, doc) = current!(cx.editor);
2592
2765
2593 let language_server = match doc.language_server() {
2766 let language_server = match doc.language_server() {
2594 Some(language_server) => language_server,
2767 Some(language_server) => language_server,
@@ -2600,7 +2773,7 @@ pub fn completion(cx: &mut Context) {
2600 let pos = pos_to_lsp_pos(doc.text(), doc.selection(view.id).cursor(), offset_encoding);
2773 let pos = pos_to_lsp_pos(doc.text(), doc.selection(view.id).cursor(), offset_encoding);
2601
2774
2602 // TODO: handle fails
2775 // TODO: handle fails
2603− let future = language_server.completion(doc.identifier(), pos);
2776+ let future = language_server.completion(doc.identifier(), pos, None);
2604
2777
2605 let trigger_offset = doc.selection(view.id).cursor();
2778 let trigger_offset = doc.selection(view.id).cursor();
2606
2779
@@ -2641,8 +2814,8 @@ pub fn completion(cx: &mut Context) {
2641 //}
2814 //}
2642}
2815}
2643
2816
2644−pub fn hover(cx: &mut Context) {
2817+fn hover(cx: &mut Context) {
2645− let (view, doc) = cx.current();
2818+ let (view, doc) = current!(cx.editor);
2646
2819
2647 let language_server = match doc.language_server() {
2820 let language_server = match doc.language_server() {
2648 Some(language_server) => language_server,
2821 Some(language_server) => language_server,
@@ -2658,7 +2831,7 @@ pub fn hover(cx: &mut Context) {
2658 );
2831 );
2659
2832
2660 // TODO: handle fails
2833 // TODO: handle fails
2661− let future = language_server.text_document_hover(doc.identifier(), pos);
2834+ let future = language_server.text_document_hover(doc.identifier(), pos, None);
2662
2835
2663 cx.callback(
2836 cx.callback(
2664 future,
2837 future,
@@ -2691,8 +2864,8 @@ pub fn hover(cx: &mut Context) {
2691}
2864}
2692
2865
2693// comments
2866// comments
2694−pub fn toggle_comments(cx: &mut Context) {
2867+fn toggle_comments(cx: &mut Context) {
2695− let (view, doc) = cx.current();
2868+ let (view, doc) = current!(cx.editor);
2696 let transaction = comment::toggle_line_comments(doc.text(), doc.selection(view.id));
2869 let transaction = comment::toggle_line_comments(doc.text(), doc.selection(view.id));
2697
2870
2698 doc.apply(&transaction, view.id);
2871 doc.apply(&transaction, view.id);
@@ -2701,8 +2874,8 @@ pub fn toggle_comments(cx: &mut Context) {
2701
2874
2702// tree sitter node selection
2875// tree sitter node selection
2703
2876
2704−pub fn expand_selection(cx: &mut Context) {
2877+fn expand_selection(cx: &mut Context) {
2705− let (view, doc) = cx.current();
2878+ let (view, doc) = current!(cx.editor);
2706
2879
2707 if let Some(syntax) = doc.syntax() {
2880 if let Some(syntax) = doc.syntax() {
2708 let text = doc.text().slice(..);
2881 let text = doc.text().slice(..);
@@ -2711,8 +2884,8 @@ pub fn expand_selection(cx: &mut Context) {
2711 }
2884 }
2712}
2885}
2713
2886
2714−pub fn match_brackets(cx: &mut Context) {
2887+fn match_brackets(cx: &mut Context) {
2715− let (view, doc) = cx.current();
2888+ let (view, doc) = current!(cx.editor);
2716
2889
2717 if let Some(syntax) = doc.syntax() {
2890 if let Some(syntax) = doc.syntax() {
2718 let pos = doc.selection(view.id).cursor();
2891 let pos = doc.selection(view.id).cursor();
@@ -2725,23 +2898,23 @@ pub fn match_brackets(cx: &mut Context) {
2725
2898
2726//
2899//
2727
2900
2728−pub fn jump_forward(cx: &mut Context) {
2901+fn jump_forward(cx: &mut Context) {
2729 let count = cx.count();
2902 let count = cx.count();
2730− let (view, doc) = cx.current();
2903+ let (view, doc) = current!(cx.editor);
2731
2904
2732 if let Some((id, selection)) = view.jumps.forward(count) {
2905 if let Some((id, selection)) = view.jumps.forward(count) {
2733 view.doc = *id;
2906 view.doc = *id;
2734 let selection = selection.clone();
2907 let selection = selection.clone();
2735− let (view, doc) = cx.current(); // refetch doc
2908+ let (view, doc) = current!(cx.editor); // refetch doc
2736 doc.set_selection(view.id, selection);
2909 doc.set_selection(view.id, selection);
2737
2910
2738 align_view(doc, view, Align::Center);
2911 align_view(doc, view, Align::Center);
2739 };
2912 };
2740}
2913}
2741
2914
2742−pub fn jump_backward(cx: &mut Context) {
2915+fn jump_backward(cx: &mut Context) {
2743 let count = cx.count();
2916 let count = cx.count();
2744− let (view, doc) = cx.current();
2917+ let (view, doc) = current!(cx.editor);
2745
2918
2746 if let Some((id, selection)) = view.jumps.backward(view.id, doc, count) {
2919 if let Some((id, selection)) = view.jumps.backward(view.id, doc, count) {
2747 // manually set the alternate_file as we cannot use the Editor::switch function here.
2920 // manually set the alternate_file as we cannot use the Editor::switch function here.
@@ -2750,14 +2923,14 @@ pub fn jump_backward(cx: &mut Context) {
2750 }
2923 }
2751 view.doc = *id;
2924 view.doc = *id;
2752 let selection = selection.clone();
2925 let selection = selection.clone();
2753− let (view, doc) = cx.current(); // refetch doc
2926+ let (view, doc) = current!(cx.editor); // refetch doc
2754 doc.set_selection(view.id, selection);
2927 doc.set_selection(view.id, selection);
2755
2928
2756 align_view(doc, view, Align::Center);
2929 align_view(doc, view, Align::Center);
2757 };
2930 };
2758}
2931}
2759
2932
2760−pub fn window_mode(cx: &mut Context) {
2933+fn window_mode(cx: &mut Context) {
2761 cx.on_next_key(move |cx, event| {
2934 cx.on_next_key(move |cx, event| {
2762 if let KeyEvent {
2935 if let KeyEvent {
2763 code: KeyCode::Char(ch),
2936 code: KeyCode::Char(ch),
@@ -2775,15 +2948,17 @@ pub fn window_mode(cx: &mut Context) {
2775 })
2948 })
2776}
2949}
2777
2950
2778−pub fn rotate_view(cx: &mut Context) {
2951+fn rotate_view(cx: &mut Context) {
2779 cx.editor.focus_next()
2952 cx.editor.focus_next()
2780}
2953}
2781
2954
2782// split helper, clear it later
2955// split helper, clear it later
2783use helix_view::editor::Action;
2956use helix_view::editor::Action;
2957+
2958+use self::cmd::TypableCommand;
2784fn split(cx: &mut Context, action: Action) {
2959fn split(cx: &mut Context, action: Action) {
2785 use helix_view::editor::Action;
2960 use helix_view::editor::Action;
2786− let (view, doc) = cx.current();
2961+ let (view, doc) = current!(cx.editor);
2787 let id = doc.id();
2962 let id = doc.id();
2788 let selection = doc.selection(view.id).clone();
2963 let selection = doc.selection(view.id).clone();
2789 let first_line = view.first_line;
2964 let first_line = view.first_line;
@@ -2791,26 +2966,26 @@ fn split(cx: &mut Context, action: Action) {
2791 cx.editor.switch(id, action);
2966 cx.editor.switch(id, action);
2792
2967
2793 // match the selection in the previous view
2968 // match the selection in the previous view
2794− let (view, doc) = cx.current();
2969+ let (view, doc) = current!(cx.editor);
2795 view.first_line = first_line;
2970 view.first_line = first_line;
2796 doc.set_selection(view.id, selection);
2971 doc.set_selection(view.id, selection);
2797}
2972}
2798
2973
2799−pub fn hsplit(cx: &mut Context) {
2974+fn hsplit(cx: &mut Context) {
2800 split(cx, Action::HorizontalSplit);
2975 split(cx, Action::HorizontalSplit);
2801}
2976}
2802
2977
2803−pub fn vsplit(cx: &mut Context) {
2978+fn vsplit(cx: &mut Context) {
2804 split(cx, Action::VerticalSplit);
2979 split(cx, Action::VerticalSplit);
2805}
2980}
2806
2981
2807−pub fn wclose(cx: &mut Context) {
2982+fn wclose(cx: &mut Context) {
2808− let view_id = cx.view().id;
2983+ let view_id = view!(cx.editor).id;
2809 // close current split
2984 // close current split
2810 cx.editor.close(view_id, /* close_buffer */ false);
2985 cx.editor.close(view_id, /* close_buffer */ false);
2811}
2986}
2812
2987
2813−pub fn select_register(cx: &mut Context) {
2988+fn select_register(cx: &mut Context) {
2814 cx.on_next_key(move |cx, event| {
2989 cx.on_next_key(move |cx, event| {
2815 if let KeyEvent {
2990 if let KeyEvent {
2816 code: KeyCode::Char(ch),
2991 code: KeyCode::Char(ch),
@@ -2822,7 +2997,7 @@ pub fn select_register(cx: &mut Context) {
2822 })
2997 })
2823}
2998}
2824
2999
2825−pub fn space_mode(cx: &mut Context) {
3000+fn space_mode(cx: &mut Context) {
2826 cx.on_next_key(move |cx, event| {
3001 cx.on_next_key(move |cx, event| {
2827 if let KeyEvent {
3002 if let KeyEvent {
2828 code: KeyCode::Char(ch),
3003 code: KeyCode::Char(ch),
@@ -2844,7 +3019,7 @@ pub fn space_mode(cx: &mut Context) {
2844 })
3019 })
2845}
3020}
2846
3021
2847−pub fn view_mode(cx: &mut Context) {
3022+fn view_mode(cx: &mut Context) {
2848 cx.on_next_key(move |cx, event| {
3023 cx.on_next_key(move |cx, event| {
2849 if let KeyEvent {
3024 if let KeyEvent {
2850 code: KeyCode::Char(ch),
3025 code: KeyCode::Char(ch),
@@ -2860,7 +3035,7 @@ pub fn view_mode(cx: &mut Context) {
2860 | 't'
3035 | 't'
2861 // bottom
3036 // bottom
2862 | 'b' => {
3037 | 'b' => {
2863− let (view, doc) = cx.current();
3038+ let (view, doc) = current!(cx.editor);
2864
3039
2865 align_view(doc, view, match ch {
3040 align_view(doc, view, match ch {
2866 'z' | 'c' => Align::Center,
3041 'z' | 'c' => Align::Center,
@@ -2870,7 +3045,7 @@ pub fn view_mode(cx: &mut Context) {
2870 });
3045 });
2871 }
3046 }
2872 'm' => {
3047 'm' => {
2873− let (view, doc) = cx.current();
3048+ let (view, doc) = current!(cx.editor);
2874 let pos = doc.selection(view.id).cursor();
3049 let pos = doc.selection(view.id).cursor();
2875 let pos = coords_at_pos(doc.text().slice(..), pos);
3050 let pos = coords_at_pos(doc.text().slice(..), pos);
2876
3051
@@ -2887,7 +3062,7 @@ pub fn view_mode(cx: &mut Context) {
2887 })
3062 })
2888}
3063}
2889
3064
2890−pub fn left_bracket_mode(cx: &mut Context) {
3065+fn left_bracket_mode(cx: &mut Context) {
2891 cx.on_next_key(move |cx, event| {
3066 cx.on_next_key(move |cx, event| {
2892 if let KeyEvent {
3067 if let KeyEvent {
2893 code: KeyCode::Char(ch),
3068 code: KeyCode::Char(ch),
@@ -2903,7 +3078,7 @@ pub fn left_bracket_mode(cx: &mut Context) {
2903 })
3078 })
2904}
3079}
2905
3080
2906−pub fn right_bracket_mode(cx: &mut Context) {
3081+fn right_bracket_mode(cx: &mut Context) {
2907 cx.on_next_key(move |cx, event| {
3082 cx.on_next_key(move |cx, event| {
2908 if let KeyEvent {
3083 if let KeyEvent {
2909 code: KeyCode::Char(ch),
3084 code: KeyCode::Char(ch),
helix-term/src/config.rsnew file+63
@@ -0,0 +1,63 @@
1+use serde::Deserialize;
2+
3+use crate::commands::Command;
4+use crate::keymap::Keymaps;
5+
6+#[derive(Debug, PartialEq, Deserialize)]
7+pub struct GlobalConfig {
8+ pub lsp_progress: bool,
9+}
10+
11+impl Default for GlobalConfig {
12+ fn default() -> Self {
13+ Self { lsp_progress: true }
14+ }
15+}
16+
17+#[derive(Debug, Default, PartialEq, Deserialize)]
18+#[serde(default)]
19+pub struct Config {
20+ pub global: GlobalConfig,
21+ pub keys: Keymaps,
22+}
23+
24+#[test]
25+fn parsing_keymaps_config_file() {
26+ use helix_core::hashmap;
27+ use helix_view::document::Mode;
28+ use helix_view::input::{KeyCode, KeyEvent, KeyModifiers};
29+
30+ let sample_keymaps = r#"
31+ [keys.insert]
32+ y = "move_line_down"
33+ S-C-a = "delete_selection"
34+
35+ [keys.normal]
36+ A-F12 = "move_next_word_end"
37+ "#;
38+
39+ assert_eq!(
40+ toml::from_str::<Config>(sample_keymaps).unwrap(),
41+ Config {
42+ global: Default::default(),
43+ keys: Keymaps(hashmap! {
44+ Mode::Insert => hashmap! {
45+ KeyEvent {
46+ code: KeyCode::Char('y'),
47+ modifiers: KeyModifiers::NONE,
48+ } => Command::move_line_down,
49+ KeyEvent {
50+ code: KeyCode::Char('a'),
51+ modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL,
52+ } => Command::delete_selection,
53+ },
54+ Mode::Normal => hashmap! {
55+ KeyEvent {
56+ code: KeyCode::F(12),
57+ modifiers: KeyModifiers::ALT,
58+ } => Command::move_next_word_end,
59+ },
60+ })
61+ }
62+ );
63+}
helix-term/src/keymap.rs+214−192
@@ -1,7 +1,16 @@
1−use crate::commands::{self, Command};
1+use crate::commands;
2+pub use crate::commands::Command;
3+use anyhow::{anyhow, Error, Result};
2use helix_core::hashmap;
4use helix_core::hashmap;
3use helix_view::document::Mode;
5use helix_view::document::Mode;
6+use helix_view::input::{KeyCode, KeyEvent, KeyModifiers};
7+use serde::Deserialize;
8+use std::{
4−use std::collections::HashMap;
9+ collections::HashMap,
10+ fmt::Display,
11+ ops::{Deref, DerefMut},
12+ str::FromStr,
13+};
5
14
6// Kakoune-inspired:
15// Kakoune-inspired:
7// mode = {
16// mode = {
@@ -92,12 +101,6 @@ use std::collections::HashMap;
92// D] = last diagnostic
101// D] = last diagnostic
93// }
102// }
94
103
95−// #[cfg(feature = "term")]
96−pub use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
97−
98−pub type Keymap = HashMap<KeyEvent, Command>;
99−pub type Keymaps = HashMap<Mode, Keymap>;
100−
101#[macro_export]
104#[macro_export]
102macro_rules! key {
105macro_rules! key {
103 ($key:ident) => {
106 ($key:ident) => {
@@ -132,188 +135,207 @@ macro_rules! alt {
132 };
135 };
133}
136}
134
137
138+#[derive(Debug, PartialEq, Deserialize)]
139+#[serde(transparent)]
140+pub struct Keymaps(pub HashMap<Mode, HashMap<KeyEvent, Command>>);
141+
142+impl Deref for Keymaps {
143+ type Target = HashMap<Mode, HashMap<KeyEvent, Command>>;
144+
145+ fn deref(&self) -> &Self::Target {
146+ &self.0
147+ }
148+}
149+
150+impl Default for Keymaps {
135−pub fn default() -> Keymaps {
151+ fn default() -> Keymaps {
136− let normal = hashmap!(
152+ let normal = hashmap!(
137− key!('h') => commands::move_char_left as Command,
153+ key!('h') => Command::move_char_left,
138− key!('j') => commands::move_line_down,
154+ key!('j') => Command::move_line_down,
139− key!('k') => commands::move_line_up,
155+ key!('k') => Command::move_line_up,
140− key!('l') => commands::move_char_right,
156+ key!('l') => Command::move_char_right,
141−
157+
142− key!(Left) => commands::move_char_left,
158+ key!(Left) => Command::move_char_left,
143− key!(Down) => commands::move_line_down,
159+ key!(Down) => Command::move_line_down,
144− key!(Up) => commands::move_line_up,
160+ key!(Up) => Command::move_line_up,
145− key!(Right) => commands::move_char_right,
161+ key!(Right) => Command::move_char_right,
146−
162+
147− key!('t') => commands::find_till_char,
163+ key!('t') => Command::find_till_char,
148− key!('f') => commands::find_next_char,
164+ key!('f') => Command::find_next_char,
149− key!('T') => commands::till_prev_char,
165+ key!('T') => Command::till_prev_char,
150− key!('F') => commands::find_prev_char,
166+ key!('F') => Command::find_prev_char,
151− // and matching set for select mode (extend)
167+ // and matching set for select mode (extend)
152− //
168+ //
153− key!('r') => commands::replace,
169+ key!('r') => Command::replace,
154− key!('R') => commands::replace_with_yanked,
170+ key!('R') => Command::replace_with_yanked,
155−
171+
156− key!(Home) => commands::move_line_start,
172+ key!(Home) => Command::move_line_start,
157− key!(End) => commands::move_line_end,
173+ key!(End) => Command::move_line_end,
158−
174+
159− key!('w') => commands::move_next_word_start,
175+ key!('w') => Command::move_next_word_start,
160− key!('b') => commands::move_prev_word_start,
176+ key!('b') => Command::move_prev_word_start,
161− key!('e') => commands::move_next_word_end,
177+ key!('e') => Command::move_next_word_end,
162−
178+
163− key!('v') => commands::select_mode,
179+ key!('v') => Command::select_mode,
164− key!('g') => commands::goto_mode,
180+ key!('g') => Command::goto_mode,
165− key!(':') => commands::command_mode,
181+ key!(':') => Command::command_mode,
166−
182+
167− key!('i') => commands::insert_mode,
183+ key!('i') => Command::insert_mode,
168− key!('I') => commands::prepend_to_line,
184+ key!('I') => Command::prepend_to_line,
169− key!('a') => commands::append_mode,
185+ key!('a') => Command::append_mode,
170− key!('A') => commands::append_to_line,
186+ key!('A') => Command::append_to_line,
171− key!('o') => commands::open_below,
187+ key!('o') => Command::open_below,
172− key!('O') => commands::open_above,
188+ key!('O') => Command::open_above,
173− // [<space> ]<space> equivalents too (add blank new line, no edit)
189+ // [<space> ]<space> equivalents too (add blank new line, no edit)
174−
190+
175−
191+
176− key!('d') => commands::delete_selection,
192+ key!('d') => Command::delete_selection,
177− // TODO: also delete without yanking
193+ // TODO: also delete without yanking
178− key!('c') => commands::change_selection,
194+ key!('c') => Command::change_selection,
179− // TODO: also change delete without yanking
195+ // TODO: also change delete without yanking
180−
196+
181− // key!('r') => commands::replace_with_char,
197+ // key!('r') => Command::replace_with_char,
182−
198+
183− key!('s') => commands::select_regex,
199+ key!('s') => Command::select_regex,
184− alt!('s') => commands::split_selection_on_newline,
200+ alt!('s') => Command::split_selection_on_newline,
185− key!('S') => commands::split_selection,
201+ key!('S') => Command::split_selection,
186− key!(';') => commands::collapse_selection,
202+ key!(';') => Command::collapse_selection,
187− alt!(';') => commands::flip_selections,
203+ alt!(';') => Command::flip_selections,
188− key!('%') => commands::select_all,
204+ key!('%') => Command::select_all,
189− key!('x') => commands::select_line,
205+ key!('x') => Command::select_line,
190− key!('X') => commands::extend_line,
206+ key!('X') => Command::extend_line,
191− // or select mode X?
207+ // or select mode X?
192− // extend_to_whole_line, crop_to_whole_line
208+ // extend_to_whole_line, crop_to_whole_line
193−
209+
194−
210+
195− key!('m') => commands::match_brackets,
211+ key!('m') => Command::match_brackets,
196− // TODO: refactor into
212+ // TODO: refactor into
197− // key!('m') => commands::select_to_matching,
213+ // key!('m') => commands::select_to_matching,
198− // key!('M') => commands::back_select_to_matching,
214+ // key!('M') => commands::back_select_to_matching,
199− // select mode extend equivalents
215+ // select mode extend equivalents
200−
216+
201− // key!('.') => commands::repeat_insert,
217+ // key!('.') => commands::repeat_insert,
202− // repeat_select
218+ // repeat_select
203−
219+
204− // TODO: figure out what key to use
220+ // TODO: figure out what key to use
205− // key!('[') => commands::expand_selection, ??
221+ // key!('[') => Command::expand_selection, ??
206− key!('[') => commands::left_bracket_mode,
222+ key!('[') => Command::left_bracket_mode,
207− key!(']') => commands::right_bracket_mode,
223+ key!(']') => Command::right_bracket_mode,
208−
224+
209− key!('/') => commands::search,
225+ key!('/') => Command::search,
210− // ? for search_reverse
226+ // ? for search_reverse
211− key!('n') => commands::search_next,
227+ key!('n') => Command::search_next,
212− key!('N') => commands::extend_search_next,
228+ key!('N') => Command::extend_search_next,
213− // N for search_prev
229+ // N for search_prev
214− key!('*') => commands::search_selection,
230+ key!('*') => Command::search_selection,
215−
231+
216− key!('u') => commands::undo,
232+ key!('u') => Command::undo,
217− key!('U') => commands::redo,
233+ key!('U') => Command::redo,
218−
234+
219− key!('y') => commands::yank,
235+ key!('y') => Command::yank,
220− // yank_all
236+ // yank_all
221− key!('p') => commands::paste_after,
237+ key!('p') => Command::paste_after,
222− // paste_all
238+ // paste_all
223− key!('P') => commands::paste_before,
239+ key!('P') => Command::paste_before,
224−
240+
225− key!('>') => commands::indent,
241+ key!('>') => Command::indent,
226− key!('<') => commands::unindent,
242+ key!('<') => Command::unindent,
227− key!('=') => commands::format_selections,
243+ key!('=') => Command::format_selections,
228− key!('J') => commands::join_selections,
244+ key!('J') => Command::join_selections,
229− // TODO: conflicts hover/doc
245+ // TODO: conflicts hover/doc
230− key!('K') => commands::keep_selections,
246+ key!('K') => Command::keep_selections,
231− // TODO: and another method for inverse
247+ // TODO: and another method for inverse
232−
248+
233− // TODO: clashes with space mode
249+ // TODO: clashes with space mode
234− key!(' ') => commands::keep_primary_selection,
250+ key!(' ') => Command::keep_primary_selection,
235−
251+
236− // key!('q') => commands::record_macro,
252+ // key!('q') => Command::record_macro,
237− // key!('Q') => commands::replay_macro,
253+ // key!('Q') => Command::replay_macro,
238−
254+
239− // ~ / apostrophe => change case
255+ // ~ / apostrophe => change case
240− // & align selections
256+ // & align selections
241− // _ trim selections
257+ // _ trim selections
242−
258+
243− // C / altC = copy (repeat) selections on prev/next lines
259+ // C / altC = copy (repeat) selections on prev/next lines
244−
260+
245− key!(Esc) => commands::normal_mode,
261+ key!(Esc) => Command::normal_mode,
246− key!(PageUp) => commands::page_up,
262+ key!(PageUp) => Command::page_up,
247− key!(PageDown) => commands::page_down,
263+ key!(PageDown) => Command::page_down,
248− ctrl!('b') => commands::page_up,
264+ ctrl!('b') => Command::page_up,
249− ctrl!('f') => commands::page_down,
265+ ctrl!('f') => Command::page_down,
250− ctrl!('u') => commands::half_page_up,
266+ ctrl!('u') => Command::half_page_up,
251− ctrl!('d') => commands::half_page_down,
267+ ctrl!('d') => Command::half_page_down,
252−
268+
253− ctrl!('w') => commands::window_mode,
269+ ctrl!('w') => Command::window_mode,
254−
270+
255− // move under <space>c
271+ // move under <space>c
256− ctrl!('c') => commands::toggle_comments,
272+ ctrl!('c') => Command::toggle_comments,
257− key!('K') => commands::hover,
273+ key!('K') => Command::hover,
258−
274+
259− // z family for save/restore/combine from/to sels from register
275+ // z family for save/restore/combine from/to sels from register
260−
276+
261− // supposedly ctrl!('i') but did not work
277+ // supposedly ctrl!('i') but did not work
262− key!(Tab) => commands::jump_forward,
278+ key!(Tab) => Command::jump_forward,
263− ctrl!('o') => commands::jump_backward,
279+ ctrl!('o') => Command::jump_backward,
264− // ctrl!('s') => commands::save_selection,
280+ // ctrl!('s') => Command::save_selection,
265−
281+
266− key!(' ') => commands::space_mode,
282+ key!(' ') => Command::space_mode,
267− key!('z') => commands::view_mode,
283+ key!('z') => Command::view_mode,
268−
284+
269− key!('"') => commands::select_register,
285+ key!('"') => Command::select_register,
270− );
286+ );
271− // TODO: decide whether we want normal mode to also be select mode (kakoune-like), or whether
287+ // TODO: decide whether we want normal mode to also be select mode (kakoune-like), or whether
272− // we keep this separate select mode. More keys can fit into normal mode then, but it's weird
288+ // we keep this separate select mode. More keys can fit into normal mode then, but it's weird
273− // because some selection operations can now be done from normal mode, some from select mode.
289+ // because some selection operations can now be done from normal mode, some from select mode.
274− let mut select = normal.clone();
290+ let mut select = normal.clone();
275− select.extend(
291+ select.extend(
276− hashmap!(
292+ hashmap!(
277− key!('h') => commands::extend_char_left as Command,
293+ key!('h') => Command::extend_char_left,
278− key!('j') => commands::extend_line_down,
294+ key!('j') => Command::extend_line_down,
279− key!('k') => commands::extend_line_up,
295+ key!('k') => Command::extend_line_up,
280− key!('l') => commands::extend_char_right,
296+ key!('l') => Command::extend_char_right,
281−
297+
282− key!(Left) => commands::extend_char_left,
298+ key!(Left) => Command::extend_char_left,
283− key!(Down) => commands::extend_line_down,
299+ key!(Down) => Command::extend_line_down,
284− key!(Up) => commands::extend_line_up,
300+ key!(Up) => Command::extend_line_up,
285− key!(Right) => commands::extend_char_right,
301+ key!(Right) => Command::extend_char_right,
286−
302+
287− key!('w') => commands::extend_next_word_start,
303+ key!('w') => Command::extend_next_word_start,
288− key!('b') => commands::extend_prev_word_start,
304+ key!('b') => Command::extend_prev_word_start,
289− key!('e') => commands::extend_next_word_end,
305+ key!('e') => Command::extend_next_word_end,
290−
306+
291− key!('t') => commands::extend_till_char,
307+ key!('t') => Command::extend_till_char,
292− key!('f') => commands::extend_next_char,
308+ key!('f') => Command::extend_next_char,
293−
309+
294− key!('T') => commands::extend_till_prev_char,
310+ key!('T') => Command::extend_till_prev_char,
295− key!('F') => commands::extend_prev_char,
311+ key!('F') => Command::extend_prev_char,
296− key!(Home) => commands::extend_line_start,
312+ key!(Home) => Command::extend_line_start,
297− key!(End) => commands::extend_line_end,
313+ key!(End) => Command::extend_line_end,
298− key!(Esc) => commands::exit_select_mode,
314+ key!(Esc) => Command::exit_select_mode,
299− )
315+ )
300− .into_iter(),
316+ .into_iter(),
301− );
317+ );
302−
318+
303− hashmap!(
319+ Keymaps(hashmap!(
304− // as long as you cast the first item, rust is able to infer the other cases
320+ // as long as you cast the first item, rust is able to infer the other cases
305− // TODO: select could be normal mode with some bindings merged over
321+ // TODO: select could be normal mode with some bindings merged over
306− Mode::Normal => normal,
322+ Mode::Normal => normal,
307− Mode::Select => select,
323+ Mode::Select => select,
308− Mode::Insert => hashmap!(
324+ Mode::Insert => hashmap!(
309− key!(Esc) => commands::normal_mode as Command,
325+ key!(Esc) => Command::normal_mode as Command,
310− key!(Backspace) => commands::insert::delete_char_backward,
326+ key!(Backspace) => Command::delete_char_backward,
311− key!(Delete) => commands::insert::delete_char_forward,
327+ key!(Delete) => Command::delete_char_forward,
312− key!(Enter) => commands::insert::insert_newline,
328+ key!(Enter) => Command::insert_newline,
313− key!(Tab) => commands::insert::insert_tab,
329+ key!(Tab) => Command::insert_tab,
314−
330+ key!(Left) => Command::move_char_left,
331+ key!(Down) => Command::move_line_down,
332+ key!(Up) => Command::move_line_up,
333+ key!(Right) => Command::move_char_right,
334+ key!(PageUp) => Command::page_up,
335+ key!(PageDown) => Command::page_down,
315− ctrl!('x') => commands::completion,
336+ ctrl!('x') => Command::completion,
316− ctrl!('w') => commands::insert::delete_word_backward,
337+ ctrl!('w') => Command::delete_word_backward,
338+ ),
317− ),
339+ ))
318− )
340+ }
319}
341}
helix-term/src/lib.rs+4
@@ -1,8 +1,12 @@
1#![allow(unused)]
1#![allow(unused)]
2
2
3+#[macro_use]
4+extern crate helix_view;
5+
3pub mod application;
6pub mod application;
4pub mod args;
7pub mod args;
5pub mod commands;
8pub mod commands;
6pub mod compositor;
9pub mod compositor;
10+pub mod config;
7pub mod keymap;
11pub mod keymap;
8pub mod ui;
12pub mod ui;
helix-term/src/main.rs+9−4
@@ -1,10 +1,9 @@
1+use anyhow::{Context, Error, Result};
1use helix_term::application::Application;
2use helix_term::application::Application;
2use helix_term::args::Args;
3use helix_term::args::Args;
3−
4+use helix_term::config::Config;
4use std::path::PathBuf;
5use std::path::PathBuf;
5
6
6−use anyhow::{Context, Result};
7−
8fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> {
7fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> {
9 let mut base_config = fern::Dispatch::new();
8 let mut base_config = fern::Dispatch::new();
10
9
@@ -89,10 +88,16 @@ FLAGS:
89 std::fs::create_dir_all(&conf_dir).ok();
88 std::fs::create_dir_all(&conf_dir).ok();
90 }
89 }
91
90
91+ let config = match std::fs::read_to_string(conf_dir.join("config.toml")) {
92+ Ok(config) => toml::from_str(&config)?,
93+ Err(err) if err.kind() == std::io::ErrorKind::NotFound => Config::default(),
94+ Err(err) => return Err(Error::new(err)),
95+ };
96+
92 setup_logging(logpath, args.verbosity).context("failed to initialize logging")?;
97 setup_logging(logpath, args.verbosity).context("failed to initialize logging")?;
93
98
94 // TODO: use the thread local executor to spawn the application task separately from the work pool
99 // TODO: use the thread local executor to spawn the application task separately from the work pool
95− let mut app = Application::new(args).context("unable to create new appliction")?;
100+ let mut app = Application::new(args, config).context("unable to create new application")?;
96 app.run().await.unwrap();
101 app.run().await.unwrap();
97
102
98 Ok(())
103 Ok(())
helix-term/src/ui/completion.rs+3−3
@@ -89,7 +89,7 @@ impl Completion {
89 // doc.state = snapshot.clone();
89 // doc.state = snapshot.clone();
90 }
90 }
91 PromptEvent::Validate => {
91 PromptEvent::Validate => {
92− let (view, doc) = editor.current();
92+ let (view, doc) = current!(editor);
93
93
94 // revert state to what it was before the last update
94 // revert state to what it was before the last update
95 // doc.state = snapshot.clone();
95 // doc.state = snapshot.clone();
@@ -169,7 +169,7 @@ impl Completion {
169 pub fn update(&mut self, cx: &mut commands::Context) {
169 pub fn update(&mut self, cx: &mut commands::Context) {
170 // recompute menu based on matches
170 // recompute menu based on matches
171 let menu = self.popup.contents_mut();
171 let menu = self.popup.contents_mut();
172− let (view, doc) = cx.editor.current();
172+ let (view, doc) = current!(cx.editor);
173
173
174 // cx.hooks()
174 // cx.hooks()
175 // cx.add_hook(enum type, ||)
175 // cx.add_hook(enum type, ||)
@@ -233,7 +233,7 @@ impl Component for Completion {
233 // ---
233 // ---
234 // option.documentation
234 // option.documentation
235
235
236− let (view, doc) = cx.editor.current();
236+ let (view, doc) = current!(cx.editor);
237 let language = doc
237 let language = doc
238 .language()
238 .language()
239 .and_then(|scope| scope.strip_prefix("source."))
239 .and_then(|scope| scope.strip_prefix("source."))
helix-term/src/ui/editor.rs+25−29
@@ -11,15 +11,13 @@ use helix_core::{
11 syntax::{self, HighlightEvent},
11 syntax::{self, HighlightEvent},
12 Position, Range,
12 Position, Range,
13};
13};
14−use helix_view::{
14+use helix_view::input::{KeyCode, KeyEvent, KeyModifiers};
15− document::{IndentStyle, Mode},
16− Document, Editor, Theme, View,
15+use helix_view::{document::Mode, Document, Editor, Theme, View};
17−};
18use std::borrow::Cow;
16use std::borrow::Cow;
19
17
20use crossterm::{
18use crossterm::{
21 cursor,
19 cursor,
22− event::{read, Event, EventStream, KeyCode, KeyEvent, KeyModifiers},
20+ event::{read, Event, EventStream},
23};
21};
24use tui::{
22use tui::{
25 backend::CrosstermBackend,
23 backend::CrosstermBackend,
@@ -30,7 +28,7 @@ use tui::{
30};
28};
31
29
32pub struct EditorView {
30pub struct EditorView {
33− keymap: Keymaps,
31+ keymaps: Keymaps,
34 on_next_key: Option<Box<dyn FnOnce(&mut commands::Context, KeyEvent)>>,
32 on_next_key: Option<Box<dyn FnOnce(&mut commands::Context, KeyEvent)>>,
35 last_insert: (commands::Command, Vec<KeyEvent>),
33 last_insert: (commands::Command, Vec<KeyEvent>),
36 completion: Option<Completion>,
34 completion: Option<Completion>,
@@ -40,16 +38,16 @@ const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
40
38
41impl Default for EditorView {
39impl Default for EditorView {
42 fn default() -> Self {
40 fn default() -> Self {
43− Self::new()
41+ Self::new(Keymaps::default())
44 }
42 }
45}
43}
46
44
47impl EditorView {
45impl EditorView {
48− pub fn new() -> Self {
46+ pub fn new(keymaps: Keymaps) -> Self {
49 Self {
47 Self {
50− keymap: keymap::default(),
48+ keymaps,
51 on_next_key: None,
49 on_next_key: None,
52− last_insert: (commands::normal_mode, Vec::new()),
50+ last_insert: (commands::Command::normal_mode, Vec::new()),
53 completion: None,
51 completion: None,
54 }
52 }
55 }
53 }
@@ -479,18 +477,15 @@ impl EditorView {
479 Mode::Select => "SEL",
477 Mode::Select => "SEL",
480 Mode::Normal => "NOR",
478 Mode::Normal => "NOR",
481 };
479 };
482− let text_color = if is_focused {
480+ let style = if is_focused {
483− theme.get("ui.text.focus")
481+ theme.get("ui.statusline")
484 } else {
482 } else {
485− theme.get("ui.text")
483+ theme.get("ui.statusline.inactive")
486 };
484 };
487 // statusline
485 // statusline
488− surface.set_style(
489− Rect::new(viewport.x, viewport.y, viewport.width, 1),
486+ surface.set_style(Rect::new(viewport.x, viewport.y, viewport.width, 1), style);
490− theme.get("ui.statusline"),
491− );
492 if is_focused {
487 if is_focused {
493− surface.set_string(viewport.x + 1, viewport.y, mode, text_color);
488+ surface.set_string(viewport.x + 1, viewport.y, mode, style);
494 }
489 }
495
490
496 if let Some(path) = doc.relative_path() {
491 if let Some(path) = doc.relative_path() {
@@ -502,7 +497,7 @@ impl EditorView {
502 viewport.y,
497 viewport.y,
503 title,
498 title,
504 viewport.width.saturating_sub(6) as usize,
499 viewport.width.saturating_sub(6) as usize,
505− text_color,
500+ style,
506 );
501 );
507 }
502 }
508
503
@@ -541,13 +536,13 @@ impl EditorView {
541 viewport.x + viewport.width.saturating_sub(text_len),
536 viewport.x + viewport.width.saturating_sub(text_len),
542 viewport.y,
537 viewport.y,
543 right_side_text,
538 right_side_text,
544− text_color,
539+ style,
545 );
540 );
546 }
541 }
547
542
548 fn insert_mode(&self, cx: &mut commands::Context, event: KeyEvent) {
543 fn insert_mode(&self, cx: &mut commands::Context, event: KeyEvent) {
549− if let Some(command) = self.keymap[&Mode::Insert].get(&event) {
544+ if let Some(command) = self.keymaps[&Mode::Insert].get(&event) {
550− command(cx);
545+ command.execute(cx);
551 } else if let KeyEvent {
546 } else if let KeyEvent {
552 code: KeyCode::Char(ch),
547 code: KeyCode::Char(ch),
553 ..
548 ..
@@ -568,7 +563,7 @@ impl EditorView {
568 // special handling for repeat operator
563 // special handling for repeat operator
569 key!('.') => {
564 key!('.') => {
570 // first execute whatever put us into insert mode
565 // first execute whatever put us into insert mode
571− (self.last_insert.0)(cxt);
566+ self.last_insert.0.execute(cxt);
572 // then replay the inputs
567 // then replay the inputs
573 for key in &self.last_insert.1 {
568 for key in &self.last_insert.1 {
574 self.insert_mode(cxt, *key)
569 self.insert_mode(cxt, *key)
@@ -584,8 +579,8 @@ impl EditorView {
584 // set the register
579 // set the register
585 cxt.selected_register = cxt.editor.selected_register.take();
580 cxt.selected_register = cxt.editor.selected_register.take();
586
581
587− if let Some(command) = self.keymap[&mode].get(&event) {
582+ if let Some(command) = self.keymaps[&mode].get(&event) {
588− command(cxt);
583+ command.execute(cxt);
589 }
584 }
590 }
585 }
591 }
586 }
@@ -613,12 +608,13 @@ impl Component for EditorView {
613 cx.editor.resize(Rect::new(0, 0, width, height - 1));
608 cx.editor.resize(Rect::new(0, 0, width, height - 1));
614 EventResult::Consumed(None)
609 EventResult::Consumed(None)
615 }
610 }
616− Event::Key(mut key) => {
611+ Event::Key(key) => {
612+ let mut key = KeyEvent::from(key);
617 canonicalize_key(&mut key);
613 canonicalize_key(&mut key);
618 // clear status
614 // clear status
619 cx.editor.status_msg = None;
615 cx.editor.status_msg = None;
620
616
621− let (view, doc) = cx.editor.current();
617+ let (view, doc) = current!(cx.editor);
622 let mode = doc.mode();
618 let mode = doc.mode();
623
619
624 let mut cxt = commands::Context {
620 let mut cxt = commands::Context {
@@ -687,7 +683,7 @@ impl Component for EditorView {
687 return EventResult::Ignored;
683 return EventResult::Ignored;
688 }
684 }
689
685
690− let (view, doc) = cx.editor.current();
686+ let (view, doc) = current!(cx.editor);
691 view.ensure_cursor_in_view(doc);
687 view.ensure_cursor_in_view(doc);
692
688
693 // mode transitions
689 // mode transitions
@@ -699,7 +695,7 @@ impl Component for EditorView {
699 // how we entered insert mode is important, and we should track that so
695 // how we entered insert mode is important, and we should track that so
700 // we can repeat the side effect.
696 // we can repeat the side effect.
701
697
702− self.last_insert.0 = self.keymap[&mode][&key];
698+ self.last_insert.0 = self.keymaps[&mode][&key];
703 self.last_insert.1.clear();
699 self.last_insert.1.clear();
704 }
700 }
705 (Mode::Insert, Mode::Normal) => {
701 (Mode::Insert, Mode::Normal) => {
helix-term/src/ui/mod.rs+20−8
@@ -30,8 +30,9 @@ pub fn regex_prompt(
30 prompt: String,
30 prompt: String,
31 fun: impl Fn(&mut View, &mut Document, &mut Registers, Regex) + 'static,
31 fun: impl Fn(&mut View, &mut Document, &mut Registers, Regex) + 'static,
32) -> Prompt {
32) -> Prompt {
33− let view_id = cx.view().id;
33+ let (view, doc) = current!(cx.editor);
34+ let view_id = view.id;
34− let snapshot = cx.doc().selection(view_id).clone();
35+ let snapshot = doc.selection(view_id).clone();
35
36
36 Prompt::new(
37 Prompt::new(
37 prompt,
38 prompt,
@@ -40,7 +41,7 @@ pub fn regex_prompt(
40 match event {
41 match event {
41 PromptEvent::Abort => {
42 PromptEvent::Abort => {
42 // TODO: also revert text
43 // TODO: also revert text
43− let (view, doc) = editor.current();
44+ let (view, doc) = current!(editor);
44 doc.set_selection(view.id, snapshot.clone());
45 doc.set_selection(view.id, snapshot.clone());
45 }
46 }
46 PromptEvent::Validate => {
47 PromptEvent::Validate => {
@@ -54,7 +55,8 @@ pub fn regex_prompt(
54
55
55 match Regex::new(input) {
56 match Regex::new(input) {
56 Ok(regex) => {
57 Ok(regex) => {
57− let (view, doc, registers) = editor.current_with_registers();
58+ let (view, doc) = current!(editor);
59+ let registers = &mut editor.registers;
58
60
59 // revert state to what it was before the last update
61 // revert state to what it was before the last update
60 // TODO: also revert text
62 // TODO: also revert text
@@ -124,10 +126,11 @@ pub mod completers {
124 use ignore::WalkBuilder;
126 use ignore::WalkBuilder;
125 use std::path::{Path, PathBuf};
127 use std::path::{Path, PathBuf};
126
128
129+ let is_tilde = input.starts_with('~') && input.len() == 1;
127− let path = Path::new(input);
130+ let path = helix_view::document::expand_tilde(Path::new(input));
128
131
129 let (dir, file_name) = if input.ends_with('/') {
132 let (dir, file_name) = if input.ends_with('/') {
130− (path.into(), None)
133+ (path, None)
131 } else {
134 } else {
132 let file_name = path
135 let file_name = path
133 .file_name()
136 .file_name()
@@ -152,7 +155,16 @@ pub mod completers {
152 let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());
155 let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());
153
156
154 let path = entry.path();
157 let path = entry.path();
158+ let mut path = if is_tilde {
159+ // if it's a single tilde an absolute path is displayed so that when `TAB` is pressed on
160+ // one of the directories the tilde will be replaced with a valid path not with a relative
161+ // home directory name.
162+ // ~ -> <TAB> -> /home/user
163+ // ~/ -> <TAB> -> ~/first_entry
164+ path.to_path_buf()
165+ } else {
155− let mut path = path.strip_prefix(&dir).unwrap_or(path).to_path_buf();
166+ path.strip_prefix(&dir).unwrap_or(path).to_path_buf()
167+ };
156
168
157 if is_dir {
169 if is_dir {
158 path.push("");
170 path.push("");
@@ -182,7 +194,7 @@ pub mod completers {
182 })
194 })
183 .collect();
195 .collect();
184
196
185− let range = ((input.len() - file_name.len())..);
197+ let range = ((input.len().saturating_sub(file_name.len()))..);
186
198
187 matches.sort_unstable_by_key(|(_file, score)| Reverse(*score));
199 matches.sort_unstable_by_key(|(_file, score)| Reverse(*score));
188 files = matches
200 files = matches
helix-term/src/ui/prompt.rs+17−2
@@ -53,7 +53,16 @@ impl Prompt {
53 }
53 }
54
54
55 pub fn insert_char(&mut self, c: char) {
55 pub fn insert_char(&mut self, c: char) {
56+ let pos = if self.line.is_empty() {
57+ 0
58+ } else {
59+ self.line
60+ .char_indices()
61+ .nth(self.cursor)
62+ .map(|(pos, _)| pos)
63+ .unwrap_or_else(|| self.line.len())
64+ };
56− self.line.insert(self.cursor, c);
65+ self.line.insert(pos, c);
57 self.cursor += 1;
66 self.cursor += 1;
58 self.completion = (self.completion_fn)(&self.line);
67 self.completion = (self.completion_fn)(&self.line);
59 self.exit_selection();
68 self.exit_selection();
@@ -79,7 +88,13 @@ impl Prompt {
79
88
80 pub fn delete_char_backwards(&mut self) {
89 pub fn delete_char_backwards(&mut self) {
81 if self.cursor > 0 {
90 if self.cursor > 0 {
91+ let pos = self
92+ .line
93+ .char_indices()
82− self.line.remove(self.cursor - 1);
94+ .nth(self.cursor - 1)
95+ .map(|(pos, _)| pos)
96+ .expect("line is not empty");
97+ self.line.remove(pos);
83 self.cursor -= 1;
98 self.cursor -= 1;
84 self.completion = (self.completion_fn)(&self.line);
99 self.completion = (self.completion_fn)(&self.line);
85 }
100 }
helix-tui/Cargo.toml+4
@@ -7,6 +7,10 @@ A library to build rich terminal user interfaces or dashboards
7"""
7"""
8edition = "2018"
8edition = "2018"
9license = "MPL-2.0"
9license = "MPL-2.0"
10+categories = ["editor"]
11+repository = "https://github.com/helix-editor/helix"
12+homepage = "https://helix-editor.com"
13+include = ["src/**/*", "README.md"]
10
14
11[features]
15[features]
12default = ["crossterm"]
16default = ["crossterm"]
helix-view/Cargo.toml+7−2
@@ -4,6 +4,10 @@ version = "0.2.0"
4authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
4authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5edition = "2018"
5edition = "2018"
6license = "MPL-2.0"
6license = "MPL-2.0"
7+description = "UI abstractions for use in backends"
8+categories = ["editor"]
9+repository = "https://github.com/helix-editor/helix"
10+homepage = "https://helix-editor.com"
7
11
8# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
12# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
9
13
@@ -13,8 +17,8 @@ default = ["term"]
13
17
14[dependencies]
18[dependencies]
15anyhow = "1"
19anyhow = "1"
16−helix-core = { path = "../helix-core" }
20+helix-core = { version = "0.2", path = "../helix-core" }
17−helix-lsp = { path = "../helix-lsp"}
21+helix-lsp = { version = "0.2", path = "../helix-lsp"}
18
22
19# Conversion traits
23# Conversion traits
20tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"], optional = true }
24tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"], optional = true }
@@ -23,6 +27,7 @@ once_cell = "1.8"
23url = "2"
27url = "2"
24
28
25tokio = { version = "1", features = ["full"] }
29tokio = { version = "1", features = ["full"] }
30+futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
26
31
27slotmap = "1"
32slotmap = "1"
28
33
helix-view/src/document.rs+96−16
@@ -1,7 +1,11 @@
1−use anyhow::{Context, Error};
1+use anyhow::{anyhow, Context, Error};
2+use serde::de::{self, Deserialize, Deserializer};
2use std::cell::Cell;
3use std::cell::Cell;
4+use std::collections::HashMap;
5+use std::fmt::Display;
3use std::future::Future;
6use std::future::Future;
4use std::path::{Component, Path, PathBuf};
7use std::path::{Component, Path, PathBuf};
8+use std::str::FromStr;
5use std::sync::Arc;
9use std::sync::Arc;
6
10
7use helix_core::{
11use helix_core::{
@@ -15,8 +19,6 @@ use helix_core::{
15
19
16use crate::{DocumentId, ViewId};
20use crate::{DocumentId, ViewId};
17
21
18−use std::collections::HashMap;
19−
20#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
22#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
21pub enum Mode {
23pub enum Mode {
22 Normal,
24 Normal,
@@ -24,6 +26,40 @@ pub enum Mode {
24 Insert,
26 Insert,
25}
27}
26
28
29+impl Display for Mode {
30+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31+ match self {
32+ Mode::Normal => f.write_str("normal"),
33+ Mode::Select => f.write_str("select"),
34+ Mode::Insert => f.write_str("insert"),
35+ }
36+ }
37+}
38+
39+impl FromStr for Mode {
40+ type Err = Error;
41+
42+ fn from_str(s: &str) -> Result<Self, Self::Err> {
43+ match s {
44+ "normal" => Ok(Mode::Normal),
45+ "select" => Ok(Mode::Select),
46+ "insert" => Ok(Mode::Insert),
47+ _ => Err(anyhow!("Invalid mode '{}'", s)),
48+ }
49+ }
50+}
51+
52+// toml deserializer doesn't seem to recognize string as enum
53+impl<'de> Deserialize<'de> for Mode {
54+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
55+ where
56+ D: Deserializer<'de>,
57+ {
58+ let s = String::deserialize(deserializer)?;
59+ s.parse().map_err(de::Error::custom)
60+ }
61+}
62+
27#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
63#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
28pub enum IndentStyle {
64pub enum IndentStyle {
29 Tabs,
65 Tabs,
@@ -105,6 +141,36 @@ where
105 }
141 }
106}
142}
107
143
144+/// Expands tilde `~` into users home directory if avilable, otherwise returns the path
145+/// unchanged. The tilde will only be expanded when present as the first component of the path
146+/// and only slash follows it.
147+pub fn expand_tilde(path: &Path) -> PathBuf {
148+ let mut components = path.components().peekable();
149+ if let Some(Component::Normal(c)) = components.peek() {
150+ if c == &"~" {
151+ if let Ok(home) = helix_core::home_dir() {
152+ // it's ok to unwrap, the path starts with `~`
153+ return home.join(path.strip_prefix("~").unwrap());
154+ }
155+ }
156+ }
157+
158+ path.to_path_buf()
159+}
160+
161+/// Replaces users home directory from `path` with tilde `~` if the directory
162+/// is available, otherwise returns the path unchanged.
163+pub fn fold_home_dir(path: &Path) -> PathBuf {
164+ if let Ok(home) = helix_core::home_dir() {
165+ if path.starts_with(&home) {
166+ // it's ok to unwrap, the path starts with home dir
167+ return PathBuf::from("~").join(path.strip_prefix(&home).unwrap());
168+ }
169+ }
170+
171+ path.to_path_buf()
172+}
173+
108/// Normalize a path, removing things like `.` and `..`.
174/// Normalize a path, removing things like `.` and `..`.
109///
175///
110/// CAUTION: This does not resolve symlinks (unlike
176/// CAUTION: This does not resolve symlinks (unlike
@@ -115,6 +181,7 @@ where
115/// needs to improve on.
181/// needs to improve on.
116/// Copied from cargo: https://github.com/rust-lang/cargo/blob/070e459c2d8b79c5b2ac5218064e7603329c92ae/crates/cargo-util/src/paths.rs#L81
182/// Copied from cargo: https://github.com/rust-lang/cargo/blob/070e459c2d8b79c5b2ac5218064e7603329c92ae/crates/cargo-util/src/paths.rs#L81
117pub fn normalize_path(path: &Path) -> PathBuf {
183pub fn normalize_path(path: &Path) -> PathBuf {
184+ let path = expand_tilde(path);
118 let mut components = path.components().peekable();
185 let mut components = path.components().peekable();
119 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
186 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
120 components.next();
187 components.next();
@@ -141,12 +208,17 @@ pub fn normalize_path(path: &Path) -> PathBuf {
141 ret
208 ret
142}
209}
143
210
144−// Returns the canonical, absolute form of a path with all intermediate components normalized.
211+/// Returns the canonical, absolute form of a path with all intermediate components normalized.
145−//
212+///
146−// This function is used instead of `std::fs::canonicalize` because we don't want to verify
213+/// This function is used instead of `std::fs::canonicalize` because we don't want to verify
147−// here if the path exists, just normalize it's components.
214+/// here if the path exists, just normalize it's components.
148pub fn canonicalize_path(path: &Path) -> std::io::Result<PathBuf> {
215pub fn canonicalize_path(path: &Path) -> std::io::Result<PathBuf> {
216+ let normalized = normalize_path(path);
217+ if normalized.is_absolute() {
218+ Ok(normalized)
219+ } else {
149− std::env::current_dir().map(|current_dir| normalize_path(&current_dir.join(path)))
220+ std::env::current_dir().map(|current_dir| current_dir.join(normalized))
221+ }
150}
222}
151
223
152use helix_lsp::lsp;
224use helix_lsp::lsp;
@@ -210,10 +282,11 @@ impl Document {
210 pub fn format(&mut self, view_id: ViewId) {
282 pub fn format(&mut self, view_id: ViewId) {
211 if let Some(language_server) = self.language_server() {
283 if let Some(language_server) = self.language_server() {
212 // TODO: await, no blocking
284 // TODO: await, no blocking
213− let transaction = helix_lsp::block_on(
285+ let transaction = helix_lsp::block_on(language_server.text_document_formatting(
214− language_server
286+ self.identifier(),
215− .text_document_formatting(self.identifier(), lsp::FormattingOptions::default()),
287+ lsp::FormattingOptions::default(),
288+ None,
216− )
289+ ))
217 .map(|edits| {
290 .map(|edits| {
218 helix_lsp::util::generate_transaction_from_edits(
291 helix_lsp::util::generate_transaction_from_edits(
219 self.text(),
292 self.text(),
@@ -696,12 +769,19 @@ impl Document {
696 &self.selections[&view_id]
769 &self.selections[&view_id]
697 }
770 }
698
771
699− pub fn relative_path(&self) -> Option<&Path> {
772+ pub fn relative_path(&self) -> Option<PathBuf> {
700 let cwdir = std::env::current_dir().expect("couldn't determine current directory");
773 let cwdir = std::env::current_dir().expect("couldn't determine current directory");
701
774
702− self.path
775+ self.path.as_ref().map(|path| {
703− .as_ref()
776+ let path = fold_home_dir(path);
777+ if path.is_relative() {
778+ path
779+ } else {
704− .map(|path| path.strip_prefix(cwdir).unwrap_or(path))
780+ path.strip_prefix(cwdir)
781+ .map(|p| p.to_path_buf())
782+ .unwrap_or(path)
783+ }
784+ })
705 }
785 }
706
786
707 // pub fn slice<R>(&self, range: R) -> RopeSlice where R: RangeBounds {
787 // pub fn slice<R>(&self, range: R) -> RopeSlice where R: RangeBounds {
helix-view/src/editor.rs+24−25
@@ -2,7 +2,9 @@ use crate::{theme::Theme, tree::Tree, Document, DocumentId, RegisterSelection, V
2use tui::layout::Rect;
2use tui::layout::Rect;
3use tui::terminal::CursorKind;
3use tui::terminal::CursorKind;
4
4
5+use futures_util::future;
5use std::path::PathBuf;
6use std::path::PathBuf;
7+use std::time::Duration;
6
8
7use slotmap::SlotMap;
9use slotmap::SlotMap;
8
10
@@ -101,19 +103,19 @@ impl Editor {
101
103
102 match action {
104 match action {
103 Action::Replace => {
105 Action::Replace => {
104− let view = self.view();
106+ let view = view!(self);
105 let jump = (
107 let jump = (
106 view.doc,
108 view.doc,
107 self.documents[view.doc].selection(view.id).clone(),
109 self.documents[view.doc].selection(view.id).clone(),
108 );
110 );
109
111
110− let view = self.view_mut();
112+ let view = view_mut!(self);
111 view.jumps.push(jump);
113 view.jumps.push(jump);
112 view.last_accessed_doc = Some(view.doc);
114 view.last_accessed_doc = Some(view.doc);
113 view.doc = id;
115 view.doc = id;
114 view.first_line = 0;
116 view.first_line = 0;
115
117
116− let (view, doc) = self.current();
118+ let (view, doc) = current!(self);
117
119
118 // initialize selection for view
120 // initialize selection for view
119 let selection = doc
121 let selection = doc
@@ -238,27 +240,6 @@ impl Editor {
238 self.tree.is_empty()
240 self.tree.is_empty()
239 }
241 }
240
242
241− pub fn current(&mut self) -> (&mut View, &mut Document) {
242− let view = self.tree.get_mut(self.tree.focus);
243− let doc = &mut self.documents[view.doc];
244− (view, doc)
245− }
246−
247− pub fn current_with_registers(&mut self) -> (&mut View, &mut Document, &mut Registers) {
248− let view = self.tree.get_mut(self.tree.focus);
249− let doc = &mut self.documents[view.doc];
250− let registers = &mut self.registers;
251− (view, doc, registers)
252− }
253−
254− pub fn view(&self) -> &View {
255− self.tree.get(self.tree.focus)
256− }
257−
258− pub fn view_mut(&mut self) -> &mut View {
259− self.tree.get_mut(self.tree.focus)
260− }
261−
262 pub fn ensure_cursor_in_view(&mut self, id: ViewId) {
243 pub fn ensure_cursor_in_view(&mut self, id: ViewId) {
263 let view = self.tree.get_mut(id);
244 let view = self.tree.get_mut(id);
264 let doc = &self.documents[view.doc];
245 let doc = &self.documents[view.doc];
@@ -280,7 +261,7 @@ impl Editor {
280
261
281 pub fn cursor(&self) -> (Option<Position>, CursorKind) {
262 pub fn cursor(&self) -> (Option<Position>, CursorKind) {
282 const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
263 const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
283− let view = self.view();
264+ let view = view!(self);
284 let doc = &self.documents[view.doc];
265 let doc = &self.documents[view.doc];
285 let cursor = doc.selection(view.id).cursor();
266 let cursor = doc.selection(view.id).cursor();
286 if let Some(mut pos) = view.screen_coords_at_pos(doc, doc.text().slice(..), cursor) {
267 if let Some(mut pos) = view.screen_coords_at_pos(doc, doc.text().slice(..), cursor) {
@@ -291,4 +272,22 @@ impl Editor {
291 (None, CursorKind::Hidden)
272 (None, CursorKind::Hidden)
292 }
273 }
293 }
274 }
275+
276+ /// Closes language servers with timeout. The default timeout is 500 ms, use
277+ /// `timeout` parameter to override this.
278+ pub async fn close_language_servers(
279+ &self,
280+ timeout: Option<u64>,
281+ ) -> Result<(), tokio::time::error::Elapsed> {
282+ tokio::time::timeout(
283+ Duration::from_millis(timeout.unwrap_or(500)),
284+ future::join_all(
285+ self.language_servers
286+ .iter_clients()
287+ .map(|client| client.force_shutdown()),
288+ ),
289+ )
290+ .await
291+ .map(|_| ())
292+ }
294}
293}
helix-view/src/input.rsnew file+226
@@ -0,0 +1,226 @@
1+//! Input event handling, currently backed by crossterm.
2+use anyhow::{anyhow, Error};
3+use crossterm::event;
4+use serde::de::{self, Deserialize, Deserializer};
5+use std::fmt;
6+
7+pub use crossterm::event::{KeyCode, KeyModifiers};
8+
9+/// Represents a key event.
10+// We use a newtype here because we want to customize Deserialize and Display.
11+#[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Copy, Hash)]
12+pub struct KeyEvent {
13+ pub code: KeyCode,
14+ pub modifiers: KeyModifiers,
15+}
16+
17+impl fmt::Display for KeyEvent {
18+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
19+ f.write_fmt(format_args!(
20+ "{}{}{}",
21+ if self.modifiers.contains(KeyModifiers::SHIFT) {
22+ "S-"
23+ } else {
24+ ""
25+ },
26+ if self.modifiers.contains(KeyModifiers::ALT) {
27+ "A-"
28+ } else {
29+ ""
30+ },
31+ if self.modifiers.contains(KeyModifiers::CONTROL) {
32+ "C-"
33+ } else {
34+ ""
35+ },
36+ ))?;
37+ match self.code {
38+ KeyCode::Backspace => f.write_str("backspace")?,
39+ KeyCode::Enter => f.write_str("ret")?,
40+ KeyCode::Left => f.write_str("left")?,
41+ KeyCode::Right => f.write_str("right")?,
42+ KeyCode::Up => f.write_str("up")?,
43+ KeyCode::Down => f.write_str("down")?,
44+ KeyCode::Home => f.write_str("home")?,
45+ KeyCode::End => f.write_str("end")?,
46+ KeyCode::PageUp => f.write_str("pageup")?,
47+ KeyCode::PageDown => f.write_str("pagedown")?,
48+ KeyCode::Tab => f.write_str("tab")?,
49+ KeyCode::BackTab => f.write_str("backtab")?,
50+ KeyCode::Delete => f.write_str("del")?,
51+ KeyCode::Insert => f.write_str("ins")?,
52+ KeyCode::Null => f.write_str("null")?,
53+ KeyCode::Esc => f.write_str("esc")?,
54+ KeyCode::Char('<') => f.write_str("lt")?,
55+ KeyCode::Char('>') => f.write_str("gt")?,
56+ KeyCode::Char('+') => f.write_str("plus")?,
57+ KeyCode::Char('-') => f.write_str("minus")?,
58+ KeyCode::Char(';') => f.write_str("semicolon")?,
59+ KeyCode::Char('%') => f.write_str("percent")?,
60+ KeyCode::F(i) => f.write_fmt(format_args!("F{}", i))?,
61+ KeyCode::Char(c) => f.write_fmt(format_args!("{}", c))?,
62+ };
63+ Ok(())
64+ }
65+}
66+
67+impl std::str::FromStr for KeyEvent {
68+ type Err = Error;
69+
70+ fn from_str(s: &str) -> Result<Self, Self::Err> {
71+ let mut tokens: Vec<_> = s.split('-').collect();
72+ let code = match tokens.pop().ok_or_else(|| anyhow!("Missing key code"))? {
73+ "backspace" => KeyCode::Backspace,
74+ "space" => KeyCode::Char(' '),
75+ "ret" => KeyCode::Enter,
76+ "lt" => KeyCode::Char('<'),
77+ "gt" => KeyCode::Char('>'),
78+ "plus" => KeyCode::Char('+'),
79+ "minus" => KeyCode::Char('-'),
80+ "semicolon" => KeyCode::Char(';'),
81+ "percent" => KeyCode::Char('%'),
82+ "left" => KeyCode::Left,
83+ "right" => KeyCode::Right,
84+ "up" => KeyCode::Down,
85+ "home" => KeyCode::Home,
86+ "end" => KeyCode::End,
87+ "pageup" => KeyCode::PageUp,
88+ "pagedown" => KeyCode::PageDown,
89+ "tab" => KeyCode::Tab,
90+ "backtab" => KeyCode::BackTab,
91+ "del" => KeyCode::Delete,
92+ "ins" => KeyCode::Insert,
93+ "null" => KeyCode::Null,
94+ "esc" => KeyCode::Esc,
95+ single if single.len() == 1 => KeyCode::Char(single.chars().next().unwrap()),
96+ function if function.len() > 1 && function.starts_with('F') => {
97+ let function: String = function.chars().skip(1).collect();
98+ let function = str::parse::<u8>(&function)?;
99+ (function > 0 && function < 13)
100+ .then(|| KeyCode::F(function))
101+ .ok_or_else(|| anyhow!("Invalid function key '{}'", function))?
102+ }
103+ invalid => return Err(anyhow!("Invalid key code '{}'", invalid)),
104+ };
105+
106+ let mut modifiers = KeyModifiers::empty();
107+ for token in tokens {
108+ let flag = match token {
109+ "S" => KeyModifiers::SHIFT,
110+ "A" => KeyModifiers::ALT,
111+ "C" => KeyModifiers::CONTROL,
112+ _ => return Err(anyhow!("Invalid key modifier '{}-'", token)),
113+ };
114+
115+ if modifiers.contains(flag) {
116+ return Err(anyhow!("Repeated key modifier '{}-'", token));
117+ }
118+ modifiers.insert(flag);
119+ }
120+
121+ Ok(KeyEvent { code, modifiers })
122+ }
123+}
124+
125+impl<'de> Deserialize<'de> for KeyEvent {
126+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
127+ where
128+ D: Deserializer<'de>,
129+ {
130+ let s = String::deserialize(deserializer)?;
131+ s.parse().map_err(de::Error::custom)
132+ }
133+}
134+
135+impl From<event::KeyEvent> for KeyEvent {
136+ fn from(event::KeyEvent { code, modifiers }: event::KeyEvent) -> KeyEvent {
137+ KeyEvent { code, modifiers }
138+ }
139+}
140+
141+#[cfg(test)]
142+mod test {
143+ use super::*;
144+
145+ #[test]
146+ fn parsing_unmodified_keys() {
147+ assert_eq!(
148+ str::parse::<KeyEvent>("backspace").unwrap(),
149+ KeyEvent {
150+ code: KeyCode::Backspace,
151+ modifiers: KeyModifiers::NONE
152+ }
153+ );
154+
155+ assert_eq!(
156+ str::parse::<KeyEvent>("left").unwrap(),
157+ KeyEvent {
158+ code: KeyCode::Left,
159+ modifiers: KeyModifiers::NONE
160+ }
161+ );
162+
163+ assert_eq!(
164+ str::parse::<KeyEvent>(",").unwrap(),
165+ KeyEvent {
166+ code: KeyCode::Char(','),
167+ modifiers: KeyModifiers::NONE
168+ }
169+ );
170+
171+ assert_eq!(
172+ str::parse::<KeyEvent>("w").unwrap(),
173+ KeyEvent {
174+ code: KeyCode::Char('w'),
175+ modifiers: KeyModifiers::NONE
176+ }
177+ );
178+
179+ assert_eq!(
180+ str::parse::<KeyEvent>("F12").unwrap(),
181+ KeyEvent {
182+ code: KeyCode::F(12),
183+ modifiers: KeyModifiers::NONE
184+ }
185+ );
186+ }
187+
188+ #[test]
189+ fn parsing_modified_keys() {
190+ assert_eq!(
191+ str::parse::<KeyEvent>("S-minus").unwrap(),
192+ KeyEvent {
193+ code: KeyCode::Char('-'),
194+ modifiers: KeyModifiers::SHIFT
195+ }
196+ );
197+
198+ assert_eq!(
199+ str::parse::<KeyEvent>("C-A-S-F12").unwrap(),
200+ KeyEvent {
201+ code: KeyCode::F(12),
202+ modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL | KeyModifiers::ALT
203+ }
204+ );
205+
206+ assert_eq!(
207+ str::parse::<KeyEvent>("S-C-2").unwrap(),
208+ KeyEvent {
209+ code: KeyCode::Char('2'),
210+ modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL
211+ }
212+ );
213+ }
214+
215+ #[test]
216+ fn parsing_nonsensical_keys_fails() {
217+ assert!(str::parse::<KeyEvent>("F13").is_err());
218+ assert!(str::parse::<KeyEvent>("F0").is_err());
219+ assert!(str::parse::<KeyEvent>("aaa").is_err());
220+ assert!(str::parse::<KeyEvent>("S-S-a").is_err());
221+ assert!(str::parse::<KeyEvent>("C-A-S-C-1").is_err());
222+ assert!(str::parse::<KeyEvent>("FU").is_err());
223+ assert!(str::parse::<KeyEvent>("123").is_err());
224+ assert!(str::parse::<KeyEvent>("S--").is_err());
225+ }
226+}
helix-view/src/lib.rs+8−3
@@ -1,13 +1,18 @@
1+#[macro_use]
2+pub mod macros;
3+
1pub mod document;
4pub mod document;
2pub mod editor;
5pub mod editor;
6+pub mod input;
3pub mod register_selection;
7pub mod register_selection;
4pub mod theme;
8pub mod theme;
5pub mod tree;
9pub mod tree;
6pub mod view;
10pub mod view;
7
11
8−use slotmap::new_key_type;
12+slotmap::new_key_type! {
9−new_key_type! { pub struct DocumentId; }
13+ pub struct DocumentId;
10−new_key_type! { pub struct ViewId; }
14+ pub struct ViewId;
15+}
11
16
12pub use document::Document;
17pub use document::Document;
13pub use editor::Editor;
18pub use editor::Editor;
helix-view/src/macros.rsnew file+29
@@ -0,0 +1,29 @@
1+#[macro_export]
2+macro_rules! current {
3+ ( $( $editor:ident ).+ ) => {{
4+ let view = $crate::view_mut!( $( $editor ).+ );
5+ let doc = &mut $( $editor ).+ .documents[view.doc];
6+ (view, doc)
7+ }};
8+}
9+
10+#[macro_export]
11+macro_rules! doc_mut {
12+ ( $( $editor:ident ).+ ) => {{
13+ $crate::current!( $( $editor ).+ ).1
14+ }};
15+}
16+
17+#[macro_export]
18+macro_rules! view_mut {
19+ ( $( $editor:ident ).+ ) => {{
20+ $( $editor ).+ .tree.get_mut($( $editor ).+ .tree.focus)
21+ }};
22+}
23+
24+#[macro_export]
25+macro_rules! view {
26+ ( $( $editor:ident ).+ ) => {{
27+ $( $editor ).+ .tree.get($( $editor ).+ .tree.focus)
28+ }};
29+}
theme.toml+2−1
@@ -41,7 +41,8 @@
41"ui.background" = { bg = "#3b224c" } # midnight
41"ui.background" = { bg = "#3b224c" } # midnight
42"ui.linenr" = { fg = "#5a5977" } # comet
42"ui.linenr" = { fg = "#5a5977" } # comet
43"ui.linenr.selected" = { fg = "#dbbfef" } # lilac
43"ui.linenr.selected" = { fg = "#dbbfef" } # lilac
44+"ui.statusline" = { fg = "#dbbfef", bg = "#281733" } # revolver
44−"ui.statusline" = { bg = "#281733" } # revolver
45+"ui.statusline.inactive" = { fg = "#a4a0e8", bg = "#281733" } # revolver
45"ui.popup" = { bg = "#281733" } # revolver
46"ui.popup" = { bg = "#281733" } # revolver
46"ui.window" = { bg = "#452859" } # bossa nova
47"ui.window" = { bg = "#452859" } # bossa nova
47"ui.window" = { bg = "#452859" } # bossa nova
48"ui.window" = { bg = "#452859" } # bossa nova