on master help sign in
Merge remote-tracking branch 'origin/master' into line_ending_detection
commitcdd9347457f0608346894cd0aab35b412cb59a7b authorJan Hrastnik <jan.hrastnik2@gmail.com> dateSat 19 Jun 2021, 14:51 (5 years ago) parent97323dc2cbec2b
35 files changed+1704−750
Cargo.lock+3−2 book/src/SUMMARY.md+1 book/src/configuration.md+11−3 book/src/install.md+5−2 book/src/remapping.md+48 contrib/themes/bogster.toml+3−2 contrib/themes/ingrid.toml+2−1 contrib/themes/onedark.toml+1 flake.lock+13−13 helix-core/Cargo.toml+7−5 helix-core/src/lib.rs+2 helix-lsp/Cargo.toml+6−2 helix-lsp/src/client.rs+78−46 helix-lsp/src/lib.rs+121−6 helix-lsp/src/transport.rs+15−6 helix-syntax/Cargo.toml+5−2 helix-term/Cargo.toml+7−3 helix-term/src/application.rs+137−59 helix-term/src/commands.rs+488−313 helix-term/src/config.rs+63 helix-term/src/keymap.rs+214−192 helix-term/src/lib.rs+4 helix-term/src/main.rs+9−4 helix-term/src/ui/completion.rs+3−3 helix-term/src/ui/editor.rs+25−29 helix-term/src/ui/mod.rs+20−8 helix-term/src/ui/prompt.rs+17−2 helix-tui/Cargo.toml+4 helix-view/Cargo.toml+7−2 helix-view/src/document.rs+96−16 helix-view/src/editor.rs+24−25 helix-view/src/input.rs+226 helix-view/src/lib.rs+8−3 helix-view/src/macros.rs+29 theme.toml+2−1
Cargo.lock+3−2
@@ -342,6 +342,7 @@ version = "0.2.0"
342342dependencies = [
343343 "anyhow",
344344 "crossterm",
345+ "futures-util",
345346 "helix-core",
346347 "helix-lsp",
347348 "helix-tui",
@@ -672,9 +673,9 @@ checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
672673
673674[[package]]
674675name = "ropey"
675−version = "1.2.0"
676+version = "1.3.0"
676677source = "registry+https://github.com/rust-lang/crates.io-index"
677−checksum = "f0f3ef16589fdbb3e8fbce3dca944c08e61f39c7f16064b21a257d68ea911a83"
678+checksum = "e4b1996ad82fb6b8ac0711329432d80aa1ad5d70a0fe6edd1a9cf6fd2375fbb2"
678679dependencies = [
679680 "smallvec",
680681]
book/src/SUMMARY.md+1
@@ -4,4 +4,5 @@
44- [Usage](./usage.md)
55- [Configuration](./configuration.md)
66 - [Keymap](./keymap.md)
7+ - [Key Remapping](./remapping.md)
78 - [Hooks](./hooks.md)
book/src/configuration.md+11−3
@@ -1,5 +1,12 @@
11# Configuration
22
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+
310## Theme
411
512Use 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:
3239| `dim` |
3340| `italic` |
3441| `underlined` |
35−| `slow\_blink` |
36−| `rapid\_blink` |
42+| `slow_blink` |
43+| `rapid_blink` |
3744| `reversed` |
3845| `hidden` |
39−| `crossed\_out` |
46+| `crossed_out` |
4047
4148Possible keys:
4249
@@ -87,3 +94,4 @@ Possible keys:
8794These 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.
8895
8996For 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
44
55## OSX
66
7−TODO: brew tap
7+A Homebrew tap is available:
88
9−Please use a pre-built binary release for the time being.
9+```
10+brew tap helix-editor/helix
11+brew install helix
12+```
1013
1114## Linux
1215
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 @@
3131"ui.background" = { bg = "#161c23" }
3232"ui.linenr" = { fg = "#415367" }
3333"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" }
3536"ui.popup" = { bg = "#232d38" }
3637"ui.window" = { bg = "#232d38" }
3738"ui.help" = { bg = "#232d38", fg = "#e5ded6" }
@@ -39,7 +40,7 @@
3940"ui.text" = { fg = "#e5ded6" }
4041"ui.text.focus" = { fg = "#e5ded6", modifiers= ["bold"] }
4142
42−"ui.selection" = { bg = "#540099" }
43+"ui.selection" = { bg = "#313f4e" }
4344"ui.menu.selected" = { fg = "#e5ded6", bg = "#313f4e" }
4445
4546"warning" = "#dc7759"
contrib/themes/ingrid.toml+2−1
@@ -31,7 +31,8 @@
3131"ui.background" = { bg = "#FFFCFD" }
3232"ui.linenr" = { fg = "#bbbbbb" }
3333"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" }
3536"ui.popup" = { bg = "#F3EAE9" }
3637"ui.window" = { bg = "#D8B8B3" }
3738"ui.help" = { bg = "#D8B8B3", fg = "#250E07" }
contrib/themes/onedark.toml+1
@@ -36,6 +36,7 @@
3636"ui.linenr.selected" = { fg = "#ABB2BF" }
3737"ui.popup" = { bg = "#3E4452" }
3838"ui.statusline" = { fg = "#ABB2BF", bg = "#2C323C" }
39+"ui.statusline.inactive" = { fg = "#ABB2Bf", bg = "#2C323C" }
3940"ui.selection" = { bg = "#3E4452" }
4041"ui.text" = { fg = "#ABB2BF", bg = "#282C34" }
4142"ui.text.focus" = { fg = "#ABB2BF", bg = "#2C323C", modifiers = ['bold'] }
flake.lock+13−13
@@ -34,11 +34,11 @@
3434 "helix": {
3535 "flake": false,
3636 "locked": {
37− "lastModified": 1623545930,
38− "narHash": "sha256-14ASoYbxXHU/qPGctiUymb4fMRCoih9c7YujjxqEkdU=",
37+ "lastModified": 1624077450,
38+ "narHash": "sha256-gcL519tetuEv0N+oBP8X3U4bPjwcAgzbPdgwW19qeVI=",
3939 "ref": "master",
40− "rev": "9640ed1425f2db904fb42cd0c54dc6fbc05ca292",
41− "revCount": 821,
40+ "rev": "1c2585202145467f0fde7ad9c571e462081c3656",
41+ "revCount": 894,
4242 "submodules": true,
4343 "type": "git",
4444 "url": "https://github.com/helix-editor/helix.git"
@@ -58,11 +58,11 @@
5858 "rustOverlay": "rustOverlay"
5959 },
6060 "locked": {
61− "lastModified": 1623591988,
62− "narHash": "sha256-a8E5LYKxYjHmBWZsFxKnCBVGsWHFEWrKjeAJkplWrfI=",
61+ "lastModified": 1624070370,
62+ "narHash": "sha256-sfFqfmerCYvk0jDeP1gfuskz7AaqDsgV8aiQrEUGdsc=",
6363 "owner": "yusdacra",
6464 "repo": "nix-cargo-integration",
65− "rev": "8254b71eddd4e85173eddc189174b873fad85360",
65+ "rev": "85e6c1ba4c0e3e6dec5a7d1f65bcc036d2ea6ae3",
6666 "type": "github"
6767 },
6868 "original": {
@@ -73,11 +73,11 @@
7373 },
7474 "nixpkgs": {
7575 "locked": {
76− "lastModified": 1623324058,
77− "narHash": "sha256-Jm9GUTXdjXz56gWDKy++EpFfjrBaxqXlLvTLfgEi8lo=",
76+ "lastModified": 1623580589,
77+ "narHash": "sha256-Ayp1cjXpwFCkAiWUE46rj9APTltsiEBdIs2+cj+U7+c=",
7878 "owner": "nixos",
7979 "repo": "nixpkgs",
80− "rev": "432fc2d9a67f92e05438dff5fdc2b39d33f77997",
80+ "rev": "fa0326ce5233f7d592271df52c9d0812bec47b84",
8181 "type": "github"
8282 },
8383 "original": {
@@ -98,11 +98,11 @@
9898 "rustOverlay": {
9999 "flake": false,
100100 "locked": {
101− "lastModified": 1623550815,
102− "narHash": "sha256-RumRrkE6OTJDndHV4qZNZv8kUGnzwRHZQSyzx29r6/g=",
101+ "lastModified": 1624069337,
102+ "narHash": "sha256-9mTcx7osE4biF2Hm/GU19s1T3+KvphWj4QaUcJh39lU=",
103103 "owner": "oxalica",
104104 "repo": "rust-overlay",
105− "rev": "9824f142cbd7bc3e2a92eefbb79addfff8704cd3",
105+ "rev": "67dc2a9543a7c24591e6cb102ad0121c3a704aab",
106106 "type": "github"
107107 },
108108 "original": {
helix-core/Cargo.toml+7−5
@@ -4,17 +4,19 @@ version = "0.2.0"
44authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
55edition = "2018"
66license = "MPL-2.0"
7−
8−# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
9−
7+description = "Helix editor core editing primitives"
8+categories = ["editor"]
9+repository = "https://github.com/helix-editor/helix"
10+homepage = "https://helix-editor.com"
11+include = ["src/**/*", "README.md"]
1012
1113[features]
1214embed_runtime = ["rust-embed"]
1315
1416[dependencies]
15−helix-syntax = { path = "../helix-syntax" }
17+helix-syntax = { version = "0.2", path = "../helix-syntax" }
1618
17−ropey = "1.2"
19+ropey = "1.3"
1820smallvec = "1.4"
1921tendril = "0.4.2"
2022unicode-segmentation = "1.7.1"
helix-core/src/lib.rs+2
@@ -90,6 +90,8 @@ pub fn cache_dir() -> std::path::PathBuf {
9090 path
9191}
9292
93+pub use etcetera::home_dir;
94+
9395use etcetera::base_strategy::{choose_base_strategy, BaseStrategy};
9496
9597pub use ropey::{Rope, RopeSlice};
helix-lsp/Cargo.toml+6−2
@@ -4,11 +4,15 @@ version = "0.2.0"
44authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
55edition = "2018"
66license = "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"
711
812# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
913
1014[dependencies]
11−helix-core = { path = "../helix-core" }
15+helix-core = { version = "0.2", path = "../helix-core" }
1216
1317anyhow = "1.0"
1418futures-executor = "0.3"
@@ -20,4 +24,4 @@ serde = { version = "1.0", features = ["derive"] }
2024serde_json = "1.0"
2125thiserror = "1.0"
2226tokio = { 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::{
1818
1919#[derive(Debug)]
2020pub struct Client {
21+ id: usize,
2122 _process: Child,
2223 server_tx: UnboundedSender<Payload>,
2324 request_counter: AtomicU64,
@@ -26,7 +27,11 @@ pub struct Client {
2627}
2728
2829impl Client {
29− pub fn start(cmd: &str, args: &[String]) -> Result<(Self, UnboundedReceiver<Call>)> {
30+ pub fn start(
31+ cmd: &str,
32+ args: &[String],
33+ id: usize,
34+ ) -> Result<(Self, UnboundedReceiver<(usize, Call)>)> {
3035 let process = Command::new(cmd)
3136 .args(args)
3237 .stdin(Stdio::piped())
@@ -43,9 +48,10 @@ impl Client {
4348 let reader = BufReader::new(process.stdout.take().expect("Failed to open stdout"));
4449 let stderr = BufReader::new(process.stderr.take().expect("Failed to open stderr"));
4550
46− let (server_rx, server_tx) = Transport::start(reader, writer, stderr);
51+ let (server_rx, server_tx) = Transport::start(reader, writer, stderr, id);
4752
4853 let client = Self {
54+ id,
4955 _process: process,
5056 server_tx,
5157 request_counter: AtomicU64::new(0),
@@ -59,6 +65,10 @@ impl Client {
5965 Ok((client, server_rx))
6066 }
6167
68+ pub fn id(&self) -> usize {
69+ self.id
70+ }
71+
6272 fn next_request_id(&self) -> jsonrpc::Id {
6373 let id = self.request_counter.fetch_add(1, Ordering::Relaxed);
6474 jsonrpc::Id::Num(id)
@@ -165,31 +175,35 @@ impl Client {
165175 }
166176
167177 /// Reply to a language server RPC call.
168− pub async fn reply(
178+ pub fn reply(
169179 &self,
170180 id: jsonrpc::Id,
171181 result: core::result::Result<Value, jsonrpc::Error>,
172− ) -> Result<()> {
182+ ) -> impl Future<Output = Result<()>> {
173183 use jsonrpc::{Failure, Output, Success, Version};
174184
175− let output = match result {
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− };
185+ let server_tx = self.server_tx.clone();
187186
188− self.server_tx
189− .send(Payload::Response(output))
190− .map_err(|e| Error::Other(e.into()))?;
187+ async move {
188+ let output = match result {
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+ };
191200
192− Ok(())
201+ server_tx
202+ .send(Payload::Response(output))
203+ .map_err(|e| Error::Other(e.into()))?;
204+
205+ Ok(())
206+ }
193207 }
194208
195209 // -------------------------------------------------------------------------------------------
@@ -229,8 +243,7 @@ impl Client {
229243 ..Default::default()
230244 }),
231245 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),
234247 ..Default::default()
235248 }),
236249 ..Default::default()
@@ -259,6 +272,21 @@ impl Client {
259272 self.notify::<lsp::notification::Exit>(())
260273 }
261274
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+
262290 // -------------------------------------------------------------------------------------------
263291 // Text document
264292 // -------------------------------------------------------------------------------------------
@@ -465,6 +493,7 @@ impl Client {
465493 &self,
466494 text_document: lsp::TextDocumentIdentifier,
467495 position: lsp::Position,
496+ work_done_token: Option<lsp::ProgressToken>,
468497 ) -> impl Future<Output = Result<Value>> {
469498 // ) -> Result<Vec<lsp::CompletionItem>> {
470499 let params = lsp::CompletionParams {
@@ -473,9 +502,7 @@ impl Client {
473502 position,
474503 },
475504 // TODO: support these tokens by async receiving and updating the choice list
476− work_done_progress_params: lsp::WorkDoneProgressParams {
477− work_done_token: None,
478− },
505+ work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
479506 partial_result_params: lsp::PartialResultParams {
480507 partial_result_token: None,
481508 },
@@ -490,15 +517,14 @@ impl Client {
490517 &self,
491518 text_document: lsp::TextDocumentIdentifier,
492519 position: lsp::Position,
520+ work_done_token: Option<lsp::ProgressToken>,
493521 ) -> impl Future<Output = Result<Value>> {
494522 let params = lsp::SignatureHelpParams {
495523 text_document_position_params: lsp::TextDocumentPositionParams {
496524 text_document,
497525 position,
498526 },
499− work_done_progress_params: lsp::WorkDoneProgressParams {
500− work_done_token: None,
501− },
527+ work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
502528 context: None,
503529 // lsp::SignatureHelpContext
504530 };
@@ -510,15 +536,14 @@ impl Client {
510536 &self,
511537 text_document: lsp::TextDocumentIdentifier,
512538 position: lsp::Position,
539+ work_done_token: Option<lsp::ProgressToken>,
513540 ) -> impl Future<Output = Result<Value>> {
514541 let params = lsp::HoverParams {
515542 text_document_position_params: lsp::TextDocumentPositionParams {
516543 text_document,
517544 position,
518545 },
519− work_done_progress_params: lsp::WorkDoneProgressParams {
520− work_done_token: None,
521− },
546+ work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
522547 // lsp::SignatureHelpContext
523548 };
524549
@@ -531,6 +556,7 @@ impl Client {
531556 &self,
532557 text_document: lsp::TextDocumentIdentifier,
533558 options: lsp::FormattingOptions,
559+ work_done_token: Option<lsp::ProgressToken>,
534560 ) -> anyhow::Result<Vec<lsp::TextEdit>> {
535561 let capabilities = self.capabilities.as_ref().unwrap();
536562
@@ -545,9 +571,7 @@ impl Client {
545571 let params = lsp::DocumentFormattingParams {
546572 text_document,
547573 options,
548− work_done_progress_params: lsp::WorkDoneProgressParams {
549− work_done_token: None,
550− },
574+ work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
551575 };
552576
553577 let response = self.request::<lsp::request::Formatting>(params).await?;
@@ -560,6 +584,7 @@ impl Client {
560584 text_document: lsp::TextDocumentIdentifier,
561585 range: lsp::Range,
562586 options: lsp::FormattingOptions,
587+ work_done_token: Option<lsp::ProgressToken>,
563588 ) -> anyhow::Result<Vec<lsp::TextEdit>> {
564589 let capabilities = self.capabilities.as_ref().unwrap();
565590
@@ -575,9 +600,7 @@ impl Client {
575600 text_document,
576601 range,
577602 options,
578− work_done_progress_params: lsp::WorkDoneProgressParams {
579− work_done_token: None,
580− },
603+ work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
581604 };
582605
583606 let response = self
@@ -596,15 +619,14 @@ impl Client {
596619 &self,
597620 text_document: lsp::TextDocumentIdentifier,
598621 position: lsp::Position,
622+ work_done_token: Option<lsp::ProgressToken>,
599623 ) -> impl Future<Output = Result<Value>> {
600624 let params = lsp::GotoDefinitionParams {
601625 text_document_position_params: lsp::TextDocumentPositionParams {
602626 text_document,
603627 position,
604628 },
605− work_done_progress_params: lsp::WorkDoneProgressParams {
606− work_done_token: None,
607− },
629+ work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
608630 partial_result_params: lsp::PartialResultParams {
609631 partial_result_token: None,
610632 },
@@ -617,30 +639,42 @@ impl Client {
617639 &self,
618640 text_document: lsp::TextDocumentIdentifier,
619641 position: lsp::Position,
642+ work_done_token: Option<lsp::ProgressToken>,
620643 ) -> 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)
622645 }
623646
624647 pub fn goto_type_definition(
625648 &self,
626649 text_document: lsp::TextDocumentIdentifier,
627650 position: lsp::Position,
651+ work_done_token: Option<lsp::ProgressToken>,
628652 ) -> 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+ )
630658 }
631659
632660 pub fn goto_implementation(
633661 &self,
634662 text_document: lsp::TextDocumentIdentifier,
635663 position: lsp::Position,
664+ work_done_token: Option<lsp::ProgressToken>,
636665 ) -> 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+ )
638671 }
639672
640673 pub fn goto_reference(
641674 &self,
642675 text_document: lsp::TextDocumentIdentifier,
643676 position: lsp::Position,
677+ work_done_token: Option<lsp::ProgressToken>,
644678 ) -> impl Future<Output = Result<Value>> {
645679 let params = lsp::ReferenceParams {
646680 text_document_position: lsp::TextDocumentPositionParams {
@@ -650,9 +684,7 @@ impl Client {
650684 context: lsp::ReferenceContext {
651685 include_declaration: true,
652686 },
653− work_done_progress_params: lsp::WorkDoneProgressParams {
654− work_done_token: None,
655− },
687+ work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
656688 partial_result_params: lsp::PartialResultParams {
657689 partial_result_token: None,
658690 },
helix-lsp/src/lib.rs+121−6
@@ -13,7 +13,10 @@ use helix_core::syntax::LanguageConfiguration;
1313
1414use std::{
1515 collections::{hash_map::Entry, HashMap},
16− sync::Arc,
16+ sync::{
17+ atomic::{AtomicUsize, Ordering},
18+ Arc,
19+ },
1720};
1821
1922use serde::{Deserialize, Serialize};
@@ -181,6 +184,30 @@ pub mod util {
181184 }
182185}
183186
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+
184211#[derive(Debug, PartialEq, Clone)]
185212pub enum Notification {
186213 PublishDiagnostics(lsp::PublishDiagnosticsParams),
@@ -230,9 +257,10 @@ impl Notification {
230257
231258#[derive(Debug)]
232259pub struct Registry {
233− inner: HashMap<LanguageId, Arc<Client>>,
260+ inner: HashMap<LanguageId, (usize, Arc<Client>)>,
234261
235− pub incoming: SelectAll<UnboundedReceiverStream<Call>>,
262+ counter: AtomicUsize,
263+ pub incoming: SelectAll<UnboundedReceiverStream<(usize, Call)>>,
236264}
237265
238266impl Default for Registry {
@@ -245,10 +273,18 @@ impl Registry {
245273 pub fn new() -> Self {
246274 Self {
247275 inner: HashMap::new(),
276+ counter: AtomicUsize::new(0),
248277 incoming: SelectAll::new(),
249278 }
250279 }
251280
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+
252288 pub fn get(&mut self, language_config: &LanguageConfiguration) -> Result<Arc<Client>> {
253289 if let Some(config) = &language_config.language_server {
254290 // avoid borrow issues
@@ -256,16 +292,17 @@ impl Registry {
256292 let s_incoming = &mut self.incoming;
257293
258294 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()),
260296 Entry::Vacant(entry) => {
261297 // initialize a new client
262− let (mut client, incoming) = Client::start(&config.command, &config.args)?;
298+ let id = self.counter.fetch_add(1, Ordering::Relaxed);
299+ let (mut client, incoming) = Client::start(&config.command, &config.args, id)?;
263300 // TODO: run this async without blocking
264301 futures_executor::block_on(client.initialize())?;
265302 s_incoming.push(UnboundedReceiverStream::new(incoming));
266303 let client = Arc::new(client);
267304
268− entry.insert(client.clone());
305+ entry.insert((id, client.clone()));
269306 Ok(client)
270307 }
271308 }
@@ -273,6 +310,84 @@ impl Registry {
273310 Err(Error::LspNotDefined)
274311 }
275312 }
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+ }
276391}
277392
278393// REGISTRY = HashMap<LanguageId, Lazy/OnceCell<Arc<RwLock<Client>>>
helix-lsp/src/transport.rs+15−6
@@ -1,4 +1,5 @@
11use crate::Result;
2+use anyhow::Context;
23use jsonrpc_core as jsonrpc;
34use log::{error, info};
45use serde::{Deserialize, Serialize};
@@ -33,7 +34,8 @@ enum ServerMessage {
3334
3435#[derive(Debug)]
3536pub struct Transport {
36− client_tx: UnboundedSender<jsonrpc::Call>,
37+ id: usize,
38+ client_tx: UnboundedSender<(usize, jsonrpc::Call)>,
3739 client_rx: UnboundedReceiver<Payload>,
3840
3941 pending_requests: HashMap<jsonrpc::Id, Sender<Result<Value>>>,
@@ -48,11 +50,16 @@ impl Transport {
4850 server_stdout: BufReader<ChildStdout>,
4951 server_stdin: BufWriter<ChildStdin>,
5052 server_stderr: BufReader<ChildStderr>,
51− ) -> (UnboundedReceiver<jsonrpc::Call>, UnboundedSender<Payload>) {
53+ id: usize,
54+ ) -> (
55+ UnboundedReceiver<(usize, jsonrpc::Call)>,
56+ UnboundedSender<Payload>,
57+ ) {
5258 let (client_tx, rx) = unbounded_channel();
5359 let (tx, client_rx) = unbounded_channel();
5460
5561 let transport = Self {
62+ id,
5663 server_stdout,
5764 server_stdin,
5865 server_stderr,
@@ -84,7 +91,7 @@ impl Transport {
8491
8592 match (parts.next(), parts.next(), parts.next()) {
8693 (Some("Content-Length"), Some(value), None) => {
87− content_length = Some(value.parse().unwrap());
94+ content_length = Some(value.parse().context("invalid content length")?);
8895 }
8996 (Some(_), Some(_), None) => {}
9097 _ => {
@@ -97,12 +104,12 @@ impl Transport {
97104 }
98105 }
99106
100− let content_length = content_length.unwrap();
107+ let content_length = content_length.context("missing content length")?;
101108
102109 //TODO: reuse vector
103110 let mut content = vec![0; content_length];
104111 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")?;
106113
107114 info!("<- {}", msg);
108115
@@ -156,7 +163,9 @@ impl Transport {
156163 match msg {
157164 ServerMessage::Output(output) => self.process_request_response(output).await?,
158165 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")?;
160169 // let notification = Notification::parse(&method, params);
161170 }
162171 };
helix-syntax/Cargo.toml+5−2
@@ -4,8 +4,11 @@ version = "0.2.0"
44authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
55edition = "2018"
66license = "MPL-2.0"
7−
8−# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+description = "Tree-sitter grammars support"
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/**/*"]
912
1013[dependencies]
1114tree-sitter = "0.19"
helix-term/Cargo.toml+7−3
@@ -5,6 +5,10 @@ description = "A post-modern text editor."
55authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
66edition = "2018"
77license = "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"]
812
913[package.metadata.nix]
1014build = true
@@ -18,9 +22,9 @@ name = "hx"
1822path = "src/main.rs"
1923
2024[dependencies]
21−helix-core = { path = "../helix-core" }
22−helix-view = { path = "../helix-view", features = ["term"]}
23−helix-lsp = { path = "../helix-lsp"}
25+helix-core = { version = "0.2", path = "../helix-core" }
26+helix-view = { version = "0.2", path = "../helix-view", features = ["term"]}
27+helix-lsp = { version = "0.2", path = "../helix-lsp"}
2428
2529anyhow = "1"
2630once_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};
22use helix_view::{document::Mode, Document, Editor, Theme, View};
33
4−use crate::{args::Args, compositor::Compositor, ui};
4+use crate::{args::Args, compositor::Compositor, config::Config, ui};
55
66use log::{error, info};
77
@@ -9,6 +9,7 @@ use std::{
99 future::Future,
1010 io::{self, stdout, Stdout, Write},
1111 path::PathBuf,
12+ pin::Pin,
1213 sync::Arc,
1314 time::Duration,
1415};
@@ -22,8 +23,7 @@ use crossterm::{
2223
2324use tui::layout::Rect;
2425
25−use futures_util::stream::FuturesUnordered;
26−use std::pin::Pin;
26+use futures_util::{future, stream::FuturesUnordered};
2727
2828type BoxFuture<T> = Pin<Box<dyn Future<Output = T> + Send>>;
2929pub type LspCallback =
@@ -37,16 +37,20 @@ pub struct Application {
3737 editor: Editor,
3838
3939 callbacks: LspCallbacks,
40+
41+ lsp_progress: LspProgressMap,
42+ lsp_progress_enabled: bool,
4043}
4144
4245impl Application {
43− pub fn new(mut args: Args) -> Result<Self, Error> {
46+ pub fn new(mut args: Args, config: Config) -> Result<Self, Error> {
4447 use helix_view::editor::Action;
4548 let mut compositor = Compositor::new()?;
4649 let size = compositor.size();
4750 let mut editor = Editor::new(size);
4851
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);
5054
5155 if !args.files.is_empty() {
5256 let first = &args.files[0]; // we know it's not empty
@@ -73,6 +77,8 @@ impl Application {
7377 editor,
7478
7579 callbacks: FuturesUnordered::new(),
80+ lsp_progress: LspProgressMap::new(),
81+ lsp_progress_enabled: config.global.lsp_progress,
7682 };
7783
7884 Ok(app)
@@ -108,8 +114,8 @@ impl Application {
108114 event = reader.next() => {
109115 self.handle_terminal_events(event)
110116 }
111− Some(call) = self.editor.language_servers.incoming.next() => {
112− self.handle_language_server_message(call).await
117+ Some((id, call)) = self.editor.language_servers.incoming.next() => {
118+ self.handle_language_server_message(call, id).await
113119 }
114120 Some(callback) = &mut self.callbacks.next() => {
115121 self.handle_language_server_callback(callback)
@@ -152,8 +158,12 @@ impl Application {
152158 }
153159 }
154160
155− pub async fn handle_language_server_message(&mut self, call: helix_lsp::Call) {
156− use helix_lsp::{Call, Notification};
161+ pub async fn handle_language_server_message(
162+ &mut self,
163+ call: helix_lsp::Call,
164+ server_id: usize,
165+ ) {
166+ use helix_lsp::{Call, MethodCall, Notification};
157167 match call {
158168 Call::Notification(helix_lsp::jsonrpc::Notification { method, params, .. }) => {
159169 let notification = match Notification::parse(&method, params) {
@@ -241,64 +251,130 @@ impl Application {
241251 log::warn!("unhandled window/logMessage: {:?}", params);
242252 }
243253 Notification::ProgressMessage(params) => {
244− let token = match params.token {
245− lsp::NumberOrString::Number(n) => n.to_string(),
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 {
280+ lsp::NumberOrString::Number(n) => n,
246281 lsp::NumberOrString::String(s) => s,
247282 };
248− let msg = {
249− let lsp::ProgressParamsValue::WorkDone(work) = params.value;
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)) => {
275− format!("{}% {} - {}", percentage, title, message)
276− }
277− (Some(title), None, Some(percentage)) => {
278− format!("{}% {}", percentage, title)
279− }
280− (Some(title), Some(message), None) => {
281− format!("{} - {}", title, message)
282− }
283− (None, Some(message), Some(percentage)) => {
284− format!("{}% {}", percentage, message)
285− }
286− (Some(title), None, None) => title,
287− (None, Some(message), None) => message,
288− (None, None, Some(percentage)) => format!("{}%", percentage),
289− (None, None, None) => "".into(),
283+
284+ let status = match parts {
285+ (Some(title), Some(message), Some(percentage)) => {
286+ format!("[{}] {}% {} - {}", token_d, percentage, title, message)
287+ }
288+ (Some(title), None, Some(percentage)) => {
289+ format!("[{}] {}% {}", token_d, percentage, title)
290+ }
291+ (Some(title), Some(message), None) => {
292+ format!("[{}] {} - {}", token_d, title, message)
293+ }
294+ (None, Some(message), Some(percentage)) => {
295+ format!("[{}] {}% {}", token_d, percentage, message)
296+ }
297+ (Some(title), None, None) => {
298+ format!("[{}] {}", token_d, title)
290299 }
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),
291307 };
292− let status = format!("[{}] {}", token, msg);
293− self.editor.set_status(status);
294− self.render();
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 {
316+ self.editor.set_status(status);
317+ self.render();
318+ }
295319 }
296320 _ => unreachable!(),
297321 }
298322 }
299− Call::MethodCall(call) => {
300− error!("Method not found {}", call.method);
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 => {
332+ error!("Method not found {}", method);
333+ return;
334+ }
335+ };
301336
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+ }
302378 // self.language_server.reply(
303379 // call.id,
304380 // // TODO: make a Into trait that can cast to Err(jsonrpc::Error)
@@ -330,6 +406,8 @@ impl Application {
330406
331407 self.event_loop().await;
332408
409+ self.editor.close_language_servers(None).await;
410+
333411 // reset cursor shape
334412 write!(stdout, "\x1B[2 q");
335413
helix-term/src/commands.rs+488−313
@@ -11,15 +11,18 @@ use helix_core::{
1111
1212use helix_view::{
1313 document::{IndentStyle, Mode},
14+ input::{KeyCode, KeyEvent},
1415 view::{View, PADDING},
1516 Document, DocumentId, Editor, ViewId,
1617};
1718
19+use anyhow::anyhow;
1820use helix_lsp::{
1921 lsp,
2022 util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range},
2123 OffsetEncoding,
2224};
25+use insert::*;
2326use movement::Movement;
2427
2528use crate::{
@@ -29,15 +32,15 @@ use crate::{
2932
3033use crate::application::{LspCallbackWrapper, LspCallbacks};
3134use futures_util::FutureExt;
32−use std::future::Future;
35+use std::{fmt, future::Future, path::Display, str::FromStr};
3336
3437use std::{
3538 borrow::Cow,
3639 path::{Path, PathBuf},
3740};
3841
39−use crossterm::event::{KeyCode, KeyEvent};
4042use once_cell::sync::Lazy;
43+use serde::de::{self, Deserialize, Deserializer};
4144
4245pub struct Context<'a> {
4346 pub selected_register: helix_view::RegisterSelection,
@@ -50,27 +53,6 @@ pub struct Context<'a> {
5053}
5154
5255impl<'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−
7456 /// Push a new component onto the compositor.
7557 pub fn push_layer(&mut self, mut component: Box<dyn Component>) {
7658 self.callback = Some(Box::new(|compositor: &mut Compositor| {
@@ -107,6 +89,7 @@ impl<'a> Context<'a> {
10789 self.callbacks.push(callback);
10890 }
10991
92+ /// Returns 1 if no explicit count was provided
11093 #[inline]
11194 pub fn count(&self) -> usize {
11295 self.count.map_or(1, |v| v.get())
@@ -132,13 +115,189 @@ fn align_view(doc: &Document, view: &mut View, align: Align) {
132115 view.first_line = line.saturating_sub(relative);
133116}
134117
135−/// A command is a function that takes the current state and a count, and does a side-effect on the
136−/// state (usually by creating and applying a transaction).
137−pub type Command = fn(cx: &mut Context);
118+/// A command is composed of a static name, and a function that takes the current state plus a count,
119+/// and does a side-effect on the state (usually by creating and applying a transaction).
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+}
138281
139−pub fn move_char_left(cx: &mut Context) {
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+
298+fn move_char_left(cx: &mut Context) {
140299 let count = cx.count();
141− let (view, doc) = cx.current();
300+ let (view, doc) = current!(cx.editor);
142301 let text = doc.text().slice(..);
143302 let selection = doc.selection(view.id).transform(|range| {
144303 movement::move_horizontally(text, range, Direction::Backward, count, Movement::Move)
@@ -146,9 +305,9 @@ pub fn move_char_left(cx: &mut Context) {
146305 doc.set_selection(view.id, selection);
147306}
148307
149−pub fn move_char_right(cx: &mut Context) {
308+fn move_char_right(cx: &mut Context) {
150309 let count = cx.count();
151− let (view, doc) = cx.current();
310+ let (view, doc) = current!(cx.editor);
152311 let text = doc.text().slice(..);
153312 let selection = doc.selection(view.id).transform(|range| {
154313 movement::move_horizontally(text, range, Direction::Forward, count, Movement::Move)
@@ -156,9 +315,9 @@ pub fn move_char_right(cx: &mut Context) {
156315 doc.set_selection(view.id, selection);
157316}
158317
159−pub fn move_line_up(cx: &mut Context) {
318+fn move_line_up(cx: &mut Context) {
160319 let count = cx.count();
161− let (view, doc) = cx.current();
320+ let (view, doc) = current!(cx.editor);
162321 let text = doc.text().slice(..);
163322 let selection = doc.selection(view.id).transform(|range| {
164323 movement::move_vertically(text, range, Direction::Backward, count, Movement::Move)
@@ -166,9 +325,9 @@ pub fn move_line_up(cx: &mut Context) {
166325 doc.set_selection(view.id, selection);
167326}
168327
169−pub fn move_line_down(cx: &mut Context) {
328+fn move_line_down(cx: &mut Context) {
170329 let count = cx.count();
171− let (view, doc) = cx.current();
330+ let (view, doc) = current!(cx.editor);
172331 let text = doc.text().slice(..);
173332 let selection = doc.selection(view.id).transform(|range| {
174333 movement::move_vertically(text, range, Direction::Forward, count, Movement::Move)
@@ -176,8 +335,8 @@ pub fn move_line_down(cx: &mut Context) {
176335 doc.set_selection(view.id, selection);
177336}
178337
179−pub fn move_line_end(cx: &mut Context) {
180− let (view, doc) = cx.current();
338+fn move_line_end(cx: &mut Context) {
339+ let (view, doc) = current!(cx.editor);
181340
182341 let selection = doc.selection(view.id).transform(|range| {
183342 let text = doc.text();
@@ -195,8 +354,8 @@ pub fn move_line_end(cx: &mut Context) {
195354 doc.set_selection(view.id, selection);
196355}
197356
198−pub fn move_line_start(cx: &mut Context) {
199− let (view, doc) = cx.current();
357+fn move_line_start(cx: &mut Context) {
358+ let (view, doc) = current!(cx.editor);
200359
201360 let selection = doc.selection(view.id).transform(|range| {
202361 let text = doc.text();
@@ -210,8 +369,8 @@ pub fn move_line_start(cx: &mut Context) {
210369 doc.set_selection(view.id, selection);
211370}
212371
213−pub fn move_first_nonwhitespace(cx: &mut Context) {
214− let (view, doc) = cx.current();
372+fn move_first_nonwhitespace(cx: &mut Context) {
373+ let (view, doc) = current!(cx.editor);
215374
216375 let selection = doc.selection(view.id).transform(|range| {
217376 let text = doc.text();
@@ -232,9 +391,9 @@ pub fn move_first_nonwhitespace(cx: &mut Context) {
232391// Range::new(if Move { pos } if Extend { range.anchor }, pos)
233392// since these all really do the same thing
234393
235−pub fn move_next_word_start(cx: &mut Context) {
394+fn move_next_word_start(cx: &mut Context) {
236395 let count = cx.count();
237− let (view, doc) = cx.current();
396+ let (view, doc) = current!(cx.editor);
238397 let text = doc.text().slice(..);
239398
240399 let selection = doc
@@ -244,9 +403,9 @@ pub fn move_next_word_start(cx: &mut Context) {
244403 doc.set_selection(view.id, selection);
245404}
246405
247−pub fn move_prev_word_start(cx: &mut Context) {
406+fn move_prev_word_start(cx: &mut Context) {
248407 let count = cx.count();
249− let (view, doc) = cx.current();
408+ let (view, doc) = current!(cx.editor);
250409 let text = doc.text().slice(..);
251410
252411 let selection = doc
@@ -256,9 +415,9 @@ pub fn move_prev_word_start(cx: &mut Context) {
256415 doc.set_selection(view.id, selection);
257416}
258417
259−pub fn move_next_word_end(cx: &mut Context) {
418+fn move_next_word_end(cx: &mut Context) {
260419 let count = cx.count();
261− let (view, doc) = cx.current();
420+ let (view, doc) = current!(cx.editor);
262421 let text = doc.text().slice(..);
263422
264423 let selection = doc
@@ -268,23 +427,23 @@ pub fn move_next_word_end(cx: &mut Context) {
268427 doc.set_selection(view.id, selection);
269428}
270429
271−pub fn move_file_start(cx: &mut Context) {
430+fn move_file_start(cx: &mut Context) {
272431 push_jump(cx.editor);
273− let (view, doc) = cx.current();
432+ let (view, doc) = current!(cx.editor);
274433 doc.set_selection(view.id, Selection::point(0));
275434}
276435
277−pub fn move_file_end(cx: &mut Context) {
436+fn move_file_end(cx: &mut Context) {
278437 push_jump(cx.editor);
279− let (view, doc) = cx.current();
438+ let (view, doc) = current!(cx.editor);
280439 let text = doc.text();
281440 let last_line = text.line_to_char(text.len_lines().saturating_sub(2));
282441 doc.set_selection(view.id, Selection::point(last_line));
283442}
284443
285−pub fn extend_next_word_start(cx: &mut Context) {
444+fn extend_next_word_start(cx: &mut Context) {
286445 let count = cx.count();
287− let (view, doc) = cx.current();
446+ let (view, doc) = current!(cx.editor);
288447 let text = doc.text().slice(..);
289448
290449 let selection = doc.selection(view.id).transform(|mut range| {
@@ -296,9 +455,9 @@ pub fn extend_next_word_start(cx: &mut Context) {
296455 doc.set_selection(view.id, selection);
297456}
298457
299−pub fn extend_prev_word_start(cx: &mut Context) {
458+fn extend_prev_word_start(cx: &mut Context) {
300459 let count = cx.count();
301− let (view, doc) = cx.current();
460+ let (view, doc) = current!(cx.editor);
302461 let text = doc.text().slice(..);
303462
304463 let selection = doc.selection(view.id).transform(|mut range| {
@@ -309,9 +468,9 @@ pub fn extend_prev_word_start(cx: &mut Context) {
309468 doc.set_selection(view.id, selection);
310469}
311470
312−pub fn extend_next_word_end(cx: &mut Context) {
471+fn extend_next_word_end(cx: &mut Context) {
313472 let count = cx.count();
314− let (view, doc) = cx.current();
473+ let (view, doc) = current!(cx.editor);
315474 let text = doc.text().slice(..);
316475
317476 let selection = doc.selection(view.id).transform(|mut range| {
@@ -348,7 +507,7 @@ where
348507 _ => return,
349508 };
350509
351− let (view, doc) = cx.current();
510+ let (view, doc) = current!(cx.editor);
352511 let text = doc.text().slice(..);
353512
354513 let selection = doc.selection(view.id).transform(|mut range| {
@@ -367,7 +526,7 @@ where
367526 })
368527}
369528
370−pub fn find_till_char(cx: &mut Context) {
529+fn find_till_char(cx: &mut Context) {
371530 find_char_impl(
372531 cx,
373532 search::find_nth_next,
@@ -376,7 +535,7 @@ pub fn find_till_char(cx: &mut Context) {
376535 )
377536}
378537
379−pub fn find_next_char(cx: &mut Context) {
538+fn find_next_char(cx: &mut Context) {
380539 find_char_impl(
381540 cx,
382541 search::find_nth_next,
@@ -385,7 +544,7 @@ pub fn find_next_char(cx: &mut Context) {
385544 )
386545}
387546
388−pub fn extend_till_char(cx: &mut Context) {
547+fn extend_till_char(cx: &mut Context) {
389548 find_char_impl(
390549 cx,
391550 search::find_nth_next,
@@ -394,7 +553,7 @@ pub fn extend_till_char(cx: &mut Context) {
394553 )
395554}
396555
397−pub fn extend_next_char(cx: &mut Context) {
556+fn extend_next_char(cx: &mut Context) {
398557 find_char_impl(
399558 cx,
400559 search::find_nth_next,
@@ -403,7 +562,7 @@ pub fn extend_next_char(cx: &mut Context) {
403562 )
404563}
405564
406−pub fn till_prev_char(cx: &mut Context) {
565+fn till_prev_char(cx: &mut Context) {
407566 find_char_impl(
408567 cx,
409568 search::find_nth_prev,
@@ -412,7 +571,7 @@ pub fn till_prev_char(cx: &mut Context) {
412571 )
413572}
414573
415−pub fn find_prev_char(cx: &mut Context) {
574+fn find_prev_char(cx: &mut Context) {
416575 find_char_impl(
417576 cx,
418577 search::find_nth_prev,
@@ -421,7 +580,7 @@ pub fn find_prev_char(cx: &mut Context) {
421580 )
422581}
423582
424−pub fn extend_till_prev_char(cx: &mut Context) {
583+fn extend_till_prev_char(cx: &mut Context) {
425584 find_char_impl(
426585 cx,
427586 search::find_nth_prev,
@@ -430,7 +589,7 @@ pub fn extend_till_prev_char(cx: &mut Context) {
430589 )
431590}
432591
433−pub fn extend_prev_char(cx: &mut Context) {
592+fn extend_prev_char(cx: &mut Context) {
434593 find_char_impl(
435594 cx,
436595 search::find_nth_prev,
@@ -439,8 +598,8 @@ pub fn extend_prev_char(cx: &mut Context) {
439598 )
440599}
441600
442−pub fn extend_first_nonwhitespace(cx: &mut Context) {
443− let (view, doc) = cx.current();
601+fn extend_first_nonwhitespace(cx: &mut Context) {
602+ let (view, doc) = current!(cx.editor);
444603
445604 let selection = doc.selection(view.id).transform(|range| {
446605 let text = doc.text();
@@ -457,7 +616,7 @@ pub fn extend_first_nonwhitespace(cx: &mut Context) {
457616 doc.set_selection(view.id, selection);
458617}
459618
460−pub fn replace(cx: &mut Context) {
619+fn replace(cx: &mut Context) {
461620 // need to wait for next key
462621 cx.on_next_key(move |cx, event| {
463622 let ch = match event {
@@ -473,7 +632,7 @@ pub fn replace(cx: &mut Context) {
473632 };
474633
475634 if let Some(ch) = ch {
476− let (view, doc) = cx.current();
635+ let (view, doc) = current!(cx.editor);
477636
478637 let transaction =
479638 Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
@@ -497,7 +656,7 @@ pub fn replace(cx: &mut Context) {
497656
498657fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
499658 use Direction::*;
500− let (view, doc) = cx.current();
659+ let (view, doc) = current!(cx.editor);
501660 let cursor = coords_at_pos(doc.text().slice(..), doc.selection(view.id).cursor());
502661 let doc_last_line = doc.text().len_lines() - 1;
503662
@@ -537,33 +696,33 @@ fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
537696 doc.set_selection(view.id, Selection::point(pos));
538697}
539698
540−pub fn page_up(cx: &mut Context) {
541− let view = cx.view();
699+fn page_up(cx: &mut Context) {
700+ let view = view!(cx.editor);
542701 let offset = view.area.height as usize;
543702 scroll(cx, offset, Direction::Backward);
544703}
545704
546−pub fn page_down(cx: &mut Context) {
547− let view = cx.view();
705+fn page_down(cx: &mut Context) {
706+ let view = view!(cx.editor);
548707 let offset = view.area.height as usize;
549708 scroll(cx, offset, Direction::Forward);
550709}
551710
552−pub fn half_page_up(cx: &mut Context) {
553− let view = cx.view();
711+fn half_page_up(cx: &mut Context) {
712+ let view = view!(cx.editor);
554713 let offset = view.area.height as usize / 2;
555714 scroll(cx, offset, Direction::Backward);
556715}
557716
558−pub fn half_page_down(cx: &mut Context) {
559− let view = cx.view();
717+fn half_page_down(cx: &mut Context) {
718+ let view = view!(cx.editor);
560719 let offset = view.area.height as usize / 2;
561720 scroll(cx, offset, Direction::Forward);
562721}
563722
564−pub fn extend_char_left(cx: &mut Context) {
723+fn extend_char_left(cx: &mut Context) {
565724 let count = cx.count();
566− let (view, doc) = cx.current();
725+ let (view, doc) = current!(cx.editor);
567726 let text = doc.text().slice(..);
568727 let selection = doc.selection(view.id).transform(|range| {
569728 movement::move_horizontally(text, range, Direction::Backward, count, Movement::Extend)
@@ -571,9 +730,9 @@ pub fn extend_char_left(cx: &mut Context) {
571730 doc.set_selection(view.id, selection);
572731}
573732
574−pub fn extend_char_right(cx: &mut Context) {
733+fn extend_char_right(cx: &mut Context) {
575734 let count = cx.count();
576− let (view, doc) = cx.current();
735+ let (view, doc) = current!(cx.editor);
577736 let text = doc.text().slice(..);
578737 let selection = doc.selection(view.id).transform(|range| {
579738 movement::move_horizontally(text, range, Direction::Forward, count, Movement::Extend)
@@ -581,9 +740,9 @@ pub fn extend_char_right(cx: &mut Context) {
581740 doc.set_selection(view.id, selection);
582741}
583742
584−pub fn extend_line_up(cx: &mut Context) {
743+fn extend_line_up(cx: &mut Context) {
585744 let count = cx.count();
586− let (view, doc) = cx.current();
745+ let (view, doc) = current!(cx.editor);
587746 let text = doc.text().slice(..);
588747 let selection = doc.selection(view.id).transform(|range| {
589748 movement::move_vertically(text, range, Direction::Backward, count, Movement::Extend)
@@ -591,9 +750,9 @@ pub fn extend_line_up(cx: &mut Context) {
591750 doc.set_selection(view.id, selection);
592751}
593752
594−pub fn extend_line_down(cx: &mut Context) {
753+fn extend_line_down(cx: &mut Context) {
595754 let count = cx.count();
596− let (view, doc) = cx.current();
755+ let (view, doc) = current!(cx.editor);
597756 let text = doc.text().slice(..);
598757 let selection = doc.selection(view.id).transform(|range| {
599758 movement::move_vertically(text, range, Direction::Forward, count, Movement::Extend)
@@ -601,8 +760,8 @@ pub fn extend_line_down(cx: &mut Context) {
601760 doc.set_selection(view.id, selection);
602761}
603762
604−pub fn extend_line_end(cx: &mut Context) {
605− let (view, doc) = cx.current();
763+fn extend_line_end(cx: &mut Context) {
764+ let (view, doc) = current!(cx.editor);
606765
607766 let selection = doc.selection(view.id).transform(|range| {
608767 let text = doc.text();
@@ -620,8 +779,8 @@ pub fn extend_line_end(cx: &mut Context) {
620779 doc.set_selection(view.id, selection);
621780}
622781
623−pub fn extend_line_start(cx: &mut Context) {
624− let (view, doc) = cx.current();
782+fn extend_line_start(cx: &mut Context) {
783+ let (view, doc) = current!(cx.editor);
625784
626785 let selection = doc.selection(view.id).transform(|range| {
627786 let text = doc.text();
@@ -635,14 +794,14 @@ pub fn extend_line_start(cx: &mut Context) {
635794 doc.set_selection(view.id, selection);
636795}
637796
638−pub fn select_all(cx: &mut Context) {
639− let (view, doc) = cx.current();
797+fn select_all(cx: &mut Context) {
798+ let (view, doc) = current!(cx.editor);
640799
641800 let end = doc.text().len_chars().saturating_sub(1);
642801 doc.set_selection(view.id, Selection::single(0, end))
643802}
644803
645−pub fn select_regex(cx: &mut Context) {
804+fn select_regex(cx: &mut Context) {
646805 let prompt = ui::regex_prompt(cx, "select:".to_string(), move |view, doc, _, regex| {
647806 let text = doc.text().slice(..);
648807 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) {
654813 cx.push_layer(Box::new(prompt));
655814}
656815
657−pub fn split_selection(cx: &mut Context) {
816+fn split_selection(cx: &mut Context) {
658817 let prompt = ui::regex_prompt(cx, "split:".to_string(), move |view, doc, _, regex| {
659818 let text = doc.text().slice(..);
660819 let selection = selection::split_on_matches(text, doc.selection(view.id), &regex);
@@ -664,8 +823,8 @@ pub fn split_selection(cx: &mut Context) {
664823 cx.push_layer(Box::new(prompt));
665824}
666825
667−pub fn split_selection_on_newline(cx: &mut Context) {
668− let (view, doc) = cx.current();
826+fn split_selection_on_newline(cx: &mut Context) {
827+ let (view, doc) = current!(cx.editor);
669828 let text = doc.text().slice(..);
670829 // only compile the regex once
671830 #[allow(clippy::trivial_regex)]
@@ -716,8 +875,8 @@ fn search_impl(doc: &mut Document, view: &mut View, contents: &str, regex: &Rege
716875}
717876
718877// TODO: use one function for search vs extend
719−pub fn search(cx: &mut Context) {
720− let (view, doc) = cx.current();
878+fn search(cx: &mut Context) {
879+ let (view, doc) = current!(cx.editor);
721880
722881 // TODO: could probably share with select_on_matches?
723882
@@ -738,10 +897,10 @@ pub fn search(cx: &mut Context) {
738897
739898 cx.push_layer(Box::new(prompt));
740899}
741−// can't search next for ""compose"" for some reason
742900
743−pub fn search_next_impl(cx: &mut Context, extend: bool) {
744− let (view, doc, registers) = cx.current_with_registers();
901+fn search_next_impl(cx: &mut Context, extend: bool) {
902+ let (view, doc) = current!(cx.editor);
903+ let registers = &mut cx.editor.registers;
745904 if let Some(query) = registers.read('\\') {
746905 let query = query.first().unwrap();
747906 let contents = doc.text().slice(..).to_string();
@@ -750,16 +909,16 @@ pub fn search_next_impl(cx: &mut Context, extend: bool) {
750909 }
751910}
752911
753−pub fn search_next(cx: &mut Context) {
912+fn search_next(cx: &mut Context) {
754913 search_next_impl(cx, false);
755914}
756915
757−pub fn extend_search_next(cx: &mut Context) {
916+fn extend_search_next(cx: &mut Context) {
758917 search_next_impl(cx, true);
759918}
760919
761−pub fn search_selection(cx: &mut Context) {
762− let (view, doc) = cx.current();
920+fn search_selection(cx: &mut Context) {
921+ let (view, doc) = current!(cx.editor);
763922 let contents = doc.text().slice(..);
764923 let query = doc.selection(view.id).primary().fragment(contents);
765924 let regex = regex::escape(&query);
@@ -773,9 +932,9 @@ pub fn search_selection(cx: &mut Context) {
773932
774933//
775934
776−pub fn select_line(cx: &mut Context) {
935+fn select_line(cx: &mut Context) {
777936 let count = cx.count();
778− let (view, doc) = cx.current();
937+ let (view, doc) = current!(cx.editor);
779938
780939 let pos = doc.selection(view.id).primary();
781940 let text = doc.text();
@@ -788,9 +947,9 @@ pub fn select_line(cx: &mut Context) {
788947
789948 doc.set_selection(view.id, Selection::single(start, end));
790949}
791−pub fn extend_line(cx: &mut Context) {
950+fn extend_line(cx: &mut Context) {
792951 let count = cx.count();
793− let (view, doc) = cx.current();
952+ let (view, doc) = current!(cx.editor);
794953
795954 let pos = doc.selection(view.id).primary();
796955 let text = doc.text();
@@ -830,9 +989,10 @@ fn delete_selection_impl(reg: &mut Register, doc: &mut Document, view_id: ViewId
830989 doc.apply(&transaction, view_id);
831990}
832991
833−pub fn delete_selection(cx: &mut Context) {
992+fn delete_selection(cx: &mut Context) {
834993 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;
836996 let reg = registers.get_or_insert(reg_name);
837997 delete_selection_impl(reg, doc, view.id);
838998
@@ -842,16 +1002,17 @@ pub fn delete_selection(cx: &mut Context) {
8421002 exit_select_mode(cx);
8431003}
8441004
845−pub fn change_selection(cx: &mut Context) {
1005+fn change_selection(cx: &mut Context) {
8461006 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;
8481009 let reg = registers.get_or_insert(reg_name);
8491010 delete_selection_impl(reg, doc, view.id);
8501011 enter_insert_mode(doc);
8511012}
8521013
853−pub fn collapse_selection(cx: &mut Context) {
854− let (view, doc) = cx.current();
1014+fn collapse_selection(cx: &mut Context) {
1015+ let (view, doc) = current!(cx.editor);
8551016 let selection = doc
8561017 .selection(view.id)
8571018 .transform(|range| Range::new(range.head, range.head));
@@ -859,8 +1020,8 @@ pub fn collapse_selection(cx: &mut Context) {
8591020 doc.set_selection(view.id, selection);
8601021}
8611022
862−pub fn flip_selections(cx: &mut Context) {
863− let (view, doc) = cx.current();
1023+fn flip_selections(cx: &mut Context) {
1024+ let (view, doc) = current!(cx.editor);
8641025 let selection = doc
8651026 .selection(view.id)
8661027 .transform(|range| Range::new(range.head, range.anchor));
@@ -873,8 +1034,8 @@ fn enter_insert_mode(doc: &mut Document) {
8731034}
8741035
8751036// inserts at the start of each selection
876−pub fn insert_mode(cx: &mut Context) {
877− let (view, doc) = cx.current();
1037+fn insert_mode(cx: &mut Context) {
1038+ let (view, doc) = current!(cx.editor);
8781039 enter_insert_mode(doc);
8791040
8801041 let selection = doc
@@ -884,8 +1045,8 @@ pub fn insert_mode(cx: &mut Context) {
8841045}
8851046
8861047// inserts at the end of each selection
887−pub fn append_mode(cx: &mut Context) {
888− let (view, doc) = cx.current();
1048+fn append_mode(cx: &mut Context) {
1049+ let (view, doc) = current!(cx.editor);
8891050 enter_insert_mode(doc);
8901051 doc.restore_cursor = true;
8911052
@@ -918,7 +1079,7 @@ mod cmd {
9181079 use ui::completers::{self, Completer};
9191080
9201081 #[derive(Clone)]
921− pub struct Command {
1082+ pub struct TypableCommand {
9221083 pub name: &'static str,
9231084 pub alias: Option<&'static str>,
9241085 pub doc: &'static str,
@@ -932,11 +1093,11 @@ mod cmd {
9321093 if editor.tree.views().count() == 1 && buffers_remaining_impl(editor) {
9331094 return;
9341095 }
935− editor.close(editor.view().id, /* close_buffer */ false);
1096+ editor.close(view!(editor).id, /* close_buffer */ false);
9361097 }
9371098
9381099 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);
9401101 }
9411102
9421103 fn open(editor: &mut Editor, args: &[&str], event: PromptEvent) {
@@ -978,7 +1139,7 @@ mod cmd {
9781139 }
9791140
9801141 fn write(editor: &mut Editor, args: &[&str], event: PromptEvent) {
981− let (view, doc) = editor.current();
1142+ let (view, doc) = current!(editor);
9821143 if let Err(e) = write_impl(view, doc, args.first()) {
9831144 editor.set_error(e.to_string());
9841145 };
@@ -989,7 +1150,7 @@ mod cmd {
9891150 }
9901151
9911152 fn format(editor: &mut Editor, args: &[&str], event: PromptEvent) {
992− let (view, doc) = editor.current();
1153+ let (view, doc) = current!(editor);
9931154
9941155 doc.format(view.id)
9951156 }
@@ -999,7 +1160,7 @@ mod cmd {
9991160
10001161 // If no argument, report current indent style.
10011162 if args.is_empty() {
1002− let style = editor.current().1.indent_style;
1163+ let style = current!(editor).1.indent_style;
10031164 editor.set_status(match style {
10041165 Tabs => "tabs".into(),
10051166 Spaces(1) => "1 space".into(),
@@ -1022,7 +1183,7 @@ mod cmd {
10221183 };
10231184
10241185 if let Some(s) = style {
1025− let (_, doc) = editor.current();
1186+ let doc = doc_mut!(editor);
10261187 doc.indent_style = s;
10271188 } else {
10281189 // Invalid argument.
@@ -1038,7 +1199,7 @@ mod cmd {
10381199 return;
10391200 }
10401201 };
1041− let (view, doc) = editor.current();
1202+ let (view, doc) = current!(editor);
10421203 doc.earlier(view.id, uk)
10431204 }
10441205
@@ -1050,12 +1211,12 @@ mod cmd {
10501211 return;
10511212 }
10521213 };
1053− let (view, doc) = editor.current();
1214+ let (view, doc) = current!(editor);
10541215 doc.later(view.id, uk)
10551216 }
10561217
10571218 fn write_quit(editor: &mut Editor, args: &[&str], event: PromptEvent) {
1058− let (view, doc) = editor.current();
1219+ let (view, doc) = current!(editor);
10591220 if let Err(e) = write_impl(view, doc, args.first()) {
10601221 editor.set_error(e.to_string());
10611222 return;
@@ -1076,8 +1237,8 @@ mod cmd {
10761237 .filter(|doc| doc.is_modified())
10771238 .map(|doc| {
10781239 doc.relative_path()
1079− .and_then(|path| path.to_str())
1080− .unwrap_or("[scratch]")
1240+ .map(|path| path.to_string_lossy().to_string())
1241+ .unwrap_or_else(|| "[scratch]".into())
10811242 })
10821243 .collect();
10831244 if !modified.is_empty() {
@@ -1157,113 +1318,113 @@ mod cmd {
11571318 quit_all_impl(editor, args, event, true)
11581319 }
11591320
1160− pub const COMMAND_LIST: &[Command] = &[
1161− Command {
1321+ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
1322+ TypableCommand {
11621323 name: "quit",
11631324 alias: Some("q"),
11641325 doc: "Close the current view.",
11651326 fun: quit,
11661327 completer: None,
11671328 },
1168− Command {
1329+ TypableCommand {
11691330 name: "quit!",
11701331 alias: Some("q!"),
11711332 doc: "Close the current view.",
11721333 fun: force_quit,
11731334 completer: None,
11741335 },
1175− Command {
1336+ TypableCommand {
11761337 name: "open",
11771338 alias: Some("o"),
11781339 doc: "Open a file from disk into the current view.",
11791340 fun: open,
11801341 completer: Some(completers::filename),
11811342 },
1182− Command {
1343+ TypableCommand {
11831344 name: "write",
11841345 alias: Some("w"),
11851346 doc: "Write changes to disk. Accepts an optional path (:write some/path.txt)",
11861347 fun: write,
11871348 completer: Some(completers::filename),
11881349 },
1189− Command {
1350+ TypableCommand {
11901351 name: "new",
11911352 alias: Some("n"),
11921353 doc: "Create a new scratch buffer.",
11931354 fun: new_file,
11941355 completer: Some(completers::filename),
11951356 },
1196− Command {
1357+ TypableCommand {
11971358 name: "format",
11981359 alias: Some("fmt"),
11991360 doc: "Format the file using a formatter.",
12001361 fun: format,
12011362 completer: None,
12021363 },
1203− Command {
1364+ TypableCommand {
12041365 name: "indent-style",
12051366 alias: None,
12061367 doc: "Set the indentation style for editing. ('t' for tabs or 1-8 for number of spaces.)",
12071368 fun: set_indent_style,
12081369 completer: None,
12091370 },
1210− Command {
1371+ TypableCommand {
12111372 name: "earlier",
12121373 alias: Some("ear"),
12131374 doc: "Jump back to an earlier point in edit history. Accepts a number of steps or a time span.",
12141375 fun: earlier,
12151376 completer: None,
12161377 },
1217− Command {
1378+ TypableCommand {
12181379 name: "later",
12191380 alias: Some("lat"),
12201381 doc: "Jump to a later point in edit history. Accepts a number of steps or a time span.",
12211382 fun: later,
12221383 completer: None,
12231384 },
1224− Command {
1385+ TypableCommand {
12251386 name: "write-quit",
12261387 alias: Some("wq"),
12271388 doc: "Writes changes to disk and closes the current view. Accepts an optional path (:wq some/path.txt)",
12281389 fun: write_quit,
12291390 completer: Some(completers::filename),
12301391 },
1231− Command {
1392+ TypableCommand {
12321393 name: "write-quit!",
12331394 alias: Some("wq!"),
12341395 doc: "Writes changes to disk and closes the current view forcefully. Accepts an optional path (:wq! some/path.txt)",
12351396 fun: force_write_quit,
12361397 completer: Some(completers::filename),
12371398 },
1238− Command {
1399+ TypableCommand {
12391400 name: "write-all",
12401401 alias: Some("wa"),
12411402 doc: "Writes changes from all views to disk.",
12421403 fun: write_all,
12431404 completer: None,
12441405 },
1245− Command {
1406+ TypableCommand {
12461407 name: "write-quit-all",
12471408 alias: Some("wqa"),
12481409 doc: "Writes changes from all views to disk and close all views.",
12491410 fun: write_all_quit,
12501411 completer: None,
12511412 },
1252− Command {
1413+ TypableCommand {
12531414 name: "write-quit-all!",
12541415 alias: Some("wqa!"),
12551416 doc: "Writes changes from all views to disk and close all views forcefully (ignoring unsaved changes).",
12561417 fun: force_write_all_quit,
12571418 completer: None,
12581419 },
1259− Command {
1420+ TypableCommand {
12601421 name: "quit-all",
12611422 alias: Some("qa"),
12621423 doc: "Close all views.",
12631424 fun: quit_all,
12641425 completer: None,
12651426 },
1266− Command {
1427+ TypableCommand {
12671428 name: "quit-all!",
12681429 alias: Some("qa!"),
12691430 doc: "Close all views forcefully (ignoring unsaved changes).",
@@ -1273,10 +1434,10 @@ mod cmd {
12731434
12741435 ];
12751436
1276− pub static COMMANDS: Lazy<HashMap<&'static str, &'static Command>> = Lazy::new(|| {
1437+ pub static COMMANDS: Lazy<HashMap<&'static str, &'static TypableCommand>> = Lazy::new(|| {
12771438 let mut map = HashMap::new();
12781439
1279− for cmd in COMMAND_LIST {
1440+ for cmd in TYPABLE_COMMAND_LIST {
12801441 map.insert(cmd.name, cmd);
12811442 if let Some(alias) = cmd.alias {
12821443 map.insert(alias, cmd);
@@ -1287,7 +1448,7 @@ mod cmd {
12871448 });
12881449}
12891450
1290−pub fn command_mode(cx: &mut Context) {
1451+fn command_mode(cx: &mut Context) {
12911452 // TODO: completion items should have a info section that would get displayed in
12921453 // a popup above the prompt when items are tabbed over
12931454
@@ -1302,7 +1463,7 @@ pub fn command_mode(cx: &mut Context) {
13021463 if parts.len() <= 1 {
13031464 use std::{borrow::Cow, ops::Range};
13041465 let end = 0..;
1305− cmd::COMMAND_LIST
1466+ cmd::TYPABLE_COMMAND_LIST
13061467 .iter()
13071468 .filter(|command| command.name.contains(input))
13081469 .map(|command| (end.clone(), Cow::Borrowed(command.name)))
@@ -1310,7 +1471,7 @@ pub fn command_mode(cx: &mut Context) {
13101471 } else {
13111472 let part = parts.last().unwrap();
13121473
1313− if let Some(cmd::Command {
1474+ if let Some(cmd::TypableCommand {
13141475 completer: Some(completer),
13151476 ..
13161477 }) = cmd::COMMANDS.get(parts[0])
@@ -1351,7 +1512,7 @@ pub fn command_mode(cx: &mut Context) {
13511512 prompt.doc_fn = Box::new(|input: &str| {
13521513 let part = input.split(' ').next().unwrap_or_default();
13531514
1354− if let Some(cmd::Command { doc, .. }) = cmd::COMMANDS.get(part) {
1515+ if let Some(cmd::TypableCommand { doc, .. }) = cmd::COMMANDS.get(part) {
13551516 return Some(doc);
13561517 }
13571518
@@ -1361,21 +1522,21 @@ pub fn command_mode(cx: &mut Context) {
13611522 cx.push_layer(Box::new(prompt));
13621523}
13631524
1364−pub fn file_picker(cx: &mut Context) {
1525+fn file_picker(cx: &mut Context) {
13651526 let root = find_root(None).unwrap_or_else(|| PathBuf::from("./"));
13661527 let picker = ui::file_picker(root);
13671528 cx.push_layer(Box::new(picker));
13681529}
13691530
1370−pub fn buffer_picker(cx: &mut Context) {
1531+fn buffer_picker(cx: &mut Context) {
13711532 use std::path::{Path, PathBuf};
1372− let current = cx.editor.view().doc;
1533+ let current = view!(cx.editor).doc;
13731534
13741535 let picker = Picker::new(
13751536 cx.editor
13761537 .documents
13771538 .iter()
1378− .map(|(id, doc)| (id, doc.relative_path().map(Path::to_path_buf)))
1539+ .map(|(id, doc)| (id, doc.relative_path()))
13791540 .collect(),
13801541 move |(id, path): &(DocumentId, Option<PathBuf>)| {
13811542 // format_fn
@@ -1398,7 +1559,7 @@ pub fn buffer_picker(cx: &mut Context) {
13981559 cx.push_layer(Box::new(picker));
13991560}
14001561
1401−pub fn symbol_picker(cx: &mut Context) {
1562+fn symbol_picker(cx: &mut Context) {
14021563 fn nested_to_flat(
14031564 list: &mut Vec<lsp::SymbolInformation>,
14041565 file: &lsp::TextDocumentIdentifier,
@@ -1417,7 +1578,7 @@ pub fn symbol_picker(cx: &mut Context) {
14171578 nested_to_flat(list, file, child);
14181579 }
14191580 }
1420− let (view, doc) = cx.current();
1581+ let (view, doc) = current!(cx.editor);
14211582
14221583 let language_server = match doc.language_server() {
14231584 Some(language_server) => language_server,
@@ -1438,7 +1599,7 @@ pub fn symbol_picker(cx: &mut Context) {
14381599 let symbols = match symbols {
14391600 lsp::DocumentSymbolResponse::Flat(symbols) => symbols,
14401601 lsp::DocumentSymbolResponse::Nested(symbols) => {
1441− let (_view, doc) = editor.current();
1602+ let (_view, doc) = current!(editor);
14421603 let mut flat_symbols = Vec::new();
14431604 for symbol in symbols {
14441605 nested_to_flat(&mut flat_symbols, &doc.identifier(), symbol)
@@ -1452,7 +1613,7 @@ pub fn symbol_picker(cx: &mut Context) {
14521613 |symbol| (&symbol.name).into(),
14531614 move |editor: &mut Editor, symbol, _action| {
14541615 push_jump(editor);
1455− let (view, doc) = editor.current();
1616+ let (view, doc) = current!(editor);
14561617
14571618 if let Some(range) =
14581619 lsp_range_to_range(doc.text(), symbol.location.range, offset_encoding)
@@ -1469,15 +1630,15 @@ pub fn symbol_picker(cx: &mut Context) {
14691630}
14701631
14711632// 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) {
14731634 move_first_nonwhitespace(cx);
1474− let doc = cx.doc();
1635+ let doc = doc_mut!(cx.editor);
14751636 enter_insert_mode(doc);
14761637}
14771638
14781639// A inserts at the end of each line with a selection
1479−pub fn append_to_line(cx: &mut Context) {
1480− let (view, doc) = cx.current();
1640+fn append_to_line(cx: &mut Context) {
1641+ let (view, doc) = current!(cx.editor);
14811642 enter_insert_mode(doc);
14821643
14831644 let selection = doc.selection(view.id).transform(|range| {
@@ -1497,73 +1658,79 @@ enum Open {
14971658
14981659fn open(cx: &mut Context, open: Open) {
14991660 let count = cx.count();
1500− let (view, doc) = cx.current();
1661+ let (view, doc) = current!(cx.editor);
15011662 enter_insert_mode(doc);
15021663
15031664 let text = doc.text().slice(..);
1665+ let contents = doc.text();
15041666 let selection = doc.selection(view.id);
15051667
15061668 let mut ranges = SmallVec::with_capacity(selection.len());
1669+ let mut offs = 0;
15071670
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 {
1514− // adjust position to the end of the line (next line - 1)
1515− Open::Below => line + 1,
1516− // adjust position to the end of the previous line (current line - 1)
1517− Open::Above => line,
1518− };
1519−
1520− let index = doc.text().line_to_char(line).saturating_sub(1);
1671+ let line = match open {
1672+ // adjust position to the end of the line (next line - 1)
1673+ Open::Below => line + 1,
1674+ // adjust position to the end of the previous line (current line - 1)
1675+ Open::Above => line,
1676+ };
15211677
1522− // TODO: share logic with insert_newline for indentation
1523− let indent_level = indent::suggested_indent_for_pos(
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);
1678+ let mut transaction = Transaction::change_by_selection(contents, selection, |range| {
1679+ let line = text.char_to_line(range.head);
15351680
1536− // calculate new selection range
1537− let pos = index + text.chars().count();
1538− ranges.push(Range::new(pos, pos));
1681+ // insert newlines after this index for both Above and Below variants
1682+ let linend_index = doc.text().line_to_char(line).saturating_sub(1);
15391683
1540− (index, index, Some(text.into()))
1541− })
1542− .collect();
1684+ // TODO: share logic with insert_newline for indentation
1685+ let indent_level = indent::suggested_indent_for_pos(
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+ }
15431711
1544− // TODO: count actually inserts "n" new lines and starts editing on all of them.
1545− // TODO: append "count" newlines and modify cursors to those lines
1712+ offs += text.chars().count();
15461713
1547− let selection = Selection::new(ranges, 0);
1714+ (linend_index, linend_index, Some(text.into()))
1715+ });
15481716
1549− let transaction =
1550− Transaction::change(doc.text(), changes.into_iter()).with_selection(selection);
1717+ transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index()));
15511718
15521719 doc.apply(&transaction, view.id);
15531720}
15541721
15551722// 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) {
15571724 open(cx, Open::Below)
15581725}
15591726
15601727// 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) {
15621729 open(cx, Open::Above)
15631730}
15641731
1565−pub fn normal_mode(cx: &mut Context) {
1566− let (view, doc) = cx.current();
1732+fn normal_mode(cx: &mut Context) {
1733+ let (view, doc) = current!(cx.editor);
15671734
15681735 doc.mode = Mode::Normal;
15691736
@@ -1586,13 +1753,13 @@ pub fn normal_mode(cx: &mut Context) {
15861753
15871754// Store a jump on the jumplist.
15881755fn push_jump(editor: &mut Editor) {
1589− let (view, doc) = editor.current();
1756+ let (view, doc) = current!(editor);
15901757 let jump = (doc.id(), doc.selection(view.id).clone());
15911758 view.jumps.push(jump);
15921759}
15931760
15941761fn 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;
15961763 if let Some(alt) = alternate_file {
15971764 cx.editor.switch(alt, Action::Replace);
15981765 } else {
@@ -1600,11 +1767,11 @@ fn switch_to_last_accessed_file(cx: &mut Context) {
16001767 }
16011768}
16021769
1603−pub fn goto_mode(cx: &mut Context) {
1770+fn goto_mode(cx: &mut Context) {
16041771 if let Some(count) = cx.count {
16051772 push_jump(cx.editor);
16061773
1607− let (view, doc) = cx.current();
1774+ let (view, doc) = current!(cx.editor);
16081775 let line_idx = std::cmp::min(count.get() - 1, doc.text().len_lines().saturating_sub(2));
16091776 let pos = doc.text().line_to_char(line_idx);
16101777 doc.set_selection(view.id, Selection::point(pos));
@@ -1618,7 +1785,8 @@ pub fn goto_mode(cx: &mut Context) {
16181785 } = event
16191786 {
16201787 // TODO: temporarily show GOTO in the mode list
1621− match (cx.doc().mode, ch) {
1788+ let doc = doc_mut!(cx.editor);
1789+ match (doc.mode, ch) {
16221790 (_, 'g') => move_file_start(cx),
16231791 (_, 'e') => move_file_end(cx),
16241792 (_, 'a') => switch_to_last_accessed_file(cx),
@@ -1634,7 +1802,7 @@ pub fn goto_mode(cx: &mut Context) {
16341802 (Mode::Select, 's') => extend_first_nonwhitespace(cx),
16351803
16361804 (_, 't') | (_, 'm') | (_, 'b') => {
1637− let (view, doc) = cx.current();
1805+ let (view, doc) = current!(cx.editor);
16381806
16391807 let pos = doc.selection(view.id).cursor();
16401808 let line = doc.text().char_to_line(pos);
@@ -1661,12 +1829,12 @@ pub fn goto_mode(cx: &mut Context) {
16611829 })
16621830}
16631831
1664−pub fn select_mode(cx: &mut Context) {
1665− cx.doc().mode = Mode::Select;
1832+fn select_mode(cx: &mut Context) {
1833+ doc_mut!(cx.editor).mode = Mode::Select;
16661834}
16671835
1668−pub fn exit_select_mode(cx: &mut Context) {
1669− cx.doc().mode = Mode::Normal;
1836+fn exit_select_mode(cx: &mut Context) {
1837+ doc_mut!(cx.editor).mode = Mode::Normal;
16701838}
16711839
16721840fn goto_impl(
@@ -1688,7 +1856,7 @@ fn goto_impl(
16881856 let id = editor
16891857 .open(PathBuf::from(location.uri.path()), action)
16901858 .expect("editor.open failed");
1691− let (view, doc) = editor.current();
1859+ let (view, doc) = current!(editor);
16921860 let definition_pos = location.range.start;
16931861 // TODO: convert inside server
16941862 let new_pos =
@@ -1725,8 +1893,8 @@ fn goto_impl(
17251893 }
17261894}
17271895
1728−pub fn goto_definition(cx: &mut Context) {
1729− let (view, doc) = cx.current();
1896+fn goto_definition(cx: &mut Context) {
1897+ let (view, doc) = current!(cx.editor);
17301898 let language_server = match doc.language_server() {
17311899 Some(language_server) => language_server,
17321900 None => return,
@@ -1737,7 +1905,7 @@ pub fn goto_definition(cx: &mut Context) {
17371905 let pos = pos_to_lsp_pos(doc.text(), doc.selection(view.id).cursor(), offset_encoding);
17381906
17391907 // TODO: handle fails
1740− let future = language_server.goto_definition(doc.identifier(), pos);
1908+ let future = language_server.goto_definition(doc.identifier(), pos, None);
17411909
17421910 cx.callback(
17431911 future,
@@ -1762,8 +1930,8 @@ pub fn goto_definition(cx: &mut Context) {
17621930 );
17631931}
17641932
1765−pub fn goto_type_definition(cx: &mut Context) {
1766− let (view, doc) = cx.current();
1933+fn goto_type_definition(cx: &mut Context) {
1934+ let (view, doc) = current!(cx.editor);
17671935 let language_server = match doc.language_server() {
17681936 Some(language_server) => language_server,
17691937 None => return,
@@ -1774,7 +1942,7 @@ pub fn goto_type_definition(cx: &mut Context) {
17741942 let pos = pos_to_lsp_pos(doc.text(), doc.selection(view.id).cursor(), offset_encoding);
17751943
17761944 // 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);
17781946
17791947 cx.callback(
17801948 future,
@@ -1799,8 +1967,8 @@ pub fn goto_type_definition(cx: &mut Context) {
17991967 );
18001968}
18011969
1802−pub fn goto_implementation(cx: &mut Context) {
1803− let (view, doc) = cx.current();
1970+fn goto_implementation(cx: &mut Context) {
1971+ let (view, doc) = current!(cx.editor);
18041972 let language_server = match doc.language_server() {
18051973 Some(language_server) => language_server,
18061974 None => return,
@@ -1811,7 +1979,7 @@ pub fn goto_implementation(cx: &mut Context) {
18111979 let pos = pos_to_lsp_pos(doc.text(), doc.selection(view.id).cursor(), offset_encoding);
18121980
18131981 // TODO: handle fails
1814− let future = language_server.goto_implementation(doc.identifier(), pos);
1982+ let future = language_server.goto_implementation(doc.identifier(), pos, None);
18151983
18161984 cx.callback(
18171985 future,
@@ -1836,8 +2004,8 @@ pub fn goto_implementation(cx: &mut Context) {
18362004 );
18372005}
18382006
1839−pub fn goto_reference(cx: &mut Context) {
1840− let (view, doc) = cx.current();
2007+fn goto_reference(cx: &mut Context) {
2008+ let (view, doc) = current!(cx.editor);
18412009 let language_server = match doc.language_server() {
18422010 Some(language_server) => language_server,
18432011 None => return,
@@ -1848,7 +2016,7 @@ pub fn goto_reference(cx: &mut Context) {
18482016 let pos = pos_to_lsp_pos(doc.text(), doc.selection(view.id).cursor(), offset_encoding);
18492017
18502018 // TODO: handle fails
1851− let future = language_server.goto_reference(doc.identifier(), pos);
2019+ let future = language_server.goto_reference(doc.identifier(), pos, None);
18522020
18532021 cx.callback(
18542022 future,
@@ -1868,15 +2036,15 @@ pub fn goto_reference(cx: &mut Context) {
18682036fn goto_pos(editor: &mut Editor, pos: usize) {
18692037 push_jump(editor);
18702038
1871− let (view, doc) = editor.current();
2039+ let (view, doc) = current!(editor);
18722040
18732041 doc.set_selection(view.id, Selection::point(pos));
18742042 align_view(doc, view, Align::Center);
18752043}
18762044
1877−pub fn goto_first_diag(cx: &mut Context) {
2045+fn goto_first_diag(cx: &mut Context) {
18782046 let editor = &mut cx.editor;
1879− let (view, doc) = editor.current();
2047+ let (view, doc) = current!(editor);
18802048
18812049 let cursor_pos = doc.selection(view.id).cursor();
18822050 let diag = if let Some(diag) = doc.diagnostics().first() {
@@ -1888,9 +2056,9 @@ pub fn goto_first_diag(cx: &mut Context) {
18882056 goto_pos(editor, diag);
18892057}
18902058
1891−pub fn goto_last_diag(cx: &mut Context) {
2059+fn goto_last_diag(cx: &mut Context) {
18922060 let editor = &mut cx.editor;
1893− let (view, doc) = editor.current();
2061+ let (view, doc) = current!(editor);
18942062
18952063 let cursor_pos = doc.selection(view.id).cursor();
18962064 let diag = if let Some(diag) = doc.diagnostics().last() {
@@ -1902,9 +2070,9 @@ pub fn goto_last_diag(cx: &mut Context) {
19022070 goto_pos(editor, diag);
19032071}
19042072
1905−pub fn goto_next_diag(cx: &mut Context) {
2073+fn goto_next_diag(cx: &mut Context) {
19062074 let editor = &mut cx.editor;
1907− let (view, doc) = editor.current();
2075+ let (view, doc) = current!(editor);
19082076
19092077 let cursor_pos = doc.selection(view.id).cursor();
19102078 let diag = if let Some(diag) = doc
@@ -1923,9 +2091,9 @@ pub fn goto_next_diag(cx: &mut Context) {
19232091 goto_pos(editor, diag);
19242092}
19252093
1926−pub fn goto_prev_diag(cx: &mut Context) {
2094+fn goto_prev_diag(cx: &mut Context) {
19272095 let editor = &mut cx.editor;
1928− let (view, doc) = editor.current();
2096+ let (view, doc) = current!(editor);
19292097
19302098 let cursor_pos = doc.selection(view.id).cursor();
19312099 let diag = if let Some(diag) = doc
@@ -1945,8 +2113,8 @@ pub fn goto_prev_diag(cx: &mut Context) {
19452113 goto_pos(editor, diag);
19462114}
19472115
1948−pub fn signature_help(cx: &mut Context) {
1949− let (view, doc) = cx.current();
2116+fn signature_help(cx: &mut Context) {
2117+ let (view, doc) = current!(cx.editor);
19502118
19512119 let language_server = match doc.language_server() {
19522120 Some(language_server) => language_server,
@@ -1960,7 +2128,7 @@ pub fn signature_help(cx: &mut Context) {
19602128 );
19612129
19622130 // 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);
19642132
19652133 cx.callback(
19662134 future,
@@ -1992,7 +2160,7 @@ pub mod insert {
19922160
19932161 fn completion(cx: &mut Context, ch: char) {
19942162 // if ch matches completion char, trigger completion
1995− let doc = cx.doc();
2163+ let doc = doc_mut!(cx.editor);
19962164 let language_server = match doc.language_server() {
19972165 Some(language_server) => language_server,
19982166 None => return,
@@ -2020,7 +2188,7 @@ pub mod insert {
20202188
20212189 fn signature_help(cx: &mut Context, ch: char) {
20222190 // if ch matches signature_help char, trigger
2023− let doc = cx.doc();
2191+ let doc = doc_mut!(cx.editor);
20242192 let language_server = match doc.language_server() {
20252193 Some(language_server) => language_server,
20262194 None => return,
@@ -2075,7 +2243,7 @@ pub mod insert {
20752243 const POST_HOOKS: &[PostHook] = &[completion, signature_help];
20762244
20772245 pub fn insert_char(cx: &mut Context, c: char) {
2078− let (view, doc) = cx.current();
2246+ let (view, doc) = current!(cx.editor);
20792247
20802248 let text = doc.text();
20812249 let selection = doc.selection(view.id);
@@ -2097,7 +2265,7 @@ pub mod insert {
20972265 }
20982266
20992267 pub fn insert_tab(cx: &mut Context) {
2100− let (view, doc) = cx.current();
2268+ let (view, doc) = current!(cx.editor);
21012269 // TODO: round out to nearest indentation level (for example a line with 3 spaces should
21022270 // indent by one to reach 4 spaces).
21032271
@@ -2107,7 +2275,7 @@ pub mod insert {
21072275 }
21082276
21092277 pub fn insert_newline(cx: &mut Context) {
2110− let (view, doc) = cx.current();
2278+ let (view, doc) = current!(cx.editor);
21112279 let text = doc.text().slice(..);
21122280
21132281 let contents = doc.text();
@@ -2176,7 +2344,7 @@ pub mod insert {
21762344 // TODO: handle indent-aware delete
21772345 pub fn delete_char_backward(cx: &mut Context) {
21782346 let count = cx.count();
2179− let (view, doc) = cx.current();
2347+ let (view, doc) = current!(cx.editor);
21802348 let text = doc.text().slice(..);
21812349 let transaction =
21822350 Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
@@ -2191,7 +2359,7 @@ pub mod insert {
21912359
21922360 pub fn delete_char_forward(cx: &mut Context) {
21932361 let count = cx.count();
2194− let (view, doc) = cx.current();
2362+ let (view, doc) = current!(cx.editor);
21952363 let text = doc.text().slice(..);
21962364 let transaction =
21972365 Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
@@ -2206,7 +2374,7 @@ pub mod insert {
22062374
22072375 pub fn delete_word_backward(cx: &mut Context) {
22082376 let count = cx.count();
2209− let (view, doc) = cx.current();
2377+ let (view, doc) = current!(cx.editor);
22102378 let text = doc.text().slice(..);
22112379 let selection = doc
22122380 .selection(view.id)
@@ -2221,21 +2389,23 @@ pub mod insert {
22212389// TODO: each command could simply return a Option<transaction>, then the higher level handles
22222390// storing it?
22232391
2224−pub fn undo(cx: &mut Context) {
2225− let view_id = cx.view().id;
2226− cx.doc().undo(view_id);
2392+fn undo(cx: &mut Context) {
2393+ let (view, doc) = current!(cx.editor);
2394+ let view_id = view.id;
2395+ doc.undo(view_id);
22272396}
22282397
2229−pub fn redo(cx: &mut Context) {
2230− let view_id = cx.view().id;
2231− cx.doc().redo(view_id);
2398+fn redo(cx: &mut Context) {
2399+ let (view, doc) = current!(cx.editor);
2400+ let view_id = view.id;
2401+ doc.redo(view_id);
22322402}
22332403
22342404// Yank / Paste
22352405
2236−pub fn yank(cx: &mut Context) {
2406+fn yank(cx: &mut Context) {
22372407 // TODO: should selections be made end inclusive?
2238− let (view, doc) = cx.current();
2408+ let (view, doc) = current!(cx.editor);
22392409 let values: Vec<String> = doc
22402410 .selection(view.id)
22412411 .fragments(doc.text().slice(..))
@@ -2300,9 +2470,10 @@ fn paste_impl(
23002470 Some(transaction)
23012471}
23022472
2303−pub fn replace_with_yanked(cx: &mut Context) {
2473+fn replace_with_yanked(cx: &mut Context) {
23042474 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;
23062477
23072478 if let Some(values) = registers.read(reg_name) {
23082479 if let Some(yank) = values.first() {
@@ -2329,9 +2500,10 @@ pub fn replace_with_yanked(cx: &mut Context) {
23292500// replace => replace
23302501// default insert
23312502
2332−pub fn paste_after(cx: &mut Context) {
2503+fn paste_after(cx: &mut Context) {
23332504 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;
23352507
23362508 if let Some(transaction) = registers
23372509 .read(reg_name)
@@ -2342,9 +2514,10 @@ pub fn paste_after(cx: &mut Context) {
23422514 }
23432515}
23442516
2345−pub fn paste_before(cx: &mut Context) {
2517+fn paste_before(cx: &mut Context) {
23462518 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;
23482521
23492522 if let Some(transaction) = registers
23502523 .read(reg_name)
@@ -2372,9 +2545,9 @@ fn get_lines(doc: &Document, view_id: ViewId) -> Vec<usize> {
23722545 lines
23732546}
23742547
2375−pub fn indent(cx: &mut Context) {
2548+fn indent(cx: &mut Context) {
23762549 let count = cx.count();
2377− let (view, doc) = cx.current();
2550+ let (view, doc) = current!(cx.editor);
23782551 let lines = get_lines(doc, view.id);
23792552
23802553 // Indent by one level
@@ -2391,9 +2564,9 @@ pub fn indent(cx: &mut Context) {
23912564 doc.append_changes_to_history(view.id);
23922565}
23932566
2394−pub fn unindent(cx: &mut Context) {
2567+fn unindent(cx: &mut Context) {
23952568 let count = cx.count();
2396− let (view, doc) = cx.current();
2569+ let (view, doc) = current!(cx.editor);
23972570 let lines = get_lines(doc, view.id);
23982571 let mut changes = Vec::with_capacity(lines.len());
23992572 let tab_width = doc.tab_width();
@@ -2431,8 +2604,8 @@ pub fn unindent(cx: &mut Context) {
24312604 doc.append_changes_to_history(view.id);
24322605}
24332606
2434−pub fn format_selections(cx: &mut Context) {
2435− let (view, doc) = cx.current();
2607+fn format_selections(cx: &mut Context) {
2608+ let (view, doc) = current!(cx.editor);
24362609
24372610 // via lsp if available
24382611 // else via tree-sitter indentation calculations
@@ -2477,9 +2650,9 @@ pub fn format_selections(cx: &mut Context) {
24772650 doc.append_changes_to_history(view.id);
24782651}
24792652
2480−pub fn join_selections(cx: &mut Context) {
2653+fn join_selections(cx: &mut Context) {
24812654 use movement::skip_while;
2482− let (view, doc) = cx.current();
2655+ let (view, doc) = current!(cx.editor);
24832656 let text = doc.text();
24842657 let slice = doc.text().slice(..);
24852658
@@ -2521,7 +2694,7 @@ pub fn join_selections(cx: &mut Context) {
25212694 doc.append_changes_to_history(view.id);
25222695}
25232696
2524−pub fn keep_selections(cx: &mut Context) {
2697+fn keep_selections(cx: &mut Context) {
25252698 // keep selections matching regex
25262699 let prompt = ui::regex_prompt(cx, "keep:".to_string(), move |view, doc, _, regex| {
25272700 let text = doc.text().slice(..);
@@ -2534,8 +2707,8 @@ pub fn keep_selections(cx: &mut Context) {
25342707 cx.push_layer(Box::new(prompt));
25352708}
25362709
2537−pub fn keep_primary_selection(cx: &mut Context) {
2538− let (view, doc) = cx.current();
2710+fn keep_primary_selection(cx: &mut Context) {
2711+ let (view, doc) = current!(cx.editor);
25392712
25402713 let range = doc.selection(view.id).primary();
25412714 let selection = Selection::single(range.anchor, range.head);
@@ -2544,14 +2717,14 @@ pub fn keep_primary_selection(cx: &mut Context) {
25442717
25452718//
25462719
2547−pub fn save(cx: &mut Context) {
2720+fn save(cx: &mut Context) {
25482721 // Spawns an async task to actually do the saving. This way we prevent blocking.
25492722
25502723 // TODO: handle save errors somehow?
2551− tokio::spawn(cx.doc().save());
2724+ tokio::spawn(doc_mut!(cx.editor).save());
25522725}
25532726
2554−pub fn completion(cx: &mut Context) {
2727+fn completion(cx: &mut Context) {
25552728 // trigger on trigger char, or if user calls it
25562729 // (or on word char typing??)
25572730 // after it's triggered, if response marked is_incomplete, update on every subsequent keypress
@@ -2588,7 +2761,7 @@ pub fn completion(cx: &mut Context) {
25882761 // The prefix still has to satisfy `company-minimum-prefix-length' before that
25892762 // happens. The value of nil means no idle completion."
25902763
2591− let (view, doc) = cx.current();
2764+ let (view, doc) = current!(cx.editor);
25922765
25932766 let language_server = match doc.language_server() {
25942767 Some(language_server) => language_server,
@@ -2600,7 +2773,7 @@ pub fn completion(cx: &mut Context) {
26002773 let pos = pos_to_lsp_pos(doc.text(), doc.selection(view.id).cursor(), offset_encoding);
26012774
26022775 // TODO: handle fails
2603− let future = language_server.completion(doc.identifier(), pos);
2776+ let future = language_server.completion(doc.identifier(), pos, None);
26042777
26052778 let trigger_offset = doc.selection(view.id).cursor();
26062779
@@ -2641,8 +2814,8 @@ pub fn completion(cx: &mut Context) {
26412814 //}
26422815}
26432816
2644−pub fn hover(cx: &mut Context) {
2645− let (view, doc) = cx.current();
2817+fn hover(cx: &mut Context) {
2818+ let (view, doc) = current!(cx.editor);
26462819
26472820 let language_server = match doc.language_server() {
26482821 Some(language_server) => language_server,
@@ -2658,7 +2831,7 @@ pub fn hover(cx: &mut Context) {
26582831 );
26592832
26602833 // 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);
26622835
26632836 cx.callback(
26642837 future,
@@ -2691,8 +2864,8 @@ pub fn hover(cx: &mut Context) {
26912864}
26922865
26932866// comments
2694−pub fn toggle_comments(cx: &mut Context) {
2695− let (view, doc) = cx.current();
2867+fn toggle_comments(cx: &mut Context) {
2868+ let (view, doc) = current!(cx.editor);
26962869 let transaction = comment::toggle_line_comments(doc.text(), doc.selection(view.id));
26972870
26982871 doc.apply(&transaction, view.id);
@@ -2701,8 +2874,8 @@ pub fn toggle_comments(cx: &mut Context) {
27012874
27022875// tree sitter node selection
27032876
2704−pub fn expand_selection(cx: &mut Context) {
2705− let (view, doc) = cx.current();
2877+fn expand_selection(cx: &mut Context) {
2878+ let (view, doc) = current!(cx.editor);
27062879
27072880 if let Some(syntax) = doc.syntax() {
27082881 let text = doc.text().slice(..);
@@ -2711,8 +2884,8 @@ pub fn expand_selection(cx: &mut Context) {
27112884 }
27122885}
27132886
2714−pub fn match_brackets(cx: &mut Context) {
2715− let (view, doc) = cx.current();
2887+fn match_brackets(cx: &mut Context) {
2888+ let (view, doc) = current!(cx.editor);
27162889
27172890 if let Some(syntax) = doc.syntax() {
27182891 let pos = doc.selection(view.id).cursor();
@@ -2725,23 +2898,23 @@ pub fn match_brackets(cx: &mut Context) {
27252898
27262899//
27272900
2728−pub fn jump_forward(cx: &mut Context) {
2901+fn jump_forward(cx: &mut Context) {
27292902 let count = cx.count();
2730− let (view, doc) = cx.current();
2903+ let (view, doc) = current!(cx.editor);
27312904
27322905 if let Some((id, selection)) = view.jumps.forward(count) {
27332906 view.doc = *id;
27342907 let selection = selection.clone();
2735− let (view, doc) = cx.current(); // refetch doc
2908+ let (view, doc) = current!(cx.editor); // refetch doc
27362909 doc.set_selection(view.id, selection);
27372910
27382911 align_view(doc, view, Align::Center);
27392912 };
27402913}
27412914
2742−pub fn jump_backward(cx: &mut Context) {
2915+fn jump_backward(cx: &mut Context) {
27432916 let count = cx.count();
2744− let (view, doc) = cx.current();
2917+ let (view, doc) = current!(cx.editor);
27452918
27462919 if let Some((id, selection)) = view.jumps.backward(view.id, doc, count) {
27472920 // 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) {
27502923 }
27512924 view.doc = *id;
27522925 let selection = selection.clone();
2753− let (view, doc) = cx.current(); // refetch doc
2926+ let (view, doc) = current!(cx.editor); // refetch doc
27542927 doc.set_selection(view.id, selection);
27552928
27562929 align_view(doc, view, Align::Center);
27572930 };
27582931}
27592932
2760−pub fn window_mode(cx: &mut Context) {
2933+fn window_mode(cx: &mut Context) {
27612934 cx.on_next_key(move |cx, event| {
27622935 if let KeyEvent {
27632936 code: KeyCode::Char(ch),
@@ -2775,15 +2948,17 @@ pub fn window_mode(cx: &mut Context) {
27752948 })
27762949}
27772950
2778−pub fn rotate_view(cx: &mut Context) {
2951+fn rotate_view(cx: &mut Context) {
27792952 cx.editor.focus_next()
27802953}
27812954
27822955// split helper, clear it later
27832956use helix_view::editor::Action;
2957+
2958+use self::cmd::TypableCommand;
27842959fn split(cx: &mut Context, action: Action) {
27852960 use helix_view::editor::Action;
2786− let (view, doc) = cx.current();
2961+ let (view, doc) = current!(cx.editor);
27872962 let id = doc.id();
27882963 let selection = doc.selection(view.id).clone();
27892964 let first_line = view.first_line;
@@ -2791,26 +2966,26 @@ fn split(cx: &mut Context, action: Action) {
27912966 cx.editor.switch(id, action);
27922967
27932968 // match the selection in the previous view
2794− let (view, doc) = cx.current();
2969+ let (view, doc) = current!(cx.editor);
27952970 view.first_line = first_line;
27962971 doc.set_selection(view.id, selection);
27972972}
27982973
2799−pub fn hsplit(cx: &mut Context) {
2974+fn hsplit(cx: &mut Context) {
28002975 split(cx, Action::HorizontalSplit);
28012976}
28022977
2803−pub fn vsplit(cx: &mut Context) {
2978+fn vsplit(cx: &mut Context) {
28042979 split(cx, Action::VerticalSplit);
28052980}
28062981
2807−pub fn wclose(cx: &mut Context) {
2808− let view_id = cx.view().id;
2982+fn wclose(cx: &mut Context) {
2983+ let view_id = view!(cx.editor).id;
28092984 // close current split
28102985 cx.editor.close(view_id, /* close_buffer */ false);
28112986}
28122987
2813−pub fn select_register(cx: &mut Context) {
2988+fn select_register(cx: &mut Context) {
28142989 cx.on_next_key(move |cx, event| {
28152990 if let KeyEvent {
28162991 code: KeyCode::Char(ch),
@@ -2822,7 +2997,7 @@ pub fn select_register(cx: &mut Context) {
28222997 })
28232998}
28242999
2825−pub fn space_mode(cx: &mut Context) {
3000+fn space_mode(cx: &mut Context) {
28263001 cx.on_next_key(move |cx, event| {
28273002 if let KeyEvent {
28283003 code: KeyCode::Char(ch),
@@ -2844,7 +3019,7 @@ pub fn space_mode(cx: &mut Context) {
28443019 })
28453020}
28463021
2847−pub fn view_mode(cx: &mut Context) {
3022+fn view_mode(cx: &mut Context) {
28483023 cx.on_next_key(move |cx, event| {
28493024 if let KeyEvent {
28503025 code: KeyCode::Char(ch),
@@ -2860,7 +3035,7 @@ pub fn view_mode(cx: &mut Context) {
28603035 | 't'
28613036 // bottom
28623037 | 'b' => {
2863− let (view, doc) = cx.current();
3038+ let (view, doc) = current!(cx.editor);
28643039
28653040 align_view(doc, view, match ch {
28663041 'z' | 'c' => Align::Center,
@@ -2870,7 +3045,7 @@ pub fn view_mode(cx: &mut Context) {
28703045 });
28713046 }
28723047 'm' => {
2873− let (view, doc) = cx.current();
3048+ let (view, doc) = current!(cx.editor);
28743049 let pos = doc.selection(view.id).cursor();
28753050 let pos = coords_at_pos(doc.text().slice(..), pos);
28763051
@@ -2887,7 +3062,7 @@ pub fn view_mode(cx: &mut Context) {
28873062 })
28883063}
28893064
2890−pub fn left_bracket_mode(cx: &mut Context) {
3065+fn left_bracket_mode(cx: &mut Context) {
28913066 cx.on_next_key(move |cx, event| {
28923067 if let KeyEvent {
28933068 code: KeyCode::Char(ch),
@@ -2903,7 +3078,7 @@ pub fn left_bracket_mode(cx: &mut Context) {
29033078 })
29043079}
29053080
2906−pub fn right_bracket_mode(cx: &mut Context) {
3081+fn right_bracket_mode(cx: &mut Context) {
29073082 cx.on_next_key(move |cx, event| {
29083083 if let KeyEvent {
29093084 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};
24use helix_core::hashmap;
35use helix_view::document::Mode;
4−use std::collections::HashMap;
6+use helix_view::input::{KeyCode, KeyEvent, KeyModifiers};
7+use serde::Deserialize;
8+use std::{
9+ collections::HashMap,
10+ fmt::Display,
11+ ops::{Deref, DerefMut},
12+ str::FromStr,
13+};
514
615// Kakoune-inspired:
716// mode = {
@@ -92,12 +101,6 @@ use std::collections::HashMap;
92101// D] = last diagnostic
93102// }
94103
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−
101104#[macro_export]
102105macro_rules! key {
103106 ($key:ident) => {
@@ -132,188 +135,207 @@ macro_rules! alt {
132135 };
133136}
134137
135−pub fn default() -> Keymaps {
136− let normal = hashmap!(
137− key!('h') => commands::move_char_left as Command,
138− key!('j') => commands::move_line_down,
139− key!('k') => commands::move_line_up,
140− key!('l') => commands::move_char_right,
141−
142− key!(Left) => commands::move_char_left,
143− key!(Down) => commands::move_line_down,
144− key!(Up) => commands::move_line_up,
145− key!(Right) => commands::move_char_right,
146−
147− key!('t') => commands::find_till_char,
148− key!('f') => commands::find_next_char,
149− key!('T') => commands::till_prev_char,
150− key!('F') => commands::find_prev_char,
151− // and matching set for select mode (extend)
152− //
153− key!('r') => commands::replace,
154− key!('R') => commands::replace_with_yanked,
155−
156− key!(Home) => commands::move_line_start,
157− key!(End) => commands::move_line_end,
158−
159− key!('w') => commands::move_next_word_start,
160− key!('b') => commands::move_prev_word_start,
161− key!('e') => commands::move_next_word_end,
162−
163− key!('v') => commands::select_mode,
164− key!('g') => commands::goto_mode,
165− key!(':') => commands::command_mode,
166−
167− key!('i') => commands::insert_mode,
168− key!('I') => commands::prepend_to_line,
169− key!('a') => commands::append_mode,
170− key!('A') => commands::append_to_line,
171− key!('o') => commands::open_below,
172− key!('O') => commands::open_above,
173− // [<space> ]<space> equivalents too (add blank new line, no edit)
174−
175−
176− key!('d') => commands::delete_selection,
177− // TODO: also delete without yanking
178− key!('c') => commands::change_selection,
179− // TODO: also change delete without yanking
180−
181− // key!('r') => commands::replace_with_char,
182−
183− key!('s') => commands::select_regex,
184− alt!('s') => commands::split_selection_on_newline,
185− key!('S') => commands::split_selection,
186− key!(';') => commands::collapse_selection,
187− alt!(';') => commands::flip_selections,
188− key!('%') => commands::select_all,
189− key!('x') => commands::select_line,
190− key!('X') => commands::extend_line,
191− // or select mode X?
192− // extend_to_whole_line, crop_to_whole_line
193−
194−
195− key!('m') => commands::match_brackets,
196− // TODO: refactor into
197− // key!('m') => commands::select_to_matching,
198− // key!('M') => commands::back_select_to_matching,
199− // select mode extend equivalents
200−
201− // key!('.') => commands::repeat_insert,
202− // repeat_select
203−
204− // TODO: figure out what key to use
205− // key!('[') => commands::expand_selection, ??
206− key!('[') => commands::left_bracket_mode,
207− key!(']') => commands::right_bracket_mode,
208−
209− key!('/') => commands::search,
210− // ? for search_reverse
211− key!('n') => commands::search_next,
212− key!('N') => commands::extend_search_next,
213− // N for search_prev
214− key!('*') => commands::search_selection,
215−
216− key!('u') => commands::undo,
217− key!('U') => commands::redo,
218−
219− key!('y') => commands::yank,
220− // yank_all
221− key!('p') => commands::paste_after,
222− // paste_all
223− key!('P') => commands::paste_before,
224−
225− key!('>') => commands::indent,
226− key!('<') => commands::unindent,
227− key!('=') => commands::format_selections,
228− key!('J') => commands::join_selections,
229− // TODO: conflicts hover/doc
230− key!('K') => commands::keep_selections,
231− // TODO: and another method for inverse
232−
233− // TODO: clashes with space mode
234− key!(' ') => commands::keep_primary_selection,
235−
236− // key!('q') => commands::record_macro,
237− // key!('Q') => commands::replay_macro,
238−
239− // ~ / apostrophe => change case
240− // & align selections
241− // _ trim selections
242−
243− // C / altC = copy (repeat) selections on prev/next lines
244−
245− key!(Esc) => commands::normal_mode,
246− key!(PageUp) => commands::page_up,
247− key!(PageDown) => commands::page_down,
248− ctrl!('b') => commands::page_up,
249− ctrl!('f') => commands::page_down,
250− ctrl!('u') => commands::half_page_up,
251− ctrl!('d') => commands::half_page_down,
252−
253− ctrl!('w') => commands::window_mode,
254−
255− // move under <space>c
256− ctrl!('c') => commands::toggle_comments,
257− key!('K') => commands::hover,
258−
259− // z family for save/restore/combine from/to sels from register
260−
261− // supposedly ctrl!('i') but did not work
262− key!(Tab) => commands::jump_forward,
263− ctrl!('o') => commands::jump_backward,
264− // ctrl!('s') => commands::save_selection,
265−
266− key!(' ') => commands::space_mode,
267− key!('z') => commands::view_mode,
268−
269− key!('"') => commands::select_register,
270− );
271− // 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
273− // because some selection operations can now be done from normal mode, some from select mode.
274− let mut select = normal.clone();
275− select.extend(
276− hashmap!(
277− key!('h') => commands::extend_char_left as Command,
278− key!('j') => commands::extend_line_down,
279− key!('k') => commands::extend_line_up,
280− key!('l') => commands::extend_char_right,
281−
282− key!(Left) => commands::extend_char_left,
283− key!(Down) => commands::extend_line_down,
284− key!(Up) => commands::extend_line_up,
285− key!(Right) => commands::extend_char_right,
286−
287− key!('w') => commands::extend_next_word_start,
288− key!('b') => commands::extend_prev_word_start,
289− key!('e') => commands::extend_next_word_end,
290−
291− key!('t') => commands::extend_till_char,
292− key!('f') => commands::extend_next_char,
293−
294− key!('T') => commands::extend_till_prev_char,
295− key!('F') => commands::extend_prev_char,
296− key!(Home) => commands::extend_line_start,
297− key!(End) => commands::extend_line_end,
298− key!(Esc) => commands::exit_select_mode,
299− )
300− .into_iter(),
301− );
302−
303− hashmap!(
304− // 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
306− Mode::Normal => normal,
307− Mode::Select => select,
308− Mode::Insert => hashmap!(
309− key!(Esc) => commands::normal_mode as Command,
310− key!(Backspace) => commands::insert::delete_char_backward,
311− key!(Delete) => commands::insert::delete_char_forward,
312− key!(Enter) => commands::insert::insert_newline,
313− key!(Tab) => commands::insert::insert_tab,
314−
315− ctrl!('x') => commands::completion,
316− ctrl!('w') => commands::insert::delete_word_backward,
317− ),
318− )
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 {
151+ fn default() -> Keymaps {
152+ let normal = hashmap!(
153+ key!('h') => Command::move_char_left,
154+ key!('j') => Command::move_line_down,
155+ key!('k') => Command::move_line_up,
156+ key!('l') => Command::move_char_right,
157+
158+ key!(Left) => Command::move_char_left,
159+ key!(Down) => Command::move_line_down,
160+ key!(Up) => Command::move_line_up,
161+ key!(Right) => Command::move_char_right,
162+
163+ key!('t') => Command::find_till_char,
164+ key!('f') => Command::find_next_char,
165+ key!('T') => Command::till_prev_char,
166+ key!('F') => Command::find_prev_char,
167+ // and matching set for select mode (extend)
168+ //
169+ key!('r') => Command::replace,
170+ key!('R') => Command::replace_with_yanked,
171+
172+ key!(Home) => Command::move_line_start,
173+ key!(End) => Command::move_line_end,
174+
175+ key!('w') => Command::move_next_word_start,
176+ key!('b') => Command::move_prev_word_start,
177+ key!('e') => Command::move_next_word_end,
178+
179+ key!('v') => Command::select_mode,
180+ key!('g') => Command::goto_mode,
181+ key!(':') => Command::command_mode,
182+
183+ key!('i') => Command::insert_mode,
184+ key!('I') => Command::prepend_to_line,
185+ key!('a') => Command::append_mode,
186+ key!('A') => Command::append_to_line,
187+ key!('o') => Command::open_below,
188+ key!('O') => Command::open_above,
189+ // [<space> ]<space> equivalents too (add blank new line, no edit)
190+
191+
192+ key!('d') => Command::delete_selection,
193+ // TODO: also delete without yanking
194+ key!('c') => Command::change_selection,
195+ // TODO: also change delete without yanking
196+
197+ // key!('r') => Command::replace_with_char,
198+
199+ key!('s') => Command::select_regex,
200+ alt!('s') => Command::split_selection_on_newline,
201+ key!('S') => Command::split_selection,
202+ key!(';') => Command::collapse_selection,
203+ alt!(';') => Command::flip_selections,
204+ key!('%') => Command::select_all,
205+ key!('x') => Command::select_line,
206+ key!('X') => Command::extend_line,
207+ // or select mode X?
208+ // extend_to_whole_line, crop_to_whole_line
209+
210+
211+ key!('m') => Command::match_brackets,
212+ // TODO: refactor into
213+ // key!('m') => commands::select_to_matching,
214+ // key!('M') => commands::back_select_to_matching,
215+ // select mode extend equivalents
216+
217+ // key!('.') => commands::repeat_insert,
218+ // repeat_select
219+
220+ // TODO: figure out what key to use
221+ // key!('[') => Command::expand_selection, ??
222+ key!('[') => Command::left_bracket_mode,
223+ key!(']') => Command::right_bracket_mode,
224+
225+ key!('/') => Command::search,
226+ // ? for search_reverse
227+ key!('n') => Command::search_next,
228+ key!('N') => Command::extend_search_next,
229+ // N for search_prev
230+ key!('*') => Command::search_selection,
231+
232+ key!('u') => Command::undo,
233+ key!('U') => Command::redo,
234+
235+ key!('y') => Command::yank,
236+ // yank_all
237+ key!('p') => Command::paste_after,
238+ // paste_all
239+ key!('P') => Command::paste_before,
240+
241+ key!('>') => Command::indent,
242+ key!('<') => Command::unindent,
243+ key!('=') => Command::format_selections,
244+ key!('J') => Command::join_selections,
245+ // TODO: conflicts hover/doc
246+ key!('K') => Command::keep_selections,
247+ // TODO: and another method for inverse
248+
249+ // TODO: clashes with space mode
250+ key!(' ') => Command::keep_primary_selection,
251+
252+ // key!('q') => Command::record_macro,
253+ // key!('Q') => Command::replay_macro,
254+
255+ // ~ / apostrophe => change case
256+ // & align selections
257+ // _ trim selections
258+
259+ // C / altC = copy (repeat) selections on prev/next lines
260+
261+ key!(Esc) => Command::normal_mode,
262+ key!(PageUp) => Command::page_up,
263+ key!(PageDown) => Command::page_down,
264+ ctrl!('b') => Command::page_up,
265+ ctrl!('f') => Command::page_down,
266+ ctrl!('u') => Command::half_page_up,
267+ ctrl!('d') => Command::half_page_down,
268+
269+ ctrl!('w') => Command::window_mode,
270+
271+ // move under <space>c
272+ ctrl!('c') => Command::toggle_comments,
273+ key!('K') => Command::hover,
274+
275+ // z family for save/restore/combine from/to sels from register
276+
277+ // supposedly ctrl!('i') but did not work
278+ key!(Tab) => Command::jump_forward,
279+ ctrl!('o') => Command::jump_backward,
280+ // ctrl!('s') => Command::save_selection,
281+
282+ key!(' ') => Command::space_mode,
283+ key!('z') => Command::view_mode,
284+
285+ key!('"') => Command::select_register,
286+ );
287+ // TODO: decide whether we want normal mode to also be select mode (kakoune-like), or whether
288+ // we keep this separate select mode. More keys can fit into normal mode then, but it's weird
289+ // because some selection operations can now be done from normal mode, some from select mode.
290+ let mut select = normal.clone();
291+ select.extend(
292+ hashmap!(
293+ key!('h') => Command::extend_char_left,
294+ key!('j') => Command::extend_line_down,
295+ key!('k') => Command::extend_line_up,
296+ key!('l') => Command::extend_char_right,
297+
298+ key!(Left) => Command::extend_char_left,
299+ key!(Down) => Command::extend_line_down,
300+ key!(Up) => Command::extend_line_up,
301+ key!(Right) => Command::extend_char_right,
302+
303+ key!('w') => Command::extend_next_word_start,
304+ key!('b') => Command::extend_prev_word_start,
305+ key!('e') => Command::extend_next_word_end,
306+
307+ key!('t') => Command::extend_till_char,
308+ key!('f') => Command::extend_next_char,
309+
310+ key!('T') => Command::extend_till_prev_char,
311+ key!('F') => Command::extend_prev_char,
312+ key!(Home) => Command::extend_line_start,
313+ key!(End) => Command::extend_line_end,
314+ key!(Esc) => Command::exit_select_mode,
315+ )
316+ .into_iter(),
317+ );
318+
319+ Keymaps(hashmap!(
320+ // as long as you cast the first item, rust is able to infer the other cases
321+ // TODO: select could be normal mode with some bindings merged over
322+ Mode::Normal => normal,
323+ Mode::Select => select,
324+ Mode::Insert => hashmap!(
325+ key!(Esc) => Command::normal_mode as Command,
326+ key!(Backspace) => Command::delete_char_backward,
327+ key!(Delete) => Command::delete_char_forward,
328+ key!(Enter) => Command::insert_newline,
329+ key!(Tab) => Command::insert_tab,
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,
336+ ctrl!('x') => Command::completion,
337+ ctrl!('w') => Command::delete_word_backward,
338+ ),
339+ ))
340+ }
319341}
helix-term/src/lib.rs+4
@@ -1,8 +1,12 @@
11#![allow(unused)]
22
3+#[macro_use]
4+extern crate helix_view;
5+
36pub mod application;
47pub mod args;
58pub mod commands;
69pub mod compositor;
10+pub mod config;
711pub mod keymap;
812pub mod ui;
helix-term/src/main.rs+9−4
@@ -1,10 +1,9 @@
1+use anyhow::{Context, Error, Result};
12use helix_term::application::Application;
23use helix_term::args::Args;
3−
4+use helix_term::config::Config;
45use std::path::PathBuf;
56
6−use anyhow::{Context, Result};
7−
87fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> {
98 let mut base_config = fern::Dispatch::new();
109
@@ -89,10 +88,16 @@ FLAGS:
8988 std::fs::create_dir_all(&conf_dir).ok();
9089 }
9190
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+
9297 setup_logging(logpath, args.verbosity).context("failed to initialize logging")?;
9398
9499 // 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")?;
96101 app.run().await.unwrap();
97102
98103 Ok(())
helix-term/src/ui/completion.rs+3−3
@@ -89,7 +89,7 @@ impl Completion {
8989 // doc.state = snapshot.clone();
9090 }
9191 PromptEvent::Validate => {
92− let (view, doc) = editor.current();
92+ let (view, doc) = current!(editor);
9393
9494 // revert state to what it was before the last update
9595 // doc.state = snapshot.clone();
@@ -169,7 +169,7 @@ impl Completion {
169169 pub fn update(&mut self, cx: &mut commands::Context) {
170170 // recompute menu based on matches
171171 let menu = self.popup.contents_mut();
172− let (view, doc) = cx.editor.current();
172+ let (view, doc) = current!(cx.editor);
173173
174174 // cx.hooks()
175175 // cx.add_hook(enum type, ||)
@@ -233,7 +233,7 @@ impl Component for Completion {
233233 // ---
234234 // option.documentation
235235
236− let (view, doc) = cx.editor.current();
236+ let (view, doc) = current!(cx.editor);
237237 let language = doc
238238 .language()
239239 .and_then(|scope| scope.strip_prefix("source."))
helix-term/src/ui/editor.rs+25−29
@@ -11,15 +11,13 @@ use helix_core::{
1111 syntax::{self, HighlightEvent},
1212 Position, Range,
1313};
14−use helix_view::{
15− document::{IndentStyle, Mode},
16− Document, Editor, Theme, View,
17−};
14+use helix_view::input::{KeyCode, KeyEvent, KeyModifiers};
15+use helix_view::{document::Mode, Document, Editor, Theme, View};
1816use std::borrow::Cow;
1917
2018use crossterm::{
2119 cursor,
22− event::{read, Event, EventStream, KeyCode, KeyEvent, KeyModifiers},
20+ event::{read, Event, EventStream},
2321};
2422use tui::{
2523 backend::CrosstermBackend,
@@ -30,7 +28,7 @@ use tui::{
3028};
3129
3230pub struct EditorView {
33− keymap: Keymaps,
31+ keymaps: Keymaps,
3432 on_next_key: Option<Box<dyn FnOnce(&mut commands::Context, KeyEvent)>>,
3533 last_insert: (commands::Command, Vec<KeyEvent>),
3634 completion: Option<Completion>,
@@ -40,16 +38,16 @@ const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
4038
4139impl Default for EditorView {
4240 fn default() -> Self {
43− Self::new()
41+ Self::new(Keymaps::default())
4442 }
4543}
4644
4745impl EditorView {
48− pub fn new() -> Self {
46+ pub fn new(keymaps: Keymaps) -> Self {
4947 Self {
50− keymap: keymap::default(),
48+ keymaps,
5149 on_next_key: None,
52− last_insert: (commands::normal_mode, Vec::new()),
50+ last_insert: (commands::Command::normal_mode, Vec::new()),
5351 completion: None,
5452 }
5553 }
@@ -479,18 +477,15 @@ impl EditorView {
479477 Mode::Select => "SEL",
480478 Mode::Normal => "NOR",
481479 };
482− let text_color = if is_focused {
483− theme.get("ui.text.focus")
480+ let style = if is_focused {
481+ theme.get("ui.statusline")
484482 } else {
485− theme.get("ui.text")
483+ theme.get("ui.statusline.inactive")
486484 };
487485 // statusline
488− surface.set_style(
489− Rect::new(viewport.x, viewport.y, viewport.width, 1),
490− theme.get("ui.statusline"),
491− );
486+ surface.set_style(Rect::new(viewport.x, viewport.y, viewport.width, 1), style);
492487 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);
494489 }
495490
496491 if let Some(path) = doc.relative_path() {
@@ -502,7 +497,7 @@ impl EditorView {
502497 viewport.y,
503498 title,
504499 viewport.width.saturating_sub(6) as usize,
505− text_color,
500+ style,
506501 );
507502 }
508503
@@ -541,13 +536,13 @@ impl EditorView {
541536 viewport.x + viewport.width.saturating_sub(text_len),
542537 viewport.y,
543538 right_side_text,
544− text_color,
539+ style,
545540 );
546541 }
547542
548543 fn insert_mode(&self, cx: &mut commands::Context, event: KeyEvent) {
549− if let Some(command) = self.keymap[&Mode::Insert].get(&event) {
550− command(cx);
544+ if let Some(command) = self.keymaps[&Mode::Insert].get(&event) {
545+ command.execute(cx);
551546 } else if let KeyEvent {
552547 code: KeyCode::Char(ch),
553548 ..
@@ -568,7 +563,7 @@ impl EditorView {
568563 // special handling for repeat operator
569564 key!('.') => {
570565 // first execute whatever put us into insert mode
571− (self.last_insert.0)(cxt);
566+ self.last_insert.0.execute(cxt);
572567 // then replay the inputs
573568 for key in &self.last_insert.1 {
574569 self.insert_mode(cxt, *key)
@@ -584,8 +579,8 @@ impl EditorView {
584579 // set the register
585580 cxt.selected_register = cxt.editor.selected_register.take();
586581
587− if let Some(command) = self.keymap[&mode].get(&event) {
588− command(cxt);
582+ if let Some(command) = self.keymaps[&mode].get(&event) {
583+ command.execute(cxt);
589584 }
590585 }
591586 }
@@ -613,12 +608,13 @@ impl Component for EditorView {
613608 cx.editor.resize(Rect::new(0, 0, width, height - 1));
614609 EventResult::Consumed(None)
615610 }
616− Event::Key(mut key) => {
611+ Event::Key(key) => {
612+ let mut key = KeyEvent::from(key);
617613 canonicalize_key(&mut key);
618614 // clear status
619615 cx.editor.status_msg = None;
620616
621− let (view, doc) = cx.editor.current();
617+ let (view, doc) = current!(cx.editor);
622618 let mode = doc.mode();
623619
624620 let mut cxt = commands::Context {
@@ -687,7 +683,7 @@ impl Component for EditorView {
687683 return EventResult::Ignored;
688684 }
689685
690− let (view, doc) = cx.editor.current();
686+ let (view, doc) = current!(cx.editor);
691687 view.ensure_cursor_in_view(doc);
692688
693689 // mode transitions
@@ -699,7 +695,7 @@ impl Component for EditorView {
699695 // how we entered insert mode is important, and we should track that so
700696 // we can repeat the side effect.
701697
702− self.last_insert.0 = self.keymap[&mode][&key];
698+ self.last_insert.0 = self.keymaps[&mode][&key];
703699 self.last_insert.1.clear();
704700 }
705701 (Mode::Insert, Mode::Normal) => {
helix-term/src/ui/mod.rs+20−8
@@ -30,8 +30,9 @@ pub fn regex_prompt(
3030 prompt: String,
3131 fun: impl Fn(&mut View, &mut Document, &mut Registers, Regex) + 'static,
3232) -> Prompt {
33− let view_id = cx.view().id;
34− let snapshot = cx.doc().selection(view_id).clone();
33+ let (view, doc) = current!(cx.editor);
34+ let view_id = view.id;
35+ let snapshot = doc.selection(view_id).clone();
3536
3637 Prompt::new(
3738 prompt,
@@ -40,7 +41,7 @@ pub fn regex_prompt(
4041 match event {
4142 PromptEvent::Abort => {
4243 // TODO: also revert text
43− let (view, doc) = editor.current();
44+ let (view, doc) = current!(editor);
4445 doc.set_selection(view.id, snapshot.clone());
4546 }
4647 PromptEvent::Validate => {
@@ -54,7 +55,8 @@ pub fn regex_prompt(
5455
5556 match Regex::new(input) {
5657 Ok(regex) => {
57− let (view, doc, registers) = editor.current_with_registers();
58+ let (view, doc) = current!(editor);
59+ let registers = &mut editor.registers;
5860
5961 // revert state to what it was before the last update
6062 // TODO: also revert text
@@ -124,10 +126,11 @@ pub mod completers {
124126 use ignore::WalkBuilder;
125127 use std::path::{Path, PathBuf};
126128
127− let path = Path::new(input);
129+ let is_tilde = input.starts_with('~') && input.len() == 1;
130+ let path = helix_view::document::expand_tilde(Path::new(input));
128131
129132 let (dir, file_name) = if input.ends_with('/') {
130− (path.into(), None)
133+ (path, None)
131134 } else {
132135 let file_name = path
133136 .file_name()
@@ -152,7 +155,16 @@ pub mod completers {
152155 let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());
153156
154157 let path = entry.path();
155− let mut path = path.strip_prefix(&dir).unwrap_or(path).to_path_buf();
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 {
166+ path.strip_prefix(&dir).unwrap_or(path).to_path_buf()
167+ };
156168
157169 if is_dir {
158170 path.push("");
@@ -182,7 +194,7 @@ pub mod completers {
182194 })
183195 .collect();
184196
185− let range = ((input.len() - file_name.len())..);
197+ let range = ((input.len().saturating_sub(file_name.len()))..);
186198
187199 matches.sort_unstable_by_key(|(_file, score)| Reverse(*score));
188200 files = matches
helix-term/src/ui/prompt.rs+17−2
@@ -53,7 +53,16 @@ impl Prompt {
5353 }
5454
5555 pub fn insert_char(&mut self, c: char) {
56− self.line.insert(self.cursor, c);
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+ };
65+ self.line.insert(pos, c);
5766 self.cursor += 1;
5867 self.completion = (self.completion_fn)(&self.line);
5968 self.exit_selection();
@@ -79,7 +88,13 @@ impl Prompt {
7988
8089 pub fn delete_char_backwards(&mut self) {
8190 if self.cursor > 0 {
82− self.line.remove(self.cursor - 1);
91+ let pos = self
92+ .line
93+ .char_indices()
94+ .nth(self.cursor - 1)
95+ .map(|(pos, _)| pos)
96+ .expect("line is not empty");
97+ self.line.remove(pos);
8398 self.cursor -= 1;
8499 self.completion = (self.completion_fn)(&self.line);
85100 }
helix-tui/Cargo.toml+4
@@ -7,6 +7,10 @@ A library to build rich terminal user interfaces or dashboards
77"""
88edition = "2018"
99license = "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"]
1014
1115[features]
1216default = ["crossterm"]
helix-view/Cargo.toml+7−2
@@ -4,6 +4,10 @@ version = "0.2.0"
44authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
55edition = "2018"
66license = "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"
711
812# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
913
@@ -13,8 +17,8 @@ default = ["term"]
1317
1418[dependencies]
1519anyhow = "1"
16−helix-core = { path = "../helix-core" }
17−helix-lsp = { path = "../helix-lsp"}
20+helix-core = { version = "0.2", path = "../helix-core" }
21+helix-lsp = { version = "0.2", path = "../helix-lsp"}
1822
1923# Conversion traits
2024tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"], optional = true }
@@ -23,6 +27,7 @@ once_cell = "1.8"
2327url = "2"
2428
2529tokio = { version = "1", features = ["full"] }
30+futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
2631
2732slotmap = "1"
2833
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};
23use std::cell::Cell;
4+use std::collections::HashMap;
5+use std::fmt::Display;
36use std::future::Future;
47use std::path::{Component, Path, PathBuf};
8+use std::str::FromStr;
59use std::sync::Arc;
610
711use helix_core::{
@@ -15,8 +19,6 @@ use helix_core::{
1519
1620use crate::{DocumentId, ViewId};
1721
18−use std::collections::HashMap;
19−
2022#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
2123pub enum Mode {
2224 Normal,
@@ -24,6 +26,40 @@ pub enum Mode {
2426 Insert,
2527}
2628
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+
2763#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
2864pub enum IndentStyle {
2965 Tabs,
@@ -105,6 +141,36 @@ where
105141 }
106142}
107143
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+
108174/// Normalize a path, removing things like `.` and `..`.
109175///
110176/// CAUTION: This does not resolve symlinks (unlike
@@ -115,6 +181,7 @@ where
115181/// needs to improve on.
116182/// Copied from cargo: https://github.com/rust-lang/cargo/blob/070e459c2d8b79c5b2ac5218064e7603329c92ae/crates/cargo-util/src/paths.rs#L81
117183pub fn normalize_path(path: &Path) -> PathBuf {
184+ let path = expand_tilde(path);
118185 let mut components = path.components().peekable();
119186 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
120187 components.next();
@@ -141,12 +208,17 @@ pub fn normalize_path(path: &Path) -> PathBuf {
141208 ret
142209}
143210
144−// Returns the canonical, absolute form of a path with all intermediate components normalized.
145−//
146−// 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.
211+/// Returns the canonical, absolute form of a path with all intermediate components normalized.
212+///
213+/// This function is used instead of `std::fs::canonicalize` because we don't want to verify
214+/// here if the path exists, just normalize it's components.
148215pub fn canonicalize_path(path: &Path) -> std::io::Result<PathBuf> {
149− std::env::current_dir().map(|current_dir| normalize_path(&current_dir.join(path)))
216+ let normalized = normalize_path(path);
217+ if normalized.is_absolute() {
218+ Ok(normalized)
219+ } else {
220+ std::env::current_dir().map(|current_dir| current_dir.join(normalized))
221+ }
150222}
151223
152224use helix_lsp::lsp;
@@ -210,10 +282,11 @@ impl Document {
210282 pub fn format(&mut self, view_id: ViewId) {
211283 if let Some(language_server) = self.language_server() {
212284 // TODO: await, no blocking
213− let transaction = helix_lsp::block_on(
214− language_server
215− .text_document_formatting(self.identifier(), lsp::FormattingOptions::default()),
216− )
285+ let transaction = helix_lsp::block_on(language_server.text_document_formatting(
286+ self.identifier(),
287+ lsp::FormattingOptions::default(),
288+ None,
289+ ))
217290 .map(|edits| {
218291 helix_lsp::util::generate_transaction_from_edits(
219292 self.text(),
@@ -696,12 +769,19 @@ impl Document {
696769 &self.selections[&view_id]
697770 }
698771
699− pub fn relative_path(&self) -> Option<&Path> {
772+ pub fn relative_path(&self) -> Option<PathBuf> {
700773 let cwdir = std::env::current_dir().expect("couldn't determine current directory");
701774
702− self.path
703− .as_ref()
704− .map(|path| path.strip_prefix(cwdir).unwrap_or(path))
775+ self.path.as_ref().map(|path| {
776+ let path = fold_home_dir(path);
777+ if path.is_relative() {
778+ path
779+ } else {
780+ path.strip_prefix(cwdir)
781+ .map(|p| p.to_path_buf())
782+ .unwrap_or(path)
783+ }
784+ })
705785 }
706786
707787 // 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
22use tui::layout::Rect;
33use tui::terminal::CursorKind;
44
5+use futures_util::future;
56use std::path::PathBuf;
7+use std::time::Duration;
68
79use slotmap::SlotMap;
810
@@ -101,19 +103,19 @@ impl Editor {
101103
102104 match action {
103105 Action::Replace => {
104− let view = self.view();
106+ let view = view!(self);
105107 let jump = (
106108 view.doc,
107109 self.documents[view.doc].selection(view.id).clone(),
108110 );
109111
110− let view = self.view_mut();
112+ let view = view_mut!(self);
111113 view.jumps.push(jump);
112114 view.last_accessed_doc = Some(view.doc);
113115 view.doc = id;
114116 view.first_line = 0;
115117
116− let (view, doc) = self.current();
118+ let (view, doc) = current!(self);
117119
118120 // initialize selection for view
119121 let selection = doc
@@ -238,27 +240,6 @@ impl Editor {
238240 self.tree.is_empty()
239241 }
240242
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−
262243 pub fn ensure_cursor_in_view(&mut self, id: ViewId) {
263244 let view = self.tree.get_mut(id);
264245 let doc = &self.documents[view.doc];
@@ -280,7 +261,7 @@ impl Editor {
280261
281262 pub fn cursor(&self) -> (Option<Position>, CursorKind) {
282263 const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
283− let view = self.view();
264+ let view = view!(self);
284265 let doc = &self.documents[view.doc];
285266 let cursor = doc.selection(view.id).cursor();
286267 if let Some(mut pos) = view.screen_coords_at_pos(doc, doc.text().slice(..), cursor) {
@@ -291,4 +272,22 @@ impl Editor {
291272 (None, CursorKind::Hidden)
292273 }
293274 }
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+ }
294293}
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+
14pub mod document;
25pub mod editor;
6+pub mod input;
37pub mod register_selection;
48pub mod theme;
59pub mod tree;
610pub mod view;
711
8−use slotmap::new_key_type;
9−new_key_type! { pub struct DocumentId; }
10−new_key_type! { pub struct ViewId; }
12+slotmap::new_key_type! {
13+ pub struct DocumentId;
14+ pub struct ViewId;
15+}
1116
1217pub use document::Document;
1318pub 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 @@
4141"ui.background" = { bg = "#3b224c" } # midnight
4242"ui.linenr" = { fg = "#5a5977" } # comet
4343"ui.linenr.selected" = { fg = "#dbbfef" } # lilac
44−"ui.statusline" = { bg = "#281733" } # revolver
44+"ui.statusline" = { fg = "#dbbfef", bg = "#281733" } # revolver
45+"ui.statusline.inactive" = { fg = "#a4a0e8", bg = "#281733" } # revolver
4546"ui.popup" = { bg = "#281733" } # revolver
4647"ui.window" = { bg = "#452859" } # bossa nova
4748"ui.window" = { bg = "#452859" } # bossa nova