From 06bad8cf492b9331d0a2d1e9242f3ad4e2c1cf79 Mon Sep 17 00:00:00 2001 From: Joseph Harrison-Lim Date: Thu, 17 Mar 2022 22:48:49 -0400 Subject: [PATCH] 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 d891b5d98..7ce133ac2 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 024019fc0..20870f240 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 e20c46a9b..251cd491b 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 1fe1f6333..0ce6a0fd4 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 aede35eb4..c099dc10e 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 bf687c188..6242ea2ee 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 3774756ec..d44774305 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 6d2b0d2ba..7327ed1a2 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;