diff --git a/.gitmodules b/.gitmodules index e750198ae..0e0156589 100644 --- a/.gitmodules +++ b/.gitmodules @@ -102,3 +102,7 @@ path = helix-syntax/languages/tree-sitter-protobuf url = https://github.com/yusdacra/tree-sitter-protobuf.git shallow = true +[submodule "helix-syntax/languages/tree-sitter-zig"] + path = helix-syntax/languages/tree-sitter-zig + url = https://github.com/maxxnino/tree-sitter-zig + shallow = true diff --git a/book/src/keymap.md b/book/src/keymap.md index 4eb856369..613788637 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -6,121 +6,121 @@ > NOTE: `f`, `F`, `t` and `T` are not confined to the current line. -| Key | Description | -| ----- | ----------- | -| `h`, `Left` | Move left | -| `j`, `Down` | Move down | -| `k`, `Up` | Move up | -| `l`, `Right` | Move right | -| `w` | Move next word start | -| `b` | Move previous word start | -| `e` | Move next word end | -| `W` | Move next WORD start | -| `B` | Move previous WORD start | -| `E` | Move next WORD end | -| `t` | Find 'till next char | -| `f` | Find next char | -| `T` | Find 'till previous char | -| `F` | Find previous char | -| `Home` | Move to the start of the line | -| `End` | Move to the end of the line | -| `PageUp` | Move page up | -| `PageDown` | Move page down | -| `Ctrl-u` | Move half page up | -| `Ctrl-d` | Move half page down | -| `Ctrl-i` | Jump forward on the jumplist TODO: conflicts tab | -| `Ctrl-o` | Jump backward on the jumplist | -| `v` | Enter [select (extend) mode](#select--extend-mode) | -| `g` | Enter [goto mode](#goto-mode) | -| `m` | Enter [match mode](#match-mode) | -| `:` | Enter command mode | -| `z` | Enter [view mode](#view-mode) | -| `Ctrl-w` | Enter [window mode](#window-mode) (maybe will be remove for spc w w later) | -| `Space` | Enter [space mode](#space-mode) | -| `K` | Show documentation for the item under the cursor | +| Key | Description | Command | +| ----- | ----------- | ------- | +| `h`, `Left` | Move left | `move_char_left` | +| `j`, `Down` | Move down | `move_char_right` | +| `k`, `Up` | Move up | `move_line_up` | +| `l`, `Right` | Move right | `move_line_down` | +| `w` | Move next word start | `move_next_word_start` | +| `b` | Move previous word start | `move_prev_word_start` | +| `e` | Move next word end | `move_next_word_end` | +| `W` | Move next WORD start | `move_next_long_word_start` | +| `B` | Move previous WORD start | `move_prev_long_word_start` | +| `E` | Move next WORD end | `move_next_long_word_end` | +| `t` | Find 'till next char | `find_till_char` | +| `f` | Find next char | `find_next_char` | +| `T` | Find 'till previous char | `till_prev_char` | +| `F` | Find previous char | `find_prev_char` | +| `Home` | Move to the start of the line | `goto_line_start` | +| `End` | Move to the end of the line | `goto_line_end` | +| `PageUp` | Move page up | `page_up` | +| `PageDown` | Move page down | `page_down` | +| `Ctrl-u` | Move half page up | `half_page_up` | +| `Ctrl-d` | Move half page down | `half_page_down` | +| `Ctrl-i` | Jump forward on the jumplist TODO: conflicts tab | `jump_forward` | +| `Ctrl-o` | Jump backward on the jumplist | `jump_backward` | +| `v` | Enter [select (extend) mode](#select--extend-mode) | `select_mode` | +| `g` | Enter [goto mode](#goto-mode) | N/A | +| `m` | Enter [match mode](#match-mode) | N/A | +| `:` | Enter command mode | `command_mode` | +| `z` | Enter [view mode](#view-mode) | N/A | +| `Ctrl-w` | Enter [window mode](#window-mode) (maybe will be remove for spc w w later) | N/A | +| `Space` | Enter [space mode](#space-mode) | N/A | +| `K` | Show documentation for the item under the cursor | `hover` | ### Changes -| Key | Description | -| ----- | ----------- | -| `r` | Replace with a character | -| `R` | Replace with yanked text | -| `~` | Switch case of the selected text | -| `` ` `` | Set the selected text to lower case | -| `` Alt-` `` | Set the selected text to upper 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 | -| `"` `` | Select a register to yank to or paste from | -| `>` | Indent selection | -| `<` | Unindent selection | -| `=` | Format selection | -| `d` | Delete selection | -| `c` | Change selection (delete and enter insert mode) | +| Key | Description | Command | +| ----- | ----------- | ------- | +| `r` | Replace with a character | `replace` | +| `R` | Replace with yanked text | `replace_with_yanked` | +| `~` | Switch case of the selected text | `switch_case` | +| `` ` `` | Set the selected text to lower case | `switch_to_lowercase` | +| `` Alt-` `` | Set the selected text to upper case | `switch_to_uppercase` | +| `i` | Insert before selection | `insert_mode` | +| `a` | Insert after selection (append) | `append_mode` | +| `I` | Insert at the start of the line | `prepend_to_line` | +| `A` | Insert at the end of the line | `append_to_line` | +| `o` | Open new line below selection | `open_below` | +| `O` | Open new line above selection | `open_above` | +| `u` | Undo change | `undo` | +| `U` | Redo change | `redo` | +| `y` | Yank selection | `yank` | +| `p` | Paste after selection | `paste_after` | +| `P` | Paste before selection | `paste_before` | +| `"` `` | Select a register to yank to or paste from | `select_register` | +| `>` | Indent selection | `indent` | +| `<` | Unindent selection | `unindent` | +| `=` | Format selection | `format_selections` | +| `d` | Delete selection | `delete_selection` | +| `c` | Change selection (delete and enter insert mode) | `change_selection` | ### Selection manipulation -| Key | Description | -| ----- | ----------- | -| `s` | Select all regex matches inside selections | -| `S` | Split selection into subselections on regex matches | -| `Alt-s` | Split selection on newlines | -| `;` | Collapse selection onto a single cursor | -| `Alt-;` | Flip selection cursor and anchor | -| `C` | Copy selection onto the next line | -| `Alt-C` | Copy selection onto the previous line | -| `(` | Rotate main selection forward | -| `)` | Rotate main selection backward | -| `Alt-(` | Rotate selection contents forward | -| `Alt-)` | Rotate selection contents backward | -| `%` | Select entire file | -| `x` | Select current line, if already selected, extend to next line | -| `X` | Extend selection to line bounds (line-wise selection) | -| | Expand selection to parent syntax node TODO: pick a key | -| `J` | Join lines inside selection | -| `K` | Keep selections matching the regex TODO: overlapped by hover help | -| `Space` | Keep only the primary selection TODO: overlapped by space mode | -| `Ctrl-c` | Comment/uncomment the selections | +| Key | Description | Command | +| ----- | ----------- | ------- | +| `s` | Select all regex matches inside selections | `select_regex` | +| `S` | Split selection into subselections on regex matches | `split_selection` | +| `Alt-s` | Split selection on newlines | `split_selection_on_newline` | +| `;` | Collapse selection onto a single cursor | `collapse_selection` | +| `Alt-;` | Flip selection cursor and anchor | `flip_selections` | +| `C` | Copy selection onto the next line | `copy_selection_on_next_line` | +| `Alt-C` | Copy selection onto the previous line | `copy_selection_on_prev_line` | +| `(` | Rotate main selection forward | `rotate_selections_backward` | +| `)` | Rotate main selection backward | `rotate_selections_forward` | +| `Alt-(` | Rotate selection contents forward | `rotate_selection_contents_backward` | +| `Alt-)` | Rotate selection contents backward | `rotate_selection_contents_forward` | +| `%` | Select entire file | `select_all` | +| `x` | Select current line, if already selected, extend to next line | `extend_line` | +| `X` | Extend selection to line bounds (line-wise selection) | `extend_to_line_bounds` | +| | Expand selection to parent syntax node TODO: pick a key | `expand_selection` | +| `J` | Join lines inside selection | `join_selections` | +| `K` | Keep selections matching the regex TODO: overlapped by hover help | `keep_selections` | +| `Space` | Keep only the primary selection TODO: overlapped by space mode | `keep_primary_selection` | +| `Ctrl-c` | Comment/uncomment the selections | `toggle_comments` | ### Insert Mode -| Key | Description | -| ----- | ----------- | -| `Escape` | Switch to normal mode | -| `Ctrl-x` | Autocomplete | -| `Ctrl-w` | Delete previous word | +| Key | Description | Command | +| ----- | ----------- | ------- | +| `Escape` | Switch to normal mode | `normal_mode` | +| `Ctrl-x` | Autocomplete | `completion` | +| `Ctrl-w` | Delete previous word | `delete_word_backward` | ### Search > TODO: The search implementation isn't ideal yet -- we don't support searching in reverse, or searching via smartcase. -| Key | Description | -| ----- | ----------- | -| `/` | Search for regex pattern | -| `n` | Select next search match | -| `N` | Add next search match to selection | -| `*` | Use current selection as the search pattern | +| Key | Description | Command | +| ----- | ----------- | ------- | +| `/` | Search for regex pattern | `search` | +| `n` | Select next search match | `search_next` | +| `N` | Add next search match to selection | `extend_search_next` | +| `*` | Use current selection as the search pattern | `search_selection` | ### Diagnostics > NOTE: `[` and `]` will likely contain more pair mappings in the style of > [vim-unimpaired](https://github.com/tpope/vim-unimpaired) -| Key | Description | -| ----- | ----------- | -| `[d` | Go to previous diagnostic | -| `]d` | Go to next diagnostic | -| `[D` | Go to first diagnostic in document | -| `]D` | Go to last diagnostic in document | +| Key | Description | Command | +| ----- | ----------- | ------- | +| `[d` | Go to previous diagnostic | `goto_prev_diag` | +| `]d` | Go to next diagnostic | `goto_next_diag` | +| `[D` | Go to first diagnostic in document | `goto_first_diag` | +| `]D` | Go to last diagnostic in document | `goto_last_diag` | ## Select / extend mode @@ -135,14 +135,14 @@ commands to extend the existing selection instead of replacing it. View mode is intended for scrolling and manipulating the view without changing the selection. -| Key | Description | -| ----- | ----------- | -| `z` , `c` | Vertically center the line | -| `t` | Align the line to the top of the screen | -| `b` | Align the line to the bottom of the screen | -| `m` | Align the line to the middle of the screen (horizontally) | -| `j` | Scroll the view downwards | -| `k` | Scroll the view upwards | +| Key | Description | Command | +| ----- | ----------- | ------- | +| `z` , `c` | Vertically center the line | `align_view_center` | +| `t` | Align the line to the top of the screen | `align_view_top` | +| `b` | Align the line to the bottom of the screen | `align_view_bottom` | +| `m` | Align the line to the middle of the screen (horizontally) | `align_view_middle` | +| `j` | Scroll the view downwards | `scroll_down` | +| `k` | Scroll the view upwards | `scroll_up` | ## Goto mode @@ -150,21 +150,21 @@ Jumps to various locations. > NOTE: Some of these features are only available with the LSP present. -| Key | Description | -| ----- | ----------- | -| `g` | Go to the start of the file | -| `e` | Go to the end of the file | -| `h` | Go to the start of the line | -| `l` | Go to the end of the line | -| `s` | Go to first non-whitespace character of the line | -| `t` | Go to the top of the screen | -| `m` | Go to the middle of the screen | -| `b` | Go to the bottom of the screen | -| `d` | Go to definition | -| `y` | Go to type definition | -| `r` | Go to references | -| `i` | Go to implementation | -| `a` | Go to the last accessed/alternate file | +| Key | Description | Command | +| ----- | ----------- | ------- | +| `g` | Go to the start of the file | `goto_file_start` | +| `e` | Go to the end of the file | `goto_last_line` | +| `h` | Go to the start of the line | `goto_line_start` | +| `l` | Go to the end of the line | `goto_line_end` | +| `s` | Go to first non-whitespace character of the line | `goto_first_nonwhitespace` | +| `t` | Go to the top of the screen | `goto_window_top` | +| `m` | Go to the middle of the screen | `goto_window_middle` | +| `b` | Go to the bottom of the screen | `goto_window_bottom` | +| `d` | Go to definition | `goto_definition` | +| `y` | Go to type definition | `goto_type_definition` | +| `r` | Go to references | `goto_reference` | +| `i` | Go to implementation | `goto_implementation` | +| `a` | Go to the last accessed/alternate file | `goto_last_accessed_file` | ## Match mode @@ -172,14 +172,14 @@ Enter this mode using `m` from normal mode. See the relavant section in [Usage](./usage.md) for an explanation about [surround](./usage.md#surround) and [textobject](./usage.md#textobject) usage. -| Key | Description | -| ----- | ----------- | -| `m` | Goto matching bracket | -| `s` `` | Surround current selection with `` | -| `r` `` | Replace surround character `` with `` | -| `d` `` | Delete surround character `` | -| `a` `` | Select around textobject | -| `i` `` | Select inside textobject | +| Key | Description | Command | +| ----- | ----------- | ------- | +| `m` | Goto matching bracket | `match_brackets` | +| `s` `` | Surround current selection with `` | `surround_add` | +| `r` `` | Replace surround character `` with `` | `surround_replace` | +| `d` `` | Delete surround character `` | `surround_delete` | +| `a` `` | Select around textobject | `select_textobject_around` | +| `i` `` | Select inside textobject | `select_textobject_inner` | ## Object mode @@ -189,35 +189,35 @@ TODO: Mappings for selecting syntax nodes (a superset of `[`). This layer is similar to vim keybindings as kakoune does not support window. -| Key | Description | -| ----- | ------------- | -| `w`, `Ctrl-w` | Switch to next window | -| `v`, `Ctrl-v` | Vertical right split | -| `h`, `Ctrl-h` | Horizontal bottom split | -| `q`, `Ctrl-q` | Close current window | +| Key | Description | Command | +| ----- | ------------- | ------- | +| `w`, `Ctrl-w` | Switch to next window | `rotate_view` | +| `v`, `Ctrl-v` | Vertical right split | `vsplit` | +| `h`, `Ctrl-h` | Horizontal bottom split | `hsplit` | +| `q`, `Ctrl-q` | Close current window | `wclose` | ## Space mode This layer is a kludge of mappings I had under leader key in neovim. -| Key | Description | -| ----- | ----------- | -| `f` | Open file picker | -| `b` | Open buffer picker | -| `s` | Open symbol picker (current document) | -| `a` | Apply code action | -| `'` | Open last fuzzy picker | -| `w` | Enter [window mode](#window-mode) | -| `space` | Keep primary selection TODO: it's here because space mode replaced it | -| `p` | Paste system clipboard after selections | -| `P` | Paste system clipboard before selections | -| `y` | Join and yank selections to clipboard | -| `Y` | Yank main selection to clipboard | -| `R` | Replace selections by clipboard contents | +| Key | Description | Command | +| ----- | ----------- | ------- | +| `f` | Open file picker | `file_picker` | +| `b` | Open buffer picker | `buffer_picker` | +| `s` | Open symbol picker (current document) | `symbol_picker` | +| `a` | Apply code action | `code_action` | +| `'` | Open last fuzzy picker | `last_picker` | +| `w` | Enter [window mode](#window-mode) | N/A | +| `space` | Keep primary selection TODO: it's here because space mode replaced it | `keep_primary_selection` | +| `p` | Paste system clipboard after selections | `paste_clipboard_after` | +| `P` | Paste system clipboard before selections | `paste_clipboard_before` | +| `y` | Join and yank selections to clipboard | `yank_joined_to_clipboard` | +| `Y` | Yank main selection to clipboard | `yank_main_selection_to_clipboard` | +| `R` | Replace selections by clipboard contents | `replace_selections_with_clipboard` | # Picker -Keys to use within picker. +Keys to use within picker. Remapping currently not supported. | Key | Description | | ----- | ------------- | diff --git a/book/src/themes.md b/book/src/themes.md index f17195aaa..0a4d58ad5 100644 --- a/book/src/themes.md +++ b/book/src/themes.md @@ -91,6 +91,9 @@ Possible keys: | `ui.help` | | | `ui.text` | | | `ui.text.focus` | | +| `ui.info` | | +| `ui.info.text` | | +| `ui.menu` | | | `ui.menu.selected` | | | `ui.selection` | For selections in the editing area | | `ui.selection.primary` | | diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs index be01c3026..d971464a8 100644 --- a/helix-core/src/lib.rs +++ b/helix-core/src/lib.rs @@ -98,6 +98,89 @@ pub fn cache_dir() -> std::path::PathBuf { path } +// right overrides left +pub fn merge_toml_values(left: toml::Value, right: toml::Value) -> toml::Value { + use toml::Value; + + fn get_name(v: &Value) -> Option<&str> { + v.get("name").and_then(Value::as_str) + } + + match (left, right) { + (Value::Array(mut left_items), Value::Array(right_items)) => { + left_items.reserve(right_items.len()); + for rvalue in right_items { + let lvalue = get_name(&rvalue) + .and_then(|rname| left_items.iter().position(|v| get_name(v) == Some(rname))) + .map(|lpos| left_items.remove(lpos)); + let mvalue = match lvalue { + Some(lvalue) => merge_toml_values(lvalue, rvalue), + None => rvalue, + }; + left_items.push(mvalue); + } + Value::Array(left_items) + } + (Value::Table(mut left_map), Value::Table(right_map)) => { + for (rname, rvalue) in right_map { + match left_map.remove(&rname) { + Some(lvalue) => { + let merged_value = merge_toml_values(lvalue, rvalue); + left_map.insert(rname, merged_value); + } + None => { + left_map.insert(rname, rvalue); + } + } + } + Value::Table(left_map) + } + // Catch everything else we didn't handle, and use the right value + (_, value) => value, + } +} + +#[cfg(test)] +mod merge_toml_tests { + use super::merge_toml_values; + + #[test] + fn language_tomls() { + use toml::Value; + + const USER: &str = " + [[language]] + name = \"nix\" + test = \"bbb\" + indent = { tab-width = 4, unit = \" \", test = \"aaa\" } + "; + + let base: Value = toml::from_slice(include_bytes!("../../languages.toml")) + .expect("Couldn't parse built-in langauges config"); + let user: Value = toml::from_str(USER).unwrap(); + + let merged = merge_toml_values(base, user); + let languages = merged.get("language").unwrap().as_array().unwrap(); + let nix = languages + .iter() + .find(|v| v.get("name").unwrap().as_str().unwrap() == "nix") + .unwrap(); + let nix_indent = nix.get("indent").unwrap(); + + // We changed tab-width and unit in indent so check them if they are the new values + assert_eq!( + nix_indent.get("tab-width").unwrap().as_integer().unwrap(), + 4 + ); + assert_eq!(nix_indent.get("unit").unwrap().as_str().unwrap(), " "); + // We added a new keys, so check them + assert_eq!(nix.get("test").unwrap().as_str().unwrap(), "bbb"); + assert_eq!(nix_indent.get("test").unwrap().as_str().unwrap(), "aaa"); + // We didn't change comment-token so it should be same + assert_eq!(nix.get("comment-token").unwrap().as_str().unwrap(), "#"); + } +} + pub use etcetera::home_dir; use etcetera::base_strategy::{choose_base_strategy, BaseStrategy}; diff --git a/helix-core/src/state.rs b/helix-core/src/state.rs index 7e4a7f701..dcc4b11b7 100644 --- a/helix-core/src/state.rs +++ b/helix-core/src/state.rs @@ -1,6 +1,5 @@ use crate::{Rope, Selection}; -/// A state represents the current editor state of a single buffer. #[derive(Debug, Clone)] pub struct State { pub doc: Rope, @@ -15,27 +14,4 @@ impl State { selection: Selection::point(0), } } - - // update/transact: - // update(desc) => transaction ? transaction.doc() for applied doc - // transaction.apply(doc) - // doc.transact(fn -> ... end) - - // replaceSelection (transaction that replaces selection) - // changeByRange - // changes - // slice - // - // getters: - // tabSize - // indentUnit - // languageDataAt() - // - // config: - // indentation - // tabSize - // lineUnit - // syntax - // foldable - // changeFilter/transactionFilter } diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index ae99a1595..a74102031 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -1831,15 +1831,14 @@ mod test { #[test] fn test_input_edits() { - use crate::State; use tree_sitter::InputEdit; - let state = State::new("hello world!\ntest 123".into()); + let doc = Rope::from("hello world!\ntest 123"); let transaction = Transaction::change( - &state.doc, + &doc, vec![(6, 11, Some("test".into())), (12, 17, None)].into_iter(), ); - let edits = LanguageLayer::generate_edits(state.doc.slice(..), transaction.changes()); + let edits = LanguageLayer::generate_edits(doc.slice(..), transaction.changes()); // transaction.apply(&mut state); assert_eq!( @@ -1865,13 +1864,13 @@ mod test { ); // Testing with the official example from tree-sitter - let mut state = State::new("fn test() {}".into()); + let mut doc = Rope::from("fn test() {}"); let transaction = - Transaction::change(&state.doc, vec![(8, 8, Some("a: u32".into()))].into_iter()); - let edits = LanguageLayer::generate_edits(state.doc.slice(..), transaction.changes()); - transaction.apply(&mut state.doc); + Transaction::change(&doc, vec![(8, 8, Some("a: u32".into()))].into_iter()); + let edits = LanguageLayer::generate_edits(doc.slice(..), transaction.changes()); + transaction.apply(&mut doc); - assert_eq!(state.doc, "fn test(a: u32) {}"); + assert_eq!(doc, "fn test(a: u32) {}"); assert_eq!( edits, &[InputEdit { diff --git a/helix-core/src/transaction.rs b/helix-core/src/transaction.rs index e20e550fa..d682f0582 100644 --- a/helix-core/src/transaction.rs +++ b/helix-core/src/transaction.rs @@ -125,7 +125,7 @@ impl ChangeSet { /// In other words, If `this` goes `docA` → `docB` and `other` represents `docB` → `docC`, the /// returned value will represent the change `docA` → `docC`. pub fn compose(self, other: Self) -> Self { - debug_assert!(self.len_after == other.len); + assert!(self.len_after == other.len); // composing fails in weird ways if one of the sets is empty // a: [] len: 0 len_after: 1 | b: [Insert(Tendril(inline: "\n")), Retain(1)] len 1 @@ -689,21 +689,21 @@ mod test { #[test] fn transaction_change() { - let mut state = State::new("hello world!\ntest 123".into()); + let mut doc = Rope::from("hello world!\ntest 123"); let transaction = Transaction::change( - &state.doc, + &doc, // (1, 1, None) is a useless 0-width delete vec![(1, 1, None), (6, 11, Some("void".into())), (12, 17, None)].into_iter(), ); - transaction.apply(&mut state.doc); - assert_eq!(state.doc, Rope::from_str("hello void! 123")); + transaction.apply(&mut doc); + assert_eq!(doc, Rope::from_str("hello void! 123")); } #[test] fn changes_iter() { - let state = State::new("hello world!\ntest 123".into()); + let doc = Rope::from("hello world!\ntest 123"); let changes = vec![(6, 11, Some("void".into())), (12, 17, None)]; - let transaction = Transaction::change(&state.doc, changes.clone().into_iter()); + let transaction = Transaction::change(&doc, changes.clone().into_iter()); assert_eq!(transaction.changes_iter().collect::>(), changes); } diff --git a/helix-syntax/languages/tree-sitter-zig b/helix-syntax/languages/tree-sitter-zig new file mode 160000 index 000000000..049162bea --- /dev/null +++ b/helix-syntax/languages/tree-sitter-zig @@ -0,0 +1 @@ +Subproject commit 049162bea8a44e1a4acd01b06e1c8672d9231a86 diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 17c762daf..9aa982711 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -1,4 +1,4 @@ -use helix_core::{syntax, Range, Selection}; +use helix_core::{merge_toml_values, syntax, Range, Selection}; use helix_dap::Payload; use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap}; use helix_view::{theme, Editor}; @@ -66,11 +66,16 @@ impl Application { let theme_loader = std::sync::Arc::new(theme::Loader::new(&conf_dir, &helix_core::runtime_dir())); - // load $HOME/.config/helix/languages.toml, fallback to default config - let lang_conf = std::fs::read(conf_dir.join("languages.toml")); - let lang_conf = lang_conf - .as_deref() - .unwrap_or(include_bytes!("../../languages.toml")); + // load default and user config, and merge both + let def_lang_conf: toml::Value = toml::from_slice(include_bytes!("../../languages.toml")) + .expect("Could not parse built-in languages.toml, something must be very wrong"); + let user_lang_conf: Option = std::fs::read(conf_dir.join("languages.toml")) + .ok() + .map(|raw| toml::from_slice(&raw).expect("Could not parse user languages.toml")); + let lang_conf = match user_lang_conf { + Some(value) => merge_toml_values(def_lang_conf, value), + None => def_lang_conf, + }; let theme = if let Some(theme) = &config.theme { match theme_loader.load(theme) { @@ -84,7 +89,9 @@ impl Application { theme_loader.default() }; - let syn_loader_conf = toml::from_slice(lang_conf).expect("Could not parse languages.toml"); + let syn_loader_conf: helix_core::syntax::Configuration = lang_conf + .try_into() + .expect("Could not parse merged (built-in + user) languages.toml"); let syn_loader = std::sync::Arc::new(syntax::Loader::new(syn_loader_conf)); let mut editor = Editor::new( diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 5eb519659..e5db16243 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2369,7 +2369,7 @@ mod cmd { }, TypableCommand { name: "vsplit", - alias: Some("vsp"), + alias: Some("vs"), doc: "Open the file in a vertical split.", fun: vsplit, completer: Some(completers::filename), diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index d16c9a3af..2aa3f9f3b 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -541,6 +541,8 @@ impl Default for Keymaps { "home" => goto_line_start, "end" => goto_line_end, "esc" => exit_select_mode, + + "v" => normal_mode, })); let insert = keymap!({ "Insert mode" "esc" => normal_mode, diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 1f4fc1c5c..6428870ef 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -508,7 +508,13 @@ impl EditorView { } else { let line = match config.line_number { LineNumber::Absolute => line + 1, - LineNumber::Relative => abs_diff(current_line, line), + LineNumber::Relative => { + if current_line == line { + line + 1 + } else { + abs_diff(current_line, line) + } + } }; format!("{:>5}", line) }; diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs index a56cf19b2..24dd3e618 100644 --- a/helix-term/src/ui/menu.rs +++ b/helix-term/src/ui/menu.rs @@ -259,8 +259,11 @@ impl Component for Menu { // TODO: required size should re-trigger when we filter items so we can draw a smaller menu fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) { - let style = cx.editor.theme.get("ui.text"); - let selected = cx.editor.theme.get("ui.menu.selected"); + let theme = &cx.editor.theme; + let style = theme + .try_get("ui.menu") + .unwrap_or_else(|| theme.get("ui.text")); + let selected = theme.get("ui.menu.selected"); let scroll = self.scroll; diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index e48713121..f3f8670e2 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -228,7 +228,8 @@ pub mod completers { let end = input.len()..; - let mut files: Vec<_> = WalkBuilder::new(dir.clone()) + let mut files: Vec<_> = WalkBuilder::new(&dir) + .hidden(false) .max_depth(Some(1)) .build() .filter_map(|file| { diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index 19986b5cb..7197adea8 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -400,18 +400,6 @@ impl Component for Prompt { }))); match event { - // char or shift char - KeyEvent { - code: KeyCode::Char(c), - modifiers: KeyModifiers::NONE, - } - | KeyEvent { - code: KeyCode::Char(c), - modifiers: KeyModifiers::SHIFT, - } => { - self.insert_char(c); - (self.callback_fn)(cx, &self.line, PromptEvent::Update); - } KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::CONTROL, @@ -539,6 +527,14 @@ impl Component for Prompt { code: KeyCode::Char('q'), modifiers: KeyModifiers::CONTROL, } => self.exit_selection(), + // any char event that's not combined with control or mapped to any other combo + KeyEvent { + code: KeyCode::Char(c), + modifiers, + } if !modifiers.contains(KeyModifiers::CONTROL) => { + self.insert_char(c); + (self.callback_fn)(cx, &self.line, PromptEvent::Update); + } _ => (), }; diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index a238644a9..e890a3366 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -102,7 +102,7 @@ pub struct Document { language_server: Option>, } -use std::fmt; +use std::{fmt, mem}; impl fmt::Debug for Document { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Document") @@ -301,20 +301,13 @@ pub async fn to_writer<'a, W: tokio::io::AsyncWriteExt + Unpin + ?Sized>( Ok(()) } -/// Like std::mem::replace() except it allows the replacement value to be mapped from the -/// original value. -fn take_with(mut_ref: &mut T, closure: F) +fn take_with(mut_ref: &mut T, f: F) where + T: Default, F: FnOnce(T) -> T, { - use std::{panic, ptr}; - - unsafe { - let old_t = ptr::read(mut_ref); - let new_t = panic::catch_unwind(panic::AssertUnwindSafe(|| closure(old_t))) - .unwrap_or_else(|_| ::std::process::abort()); - ptr::write(mut_ref, new_t); - } + let t = mem::take(mut_ref); + let _ = mem::replace(mut_ref, f(t)); } use helix_lsp::lsp; diff --git a/helix-view/src/input.rs b/helix-view/src/input.rs index 28b204bb8..1e0ddfe25 100644 --- a/helix-view/src/input.rs +++ b/helix-view/src/input.rs @@ -15,10 +15,10 @@ pub struct KeyEvent { } impl KeyEvent { - /// If a character was pressed (without modifiers), return it. + /// If a character was pressed, return it. pub fn char(&self) -> Option { match self.code { - KeyCode::Char(ch) if self.modifiers.is_empty() => Some(ch), + KeyCode::Char(ch) => Some(ch), _ => None, } } diff --git a/languages.toml b/languages.toml index 9b9fb4b08..f4badb6ea 100644 --- a/languages.toml +++ b/languages.toml @@ -311,3 +311,15 @@ indent = { tab-width = 4, unit = " " } # comment-token = "--" # # indent = { tab-width = 2, unit = " " } + +[[language]] +name = "zig" +scope = "source.zig" +injection-regex = "zig" +file-types = ["zig"] +roots = ["build.zig"] +auto-format = true +comment-token = "//" + +language-server = { command = "zls" } +indent = { tab-width = 4, unit = " " } diff --git a/runtime/queries/zig/highlights.scm b/runtime/queries/zig/highlights.scm new file mode 100644 index 000000000..5a3d62dc8 --- /dev/null +++ b/runtime/queries/zig/highlights.scm @@ -0,0 +1,198 @@ +[ + (container_doc_comment) + (doc_comment) + (line_comment) +] @comment + +; field in top level decl, and in struct, union... +(ContainerField + (IDENTIFIER) @property + (SuffixExpr (IDENTIFIER) @type)? +) + +; error.OutOfMemory; +(SuffixExpr + "error" + "." + (IDENTIFIER) @constant +) + +; var x: IDENTIFIER +type: (SuffixExpr (IDENTIFIER) @type) + +; IDENTIFIER{} +constructor: (SuffixExpr (IDENTIFIER) @constructor) + +; fields +(FieldInit (IDENTIFIER) @property) + +; foo.bar.baz.function() calls +( + (SuffixOp + (IDENTIFIER) @function + ) + . + (FnCallArguments) +) + +; function() calls +( + ( + (IDENTIFIER) @function + ) + . + (FnCallArguments) +) + +; functionn decl +(FnProto + (IDENTIFIER) @function + (SuffixExpr (IDENTIFIER) @type)? + ("!")? @function.macro +) + +; function parameters and types +(ParamDecl + (IDENTIFIER) @variable.parameter + ":" + [ + (ParamType (SuffixExpr (IDENTIFIER) @type)) + (ParamType) + ] +) + +; switch +(SwitchItem + (SuffixExpr + "." + . + (IDENTIFIER) @constant + ) +) + +(INTEGER) @number + +(FLOAT) @number + +[ + (STRINGLITERAL) + (STRINGLITERALSINGLE) +] @string + +(CHAR_LITERAL) @string + +[ + "allowzero" + "volatile" + "anytype" + "anyframe" + (BuildinTypeExpr) +] @type.builtin + +(BreakLabel (IDENTIFIER) @label) +(BlockLabel (IDENTIFIER) @label) + +[ + "true" + "false" + "undefined" + "unreachable" + "null" +] @constant.builtin + +[ + "else" + "if" + "switch" + "for" + "while" + "return" + "break" + "continue" + "defer" + "errdefer" + "async" + "nosuspend" + "await" + "suspend" + "resume" + "try" + "catch" +] @keyword.control + +[ + "struct" + "enum" + "union" + "error" + "packed" + "opaque" + "test" + "usingnamespace" + "export" + "extern" + "const" + "var" + "comptime" + "threadlocal" +] @keyword + +[ + "pub" + "fn" +] @keyword.function + +; PrecProc +[ + "inline" + "noinline" + "asm" + "callconv" + "noalias" +] @attribute + +[ + (BUILTINIDENTIFIER) + "linksection" + "align" +] @function.builtin + +[ + (CompareOp) + (BitwiseOp) + (BitShiftOp) + (AdditionOp) + (MultiplyOp) + (PrefixOp) + "or" + "and" + "orelse" + "*" + "**" + "->" + "=>" + ".?" + ".*" + "=" +] @operator + +[ + ";" + "." + "," + ":" +] @punctuation.delimiter + +[ + ".." + "..." + "[" + "]" + "(" + ")" + "{" + "}" + (Payload "|") + (PtrPayload "|") + (PtrIndexPayload "|") +] @punctuation diff --git a/runtime/queries/zig/indents.toml b/runtime/queries/zig/indents.toml new file mode 100644 index 000000000..e119078b3 --- /dev/null +++ b/runtime/queries/zig/indents.toml @@ -0,0 +1,12 @@ +indent = [ + "block", + "match_block", + "arguments", + "parameters" +] + +outdent = [ + "}", + "]", + ")" +]