Put keymaps behind dyn access, refactor config.load()

This commit is contained in:
Joseph Harrison-Lim 2022-03-17 22:48:49 -04:00
parent a41c21539b
commit 06bad8cf49
No known key found for this signature in database
GPG Key ID: 5DAB214D5E62987D
8 changed files with 106 additions and 88 deletions

View File

@ -111,9 +111,11 @@ pub fn new(args: Args, config: Config) -> Result<Self, Error> {
})),
);
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 @@ pub fn handle_config_events(&mut self, config_event: ConfigEvent) {
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));
}
}
}

View File

@ -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::<ui::EditorView>().unwrap().keymaps[&doc.mode].reverse_map();
let keymap = compositor.find::<ui::EditorView>().unwrap().keymaps()[&doc.mode]
.reverse_map();
let mut commands: Vec<MappableCommand> = 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 fn idle_completion(cx: &mut Context) {
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);
},
);

View File

@ -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()?,

View File

@ -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>,

View File

@ -6,6 +6,7 @@
ui::{Completion, ProgressSpinners},
};
use arc_swap::access::{DynAccess, DynGuard};
use helix_core::{
coords_at_pos, encoding,
graphemes::{
@ -31,7 +32,7 @@
use tui::buffer::Buffer as Surface;
pub struct EditorView {
pub keymaps: Keymaps,
pub keymaps: Box<dyn DynAccess<Keymaps>>,
on_next_key: Option<Box<dyn FnOnce(&mut commands::Context, KeyEvent)>>,
last_insert: (commands::MappableCommand, Vec<InsertEvent>),
pub(crate) completion: Option<Completion>,
@ -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<dyn DynAccess<Keymaps>>) -> Self {
Self {
keymaps,
on_next_key: None,
@ -62,6 +57,10 @@ pub fn new(keymaps: Keymaps) -> Self {
}
}
pub fn keymaps(&self) -> DynGuard<Keymaps> {
self.keymaps.load()
}
pub fn spinners_mut(&mut self) -> &mut ProgressSpinners {
&mut self.spinners
}
@ -122,7 +121,7 @@ pub fn render_view(
doc,
view,
theme,
&editor.config.load().cursor_shape,
&editor.config().cursor_shape,
),
))
} else {
@ -704,51 +703,50 @@ pub fn render_statusline(
/// otherwise.
fn handle_keymap_event(
&mut self,
mode: Mode,
cxt: &mut commands::Context,
event: KeyEvent,
) -> Option<KeymapResult> {
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<KeymapResultKind> {
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 @@ fn command_mode(&mut self, mode: Mode, cxt: &mut commands::Context, event: KeyEv
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 @@ fn command_mode(&mut self, mode: Mode, cxt: &mut commands::Context, event: KeyEv
// 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 @@ pub fn clear_completion(&mut self, editor: &mut Editor) {
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 @@ fn handle_mouse_event(
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 @@ fn handle_event(
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 @@ fn handle_event(
// 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 @@ fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
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 @@ fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
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));

View File

@ -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,

View File

@ -40,7 +40,7 @@
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<Duration, D::Error>
where
D: serde::Deserializer<'de>,
@ -372,6 +372,10 @@ pub fn new(
}
}
pub fn config(&self) -> DynGuard<Config> {
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 @@ pub fn clear_idle_timer(&mut self) {
}
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 @@ fn launch_language_server(ls: &mut helix_lsp::Registry, doc: &mut Document) -> O
}
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 @@ pub fn should_close(&self) -> bool {
}
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 @@ pub fn cursor(&self) -> (Option<Position>, CursorKind) {
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())

View File

@ -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;