From 9507c24f6845c86fd69fd380045753e4d89c3471 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jul 2021 23:08:19 +0000 Subject: [PATCH 01/31] Bump thiserror from 1.0.25 to 1.0.26 Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.25 to 1.0.26. - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/1.0.25...1.0.26) --- updated-dependencies: - dependency-name: thiserror dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2cd202f3..a44ed5b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -957,18 +957,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa6f76457f59514c7eeb4e59d891395fab0b2fd1d40723ae737d64153392e9c6" +checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d" +checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" dependencies = [ "proc-macro2", "quote", From bb41a82a85f6cc2b2867bf77c36d2b642eaa4f3e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jul 2021 23:08:06 +0000 Subject: [PATCH 02/31] Bump slotmap from 1.0.3 to 1.0.5 Bumps [slotmap](https://github.com/orlp/slotmap) from 1.0.3 to 1.0.5. - [Release notes](https://github.com/orlp/slotmap/releases) - [Changelog](https://github.com/orlp/slotmap/blob/master/RELEASES.md) - [Commits](https://github.com/orlp/slotmap/compare/v1.0.3...v1.0.5) --- updated-dependencies: - dependency-name: slotmap dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a44ed5b4..a65950ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -914,9 +914,9 @@ checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527" [[package]] name = "slotmap" -version = "1.0.3" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585cd5dffe4e9e06f6dfdf66708b70aca3f781bed561f4f667b2d9c0d4559e36" +checksum = "a952280edbecfb1d4bd3cf2dbc309dc6ab523e53487c438ae21a6df09fe84bc4" dependencies = [ "version_check", ] From c8681a820ccd149dedc631b6e94bc6ece0442fcb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jul 2021 23:07:56 +0000 Subject: [PATCH 03/31] Bump unicode-segmentation from 1.7.1 to 1.8.0 Bumps [unicode-segmentation](https://github.com/unicode-rs/unicode-segmentation) from 1.7.1 to 1.8.0. - [Release notes](https://github.com/unicode-rs/unicode-segmentation/releases) - [Commits](https://github.com/unicode-rs/unicode-segmentation/compare/1.7.1...v1.8.0) --- updated-dependencies: - dependency-name: unicode-segmentation dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- helix-core/Cargo.toml | 2 +- helix-tui/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a65950ab..657df77c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1104,9 +1104,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" +checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" [[package]] name = "unicode-width" diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml index 80d559a9..634c4d9f 100644 --- a/helix-core/Cargo.toml +++ b/helix-core/Cargo.toml @@ -19,7 +19,7 @@ helix-syntax = { version = "0.3", path = "../helix-syntax" } ropey = "1.3" smallvec = "1.4" tendril = "0.4.2" -unicode-segmentation = "1.7" +unicode-segmentation = "1.8" unicode-width = "0.1" unicode-general-category = "0.4" # slab = "0.4.2" diff --git a/helix-tui/Cargo.toml b/helix-tui/Cargo.toml index 7f98144c..33a9427a 100644 --- a/helix-tui/Cargo.toml +++ b/helix-tui/Cargo.toml @@ -18,7 +18,7 @@ default = ["crossterm"] [dependencies] bitflags = "1.0" cassowary = "0.3" -unicode-segmentation = "1.2" +unicode-segmentation = "1.8" crossterm = { version = "0.20", optional = true } serde = { version = "1", "optional" = true, features = ["derive"]} helix-view = { version = "0.3", path = "../helix-view", features = ["term"] } From 47a6882738c7a125f6746bd20f4c67c517108a56 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jul 2021 23:07:47 +0000 Subject: [PATCH 04/31] Bump tokio from 1.7.1 to 1.8.0 Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.7.1 to 1.8.0. - [Release notes](https://github.com/tokio-rs/tokio/releases) - [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.7.1...tokio-1.8.0) --- updated-dependencies: - dependency-name: tokio dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- helix-lsp/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 657df77c..ab3cdd1e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1010,9 +1010,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fb2ed024293bb19f7a5dc54fe83bf86532a44c12a2bb8ba40d64a4509395ca2" +checksum = "570c2eb13b3ab38208130eccd41be92520388791207fde783bda7c1e8ace28d4" dependencies = [ "autocfg", "bytes", diff --git a/helix-lsp/Cargo.toml b/helix-lsp/Cargo.toml index 8d43af69..3a13fb56 100644 --- a/helix-lsp/Cargo.toml +++ b/helix-lsp/Cargo.toml @@ -23,5 +23,5 @@ lsp-types = { version = "0.89", features = ["proposed"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror = "1.0" -tokio = { version = "1.7", features = ["full"] } +tokio = { version = "1.8", features = ["full"] } tokio-stream = "0.1.6" From a0a5bd555b47cbc9fbbd73f680eef62ae3dcb511 Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Sun, 4 Jul 2021 00:19:59 +0800 Subject: [PATCH 05/31] More responsive key input Use biased select!, don't eagerly process lsp message since we want to prioritize user input rather than lsp messages, but still limit rendering for lsp messages. --- helix-term/src/application.rs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 17ba2652..79dd7c3b 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -9,6 +9,7 @@ use log::error; use std::{ io::{stdout, Write}, sync::Arc, + time::{Duration, Instant}, }; use anyhow::Error; @@ -130,6 +131,8 @@ impl Application { pub async fn event_loop(&mut self) { let mut reader = EventStream::new(); + let mut last_render = Instant::now(); + let deadline = Duration::from_secs(1) / 60; self.render(); @@ -139,26 +142,22 @@ impl Application { break; } - use futures_util::{FutureExt, StreamExt}; + use futures_util::StreamExt; tokio::select! { + biased; + event = reader.next() => { self.handle_terminal_events(event) } Some((id, call)) = self.editor.language_servers.incoming.next() => { self.handle_language_server_message(call, id).await; - - // eagerly process any other available notifications/calls - let now = std::time::Instant::now(); - let deadline = std::time::Duration::from_millis(10); - while let Some(Some((id, call))) = self.editor.language_servers.incoming.next().now_or_never() { - self.handle_language_server_message(call, id).await; - - if now.elapsed() > deadline { // use a deadline so we don't block too long - break; - } + // limit render calls for fast language server messages + let last = self.editor.language_servers.incoming.is_empty(); + if last || last_render.elapsed() > deadline { + self.render(); + last_render = Instant::now(); } - self.render(); } Some(callback) = self.jobs.futures.next() => { self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback); From 906cfd52e070dbffa47b3c2ec6dbcd25b6e5cdc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Tue, 6 Jul 2021 10:53:29 +0900 Subject: [PATCH 06/31] Clean up the default theme definition --- theme.toml | 115 +++++++++++++++++++++++++++++------------------------ 1 file changed, 64 insertions(+), 51 deletions(-) diff --git a/theme.toml b/theme.toml index 13753dc6..34cd09b6 100644 --- a/theme.toml +++ b/theme.toml @@ -1,68 +1,81 @@ -"attribute" = "#dbbfef" # lilac -"keyword" = "#eccdba" # almond -"keyword.directive" = "#dbbfef" # lilac -- preprocessor comments (#if in C) -"namespace" = "#dbbfef" # lilac -"punctuation" = "#a4a0e8" # lavender -"punctuation.delimiter" = "#a4a0e8" # lavender -"operator" = "#dbbfef" # lilac -"special" = "#efba5d" # honey -# "property" = "#a4a0e8" # lavender -"property" = "#ffffff" # white -"variable" = "#a4a0e8" # lavender -# "variable" = "#eccdba" # almond TODO: metavariables only -"variable.parameter" = "#a4a0e8" # lavender -# TODO distinguish type from type.builtin? -"type" = "#ffffff" # white -"type.builtin" = "#ffffff" # white -"constructor" = "#dbbfef" # lilac -"function" = "#ffffff" # white -"function.macro" = "#dbbfef" # lilac -"function.builtin" = "#ffffff" # white -"comment" = "#697C81" # sirocco -"variable.builtin" = "#9ff28f" # mint -"constant" = "#ffffff" # white -"constant.builtin" = "#ffffff" # white -"string" = "#cccccc" # silver -"number" = "#e8dca0" # chamois -"escape" = "#efba5d" # honey +attribute = "lilac" +keyword = "almond" +"keyword.directive" = "lilac" # -- preprocessor comments (#if in C) +namespace = "lilac" +punctuation = "lavender" +"punctuation.delimiter" = "lavender" +operator = "lilac" +special = "honey" +property = "white" +variable = "lavender" +# variable = "almond" # TODO: metavariables only +"variable.parameter" = "lavender" +"variable.builtin" = "mint" +type = "white" +"type.builtin" = "white" # TODO: distinguish? +constructor = "lilac" +function = "white" +"function.macro" = "lilac" +"function.builtin" = "white" +comment = "sirocco" +constant = "white" +"constant.builtin" = "white" +string = "silver" +number = "chamois" +escape = "honey" # used for lifetimes -"label" = "#efba5d" # honey +label = "honey" -# TODO: diferentiate number builtin # TODO: diferentiate doc comment -# TODO: variable as lilac -# TODO: mod/use statements as white -# TODO: mod stuff as chamois -# # concat (ERROR) @syntax-error and "MISSING ;" selectors for errors -"module" = "#ff0000" +module = "#ff0000" -"ui.background" = { bg = "#3b224c" } # midnight -"ui.linenr" = { fg = "#5a5977" } # comet -"ui.linenr.selected" = { fg = "#dbbfef" } # lilac -"ui.statusline" = { fg = "#dbbfef", bg = "#281733" } # revolver -"ui.statusline.inactive" = { fg = "#a4a0e8", bg = "#281733" } # revolver -"ui.popup" = { bg = "#281733" } # revolver -"ui.window" = { fg = "#452859" } # bossa nova +"ui.background" = { bg = "midnight" } +"ui.linenr" = { fg = "comet" } +"ui.linenr.selected" = { fg = "lilac" } +"ui.statusline" = { fg = "lilac", bg = "revolver" } +"ui.statusline.inactive" = { fg = "lavender", bg = "revolver" } +"ui.popup" = { bg = "revolver" } +"ui.window" = { fg = "bossanova" } "ui.help" = { bg = "#7958DC", fg = "#171452" } -"ui.text" = { fg = "#a4a0e8" } # lavender -"ui.text.focus" = { fg = "#dbbfef" } # lilac +"ui.text" = { fg = "lavender" } +"ui.text.focus" = { fg = "lilac" } "ui.selection" = { bg = "#540099" } "ui.selection.primary" = { bg = "#540099" } # TODO: namespace ui.cursor as ui.selection.cursor? -"ui.cursor.select" = { bg = "#6F44F0" } -"ui.cursor.insert" = { bg = "#ffffff" } +"ui.cursor.select" = { bg = "delta" } +"ui.cursor.insert" = { bg = "white" } "ui.cursor.match" = { fg = "#212121", bg = "#6C6999" } "ui.cursor" = { modifiers = ["reversed"] } -"ui.menu.selected" = { fg = "#281733", bg = "#ffffff" } # revolver +"ui.menu.selected" = { fg = "revolver", bg = "white" } -"diagnostic" = { modifiers = ["underlined"] } +diagnostic = { modifiers = ["underlined"] } -"warning" = "#ffcd1c" -"error" = "#f47868" -"info" = "#6F44F0" -"hint" = "#cccccc" +warning = "lightning" +error = "apricot" +info = "delta" +hint = "silver" + +[palette] +white = "#ffffff" +lilac = "#dbbfef" +lavender = "#a4a0e8" +comet = "#5a5977" +bossanova = "#452859" +midnight = "#3b224c" +revolver = "#281733" + +silver = "#cccccc" +sirocco = "#697C81" +mint = "#9ff28f" +almond = "#eccdba" +chamois = "#a4a0e8" +honey = "#efba5d" + +apricot = "#f47868" +lightning = "#ffcd1c" +delta = "#6F44F0" From 1c71fced0e38a4f126155cb7161d4f4fa4f82673 Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Tue, 6 Jul 2021 22:18:30 +0800 Subject: [PATCH 07/31] Add more modes to infobox --- helix-term/src/commands.rs | 132 ++++++++++++++++--------------------- 1 file changed, 57 insertions(+), 75 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 63b91942..2810d54a 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -9,8 +9,8 @@ use helix_core::{ object, pos_at_coords, regex::{self, Regex}, register::Register, - search, selection, LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection, - SmallVec, Tendril, Transaction, + search, selection, surround, textobject, LineEnding, Position, Range, Rope, RopeGraphemes, + RopeSlice, Selection, SmallVec, Tendril, Transaction, }; use helix_view::{ @@ -78,7 +78,9 @@ impl<'a> Context<'a> { #[inline] pub fn on_next_key_mode(&mut self, map: HashMap) { + let count = self.count; self.on_next_key(move |cx, event| { + cx.count = count; cx.editor.autoinfo = None; if let Some(func) = map.get(&event) { func(cx); @@ -3337,24 +3339,6 @@ fn jump_backward(cx: &mut Context) { }; } -fn window_mode(cx: &mut Context) { - cx.on_next_key(move |cx, event| { - if let KeyEvent { - code: KeyCode::Char(ch), - .. - } = event - { - match ch { - 'w' => rotate_view(cx), - 'h' => hsplit(cx), - 'v' => vsplit(cx), - 'q' => wclose(cx), - _ => {} - } - } - }) -} - fn rotate_view(cx: &mut Context) { cx.editor.focus_next() } @@ -3443,62 +3427,12 @@ fn view_mode(cx: &mut Context) { }) } -fn left_bracket_mode(cx: &mut Context) { - cx.on_next_key(move |cx, event| { - if let KeyEvent { - code: KeyCode::Char(ch), - .. - } = event - { - match ch { - 'd' => goto_prev_diag(cx), - 'D' => goto_first_diag(cx), - _ => (), - } - } - }) -} - -fn right_bracket_mode(cx: &mut Context) { - cx.on_next_key(move |cx, event| { - if let KeyEvent { - code: KeyCode::Char(ch), - .. - } = event - { - match ch { - 'd' => goto_next_diag(cx), - 'D' => goto_last_diag(cx), - _ => (), - } - } - }) +fn select_textobject_around(cx: &mut Context) { + select_textobject(cx, textobject::TextObject::Around); } -use helix_core::surround; -use helix_core::textobject; - -fn match_mode(cx: &mut Context) { - let count = cx.count; - cx.on_next_key(move |cx, event| { - if let KeyEvent { - code: KeyCode::Char(ch), - .. - } = event - { - // FIXME: count gets reset because of cx.on_next_key() - cx.count = count; - match ch { - 'm' => match_brackets(cx), - 's' => surround_add(cx), - 'r' => surround_replace(cx), - 'd' => surround_delete(cx), - 'a' => select_textobject(cx, textobject::TextObject::Around), - 'i' => select_textobject(cx, textobject::TextObject::Inside), - _ => (), - } - } - }) +fn select_textobject_inner(cx: &mut Context) { + select_textobject(cx, textobject::TextObject::Inside); } fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) { @@ -3719,7 +3653,7 @@ mode_info! { } mode_info! { - /// goto mode + /// goto /// /// When specified with a count, it will go to that line without entering the mode. goto_mode, GOTO_MODE, goto_prehook, @@ -3750,3 +3684,51 @@ mode_info! { /// last accessed file "a" => goto_last_accessed_file, } + +mode_info! { + /// window + window_mode, WINDOW_MODE, + /// rotate + "w" => rotate_view, + /// horizontal split + "h" => hsplit, + /// vertical split + "v" => vsplit, + /// close + "q" => wclose, +} + +mode_info! { + /// match + match_mode, MATCH_MODE, + /// matching character + "m" => match_brackets, + /// surround add + "s" => surround_add, + /// surround replace + "r" => surround_replace, + /// surround delete + "d" => surround_delete, + /// around object + "a" => select_textobject_around, + /// inside object + "i" => select_textobject_inner, +} + +mode_info! { + /// select to previous + left_bracket_mode, LEFT_BRACKET_MODE, + /// previous diagnostic + "d" => goto_prev_diag, + /// diagnostic (first) + "D" => goto_first_diag, +} + +mode_info! { + /// select to next + right_bracket_mode, RIGHT_BRACKET_MODE, + /// diagnostic + "d" => goto_next_diag, + /// diagnostic (last) + "D" => goto_last_diag, +} From c7aa7bf4baf8e0ac4eca3ebdca065ab2a7bea95c Mon Sep 17 00:00:00 2001 From: Kirawi <67773714+kirawi@users.noreply.github.com> Date: Wed, 7 Jul 2021 20:51:46 -0400 Subject: [PATCH 08/31] VSCode Dark+ Theme (#414) * wip * Add VSCode Dark+ Theme wip wip wip wip wip wip properly detect constants add bool wip * suggestion * add variant for c/c++ * fix hexcode error * removed regex highlight * fixed constant higlighting * wip * add space * add suggestions * update theme * update book * suggestions * fix c/c++ enum * update book --- book/src/themes.md | 1 + runtime/queries/c/highlights.scm | 2 + runtime/queries/rust/highlights.scm | 3 +- runtime/themes/dark_plus.toml | 79 +++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 runtime/themes/dark_plus.toml diff --git a/book/src/themes.md b/book/src/themes.md index e5c461fd..4d80f99a 100644 --- a/book/src/themes.md +++ b/book/src/themes.md @@ -61,6 +61,7 @@ Possible keys: | `variable.parameter` | | | `type` | | | `type.builtin` | | +| `type.enum.variant` | Enum variants | | `constructor` | | | `function` | | | `function.macro` | | diff --git a/runtime/queries/c/highlights.scm b/runtime/queries/c/highlights.scm index 36fe47d9..258e07e7 100644 --- a/runtime/queries/c/highlights.scm +++ b/runtime/queries/c/highlights.scm @@ -54,6 +54,8 @@ "." @punctuation.delimiter ";" @punctuation.delimiter +(enumerator) @type.enum.variant + (string_literal) @string (system_lib_string) @string diff --git a/runtime/queries/rust/highlights.scm b/runtime/queries/rust/highlights.scm index b72216f6..bb15074b 100644 --- a/runtime/queries/rust/highlights.scm +++ b/runtime/queries/rust/highlights.scm @@ -3,9 +3,10 @@ ; Assume all-caps names are constants ((identifier) @constant - (#match? @constant "^[A-Z][A-Z\\d_]+$'")) + (#match? @constant "^[A-Z][A-Z\\d_]+$")) ; Assume other uppercase names are enum constructors +(enum_variant) @type.enum.variant ((identifier) @constructor (#match? @constructor "^[A-Z]")) diff --git a/runtime/themes/dark_plus.toml b/runtime/themes/dark_plus.toml new file mode 100644 index 00000000..101c6d9e --- /dev/null +++ b/runtime/themes/dark_plus.toml @@ -0,0 +1,79 @@ +# Author: Shafkath Shuhan + +"namespace" = { fg = "type" } +"module" = { fg = "type" } +"type" = { fg = "type" } +"type.builtin" = { fg = "type" } + +"keyword" = { fg = "keyword" } +"keyword.directive" = { fg = "keyword" } +"function.macro" = { fg = "keyword" } +"variable.builtin" = { fg = "keyword" } +"label" = { fg = "keyword" } +"constant.builtin" = { fg = "keyword" } + +"punctuation" = { fg = "text" } +"punctuation.delimiter" = { fg = "text" } + +"keyword.control" = { fg = "special" } +"special" = { fg = "text" } +"operator" = { fg = "text" } + +"variable" = { fg = "variable" } +"variable.parameter" = { fg = "variable" } +"property" = { fg = "variable" } + +"attribute" = { fg = "fn_declaration" } +"function" = { fg = "fn_declaration" } +"function.builtin" = { fg = "fn_declaration" } + +"comment" = { fg = "#6A9955" } + +"constant" = { fg = "constant" } +"type.enum.variant" = { fg = "constant" } +"constructor" = { fg = "constant" } + +"string" = { fg = "#ce9178" } +"number" = { fg = "#b5cea8" } +"escape" = { fg = "#d7ba7d" } + +"ui.background" = { fg = "#d4d4d4", bg = "#1e1e1e" } + +"ui.help" = { bg = "widget" } +"ui.popup" = { bg = "widget" } +"ui.window" = { bg = "widget" } +"ui.menu.selected" = { bg = "widget" } + +"ui.cursor" = { fg = "cursor", modifiers = ["reversed"] } +"ui.cursor.primary" = { fg = "cursor", modifiers = ["reversed"] } +"ui.cursor.match" = { fg = "cursor", modifiers = ['underlined'] } + +"ui.selection" = { bg = "#3a3d41" } +"ui.selection.primary" = { bg = "#add6ff26" } + +"ui.linenr" = { fg = "#858585" } +"ui.linenr.selected" = { fg = "#c6c6c6" } + +"ui.statusline" = { fg = "#ffffff", bg = "#007acc" } + +"ui.text" = { fg = "text", bg = "background" } +"ui.text.focus" = { fg = "#ffffff" } + +"warning" = { fg = "#cca700" } +"error" = { fg = "#f48771" } +"info" = { fg = "#75beff" } +"hint" = { fg = "#eeeeeeb3" } + +[palette] +type = "#4EC9B0" +keyword = "#569CD6" +regex = "#CE9178" +special = "#C586C0" +variable = "#9CDCFE" +fn_declaration = "#DCDCAA" +constant = "#4FC1FF" + +background = "#1e1e1e" +text = "#d4d4d4" +cursor = "#a6a6a6" +widget = "#252526" From 084a8a952292521f1f7b7f4144e6823c29b85507 Mon Sep 17 00:00:00 2001 From: Kirawi <67773714+kirawi@users.noreply.github.com> Date: Thu, 8 Jul 2021 12:11:20 -0400 Subject: [PATCH 09/31] Rewritten Rust `highlights.scm` (#425) * rewrote Rust highlights.scm * wip * wip * wip * wip * fixed type highlighting * wip * rewrite again * moved operators * missing newline * missing newline * update book * fix constructor highlighting * fix constructor highlighting * fix const highlighting * better constructor highlighting * remove dup, bug was my locals.scm file * fixed docs * merge * fixed for highlighting * add yield * remove yield * added yield back * fixed yield highlighting * unecessary --- book/src/themes.md | 1 + helix-core/src/syntax.rs | 4 +- runtime/queries/rust/highlights.scm | 484 +++++++++++++++++----------- 3 files changed, 306 insertions(+), 183 deletions(-) diff --git a/book/src/themes.md b/book/src/themes.md index 4d80f99a..f17195aa 100644 --- a/book/src/themes.md +++ b/book/src/themes.md @@ -51,6 +51,7 @@ Possible keys: | `attribute` | | | `keyword` | | | `keyword.directive` | Preprocessor directives (\#if in C) | +| `keyword.control` | Control flow | | `namespace` | | | `punctuation` | | | `punctuation.delimiter` | | diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index 5b45a88f..833ccfb9 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -161,7 +161,7 @@ impl LanguageConfiguration { let injections_query = read_query(&language, "injections.scm"); - let locals_query = ""; + let locals_query = read_query(&language, "locals.scm"); if highlights_query.is_empty() { None @@ -171,7 +171,7 @@ impl LanguageConfiguration { language, &highlights_query, &injections_query, - locals_query, + &locals_query, ) .unwrap(); // TODO: no unwrap config.configure(scopes); diff --git a/runtime/queries/rust/highlights.scm b/runtime/queries/rust/highlights.scm index bb15074b..5e469e67 100644 --- a/runtime/queries/rust/highlights.scm +++ b/runtime/queries/rust/highlights.scm @@ -1,214 +1,336 @@ -; Identifier conventions +; ------- +; Tree-Sitter doesn't allow overrides in regards to captures, +; though it is possible to affect the child node of a captured +; node. Thus, the approach here is to flip the order so that +; overrides are unnecessary. +; ------- -; Assume all-caps names are constants -((identifier) @constant - (#match? @constant "^[A-Z][A-Z\\d_]+$")) -; Assume other uppercase names are enum constructors -(enum_variant) @type.enum.variant -((identifier) @constructor - (#match? @constructor "^[A-Z]")) +; ------- +; Types +; ------- -; Assume that uppercase names in paths are types -(mod_item - name: (identifier) @namespace) -(scoped_identifier - path: (identifier) @namespace) -(scoped_identifier - (scoped_identifier - name: (identifier) @namespace)) -(scoped_type_identifier - path: (identifier) @namespace) -(scoped_type_identifier - (scoped_identifier - name: (identifier) @namespace)) +; --- +; Primitives +; --- -((scoped_identifier - path: (identifier) @type) - (#match? @type "^[A-Z]")) -((scoped_identifier - path: (scoped_identifier - name: (identifier) @type)) - (#match? @type "^[A-Z]")) +(escape_sequence) @escape +(primitive_type) @type.builtin +(boolean_literal) @constant.builtin +[ + (integer_literal) + (float_literal) +] @number +[ + (char_literal) + (string_literal) + (raw_string_literal) +] @string +[ + (line_comment) + (block_comment) +] @comment -; Namespaces +; --- +; Extraneous +; --- -(crate) @namespace -(extern_crate_declaration - (crate) - name: (identifier) @namespace) -(scoped_use_list - path: (identifier) @namespace) -(scoped_use_list - path: (scoped_identifier - (identifier) @namespace)) -(use_list (scoped_identifier (identifier) @namespace . (_))) +(self) @variable.builtin +(enum_variant (identifier) @type.enum.variant) -; Function calls +(field_initializer + (field_identifier) @property) +(shorthand_field_initializer) @variable +(shorthand_field_identifier) @variable -(call_expression - function: (identifier) @function) -(call_expression - function: (field_expression - field: (field_identifier) @function.method)) -(call_expression - function: (scoped_identifier - "::" - name: (identifier) @function)) +(lifetime + "'" @label + (identifier) @label) +(loop_label + (identifier) @type) -(generic_function - function: (identifier) @function) -(generic_function - function: (scoped_identifier - name: (identifier) @function)) -(generic_function - function: (field_expression - field: (field_identifier) @function.method)) +; --- +; Punctuation +; --- -(macro_invocation - macro: (identifier) @function.macro - "!" @function.macro) -(macro_invocation - macro: (scoped_identifier - (identifier) @function.macro .)) +[ + "::" + "." + ";" +] @punctuation.delimiter -; (metavariable) @variable -(metavariable) @function.macro +[ + "(" + ")" + "[" + "]" +] @punctuation.bracket +(type_arguments + [ + "<" + ">" + ] @punctuation.bracket) +(type_parameters + [ + "<" + ">" + ] @punctuation.bracket) -"$" @function.macro +; --- +; Parameters +; --- -; Function definitions +(parameter + pattern: (identifier) @variable.parameter) +(closure_parameters + (identifier) @variable.parameter) -(function_item (identifier) @function) -(function_signature_item (identifier) @function) -; Other identifiers -(type_identifier) @type -(primitive_type) @type.builtin -(field_identifier) @property +; ------- +; Keywords +; ------- -(line_comment) @comment -(block_comment) @comment +(for_expression + "for" @keyword.control) +((identifier) @keyword.control + (#match? @keyword.control "^yield$")) +[ + "while" + "loop" + "in" + "break" + "continue" -"(" @punctuation.bracket -")" @punctuation.bracket -"[" @punctuation.bracket -"]" @punctuation.bracket + "match" + "if" + "else" + "return" + + "await" +] @keyword.control + +[ + (crate) + (super) + "as" + "use" + "pub" + "mod" + "extern" + + "fn" + "struct" + "enum" + "impl" + "where" + "trait" + "for" + + "type" + "union" + "unsafe" + "default" + "macro_rules!" + + "let" + "ref" + "move" + + "dyn" + "static" + "const" + "async" +] @keyword -(type_arguments - "<" @punctuation.bracket - ">" @punctuation.bracket) -(type_parameters - "<" @punctuation.bracket - ">" @punctuation.bracket) - -"::" @punctuation.delimiter -"." @punctuation.delimiter -";" @punctuation.delimiter - -(parameter (identifier) @variable.parameter) -(closure_parameters (_) @variable.parameter) - -(lifetime (identifier) @label) - -"async" @keyword -"break" @keyword -"const" @keyword -"continue" @keyword -(crate) @keyword -"default" @keyword -"dyn" @keyword -"else" @keyword -"enum" @keyword -"extern" @keyword -"fn" @keyword -"for" @keyword -"if" @keyword -"impl" @keyword -"in" @keyword -"let" @keyword -"let" @keyword -"loop" @keyword -"macro_rules!" @keyword -"match" @keyword -"mod" @keyword -"move" @keyword -"pub" @keyword -"ref" @keyword -"return" @keyword -"static" @keyword -"struct" @keyword -"trait" @keyword -"type" @keyword -"union" @keyword -"unsafe" @keyword -"use" @keyword -"where" @keyword -"while" @keyword (mutable_specifier) @keyword.mut -(use_list (self) @keyword) -(scoped_use_list (self) @keyword) -(scoped_identifier (self) @keyword) -(super) @keyword -"as" @keyword -(self) @variable.builtin -[ -(char_literal) -(string_literal) -(raw_string_literal) -] @string -(boolean_literal) @constant.builtin -(integer_literal) @number -(float_literal) @number +; ------- +; Guess Other Types +; ------- -(escape_sequence) @escape +((identifier) @constant + (#match? @constant "^[A-Z][A-Z\\d_]+$")) + +; --- +; PascalCase identifiers in call_expressions (e.g. `Ok()`) +; are assumed to be enum constructors. +; --- + +(call_expression + function: [ + ((identifier) @type.variant + (#match? @type.variant "^[A-Z]")) + (scoped_identifier + name: ((identifier) @type.variant + (#match? @type.variant "^[A-Z]"))) + ]) + +; --- +; Assume that types in match arms are enums and not +; tuple structs. Same for `if let` expressions. +; --- + +(match_pattern + (scoped_identifier + name: (identifier) @constructor)) +(tuple_struct_pattern + type: [ + ((identifier) @constructor) + (scoped_identifier + name: (identifier) @constructor) + ]) +(struct_pattern + type: [ + ((type_identifier) @constructor) + (scoped_type_identifier + name: (type_identifier) @constructor) + ]) +; --- +; Other PascalCase identifiers are assumed to be structs. +; --- + +((identifier) @type + (#match? @type "^[A-Z]")) + + + +; ------- +; Functions +; ------- + +(call_expression + function: [ + ((identifier) @function) + (scoped_identifier + name: (identifier) @function) + (field_expression + field: (field_identifier) @function) + ]) +(generic_function + function: [ + ((identifier) @function) + (scoped_identifier + name: (identifier) @function) + (field_expression + field: (field_identifier) @function.method) + ]) + +(function_item + name: (identifier) @function) + +; --- +; Macros +; --- + +(meta_item + (identifier) @attribute) (attribute_item) @attribute (inner_attribute_item) @attribute +(macro_definition + name: (identifier) @function.macro) +(macro_invocation + macro: [ + ((identifier) @function.macro) + (scoped_identifier + name: (identifier) @function.macro) + ] + "!" @function.macro) + +(metavariable) @variable.parameter +(fragment_specifier) @variable.parameter + + + +; ------- +; Operators +; ------- + [ -"*" -"'" -"->" -"=>" -"<=" -"=" -"==" -"!" -"!=" -"%" -"%=" -"&" -"&=" -"&&" -"|" -"|=" -"||" -"^" -"^=" -"*" -"*=" -"-" -"-=" -"+" -"+=" -"/" -"/=" -">" -"<" -">=" -">>" -"<<" -">>=" -"@" -".." -"..=" -"'" + "*" + "'" + "->" + "=>" + "<=" + "=" + "==" + "!" + "!=" + "%" + "%=" + "&" + "&=" + "&&" + "|" + "|=" + "||" + "^" + "^=" + "*" + "*=" + "-" + "-=" + "+" + "+=" + "/" + "/=" + ">" + "<" + ">=" + ">>" + "<<" + ">>=" + "@" + ".." + "..=" + "'" ] @operator + + +; ------- +; Paths +; ------- + +(use_declaration + argument: (identifier) @namespace) +(use_wildcard + (identifier) @namespace) +(extern_crate_declaration + name: (identifier) @namespace) +(mod_item + name: (identifier) @namespace) +(scoped_use_list + path: (identifier)? @namespace) +(use_list + (identifier) @namespace) +(use_as_clause + path: (identifier)? @namespace + alias: (identifier) @namespace) + +; --- +; Remaining Paths +; --- + +(scoped_identifier + path: (identifier)? @namespace + name: (identifier) @namespace) +(scoped_type_identifier + path: (identifier) @namespace) + + + +; ------- +; Remaining Identifiers +; ------- + "?" @special + +(type_identifier) @type +(identifier) @variable +(field_identifier) @variable From bb121a3e4b97efef1380414f33361404900d6f72 Mon Sep 17 00:00:00 2001 From: Kirawi <67773714+kirawi@users.noreply.github.com> Date: Sat, 10 Jul 2021 21:40:18 -0400 Subject: [PATCH 10/31] Injection Query Support (#430) * wip * wip * fixed unsafe * fix clippy * move out reference variable * fmt * remove arc * change safety comment --- helix-term/src/ui/editor.rs | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index ef13004c..9a2fbf57 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -64,6 +64,7 @@ impl EditorView { surface: &mut Surface, theme: &Theme, is_focused: bool, + loader: &syntax::Loader, ) { let area = Rect::new( view.area.x + OFFSET, @@ -72,7 +73,7 @@ impl EditorView { view.area.height.saturating_sub(1), ); // - 1 for statusline - self.render_buffer(doc, view, area, surface, theme, is_focused); + self.render_buffer(doc, view, area, surface, theme, is_focused, loader); // if we're not at the edge of the screen, draw a right border if viewport.right() != view.area.right() { @@ -98,6 +99,7 @@ impl EditorView { self.render_statusline(doc, view, area, surface, theme, is_focused); } + #[allow(clippy::too_many_arguments)] pub fn render_buffer( &self, doc: &Document, @@ -106,6 +108,7 @@ impl EditorView { surface: &mut Surface, theme: &Theme, is_focused: bool, + loader: &syntax::Loader, ) { let text = doc.text().slice(..); @@ -122,8 +125,26 @@ impl EditorView { // TODO: range doesn't actually restrict source, just highlight range let highlights: Vec<_> = match doc.syntax() { Some(syntax) => { + let scopes = theme.scopes(); syntax - .highlight_iter(text.slice(..), Some(range), None, |_| None) + .highlight_iter(text.slice(..), Some(range), None, |language| { + loader + .language_config_for_scope(&format!("source.{}", language)) + .and_then(|language_config| { + let config = language_config.highlight_config(scopes)?; + let config_ref = config.as_ref(); + // SAFETY: the referenced `HighlightConfiguration` behind + // the `Arc` is guaranteed to remain valid throughout the + // duration of the highlight. + let config_ref = unsafe { + std::mem::transmute::< + _, + &'static syntax::HighlightConfiguration, + >(config_ref) + }; + Some(config_ref) + }) + }) .collect() // TODO: we collect here to avoid holding the lock, fix later } None => vec![Ok(HighlightEvent::Source { @@ -719,7 +740,16 @@ impl Component for EditorView { for (view, is_focused) in cx.editor.tree.views() { let doc = cx.editor.document(view.doc).unwrap(); - self.render_view(doc, view, area, surface, &cx.editor.theme, is_focused); + let loader = &cx.editor.syn_loader; + self.render_view( + doc, + view, + area, + surface, + &cx.editor.theme, + is_focused, + loader, + ); } if let Some(info) = std::mem::take(&mut cx.editor.autoinfo) { From 3e4cd8f8e6456fdea4f0e82908d395eb516e1be6 Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Thu, 8 Jul 2021 09:58:11 +0800 Subject: [PATCH 11/31] Add infobox for view --- helix-term/src/commands.rs | 89 +++++++++++++++++++++----------------- 1 file changed, 49 insertions(+), 40 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 2810d54a..e35b36f9 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -40,6 +40,7 @@ use crate::{ use crate::job::{self, Job, Jobs}; use futures_util::{FutureExt, TryFutureExt}; use std::collections::HashMap; +use std::num::NonZeroUsize; use std::{fmt, future::Future}; use std::{ @@ -52,7 +53,7 @@ use serde::de::{self, Deserialize, Deserializer}; pub struct Context<'a> { pub selected_register: helix_view::RegisterSelection, - pub count: Option, + pub count: Option, pub editor: &'a mut Editor, pub callback: Option, @@ -3384,47 +3385,38 @@ fn select_register(cx: &mut Context) { }) } -fn view_mode(cx: &mut Context) { - cx.on_next_key(move |cx, event| { - if let KeyEvent { - code: KeyCode::Char(ch), - .. - } = event - { - // if lock, call cx again - // TODO: temporarily show VIE in the mode list - match ch { - // center - 'z' | 'c' - // top - | 't' - // bottom - | 'b' => { - let (view, doc) = current!(cx.editor); +fn align_view_top(cx: &mut Context) { + let (view, doc) = current!(cx.editor); + align_view(doc, view, Align::Top); +} - align_view(doc, view, match ch { - 'z' | 'c' => Align::Center, - 't' => Align::Top, - 'b' => Align::Bottom, - _ => unreachable!() - }); - } - 'm' => { - let (view, doc) = current!(cx.editor); - let pos = doc.selection(view.id).cursor(); - let pos = coords_at_pos(doc.text().slice(..), pos); +fn align_view_center(cx: &mut Context) { + let (view, doc) = current!(cx.editor); + align_view(doc, view, Align::Center); +} - const OFFSET: usize = 7; // gutters - view.first_col = pos.col.saturating_sub(((view.area.width as usize).saturating_sub(OFFSET)) / 2); - }, - 'h' => (), - 'j' => scroll(cx, 1, Direction::Forward), - 'k' => scroll(cx, 1, Direction::Backward), - 'l' => (), - _ => (), - } - } - }) +fn align_view_bottom(cx: &mut Context) { + let (view, doc) = current!(cx.editor); + align_view(doc, view, Align::Bottom); +} + +fn align_view_middle(cx: &mut Context) { + let (view, doc) = current!(cx.editor); + let pos = doc.selection(view.id).cursor(); + let pos = coords_at_pos(doc.text().slice(..), pos); + + const OFFSET: usize = 7; // gutters + view.first_col = pos + .col + .saturating_sub(((view.area.width as usize).saturating_sub(OFFSET)) / 2); +} + +fn scroll_up(cx: &mut Context) { + scroll(cx, cx.count(), Direction::Backward); +} + +fn scroll_down(cx: &mut Context) { + scroll(cx, cx.count(), Direction::Forward); } fn select_textobject_around(cx: &mut Context) { @@ -3732,3 +3724,20 @@ mode_info! { /// diagnostic (last) "D" => goto_last_diag, } + +mode_info! { + /// view + view_mode, VIEW_MODE, + /// align view top + "t" => align_view_top, + /// align view center + "z" | "c" => align_view_center, + /// align view bottom + "b" => align_view_bottom, + /// align view middle + "m" => align_view_middle, + /// scroll up + "k" => scroll_up, + /// scroll down + "j" => scroll_down, +} From 9c02a1b070b90668c97968b848421ad2de9d459b Mon Sep 17 00:00:00 2001 From: Lionel Flandrin Date: Sat, 26 Jun 2021 18:50:44 +0100 Subject: [PATCH 12/31] Make command implementation return a Result<()> The error message is displayed with cx.editor.set_error. --- helix-term/src/commands.rs | 415 +++++++++++++++++++++++-------------- helix-view/src/editor.rs | 5 +- 2 files changed, 263 insertions(+), 157 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index e35b36f9..8a9ffb91 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -23,7 +23,7 @@ use helix_view::{ Document, DocumentId, Editor, ViewId, }; -use anyhow::anyhow; +use anyhow::{anyhow, bail}; use helix_lsp::{ lsp, util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range}, @@ -1161,34 +1161,52 @@ mod cmd { pub alias: Option<&'static str>, pub doc: &'static str, // params, flags, helper, completer - pub fun: fn(&mut compositor::Context, &[&str], PromptEvent), + pub fun: fn(&mut compositor::Context, &[&str], PromptEvent) -> anyhow::Result<()>, pub completer: Option, } - fn quit(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) { + fn quit( + cx: &mut compositor::Context, + _args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { // last view and we have unsaved changes - if cx.editor.tree.views().count() == 1 && buffers_remaining_impl(cx.editor) { - return; + if cx.editor.tree.views().count() == 1 { + buffers_remaining_impl(cx.editor)? } + cx.editor .close(view!(cx.editor).id, /* close_buffer */ false); + + Ok(()) } - fn force_quit(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) { + fn force_quit( + cx: &mut compositor::Context, + _args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { cx.editor .close(view!(cx.editor).id, /* close_buffer */ false); + + Ok(()) } - fn open(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) { + fn open( + cx: &mut compositor::Context, + args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { match args.get(0) { Some(path) => { - // TODO: handle error - let _ = cx.editor.open(path.into(), Action::Replace); + let _ = cx.editor.open(path.into(), Action::Replace)?; + + Ok(()) } None => { - cx.editor.set_error("wrong argument count".to_string()); + bail!("wrong argument count"); } - }; + } } fn write_impl>( @@ -1200,11 +1218,11 @@ mod cmd { if let Some(path) = path { if let Err(err) = doc.set_path(path.as_ref()) { - return Err(anyhow!("invalid filepath: {}", err)); + bail!("invalid filepath: {}", err); }; } if doc.path().is_none() { - return Err(anyhow!("cannot write a buffer without a filename")); + bail!("cannot write a buffer without a filename"); } let fmt = doc.auto_format().map(|fmt| { let shared = fmt.shared(); @@ -1220,21 +1238,33 @@ mod cmd { Ok(tokio::spawn(doc.format_and_save(fmt))) } - fn write(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) { - match write_impl(cx, args.first()) { - Err(e) => cx.editor.set_error(e.to_string()), - Ok(handle) => { - cx.jobs - .add(Job::new(handle.unwrap_or_else(|e| Err(e.into()))).wait_before_exiting()); - } - }; + fn write( + cx: &mut compositor::Context, + args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + let handle = write_impl(cx, args.first())?; + cx.jobs + .add(Job::new(handle.unwrap_or_else(|e| Err(e.into()))).wait_before_exiting()); + + Ok(()) } - fn new_file(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) { + fn new_file( + cx: &mut compositor::Context, + _args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { cx.editor.new_file(Action::Replace); + + Ok(()) } - fn format(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) { + fn format( + cx: &mut compositor::Context, + _args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { let (_, doc) = current!(cx.editor); if let Some(format) = doc.format() { @@ -1242,9 +1272,14 @@ mod cmd { make_format_callback(doc.id(), doc.version(), Modified::LeaveModified, format); cx.jobs.callback(callback); } - } - fn set_indent_style(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) { + Ok(()) + } + fn set_indent_style( + cx: &mut compositor::Context, + args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { use IndentStyle::*; // If no argument, report current indent style. @@ -1256,7 +1291,7 @@ mod cmd { Spaces(n) if (2..=8).contains(&n) => format!("{} spaces", n), _ => "error".into(), // Shouldn't happen. }); - return; + return Ok(()); } // Attempt to parse argument as an indent style. @@ -1274,15 +1309,19 @@ mod cmd { if let Some(s) = style { let doc = doc_mut!(cx.editor); doc.indent_style = s; + + Ok(()) } else { - // Invalid argument. - cx.editor - .set_error(format!("invalid indent style '{}'", args[0],)); + bail!("invalid indent style '{}'", args[0]); } } /// Sets or reports the current document's line ending setting. - fn set_line_ending(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) { + fn set_line_ending( + cx: &mut compositor::Context, + args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { use LineEnding::*; // If no argument, report current line ending setting. @@ -1298,7 +1337,8 @@ mod cmd { // These should never be a document's default line ending. VT | LS | PS => "error".into(), }); - return; + + return Ok(()); } // Attempt to parse argument as a line ending. @@ -1314,70 +1354,66 @@ mod cmd { if let Some(le) = line_ending { doc_mut!(cx.editor).line_ending = le; + Ok(()) } else { - // Invalid argument. - cx.editor - .set_error(format!("invalid line ending '{}'", args[0],)); + bail!("invalid line ending '{}'", args[0]); } } - fn earlier(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) { - let uk = match args.join(" ").parse::() { - Ok(uk) => uk, - Err(msg) => { - cx.editor.set_error(msg); - return; - } - }; + fn earlier( + cx: &mut compositor::Context, + args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + let uk = args + .join(" ") + .parse::() + .map_err(|s| anyhow!(s))?; + let (view, doc) = current!(cx.editor); - doc.earlier(view.id, uk) + doc.earlier(view.id, uk); + + Ok(()) } - fn later(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) { - let uk = match args.join(" ").parse::() { - Ok(uk) => uk, - Err(msg) => { - cx.editor.set_error(msg); - return; - } - }; + fn later( + cx: &mut compositor::Context, + args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + let uk = args + .join(" ") + .parse::() + .map_err(|s| anyhow!(s))?; let (view, doc) = current!(cx.editor); - doc.later(view.id, uk) + doc.later(view.id, uk); + + Ok(()) } - fn write_quit(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) { - match write_impl(cx, args.first()) { - Ok(handle) => { - if let Err(e) = helix_lsp::block_on(handle) { - cx.editor.set_error(e.to_string()); - } else { - quit(cx, &[], event); - } - } - Err(e) => { - cx.editor.set_error(e.to_string()); - } - } + fn write_quit( + cx: &mut compositor::Context, + args: &[&str], + event: PromptEvent, + ) -> anyhow::Result<()> { + let handle = write_impl(cx, args.first())?; + helix_lsp::block_on(handle)?; + quit(cx, &[], event) } - fn force_write_quit(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) { - match write_impl(cx, args.first()) { - Ok(handle) => { - if let Err(e) = helix_lsp::block_on(handle) { - cx.editor.set_error(e.to_string()); - } else { - force_quit(cx, &[], event); - } - } - Err(e) => { - cx.editor.set_error(e.to_string()); - } - } + fn force_write_quit( + cx: &mut compositor::Context, + args: &[&str], + event: PromptEvent, + ) -> anyhow::Result<()> { + let handle = write_impl(cx, args.first())?; + helix_lsp::block_on(handle)?; + force_quit(cx, &[], event) } - /// Returns `true` if there are modified buffers remaining and sets editor error, - /// otherwise returns `false` - fn buffers_remaining_impl(editor: &mut Editor) -> bool { + /// Results an error if there are modified buffers remaining and sets editor error, + /// otherwise returns `Ok(())` + fn buffers_remaining_impl(editor: &mut Editor) -> anyhow::Result<()> { let modified: Vec<_> = editor .documents() .filter(|doc| doc.is_modified()) @@ -1388,16 +1424,13 @@ mod cmd { }) .collect(); if !modified.is_empty() { - let err = format!( + bail!( "{} unsaved buffer(s) remaining: {:?}", modified.len(), modified ); - editor.set_error(err); - true - } else { - false } + Ok(()) } fn write_all_impl( @@ -1406,7 +1439,7 @@ mod cmd { _event: PromptEvent, quit: bool, force: bool, - ) { + ) -> anyhow::Result<()> { let mut errors = String::new(); // save all documents @@ -1419,11 +1452,10 @@ mod cmd { // TODO: handle error. let _ = helix_lsp::block_on(tokio::spawn(doc.save())); } - editor.set_error(errors); if quit { - if !force && buffers_remaining_impl(editor) { - return; + if !force { + buffers_remaining_impl(editor)?; } // close all views @@ -1432,23 +1464,42 @@ mod cmd { editor.close(view_id, false); } } + + bail!(errors) } - fn write_all(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) { + fn write_all( + cx: &mut compositor::Context, + args: &[&str], + event: PromptEvent, + ) -> anyhow::Result<()> { write_all_impl(&mut cx.editor, args, event, false, false) } - fn write_all_quit(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) { + fn write_all_quit( + cx: &mut compositor::Context, + args: &[&str], + event: PromptEvent, + ) -> anyhow::Result<()> { write_all_impl(&mut cx.editor, args, event, true, false) } - fn force_write_all_quit(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) { + fn force_write_all_quit( + cx: &mut compositor::Context, + args: &[&str], + event: PromptEvent, + ) -> anyhow::Result<()> { write_all_impl(&mut cx.editor, args, event, true, true) } - fn quit_all_impl(editor: &mut Editor, _args: &[&str], _event: PromptEvent, force: bool) { - if !force && buffers_remaining_impl(editor) { - return; + fn quit_all_impl( + editor: &mut Editor, + _args: &[&str], + _event: PromptEvent, + force: bool, + ) -> anyhow::Result<()> { + if !force { + buffers_remaining_impl(editor)?; } // close all views @@ -1456,57 +1507,82 @@ mod cmd { for view_id in views { editor.close(view_id, false); } + + Ok(()) } - fn quit_all(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) { + fn quit_all( + cx: &mut compositor::Context, + args: &[&str], + event: PromptEvent, + ) -> anyhow::Result<()> { quit_all_impl(&mut cx.editor, args, event, false) } - fn force_quit_all(cx: &mut compositor::Context, args: &[&str], event: PromptEvent) { + fn force_quit_all( + cx: &mut compositor::Context, + args: &[&str], + event: PromptEvent, + ) -> anyhow::Result<()> { quit_all_impl(&mut cx.editor, args, event, true) } - fn theme(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) { + fn theme( + cx: &mut compositor::Context, + args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { let theme = if let Some(theme) = args.first() { theme } else { - cx.editor.set_error("theme name not provided".into()); - return; + bail!("theme name not provided"); }; - cx.editor.set_theme_from_name(theme); + cx.editor.set_theme_from_name(theme) } fn yank_main_selection_to_clipboard( cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent, - ) { - yank_main_selection_to_clipboard_impl(&mut cx.editor); + ) -> anyhow::Result<()> { + yank_main_selection_to_clipboard_impl(&mut cx.editor) } - fn yank_joined_to_clipboard(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) { + fn yank_joined_to_clipboard( + cx: &mut compositor::Context, + args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { let (_, doc) = current!(cx.editor); let separator = args .first() .copied() .unwrap_or_else(|| doc.line_ending.as_str()); - yank_joined_to_clipboard_impl(&mut cx.editor, separator); + yank_joined_to_clipboard_impl(&mut cx.editor, separator) } - fn paste_clipboard_after(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) { - paste_clipboard_impl(&mut cx.editor, Paste::After); + fn paste_clipboard_after( + cx: &mut compositor::Context, + _args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + paste_clipboard_impl(&mut cx.editor, Paste::After) } - fn paste_clipboard_before(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) { - paste_clipboard_impl(&mut cx.editor, Paste::After); + fn paste_clipboard_before( + cx: &mut compositor::Context, + _args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + paste_clipboard_impl(&mut cx.editor, Paste::After) } fn replace_selections_with_clipboard( cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent, - ) { + ) -> anyhow::Result<()> { let (view, doc) = current!(cx.editor); match cx.editor.clipboard_provider.get_contents() { @@ -1520,71 +1596,89 @@ mod cmd { doc.apply(&transaction, view.id); doc.append_changes_to_history(view.id); + Ok(()) + } + Err(e) => { + log::error!("Couldn't get system clipboard contents: {:?}", e); + bail!("Couldn't get system clipboard contents: {:?}", e); } - Err(e) => log::error!("Couldn't get system clipboard contents: {:?}", e), } } - fn show_clipboard_provider(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) { + fn show_clipboard_provider( + cx: &mut compositor::Context, + _args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { cx.editor .set_status(cx.editor.clipboard_provider.name().into()); + Ok(()) } - fn change_current_directory(cx: &mut compositor::Context, args: &[&str], _event: PromptEvent) { - let dir = match args.first() { - Some(dir) => dir, - None => { - cx.editor.set_error("target directory not provided".into()); - return; - } - }; + fn change_current_directory( + cx: &mut compositor::Context, + args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + let dir = args + .first() + .ok_or_else(|| anyhow!("target directory not provided"))?; if let Err(e) = std::env::set_current_dir(dir) { - cx.editor.set_error(format!( - "Couldn't change the current working directory: {:?}", - e - )); - return; + bail!("Couldn't change the current working directory: {:?}", e); } match std::env::current_dir() { - Ok(cwd) => cx.editor.set_status(format!( - "Current working directory is now {}", - cwd.display() - )), - Err(e) => cx - .editor - .set_error(format!("Couldn't get the new working directory: {}", e)), + Ok(cwd) => { + cx.editor.set_status(format!( + "Current working directory is now {}", + cwd.display() + )); + Ok(()) + } + Err(e) => bail!("Couldn't get the new working directory: {}", e), } } - fn show_current_directory(cx: &mut compositor::Context, _args: &[&str], _event: PromptEvent) { + fn show_current_directory( + cx: &mut compositor::Context, + _args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { match std::env::current_dir() { - Ok(cwd) => cx - .editor - .set_status(format!("Current working directory is {}", cwd.display())), - Err(e) => cx - .editor - .set_error(format!("Couldn't get the current working directory: {}", e)), + Ok(cwd) => { + cx.editor + .set_status(format!("Current working directory is {}", cwd.display())); + Ok(()) + } + Err(e) => bail!("Couldn't get the current working directory: {}", e), } } /// Sets the [`Document`]'s encoding.. - fn set_encoding(cx: &mut compositor::Context, args: &[&str], _: PromptEvent) { + fn set_encoding( + cx: &mut compositor::Context, + args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { let (_, doc) = current!(cx.editor); if let Some(label) = args.first() { doc.set_encoding(label) - .unwrap_or_else(|e| cx.editor.set_error(e.to_string())); } else { let encoding = doc.encoding().name().to_string(); - cx.editor.set_status(encoding) + cx.editor.set_status(encoding); + Ok(()) } } /// Reload the [`Document`] from its source file. - fn reload(cx: &mut compositor::Context, _args: &[&str], _: PromptEvent) { + fn reload( + cx: &mut compositor::Context, + _args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { let (view, doc) = current!(cx.editor); - doc.reload(view.id).unwrap(); + doc.reload(view.id) } pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ @@ -1849,7 +1943,9 @@ fn command_mode(cx: &mut Context) { } if let Some(cmd) = cmd::COMMANDS.get(parts[0]) { - (cmd.fun)(cx, &parts[1..], event); + if let Err(e) = (cmd.fun)(cx, &parts[1..], event) { + cx.editor.set_error(format!("{}", e)); + } } else { cx.editor .set_error(format!("no such command: '{}'", parts[0])); @@ -2758,7 +2854,7 @@ fn yank(cx: &mut Context) { cx.editor.set_status(msg) } -fn yank_joined_to_clipboard_impl(editor: &mut Editor, separator: &str) { +fn yank_joined_to_clipboard_impl(editor: &mut Editor, separator: &str) -> anyhow::Result<()> { let (view, doc) = current!(editor); let values: Vec = doc @@ -2776,17 +2872,20 @@ fn yank_joined_to_clipboard_impl(editor: &mut Editor, separator: &str) { if let Err(e) = editor.clipboard_provider.set_contents(joined) { log::error!("Couldn't set system clipboard content: {:?}", e); + bail!("Couldn't set system clipboard content: {:?}", e); } editor.set_status(msg); + + Ok(()) } fn yank_joined_to_clipboard(cx: &mut Context) { let line_ending = current!(cx.editor).1.line_ending; - yank_joined_to_clipboard_impl(&mut cx.editor, line_ending.as_str()); + let _ = yank_joined_to_clipboard_impl(&mut cx.editor, line_ending.as_str()); } -fn yank_main_selection_to_clipboard_impl(editor: &mut Editor) { +fn yank_main_selection_to_clipboard_impl(editor: &mut Editor) -> anyhow::Result<()> { let (view, doc) = current!(editor); let value = doc @@ -2796,13 +2895,15 @@ fn yank_main_selection_to_clipboard_impl(editor: &mut Editor) { if let Err(e) = editor.clipboard_provider.set_contents(value.into_owned()) { log::error!("Couldn't set system clipboard content: {:?}", e); + bail!("Couldn't set system clipboard content: {:?}", e); } editor.set_status("yanked main selection to system clipboard".to_owned()); + Ok(()) } fn yank_main_selection_to_clipboard(cx: &mut Context) { - yank_main_selection_to_clipboard_impl(&mut cx.editor); + let _ = yank_main_selection_to_clipboard_impl(&mut cx.editor); } #[derive(Copy, Clone)] @@ -2850,7 +2951,7 @@ fn paste_impl( Some(transaction) } -fn paste_clipboard_impl(editor: &mut Editor, action: Paste) { +fn paste_clipboard_impl(editor: &mut Editor, action: Paste) -> anyhow::Result<()> { let (view, doc) = current!(editor); match editor @@ -2861,18 +2962,22 @@ fn paste_clipboard_impl(editor: &mut Editor, action: Paste) { Ok(Some(transaction)) => { doc.apply(&transaction, view.id); doc.append_changes_to_history(view.id); + Ok(()) + } + Ok(None) => Ok(()), + Err(e) => { + log::error!("Couldn't get system clipboard contents: {:?}", e); + bail!("Couldn't get system clipboard contents: {:?}", e); } - Ok(None) => {} - Err(e) => log::error!("Couldn't get system clipboard contents: {:?}", e), } } fn paste_clipboard_after(cx: &mut Context) { - paste_clipboard_impl(&mut cx.editor, Paste::After); + let _ = paste_clipboard_impl(&mut cx.editor, Paste::After); } fn paste_clipboard_before(cx: &mut Context) { - paste_clipboard_impl(&mut cx.editor, Paste::Before); + let _ = paste_clipboard_impl(&mut cx.editor, Paste::Before); } fn replace_with_yanked(cx: &mut Context) { diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 4f01cce4..c80535ed 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -97,16 +97,17 @@ impl Editor { self._refresh(); } - pub fn set_theme_from_name(&mut self, theme: &str) { + pub fn set_theme_from_name(&mut self, theme: &str) -> anyhow::Result<()> { let theme = match self.theme_loader.load(theme.as_ref()) { Ok(theme) => theme, Err(e) => { log::warn!("failed setting theme `{}` - {}", theme, e); - return; + anyhow::bail!("failed setting theme `{}` - {}", theme, e); } }; self.set_theme(theme); + Ok(()) } fn _refresh(&mut self) { From d530d6e39d58f5759d2db0a9bda1ea5e21154f83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Sun, 11 Jul 2021 16:35:57 +0900 Subject: [PATCH 13/31] Further simplify error handling in :commands --- helix-term/src/commands.rs | 108 +++++++++++++------------------------ helix-view/src/editor.rs | 13 ++--- 2 files changed, 41 insertions(+), 80 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 8a9ffb91..8bbdfdfa 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -23,7 +23,7 @@ use helix_view::{ Document, DocumentId, Editor, ViewId, }; -use anyhow::{anyhow, bail}; +use anyhow::{anyhow, bail, Context as _}; use helix_lsp::{ lsp, util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range}, @@ -1197,16 +1197,9 @@ mod cmd { args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()> { - match args.get(0) { - Some(path) => { - let _ = cx.editor.open(path.into(), Action::Replace)?; - - Ok(()) - } - None => { - bail!("wrong argument count"); - } - } + let path = args.get(0).context("wrong argument count")?; + let _ = cx.editor.open(path.into(), Action::Replace)?; + Ok(()) } fn write_impl>( @@ -1217,9 +1210,7 @@ mod cmd { let (_, doc) = current!(cx.editor); if let Some(path) = path { - if let Err(err) = doc.set_path(path.as_ref()) { - bail!("invalid filepath: {}", err); - }; + doc.set_path(path.as_ref()).context("invalid filepath")?; } if doc.path().is_none() { bail!("cannot write a buffer without a filename"); @@ -1306,14 +1297,11 @@ mod cmd { _ => None, }; - if let Some(s) = style { - let doc = doc_mut!(cx.editor); - doc.indent_style = s; + let style = style.context("invalid indent style")?; + let doc = doc_mut!(cx.editor); + doc.indent_style = style; - Ok(()) - } else { - bail!("invalid indent style '{}'", args[0]); - } + Ok(()) } /// Sets or reports the current document's line ending setting. @@ -1352,12 +1340,9 @@ mod cmd { _ => None, }; - if let Some(le) = line_ending { - doc_mut!(cx.editor).line_ending = le; - Ok(()) - } else { - bail!("invalid line ending '{}'", args[0]); - } + let line_ending = line_ending.context("invalid line ending")?; + doc_mut!(cx.editor).line_ending = line_ending; + Ok(()) } fn earlier( @@ -1397,7 +1382,7 @@ mod cmd { event: PromptEvent, ) -> anyhow::Result<()> { let handle = write_impl(cx, args.first())?; - helix_lsp::block_on(handle)?; + let _ = helix_lsp::block_on(handle)?; quit(cx, &[], event) } @@ -1407,7 +1392,7 @@ mod cmd { event: PromptEvent, ) -> anyhow::Result<()> { let handle = write_impl(cx, args.first())?; - helix_lsp::block_on(handle)?; + let _ = helix_lsp::block_on(handle)?; force_quit(cx, &[], event) } @@ -1532,12 +1517,7 @@ mod cmd { args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()> { - let theme = if let Some(theme) = args.first() { - theme - } else { - bail!("theme name not provided"); - }; - + let theme = args.first().context("theme not provided")?; cx.editor.set_theme_from_name(theme) } @@ -1598,10 +1578,7 @@ mod cmd { doc.append_changes_to_history(view.id); Ok(()) } - Err(e) => { - log::error!("Couldn't get system clipboard contents: {:?}", e); - bail!("Couldn't get system clipboard contents: {:?}", e); - } + Err(e) => Err(e.context("Couldn't get system clipboard contents")), } } @@ -1620,24 +1597,18 @@ mod cmd { args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()> { - let dir = args - .first() - .ok_or_else(|| anyhow!("target directory not provided"))?; + let dir = args.first().context("target directory not provided")?; if let Err(e) = std::env::set_current_dir(dir) { bail!("Couldn't change the current working directory: {:?}", e); } - match std::env::current_dir() { - Ok(cwd) => { - cx.editor.set_status(format!( - "Current working directory is now {}", - cwd.display() - )); - Ok(()) - } - Err(e) => bail!("Couldn't get the new working directory: {}", e), - } + let cwd = std::env::current_dir().context("Couldn't get the new working directory")?; + cx.editor.set_status(format!( + "Current working directory is now {}", + cwd.display() + )); + Ok(()) } fn show_current_directory( @@ -1645,14 +1616,10 @@ mod cmd { _args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()> { - match std::env::current_dir() { - Ok(cwd) => { - cx.editor - .set_status(format!("Current working directory is {}", cwd.display())); - Ok(()) - } - Err(e) => bail!("Couldn't get the current working directory: {}", e), - } + let cwd = std::env::current_dir().context("Couldn't get the new working directory")?; + cx.editor + .set_status(format!("Current working directory is {}", cwd.display())); + Ok(()) } /// Sets the [`Document`]'s encoding.. @@ -2870,10 +2837,10 @@ fn yank_joined_to_clipboard_impl(editor: &mut Editor, separator: &str) -> anyhow let joined = values.join(separator); - if let Err(e) = editor.clipboard_provider.set_contents(joined) { - log::error!("Couldn't set system clipboard content: {:?}", e); - bail!("Couldn't set system clipboard content: {:?}", e); - } + editor + .clipboard_provider + .set_contents(joined) + .context("Couldn't set system clipboard content")?; editor.set_status(msg); @@ -2894,7 +2861,6 @@ fn yank_main_selection_to_clipboard_impl(editor: &mut Editor) -> anyhow::Result< .fragment(doc.text().slice(..)); if let Err(e) = editor.clipboard_provider.set_contents(value.into_owned()) { - log::error!("Couldn't set system clipboard content: {:?}", e); bail!("Couldn't set system clipboard content: {:?}", e); } @@ -2965,10 +2931,7 @@ fn paste_clipboard_impl(editor: &mut Editor, action: Paste) -> anyhow::Result<() Ok(()) } Ok(None) => Ok(()), - Err(e) => { - log::error!("Couldn't get system clipboard contents: {:?}", e); - bail!("Couldn't get system clipboard contents: {:?}", e); - } + Err(e) => Err(e.context("Couldn't get system clipboard contents")), } } @@ -3000,7 +2963,7 @@ fn replace_with_yanked(cx: &mut Context) { } } -fn replace_selections_with_clipboard_impl(editor: &mut Editor) { +fn replace_selections_with_clipboard_impl(editor: &mut Editor) -> anyhow::Result<()> { let (view, doc) = current!(editor); match editor.clipboard_provider.get_contents() { @@ -3014,13 +2977,14 @@ fn replace_selections_with_clipboard_impl(editor: &mut Editor) { doc.apply(&transaction, view.id); doc.append_changes_to_history(view.id); + Ok(()) } - Err(e) => log::error!("Couldn't get system clipboard contents: {:?}", e), + Err(e) => Err(e.context("Couldn't get system clipboard contents")), } } fn replace_selections_with_clipboard(cx: &mut Context) { - replace_selections_with_clipboard_impl(&mut cx.editor); + let _ = replace_selections_with_clipboard_impl(&mut cx.editor); } fn paste_after(cx: &mut Context) { diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index c80535ed..a468b87c 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -98,14 +98,11 @@ impl Editor { } pub fn set_theme_from_name(&mut self, theme: &str) -> anyhow::Result<()> { - let theme = match self.theme_loader.load(theme.as_ref()) { - Ok(theme) => theme, - Err(e) => { - log::warn!("failed setting theme `{}` - {}", theme, e); - anyhow::bail!("failed setting theme `{}` - {}", theme, e); - } - }; - + use anyhow::Context; + let theme = self + .theme_loader + .load(theme.as_ref()) + .with_context(|| format!("failed setting theme `{}`", theme))?; self.set_theme(theme); Ok(()) } From 4a5cb0e04bf4861fe58ead4a83dc90a9f9940f1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Sun, 11 Jul 2021 16:42:23 +0900 Subject: [PATCH 14/31] Restore C-w shortcut --- helix-term/src/commands.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 8bbdfdfa..3b208ca8 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -3750,7 +3750,7 @@ mode_info! { /// window window_mode, WINDOW_MODE, /// rotate - "w" => rotate_view, + "w" | "C-w" => rotate_view, /// horizontal split "h" => hsplit, /// vertical split From 929f553f896cbdc2a628d715de62d8acb3ce6868 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Jul 2021 23:06:58 +0000 Subject: [PATCH 15/31] Bump tokio from 1.8.0 to 1.8.1 Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.8.0 to 1.8.1. - [Release notes](https://github.com/tokio-rs/tokio/releases) - [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.8.0...tokio-1.8.1) --- updated-dependencies: - dependency-name: tokio dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ab3cdd1e..7d30977b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1010,9 +1010,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "570c2eb13b3ab38208130eccd41be92520388791207fde783bda7c1e8ace28d4" +checksum = "98c8b05dc14c75ea83d63dd391100353789f5f24b8b3866542a5e85c8be8e985" dependencies = [ "autocfg", "bytes", From c74198a3bf3456140f07df068abe7dc1a9515fbc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Jul 2021 23:07:07 +0000 Subject: [PATCH 16/31] Bump tokio-stream from 0.1.6 to 0.1.7 Bumps [tokio-stream](https://github.com/tokio-rs/tokio) from 0.1.6 to 0.1.7. - [Release notes](https://github.com/tokio-rs/tokio/releases) - [Commits](https://github.com/tokio-rs/tokio/compare/tokio-stream-0.1.6...tokio-stream-0.1.7) --- updated-dependencies: - dependency-name: tokio-stream dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- helix-lsp/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7d30977b..ec389404 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1041,9 +1041,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8864d706fdb3cc0843a49647ac892720dac98a6eeb818b77190592cf4994066" +checksum = "7b2f3f698253f03119ac0102beaa64f67a67e08074d03a22d18784104543727f" dependencies = [ "futures-core", "pin-project-lite", diff --git a/helix-lsp/Cargo.toml b/helix-lsp/Cargo.toml index 3a13fb56..be099821 100644 --- a/helix-lsp/Cargo.toml +++ b/helix-lsp/Cargo.toml @@ -24,4 +24,4 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror = "1.0" tokio = { version = "1.8", features = ["full"] } -tokio-stream = "0.1.6" +tokio-stream = "0.1.7" From 7e5c20cc58a94c4af2fa525dcccfdc35ec0d58f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Jul 2021 23:07:15 +0000 Subject: [PATCH 17/31] Bump cc from 1.0.68 to 1.0.69 Bumps [cc](https://github.com/alexcrichton/cc-rs) from 1.0.68 to 1.0.69. - [Release notes](https://github.com/alexcrichton/cc-rs/releases) - [Commits](https://github.com/alexcrichton/cc-rs/compare/1.0.68...1.0.69) --- updated-dependencies: - dependency-name: cc dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ec389404..cd55edde 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,9 +58,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" [[package]] name = "cc" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" +checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" dependencies = [ "jobserver", ] From dd5e8082e410032c9782835cf0fc52a469d050b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Jul 2021 23:07:25 +0000 Subject: [PATCH 18/31] Bump anyhow from 1.0.41 to 1.0.42 Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.41 to 1.0.42. - [Release notes](https://github.com/dtolnay/anyhow/releases) - [Commits](https://github.com/dtolnay/anyhow/compare/1.0.41...1.0.42) --- updated-dependencies: - dependency-name: anyhow dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cd55edde..222751df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15af2628f6890fe2609a3b91bef4c83450512802e59489f9c1cb1fa5df064a61" +checksum = "595d3cfa7a60d4555cb5067b99f07142a08ea778de5cf993f7b75c7d8fabc486" [[package]] name = "arc-swap" From dd2903ff10387c04e933aa37846663131297b8b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Sun, 11 Jul 2021 19:36:45 +0900 Subject: [PATCH 19/31] Dynamically load grammar libraries at runtime --- .gitignore | 1 + Cargo.lock | 25 +++-- helix-core/src/indent.rs | 4 +- helix-core/src/syntax.rs | 12 +-- helix-syntax/Cargo.toml | 6 +- helix-syntax/build.rs | 204 +++++++++++++++++++++++++++----------- helix-syntax/src/lib.rs | 115 ++++++--------------- runtime/grammars/.gitkeep | 0 8 files changed, 201 insertions(+), 166 deletions(-) create mode 100644 runtime/grammars/.gitkeep diff --git a/.gitignore b/.gitignore index 1a42b440..346d0946 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ target helix-term/rustfmt.toml helix-syntax/languages/ result +runtime/grammars diff --git a/Cargo.lock b/Cargo.lock index 222751df..e262f081 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -61,9 +61,6 @@ name = "cc" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" -dependencies = [ - "jobserver", -] [[package]] name = "cfg-if" @@ -354,8 +351,9 @@ dependencies = [ name = "helix-syntax" version = "0.3.0" dependencies = [ + "anyhow", "cc", - "serde", + "libloading", "threadpool", "tree-sitter", ] @@ -475,15 +473,6 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" -[[package]] -name = "jobserver" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "972f5ae5d1cb9c6ae417789196c803205313edde988685da5e3aae0827b9e7fd" -dependencies = [ - "libc", -] - [[package]] name = "jsonrpc-core" version = "17.1.0" @@ -509,6 +498,16 @@ version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" +[[package]] +name = "libloading" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" +dependencies = [ + "cfg-if 1.0.0", + "winapi", +] + [[package]] name = "lock_api" version = "0.4.4" diff --git a/helix-core/src/indent.rs b/helix-core/src/indent.rs index 81bdffc0..1b36db7b 100644 --- a/helix-core/src/indent.rs +++ b/helix-core/src/indent.rs @@ -253,14 +253,14 @@ where let doc = Rope::from(doc); use crate::syntax::{ - Configuration, IndentationConfiguration, Lang, LanguageConfiguration, Loader, + Configuration, IndentationConfiguration, LanguageConfiguration, Loader, }; use once_cell::sync::OnceCell; let loader = Loader::new(Configuration { language: vec![LanguageConfiguration { scope: "source.rust".to_string(), file_types: vec!["rs".to_string()], - language_id: Lang::Rust, + language_id: "Rust".to_string(), highlight_config: OnceCell::new(), // roots: vec![], diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index 833ccfb9..f249f5fe 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -5,7 +5,7 @@ use crate::{ Rope, RopeSlice, Tendril, }; -pub use helix_syntax::{get_language, get_language_name, Lang}; +pub use helix_syntax::get_language; use arc_swap::ArcSwap; @@ -31,7 +31,7 @@ pub struct Configuration { #[serde(rename_all = "kebab-case")] pub struct LanguageConfiguration { #[serde(rename = "name")] - pub(crate) language_id: Lang, + pub(crate) language_id: String, pub scope: String, // source.rust pub file_types: Vec, // filename ends_with? pub roots: Vec, // these indicate project roots <.git, Cargo.toml> @@ -153,7 +153,7 @@ fn read_query(language: &str, filename: &str) -> String { impl LanguageConfiguration { fn initialize_highlight(&self, scopes: &[String]) -> Option> { - let language = get_language_name(self.language_id).to_ascii_lowercase(); + let language = self.language_id.to_ascii_lowercase(); let highlights_query = read_query(&language, "highlights.scm"); // always highlight syntax errors @@ -166,7 +166,7 @@ impl LanguageConfiguration { if highlights_query.is_empty() { None } else { - let language = get_language(self.language_id); + let language = get_language(&crate::RUNTIME_DIR, &self.language_id).ok()?; let config = HighlightConfiguration::new( language, &highlights_query, @@ -198,7 +198,7 @@ impl LanguageConfiguration { pub fn indent_query(&self) -> Option<&IndentQuery> { self.indent_query .get_or_init(|| { - let language = get_language_name(self.language_id).to_ascii_lowercase(); + let language = self.language_id.to_ascii_lowercase(); let toml = load_runtime_file(&language, "indents.toml").ok()?; toml::from_slice(toml.as_bytes()).ok() @@ -1802,7 +1802,7 @@ mod test { .map(String::from) .collect(); - let language = get_language(Lang::Rust); + let language = get_language(&crate::RUNTIME_DIR, "Rust").unwrap(); let config = HighlightConfiguration::new( language, &std::fs::read_to_string( diff --git a/helix-syntax/Cargo.toml b/helix-syntax/Cargo.toml index 140e3d24..7ad24488 100644 --- a/helix-syntax/Cargo.toml +++ b/helix-syntax/Cargo.toml @@ -12,8 +12,10 @@ include = ["src/**/*", "languages/**/*", "build.rs", "!**/docs/**/*", "!**/test/ [dependencies] tree-sitter = "0.19" -serde = { version = "1.0", features = ["derive"] } +libloading = "0.7" +anyhow = "1" [build-dependencies] -cc = { version = "1", features = ["parallel"] } +cc = { version = "1" } threadpool = { version = "1.0" } +anyhow = "1" diff --git a/helix-syntax/build.rs b/helix-syntax/build.rs index 847f8a67..02a5bf49 100644 --- a/helix-syntax/build.rs +++ b/helix-syntax/build.rs @@ -1,5 +1,7 @@ +use anyhow::Result; use std::fs; use std::path::PathBuf; +use std::time::SystemTime; use std::sync::mpsc::channel; @@ -15,66 +17,156 @@ fn collect_tree_sitter_dirs(ignore: &[String]) -> Vec { dirs } -fn collect_src_files(dir: &str) -> (Vec, Vec) { - eprintln!("Collect files for {}", dir); +#[cfg(unix)] +const DYLIB_EXTENSION: &str = "so"; - let mut c_files = Vec::new(); - let mut cpp_files = Vec::new(); - let path = PathBuf::from("languages").join(&dir).join("src"); - for entry in fs::read_dir(path).unwrap().flatten() { - let path = entry.path(); - if path - .file_stem() - .unwrap() - .to_str() - .unwrap() - .starts_with("binding") - { - continue; +#[cfg(windows)] +const DYLIB_EXTENSION: &str = "dll"; + +// const BUILD_TARGET: &'static str = env!("BUILD_TARGET"); + +use anyhow::{anyhow, Context}; +use std::{path::Path, process::Command}; + +fn build_library(src_path: &Path, language: &str) -> Result<()> { + let header_path = src_path; + // let grammar_path = src_path.join("grammar.json"); + let parser_path = src_path.join("parser.c"); + let mut scanner_path = src_path.join("scanner.c"); + + let scanner_path = if scanner_path.exists() { + Some(scanner_path) + } else { + scanner_path.set_extension("cc"); + if scanner_path.exists() { + Some(scanner_path) + } else { + None + } + }; + let parser_lib_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../runtime/grammars"); + let mut library_path = parser_lib_path.join(language); + library_path.set_extension(DYLIB_EXTENSION); + + let recompile = needs_recompile(&library_path, &parser_path, &scanner_path) + .with_context(|| "Failed to compare source and binary timestamps")?; + + if !recompile { + return Ok(()); + } + + let mut config = cc::Build::new(); + config.cpp(true).opt_level(2).cargo_metadata(false); + // .target(BUILD_TARGET) + // .host(BUILD_TARGET); + let compiler = config.get_compiler(); + let mut command = Command::new(compiler.path()); + for (key, value) in compiler.env() { + command.env(key, value); + } + + if cfg!(windows) { + command + .args(&["/nologo", "/LD", "/I"]) + .arg(header_path) + .arg("/Od") + .arg(parser_path); + if let Some(scanner_path) = scanner_path.as_ref() { + command.arg(scanner_path); } - if let Some(ext) = path.extension() { - if ext == "c" { - c_files.push(path.to_str().unwrap().to_string()); - } else if ext == "cc" || ext == "cpp" || ext == "cxx" { - cpp_files.push(path.to_str().unwrap().to_string()); + command + .arg("/link") + .arg(format!("/out:{}", library_path.to_str().unwrap())); + } else { + command + .arg("-shared") + .arg("-fPIC") + .arg("-fno-exceptions") + .arg("-g") + .arg("-I") + .arg(header_path) + .arg("-o") + .arg(&library_path) + .arg("-O2"); + if let Some(scanner_path) = scanner_path.as_ref() { + if scanner_path.extension() == Some("c".as_ref()) { + command.arg("-xc").arg("-std=c99").arg(scanner_path); + } else { + command.arg(scanner_path); } } + command.arg("-xc").arg(parser_path); } - (c_files, cpp_files) -} -fn build_c(files: Vec, language: &str) { - let mut build = cc::Build::new(); - for file in files { - build - .file(&file) - .include(PathBuf::from(file).parent().unwrap()) - .pic(true) - .warnings(false); + let output = command + .output() + .with_context(|| "Failed to execute C compiler")?; + if !output.status.success() { + return Err(anyhow!( + "Parser compilation failed.\nStdout: {}\nStderr: {}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + )); } - build.compile(&format!("tree-sitter-{}-c", language)); + + Ok(()) +} +fn needs_recompile( + lib_path: &Path, + parser_c_path: &Path, + scanner_path: &Option, +) -> Result { + if !lib_path.exists() { + return Ok(true); + } + let lib_mtime = mtime(lib_path)?; + if mtime(parser_c_path)? > lib_mtime { + return Ok(true); + } + if let Some(scanner_path) = scanner_path { + if mtime(scanner_path)? > lib_mtime { + return Ok(true); + } + } + Ok(false) } -fn build_cpp(files: Vec, language: &str) { - let mut build = cc::Build::new(); +fn mtime(path: &Path) -> Result { + Ok(fs::metadata(path)?.modified()?) +} - let flag = if build.get_compiler().is_like_msvc() { - "/std:c++17" - } else { - "-std=c++14" - }; +// fn build_c(files: Vec, language: &str) { +// let mut build = cc::Build::new(); +// for file in files { +// build +// .file(&file) +// .include(PathBuf::from(file).parent().unwrap()) +// .pic(true) +// .warnings(false); +// } +// build.compile(&format!("tree-sitter-{}-c", language)); +// } - for file in files { - build - .file(&file) - .include(PathBuf::from(file).parent().unwrap()) - .pic(true) - .warnings(false) - .cpp(true) - .flag_if_supported(flag); - } - build.compile(&format!("tree-sitter-{}-cpp", language)); -} +// fn build_cpp(files: Vec, language: &str) { +// let mut build = cc::Build::new(); + +// let flag = if build.get_compiler().is_like_msvc() { +// "/std:c++17" +// } else { +// "-std=c++14" +// }; + +// for file in files { +// build +// .file(&file) +// .include(PathBuf::from(file).parent().unwrap()) +// .pic(true) +// .warnings(false) +// .cpp(true) +// .flag_if_supported(flag); +// } +// build.compile(&format!("tree-sitter-{}-cpp", language)); +// } fn build_dir(dir: &str, language: &str) { println!("Build language {}", language); @@ -92,13 +184,9 @@ fn build_dir(dir: &str, language: &str) { eprintln!("You can fix in using 'git submodule init && git submodule update --recursive'."); std::process::exit(1); } - let (c, cpp) = collect_src_files(dir); - if !c.is_empty() { - build_c(c, language); - } - if !cpp.is_empty() { - build_cpp(cpp, language); - } + + let path = Path::new("languages").join(dir).join("src"); + build_library(&path, language).unwrap(); } fn main() { @@ -129,6 +217,6 @@ fn main() { // drop(tx); assert_eq!(rx.try_iter().sum::(), n_jobs); - build_dir("tree-sitter-typescript/tsx", "tsx"); - build_dir("tree-sitter-typescript/typescript", "typescript"); + // build_dir("tree-sitter-typescript/tsx", "tsx"); + // build_dir("tree-sitter-typescript/typescript", "typescript"); } diff --git a/helix-syntax/src/lib.rs b/helix-syntax/src/lib.rs index 5e3bb3ea..b6c0ecf3 100644 --- a/helix-syntax/src/lib.rs +++ b/helix-syntax/src/lib.rs @@ -1,94 +1,39 @@ -use serde::{Deserialize, Serialize}; +use anyhow::{Context, Result}; +use libloading::{Library, Symbol}; use tree_sitter::Language; -#[macro_export] -macro_rules! mk_extern { - ( $( $name:ident ),* ) => { - $( - extern "C" { pub fn $name() -> Language; } - )* - }; -} - -#[macro_export] -macro_rules! mk_enum { - ( $( $camel:ident ),* ) => { - #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] - #[serde(rename_all = "lowercase")] - pub enum Lang { - $( - $camel, - )* +fn replace_dashes_with_underscores(name: &str) -> String { + let mut result = String::with_capacity(name.len()); + for c in name.chars() { + if c == '-' { + result.push('_'); + } else { + result.push(c); } - }; + } + result } +#[cfg(unix)] +const DYLIB_EXTENSION: &str = "so"; -#[macro_export] -macro_rules! mk_get_language { - ( $( ($camel:ident, $name:ident) ),* ) => { - #[must_use] - pub fn get_language(lang: Lang) -> Language { - unsafe { - match lang { - $( - Lang::$camel => $name(), - )* - } - } - } - }; -} +#[cfg(windows)] +const DYLIB_EXTENSION: &str = "dll"; -#[macro_export] -macro_rules! mk_get_language_name { - ( $( $camel:ident ),* ) => { - #[must_use] - pub const fn get_language_name(lang: Lang) -> &'static str { - match lang { - $( - Lang::$camel => stringify!($camel), - )* - } - } - }; -} +pub fn get_language(runtime_path: &std::path::Path, name: &str) -> Result { + let name = name.to_ascii_lowercase(); + let mut library_path = runtime_path.join("grammars").join(&name); + // TODO: duplicated under build + library_path.set_extension(DYLIB_EXTENSION); -#[macro_export] -macro_rules! mk_langs { - ( $( ($camel:ident, $name:ident) ),* ) => { - mk_extern!($( $name ),*); - mk_enum!($( $camel ),*); - mk_get_language!($( ($camel, $name) ),*); - mk_get_language_name!($( $camel ),*); + let library = unsafe { Library::new(&library_path) } + .with_context(|| format!("Error opening dynamic library {:?}", &library_path))?; + let language_fn_name = format!("tree_sitter_{}", replace_dashes_with_underscores(&name)); + let language = unsafe { + let language_fn: Symbol Language> = library + .get(language_fn_name.as_bytes()) + .with_context(|| format!("Failed to load symbol {}", language_fn_name))?; + language_fn() }; + std::mem::forget(library); + Ok(language) } - -mk_langs!( - // 1) Name for enum - // 2) tree-sitter function to call to get a Language - (Agda, tree_sitter_agda), - (Bash, tree_sitter_bash), - (Cpp, tree_sitter_cpp), - (CSharp, tree_sitter_c_sharp), - (Css, tree_sitter_css), - (C, tree_sitter_c), - (Elixir, tree_sitter_elixir), - (Go, tree_sitter_go), - // (Haskell, tree_sitter_haskell), - (Html, tree_sitter_html), - (Javascript, tree_sitter_javascript), - (Java, tree_sitter_java), - (Json, tree_sitter_json), - (Julia, tree_sitter_julia), - (Latex, tree_sitter_latex), - (Nix, tree_sitter_nix), - (Php, tree_sitter_php), - (Python, tree_sitter_python), - (Ruby, tree_sitter_ruby), - (Rust, tree_sitter_rust), - (Scala, tree_sitter_scala), - (Swift, tree_sitter_swift), - (Toml, tree_sitter_toml), - (Tsx, tree_sitter_tsx), - (Typescript, tree_sitter_typescript) -); diff --git a/runtime/grammars/.gitkeep b/runtime/grammars/.gitkeep new file mode 100644 index 00000000..e69de29b From c8dc9b64dd0f6726cd1d36a61c563212721ef249 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Mon, 12 Jul 2021 00:44:14 +0900 Subject: [PATCH 20/31] windows: Try building inside OUT_DIR? --- helix-syntax/build.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/helix-syntax/build.rs b/helix-syntax/build.rs index 02a5bf49..2882f513 100644 --- a/helix-syntax/build.rs +++ b/helix-syntax/build.rs @@ -7,7 +7,8 @@ use std::sync::mpsc::channel; fn collect_tree_sitter_dirs(ignore: &[String]) -> Vec { let mut dirs = Vec::new(); - for entry in fs::read_dir("languages").unwrap().flatten() { + let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("languages"); + for entry in fs::read_dir(path).unwrap().flatten() { let path = entry.path(); let dir = path.file_name().unwrap().to_str().unwrap().to_string(); if !ignore.contains(&dir) { @@ -54,13 +55,14 @@ fn build_library(src_path: &Path, language: &str) -> Result<()> { if !recompile { return Ok(()); } - + let build_dir = std::env::var("OUT_DIR").unwrap(); let mut config = cc::Build::new(); config.cpp(true).opt_level(2).cargo_metadata(false); // .target(BUILD_TARGET) // .host(BUILD_TARGET); let compiler = config.get_compiler(); let mut command = Command::new(compiler.path()); + command.current_dir(build_dir); for (key, value) in compiler.env() { command.env(key, value); } @@ -185,7 +187,10 @@ fn build_dir(dir: &str, language: &str) { std::process::exit(1); } - let path = Path::new("languages").join(dir).join("src"); + let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("languages") + .join(dir) + .join("src"); build_library(&path, language).unwrap(); } From a7fa5621ce313e4e7d16621cf04b218d1ba1c3fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Mon, 12 Jul 2021 01:01:56 +0900 Subject: [PATCH 21/31] Try to rearrange the file order? --- helix-syntax/build.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/helix-syntax/build.rs b/helix-syntax/build.rs index 2882f513..b73658d1 100644 --- a/helix-syntax/build.rs +++ b/helix-syntax/build.rs @@ -71,12 +71,13 @@ fn build_library(src_path: &Path, language: &str) -> Result<()> { command .args(&["/nologo", "/LD", "/I"]) .arg(header_path) - .arg("/Od") - .arg(parser_path); + .arg("/Od"); if let Some(scanner_path) = scanner_path.as_ref() { command.arg(scanner_path); } + command + .arg(parser_path) .arg("/link") .arg(format!("/out:{}", library_path.to_str().unwrap())); } else { From e6bf6a8f285f4d11eb7be0b745b70d37e1dd6dfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Mon, 12 Jul 2021 17:48:45 +0900 Subject: [PATCH 22/31] Build each grammar in it's own src dir Windows places temporary files in the current dir, so compiling in parallel caused conflicts. --- helix-syntax/build.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/helix-syntax/build.rs b/helix-syntax/build.rs index b73658d1..ff82c892 100644 --- a/helix-syntax/build.rs +++ b/helix-syntax/build.rs @@ -55,14 +55,13 @@ fn build_library(src_path: &Path, language: &str) -> Result<()> { if !recompile { return Ok(()); } - let build_dir = std::env::var("OUT_DIR").unwrap(); let mut config = cc::Build::new(); config.cpp(true).opt_level(2).cargo_metadata(false); // .target(BUILD_TARGET) // .host(BUILD_TARGET); let compiler = config.get_compiler(); let mut command = Command::new(compiler.path()); - command.current_dir(build_dir); + command.current_dir(src_path); for (key, value) in compiler.env() { command.env(key, value); } From 3ca05fce313f37fe69462e1b93c4cef06c45510d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Tue, 13 Jul 2021 15:10:43 +0900 Subject: [PATCH 23/31] Fix number highlighting --- theme.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/theme.toml b/theme.toml index 34cd09b6..67b7dc57 100644 --- a/theme.toml +++ b/theme.toml @@ -73,7 +73,7 @@ silver = "#cccccc" sirocco = "#697C81" mint = "#9ff28f" almond = "#eccdba" -chamois = "#a4a0e8" +chamois = "#E8DCA0" honey = "#efba5d" apricot = "#f47868" From a4b077e9b901f19583ad04282ac502f99599f2cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Tue, 13 Jul 2021 23:27:06 +0900 Subject: [PATCH 24/31] Build ts/tsx again, refactor collect_tree_sitter_dirs --- helix-syntax/build.rs | 78 +++++++++++++++---------------------------- 1 file changed, 26 insertions(+), 52 deletions(-) diff --git a/helix-syntax/build.rs b/helix-syntax/build.rs index ff82c892..02c4bc0a 100644 --- a/helix-syntax/build.rs +++ b/helix-syntax/build.rs @@ -1,21 +1,35 @@ -use anyhow::Result; +use anyhow::{anyhow, Context, Result}; use std::fs; -use std::path::PathBuf; use std::time::SystemTime; +use std::{ + path::{Path, PathBuf}, + process::Command, +}; use std::sync::mpsc::channel; -fn collect_tree_sitter_dirs(ignore: &[String]) -> Vec { +fn collect_tree_sitter_dirs(ignore: &[String]) -> Result> { let mut dirs = Vec::new(); let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("languages"); - for entry in fs::read_dir(path).unwrap().flatten() { + + for entry in fs::read_dir(path)? { + let entry = entry?; let path = entry.path(); + + if !entry.file_type()?.is_dir() { + continue; + } + let dir = path.file_name().unwrap().to_str().unwrap().to_string(); - if !ignore.contains(&dir) { - dirs.push(dir); + + // filter ignores + if ignore.contains(&dir) { + continue; } + dirs.push(dir) } - dirs + + Ok(dirs) } #[cfg(unix)] @@ -24,11 +38,6 @@ const DYLIB_EXTENSION: &str = "so"; #[cfg(windows)] const DYLIB_EXTENSION: &str = "dll"; -// const BUILD_TARGET: &'static str = env!("BUILD_TARGET"); - -use anyhow::{anyhow, Context}; -use std::{path::Path, process::Command}; - fn build_library(src_path: &Path, language: &str) -> Result<()> { let header_path = src_path; // let grammar_path = src_path.join("grammar.json"); @@ -57,8 +66,6 @@ fn build_library(src_path: &Path, language: &str) -> Result<()> { } let mut config = cc::Build::new(); config.cpp(true).opt_level(2).cargo_metadata(false); - // .target(BUILD_TARGET) - // .host(BUILD_TARGET); let compiler = config.get_compiler(); let mut command = Command::new(compiler.path()); command.current_dir(src_path); @@ -137,39 +144,6 @@ fn mtime(path: &Path) -> Result { Ok(fs::metadata(path)?.modified()?) } -// fn build_c(files: Vec, language: &str) { -// let mut build = cc::Build::new(); -// for file in files { -// build -// .file(&file) -// .include(PathBuf::from(file).parent().unwrap()) -// .pic(true) -// .warnings(false); -// } -// build.compile(&format!("tree-sitter-{}-c", language)); -// } - -// fn build_cpp(files: Vec, language: &str) { -// let mut build = cc::Build::new(); - -// let flag = if build.get_compiler().is_like_msvc() { -// "/std:c++17" -// } else { -// "-std=c++14" -// }; - -// for file in files { -// build -// .file(&file) -// .include(PathBuf::from(file).parent().unwrap()) -// .pic(true) -// .warnings(false) -// .cpp(true) -// .flag_if_supported(flag); -// } -// build.compile(&format!("tree-sitter-{}-cpp", language)); -// } - fn build_dir(dir: &str, language: &str) { println!("Build language {}", language); if PathBuf::from("languages") @@ -191,6 +165,7 @@ fn build_dir(dir: &str, language: &str) { .join("languages") .join(dir) .join("src"); + build_library(&path, language).unwrap(); } @@ -198,9 +173,8 @@ fn main() { let ignore = vec![ "tree-sitter-typescript".to_string(), "tree-sitter-haskell".to_string(), // aarch64 failures: https://github.com/tree-sitter/tree-sitter-haskell/issues/34 - ".DS_Store".to_string(), ]; - let dirs = collect_tree_sitter_dirs(&ignore); + let dirs = collect_tree_sitter_dirs(&ignore).unwrap(); let mut n_jobs = 0; let pool = threadpool::Builder::new().build(); // by going through the builder, it'll use num_cpus @@ -211,7 +185,7 @@ fn main() { n_jobs += 1; pool.execute(move || { - let language = &dir[12..]; // skip tree-sitter- prefix + let language = &dir.strip_prefix("tree-sitter-").unwrap(); build_dir(&dir, language); // report progress @@ -222,6 +196,6 @@ fn main() { // drop(tx); assert_eq!(rx.try_iter().sum::(), n_jobs); - // build_dir("tree-sitter-typescript/tsx", "tsx"); - // build_dir("tree-sitter-typescript/typescript", "typescript"); + build_dir("tree-sitter-typescript/tsx", "tsx"); + build_dir("tree-sitter-typescript/typescript", "typescript"); } From d84b3a198a04c1c6931f07ab2fac5a3050bd7cff Mon Sep 17 00:00:00 2001 From: Kirawi <67773714+kirawi@users.noreply.github.com> Date: Wed, 14 Jul 2021 16:49:27 -0400 Subject: [PATCH 25/31] Update dark_plus.toml Didn't realize what `ui.statusline.active` was for. It's needed for view splits. --- runtime/themes/dark_plus.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/runtime/themes/dark_plus.toml b/runtime/themes/dark_plus.toml index 101c6d9e..82cc6289 100644 --- a/runtime/themes/dark_plus.toml +++ b/runtime/themes/dark_plus.toml @@ -55,6 +55,7 @@ "ui.linenr.selected" = { fg = "#c6c6c6" } "ui.statusline" = { fg = "#ffffff", bg = "#007acc" } +"ui.statusline.inactive" = { fg = "#ffffff", bg = "#007acc" } "ui.text" = { fg = "text", bg = "background" } "ui.text.focus" = { fg = "#ffffff" } From 0b1ed8656db35397122c1c2779e31bf86e38c430 Mon Sep 17 00:00:00 2001 From: Kirawi <67773714+kirawi@users.noreply.github.com> Date: Wed, 14 Jul 2021 22:22:34 -0400 Subject: [PATCH 26/31] Fix #442 (#446) * fix #442 fix #442 fmt * create Rope from default line ending * Fix use of encoding in Document::open() --- helix-view/src/document.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index a2bd1c41..8fdf7d98 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -456,14 +456,16 @@ impl Document { theme: Option<&Theme>, config_loader: Option<&syntax::Loader>, ) -> Result { - if !path.exists() { - return Ok(Self::default()); - } + let (mut rope, encoding) = if path.exists() { + let mut file = + std::fs::File::open(&path).context(format!("unable to open {:?}", path))?; + from_reader(&mut file, encoding)? + } else { + let encoding = encoding.unwrap_or(encoding_rs::UTF_8); + (Rope::from(DEFAULT_LINE_ENDING.as_str()), encoding) + }; - let mut file = std::fs::File::open(&path).context(format!("unable to open {:?}", path))?; - let (mut rope, encoding) = from_reader(&mut file, encoding)?; let line_ending = with_line_ending(&mut rope); - let mut doc = Self::from(rope, Some(encoding)); // set the path and try detecting the language From e2bcef718abfe9df8b3169e6017053506de5e1d1 Mon Sep 17 00:00:00 2001 From: Cor Date: Thu, 15 Jul 2021 10:27:27 +0200 Subject: [PATCH 27/31] Removed double entry of extend_line --- helix-term/src/keymap.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index d815e006..fe32e49f 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -120,7 +120,6 @@ impl Default for Keymaps { alt!(';') => Command::flip_selections, key!('%') => Command::select_all, key!('x') => Command::extend_line, - key!('x') => Command::extend_line, key!('X') => Command::extend_to_line_bounds, // crop_to_whole_line From 722cfedb382abf2a4e48ac45bdfd9b3c5de50a58 Mon Sep 17 00:00:00 2001 From: Cor Peters Date: Fri, 16 Jul 2021 18:12:59 +0200 Subject: [PATCH 28/31] Added change_case command (#441) * Added change_case command * Added switch_to_uppercase and switch_to_lowercase Renamed change_case to switch_case. * Updated the Keymap section of the Book * Use flat_map instead of map + flatten * Fix switch_to_uppercase using to_lowercase * Switched 'Alt-`' to uppercase and '`' to lowercase Co-authored-by: Cor --- book/src/keymap.md | 43 ++++++++++++++++-------------- helix-term/src/commands.rs | 54 ++++++++++++++++++++++++++++++++++++++ helix-term/src/keymap.rs | 4 +++ 3 files changed, 81 insertions(+), 20 deletions(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index c0c455d3..5d6e5795 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -41,26 +41,29 @@ ### Changes -| Key | Description | -| ----- | ----------- | -| `r` | Replace with a character | -| `R` | Replace with yanked text | -| `i` | Insert before selection | -| `a` | Insert after selection (append) | -| `I` | Insert at the start of the line | -| `A` | Insert at the end of the line | -| `o` | Open new line below selection | -| `o` | Open new line above selection | -| `u` | Undo change | -| `U` | Redo change | -| `y` | Yank selection | -| `p` | Paste after selection | -| `P` | Paste before selection | -| `>` | Indent selection | -| `<` | Unindent selection | -| `=` | Format selection | -| `d` | Delete selection | -| `c` | Change selection (delete and enter insert mode) | +| Key | Description | +| ----- | ----------- | +| `r` | Replace with a character | +| `R` | Replace with yanked text | +| `~` | Switch case of the selected text | +| `\`` | Set the selected text to upper case | +| `Alt-\`` | Set the selected text to lower case | +| `i` | Insert before selection | +| `a` | Insert after selection (append) | +| `I` | Insert at the start of the line | +| `A` | Insert at the end of the line | +| `o` | Open new line below selection | +| `o` | Open new line above selection | +| `u` | Undo change | +| `U` | Redo change | +| `y` | Yank selection | +| `p` | Paste after selection | +| `P` | Paste before selection | +| `>` | Indent selection | +| `<` | Unindent selection | +| `=` | Format selection | +| `d` | Delete selection | +| `c` | Change selection (delete and enter insert mode) | ### Selection manipulation diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 3b208ca8..7e769f4e 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -186,6 +186,9 @@ impl Command { extend_till_prev_char, extend_prev_char, replace, + switch_case, + switch_to_uppercase, + switch_to_lowercase, page_up, page_down, half_page_up, @@ -780,6 +783,57 @@ fn replace(cx: &mut Context) { }) } +fn switch_case(cx: &mut Context) { + let (view, doc) = current!(cx.editor); + let transaction = + Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| { + let text: Tendril = range + .fragment(doc.text().slice(..)) + .chars() + .flat_map(|ch| { + if ch.is_lowercase() { + ch.to_uppercase().collect() + } else if ch.is_uppercase() { + ch.to_lowercase().collect() + } else { + vec![ch] + } + }) + .collect(); + + (range.from(), range.to() + 1, Some(text)) + }); + + doc.apply(&transaction, view.id); + doc.append_changes_to_history(view.id); +} + +fn switch_to_uppercase(cx: &mut Context) { + let (view, doc) = current!(cx.editor); + let transaction = + Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| { + let text: Tendril = range.fragment(doc.text().slice(..)).to_uppercase().into(); + + (range.from(), range.to() + 1, Some(text)) + }); + + doc.apply(&transaction, view.id); + doc.append_changes_to_history(view.id); +} + +fn switch_to_lowercase(cx: &mut Context) { + let (view, doc) = current!(cx.editor); + let transaction = + Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| { + let text: Tendril = range.fragment(doc.text().slice(..)).to_lowercase().into(); + + (range.from(), range.to() + 1, Some(text)) + }); + + doc.apply(&transaction, view.id); + doc.append_changes_to_history(view.id); +} + fn scroll(cx: &mut Context, offset: usize, direction: Direction) { use Direction::*; let (view, doc) = current!(cx.editor); diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index fe32e49f..32994c37 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -82,6 +82,10 @@ impl Default for Keymaps { key!('r') => Command::replace, key!('R') => Command::replace_with_yanked, + key!('~') => Command::switch_case, + alt!('`') => Command::switch_to_uppercase, + key!('`') => Command::switch_to_lowercase, + key!(Home) => Command::goto_line_start, key!(End) => Command::goto_line_end, From 000b7b7c977aef59907011dd9a14d7a5d76826da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Sun, 18 Jul 2021 00:22:58 +0900 Subject: [PATCH 29/31] Make instructions regarding runtime clearer --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 56eddb31..b4f5b867 100644 --- a/README.md +++ b/README.md @@ -41,14 +41,17 @@ cargo install --path helix-term This will install the `hx` binary to `$HOME/.cargo/bin`. -Now copy the `runtime/` directory somewhere. Helix will by default look for the runtime -inside the config directory or the same directory as executable, but that can be overriden +Helix also needs it's runtime files so make sure to copy/symlink the `runtime/` directory into the +config directory (for example `~/.config/helix/runtime` on Linux/macOS). This location can be overriden via the `HELIX_RUNTIME` environment variable. -> NOTE: running via cargo doesn't require setting explicit `HELIX_RUNTIME` path, it will automatically +Packages already solve this for you by wrapping the `hx` binary with a wrapper +that sets the variable to the install dir. + +> NOTE: running via cargo also doesn't require setting explicit `HELIX_RUNTIME` path, it will automatically > detect the `runtime` directory in the project root. -If you want to embed the `runtime/` directory into the Helix binary you can build +Alternatively, if you want to embed the `runtime/` directory into the Helix binary you can build it with: ``` From 9fcbbfa46762484fc132383d9c2855ce37f60d6f Mon Sep 17 00:00:00 2001 From: Cor Date: Wed, 14 Jul 2021 21:29:39 +0200 Subject: [PATCH 30/31] Changed startup behaviour to only open a single view when multiple files are specified on the commandline. Changed the behaviour; the first argument on the commandline is the file on display --- helix-term/src/application.rs | 5 ++++- helix-view/src/editor.rs | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 79dd7c3b..c55d4c98 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -83,15 +83,18 @@ impl Application { editor.new_file(Action::VerticalSplit); compositor.push(Box::new(ui::file_picker(first.clone()))); } else { + let nr_of_files = args.files.len(); + editor.open(first.to_path_buf(), Action::VerticalSplit)?; for file in args.files { if file.is_dir() { return Err(anyhow::anyhow!( "expected a path to file, found a directory. (to open a directory pass it as first argument)" )); } else { - editor.open(file, Action::VerticalSplit)?; + editor.open(file.to_path_buf(), Action::Load)?; } } + editor.set_status(format!("Loaded {} files.", nr_of_files)); } } else { editor.new_file(Action::VerticalSplit); diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index a468b87c..cd9d0a92 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -39,6 +39,7 @@ pub struct Editor { #[derive(Debug, Copy, Clone)] pub enum Action { + Load, Replace, HorizontalSplit, VerticalSplit, @@ -151,6 +152,9 @@ impl Editor { return; } + Action::Load => { + return; + } Action::HorizontalSplit => { let view = View::new(id); let view_id = self.tree.split(view, Layout::Horizontal); From 6cba62b49917fde7c5876a1cce9d3883c6bef6c9 Mon Sep 17 00:00:00 2001 From: kabirz Date: Sat, 17 Jul 2021 22:29:28 +0800 Subject: [PATCH 31/31] action: copy grammar libraries to runtime --- .github/workflows/release.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 467ccbf4..5ce9ed62 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -99,6 +99,7 @@ jobs: else cp "target/${{ matrix.target }}/release/hx" "dist/" fi + cp -r runtime dist - uses: actions/upload-artifact@v2.2.4 with: @@ -148,7 +149,7 @@ jobs: pkgname=helix-$TAG-$platform mkdir tmp/$pkgname cp LICENSE README.md tmp/$pkgname - cp -r runtime tmp/$pkgname/ + mv bins-$platform/runtime tmp/$pkgname/ mv bins-$platform/hx$exe tmp/$pkgname chmod +x tmp/$pkgname/hx$exe