From 03bade90d2dfe4863c4dcb31224612894cb984fc Mon Sep 17 00:00:00 2001 From: Joseph Harrison-Lim Date: Sun, 13 Mar 2022 01:05:06 -0500 Subject: [PATCH 01/22] Add refresh-config and open-config command --- Cargo.lock | 2 + helix-term/Cargo.toml | 1 + helix-term/src/application.rs | 57 +++++++++++++++++++++---- helix-term/src/commands.rs | 26 ++++++------ helix-term/src/commands/typed.rs | 72 +++++++++++++++++++++++--------- helix-term/src/config.rs | 24 ++++++++++- helix-term/src/main.rs | 16 +------ helix-term/src/ui/editor.rs | 15 +++---- helix-term/src/ui/mod.rs | 5 ++- helix-view/Cargo.toml | 2 + helix-view/src/editor.rs | 30 ++++++++----- helix-view/src/gutter.rs | 2 +- 12 files changed, 173 insertions(+), 79 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0e9f9765adb3..d07fdeb724b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -435,6 +435,7 @@ name = "helix-term" version = "0.6.0" dependencies = [ "anyhow", + "arc-swap", "chrono", "content_inspector", "crossterm", @@ -483,6 +484,7 @@ name = "helix-view" version = "0.6.0" dependencies = [ "anyhow", + "arc-swap", "bitflags", "chardetng", "clipboard-win", diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index 48365743fa00..92393413532a 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -40,6 +40,7 @@ crossterm = { version = "0.23", features = ["event-stream"] } signal-hook = "0.3" tokio-stream = "0.1" futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false } +arc-swap = { version = "1.5.0" } # Logging fern = "0.6" diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 269ce13d15a8..5fa1f21a2c71 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -1,10 +1,11 @@ +use arc_swap::{access::Map, ArcSwap}; use helix_core::{ config::{default_syntax_loader, user_syntax_loader}, pos_at_coords, syntax, Selection, }; use helix_dap::{self as dap, Payload, Request}; use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap}; -use helix_view::{editor::Breakpoint, theme, Editor}; +use helix_view::{editor::{Breakpoint, ConfigEvent}, theme, Editor}; use serde_json::json; use crate::{ @@ -42,8 +43,7 @@ pub struct Application { compositor: Compositor, editor: Editor, - // TODO: share an ArcSwap with Editor? - config: Config, + config: Arc>, #[allow(dead_code)] theme_loader: Arc, @@ -56,7 +56,7 @@ pub struct Application { } impl Application { - pub fn new(args: Args, mut config: Config) -> Result { + pub fn new(args: Args, config: Config) -> Result { use helix_view::editor::Action; let mut compositor = Compositor::new()?; let size = compositor.size(); @@ -98,14 +98,16 @@ impl Application { }); let syn_loader = std::sync::Arc::new(syntax::Loader::new(syn_loader_conf)); + let config = Arc::new(ArcSwap::from_pointee(config)); let mut editor = Editor::new( size, theme_loader.clone(), syn_loader.clone(), - config.editor.clone(), + ArcSwap::from_pointee(config.load().editor.clone()) + // config.clone(), ); - let editor_view = Box::new(ui::EditorView::new(std::mem::take(&mut config.keys))); + let editor_view = Box::new(ui::EditorView::new(std::mem::take(&mut config.load().keys.clone()))); compositor.push(editor_view); if args.load_tutor { @@ -121,7 +123,7 @@ impl Application { if first.is_dir() { std::env::set_current_dir(&first)?; editor.new_file(Action::VerticalSplit); - let picker = ui::file_picker(".".into(), &config.editor); + let picker = ui::file_picker(".".into(), &config.load().editor); compositor.push(Box::new(overlayed(picker))); } else { let nr_of_files = args.files.len(); @@ -228,6 +230,10 @@ impl Application { Some(payload) = self.editor.debugger_events.next() => { self.handle_debugger_message(payload).await; } + Some(ConfigEvent) = self.editor.config_events.next() => { + self.refresh_config(); + self.render(); + } Some(callback) = self.jobs.futures.next() => { self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback); self.render(); @@ -245,6 +251,39 @@ impl Application { } } + pub fn refresh_config(&mut self) { + let config = Config::load(helix_loader::config_file()).unwrap(); + // Just an example to start; Some config properties like "theme" are a bit more involved and require a reload + if let Some(theme) = config.theme.clone() { + let true_color = self.true_color(); + self.editor.set_theme( + self.theme_loader + .load(&theme) + .map_err(|e| { + log::warn!("failed to load theme `{}` - {}", theme, e); + e + }) + .ok() + .filter(|theme| (true_color || theme.is_16_color())) + .unwrap_or_else(|| { + if true_color { + self.theme_loader.default() + } else { + self.theme_loader.base16_default() + } + }) + .clone(), + ); + } + self.config.store(Arc::new(config)); + // Is it possible to not do this manually? Presumably I've completely butchered using ArcSwap? + self.editor.config.store(Arc::new(self.config.load().editor.clone())); + } + + fn true_color(&self) -> bool { + self.config.load().editor.true_color || crate::true_color() + } + #[cfg(windows)] // no signal handling available on windows pub async fn handle_signals(&mut self, _signal: ()) {} @@ -700,7 +739,7 @@ impl Application { self.lsp_progress.update(server_id, token, work); } - if self.config.lsp.display_messages { + if self.config.load().lsp.display_messages { self.editor.set_status(status); } } @@ -809,7 +848,7 @@ impl Application { terminal::enable_raw_mode()?; let mut stdout = stdout(); execute!(stdout, terminal::EnterAlternateScreen)?; - if self.config.editor.mouse { + if self.config.load().editor.mouse { execute!(stdout, EnableMouseCapture)?; } Ok(()) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index b9401d40e01a..a9c3aaca5a52 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -848,7 +848,7 @@ fn goto_window(cx: &mut Context, align: Align) { // - 1 so we have at least one gap in the middle. // a height of 6 with padding of 3 on each side will keep shifting the view back and forth // as we type - let scrolloff = cx.editor.config.scrolloff.min(height.saturating_sub(1) / 2); + let scrolloff = cx.editor.config.load().scrolloff.min(height.saturating_sub(1) / 2); let last_line = view.last_line(doc); @@ -1290,7 +1290,7 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) { let height = view.inner_area().height; - let scrolloff = cx.editor.config.scrolloff.min(height as usize / 2); + let scrolloff = cx.editor.config.load().scrolloff.min(height as usize / 2); view.offset.row = match direction { Forward => view.offset.row + offset, @@ -1583,8 +1583,8 @@ fn rsearch(cx: &mut Context) { fn searcher(cx: &mut Context, direction: Direction) { let reg = cx.register.unwrap_or('/'); - let scrolloff = cx.editor.config.scrolloff; - let wrap_around = cx.editor.config.search.wrap_around; + let scrolloff = cx.editor.config.load().scrolloff; + let wrap_around = cx.editor.config.load().search.wrap_around; let doc = doc!(cx.editor); @@ -1627,13 +1627,13 @@ fn searcher(cx: &mut Context, direction: Direction) { } fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Direction) { - let scrolloff = cx.editor.config.scrolloff; + let scrolloff = cx.editor.config.load().scrolloff; let (view, doc) = current!(cx.editor); let registers = &cx.editor.registers; if let Some(query) = registers.read('/') { let query = query.last().unwrap(); let contents = doc.text().slice(..).to_string(); - let search_config = &cx.editor.config.search; + let search_config = &cx.editor.config.load().search; let case_insensitive = if search_config.smart_case { !query.chars().any(char::is_uppercase) } else { @@ -1693,8 +1693,8 @@ fn search_selection(cx: &mut Context) { fn global_search(cx: &mut Context) { let (all_matches_sx, all_matches_rx) = tokio::sync::mpsc::unbounded_channel::<(usize, PathBuf)>(); - let smart_case = cx.editor.config.search.smart_case; - let file_picker_config = cx.editor.config.file_picker.clone(); + let smart_case = cx.editor.config.load().search.smart_case; + let file_picker_config = cx.editor.config.load().file_picker.clone(); let completions = search_completions(cx, None); let prompt = ui::regex_prompt( @@ -2023,7 +2023,7 @@ fn append_mode(cx: &mut Context) { fn file_picker(cx: &mut Context) { // We don't specify language markers, root will be the root of the current git repo let root = find_root(None, &[]).unwrap_or_else(|| PathBuf::from("./")); - let picker = ui::file_picker(root, &cx.editor.config); + let picker = ui::file_picker(root, &cx.editor.config.load()); cx.push_layer(Box::new(overlayed(picker))); } @@ -2573,7 +2573,7 @@ pub mod insert { use helix_core::chars::char_is_word; let mut iter = text.chars_at(cursor); iter.reverse(); - for _ in 0..cx.editor.config.completion_trigger_len { + for _ in 0..cx.editor.config.load().completion_trigger_len { match iter.next() { Some(c) if char_is_word(c) => {} _ => return, @@ -4136,7 +4136,7 @@ fn shell_keep_pipe(cx: &mut Context) { Some('|'), ui::completers::none, move |cx: &mut compositor::Context, input: &str, event: PromptEvent| { - let shell = &cx.editor.config.shell; + let shell = &cx.editor.config.load().shell; if event != PromptEvent::Validate { return; } @@ -4232,7 +4232,7 @@ fn shell(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) { Some('|'), ui::completers::none, move |cx: &mut compositor::Context, input: &str, event: PromptEvent| { - let shell = &cx.editor.config.shell; + let shell = &cx.editor.config.load().shell; if event != PromptEvent::Validate { return; } @@ -4277,7 +4277,7 @@ fn shell(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) { // after replace cursor may be out of bounds, do this to // make sure cursor is in view and update scroll as well - view.ensure_cursor_in_view(doc, cx.editor.config.scrolloff); + view.ensure_cursor_in_view(doc, cx.editor.config.load().scrolloff); }, ); diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 3301d1486bd9..84fb3b7507a6 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1,6 +1,8 @@ +use std::{borrow::BorrowMut, sync::Arc}; + use super::*; -use helix_view::editor::Action; +use helix_view::editor::{Action, ConfigEvent}; use ui::completers::{self, Completer}; #[derive(Clone)] @@ -533,7 +535,7 @@ fn theme( .theme_loader .load(theme) .with_context(|| format!("Failed setting theme {}", theme))?; - let true_color = cx.editor.config.true_color || crate::true_color(); + let true_color = cx.editor.config.load().true_color || crate::true_color(); if !(true_color || theme.is_16_color()) { bail!("Unsupported theme: theme requires true color support"); } @@ -857,28 +859,28 @@ fn setting( args: &[Cow], _event: PromptEvent, ) -> anyhow::Result<()> { - let runtime_config = &mut cx.editor.config; - if args.len() != 2 { anyhow::bail!("Bad arguments. Usage: `:set key field`"); } - let (key, arg) = (&args[0].to_lowercase(), &args[1]); - match key.as_ref() { - "scrolloff" => runtime_config.scrolloff = arg.parse()?, - "scroll-lines" => runtime_config.scroll_lines = arg.parse()?, - "mouse" => runtime_config.mouse = arg.parse()?, - "line-number" => runtime_config.line_number = arg.parse()?, - "middle-click_paste" => runtime_config.middle_click_paste = arg.parse()?, - "auto-pairs" => runtime_config.auto_pairs = arg.parse()?, - "auto-completion" => runtime_config.auto_completion = arg.parse()?, - "completion-trigger-len" => runtime_config.completion_trigger_len = arg.parse()?, - "auto-info" => runtime_config.auto_info = arg.parse()?, - "true-color" => runtime_config.true_color = arg.parse()?, - "search.smart-case" => runtime_config.search.smart_case = arg.parse()?, - "search.wrap-around" => runtime_config.search.wrap_around = arg.parse()?, - _ => anyhow::bail!("Unknown key `{}`.", args[0]), + if let Ok(runtime_config) = &mut std::sync::Arc::try_unwrap(cx.editor.config.load().clone()) { + match key.as_ref() { + "scrolloff" => runtime_config.scrolloff = arg.parse()?, + "scroll-lines" => runtime_config.scroll_lines = arg.parse()?, + "mouse" => runtime_config.mouse = arg.parse()?, + "line-number" => runtime_config.line_number = arg.parse()?, + "middle-click_paste" => runtime_config.middle_click_paste = arg.parse()?, + "auto-pairs" => runtime_config.auto_pairs = arg.parse()?, + "auto-completion" => runtime_config.auto_completion = arg.parse()?, + "completion-trigger-len" => runtime_config.completion_trigger_len = arg.parse()?, + "auto-info" => runtime_config.auto_info = arg.parse()?, + "true-color" => runtime_config.true_color = arg.parse()?, + "search.smart-case" => runtime_config.search.smart_case = arg.parse()?, + "search.wrap-around" => runtime_config.search.wrap_around = arg.parse()?, + _ => anyhow::bail!("Unknown key `{}`.", args[0]), + } + cx.editor.config.store(Arc::new(runtime_config.clone())); } Ok(()) @@ -970,6 +972,24 @@ fn tree_sitter_subtree( Ok(()) } +fn open_config( + cx: &mut compositor::Context, + _args: &[Cow], + _event: PromptEvent, +) -> anyhow::Result<()> { + cx.editor.open(helix_loader::config_file(), Action::Replace)?; + Ok(()) +} + +fn refresh_config( + cx: &mut compositor::Context, + _args: &[Cow], + _event: PromptEvent, +) -> anyhow::Result<()> { + cx.editor.config_events.push(tokio_stream::once(ConfigEvent)); + Ok(()) +} + pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ TypableCommand { name: "quit", @@ -1342,6 +1362,20 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ fun: tree_sitter_subtree, completer: None, }, + TypableCommand { + name: "refresh-config", + aliases: &[], + doc: "Refreshes helix's config.", + fun: refresh_config, + completer: None, + }, + TypableCommand { + name: "open-config", + aliases: &[], + doc: "Open the helix config.toml file.", + fun: open_config, + completer: None, + }, ]; pub static TYPABLE_COMMAND_MAP: Lazy> = diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 6b8bbc1b8290..3d5560362789 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -1,6 +1,7 @@ +use crate::keymap::{merge_keys, Keymaps}; +use anyhow::{Error, Result}; use serde::Deserialize; - -use crate::keymap::Keymaps; +use std::path::PathBuf; #[derive(Debug, Default, Clone, PartialEq, Deserialize)] #[serde(deny_unknown_fields)] @@ -20,6 +21,25 @@ pub struct LspConfig { pub display_messages: bool, } +impl Config { + pub fn load(config_path: PathBuf) -> Result { + match std::fs::read_to_string(config_path) { + Ok(config) => Result::Ok(toml::from_str(&config) + .map(merge_keys) + .unwrap_or_else(|err| { + eprintln!("Bad config: {}", err); + eprintln!("Press to continue with default config"); + use std::io::Read; + // This waits for an enter press. + let _ = std::io::stdin().read(&mut []); + Config::default() + })), + Err(err) if err.kind() == std::io::ErrorKind::NotFound => Result::Ok(Config::default()), + Err(err) => return Err(Error::new(err)), + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index e554a21b935a..ca8bca72e940 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -2,7 +2,6 @@ use anyhow::{Context, Error, Result}; use helix_term::application::Application; use helix_term::args::Args; use helix_term::config::Config; -use helix_term::keymap::merge_keys; use std::path::PathBuf; fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> { @@ -118,20 +117,7 @@ FLAGS: std::fs::create_dir_all(&conf_dir).ok(); } - let config = match std::fs::read_to_string(helix_loader::config_file()) { - Ok(config) => toml::from_str(&config) - .map(merge_keys) - .unwrap_or_else(|err| { - eprintln!("Bad config: {}", err); - eprintln!("Press to continue with default config"); - use std::io::Read; - // This waits for an enter press. - let _ = std::io::stdin().read(&mut []); - Config::default() - }), - Err(err) if err.kind() == std::io::ErrorKind::NotFound => Config::default(), - Err(err) => return Err(Error::new(err)), - }; + let config = Config::load(helix_loader::config_file())?; setup_logging(logpath, args.verbosity).context("failed to initialize logging")?; diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 31a9bfc86b5e..d8d105960a2d 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -118,7 +118,7 @@ impl EditorView { let highlights: Box> = if is_focused { Box::new(syntax::merge( highlights, - Self::doc_selection_highlights(doc, view, theme, &editor.config.cursor_shape), + Self::doc_selection_highlights(doc, view, theme, &editor.config.load().cursor_shape), )) } else { Box::new(highlights) @@ -846,7 +846,7 @@ impl EditorView { pub fn handle_idle_timeout(&mut self, cx: &mut crate::compositor::Context) -> EventResult { if self.completion.is_some() - || !cx.editor.config.auto_completion + || !cx.editor.config.load().auto_completion || doc!(cx.editor).mode != Mode::Insert { return EventResult::Ignored(None); @@ -872,6 +872,7 @@ impl EditorView { event: MouseEvent, cxt: &mut commands::Context, ) -> EventResult { + let config = cxt.editor.config.load(); match event { MouseEvent { kind: MouseEventKind::Down(MouseButton::Left), @@ -972,7 +973,7 @@ impl EditorView { None => return EventResult::Ignored(None), } - let offset = cxt.editor.config.scroll_lines.abs() as usize; + let offset = config.scroll_lines.abs() as usize; commands::scroll(cxt, offset, direction); cxt.editor.tree.focus = current_view; @@ -984,7 +985,7 @@ impl EditorView { kind: MouseEventKind::Up(MouseButton::Left), .. } => { - if !cxt.editor.config.middle_click_paste { + if !config.middle_click_paste { return EventResult::Ignored(None); } @@ -1040,7 +1041,7 @@ impl EditorView { .. } => { let editor = &mut cxt.editor; - if !editor.config.middle_click_paste { + if !config.middle_click_paste { return EventResult::Ignored(None); } @@ -1166,7 +1167,7 @@ impl Component for EditorView { } let (view, doc) = current!(cx.editor); - view.ensure_cursor_in_view(doc, cx.editor.config.scrolloff); + view.ensure_cursor_in_view(doc, cx.editor.config.load().scrolloff); // Store a history state if not in insert mode. This also takes care of // commiting changes when leaving insert mode. @@ -1217,7 +1218,7 @@ impl Component for EditorView { self.render_view(cx.editor, doc, view, area, surface, is_focused); } - if cx.editor.config.auto_info { + if cx.editor.config.load().auto_info { if let Some(mut info) = cx.editor.autoinfo.take() { info.render(area, surface, cx); cx.editor.autoinfo = Some(info) diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 2273477ff437..bf687c188eff 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -37,6 +37,7 @@ pub fn regex_prompt( let doc_id = view.doc; let snapshot = doc.selection(view.id).clone(); let offset_snapshot = view.offset; + let config = cx.editor.config.load(); let mut prompt = Prompt::new( prompt, @@ -65,7 +66,7 @@ pub fn regex_prompt( return; } - let case_insensitive = if cx.editor.config.search.smart_case { + let case_insensitive = if config.search.smart_case { !input.chars().any(char::is_uppercase) } else { false @@ -84,7 +85,7 @@ pub fn regex_prompt( fun(view, doc, regex, event); - view.ensure_cursor_in_view(doc, cx.editor.config.scrolloff); + view.ensure_cursor_in_view(doc, config.scrolloff); } Err(_err) => (), // TODO: mark command line as error } diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml index a4fa256d7fbc..2616072ecfe0 100644 --- a/helix-view/Cargo.toml +++ b/helix-view/Cargo.toml @@ -25,6 +25,8 @@ crossterm = { version = "0.23", optional = true } once_cell = "1.10" url = "2" +arc-swap = { version = "1.5.0" } + tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] } tokio-stream = "0.1" futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 0eb613087d80..07d6c505e4af 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -13,7 +13,6 @@ use futures_util::future; use futures_util::stream::select_all::SelectAll; use tokio_stream::wrappers::UnboundedReceiverStream; -use log::debug; use std::{ borrow::Cow, collections::{BTreeMap, HashMap}, @@ -40,6 +39,8 @@ use helix_dap as dap; use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize}; +use arc_swap::{access::{DynAccess}, ArcSwap}; + fn deserialize_duration_millis<'de, D>(deserializer: D) -> Result where D: serde::Deserializer<'de>, @@ -271,6 +272,8 @@ pub struct Breakpoint { pub log_message: Option, } +pub trait DynAccessDebug: DynAccess + std::fmt::Debug {} + #[derive(Debug)] pub struct Editor { pub tree: Tree, @@ -295,7 +298,7 @@ pub struct Editor { pub status_msg: Option<(Cow<'static, str>, Severity)>, pub autoinfo: Option, - pub config: Config, + pub config: ArcSwap, pub auto_pairs: Option, pub idle_timer: Pin>, @@ -305,8 +308,13 @@ pub struct Editor { pub last_completion: Option, pub exit_code: i32, + + pub config_events: SelectAll>, } +#[derive(Debug)] +pub struct ConfigEvent; + #[derive(Debug, Clone)] pub struct CompleteAction { pub trigger_offset: usize, @@ -326,12 +334,11 @@ impl Editor { mut area: Rect, theme_loader: Arc, syn_loader: Arc, - config: Config, + config: ArcSwap, ) -> Self { let language_servers = helix_lsp::Registry::new(); - let auto_pairs = (&config.auto_pairs).into(); - - debug!("Editor config: {config:#?}"); + let conf = config.load(); + let auto_pairs = (&conf.auto_pairs).into(); // HAXX: offset the render area height by 1 to account for prompt/commandline area.height -= 1; @@ -354,13 +361,14 @@ impl Editor { clipboard_provider: get_clipboard_provider(), status_msg: None, autoinfo: None, - idle_timer: Box::pin(sleep(config.idle_timeout)), + idle_timer: Box::pin(sleep(conf.idle_timeout)), last_motion: None, last_completion: None, pseudo_pending: None, config, auto_pairs, exit_code: 0, + config_events: SelectAll::new(), } } @@ -374,7 +382,7 @@ impl Editor { pub fn reset_idle_timer(&mut self) { self.idle_timer .as_mut() - .reset(Instant::now() + self.config.idle_timeout); + .reset(Instant::now() + self.config.load().idle_timeout); } pub fn clear_status(&mut self) { @@ -452,7 +460,7 @@ impl Editor { fn _refresh(&mut self) { for (view, _) in self.tree.views_mut() { let doc = &self.documents[&view.doc]; - view.ensure_cursor_in_view(doc, self.config.scrolloff) + view.ensure_cursor_in_view(doc, self.config.load().scrolloff) } } @@ -702,7 +710,7 @@ impl Editor { pub fn ensure_cursor_in_view(&mut self, id: ViewId) { let view = self.tree.get_mut(id); let doc = &self.documents[&view.doc]; - view.ensure_cursor_in_view(doc, self.config.scrolloff) + view.ensure_cursor_in_view(doc, self.config.load().scrolloff) } #[inline] @@ -745,7 +753,7 @@ impl Editor { let inner = view.inner_area(); pos.col += inner.x as usize; pos.row += inner.y as usize; - let cursorkind = self.config.cursor_shape.from_mode(doc.mode()); + let cursorkind = self.config.load().cursor_shape.from_mode(doc.mode()); (Some(pos), cursorkind) } else { (None, CursorKind::default()) diff --git a/helix-view/src/gutter.rs b/helix-view/src/gutter.rs index 6a77c41fcb0c..6d2b0d2ba97a 100644 --- a/helix-view/src/gutter.rs +++ b/helix-view/src/gutter.rs @@ -60,7 +60,7 @@ pub fn line_number<'doc>( .text() .char_to_line(doc.selection(view.id).primary().cursor(text)); - let config = editor.config.line_number; + let config = editor.config.load().line_number; let mode = doc.mode; Box::new(move |line: usize, selected: bool, out: &mut String| { From 4a0eff1129f8e4b310e2f98de639d9566d26613c Mon Sep 17 00:00:00 2001 From: Joseph Harrison-Lim Date: Sun, 13 Mar 2022 01:11:32 -0500 Subject: [PATCH 02/22] clippy --- helix-term/src/application.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 5fa1f21a2c71..66083575cc32 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -271,8 +271,7 @@ impl Application { } else { self.theme_loader.base16_default() } - }) - .clone(), + }), ); } self.config.store(Arc::new(config)); From 209112bdbc1184f5a6038497208afb18f9730ef6 Mon Sep 17 00:00:00 2001 From: Joseph Harrison-Lim Date: Sun, 13 Mar 2022 13:36:42 -0400 Subject: [PATCH 03/22] Use dynamic dispatch for editor config --- helix-term/src/application.rs | 33 +++++++++++++++++------- helix-term/src/commands/typed.rs | 44 +++++++++++++++++--------------- helix-term/src/config.rs | 2 +- helix-term/src/main.rs | 2 +- helix-view/Cargo.toml | 4 +-- helix-view/src/editor.rs | 14 +++++----- 6 files changed, 58 insertions(+), 41 deletions(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 66083575cc32..77e41540933f 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -5,7 +5,10 @@ use helix_core::{ }; use helix_dap::{self as dap, Payload, Request}; use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap}; -use helix_view::{editor::{Breakpoint, ConfigEvent}, theme, Editor}; +use helix_view::{ + editor::{Breakpoint, ConfigEvent}, + theme, Editor, +}; use serde_json::json; use crate::{ @@ -103,11 +106,14 @@ impl Application { size, theme_loader.clone(), syn_loader.clone(), - ArcSwap::from_pointee(config.load().editor.clone()) - // config.clone(), + Box::new(Map::new(Arc::clone(&config), |config: &Config| { + &config.editor + })), ); - let editor_view = Box::new(ui::EditorView::new(std::mem::take(&mut config.load().keys.clone()))); + let editor_view = Box::new(ui::EditorView::new(std::mem::take( + &mut config.load().keys.clone(), + ))); compositor.push(editor_view); if args.load_tutor { @@ -230,8 +236,8 @@ impl Application { Some(payload) = self.editor.debugger_events.next() => { self.handle_debugger_message(payload).await; } - Some(ConfigEvent) = self.editor.config_events.next() => { - self.refresh_config(); + Some(config_event) = self.editor.config_events.next() => { + self.handle_config_events(config_event); self.render(); } Some(callback) = self.jobs.futures.next() => { @@ -251,7 +257,18 @@ impl Application { } } - pub fn refresh_config(&mut self) { + pub fn handle_config_events(&mut self, config_event: ConfigEvent) { + match config_event { + ConfigEvent::Refresh => { self.refresh_config() } + ConfigEvent::Update(editor_config) => { + let mut app_config = (*self.config.load().clone()).clone(); + app_config.editor = editor_config; + self.config.swap(Arc::new(app_config)); + } + } + } + + fn refresh_config(&mut self) { let config = Config::load(helix_loader::config_file()).unwrap(); // Just an example to start; Some config properties like "theme" are a bit more involved and require a reload if let Some(theme) = config.theme.clone() { @@ -275,8 +292,6 @@ impl Application { ); } self.config.store(Arc::new(config)); - // Is it possible to not do this manually? Presumably I've completely butchered using ArcSwap? - self.editor.config.store(Arc::new(self.config.load().editor.clone())); } fn true_color(&self) -> bool { diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 84fb3b7507a6..e20c46a9b3ca 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1,5 +1,3 @@ -use std::{borrow::BorrowMut, sync::Arc}; - use super::*; use helix_view::editor::{Action, ConfigEvent}; @@ -864,25 +862,26 @@ fn setting( } let (key, arg) = (&args[0].to_lowercase(), &args[1]); - if let Ok(runtime_config) = &mut std::sync::Arc::try_unwrap(cx.editor.config.load().clone()) { - match key.as_ref() { - "scrolloff" => runtime_config.scrolloff = arg.parse()?, - "scroll-lines" => runtime_config.scroll_lines = arg.parse()?, - "mouse" => runtime_config.mouse = arg.parse()?, - "line-number" => runtime_config.line_number = arg.parse()?, - "middle-click_paste" => runtime_config.middle_click_paste = arg.parse()?, - "auto-pairs" => runtime_config.auto_pairs = arg.parse()?, - "auto-completion" => runtime_config.auto_completion = arg.parse()?, - "completion-trigger-len" => runtime_config.completion_trigger_len = arg.parse()?, - "auto-info" => runtime_config.auto_info = arg.parse()?, - "true-color" => runtime_config.true_color = arg.parse()?, - "search.smart-case" => runtime_config.search.smart_case = arg.parse()?, - "search.wrap-around" => runtime_config.search.wrap_around = arg.parse()?, - _ => anyhow::bail!("Unknown key `{}`.", args[0]), - } - cx.editor.config.store(Arc::new(runtime_config.clone())); + let mut runtime_config = cx.editor.config.load().clone(); + match key.as_ref() { + "scrolloff" => runtime_config.scrolloff = arg.parse()?, + "scroll-lines" => runtime_config.scroll_lines = arg.parse()?, + "mouse" => runtime_config.mouse = arg.parse()?, + "line-number" => runtime_config.line_number = arg.parse()?, + "middle-click_paste" => runtime_config.middle_click_paste = arg.parse()?, + "auto-pairs" => runtime_config.auto_pairs = arg.parse()?, + "auto-completion" => runtime_config.auto_completion = arg.parse()?, + "completion-trigger-len" => runtime_config.completion_trigger_len = arg.parse()?, + "auto-info" => runtime_config.auto_info = arg.parse()?, + "true-color" => runtime_config.true_color = arg.parse()?, + "search.smart-case" => runtime_config.search.smart_case = arg.parse()?, + "search.wrap-around" => runtime_config.search.wrap_around = arg.parse()?, + _ => anyhow::bail!("Unknown key `{}`.", args[0]), } + cx.editor + .config_events + .push(tokio_stream::once(ConfigEvent::Update(runtime_config))); Ok(()) } @@ -977,7 +976,8 @@ fn open_config( _args: &[Cow], _event: PromptEvent, ) -> anyhow::Result<()> { - cx.editor.open(helix_loader::config_file(), Action::Replace)?; + cx.editor + .open(helix_loader::config_file(), Action::Replace)?; Ok(()) } @@ -986,7 +986,9 @@ fn refresh_config( _args: &[Cow], _event: PromptEvent, ) -> anyhow::Result<()> { - cx.editor.config_events.push(tokio_stream::once(ConfigEvent)); + cx.editor + .config_events + .push(tokio_stream::once(ConfigEvent::Refresh)); Ok(()) } diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 3d5560362789..dd89c3606fed 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -35,7 +35,7 @@ impl Config { Config::default() })), Err(err) if err.kind() == std::io::ErrorKind::NotFound => Result::Ok(Config::default()), - Err(err) => return Err(Error::new(err)), + Err(err) => Err(err.into()), } } } diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index ca8bca72e940..14da7d8b0e52 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -1,4 +1,4 @@ -use anyhow::{Context, Error, Result}; +use anyhow::{Context, Result}; use helix_term::application::Application; use helix_term::args::Args; use helix_term::config::Config; diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml index 2616072ecfe0..e54b02891fa5 100644 --- a/helix-view/Cargo.toml +++ b/helix-view/Cargo.toml @@ -17,8 +17,8 @@ term = ["crossterm"] bitflags = "1.3" anyhow = "1" helix-core = { version = "0.6", path = "../helix-core" } -helix-lsp = { version = "0.6", path = "../helix-lsp"} -helix-dap = { version = "0.6", path = "../helix-dap"} +helix-lsp = { version = "0.6", path = "../helix-lsp" } +helix-dap = { version = "0.6", path = "../helix-dap" } crossterm = { version = "0.23", optional = true } # Conversion traits diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 07d6c505e4af..76e97d010138 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -39,7 +39,7 @@ use helix_dap as dap; use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize}; -use arc_swap::{access::{DynAccess}, ArcSwap}; +use arc_swap::{access::{DynAccess, DynGuard}}; fn deserialize_duration_millis<'de, D>(deserializer: D) -> Result where @@ -272,9 +272,6 @@ pub struct Breakpoint { pub log_message: Option, } -pub trait DynAccessDebug: DynAccess + std::fmt::Debug {} - -#[derive(Debug)] pub struct Editor { pub tree: Tree, pub next_document_id: DocumentId, @@ -298,7 +295,7 @@ pub struct Editor { pub status_msg: Option<(Cow<'static, str>, Severity)>, pub autoinfo: Option, - pub config: ArcSwap, + pub config: Box>, pub auto_pairs: Option, pub idle_timer: Pin>, @@ -313,7 +310,10 @@ pub struct Editor { } #[derive(Debug)] -pub struct ConfigEvent; +pub enum ConfigEvent { + Refresh, + Update(Config), +} #[derive(Debug, Clone)] pub struct CompleteAction { @@ -334,7 +334,7 @@ impl Editor { mut area: Rect, theme_loader: Arc, syn_loader: Arc, - config: ArcSwap, + config: Box>, ) -> Self { let language_servers = helix_lsp::Registry::new(); let conf = config.load(); From f1b21af8bcff1b95fc1c6b5719ab5b8dcb501774 Mon Sep 17 00:00:00 2001 From: Joseph Harrison-Lim Date: Sun, 13 Mar 2022 13:38:31 -0400 Subject: [PATCH 04/22] Refactor Result::Ok to Ok --- helix-term/src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index dd89c3606fed..71d4b0255c21 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -24,7 +24,7 @@ pub struct LspConfig { impl Config { pub fn load(config_path: PathBuf) -> Result { match std::fs::read_to_string(config_path) { - Ok(config) => Result::Ok(toml::from_str(&config) + Ok(config) => Ok(toml::from_str(&config) .map(merge_keys) .unwrap_or_else(|err| { eprintln!("Bad config: {}", err); From 124ea239b4338b39f08fc109f63d3ba02020b546 Mon Sep 17 00:00:00 2001 From: Joseph Harrison-Lim Date: Sun, 13 Mar 2022 13:41:17 -0400 Subject: [PATCH 05/22] Remove unused import --- helix-view/src/editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 76e97d010138..df934c007f99 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -39,7 +39,7 @@ use helix_dap as dap; use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize}; -use arc_swap::{access::{DynAccess, DynGuard}}; +use arc_swap::{access::DynAccess}; fn deserialize_duration_millis<'de, D>(deserializer: D) -> Result where From 3676e2e5d93d63189f2eb5316a3249503c5738de Mon Sep 17 00:00:00 2001 From: Joseph Harrison-Lim Date: Sun, 13 Mar 2022 13:48:38 -0400 Subject: [PATCH 06/22] cargo fmt --- helix-term/src/application.rs | 2 +- helix-term/src/commands.rs | 7 ++++++- helix-term/src/ui/editor.rs | 7 ++++++- helix-view/src/editor.rs | 2 +- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 77e41540933f..f93a8656fe12 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -259,7 +259,7 @@ impl Application { pub fn handle_config_events(&mut self, config_event: ConfigEvent) { match config_event { - ConfigEvent::Refresh => { self.refresh_config() } + ConfigEvent::Refresh => self.refresh_config(), ConfigEvent::Update(editor_config) => { let mut app_config = (*self.config.load().clone()).clone(); app_config.editor = editor_config; diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index a9c3aaca5a52..024019fc04ac 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -848,7 +848,12 @@ fn goto_window(cx: &mut Context, align: Align) { // - 1 so we have at least one gap in the middle. // a height of 6 with padding of 3 on each side will keep shifting the view back and forth // as we type - let scrolloff = cx.editor.config.load().scrolloff.min(height.saturating_sub(1) / 2); + let scrolloff = cx + .editor + .config + .load() + .scrolloff + .min(height.saturating_sub(1) / 2); let last_line = view.last_line(doc); diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index d8d105960a2d..aede35eb4f12 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -118,7 +118,12 @@ impl EditorView { let highlights: Box> = if is_focused { Box::new(syntax::merge( highlights, - Self::doc_selection_highlights(doc, view, theme, &editor.config.load().cursor_shape), + Self::doc_selection_highlights( + doc, + view, + theme, + &editor.config.load().cursor_shape, + ), )) } else { Box::new(highlights) diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index df934c007f99..3d372966bc44 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -39,7 +39,7 @@ use helix_dap as dap; use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize}; -use arc_swap::{access::DynAccess}; +use arc_swap::access::DynAccess; fn deserialize_duration_millis<'de, D>(deserializer: D) -> Result where From 7b74c43ebb910410be20d026494b12504fa94932 Mon Sep 17 00:00:00 2001 From: Joseph Harrison-Lim Date: Sun, 13 Mar 2022 14:50:34 -0400 Subject: [PATCH 07/22] Modify config error handling --- helix-term/src/application.rs | 2 +- helix-term/src/config.rs | 29 ++++++++++++++++------------- helix-term/src/main.rs | 24 +++++++++++++++++++++--- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index f93a8656fe12..b4153b06204c 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -269,7 +269,7 @@ impl Application { } fn refresh_config(&mut self) { - let config = Config::load(helix_loader::config_file()).unwrap(); + let config = Config::load(helix_loader::config_file()).unwrap_or_default(); // Just an example to start; Some config properties like "theme" are a bit more involved and require a reload if let Some(theme) = config.theme.clone() { let true_color = self.true_color(); diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 71d4b0255c21..f665e72cbd21 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -1,7 +1,8 @@ use crate::keymap::{merge_keys, Keymaps}; -use anyhow::{Error, Result}; use serde::Deserialize; +use std::io::Error as IOError; use std::path::PathBuf; +use toml::de::Error as TomlError; #[derive(Debug, Default, Clone, PartialEq, Deserialize)] #[serde(deny_unknown_fields)] @@ -15,6 +16,12 @@ pub struct Config { pub editor: helix_view::editor::Config, } +#[derive(Debug)] +pub enum ConfigLoadError { + BadConfig(TomlError), + Error(IOError), +} + #[derive(Debug, Default, Clone, PartialEq, Deserialize)] #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub struct LspConfig { @@ -22,22 +29,18 @@ pub struct LspConfig { } impl Config { - pub fn load(config_path: PathBuf) -> Result { + pub fn load(config_path: PathBuf) -> Result { match std::fs::read_to_string(config_path) { - Ok(config) => Ok(toml::from_str(&config) + Ok(config) => toml::from_str(&config) .map(merge_keys) - .unwrap_or_else(|err| { - eprintln!("Bad config: {}", err); - eprintln!("Press to continue with default config"); - use std::io::Read; - // This waits for an enter press. - let _ = std::io::stdin().read(&mut []); - Config::default() - })), - Err(err) if err.kind() == std::io::ErrorKind::NotFound => Result::Ok(Config::default()), - Err(err) => Err(err.into()), + .map_err(ConfigLoadError::BadConfig), + Err(err) => Err(ConfigLoadError::Error(err)), } } + + pub fn load_default() -> Result { + Config::load(helix_loader::config_file()) + } } #[cfg(test)] diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index 14da7d8b0e52..75a02f852736 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -1,7 +1,7 @@ -use anyhow::{Context, Result}; +use anyhow::{Context, Result, Error}; use helix_term::application::Application; use helix_term::args::Args; -use helix_term::config::Config; +use helix_term::config::{Config, ConfigLoadError}; use std::path::PathBuf; fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> { @@ -117,7 +117,25 @@ FLAGS: std::fs::create_dir_all(&conf_dir).ok(); } - let config = Config::load(helix_loader::config_file())?; + let config = match Config::load_default() { + Ok(config) => config, + Err(err) => { + match err { + ConfigLoadError::BadConfig(err) => { + eprintln!("Bad config: {}", err); + eprintln!("Press to continue with default config"); + use std::io::Read; + // This waits for an enter press. + let _ = std::io::stdin().read(&mut []); + Config::default() + } + ConfigLoadError::Error(err) if err.kind() == std::io::ErrorKind::NotFound => { + Config::default() + } + ConfigLoadError::Error(err) => return Err(Error::new(err)), + } + } + }; setup_logging(logpath, args.verbosity).context("failed to initialize logging")?; From 6e4da9405d6ea59d2d914628be745d010dc3c823 Mon Sep 17 00:00:00 2001 From: Joseph Harrison-Lim Date: Sun, 13 Mar 2022 14:51:40 -0400 Subject: [PATCH 08/22] cargo xtask docgen --- book/src/generated/typable-cmd.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/book/src/generated/typable-cmd.md b/book/src/generated/typable-cmd.md index 370da21a9372..48db166ae5a7 100644 --- a/book/src/generated/typable-cmd.md +++ b/book/src/generated/typable-cmd.md @@ -53,3 +53,5 @@ | `:sort` | Sort ranges in selection. | | `:rsort` | Sort ranges in selection in reverse order. | | `:tree-sitter-subtree`, `:ts-subtree` | Display tree sitter subtree under cursor, primarily for debugging queries. | +| `:refresh-config` | Refreshes helix's config. | +| `:open-config` | Open the helix config.toml file. | From d7e552b51052cf3185df6d4a63440b01f85c37de Mon Sep 17 00:00:00 2001 From: Joseph Harrison-Lim Date: Sun, 13 Mar 2022 15:00:24 -0400 Subject: [PATCH 09/22] impl display for ConfigLoadError --- helix-term/src/application.rs | 8 +++++++- helix-term/src/config.rs | 10 ++++++++++ helix-term/src/main.rs | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index b4153b06204c..f759d591e748 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -22,6 +22,7 @@ use crate::{ use log::{error, warn}; use std::{ + fmt::Debug, io::{stdin, stdout, Write}, sync::Arc, time::{Duration, Instant}, @@ -269,7 +270,12 @@ impl Application { } fn refresh_config(&mut self) { - let config = Config::load(helix_loader::config_file()).unwrap_or_default(); + let config = Config::load(helix_loader::config_file()) + .map_err(|err| { + self.editor.set_error(err.to_string()); + Config::default() + }) + .unwrap(); // Just an example to start; Some config properties like "theme" are a bit more involved and require a reload if let Some(theme) = config.theme.clone() { let true_color = self.true_color(); diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index f665e72cbd21..94d5fc80d214 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -1,5 +1,6 @@ use crate::keymap::{merge_keys, Keymaps}; use serde::Deserialize; +use std::fmt::Display; use std::io::Error as IOError; use std::path::PathBuf; use toml::de::Error as TomlError; @@ -22,6 +23,15 @@ pub enum ConfigLoadError { Error(IOError), } +impl Display for ConfigLoadError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ConfigLoadError::BadConfig(err) => err.fmt(f), + ConfigLoadError::Error(err) => err.fmt(f), + } + } +} + #[derive(Debug, Default, Clone, PartialEq, Deserialize)] #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub struct LspConfig { diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index 75a02f852736..90ac6219e22c 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -1,4 +1,4 @@ -use anyhow::{Context, Result, Error}; +use anyhow::{Context, Error, Result}; use helix_term::application::Application; use helix_term::args::Args; use helix_term::config::{Config, ConfigLoadError}; From a41c21539b756957ee72ea7a9c05a332a4a4fa71 Mon Sep 17 00:00:00 2001 From: Joseph Harrison-Lim Date: Sun, 13 Mar 2022 15:12:20 -0400 Subject: [PATCH 10/22] cargo fmt --- helix-term/src/application.rs | 1 - helix-view/src/editor.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index f759d591e748..d891b5d988b0 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -22,7 +22,6 @@ use crate::{ use log::{error, warn}; use std::{ - fmt::Debug, io::{stdin, stdout, Write}, sync::Arc, time::{Duration, Instant}, diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 3d372966bc44..3774756eced9 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -309,7 +309,7 @@ pub struct Editor { pub config_events: SelectAll>, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum ConfigEvent { Refresh, Update(Config), From 06bad8cf492b9331d0a2d1e9242f3ad4e2c1cf79 Mon Sep 17 00:00:00 2001 From: Joseph Harrison-Lim Date: Thu, 17 Mar 2022 22:48:49 -0400 Subject: [PATCH 11/22] Put keymaps behind dyn access, refactor config.load() --- helix-term/src/application.rs | 10 +-- helix-term/src/commands.rs | 42 ++++++------ helix-term/src/commands/typed.rs | 4 +- helix-term/src/keymap.rs | 2 +- helix-term/src/ui/editor.rs | 112 ++++++++++++++++--------------- helix-term/src/ui/mod.rs | 2 +- helix-view/src/editor.rs | 18 +++-- helix-view/src/gutter.rs | 4 +- 8 files changed, 106 insertions(+), 88 deletions(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index d891b5d988b0..7ce133ac2383 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -111,9 +111,11 @@ impl Application { })), ); - let editor_view = Box::new(ui::EditorView::new(std::mem::take( - &mut config.load().keys.clone(), - ))); + let keymaps = Box::new(Map::new(Arc::clone(&config), |config: &Config| { + &config.keys + })); + + let editor_view = Box::new(ui::EditorView::new(keymaps)); compositor.push(editor_view); if args.load_tutor { @@ -263,7 +265,7 @@ impl Application { ConfigEvent::Update(editor_config) => { let mut app_config = (*self.config.load().clone()).clone(); app_config.editor = editor_config; - self.config.swap(Arc::new(app_config)); + self.config.store(Arc::new(app_config)); } } } diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 024019fc04ac..20870f2405c5 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -840,6 +840,7 @@ fn align_selections(cx: &mut Context) { fn goto_window(cx: &mut Context, align: Align) { let count = cx.count() - 1; + let config = cx.editor.config(); let (view, doc) = current!(cx.editor); let height = view.inner_area().height as usize; @@ -848,12 +849,7 @@ fn goto_window(cx: &mut Context, align: Align) { // - 1 so we have at least one gap in the middle. // a height of 6 with padding of 3 on each side will keep shifting the view back and forth // as we type - let scrolloff = cx - .editor - .config - .load() - .scrolloff - .min(height.saturating_sub(1) / 2); + let scrolloff = config.scrolloff.min(height.saturating_sub(1) / 2); let last_line = view.last_line(doc); @@ -1277,6 +1273,7 @@ fn switch_to_lowercase(cx: &mut Context) { pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) { use Direction::*; + let config = cx.editor.config(); let (view, doc) = current!(cx.editor); let range = doc.selection(view.id).primary(); @@ -1295,7 +1292,7 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) { let height = view.inner_area().height; - let scrolloff = cx.editor.config.load().scrolloff.min(height as usize / 2); + let scrolloff = config.scrolloff.min(height as usize / 2); view.offset.row = match direction { Forward => view.offset.row + offset, @@ -1587,9 +1584,10 @@ fn rsearch(cx: &mut Context) { } fn searcher(cx: &mut Context, direction: Direction) { + let config = cx.editor.config(); let reg = cx.register.unwrap_or('/'); - let scrolloff = cx.editor.config.load().scrolloff; - let wrap_around = cx.editor.config.load().search.wrap_around; + let scrolloff = config.scrolloff; + let wrap_around = config.search.wrap_around; let doc = doc!(cx.editor); @@ -1632,13 +1630,14 @@ fn searcher(cx: &mut Context, direction: Direction) { } fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Direction) { - let scrolloff = cx.editor.config.load().scrolloff; + let config = cx.editor.config(); + let scrolloff = config.scrolloff; let (view, doc) = current!(cx.editor); let registers = &cx.editor.registers; if let Some(query) = registers.read('/') { let query = query.last().unwrap(); let contents = doc.text().slice(..).to_string(); - let search_config = &cx.editor.config.load().search; + let search_config = &config.search; let case_insensitive = if search_config.smart_case { !query.chars().any(char::is_uppercase) } else { @@ -1698,8 +1697,9 @@ fn search_selection(cx: &mut Context) { fn global_search(cx: &mut Context) { let (all_matches_sx, all_matches_rx) = tokio::sync::mpsc::unbounded_channel::<(usize, PathBuf)>(); - let smart_case = cx.editor.config.load().search.smart_case; - let file_picker_config = cx.editor.config.load().file_picker.clone(); + let config = cx.editor.config(); + let smart_case = config.search.smart_case; + let file_picker_config = config.file_picker.clone(); let completions = search_completions(cx, None); let prompt = ui::regex_prompt( @@ -2028,7 +2028,7 @@ fn append_mode(cx: &mut Context) { fn file_picker(cx: &mut Context) { // We don't specify language markers, root will be the root of the current git repo let root = find_root(None, &[]).unwrap_or_else(|| PathBuf::from("./")); - let picker = ui::file_picker(root, &cx.editor.config.load()); + let picker = ui::file_picker(root, &cx.editor.config()); cx.push_layer(Box::new(overlayed(picker))); } @@ -2104,8 +2104,8 @@ pub fn command_palette(cx: &mut Context) { cx.callback = Some(Box::new( move |compositor: &mut Compositor, cx: &mut compositor::Context| { let doc = doc_mut!(cx.editor); - let keymap = - compositor.find::().unwrap().keymaps[&doc.mode].reverse_map(); + let keymap = compositor.find::().unwrap().keymaps()[&doc.mode] + .reverse_map(); let mut commands: Vec = MappableCommand::STATIC_COMMAND_LIST.into(); commands.extend(typed::TYPABLE_COMMAND_LIST.iter().map(|cmd| { @@ -2571,6 +2571,7 @@ pub mod insert { // It trigger completion when idle timer reaches deadline // Only trigger completion if the word under cursor is longer than n characters pub fn idle_completion(cx: &mut Context) { + let config = cx.editor.config(); let (view, doc) = current!(cx.editor); let text = doc.text().slice(..); let cursor = doc.selection(view.id).primary().cursor(text); @@ -2578,7 +2579,7 @@ pub mod insert { use helix_core::chars::char_is_word; let mut iter = text.chars_at(cursor); iter.reverse(); - for _ in 0..cx.editor.config.load().completion_trigger_len { + for _ in 0..config.completion_trigger_len { match iter.next() { Some(c) if char_is_word(c) => {} _ => return, @@ -4141,7 +4142,7 @@ fn shell_keep_pipe(cx: &mut Context) { Some('|'), ui::completers::none, move |cx: &mut compositor::Context, input: &str, event: PromptEvent| { - let shell = &cx.editor.config.load().shell; + let shell = &cx.editor.config().shell; if event != PromptEvent::Validate { return; } @@ -4237,7 +4238,8 @@ fn shell(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) { Some('|'), ui::completers::none, move |cx: &mut compositor::Context, input: &str, event: PromptEvent| { - let shell = &cx.editor.config.load().shell; + let config = cx.editor.config(); + let shell = &config.shell; if event != PromptEvent::Validate { return; } @@ -4282,7 +4284,7 @@ fn shell(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBehavior) { // after replace cursor may be out of bounds, do this to // make sure cursor is in view and update scroll as well - view.ensure_cursor_in_view(doc, cx.editor.config.load().scrolloff); + view.ensure_cursor_in_view(doc, config.scrolloff); }, ); diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index e20c46a9b3ca..251cd491b5de 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -533,7 +533,7 @@ fn theme( .theme_loader .load(theme) .with_context(|| format!("Failed setting theme {}", theme))?; - let true_color = cx.editor.config.load().true_color || crate::true_color(); + let true_color = cx.editor.config().true_color || crate::true_color(); if !(true_color || theme.is_16_color()) { bail!("Unsupported theme: theme requires true color support"); } @@ -862,7 +862,7 @@ fn setting( } let (key, arg) = (&args[0].to_lowercase(), &args[1]); - let mut runtime_config = cx.editor.config.load().clone(); + let mut runtime_config = cx.editor.config().clone(); match key.as_ref() { "scrolloff" => runtime_config.scrolloff = arg.parse()?, "scroll-lines" => runtime_config.scroll_lines = arg.parse()?, diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 1fe1f633385f..0ce6a0fd4522 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -315,7 +315,7 @@ pub enum KeymapResultKind { /// Returned after looking up a key in [`Keymap`]. The `sticky` field has a /// reference to the sticky node if one is currently active. -#[derive(Debug)] +#[derive(Debug, Clone, PartialEq)] pub struct KeymapResult<'a> { pub kind: KeymapResultKind, pub sticky: Option<&'a KeyTrieNode>, diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index aede35eb4f12..c099dc10e5e8 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -6,6 +6,7 @@ use crate::{ ui::{Completion, ProgressSpinners}, }; +use arc_swap::access::{DynAccess, DynGuard}; use helix_core::{ coords_at_pos, encoding, graphemes::{ @@ -31,7 +32,7 @@ use crossterm::event::{Event, MouseButton, MouseEvent, MouseEventKind}; use tui::buffer::Buffer as Surface; pub struct EditorView { - pub keymaps: Keymaps, + pub keymaps: Box>, on_next_key: Option>, last_insert: (commands::MappableCommand, Vec), pub(crate) completion: Option, @@ -45,14 +46,8 @@ pub enum InsertEvent { TriggerCompletion, } -impl Default for EditorView { - fn default() -> Self { - Self::new(Keymaps::default()) - } -} - impl EditorView { - pub fn new(keymaps: Keymaps) -> Self { + pub fn new(keymaps: Box>) -> Self { Self { keymaps, on_next_key: None, @@ -62,6 +57,10 @@ impl EditorView { } } + pub fn keymaps(&self) -> DynGuard { + self.keymaps.load() + } + pub fn spinners_mut(&mut self) -> &mut ProgressSpinners { &mut self.spinners } @@ -122,7 +121,7 @@ impl EditorView { doc, view, theme, - &editor.config.load().cursor_shape, + &editor.config().cursor_shape, ), )) } else { @@ -704,51 +703,50 @@ impl EditorView { /// otherwise. fn handle_keymap_event( &mut self, - mode: Mode, - cxt: &mut commands::Context, - event: KeyEvent, - ) -> Option { - cxt.editor.autoinfo = None; - let key_result = self.keymaps.get_mut(&mode).unwrap().get(event); - cxt.editor.autoinfo = key_result.sticky.map(|node| node.infobox()); + cx: &mut commands::Context, + key_result: &KeymapResult, + ) -> Option { + cx.editor.autoinfo = key_result.sticky.map(|node| node.infobox()); match &key_result.kind { - KeymapResultKind::Matched(command) => command.execute(cxt), - KeymapResultKind::Pending(node) => cxt.editor.autoinfo = Some(node.infobox()), + KeymapResultKind::Matched(command) => command.execute(cx), + KeymapResultKind::Pending(node) => cx.editor.autoinfo = Some(node.infobox()), KeymapResultKind::MatchedSequence(commands) => { for command in commands { - command.execute(cxt); + command.execute(cx); } } - KeymapResultKind::NotFound | KeymapResultKind::Cancelled(_) => return Some(key_result), + KeymapResultKind::NotFound => return Some(KeymapResultKind::NotFound), + KeymapResultKind::Cancelled(evs) => { + return Some(KeymapResultKind::Cancelled(evs.to_vec())) + } } None } fn insert_mode(&mut self, cx: &mut commands::Context, event: KeyEvent) { - if let Some(keyresult) = self.handle_keymap_event(Mode::Insert, cx, event) { - match keyresult.kind { - KeymapResultKind::NotFound => { - if let Some(ch) = event.char() { - commands::insert::insert_char(cx, ch) - } + let mut keymaps = self.keymaps().clone(); + let insert_keys = keymaps.get_mut(&Mode::Insert).unwrap(); + + match self.handle_keymap_event(cx, &insert_keys.get(event)) { + Some(KeymapResultKind::NotFound) => { + if let Some(ch) = event.char() { + commands::insert::insert_char(cx, ch) } - KeymapResultKind::Cancelled(pending) => { - for ev in pending { - match ev.char() { - Some(ch) => commands::insert::insert_char(cx, ch), - None => { - if let KeymapResultKind::Matched(command) = - self.keymaps.get_mut(&Mode::Insert).unwrap().get(ev).kind - { - command.execute(cx); - } + } + Some(KeymapResultKind::Cancelled(pending)) => { + for ev in pending { + match ev.char() { + Some(ch) => commands::insert::insert_char(cx, ch), + None => { + if let KeymapResultKind::Matched(command) = insert_keys.get(ev).kind { + command.execute(cx); } } } } - _ => unreachable!(), } + _ => unreachable!(), } } @@ -761,7 +759,7 @@ impl EditorView { std::num::NonZeroUsize::new(cxt.editor.count.map_or(i, |c| c.get() * 10 + i)); } // special handling for repeat operator - key!('.') if self.keymaps.pending().is_empty() => { + key!('.') if self.keymaps().pending().is_empty() => { // first execute whatever put us into insert mode self.last_insert.0.execute(cxt); // then replay the inputs @@ -803,9 +801,10 @@ impl EditorView { // set the register cxt.register = cxt.editor.selected_register.take(); - - self.handle_keymap_event(mode, cxt, event); - if self.keymaps.pending().is_empty() { + let mut keymaps = self.keymaps().clone(); + let key_result = keymaps.get_mut(&mode).unwrap().get(event); + self.handle_keymap_event(cxt, &key_result); + if keymaps.pending().is_empty() { cxt.editor.count = None } } @@ -851,7 +850,7 @@ impl EditorView { pub fn handle_idle_timeout(&mut self, cx: &mut crate::compositor::Context) -> EventResult { if self.completion.is_some() - || !cx.editor.config.load().auto_completion + || !cx.editor.config().auto_completion || doc!(cx.editor).mode != Mode::Insert { return EventResult::Ignored(None); @@ -877,7 +876,7 @@ impl EditorView { event: MouseEvent, cxt: &mut commands::Context, ) -> EventResult { - let config = cxt.editor.config.load(); + let config = cxt.editor.config(); match event { MouseEvent { kind: MouseEventKind::Down(MouseButton::Left), @@ -1170,9 +1169,9 @@ impl Component for EditorView { if cx.editor.should_close() { return EventResult::Ignored(None); } - + let config = cx.editor.config(); let (view, doc) = current!(cx.editor); - view.ensure_cursor_in_view(doc, cx.editor.config.load().scrolloff); + view.ensure_cursor_in_view(doc, config.scrolloff); // Store a history state if not in insert mode. This also takes care of // commiting changes when leaving insert mode. @@ -1189,12 +1188,19 @@ impl Component for EditorView { // how we entered insert mode is important, and we should track that so // we can repeat the side effect. - self.last_insert.0 = - match self.keymaps.get_mut(&mode).unwrap().get(key).kind { - KeymapResultKind::Matched(command) => command, - // FIXME: insert mode can only be entered through single KeyCodes - _ => unimplemented!(), - }; + self.last_insert.0 = match self + .keymaps + .load() + .clone() + .get_mut(&mode) + .unwrap() + .get(key) + .kind + { + KeymapResultKind::Matched(command) => command, + // FIXME: insert mode can only be entered through single KeyCodes + _ => unimplemented!(), + }; self.last_insert.1.clear(); } (Mode::Insert, Mode::Normal) => { @@ -1223,7 +1229,7 @@ impl Component for EditorView { self.render_view(cx.editor, doc, view, area, surface, is_focused); } - if cx.editor.config.load().auto_info { + if cx.editor.config().auto_info { if let Some(mut info) = cx.editor.autoinfo.take() { info.render(area, surface, cx); cx.editor.autoinfo = Some(info) @@ -1256,7 +1262,7 @@ impl Component for EditorView { if let Some(count) = cx.editor.count { disp.push_str(&count.to_string()) } - for key in self.keymaps.pending() { + for key in self.keymaps().pending() { let s = key.to_string(); if s.graphemes(true).count() > 1 { disp.push_str(&format!("<{}>", s)); diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index bf687c188eff..6242ea2eed4d 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -37,7 +37,7 @@ pub fn regex_prompt( let doc_id = view.doc; let snapshot = doc.selection(view.id).clone(); let offset_snapshot = view.offset; - let config = cx.editor.config.load(); + let config = cx.editor.config(); let mut prompt = Prompt::new( prompt, diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 3774756eced9..d447743054cf 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -40,7 +40,7 @@ use helix_dap as dap; use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize}; use arc_swap::access::DynAccess; - +use arc_swap::access::DynGuard; fn deserialize_duration_millis<'de, D>(deserializer: D) -> Result where D: serde::Deserializer<'de>, @@ -372,6 +372,10 @@ impl Editor { } } + pub fn config(&self) -> DynGuard { + self.config.load() + } + pub fn clear_idle_timer(&mut self) { // equivalent to internal Instant::far_future() (30 years) self.idle_timer @@ -380,9 +384,10 @@ impl Editor { } pub fn reset_idle_timer(&mut self) { + let config = self.config(); self.idle_timer .as_mut() - .reset(Instant::now() + self.config.load().idle_timeout); + .reset(Instant::now() + config.idle_timeout); } pub fn clear_status(&mut self) { @@ -458,9 +463,10 @@ impl Editor { } fn _refresh(&mut self) { + let config = self.config(); for (view, _) in self.tree.views_mut() { let doc = &self.documents[&view.doc]; - view.ensure_cursor_in_view(doc, self.config.load().scrolloff) + view.ensure_cursor_in_view(doc, config.scrolloff) } } @@ -708,9 +714,10 @@ impl Editor { } pub fn ensure_cursor_in_view(&mut self, id: ViewId) { + let config = self.config(); let view = self.tree.get_mut(id); let doc = &self.documents[&view.doc]; - view.ensure_cursor_in_view(doc, self.config.load().scrolloff) + view.ensure_cursor_in_view(doc, config.scrolloff) } #[inline] @@ -753,7 +760,8 @@ impl Editor { let inner = view.inner_area(); pos.col += inner.x as usize; pos.row += inner.y as usize; - let cursorkind = self.config.load().cursor_shape.from_mode(doc.mode()); + let config = self.config(); + let cursorkind = config.cursor_shape.from_mode(doc.mode()); (Some(pos), cursorkind) } else { (None, CursorKind::default()) diff --git a/helix-view/src/gutter.rs b/helix-view/src/gutter.rs index 6d2b0d2ba97a..7327ed1a2030 100644 --- a/helix-view/src/gutter.rs +++ b/helix-view/src/gutter.rs @@ -60,7 +60,7 @@ pub fn line_number<'doc>( .text() .char_to_line(doc.selection(view.id).primary().cursor(text)); - let config = editor.config.load().line_number; + let line_number = editor.config().line_number; let mode = doc.mode; Box::new(move |line: usize, selected: bool, out: &mut String| { @@ -70,7 +70,7 @@ pub fn line_number<'doc>( } else { use crate::{document::Mode, editor::LineNumber}; - let relative = config == LineNumber::Relative + let relative = line_number == LineNumber::Relative && mode != Mode::Insert && is_focused && current_line != line; From ea392015880edc08ffaecdb02d5574676b15007e Mon Sep 17 00:00:00 2001 From: Joseph Harrison-Lim Date: Thu, 17 Mar 2022 22:50:24 -0400 Subject: [PATCH 12/22] Update command names --- book/src/generated/typable-cmd.md | 4 ++-- helix-term/src/commands/typed.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/book/src/generated/typable-cmd.md b/book/src/generated/typable-cmd.md index 48db166ae5a7..f88ce2519070 100644 --- a/book/src/generated/typable-cmd.md +++ b/book/src/generated/typable-cmd.md @@ -53,5 +53,5 @@ | `:sort` | Sort ranges in selection. | | `:rsort` | Sort ranges in selection in reverse order. | | `:tree-sitter-subtree`, `:ts-subtree` | Display tree sitter subtree under cursor, primarily for debugging queries. | -| `:refresh-config` | Refreshes helix's config. | -| `:open-config` | Open the helix config.toml file. | +| `:config-reload` | Refreshes helix's config. | +| `:config-open` | Open the helix config.toml file. | diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 251cd491b5de..7506672333bf 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1365,14 +1365,14 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ completer: None, }, TypableCommand { - name: "refresh-config", + name: "config-reload", aliases: &[], doc: "Refreshes helix's config.", fun: refresh_config, completer: None, }, TypableCommand { - name: "open-config", + name: "config-open", aliases: &[], doc: "Open the helix config.toml file.", fun: open_config, From 0bf7beb429499951357e07ce611f33cb2cfc3462 Mon Sep 17 00:00:00 2001 From: Joe Date: Thu, 17 Mar 2022 22:51:48 -0400 Subject: [PATCH 13/22] Update helix-term/src/application.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Blaž Hrastnik --- helix-term/src/application.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 7ce133ac2383..08f0612cc343 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -272,11 +272,10 @@ impl Application { fn refresh_config(&mut self) { let config = Config::load(helix_loader::config_file()) - .map_err(|err| { + .unwrap_or_else(|err| { self.editor.set_error(err.to_string()); Config::default() - }) - .unwrap(); + }); // Just an example to start; Some config properties like "theme" are a bit more involved and require a reload if let Some(theme) = config.theme.clone() { let true_color = self.true_color(); From b459895f0df62a77539dbd2ba69144db0a0bd23e Mon Sep 17 00:00:00 2001 From: Joseph Harrison-Lim Date: Thu, 17 Mar 2022 23:22:25 -0400 Subject: [PATCH 14/22] Switch to unbounded_channel --- helix-term/src/application.rs | 18 +++++++++++------- helix-term/src/commands.rs | 4 ++-- helix-term/src/commands/typed.rs | 7 +++---- helix-term/src/ui/editor.rs | 8 ++------ helix-view/src/editor.rs | 9 ++++++--- 5 files changed, 24 insertions(+), 22 deletions(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 08f0612cc343..0708d56f575a 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -238,7 +238,7 @@ impl Application { Some(payload) = self.editor.debugger_events.next() => { self.handle_debugger_message(payload).await; } - Some(config_event) = self.editor.config_events.next() => { + Some(config_event) = self.editor.config_events.1.recv() => { self.handle_config_events(config_event); self.render(); } @@ -262,6 +262,10 @@ impl Application { pub fn handle_config_events(&mut self, config_event: ConfigEvent) { match config_event { ConfigEvent::Refresh => self.refresh_config(), + + // Since only the Application can make changes to Editor's config, + // the Editor must send up a new copy of a modified config so that + // the Application can apply it. ConfigEvent::Update(editor_config) => { let mut app_config = (*self.config.load().clone()).clone(); app_config.editor = editor_config; @@ -271,12 +275,12 @@ impl Application { } fn refresh_config(&mut self) { - let config = Config::load(helix_loader::config_file()) - .unwrap_or_else(|err| { - self.editor.set_error(err.to_string()); - Config::default() - }); - // Just an example to start; Some config properties like "theme" are a bit more involved and require a reload + let config = Config::load(helix_loader::config_file()).unwrap_or_else(|err| { + self.editor.set_error(err.to_string()); + Config::default() + }); + + // Refresh theme if let Some(theme) = config.theme.clone() { let true_color = self.true_color(); self.editor.set_theme( diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index d5f2e6dfa3ea..c168e5580666 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2109,8 +2109,8 @@ pub fn command_palette(cx: &mut Context) { cx.callback = Some(Box::new( move |compositor: &mut Compositor, cx: &mut compositor::Context| { let doc = doc_mut!(cx.editor); - let keymap = compositor.find::().unwrap().keymaps()[&doc.mode] - .reverse_map(); + let keymap = + compositor.find::().unwrap().keymaps()[&doc.mode].reverse_map(); let mut commands: Vec = MappableCommand::STATIC_COMMAND_LIST.into(); commands.extend(typed::TYPABLE_COMMAND_LIST.iter().map(|cmd| { diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 271a1214f8be..8b7f481b9c19 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -908,7 +908,8 @@ fn setting( cx.editor .config_events - .push(tokio_stream::once(ConfigEvent::Update(config))); + .0 + .send(ConfigEvent::Update(config))?; Ok(()) } @@ -1013,9 +1014,7 @@ fn refresh_config( _args: &[Cow], _event: PromptEvent, ) -> anyhow::Result<()> { - cx.editor - .config_events - .push(tokio_stream::once(ConfigEvent::Refresh)); + cx.editor.config_events.0.send(ConfigEvent::Refresh)?; Ok(()) } diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 7cab5f918888..f1c9aae7ab90 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -117,12 +117,7 @@ impl EditorView { let highlights: Box> = if is_focused { Box::new(syntax::merge( highlights, - Self::doc_selection_highlights( - doc, - view, - theme, - &editor.config().cursor_shape, - ), + Self::doc_selection_highlights(doc, view, theme, &editor.config().cursor_shape), )) } else { Box::new(highlights) @@ -745,6 +740,7 @@ impl EditorView { } } } + None => {} _ => unreachable!(), } } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 52c5937e69bc..88bdd93c0311 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -23,7 +23,10 @@ use std::{ sync::Arc, }; -use tokio::time::{sleep, Duration, Instant, Sleep}; +use tokio::{ + sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}, + time::{sleep, Duration, Instant, Sleep}, +}; use anyhow::{bail, Error}; @@ -322,7 +325,7 @@ pub struct Editor { pub exit_code: i32, - pub config_events: SelectAll>, + pub config_events: (UnboundedSender, UnboundedReceiver), } #[derive(Debug, Clone)] @@ -384,7 +387,7 @@ impl Editor { config, auto_pairs, exit_code: 0, - config_events: SelectAll::new(), + config_events: unbounded_channel(), } } From f930c77e0f876ec1ebe7723e49d52d1c6da5e6c6 Mon Sep 17 00:00:00 2001 From: Joseph Harrison-Lim Date: Thu, 17 Mar 2022 23:25:49 -0400 Subject: [PATCH 15/22] Remove --edit-config command --- helix-term/src/application.rs | 3 --- helix-term/src/args.rs | 2 -- helix-term/src/main.rs | 1 - 3 files changed, 6 deletions(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 0708d56f575a..0821214895df 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -123,9 +123,6 @@ impl Application { editor.open(path, Action::VerticalSplit)?; // Unset path to prevent accidentally saving to the original tutor file. doc_mut!(editor).set_path(None)?; - } else if args.edit_config { - let path = conf_dir.join("config.toml"); - editor.open(path, Action::VerticalSplit)?; } else if !args.files.is_empty() { let first = &args.files[0].0; // we know it's not empty if first.is_dir() { diff --git a/helix-term/src/args.rs b/helix-term/src/args.rs index e0f0af00f55d..b99c7d1a2768 100644 --- a/helix-term/src/args.rs +++ b/helix-term/src/args.rs @@ -13,7 +13,6 @@ pub struct Args { pub build_grammars: bool, pub verbosity: u64, pub files: Vec<(PathBuf, Position)>, - pub edit_config: bool, } impl Args { @@ -29,7 +28,6 @@ impl Args { "--version" => args.display_version = true, "--help" => args.display_help = true, "--tutor" => args.load_tutor = true, - "--edit-config" => args.edit_config = true, "--health" => { args.health = true; args.health_arg = argv.next_if(|opt| !opt.starts_with('-')); diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index 90ac6219e22c..0385d92c0dd5 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -59,7 +59,6 @@ ARGS: FLAGS: -h, --help Prints help information - --edit-config Opens the helix config file --tutor Loads the tutorial --health [LANG] Checks for potential errors in editor setup If given, checks for config errors in language LANG From e6a2a605495365b1ac10c1a4f94506de39b70689 Mon Sep 17 00:00:00 2001 From: Joseph Harrison-Lim Date: Thu, 17 Mar 2022 23:33:36 -0400 Subject: [PATCH 16/22] Update configuration docs --- book/src/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/configuration.md b/book/src/configuration.md index 2b29379eacda..f110edd7c800 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -5,7 +5,7 @@ To override global configuration parameters, create a `config.toml` file located * Linux and Mac: `~/.config/helix/config.toml` * Windows: `%AppData%\helix\config.toml` -> Note: You may use `hx --edit-config` to create and edit the `config.toml` file. +> Hint: You can easily open the config file by typing `:config-open` within Helix normal mode. Example config: From c5d724219823a5141e114b2bdd482099f76b29bf Mon Sep 17 00:00:00 2001 From: Joseph Harrison-Lim Date: Sat, 19 Mar 2022 18:04:31 -0400 Subject: [PATCH 17/22] Revert "Put keymaps behind dyn access", too hard This reverts commit 06bad8cf492b9331d0a2d1e9242f3ad4e2c1cf79. --- helix-term/src/application.rs | 10 ++-- helix-term/src/commands.rs | 4 +- helix-term/src/keymap.rs | 2 +- helix-term/src/ui/editor.rs | 105 ++++++++++++++++------------------ helix-view/src/editor.rs | 6 +- 5 files changed, 59 insertions(+), 68 deletions(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 0821214895df..19f08f855311 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -111,11 +111,9 @@ impl Application { })), ); - let keymaps = Box::new(Map::new(Arc::clone(&config), |config: &Config| { - &config.keys - })); - - let editor_view = Box::new(ui::EditorView::new(keymaps)); + let editor_view = Box::new(ui::EditorView::new(std::mem::take( + &mut config.load().keys.clone(), + ))); compositor.push(editor_view); if args.load_tutor { @@ -266,7 +264,7 @@ impl Application { ConfigEvent::Update(editor_config) => { let mut app_config = (*self.config.load().clone()).clone(); app_config.editor = editor_config; - self.config.store(Arc::new(app_config)); + self.config.swap(Arc::new(app_config)); } } } diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index c168e5580666..813b4b2ea640 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1586,8 +1586,8 @@ fn rsearch(cx: &mut Context) { } fn searcher(cx: &mut Context, direction: Direction) { - let config = cx.editor.config(); let reg = cx.register.unwrap_or('/'); + let config = cx.editor.config(); let scrolloff = config.scrolloff; let wrap_around = config.search.wrap_around; @@ -2110,7 +2110,7 @@ pub fn command_palette(cx: &mut Context) { move |compositor: &mut Compositor, cx: &mut compositor::Context| { let doc = doc_mut!(cx.editor); let keymap = - compositor.find::().unwrap().keymaps()[&doc.mode].reverse_map(); + compositor.find::().unwrap().keymaps[&doc.mode].reverse_map(); let mut commands: Vec = MappableCommand::STATIC_COMMAND_LIST.into(); commands.extend(typed::TYPABLE_COMMAND_LIST.iter().map(|cmd| { diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 24ec7facaed6..1059feaea473 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -315,7 +315,7 @@ pub enum KeymapResultKind { /// Returned after looking up a key in [`Keymap`]. The `sticky` field has a /// reference to the sticky node if one is currently active. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug)] pub struct KeymapResult<'a> { pub kind: KeymapResultKind, pub sticky: Option<&'a KeyTrieNode>, diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index f1c9aae7ab90..6b7455d70cee 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -6,7 +6,6 @@ use crate::{ ui::{Completion, ProgressSpinners}, }; -use arc_swap::access::{DynAccess, DynGuard}; use helix_core::{ coords_at_pos, encoding, graphemes::{ @@ -32,7 +31,7 @@ use crossterm::event::{Event, MouseButton, MouseEvent, MouseEventKind}; use tui::buffer::Buffer as Surface; pub struct EditorView { - pub keymaps: Box>, + pub keymaps: Keymaps, on_next_key: Option>, last_insert: (commands::MappableCommand, Vec), pub(crate) completion: Option, @@ -46,8 +45,14 @@ pub enum InsertEvent { TriggerCompletion, } +impl Default for EditorView { + fn default() -> Self { + Self::new(Keymaps::default()) + } +} + impl EditorView { - pub fn new(keymaps: Box>) -> Self { + pub fn new(keymaps: Keymaps) -> Self { Self { keymaps, on_next_key: None, @@ -57,10 +62,6 @@ impl EditorView { } } - pub fn keymaps(&self) -> DynGuard { - self.keymaps.load() - } - pub fn spinners_mut(&mut self) -> &mut ProgressSpinners { &mut self.spinners } @@ -697,51 +698,51 @@ impl EditorView { /// otherwise. fn handle_keymap_event( &mut self, - cx: &mut commands::Context, - key_result: &KeymapResult, - ) -> Option { - cx.editor.autoinfo = key_result.sticky.map(|node| node.infobox()); + mode: Mode, + cxt: &mut commands::Context, + event: KeyEvent, + ) -> Option { + cxt.editor.autoinfo = None; + let key_result = self.keymaps.get_mut(&mode).unwrap().get(event); + cxt.editor.autoinfo = key_result.sticky.map(|node| node.infobox()); match &key_result.kind { - KeymapResultKind::Matched(command) => command.execute(cx), - KeymapResultKind::Pending(node) => cx.editor.autoinfo = Some(node.infobox()), + KeymapResultKind::Matched(command) => command.execute(cxt), + KeymapResultKind::Pending(node) => cxt.editor.autoinfo = Some(node.infobox()), KeymapResultKind::MatchedSequence(commands) => { for command in commands { - command.execute(cx); + command.execute(cxt); } } - KeymapResultKind::NotFound => return Some(KeymapResultKind::NotFound), - KeymapResultKind::Cancelled(evs) => { - return Some(KeymapResultKind::Cancelled(evs.to_vec())) - } + KeymapResultKind::NotFound | KeymapResultKind::Cancelled(_) => return Some(key_result), } None } fn insert_mode(&mut self, cx: &mut commands::Context, event: KeyEvent) { - let mut keymaps = self.keymaps().clone(); - let insert_keys = keymaps.get_mut(&Mode::Insert).unwrap(); - - match self.handle_keymap_event(cx, &insert_keys.get(event)) { - Some(KeymapResultKind::NotFound) => { - if let Some(ch) = event.char() { - commands::insert::insert_char(cx, ch) + if let Some(keyresult) = self.handle_keymap_event(Mode::Insert, cx, event) { + match keyresult.kind { + KeymapResultKind::NotFound => { + if let Some(ch) = event.char() { + commands::insert::insert_char(cx, ch) + } } - } - Some(KeymapResultKind::Cancelled(pending)) => { - for ev in pending { - match ev.char() { - Some(ch) => commands::insert::insert_char(cx, ch), - None => { - if let KeymapResultKind::Matched(command) = insert_keys.get(ev).kind { - command.execute(cx); + KeymapResultKind::Cancelled(pending) => { + for ev in pending { + match ev.char() { + Some(ch) => commands::insert::insert_char(cx, ch), + None => { + if let KeymapResultKind::Matched(command) = + self.keymaps.get_mut(&Mode::Insert).unwrap().get(ev).kind + { + command.execute(cx); + } } } } } + _ => unreachable!(), } - None => {} - _ => unreachable!(), } } @@ -754,7 +755,7 @@ impl EditorView { std::num::NonZeroUsize::new(cxt.editor.count.map_or(i, |c| c.get() * 10 + i)); } // special handling for repeat operator - key!('.') if self.keymaps().pending().is_empty() => { + key!('.') if self.keymaps.pending().is_empty() => { // first execute whatever put us into insert mode self.last_insert.0.execute(cxt); // then replay the inputs @@ -796,10 +797,9 @@ impl EditorView { // set the register cxt.register = cxt.editor.selected_register.take(); - let mut keymaps = self.keymaps().clone(); - let key_result = keymaps.get_mut(&mode).unwrap().get(event); - self.handle_keymap_event(cxt, &key_result); - if keymaps.pending().is_empty() { + + self.handle_keymap_event(mode, cxt, event); + if self.keymaps.pending().is_empty() { cxt.editor.count = None } } @@ -1183,19 +1183,12 @@ impl Component for EditorView { // how we entered insert mode is important, and we should track that so // we can repeat the side effect. - self.last_insert.0 = match self - .keymaps - .load() - .clone() - .get_mut(&mode) - .unwrap() - .get(key) - .kind - { - KeymapResultKind::Matched(command) => command, - // FIXME: insert mode can only be entered through single KeyCodes - _ => unimplemented!(), - }; + self.last_insert.0 = + match self.keymaps.get_mut(&mode).unwrap().get(key).kind { + KeymapResultKind::Matched(command) => command, + // FIXME: insert mode can only be entered through single KeyCodes + _ => unimplemented!(), + }; self.last_insert.1.clear(); } (Mode::Insert, Mode::Normal) => { @@ -1215,7 +1208,7 @@ impl Component for EditorView { fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) { // clear with background color surface.set_style(area, cx.editor.theme.get("ui.background")); - + let config = cx.editor.config(); // if the terminal size suddenly changed, we need to trigger a resize cx.editor.resize(area.clip_bottom(1)); // -1 from bottom for commandline @@ -1224,7 +1217,7 @@ impl Component for EditorView { self.render_view(cx.editor, doc, view, area, surface, is_focused); } - if cx.editor.config().auto_info { + if config.auto_info { if let Some(mut info) = cx.editor.autoinfo.take() { info.render(area, surface, cx); cx.editor.autoinfo = Some(info) @@ -1257,7 +1250,7 @@ impl Component for EditorView { if let Some(count) = cx.editor.count { disp.push_str(&count.to_string()) } - for key in self.keymaps().pending() { + for key in self.keymaps.pending() { let s = key.to_string(); if s.graphemes(true).count() > 1 { disp.push_str(&format!("<{}>", s)); diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 88bdd93c0311..8220deb7f761 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -42,8 +42,8 @@ use helix_dap as dap; use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; -use arc_swap::access::DynAccess; -use arc_swap::access::DynGuard; +use arc_swap::access::{DynAccess, DynGuard}; + fn deserialize_duration_millis<'de, D>(deserializer: D) -> Result where D: serde::Deserializer<'de>, @@ -770,6 +770,7 @@ impl Editor { } pub fn cursor(&self) -> (Option, CursorKind) { + let config = self.config(); let (view, doc) = current_ref!(self); let cursor = doc .selection(view.id) @@ -779,7 +780,6 @@ impl Editor { let inner = view.inner_area(); pos.col += inner.x as usize; pos.row += inner.y as usize; - let config = self.config(); let cursorkind = config.cursor_shape.from_mode(doc.mode()); (Some(pos), cursorkind) } else { From 7df2f0cc42462c3456549408d1ff60694a2fef9f Mon Sep 17 00:00:00 2001 From: Joseph Harrison-Lim Date: Tue, 22 Mar 2022 22:37:55 -0400 Subject: [PATCH 18/22] Add refresh for keys --- helix-term/src/application.rs | 8 +- helix-term/src/commands.rs | 2 +- helix-term/src/config.rs | 10 +- helix-term/src/keymap.rs | 728 +++++++++++++++++----------------- helix-term/src/ui/editor.rs | 1 - 5 files changed, 384 insertions(+), 365 deletions(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 19f08f855311..948bbe44c433 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -17,6 +17,7 @@ use crate::{ compositor::Compositor, config::Config, job::Jobs, + keymap::Keymaps, ui::{self, overlay::overlayed}, }; @@ -111,9 +112,10 @@ impl Application { })), ); - let editor_view = Box::new(ui::EditorView::new(std::mem::take( - &mut config.load().keys.clone(), - ))); + let keys = Box::new(Map::new(Arc::clone(&config), |config: &Config| { + &config.keys + })); + let editor_view = Box::new(ui::EditorView::new(Keymaps::new(keys))); compositor.push(editor_view); if args.load_tutor { diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 3087429ce0ae..0b624f2569a5 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2110,7 +2110,7 @@ pub fn command_palette(cx: &mut Context) { move |compositor: &mut Compositor, cx: &mut compositor::Context| { let doc = doc_mut!(cx.editor); let keymap = - compositor.find::().unwrap().keymaps.map[&doc.mode].reverse_map(); + compositor.find::().unwrap().keymaps.map()[&doc.mode].reverse_map(); let mut commands: Vec = MappableCommand::STATIC_COMMAND_LIST.into(); commands.extend(typed::TYPABLE_COMMAND_LIST.iter().map(|cmd| { diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 5344ea891af1..f0205779cde2 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -1,5 +1,7 @@ -use crate::keymap::{merge_keys, Keymaps}; +use crate::keymap::{merge_keys, Keymap}; +use helix_view::document::Mode; use serde::Deserialize; +use std::collections::HashMap; use std::fmt::Display; use std::io::Error as IOError; use std::path::PathBuf; @@ -12,7 +14,7 @@ pub struct Config { #[serde(default)] pub lsp: LspConfig, #[serde(default)] - pub keys: Keymaps, + pub keys: HashMap, #[serde(default)] pub editor: helix_view::editor::Config, } @@ -76,7 +78,7 @@ mod tests { assert_eq!( toml::from_str::(sample_keymaps).unwrap(), Config { - keys: Keymaps::new(hashmap! { + keys: hashmap! { Mode::Insert => Keymap::new(keymap!({ "Insert mode" "y" => move_line_down, "S-C-a" => delete_selection, @@ -84,7 +86,7 @@ mod tests { Mode::Normal => Keymap::new(keymap!({ "Normal mode" "A-F12" => move_next_word_end, })), - }), + }, ..Default::default() } ); diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 992a0cb8ba85..9475a0a59575 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -1,5 +1,9 @@ pub use crate::commands::MappableCommand; use crate::config::Config; +use arc_swap::{ + access::{DynAccess, DynGuard}, + ArcSwap, +}; use helix_core::hashmap; use helix_view::{document::Mode, info::Info, input::KeyEvent}; use serde::Deserialize; @@ -7,6 +11,7 @@ use std::{ borrow::Cow, collections::{BTreeSet, HashMap}, ops::{Deref, DerefMut}, + sync::Arc, }; #[macro_export] @@ -381,23 +386,17 @@ impl Default for Keymap { } } -#[derive(Debug, Clone, PartialEq, Deserialize)] pub struct Keymaps { - #[serde(flatten)] - pub map: HashMap, - + pub map: Box>>, /// Stores pending keys waiting for the next key. This is relative to a /// sticky node if one is in use. - #[serde(skip)] state: Vec, - /// Stores the sticky node if one is activated. - #[serde(skip)] pub sticky: Option, } impl Keymaps { - pub fn new(map: HashMap) -> Self { + pub fn new(map: Box>>) -> Self { Self { map, state: Vec::new(), @@ -405,6 +404,10 @@ impl Keymaps { } } + pub fn map(&self) -> DynGuard> { + self.map.load() + } + /// Returns list of keys waiting to be disambiguated in current mode. pub fn pending(&self) -> &[KeyEvent] { &self.state @@ -419,7 +422,8 @@ impl Keymaps { /// sticky node is in use, it will be cleared. pub fn get(&mut self, mode: Mode, key: KeyEvent) -> KeymapResult { // TODO: remove the sticky part and look up manually - let keymap = &self.map[&mode]; + let keymaps = self.map().clone(); + let keymap = &keymaps[&mode]; if key!(Esc) == key { if !self.state.is_empty() { @@ -468,169 +472,222 @@ impl Keymaps { } } -impl Default for Keymaps { - fn default() -> Self { - let normal = keymap!({ "Normal mode" - "h" | "left" => move_char_left, - "j" | "down" => move_line_down, - "k" | "up" => move_line_up, - "l" | "right" => move_char_right, - - "t" => find_till_char, - "f" => find_next_char, - "T" => till_prev_char, - "F" => find_prev_char, - "r" => replace, - "R" => replace_with_yanked, - "A-." => repeat_last_motion, - - "~" => switch_case, - "`" => switch_to_lowercase, - "A-`" => switch_to_uppercase, - - "home" => goto_line_start, - "end" => goto_line_end, - - "w" => move_next_word_start, - "b" => move_prev_word_start, - "e" => move_next_word_end, - - "W" => move_next_long_word_start, - "B" => move_prev_long_word_start, - "E" => move_next_long_word_end, - - "v" => select_mode, - "G" => goto_line, - "g" => { "Goto" - "g" => goto_file_start, - "e" => goto_last_line, - "f" => goto_file, - "h" => goto_line_start, - "l" => goto_line_end, - "s" => goto_first_nonwhitespace, - "d" => goto_definition, - "y" => goto_type_definition, - "r" => goto_reference, - "i" => goto_implementation, - "t" => goto_window_top, - "c" => goto_window_center, - "b" => goto_window_bottom, - "a" => goto_last_accessed_file, - "m" => goto_last_modified_file, - "n" => goto_next_buffer, - "p" => goto_previous_buffer, - "." => goto_last_modification, - }, - ":" => command_mode, - - "i" => insert_mode, - "I" => prepend_to_line, - "a" => append_mode, - "A" => append_to_line, - "o" => open_below, - "O" => open_above, - - "d" => delete_selection, - "A-d" => delete_selection_noyank, - "c" => change_selection, - "A-c" => change_selection_noyank, - - "C" => copy_selection_on_next_line, - "A-C" => copy_selection_on_prev_line, - - - "s" => select_regex, - "A-s" => split_selection_on_newline, - "S" => split_selection, - ";" => collapse_selection, - "A-;" => flip_selections, - "A-k" | "A-up" => expand_selection, - "A-j" | "A-down" => shrink_selection, - "A-h" | "A-left" => select_prev_sibling, - "A-l" | "A-right" => select_next_sibling, - - "%" => select_all, - "x" => extend_line, - "X" => extend_to_line_bounds, - // crop_to_whole_line - - "m" => { "Match" - "m" => match_brackets, - "s" => surround_add, - "r" => surround_replace, - "d" => surround_delete, - "a" => select_textobject_around, - "i" => select_textobject_inner, - }, - "[" => { "Left bracket" - "d" => goto_prev_diag, - "D" => goto_first_diag, - "f" => goto_prev_function, - "c" => goto_prev_class, - "a" => goto_prev_parameter, - "o" => goto_prev_comment, - "space" => add_newline_above, +fn default_keymaps() -> HashMap { + let normal = keymap!({ "Normal mode" + "h" | "left" => move_char_left, + "j" | "down" => move_line_down, + "k" | "up" => move_line_up, + "l" | "right" => move_char_right, + + "t" => find_till_char, + "f" => find_next_char, + "T" => till_prev_char, + "F" => find_prev_char, + "r" => replace, + "R" => replace_with_yanked, + "A-." => repeat_last_motion, + + "~" => switch_case, + "`" => switch_to_lowercase, + "A-`" => switch_to_uppercase, + + "home" => goto_line_start, + "end" => goto_line_end, + + "w" => move_next_word_start, + "b" => move_prev_word_start, + "e" => move_next_word_end, + + "W" => move_next_long_word_start, + "B" => move_prev_long_word_start, + "E" => move_next_long_word_end, + + "v" => select_mode, + "G" => goto_line, + "g" => { "Goto" + "g" => goto_file_start, + "e" => goto_last_line, + "f" => goto_file, + "h" => goto_line_start, + "l" => goto_line_end, + "s" => goto_first_nonwhitespace, + "d" => goto_definition, + "y" => goto_type_definition, + "r" => goto_reference, + "i" => goto_implementation, + "t" => goto_window_top, + "c" => goto_window_center, + "b" => goto_window_bottom, + "a" => goto_last_accessed_file, + "m" => goto_last_modified_file, + "n" => goto_next_buffer, + "p" => goto_previous_buffer, + "." => goto_last_modification, + }, + ":" => command_mode, + + "i" => insert_mode, + "I" => prepend_to_line, + "a" => append_mode, + "A" => append_to_line, + "o" => open_below, + "O" => open_above, + + "d" => delete_selection, + "A-d" => delete_selection_noyank, + "c" => change_selection, + "A-c" => change_selection_noyank, + + "C" => copy_selection_on_next_line, + "A-C" => copy_selection_on_prev_line, + + + "s" => select_regex, + "A-s" => split_selection_on_newline, + "S" => split_selection, + ";" => collapse_selection, + "A-;" => flip_selections, + "A-k" | "A-up" => expand_selection, + "A-j" | "A-down" => shrink_selection, + "A-h" | "A-left" => select_prev_sibling, + "A-l" | "A-right" => select_next_sibling, + + "%" => select_all, + "x" => extend_line, + "X" => extend_to_line_bounds, + // crop_to_whole_line + + "m" => { "Match" + "m" => match_brackets, + "s" => surround_add, + "r" => surround_replace, + "d" => surround_delete, + "a" => select_textobject_around, + "i" => select_textobject_inner, + }, + "[" => { "Left bracket" + "d" => goto_prev_diag, + "D" => goto_first_diag, + "f" => goto_prev_function, + "c" => goto_prev_class, + "a" => goto_prev_parameter, + "o" => goto_prev_comment, + "space" => add_newline_above, + }, + "]" => { "Right bracket" + "d" => goto_next_diag, + "D" => goto_last_diag, + "f" => goto_next_function, + "c" => goto_next_class, + "a" => goto_next_parameter, + "o" => goto_next_comment, + "space" => add_newline_below, + }, + + "/" => search, + "?" => rsearch, + "n" => search_next, + "N" => search_prev, + "*" => search_selection, + + "u" => undo, + "U" => redo, + "A-u" => earlier, + "A-U" => later, + + "y" => yank, + // yank_all + "p" => paste_after, + // paste_all + "P" => paste_before, + + "Q" => record_macro, + "q" => replay_macro, + + ">" => indent, + "<" => unindent, + "=" => format_selections, + "J" => join_selections, + "K" => keep_selections, + "A-K" => remove_selections, + + "," => keep_primary_selection, + "A-," => remove_primary_selection, + + // "q" => record_macro, + // "Q" => replay_macro, + + "&" => align_selections, + "_" => trim_selections, + + "(" => rotate_selections_backward, + ")" => rotate_selections_forward, + "A-(" => rotate_selection_contents_backward, + "A-)" => rotate_selection_contents_forward, + + "A-:" => ensure_selections_forward, + + "esc" => normal_mode, + "C-b" | "pageup" => page_up, + "C-f" | "pagedown" => page_down, + "C-u" => half_page_up, + "C-d" => half_page_down, + + "C-w" => { "Window" + "C-w" | "w" => rotate_view, + "C-s" | "s" => hsplit, + "C-v" | "v" => vsplit, + "f" => goto_file_hsplit, + "F" => goto_file_vsplit, + "C-q" | "q" => wclose, + "C-o" | "o" => wonly, + "C-h" | "h" | "left" => jump_view_left, + "C-j" | "j" | "down" => jump_view_down, + "C-k" | "k" | "up" => jump_view_up, + "C-l" | "l" | "right" => jump_view_right, + "n" => { "New split scratch buffer" + "C-s" | "s" => hsplit_new, + "C-v" | "v" => vsplit_new, }, - "]" => { "Right bracket" - "d" => goto_next_diag, - "D" => goto_last_diag, - "f" => goto_next_function, - "c" => goto_next_class, - "a" => goto_next_parameter, - "o" => goto_next_comment, - "space" => add_newline_below, + }, + + // move under c + "C-c" => toggle_comments, + + // z family for save/restore/combine from/to sels from register + + "tab" => jump_forward, // tab == + "C-o" => jump_backward, + "C-s" => save_selection, + + "space" => { "Space" + "f" => file_picker, + "b" => buffer_picker, + "s" => symbol_picker, + "S" => workspace_symbol_picker, + "a" => code_action, + "'" => last_picker, + "d" => { "Debug (experimental)" sticky=true + "l" => dap_launch, + "b" => dap_toggle_breakpoint, + "c" => dap_continue, + "h" => dap_pause, + "i" => dap_step_in, + "o" => dap_step_out, + "n" => dap_next, + "v" => dap_variables, + "t" => dap_terminate, + "C-c" => dap_edit_condition, + "C-l" => dap_edit_log, + "s" => { "Switch" + "t" => dap_switch_thread, + "f" => dap_switch_stack_frame, + // sl, sb + }, + "e" => dap_enable_exceptions, + "E" => dap_disable_exceptions, }, - - "/" => search, - "?" => rsearch, - "n" => search_next, - "N" => search_prev, - "*" => search_selection, - - "u" => undo, - "U" => redo, - "A-u" => earlier, - "A-U" => later, - - "y" => yank, - // yank_all - "p" => paste_after, - // paste_all - "P" => paste_before, - - "Q" => record_macro, - "q" => replay_macro, - - ">" => indent, - "<" => unindent, - "=" => format_selections, - "J" => join_selections, - "K" => keep_selections, - "A-K" => remove_selections, - - "," => keep_primary_selection, - "A-," => remove_primary_selection, - - // "q" => record_macro, - // "Q" => replay_macro, - - "&" => align_selections, - "_" => trim_selections, - - "(" => rotate_selections_backward, - ")" => rotate_selections_forward, - "A-(" => rotate_selection_contents_backward, - "A-)" => rotate_selection_contents_forward, - - "A-:" => ensure_selections_forward, - - "esc" => normal_mode, - "C-b" | "pageup" => page_up, - "C-f" | "pagedown" => page_down, - "C-u" => half_page_up, - "C-d" => half_page_down, - - "C-w" => { "Window" + "w" => { "Window" "C-w" | "w" => rotate_view, "C-s" | "s" => hsplit, "C-v" | "v" => vsplit, @@ -647,194 +704,153 @@ impl Default for Keymaps { "C-v" | "v" => vsplit_new, }, }, + "y" => yank_joined_to_clipboard, + "Y" => yank_main_selection_to_clipboard, + "p" => paste_clipboard_after, + "P" => paste_clipboard_before, + "R" => replace_selections_with_clipboard, + "/" => global_search, + "k" => hover, + "r" => rename_symbol, + "?" => command_palette, + }, + "z" => { "View" + "z" | "c" => align_view_center, + "t" => align_view_top, + "b" => align_view_bottom, + "m" => align_view_middle, + "k" | "up" => scroll_up, + "j" | "down" => scroll_down, + "C-b" | "pageup" => page_up, + "C-f" | "pagedown" => page_down, + "C-u" => half_page_up, + "C-d" => half_page_down, + }, + "Z" => { "View" sticky=true + "z" | "c" => align_view_center, + "t" => align_view_top, + "b" => align_view_bottom, + "m" => align_view_middle, + "k" | "up" => scroll_up, + "j" | "down" => scroll_down, + "C-b" | "pageup" => page_up, + "C-f" | "pagedown" => page_down, + "C-u" => half_page_up, + "C-d" => half_page_down, + }, + + "\"" => select_register, + "|" => shell_pipe, + "A-|" => shell_pipe_to, + "!" => shell_insert_output, + "A-!" => shell_append_output, + "$" => shell_keep_pipe, + "C-z" => suspend, + + "C-a" => increment, + "C-x" => decrement, + }); + let mut select = normal.clone(); + select.merge_nodes(keymap!({ "Select mode" + "h" | "left" => extend_char_left, + "j" | "down" => extend_line_down, + "k" | "up" => extend_line_up, + "l" | "right" => extend_char_right, + + "w" => extend_next_word_start, + "b" => extend_prev_word_start, + "e" => extend_next_word_end, + "W" => extend_next_long_word_start, + "B" => extend_prev_long_word_start, + "E" => extend_next_long_word_end, + + "n" => extend_search_next, + "N" => extend_search_prev, + + "t" => extend_till_char, + "f" => extend_next_char, + "T" => extend_till_prev_char, + "F" => extend_prev_char, + + "home" => extend_to_line_start, + "end" => extend_to_line_end, + "esc" => exit_select_mode, + + "v" => normal_mode, + })); + let insert = keymap!({ "Insert mode" + "esc" => normal_mode, + + "backspace" => delete_char_backward, + "C-h" => delete_char_backward, + "del" => delete_char_forward, + "C-d" => delete_char_forward, + "ret" => insert_newline, + "C-j" => insert_newline, + "tab" => insert_tab, + "C-w" => delete_word_backward, + "A-backspace" => delete_word_backward, + "A-d" => delete_word_forward, + + "left" => move_char_left, + "C-b" => move_char_left, + "down" => move_line_down, + "C-n" => move_line_down, + "up" => move_line_up, + "C-p" => move_line_up, + "right" => move_char_right, + "C-f" => move_char_right, + "A-b" => move_prev_word_end, + "A-left" => move_prev_word_end, + "A-f" => move_next_word_start, + "A-right" => move_next_word_start, + "A-<" => goto_file_start, + "A->" => goto_file_end, + "pageup" => page_up, + "pagedown" => page_down, + "home" => goto_line_start, + "C-a" => goto_line_start, + "end" => goto_line_end_newline, + "C-e" => goto_line_end_newline, + + "C-k" => kill_to_line_end, + "C-u" => kill_to_line_start, + + "C-x" => completion, + "C-r" => insert_register, + }); + hashmap!( + Mode::Normal => Keymap::new(normal), + Mode::Select => Keymap::new(select), + Mode::Insert => Keymap::new(insert), + ) +} - // move under c - "C-c" => toggle_comments, - - // z family for save/restore/combine from/to sels from register - - "tab" => jump_forward, // tab == - "C-o" => jump_backward, - "C-s" => save_selection, - - "space" => { "Space" - "f" => file_picker, - "b" => buffer_picker, - "s" => symbol_picker, - "S" => workspace_symbol_picker, - "a" => code_action, - "'" => last_picker, - "d" => { "Debug (experimental)" sticky=true - "l" => dap_launch, - "b" => dap_toggle_breakpoint, - "c" => dap_continue, - "h" => dap_pause, - "i" => dap_step_in, - "o" => dap_step_out, - "n" => dap_next, - "v" => dap_variables, - "t" => dap_terminate, - "C-c" => dap_edit_condition, - "C-l" => dap_edit_log, - "s" => { "Switch" - "t" => dap_switch_thread, - "f" => dap_switch_stack_frame, - // sl, sb - }, - "e" => dap_enable_exceptions, - "E" => dap_disable_exceptions, - }, - "w" => { "Window" - "C-w" | "w" => rotate_view, - "C-s" | "s" => hsplit, - "C-v" | "v" => vsplit, - "f" => goto_file_hsplit, - "F" => goto_file_vsplit, - "C-q" | "q" => wclose, - "C-o" | "o" => wonly, - "C-h" | "h" | "left" => jump_view_left, - "C-j" | "j" | "down" => jump_view_down, - "C-k" | "k" | "up" => jump_view_up, - "C-l" | "l" | "right" => jump_view_right, - "n" => { "New split scratch buffer" - "C-s" | "s" => hsplit_new, - "C-v" | "v" => vsplit_new, - }, - }, - "y" => yank_joined_to_clipboard, - "Y" => yank_main_selection_to_clipboard, - "p" => paste_clipboard_after, - "P" => paste_clipboard_before, - "R" => replace_selections_with_clipboard, - "/" => global_search, - "k" => hover, - "r" => rename_symbol, - "?" => command_palette, - }, - "z" => { "View" - "z" | "c" => align_view_center, - "t" => align_view_top, - "b" => align_view_bottom, - "m" => align_view_middle, - "k" | "up" => scroll_up, - "j" | "down" => scroll_down, - "C-b" | "pageup" => page_up, - "C-f" | "pagedown" => page_down, - "C-u" => half_page_up, - "C-d" => half_page_down, - }, - "Z" => { "View" sticky=true - "z" | "c" => align_view_center, - "t" => align_view_top, - "b" => align_view_bottom, - "m" => align_view_middle, - "k" | "up" => scroll_up, - "j" | "down" => scroll_down, - "C-b" | "pageup" => page_up, - "C-f" | "pagedown" => page_down, - "C-u" => half_page_up, - "C-d" => half_page_down, - }, - - "\"" => select_register, - "|" => shell_pipe, - "A-|" => shell_pipe_to, - "!" => shell_insert_output, - "A-!" => shell_append_output, - "$" => shell_keep_pipe, - "C-z" => suspend, +impl Default for Keymaps { + fn default() -> Self { + Self::new(Box::new(ArcSwap::new(Arc::new(default_keymaps())))) + } +} - "C-a" => increment, - "C-x" => decrement, - }); - let mut select = normal.clone(); - select.merge_nodes(keymap!({ "Select mode" - "h" | "left" => extend_char_left, - "j" | "down" => extend_line_down, - "k" | "up" => extend_line_up, - "l" | "right" => extend_char_right, - - "w" => extend_next_word_start, - "b" => extend_prev_word_start, - "e" => extend_next_word_end, - "W" => extend_next_long_word_start, - "B" => extend_prev_long_word_start, - "E" => extend_next_long_word_end, - - "n" => extend_search_next, - "N" => extend_search_prev, - - "t" => extend_till_char, - "f" => extend_next_char, - "T" => extend_till_prev_char, - "F" => extend_prev_char, - - "home" => extend_to_line_start, - "end" => extend_to_line_end, - "esc" => exit_select_mode, - - "v" => normal_mode, - })); - let insert = keymap!({ "Insert mode" - "esc" => normal_mode, - - "backspace" => delete_char_backward, - "C-h" => delete_char_backward, - "del" => delete_char_forward, - "C-d" => delete_char_forward, - "ret" => insert_newline, - "C-j" => insert_newline, - "tab" => insert_tab, - "C-w" => delete_word_backward, - "A-backspace" => delete_word_backward, - "A-d" => delete_word_forward, - - "left" => move_char_left, - "C-b" => move_char_left, - "down" => move_line_down, - "C-n" => move_line_down, - "up" => move_line_up, - "C-p" => move_line_up, - "right" => move_char_right, - "C-f" => move_char_right, - "A-b" => move_prev_word_end, - "A-left" => move_prev_word_end, - "A-f" => move_next_word_start, - "A-right" => move_next_word_start, - "A-<" => goto_file_start, - "A->" => goto_file_end, - "pageup" => page_up, - "pagedown" => page_down, - "home" => goto_line_start, - "C-a" => goto_line_start, - "end" => goto_line_end_newline, - "C-e" => goto_line_end_newline, - - "C-k" => kill_to_line_end, - "C-u" => kill_to_line_start, - - "C-x" => completion, - "C-r" => insert_register, - }); - Self::new(hashmap!( - Mode::Normal => Keymap::new(normal), - Mode::Select => Keymap::new(select), - Mode::Insert => Keymap::new(insert), - )) +impl PartialEq for Keymaps { + fn eq(&self, other: &Self) -> bool { + *self.map() == *other.map() && self.state == other.state && self.sticky == other.sticky } } /// Merge default config keys with user overwritten keys for custom user config. pub fn merge_keys(mut config: Config) -> Config { - let mut delta = std::mem::take(&mut config.keys); - for (mode, keys) in &mut config.keys.map { - keys.merge(delta.map.remove(mode).unwrap_or_default()) + let mut delta = std::mem::replace(&mut config.keys, default_keymaps()); + for (mode, keys) in &mut config.keys { + keys.merge(delta.remove(mode).unwrap_or_default()) } config } #[cfg(test)] mod tests { + use arc_swap::access::Constant; + use super::*; #[test] @@ -855,7 +871,7 @@ mod tests { #[test] fn merge_partial_keys() { let config = Config { - keys: Keymaps::new(hashmap! { + keys: hashmap! { Mode::Normal => Keymap::new( keymap!({ "Normal mode" "i" => normal_mode, @@ -867,13 +883,13 @@ mod tests { }, }) ) - }), + }, ..Default::default() }; let mut merged_config = merge_keys(config.clone()); assert_ne!(config, merged_config); - let keymap = &mut merged_config.keys; + let mut keymap = Keymaps::new(Box::new(Constant(merged_config.keys.clone()))); assert_eq!( keymap.get(Mode::Normal, key!('i')), KeymapResult::Matched(MappableCommand::normal_mode), @@ -891,7 +907,7 @@ mod tests { "Leaf should replace node" ); - let keymap = merged_config.keys.map.get_mut(&Mode::Normal).unwrap(); + let keymap = merged_config.keys.get_mut(&Mode::Normal).unwrap(); // Assumes that `g` is a node in default keymap assert_eq!( keymap.root().search(&[key!('g'), key!('$')]).unwrap(), @@ -911,14 +927,14 @@ mod tests { "Old leaves in subnode should be present in merged node" ); - assert!(merged_config.keys.map.get(&Mode::Normal).unwrap().len() > 1); - assert!(merged_config.keys.map.get(&Mode::Insert).unwrap().len() > 0); + assert!(merged_config.keys.get(&Mode::Normal).unwrap().len() > 1); + assert!(merged_config.keys.get(&Mode::Insert).unwrap().len() > 0); } #[test] fn order_should_be_set() { let config = Config { - keys: Keymaps::new(hashmap! { + keys: hashmap! { Mode::Normal => Keymap::new( keymap!({ "Normal mode" "space" => { "" @@ -929,12 +945,12 @@ mod tests { }, }) ) - }), + }, ..Default::default() }; let mut merged_config = merge_keys(config.clone()); assert_ne!(config, merged_config); - let keymap = merged_config.keys.map.get_mut(&Mode::Normal).unwrap(); + let keymap = merged_config.keys.get_mut(&Mode::Normal).unwrap(); // Make sure mapping works assert_eq!( keymap @@ -951,8 +967,8 @@ mod tests { #[test] fn aliased_modes_are_same_in_default_keymap() { - let keymaps = Keymaps::default(); - let root = keymaps.map.get(&Mode::Normal).unwrap().root(); + let keymaps = Keymaps::default().map(); + let root = keymaps.get(&Mode::Normal).unwrap().root(); assert_eq!( root.search(&[key!(' '), key!('w')]).unwrap(), root.search(&["C-w".parse::().unwrap()]).unwrap(), diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index ef6a26fd877e..28665ec3b2e8 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -702,7 +702,6 @@ impl EditorView { cxt: &mut commands::Context, event: KeyEvent, ) -> Option { - cxt.editor.autoinfo = None; let key_result = self.keymaps.get(mode, event); cxt.editor.autoinfo = self.keymaps.sticky().map(|node| node.infobox()); From 681b8c82afff5e44cdb68a593734f41755ebe506 Mon Sep 17 00:00:00 2001 From: Joseph Harrison-Lim Date: Tue, 22 Mar 2022 23:24:44 -0400 Subject: [PATCH 19/22] Refactor default_keymaps, fix config default, add test --- helix-term/src/config.rs | 28 +- helix-term/src/keymap.rs | 489 +---------------------- helix-term/src/keymap/default_keymaps.rs | 359 +++++++++++++++++ helix-term/src/keymap/macros.rs | 127 ++++++ helix-term/src/lib.rs | 1 + 5 files changed, 522 insertions(+), 482 deletions(-) create mode 100644 helix-term/src/keymap/default_keymaps.rs create mode 100644 helix-term/src/keymap/macros.rs diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index f0205779cde2..d42834ec75b0 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -1,4 +1,4 @@ -use crate::keymap::{merge_keys, Keymap}; +use crate::keymap::{default_keymaps::default_keymaps, merge_keys, Keymap}; use helix_view::document::Mode; use serde::Deserialize; use std::collections::HashMap; @@ -7,18 +7,29 @@ use std::io::Error as IOError; use std::path::PathBuf; use toml::de::Error as TomlError; -#[derive(Debug, Default, Clone, PartialEq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(deny_unknown_fields)] pub struct Config { pub theme: Option, #[serde(default)] pub lsp: LspConfig, - #[serde(default)] + #[serde(default = "default_keymaps")] pub keys: HashMap, #[serde(default)] pub editor: helix_view::editor::Config, } +impl Default for Config { + fn default() -> Config { + Config { + theme: None, + lsp: LspConfig::default(), + keys: default_keymaps(), + editor: helix_view::editor::Config::default(), + } + } +} + #[derive(Debug)] pub enum ConfigLoadError { BadConfig(TomlError), @@ -91,4 +102,15 @@ mod tests { } ); } + + #[test] + fn keys_resolve_to_correct_defaults() { + // From serde default + let default_keys = toml::from_str::("").unwrap().keys; + assert_eq!(default_keys, default_keymaps()); + + // From the Default trait + let default_keys = Config::default().keys; + assert_eq!(default_keys, default_keymaps()); + } } diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 9475a0a59575..d119e4e16d2f 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -1,10 +1,12 @@ +pub mod default_keymaps; +pub mod macros; + pub use crate::commands::MappableCommand; use crate::config::Config; use arc_swap::{ access::{DynAccess, DynGuard}, ArcSwap, }; -use helix_core::hashmap; use helix_view::{document::Mode, info::Info, input::KeyEvent}; use serde::Deserialize; use std::{ @@ -14,127 +16,8 @@ use std::{ sync::Arc, }; -#[macro_export] -macro_rules! key { - ($key:ident) => { - ::helix_view::input::KeyEvent { - code: ::helix_view::keyboard::KeyCode::$key, - modifiers: ::helix_view::keyboard::KeyModifiers::NONE, - } - }; - ($($ch:tt)*) => { - ::helix_view::input::KeyEvent { - code: ::helix_view::keyboard::KeyCode::Char($($ch)*), - modifiers: ::helix_view::keyboard::KeyModifiers::NONE, - } - }; -} - -#[macro_export] -macro_rules! shift { - ($key:ident) => { - ::helix_view::input::KeyEvent { - code: ::helix_view::keyboard::KeyCode::$key, - modifiers: ::helix_view::keyboard::KeyModifiers::SHIFT, - } - }; - ($($ch:tt)*) => { - ::helix_view::input::KeyEvent { - code: ::helix_view::keyboard::KeyCode::Char($($ch)*), - modifiers: ::helix_view::keyboard::KeyModifiers::SHIFT, - } - }; -} - -#[macro_export] -macro_rules! ctrl { - ($key:ident) => { - ::helix_view::input::KeyEvent { - code: ::helix_view::keyboard::KeyCode::$key, - modifiers: ::helix_view::keyboard::KeyModifiers::CONTROL, - } - }; - ($($ch:tt)*) => { - ::helix_view::input::KeyEvent { - code: ::helix_view::keyboard::KeyCode::Char($($ch)*), - modifiers: ::helix_view::keyboard::KeyModifiers::CONTROL, - } - }; -} - -#[macro_export] -macro_rules! alt { - ($key:ident) => { - ::helix_view::input::KeyEvent { - code: ::helix_view::keyboard::KeyCode::$key, - modifiers: ::helix_view::keyboard::KeyModifiers::ALT, - } - }; - ($($ch:tt)*) => { - ::helix_view::input::KeyEvent { - code: ::helix_view::keyboard::KeyCode::Char($($ch)*), - modifiers: ::helix_view::keyboard::KeyModifiers::ALT, - } - }; -} - -/// Macro for defining the root of a `Keymap` object. Example: -/// -/// ``` -/// # use helix_core::hashmap; -/// # use helix_term::keymap; -/// # use helix_term::keymap::Keymap; -/// let normal_mode = keymap!({ "Normal mode" -/// "i" => insert_mode, -/// "g" => { "Goto" -/// "g" => goto_file_start, -/// "e" => goto_file_end, -/// }, -/// "j" | "down" => move_line_down, -/// }); -/// let keymap = Keymap::new(normal_mode); -/// ``` -#[macro_export] -macro_rules! keymap { - (@trie $cmd:ident) => { - $crate::keymap::KeyTrie::Leaf($crate::commands::MappableCommand::$cmd) - }; - - (@trie - { $label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ } - ) => { - keymap!({ $label $(sticky=$sticky)? $($($key)|+ => $value,)+ }) - }; - - (@trie [$($cmd:ident),* $(,)?]) => { - $crate::keymap::KeyTrie::Sequence(vec![$($crate::commands::Command::$cmd),*]) - }; - - ( - { $label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ } - ) => { - // modified from the hashmap! macro - { - let _cap = hashmap!(@count $($($key),+),*); - let mut _map = ::std::collections::HashMap::with_capacity(_cap); - let mut _order = ::std::vec::Vec::with_capacity(_cap); - $( - $( - let _key = $key.parse::<::helix_view::input::KeyEvent>().unwrap(); - let _duplicate = _map.insert( - _key, - keymap!(@trie $value) - ); - assert!(_duplicate.is_none(), "Duplicate key found: {:?}", _duplicate.unwrap()); - _order.push(_key); - )+ - )* - let mut _node = $crate::keymap::KeyTrieNode::new($label, _map, _order); - $( _node.is_sticky = $sticky; )? - $crate::keymap::KeyTrie::Node(_node) - } - }; -} +use default_keymaps::default_keymaps; +use macros::key; #[derive(Debug, Clone)] pub struct KeyTrieNode { @@ -472,360 +355,6 @@ impl Keymaps { } } -fn default_keymaps() -> HashMap { - let normal = keymap!({ "Normal mode" - "h" | "left" => move_char_left, - "j" | "down" => move_line_down, - "k" | "up" => move_line_up, - "l" | "right" => move_char_right, - - "t" => find_till_char, - "f" => find_next_char, - "T" => till_prev_char, - "F" => find_prev_char, - "r" => replace, - "R" => replace_with_yanked, - "A-." => repeat_last_motion, - - "~" => switch_case, - "`" => switch_to_lowercase, - "A-`" => switch_to_uppercase, - - "home" => goto_line_start, - "end" => goto_line_end, - - "w" => move_next_word_start, - "b" => move_prev_word_start, - "e" => move_next_word_end, - - "W" => move_next_long_word_start, - "B" => move_prev_long_word_start, - "E" => move_next_long_word_end, - - "v" => select_mode, - "G" => goto_line, - "g" => { "Goto" - "g" => goto_file_start, - "e" => goto_last_line, - "f" => goto_file, - "h" => goto_line_start, - "l" => goto_line_end, - "s" => goto_first_nonwhitespace, - "d" => goto_definition, - "y" => goto_type_definition, - "r" => goto_reference, - "i" => goto_implementation, - "t" => goto_window_top, - "c" => goto_window_center, - "b" => goto_window_bottom, - "a" => goto_last_accessed_file, - "m" => goto_last_modified_file, - "n" => goto_next_buffer, - "p" => goto_previous_buffer, - "." => goto_last_modification, - }, - ":" => command_mode, - - "i" => insert_mode, - "I" => prepend_to_line, - "a" => append_mode, - "A" => append_to_line, - "o" => open_below, - "O" => open_above, - - "d" => delete_selection, - "A-d" => delete_selection_noyank, - "c" => change_selection, - "A-c" => change_selection_noyank, - - "C" => copy_selection_on_next_line, - "A-C" => copy_selection_on_prev_line, - - - "s" => select_regex, - "A-s" => split_selection_on_newline, - "S" => split_selection, - ";" => collapse_selection, - "A-;" => flip_selections, - "A-k" | "A-up" => expand_selection, - "A-j" | "A-down" => shrink_selection, - "A-h" | "A-left" => select_prev_sibling, - "A-l" | "A-right" => select_next_sibling, - - "%" => select_all, - "x" => extend_line, - "X" => extend_to_line_bounds, - // crop_to_whole_line - - "m" => { "Match" - "m" => match_brackets, - "s" => surround_add, - "r" => surround_replace, - "d" => surround_delete, - "a" => select_textobject_around, - "i" => select_textobject_inner, - }, - "[" => { "Left bracket" - "d" => goto_prev_diag, - "D" => goto_first_diag, - "f" => goto_prev_function, - "c" => goto_prev_class, - "a" => goto_prev_parameter, - "o" => goto_prev_comment, - "space" => add_newline_above, - }, - "]" => { "Right bracket" - "d" => goto_next_diag, - "D" => goto_last_diag, - "f" => goto_next_function, - "c" => goto_next_class, - "a" => goto_next_parameter, - "o" => goto_next_comment, - "space" => add_newline_below, - }, - - "/" => search, - "?" => rsearch, - "n" => search_next, - "N" => search_prev, - "*" => search_selection, - - "u" => undo, - "U" => redo, - "A-u" => earlier, - "A-U" => later, - - "y" => yank, - // yank_all - "p" => paste_after, - // paste_all - "P" => paste_before, - - "Q" => record_macro, - "q" => replay_macro, - - ">" => indent, - "<" => unindent, - "=" => format_selections, - "J" => join_selections, - "K" => keep_selections, - "A-K" => remove_selections, - - "," => keep_primary_selection, - "A-," => remove_primary_selection, - - // "q" => record_macro, - // "Q" => replay_macro, - - "&" => align_selections, - "_" => trim_selections, - - "(" => rotate_selections_backward, - ")" => rotate_selections_forward, - "A-(" => rotate_selection_contents_backward, - "A-)" => rotate_selection_contents_forward, - - "A-:" => ensure_selections_forward, - - "esc" => normal_mode, - "C-b" | "pageup" => page_up, - "C-f" | "pagedown" => page_down, - "C-u" => half_page_up, - "C-d" => half_page_down, - - "C-w" => { "Window" - "C-w" | "w" => rotate_view, - "C-s" | "s" => hsplit, - "C-v" | "v" => vsplit, - "f" => goto_file_hsplit, - "F" => goto_file_vsplit, - "C-q" | "q" => wclose, - "C-o" | "o" => wonly, - "C-h" | "h" | "left" => jump_view_left, - "C-j" | "j" | "down" => jump_view_down, - "C-k" | "k" | "up" => jump_view_up, - "C-l" | "l" | "right" => jump_view_right, - "n" => { "New split scratch buffer" - "C-s" | "s" => hsplit_new, - "C-v" | "v" => vsplit_new, - }, - }, - - // move under c - "C-c" => toggle_comments, - - // z family for save/restore/combine from/to sels from register - - "tab" => jump_forward, // tab == - "C-o" => jump_backward, - "C-s" => save_selection, - - "space" => { "Space" - "f" => file_picker, - "b" => buffer_picker, - "s" => symbol_picker, - "S" => workspace_symbol_picker, - "a" => code_action, - "'" => last_picker, - "d" => { "Debug (experimental)" sticky=true - "l" => dap_launch, - "b" => dap_toggle_breakpoint, - "c" => dap_continue, - "h" => dap_pause, - "i" => dap_step_in, - "o" => dap_step_out, - "n" => dap_next, - "v" => dap_variables, - "t" => dap_terminate, - "C-c" => dap_edit_condition, - "C-l" => dap_edit_log, - "s" => { "Switch" - "t" => dap_switch_thread, - "f" => dap_switch_stack_frame, - // sl, sb - }, - "e" => dap_enable_exceptions, - "E" => dap_disable_exceptions, - }, - "w" => { "Window" - "C-w" | "w" => rotate_view, - "C-s" | "s" => hsplit, - "C-v" | "v" => vsplit, - "f" => goto_file_hsplit, - "F" => goto_file_vsplit, - "C-q" | "q" => wclose, - "C-o" | "o" => wonly, - "C-h" | "h" | "left" => jump_view_left, - "C-j" | "j" | "down" => jump_view_down, - "C-k" | "k" | "up" => jump_view_up, - "C-l" | "l" | "right" => jump_view_right, - "n" => { "New split scratch buffer" - "C-s" | "s" => hsplit_new, - "C-v" | "v" => vsplit_new, - }, - }, - "y" => yank_joined_to_clipboard, - "Y" => yank_main_selection_to_clipboard, - "p" => paste_clipboard_after, - "P" => paste_clipboard_before, - "R" => replace_selections_with_clipboard, - "/" => global_search, - "k" => hover, - "r" => rename_symbol, - "?" => command_palette, - }, - "z" => { "View" - "z" | "c" => align_view_center, - "t" => align_view_top, - "b" => align_view_bottom, - "m" => align_view_middle, - "k" | "up" => scroll_up, - "j" | "down" => scroll_down, - "C-b" | "pageup" => page_up, - "C-f" | "pagedown" => page_down, - "C-u" => half_page_up, - "C-d" => half_page_down, - }, - "Z" => { "View" sticky=true - "z" | "c" => align_view_center, - "t" => align_view_top, - "b" => align_view_bottom, - "m" => align_view_middle, - "k" | "up" => scroll_up, - "j" | "down" => scroll_down, - "C-b" | "pageup" => page_up, - "C-f" | "pagedown" => page_down, - "C-u" => half_page_up, - "C-d" => half_page_down, - }, - - "\"" => select_register, - "|" => shell_pipe, - "A-|" => shell_pipe_to, - "!" => shell_insert_output, - "A-!" => shell_append_output, - "$" => shell_keep_pipe, - "C-z" => suspend, - - "C-a" => increment, - "C-x" => decrement, - }); - let mut select = normal.clone(); - select.merge_nodes(keymap!({ "Select mode" - "h" | "left" => extend_char_left, - "j" | "down" => extend_line_down, - "k" | "up" => extend_line_up, - "l" | "right" => extend_char_right, - - "w" => extend_next_word_start, - "b" => extend_prev_word_start, - "e" => extend_next_word_end, - "W" => extend_next_long_word_start, - "B" => extend_prev_long_word_start, - "E" => extend_next_long_word_end, - - "n" => extend_search_next, - "N" => extend_search_prev, - - "t" => extend_till_char, - "f" => extend_next_char, - "T" => extend_till_prev_char, - "F" => extend_prev_char, - - "home" => extend_to_line_start, - "end" => extend_to_line_end, - "esc" => exit_select_mode, - - "v" => normal_mode, - })); - let insert = keymap!({ "Insert mode" - "esc" => normal_mode, - - "backspace" => delete_char_backward, - "C-h" => delete_char_backward, - "del" => delete_char_forward, - "C-d" => delete_char_forward, - "ret" => insert_newline, - "C-j" => insert_newline, - "tab" => insert_tab, - "C-w" => delete_word_backward, - "A-backspace" => delete_word_backward, - "A-d" => delete_word_forward, - - "left" => move_char_left, - "C-b" => move_char_left, - "down" => move_line_down, - "C-n" => move_line_down, - "up" => move_line_up, - "C-p" => move_line_up, - "right" => move_char_right, - "C-f" => move_char_right, - "A-b" => move_prev_word_end, - "A-left" => move_prev_word_end, - "A-f" => move_next_word_start, - "A-right" => move_next_word_start, - "A-<" => goto_file_start, - "A->" => goto_file_end, - "pageup" => page_up, - "pagedown" => page_down, - "home" => goto_line_start, - "C-a" => goto_line_start, - "end" => goto_line_end_newline, - "C-e" => goto_line_end_newline, - - "C-k" => kill_to_line_end, - "C-u" => kill_to_line_start, - - "C-x" => completion, - "C-r" => insert_register, - }); - hashmap!( - Mode::Normal => Keymap::new(normal), - Mode::Select => Keymap::new(select), - Mode::Insert => Keymap::new(insert), - ) -} - impl Default for Keymaps { fn default() -> Self { Self::new(Box::new(ArcSwap::new(Arc::new(default_keymaps())))) @@ -834,7 +363,8 @@ impl Default for Keymaps { impl PartialEq for Keymaps { fn eq(&self, other: &Self) -> bool { - *self.map() == *other.map() && self.state == other.state && self.sticky == other.sticky + let a = *self.map() == *other.map(); + a && self.state == other.state && self.sticky == other.sticky } } @@ -849,9 +379,10 @@ pub fn merge_keys(mut config: Config) -> Config { #[cfg(test)] mod tests { - use arc_swap::access::Constant; - + use super::macros::keymap; use super::*; + use arc_swap::access::Constant; + use helix_core::hashmap; #[test] #[should_panic] diff --git a/helix-term/src/keymap/default_keymaps.rs b/helix-term/src/keymap/default_keymaps.rs new file mode 100644 index 000000000000..cef7b146237f --- /dev/null +++ b/helix-term/src/keymap/default_keymaps.rs @@ -0,0 +1,359 @@ +use std::collections::HashMap; + +use helix_core::hashmap; +use super::macros::keymap; +use super::{Mode, Keymap}; + +pub fn default_keymaps() -> HashMap { + let normal = keymap!({ "Normal mode" + "h" | "left" => move_char_left, + "j" | "down" => move_line_down, + "k" | "up" => move_line_up, + "l" | "right" => move_char_right, + + "t" => find_till_char, + "f" => find_next_char, + "T" => till_prev_char, + "F" => find_prev_char, + "r" => replace, + "R" => replace_with_yanked, + "A-." => repeat_last_motion, + + "~" => switch_case, + "`" => switch_to_lowercase, + "A-`" => switch_to_uppercase, + + "home" => goto_line_start, + "end" => goto_line_end, + + "w" => move_next_word_start, + "b" => move_prev_word_start, + "e" => move_next_word_end, + + "W" => move_next_long_word_start, + "B" => move_prev_long_word_start, + "E" => move_next_long_word_end, + + "v" => select_mode, + "G" => goto_line, + "g" => { "Goto" + "g" => goto_file_start, + "e" => goto_last_line, + "f" => goto_file, + "h" => goto_line_start, + "l" => goto_line_end, + "s" => goto_first_nonwhitespace, + "d" => goto_definition, + "y" => goto_type_definition, + "r" => goto_reference, + "i" => goto_implementation, + "t" => goto_window_top, + "c" => goto_window_center, + "b" => goto_window_bottom, + "a" => goto_last_accessed_file, + "m" => goto_last_modified_file, + "n" => goto_next_buffer, + "p" => goto_previous_buffer, + "." => goto_last_modification, + }, + ":" => command_mode, + + "i" => insert_mode, + "I" => prepend_to_line, + "a" => append_mode, + "A" => append_to_line, + "o" => open_below, + "O" => open_above, + + "d" => delete_selection, + "A-d" => delete_selection_noyank, + "c" => change_selection, + "A-c" => change_selection_noyank, + + "C" => copy_selection_on_next_line, + "A-C" => copy_selection_on_prev_line, + + + "s" => select_regex, + "A-s" => split_selection_on_newline, + "S" => split_selection, + ";" => collapse_selection, + "A-;" => flip_selections, + "A-k" | "A-up" => expand_selection, + "A-j" | "A-down" => shrink_selection, + "A-h" | "A-left" => select_prev_sibling, + "A-l" | "A-right" => select_next_sibling, + + "%" => select_all, + "x" => extend_line, + "X" => extend_to_line_bounds, + // crop_to_whole_line + + "m" => { "Match" + "m" => match_brackets, + "s" => surround_add, + "r" => surround_replace, + "d" => surround_delete, + "a" => select_textobject_around, + "i" => select_textobject_inner, + }, + "[" => { "Left bracket" + "d" => goto_prev_diag, + "D" => goto_first_diag, + "f" => goto_prev_function, + "c" => goto_prev_class, + "a" => goto_prev_parameter, + "o" => goto_prev_comment, + "space" => add_newline_above, + }, + "]" => { "Right bracket" + "d" => goto_next_diag, + "D" => goto_last_diag, + "f" => goto_next_function, + "c" => goto_next_class, + "a" => goto_next_parameter, + "o" => goto_next_comment, + "space" => add_newline_below, + }, + + "/" => search, + "?" => rsearch, + "n" => search_next, + "N" => search_prev, + "*" => search_selection, + + "u" => undo, + "U" => redo, + "A-u" => earlier, + "A-U" => later, + + "y" => yank, + // yank_all + "p" => paste_after, + // paste_all + "P" => paste_before, + + "Q" => record_macro, + "q" => replay_macro, + + ">" => indent, + "<" => unindent, + "=" => format_selections, + "J" => join_selections, + "K" => keep_selections, + "A-K" => remove_selections, + + "," => keep_primary_selection, + "A-," => remove_primary_selection, + + // "q" => record_macro, + // "Q" => replay_macro, + + "&" => align_selections, + "_" => trim_selections, + + "(" => rotate_selections_backward, + ")" => rotate_selections_forward, + "A-(" => rotate_selection_contents_backward, + "A-)" => rotate_selection_contents_forward, + + "A-:" => ensure_selections_forward, + + "esc" => normal_mode, + "C-b" | "pageup" => page_up, + "C-f" | "pagedown" => page_down, + "C-u" => half_page_up, + "C-d" => half_page_down, + + "C-w" => { "Window" + "C-w" | "w" => rotate_view, + "C-s" | "s" => hsplit, + "C-v" | "v" => vsplit, + "f" => goto_file_hsplit, + "F" => goto_file_vsplit, + "C-q" | "q" => wclose, + "C-o" | "o" => wonly, + "C-h" | "h" | "left" => jump_view_left, + "C-j" | "j" | "down" => jump_view_down, + "C-k" | "k" | "up" => jump_view_up, + "C-l" | "l" | "right" => jump_view_right, + "n" => { "New split scratch buffer" + "C-s" | "s" => hsplit_new, + "C-v" | "v" => vsplit_new, + }, + }, + + // move under c + "C-c" => toggle_comments, + + // z family for save/restore/combine from/to sels from register + + "tab" => jump_forward, // tab == + "C-o" => jump_backward, + "C-s" => save_selection, + + "space" => { "Space" + "f" => file_picker, + "b" => buffer_picker, + "s" => symbol_picker, + "S" => workspace_symbol_picker, + "a" => code_action, + "'" => last_picker, + "d" => { "Debug (experimental)" sticky=true + "l" => dap_launch, + "b" => dap_toggle_breakpoint, + "c" => dap_continue, + "h" => dap_pause, + "i" => dap_step_in, + "o" => dap_step_out, + "n" => dap_next, + "v" => dap_variables, + "t" => dap_terminate, + "C-c" => dap_edit_condition, + "C-l" => dap_edit_log, + "s" => { "Switch" + "t" => dap_switch_thread, + "f" => dap_switch_stack_frame, + // sl, sb + }, + "e" => dap_enable_exceptions, + "E" => dap_disable_exceptions, + }, + "w" => { "Window" + "C-w" | "w" => rotate_view, + "C-s" | "s" => hsplit, + "C-v" | "v" => vsplit, + "f" => goto_file_hsplit, + "F" => goto_file_vsplit, + "C-q" | "q" => wclose, + "C-o" | "o" => wonly, + "C-h" | "h" | "left" => jump_view_left, + "C-j" | "j" | "down" => jump_view_down, + "C-k" | "k" | "up" => jump_view_up, + "C-l" | "l" | "right" => jump_view_right, + "n" => { "New split scratch buffer" + "C-s" | "s" => hsplit_new, + "C-v" | "v" => vsplit_new, + }, + }, + "y" => yank_joined_to_clipboard, + "Y" => yank_main_selection_to_clipboard, + "p" => paste_clipboard_after, + "P" => paste_clipboard_before, + "R" => replace_selections_with_clipboard, + "/" => global_search, + "k" => hover, + "r" => rename_symbol, + "?" => command_palette, + }, + "z" => { "View" + "z" | "c" => align_view_center, + "t" => align_view_top, + "b" => align_view_bottom, + "m" => align_view_middle, + "k" | "up" => scroll_up, + "j" | "down" => scroll_down, + "C-b" | "pageup" => page_up, + "C-f" | "pagedown" => page_down, + "C-u" => half_page_up, + "C-d" => half_page_down, + }, + "Z" => { "View" sticky=true + "z" | "c" => align_view_center, + "t" => align_view_top, + "b" => align_view_bottom, + "m" => align_view_middle, + "k" | "up" => scroll_up, + "j" | "down" => scroll_down, + "C-b" | "pageup" => page_up, + "C-f" | "pagedown" => page_down, + "C-u" => half_page_up, + "C-d" => half_page_down, + }, + + "\"" => select_register, + "|" => shell_pipe, + "A-|" => shell_pipe_to, + "!" => shell_insert_output, + "A-!" => shell_append_output, + "$" => shell_keep_pipe, + "C-z" => suspend, + + "C-a" => increment, + "C-x" => decrement, + }); + let mut select = normal.clone(); + select.merge_nodes(keymap!({ "Select mode" + "h" | "left" => extend_char_left, + "j" | "down" => extend_line_down, + "k" | "up" => extend_line_up, + "l" | "right" => extend_char_right, + + "w" => extend_next_word_start, + "b" => extend_prev_word_start, + "e" => extend_next_word_end, + "W" => extend_next_long_word_start, + "B" => extend_prev_long_word_start, + "E" => extend_next_long_word_end, + + "n" => extend_search_next, + "N" => extend_search_prev, + + "t" => extend_till_char, + "f" => extend_next_char, + "T" => extend_till_prev_char, + "F" => extend_prev_char, + + "home" => extend_to_line_start, + "end" => extend_to_line_end, + "esc" => exit_select_mode, + + "v" => normal_mode, + })); + let insert = keymap!({ "Insert mode" + "esc" => normal_mode, + + "backspace" => delete_char_backward, + "C-h" => delete_char_backward, + "del" => delete_char_forward, + "C-d" => delete_char_forward, + "ret" => insert_newline, + "C-j" => insert_newline, + "tab" => insert_tab, + "C-w" => delete_word_backward, + "A-backspace" => delete_word_backward, + "A-d" => delete_word_forward, + + "left" => move_char_left, + "C-b" => move_char_left, + "down" => move_line_down, + "C-n" => move_line_down, + "up" => move_line_up, + "C-p" => move_line_up, + "right" => move_char_right, + "C-f" => move_char_right, + "A-b" => move_prev_word_end, + "A-left" => move_prev_word_end, + "A-f" => move_next_word_start, + "A-right" => move_next_word_start, + "A-<" => goto_file_start, + "A->" => goto_file_end, + "pageup" => page_up, + "pagedown" => page_down, + "home" => goto_line_start, + "C-a" => goto_line_start, + "end" => goto_line_end_newline, + "C-e" => goto_line_end_newline, + + "C-k" => kill_to_line_end, + "C-u" => kill_to_line_start, + + "C-x" => completion, + "C-r" => insert_register, + }); + hashmap!( + Mode::Normal => Keymap::new(normal), + Mode::Select => Keymap::new(select), + Mode::Insert => Keymap::new(insert), + ) +} diff --git a/helix-term/src/keymap/macros.rs b/helix-term/src/keymap/macros.rs new file mode 100644 index 000000000000..977bcc903ae3 --- /dev/null +++ b/helix-term/src/keymap/macros.rs @@ -0,0 +1,127 @@ +#[macro_export] +macro_rules! key { + ($key:ident) => { + ::helix_view::input::KeyEvent { + code: ::helix_view::keyboard::KeyCode::$key, + modifiers: ::helix_view::keyboard::KeyModifiers::NONE, + } + }; + ($($ch:tt)*) => { + ::helix_view::input::KeyEvent { + code: ::helix_view::keyboard::KeyCode::Char($($ch)*), + modifiers: ::helix_view::keyboard::KeyModifiers::NONE, + } + }; +} + +#[macro_export] +macro_rules! shift { + ($key:ident) => { + ::helix_view::input::KeyEvent { + code: ::helix_view::keyboard::KeyCode::$key, + modifiers: ::helix_view::keyboard::KeyModifiers::SHIFT, + } + }; + ($($ch:tt)*) => { + ::helix_view::input::KeyEvent { + code: ::helix_view::keyboard::KeyCode::Char($($ch)*), + modifiers: ::helix_view::keyboard::KeyModifiers::SHIFT, + } + }; +} + +#[macro_export] +macro_rules! ctrl { + ($key:ident) => { + ::helix_view::input::KeyEvent { + code: ::helix_view::keyboard::KeyCode::$key, + modifiers: ::helix_view::keyboard::KeyModifiers::CONTROL, + } + }; + ($($ch:tt)*) => { + ::helix_view::input::KeyEvent { + code: ::helix_view::keyboard::KeyCode::Char($($ch)*), + modifiers: ::helix_view::keyboard::KeyModifiers::CONTROL, + } + }; +} + +#[macro_export] +macro_rules! alt { + ($key:ident) => { + ::helix_view::input::KeyEvent { + code: ::helix_view::keyboard::KeyCode::$key, + modifiers: ::helix_view::keyboard::KeyModifiers::ALT, + } + }; + ($($ch:tt)*) => { + ::helix_view::input::KeyEvent { + code: ::helix_view::keyboard::KeyCode::Char($($ch)*), + modifiers: ::helix_view::keyboard::KeyModifiers::ALT, + } + }; +} + +/// Macro for defining the root of a `Keymap` object. Example: +/// +/// ``` +/// # use helix_core::hashmap; +/// # use helix_term::keymap; +/// # use helix_term::keymap::Keymap; +/// let normal_mode = keymap!({ "Normal mode" +/// "i" => insert_mode, +/// "g" => { "Goto" +/// "g" => goto_file_start, +/// "e" => goto_file_end, +/// }, +/// "j" | "down" => move_line_down, +/// }); +/// let keymap = Keymap::new(normal_mode); +/// ``` +#[macro_export] +macro_rules! keymap { + (@trie $cmd:ident) => { + $crate::keymap::KeyTrie::Leaf($crate::commands::MappableCommand::$cmd) + }; + + (@trie + { $label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ } + ) => { + keymap!({ $label $(sticky=$sticky)? $($($key)|+ => $value,)+ }) + }; + + (@trie [$($cmd:ident),* $(,)?]) => { + $crate::keymap::KeyTrie::Sequence(vec![$($crate::commands::Command::$cmd),*]) + }; + + ( + { $label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ } + ) => { + // modified from the hashmap! macro + { + let _cap = hashmap!(@count $($($key),+),*); + let mut _map = ::std::collections::HashMap::with_capacity(_cap); + let mut _order = ::std::vec::Vec::with_capacity(_cap); + $( + $( + let _key = $key.parse::<::helix_view::input::KeyEvent>().unwrap(); + let _duplicate = _map.insert( + _key, + keymap!(@trie $value) + ); + assert!(_duplicate.is_none(), "Duplicate key found: {:?}", _duplicate.unwrap()); + _order.push(_key); + )+ + )* + let mut _node = $crate::keymap::KeyTrieNode::new($label, _map, _order); + $( _node.is_sticky = $sticky; )? + $crate::keymap::KeyTrie::Node(_node) + } + }; +} + +pub use key; +pub use shift; +pub use ctrl; +pub use alt; +pub use keymap; diff --git a/helix-term/src/lib.rs b/helix-term/src/lib.rs index fc8e934e1a7d..a945b20dedaf 100644 --- a/helix-term/src/lib.rs +++ b/helix-term/src/lib.rs @@ -10,6 +10,7 @@ pub mod health; pub mod job; pub mod keymap; pub mod ui; +pub use keymap::macros::*; #[cfg(not(windows))] fn true_color() -> bool { From ad5dc91e0edf014acaf1539d9df3fc3b9001db4d Mon Sep 17 00:00:00 2001 From: Joseph Harrison-Lim Date: Wed, 23 Mar 2022 12:35:33 -0400 Subject: [PATCH 20/22] swap -> store, remove unneeded clone --- helix-term/src/application.rs | 2 +- helix-term/src/keymap.rs | 9 +-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 948bbe44c433..285c2afbc0f1 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -266,7 +266,7 @@ impl Application { ConfigEvent::Update(editor_config) => { let mut app_config = (*self.config.load().clone()).clone(); app_config.editor = editor_config; - self.config.swap(Arc::new(app_config)); + self.config.store(Arc::new(app_config)); } } } diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index d119e4e16d2f..91953c298523 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -305,7 +305,7 @@ impl Keymaps { /// sticky node is in use, it will be cleared. pub fn get(&mut self, mode: Mode, key: KeyEvent) -> KeymapResult { // TODO: remove the sticky part and look up manually - let keymaps = self.map().clone(); + let keymaps = &*self.map(); let keymap = &keymaps[&mode]; if key!(Esc) == key { @@ -361,13 +361,6 @@ impl Default for Keymaps { } } -impl PartialEq for Keymaps { - fn eq(&self, other: &Self) -> bool { - let a = *self.map() == *other.map(); - a && self.state == other.state && self.sticky == other.sticky - } -} - /// Merge default config keys with user overwritten keys for custom user config. pub fn merge_keys(mut config: Config) -> Config { let mut delta = std::mem::replace(&mut config.keys, default_keymaps()); From 23e96a641be649bd512693a51dd06df2c97f3219 Mon Sep 17 00:00:00 2001 From: Joseph Harrison-Lim Date: Wed, 23 Mar 2022 12:36:26 -0400 Subject: [PATCH 21/22] cargo fmt --- helix-term/src/keymap/default_keymaps.rs | 706 +++++++++++------------ helix-term/src/keymap/macros.rs | 6 +- 2 files changed, 356 insertions(+), 356 deletions(-) diff --git a/helix-term/src/keymap/default_keymaps.rs b/helix-term/src/keymap/default_keymaps.rs index cef7b146237f..f731c40f654e 100644 --- a/helix-term/src/keymap/default_keymaps.rs +++ b/helix-term/src/keymap/default_keymaps.rs @@ -1,359 +1,359 @@ use std::collections::HashMap; -use helix_core::hashmap; use super::macros::keymap; -use super::{Mode, Keymap}; +use super::{Keymap, Mode}; +use helix_core::hashmap; pub fn default_keymaps() -> HashMap { - let normal = keymap!({ "Normal mode" - "h" | "left" => move_char_left, - "j" | "down" => move_line_down, - "k" | "up" => move_line_up, - "l" | "right" => move_char_right, - - "t" => find_till_char, - "f" => find_next_char, - "T" => till_prev_char, - "F" => find_prev_char, - "r" => replace, - "R" => replace_with_yanked, - "A-." => repeat_last_motion, - - "~" => switch_case, - "`" => switch_to_lowercase, - "A-`" => switch_to_uppercase, - - "home" => goto_line_start, - "end" => goto_line_end, - - "w" => move_next_word_start, - "b" => move_prev_word_start, - "e" => move_next_word_end, - - "W" => move_next_long_word_start, - "B" => move_prev_long_word_start, - "E" => move_next_long_word_end, - - "v" => select_mode, - "G" => goto_line, - "g" => { "Goto" - "g" => goto_file_start, - "e" => goto_last_line, - "f" => goto_file, - "h" => goto_line_start, - "l" => goto_line_end, - "s" => goto_first_nonwhitespace, - "d" => goto_definition, - "y" => goto_type_definition, - "r" => goto_reference, - "i" => goto_implementation, - "t" => goto_window_top, - "c" => goto_window_center, - "b" => goto_window_bottom, - "a" => goto_last_accessed_file, - "m" => goto_last_modified_file, - "n" => goto_next_buffer, - "p" => goto_previous_buffer, - "." => goto_last_modification, - }, - ":" => command_mode, - - "i" => insert_mode, - "I" => prepend_to_line, - "a" => append_mode, - "A" => append_to_line, - "o" => open_below, - "O" => open_above, - - "d" => delete_selection, - "A-d" => delete_selection_noyank, - "c" => change_selection, - "A-c" => change_selection_noyank, - - "C" => copy_selection_on_next_line, - "A-C" => copy_selection_on_prev_line, - - - "s" => select_regex, - "A-s" => split_selection_on_newline, - "S" => split_selection, - ";" => collapse_selection, - "A-;" => flip_selections, - "A-k" | "A-up" => expand_selection, - "A-j" | "A-down" => shrink_selection, - "A-h" | "A-left" => select_prev_sibling, - "A-l" | "A-right" => select_next_sibling, - - "%" => select_all, - "x" => extend_line, - "X" => extend_to_line_bounds, - // crop_to_whole_line - - "m" => { "Match" - "m" => match_brackets, - "s" => surround_add, - "r" => surround_replace, - "d" => surround_delete, - "a" => select_textobject_around, - "i" => select_textobject_inner, - }, - "[" => { "Left bracket" - "d" => goto_prev_diag, - "D" => goto_first_diag, - "f" => goto_prev_function, - "c" => goto_prev_class, - "a" => goto_prev_parameter, - "o" => goto_prev_comment, - "space" => add_newline_above, - }, - "]" => { "Right bracket" - "d" => goto_next_diag, - "D" => goto_last_diag, - "f" => goto_next_function, - "c" => goto_next_class, - "a" => goto_next_parameter, - "o" => goto_next_comment, - "space" => add_newline_below, - }, - - "/" => search, - "?" => rsearch, - "n" => search_next, - "N" => search_prev, - "*" => search_selection, - - "u" => undo, - "U" => redo, - "A-u" => earlier, - "A-U" => later, - - "y" => yank, - // yank_all - "p" => paste_after, - // paste_all - "P" => paste_before, - - "Q" => record_macro, - "q" => replay_macro, - - ">" => indent, - "<" => unindent, - "=" => format_selections, - "J" => join_selections, - "K" => keep_selections, - "A-K" => remove_selections, - - "," => keep_primary_selection, - "A-," => remove_primary_selection, - - // "q" => record_macro, - // "Q" => replay_macro, - - "&" => align_selections, - "_" => trim_selections, - - "(" => rotate_selections_backward, - ")" => rotate_selections_forward, - "A-(" => rotate_selection_contents_backward, - "A-)" => rotate_selection_contents_forward, - - "A-:" => ensure_selections_forward, - - "esc" => normal_mode, - "C-b" | "pageup" => page_up, - "C-f" | "pagedown" => page_down, - "C-u" => half_page_up, - "C-d" => half_page_down, - - "C-w" => { "Window" - "C-w" | "w" => rotate_view, - "C-s" | "s" => hsplit, - "C-v" | "v" => vsplit, - "f" => goto_file_hsplit, - "F" => goto_file_vsplit, - "C-q" | "q" => wclose, - "C-o" | "o" => wonly, - "C-h" | "h" | "left" => jump_view_left, - "C-j" | "j" | "down" => jump_view_down, - "C-k" | "k" | "up" => jump_view_up, - "C-l" | "l" | "right" => jump_view_right, - "n" => { "New split scratch buffer" - "C-s" | "s" => hsplit_new, - "C-v" | "v" => vsplit_new, - }, - }, - - // move under c - "C-c" => toggle_comments, - - // z family for save/restore/combine from/to sels from register - - "tab" => jump_forward, // tab == - "C-o" => jump_backward, - "C-s" => save_selection, - - "space" => { "Space" - "f" => file_picker, - "b" => buffer_picker, - "s" => symbol_picker, - "S" => workspace_symbol_picker, - "a" => code_action, - "'" => last_picker, - "d" => { "Debug (experimental)" sticky=true - "l" => dap_launch, - "b" => dap_toggle_breakpoint, - "c" => dap_continue, - "h" => dap_pause, - "i" => dap_step_in, - "o" => dap_step_out, - "n" => dap_next, - "v" => dap_variables, - "t" => dap_terminate, - "C-c" => dap_edit_condition, - "C-l" => dap_edit_log, - "s" => { "Switch" - "t" => dap_switch_thread, - "f" => dap_switch_stack_frame, - // sl, sb - }, - "e" => dap_enable_exceptions, - "E" => dap_disable_exceptions, - }, - "w" => { "Window" - "C-w" | "w" => rotate_view, - "C-s" | "s" => hsplit, - "C-v" | "v" => vsplit, - "f" => goto_file_hsplit, - "F" => goto_file_vsplit, - "C-q" | "q" => wclose, - "C-o" | "o" => wonly, - "C-h" | "h" | "left" => jump_view_left, - "C-j" | "j" | "down" => jump_view_down, - "C-k" | "k" | "up" => jump_view_up, - "C-l" | "l" | "right" => jump_view_right, - "n" => { "New split scratch buffer" - "C-s" | "s" => hsplit_new, - "C-v" | "v" => vsplit_new, - }, - }, - "y" => yank_joined_to_clipboard, - "Y" => yank_main_selection_to_clipboard, - "p" => paste_clipboard_after, - "P" => paste_clipboard_before, - "R" => replace_selections_with_clipboard, - "/" => global_search, - "k" => hover, - "r" => rename_symbol, - "?" => command_palette, - }, - "z" => { "View" - "z" | "c" => align_view_center, - "t" => align_view_top, - "b" => align_view_bottom, - "m" => align_view_middle, - "k" | "up" => scroll_up, - "j" | "down" => scroll_down, - "C-b" | "pageup" => page_up, - "C-f" | "pagedown" => page_down, - "C-u" => half_page_up, - "C-d" => half_page_down, - }, - "Z" => { "View" sticky=true - "z" | "c" => align_view_center, - "t" => align_view_top, - "b" => align_view_bottom, - "m" => align_view_middle, - "k" | "up" => scroll_up, - "j" | "down" => scroll_down, - "C-b" | "pageup" => page_up, - "C-f" | "pagedown" => page_down, - "C-u" => half_page_up, - "C-d" => half_page_down, - }, - - "\"" => select_register, - "|" => shell_pipe, - "A-|" => shell_pipe_to, - "!" => shell_insert_output, - "A-!" => shell_append_output, - "$" => shell_keep_pipe, - "C-z" => suspend, - - "C-a" => increment, - "C-x" => decrement, - }); - let mut select = normal.clone(); - select.merge_nodes(keymap!({ "Select mode" - "h" | "left" => extend_char_left, - "j" | "down" => extend_line_down, - "k" | "up" => extend_line_up, - "l" | "right" => extend_char_right, - - "w" => extend_next_word_start, - "b" => extend_prev_word_start, - "e" => extend_next_word_end, - "W" => extend_next_long_word_start, - "B" => extend_prev_long_word_start, - "E" => extend_next_long_word_end, - - "n" => extend_search_next, - "N" => extend_search_prev, - - "t" => extend_till_char, - "f" => extend_next_char, - "T" => extend_till_prev_char, - "F" => extend_prev_char, - - "home" => extend_to_line_start, - "end" => extend_to_line_end, - "esc" => exit_select_mode, - - "v" => normal_mode, - })); - let insert = keymap!({ "Insert mode" - "esc" => normal_mode, - - "backspace" => delete_char_backward, - "C-h" => delete_char_backward, - "del" => delete_char_forward, - "C-d" => delete_char_forward, - "ret" => insert_newline, - "C-j" => insert_newline, - "tab" => insert_tab, - "C-w" => delete_word_backward, - "A-backspace" => delete_word_backward, - "A-d" => delete_word_forward, - - "left" => move_char_left, - "C-b" => move_char_left, - "down" => move_line_down, - "C-n" => move_line_down, - "up" => move_line_up, - "C-p" => move_line_up, - "right" => move_char_right, - "C-f" => move_char_right, - "A-b" => move_prev_word_end, - "A-left" => move_prev_word_end, - "A-f" => move_next_word_start, - "A-right" => move_next_word_start, - "A-<" => goto_file_start, - "A->" => goto_file_end, - "pageup" => page_up, - "pagedown" => page_down, - "home" => goto_line_start, - "C-a" => goto_line_start, - "end" => goto_line_end_newline, - "C-e" => goto_line_end_newline, - - "C-k" => kill_to_line_end, - "C-u" => kill_to_line_start, - - "C-x" => completion, - "C-r" => insert_register, - }); - hashmap!( - Mode::Normal => Keymap::new(normal), - Mode::Select => Keymap::new(select), - Mode::Insert => Keymap::new(insert), - ) + let normal = keymap!({ "Normal mode" + "h" | "left" => move_char_left, + "j" | "down" => move_line_down, + "k" | "up" => move_line_up, + "l" | "right" => move_char_right, + + "t" => find_till_char, + "f" => find_next_char, + "T" => till_prev_char, + "F" => find_prev_char, + "r" => replace, + "R" => replace_with_yanked, + "A-." => repeat_last_motion, + + "~" => switch_case, + "`" => switch_to_lowercase, + "A-`" => switch_to_uppercase, + + "home" => goto_line_start, + "end" => goto_line_end, + + "w" => move_next_word_start, + "b" => move_prev_word_start, + "e" => move_next_word_end, + + "W" => move_next_long_word_start, + "B" => move_prev_long_word_start, + "E" => move_next_long_word_end, + + "v" => select_mode, + "G" => goto_line, + "g" => { "Goto" + "g" => goto_file_start, + "e" => goto_last_line, + "f" => goto_file, + "h" => goto_line_start, + "l" => goto_line_end, + "s" => goto_first_nonwhitespace, + "d" => goto_definition, + "y" => goto_type_definition, + "r" => goto_reference, + "i" => goto_implementation, + "t" => goto_window_top, + "c" => goto_window_center, + "b" => goto_window_bottom, + "a" => goto_last_accessed_file, + "m" => goto_last_modified_file, + "n" => goto_next_buffer, + "p" => goto_previous_buffer, + "." => goto_last_modification, + }, + ":" => command_mode, + + "i" => insert_mode, + "I" => prepend_to_line, + "a" => append_mode, + "A" => append_to_line, + "o" => open_below, + "O" => open_above, + + "d" => delete_selection, + "A-d" => delete_selection_noyank, + "c" => change_selection, + "A-c" => change_selection_noyank, + + "C" => copy_selection_on_next_line, + "A-C" => copy_selection_on_prev_line, + + + "s" => select_regex, + "A-s" => split_selection_on_newline, + "S" => split_selection, + ";" => collapse_selection, + "A-;" => flip_selections, + "A-k" | "A-up" => expand_selection, + "A-j" | "A-down" => shrink_selection, + "A-h" | "A-left" => select_prev_sibling, + "A-l" | "A-right" => select_next_sibling, + + "%" => select_all, + "x" => extend_line, + "X" => extend_to_line_bounds, + // crop_to_whole_line + + "m" => { "Match" + "m" => match_brackets, + "s" => surround_add, + "r" => surround_replace, + "d" => surround_delete, + "a" => select_textobject_around, + "i" => select_textobject_inner, + }, + "[" => { "Left bracket" + "d" => goto_prev_diag, + "D" => goto_first_diag, + "f" => goto_prev_function, + "c" => goto_prev_class, + "a" => goto_prev_parameter, + "o" => goto_prev_comment, + "space" => add_newline_above, + }, + "]" => { "Right bracket" + "d" => goto_next_diag, + "D" => goto_last_diag, + "f" => goto_next_function, + "c" => goto_next_class, + "a" => goto_next_parameter, + "o" => goto_next_comment, + "space" => add_newline_below, + }, + + "/" => search, + "?" => rsearch, + "n" => search_next, + "N" => search_prev, + "*" => search_selection, + + "u" => undo, + "U" => redo, + "A-u" => earlier, + "A-U" => later, + + "y" => yank, + // yank_all + "p" => paste_after, + // paste_all + "P" => paste_before, + + "Q" => record_macro, + "q" => replay_macro, + + ">" => indent, + "<" => unindent, + "=" => format_selections, + "J" => join_selections, + "K" => keep_selections, + "A-K" => remove_selections, + + "," => keep_primary_selection, + "A-," => remove_primary_selection, + + // "q" => record_macro, + // "Q" => replay_macro, + + "&" => align_selections, + "_" => trim_selections, + + "(" => rotate_selections_backward, + ")" => rotate_selections_forward, + "A-(" => rotate_selection_contents_backward, + "A-)" => rotate_selection_contents_forward, + + "A-:" => ensure_selections_forward, + + "esc" => normal_mode, + "C-b" | "pageup" => page_up, + "C-f" | "pagedown" => page_down, + "C-u" => half_page_up, + "C-d" => half_page_down, + + "C-w" => { "Window" + "C-w" | "w" => rotate_view, + "C-s" | "s" => hsplit, + "C-v" | "v" => vsplit, + "f" => goto_file_hsplit, + "F" => goto_file_vsplit, + "C-q" | "q" => wclose, + "C-o" | "o" => wonly, + "C-h" | "h" | "left" => jump_view_left, + "C-j" | "j" | "down" => jump_view_down, + "C-k" | "k" | "up" => jump_view_up, + "C-l" | "l" | "right" => jump_view_right, + "n" => { "New split scratch buffer" + "C-s" | "s" => hsplit_new, + "C-v" | "v" => vsplit_new, + }, + }, + + // move under c + "C-c" => toggle_comments, + + // z family for save/restore/combine from/to sels from register + + "tab" => jump_forward, // tab == + "C-o" => jump_backward, + "C-s" => save_selection, + + "space" => { "Space" + "f" => file_picker, + "b" => buffer_picker, + "s" => symbol_picker, + "S" => workspace_symbol_picker, + "a" => code_action, + "'" => last_picker, + "d" => { "Debug (experimental)" sticky=true + "l" => dap_launch, + "b" => dap_toggle_breakpoint, + "c" => dap_continue, + "h" => dap_pause, + "i" => dap_step_in, + "o" => dap_step_out, + "n" => dap_next, + "v" => dap_variables, + "t" => dap_terminate, + "C-c" => dap_edit_condition, + "C-l" => dap_edit_log, + "s" => { "Switch" + "t" => dap_switch_thread, + "f" => dap_switch_stack_frame, + // sl, sb + }, + "e" => dap_enable_exceptions, + "E" => dap_disable_exceptions, + }, + "w" => { "Window" + "C-w" | "w" => rotate_view, + "C-s" | "s" => hsplit, + "C-v" | "v" => vsplit, + "f" => goto_file_hsplit, + "F" => goto_file_vsplit, + "C-q" | "q" => wclose, + "C-o" | "o" => wonly, + "C-h" | "h" | "left" => jump_view_left, + "C-j" | "j" | "down" => jump_view_down, + "C-k" | "k" | "up" => jump_view_up, + "C-l" | "l" | "right" => jump_view_right, + "n" => { "New split scratch buffer" + "C-s" | "s" => hsplit_new, + "C-v" | "v" => vsplit_new, + }, + }, + "y" => yank_joined_to_clipboard, + "Y" => yank_main_selection_to_clipboard, + "p" => paste_clipboard_after, + "P" => paste_clipboard_before, + "R" => replace_selections_with_clipboard, + "/" => global_search, + "k" => hover, + "r" => rename_symbol, + "?" => command_palette, + }, + "z" => { "View" + "z" | "c" => align_view_center, + "t" => align_view_top, + "b" => align_view_bottom, + "m" => align_view_middle, + "k" | "up" => scroll_up, + "j" | "down" => scroll_down, + "C-b" | "pageup" => page_up, + "C-f" | "pagedown" => page_down, + "C-u" => half_page_up, + "C-d" => half_page_down, + }, + "Z" => { "View" sticky=true + "z" | "c" => align_view_center, + "t" => align_view_top, + "b" => align_view_bottom, + "m" => align_view_middle, + "k" | "up" => scroll_up, + "j" | "down" => scroll_down, + "C-b" | "pageup" => page_up, + "C-f" | "pagedown" => page_down, + "C-u" => half_page_up, + "C-d" => half_page_down, + }, + + "\"" => select_register, + "|" => shell_pipe, + "A-|" => shell_pipe_to, + "!" => shell_insert_output, + "A-!" => shell_append_output, + "$" => shell_keep_pipe, + "C-z" => suspend, + + "C-a" => increment, + "C-x" => decrement, + }); + let mut select = normal.clone(); + select.merge_nodes(keymap!({ "Select mode" + "h" | "left" => extend_char_left, + "j" | "down" => extend_line_down, + "k" | "up" => extend_line_up, + "l" | "right" => extend_char_right, + + "w" => extend_next_word_start, + "b" => extend_prev_word_start, + "e" => extend_next_word_end, + "W" => extend_next_long_word_start, + "B" => extend_prev_long_word_start, + "E" => extend_next_long_word_end, + + "n" => extend_search_next, + "N" => extend_search_prev, + + "t" => extend_till_char, + "f" => extend_next_char, + "T" => extend_till_prev_char, + "F" => extend_prev_char, + + "home" => extend_to_line_start, + "end" => extend_to_line_end, + "esc" => exit_select_mode, + + "v" => normal_mode, + })); + let insert = keymap!({ "Insert mode" + "esc" => normal_mode, + + "backspace" => delete_char_backward, + "C-h" => delete_char_backward, + "del" => delete_char_forward, + "C-d" => delete_char_forward, + "ret" => insert_newline, + "C-j" => insert_newline, + "tab" => insert_tab, + "C-w" => delete_word_backward, + "A-backspace" => delete_word_backward, + "A-d" => delete_word_forward, + + "left" => move_char_left, + "C-b" => move_char_left, + "down" => move_line_down, + "C-n" => move_line_down, + "up" => move_line_up, + "C-p" => move_line_up, + "right" => move_char_right, + "C-f" => move_char_right, + "A-b" => move_prev_word_end, + "A-left" => move_prev_word_end, + "A-f" => move_next_word_start, + "A-right" => move_next_word_start, + "A-<" => goto_file_start, + "A->" => goto_file_end, + "pageup" => page_up, + "pagedown" => page_down, + "home" => goto_line_start, + "C-a" => goto_line_start, + "end" => goto_line_end_newline, + "C-e" => goto_line_end_newline, + + "C-k" => kill_to_line_end, + "C-u" => kill_to_line_start, + + "C-x" => completion, + "C-r" => insert_register, + }); + hashmap!( + Mode::Normal => Keymap::new(normal), + Mode::Select => Keymap::new(select), + Mode::Insert => Keymap::new(insert), + ) } diff --git a/helix-term/src/keymap/macros.rs b/helix-term/src/keymap/macros.rs index 977bcc903ae3..c4a1bfbb3064 100644 --- a/helix-term/src/keymap/macros.rs +++ b/helix-term/src/keymap/macros.rs @@ -120,8 +120,8 @@ macro_rules! keymap { }; } -pub use key; -pub use shift; -pub use ctrl; pub use alt; +pub use ctrl; +pub use key; pub use keymap; +pub use shift; From 252d746250b9170552be075ba687ac13be48ab54 Mon Sep 17 00:00:00 2001 From: Joseph Harrison-Lim Date: Fri, 25 Mar 2022 00:32:30 -0400 Subject: [PATCH 22/22] Rename default_keymaps to default --- helix-term/src/config.rs | 10 +++++----- helix-term/src/keymap.rs | 8 ++++---- .../src/keymap/{default_keymaps.rs => default.rs} | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) rename helix-term/src/keymap/{default_keymaps.rs => default.rs} (99%) diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index d42834ec75b0..06e44ad9236c 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -1,4 +1,4 @@ -use crate::keymap::{default_keymaps::default_keymaps, merge_keys, Keymap}; +use crate::keymap::{default::default, merge_keys, Keymap}; use helix_view::document::Mode; use serde::Deserialize; use std::collections::HashMap; @@ -13,7 +13,7 @@ pub struct Config { pub theme: Option, #[serde(default)] pub lsp: LspConfig, - #[serde(default = "default_keymaps")] + #[serde(default = "default")] pub keys: HashMap, #[serde(default)] pub editor: helix_view::editor::Config, @@ -24,7 +24,7 @@ impl Default for Config { Config { theme: None, lsp: LspConfig::default(), - keys: default_keymaps(), + keys: default(), editor: helix_view::editor::Config::default(), } } @@ -107,10 +107,10 @@ mod tests { fn keys_resolve_to_correct_defaults() { // From serde default let default_keys = toml::from_str::("").unwrap().keys; - assert_eq!(default_keys, default_keymaps()); + assert_eq!(default_keys, default()); // From the Default trait let default_keys = Config::default().keys; - assert_eq!(default_keys, default_keymaps()); + assert_eq!(default_keys, default()); } } diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 91953c298523..37dbc5de2e98 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -1,4 +1,4 @@ -pub mod default_keymaps; +pub mod default; pub mod macros; pub use crate::commands::MappableCommand; @@ -16,7 +16,7 @@ use std::{ sync::Arc, }; -use default_keymaps::default_keymaps; +use default::default; use macros::key; #[derive(Debug, Clone)] @@ -357,13 +357,13 @@ impl Keymaps { impl Default for Keymaps { fn default() -> Self { - Self::new(Box::new(ArcSwap::new(Arc::new(default_keymaps())))) + Self::new(Box::new(ArcSwap::new(Arc::new(default())))) } } /// Merge default config keys with user overwritten keys for custom user config. pub fn merge_keys(mut config: Config) -> Config { - let mut delta = std::mem::replace(&mut config.keys, default_keymaps()); + let mut delta = std::mem::replace(&mut config.keys, default()); for (mode, keys) in &mut config.keys { keys.merge(delta.remove(mode).unwrap_or_default()) } diff --git a/helix-term/src/keymap/default_keymaps.rs b/helix-term/src/keymap/default.rs similarity index 99% rename from helix-term/src/keymap/default_keymaps.rs rename to helix-term/src/keymap/default.rs index f731c40f654e..b5685082c675 100644 --- a/helix-term/src/keymap/default_keymaps.rs +++ b/helix-term/src/keymap/default.rs @@ -4,7 +4,7 @@ use super::macros::keymap; use super::{Keymap, Mode}; use helix_core::hashmap; -pub fn default_keymaps() -> HashMap { +pub fn default() -> HashMap { let normal = keymap!({ "Normal mode" "h" | "left" => move_char_left, "j" | "down" => move_line_down,