diff options
Diffstat (limited to 'helix-term/src')
-rw-r--r-- | helix-term/src/application.rs | 6 | ||||
-rw-r--r-- | helix-term/src/commands.rs | 478 | ||||
-rw-r--r-- | helix-term/src/job.rs | 14 | ||||
-rw-r--r-- | helix-term/src/keymap.rs | 128 | ||||
-rw-r--r-- | helix-term/src/ui/editor.rs | 8 | ||||
-rw-r--r-- | helix-term/src/ui/info.rs | 30 | ||||
-rw-r--r-- | helix-term/src/ui/mod.rs | 1 |
7 files changed, 390 insertions, 275 deletions
diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 9622ad91..17ba2652 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -160,7 +160,11 @@ impl Application { } self.render(); } - Some(callback) = self.jobs.next_job() => { + Some(callback) = self.jobs.futures.next() => { + self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback); + self.render(); + } + Some(callback) = self.jobs.wait_futures.next() => { self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback); self.render(); } diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 5ab0926a..fbeae5ff 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -16,6 +16,7 @@ use helix_core::{ use helix_view::{ document::{IndentStyle, Mode}, editor::Action, + info::Info, input::KeyEvent, keyboard::KeyCode, view::{View, PADDING}, @@ -38,6 +39,7 @@ use crate::{ use crate::job::{self, Job, Jobs}; use futures_util::{FutureExt, TryFutureExt}; +use std::collections::HashMap; use std::{fmt, future::Future}; use std::{ @@ -45,7 +47,7 @@ use std::{ path::{Path, PathBuf}, }; -use once_cell::sync::Lazy; +use once_cell::sync::{Lazy, OnceCell}; use serde::de::{self, Deserialize, Deserializer}; pub struct Context<'a> { @@ -75,6 +77,16 @@ impl<'a> Context<'a> { } #[inline] + pub fn on_next_key_mode(&mut self, map: HashMap<KeyEvent, fn(&mut Context)>) { + self.on_next_key(move |cx, event| { + cx.editor.autoinfo = None; + if let Some(func) = map.get(&event) { + func(cx); + } + }); + } + + #[inline] pub fn callback<T, F>( &mut self, call: impl Future<Output = helix_lsp::Result<serde_json::Value>> + 'static + Send, @@ -153,17 +165,12 @@ impl Command { move_char_right, move_line_up, move_line_down, - move_line_end, - move_line_start, - move_first_nonwhitespace, move_next_word_start, move_prev_word_start, move_next_word_end, move_next_long_word_start, move_prev_long_word_start, move_next_long_word_end, - move_file_start, - move_file_end, extend_next_word_start, extend_prev_word_start, extend_next_word_end, @@ -175,7 +182,6 @@ impl Command { find_prev_char, extend_till_prev_char, extend_prev_char, - extend_first_nonwhitespace, replace, page_up, page_down, @@ -185,8 +191,6 @@ impl Command { extend_char_right, extend_line_up, extend_line_down, - extend_line_end, - extend_line_start, select_all, select_regex, split_selection, @@ -196,6 +200,7 @@ impl Command { extend_search_next, search_selection, extend_line, + extend_to_line_bounds, delete_selection, change_selection, collapse_selection, @@ -217,11 +222,17 @@ impl Command { goto_definition, goto_type_definition, goto_implementation, + goto_file_start, + goto_file_end, goto_reference, goto_first_diag, goto_last_diag, goto_next_diag, goto_prev_diag, + goto_line_start, + goto_line_end, + goto_line_end_newline, + goto_first_nonwhitespace, signature_help, insert_tab, insert_newline, @@ -376,7 +387,7 @@ fn move_line_down(cx: &mut Context) { ); } -fn move_line_end(cx: &mut Context) { +fn goto_line_end(cx: &mut Context) { let (view, doc) = current!(cx.editor); doc.set_selection( view.id, @@ -388,12 +399,33 @@ fn move_line_end(cx: &mut Context) { let pos = graphemes::nth_prev_grapheme_boundary(text.slice(..), pos, 1); let pos = range.head.max(pos).max(text.line_to_char(line)); + Range::new( + match doc.mode { + Mode::Normal | Mode::Insert => pos, + Mode::Select => range.anchor, + }, + pos, + ) + }), + ); +} + +fn goto_line_end_newline(cx: &mut Context) { + let (view, doc) = current!(cx.editor); + + doc.set_selection( + view.id, + doc.selection(view.id).clone().transform(|range| { + let text = doc.text(); + let line = text.char_to_line(range.head); + + let pos = line_end_char_index(&text.slice(..), line); Range::new(pos, pos) }), ); } -fn move_line_start(cx: &mut Context) { +fn goto_line_start(cx: &mut Context) { let (view, doc) = current!(cx.editor); doc.set_selection( view.id, @@ -403,12 +435,18 @@ fn move_line_start(cx: &mut Context) { // adjust to start of the line let pos = text.line_to_char(line); - Range::new(pos, pos) + Range::new( + match doc.mode { + Mode::Normal | Mode::Insert => pos, + Mode::Select => range.anchor, + }, + pos, + ) }), ); } -fn move_first_nonwhitespace(cx: &mut Context) { +fn goto_first_nonwhitespace(cx: &mut Context) { let (view, doc) = current!(cx.editor); doc.set_selection( view.id, @@ -418,7 +456,13 @@ fn move_first_nonwhitespace(cx: &mut Context) { if let Some(pos) = find_first_non_whitespace_char(text.line(line_idx)) { let pos = pos + text.line_to_char(line_idx); - Range::new(pos, pos) + Range::new( + match doc.mode { + Mode::Normal | Mode::Insert => pos, + Mode::Select => range.anchor, + }, + pos, + ) } else { range } @@ -426,6 +470,37 @@ fn move_first_nonwhitespace(cx: &mut Context) { ); } +fn goto_window(cx: &mut Context, align: Align) { + let (view, doc) = current!(cx.editor); + + let scrolloff = PADDING.min(view.area.height as usize / 2); // TODO: user pref + + let last_line = view.last_line(doc); + + let line = match align { + Align::Top => (view.first_line + scrolloff), + Align::Center => (view.first_line + (view.area.height as usize / 2)), + Align::Bottom => last_line.saturating_sub(scrolloff), + } + .min(last_line.saturating_sub(scrolloff)); + + let pos = doc.text().line_to_char(line); + + doc.set_selection(view.id, Selection::point(pos)); +} + +fn goto_window_top(cx: &mut Context) { + goto_window(cx, Align::Top) +} + +fn goto_window_middle(cx: &mut Context) { + goto_window(cx, Align::Center) +} + +fn goto_window_bottom(cx: &mut Context) { + goto_window(cx, Align::Bottom) +} + // TODO: move vs extend could take an extra type Extend/Move that would // Range::new(if Move { pos } if Extend { range.anchor }, pos) // since these all really do the same thing @@ -497,13 +572,13 @@ fn move_next_long_word_end(cx: &mut Context) { ); } -fn move_file_start(cx: &mut Context) { +fn goto_file_start(cx: &mut Context) { push_jump(cx.editor); let (view, doc) = current!(cx.editor); doc.set_selection(view.id, Selection::point(0)); } -fn move_file_end(cx: &mut Context) { +fn goto_file_end(cx: &mut Context) { push_jump(cx.editor); let (view, doc) = current!(cx.editor); let text = doc.text(); @@ -683,24 +758,6 @@ fn extend_prev_char(cx: &mut Context) { ) } -fn extend_first_nonwhitespace(cx: &mut Context) { - let (view, doc) = current!(cx.editor); - doc.set_selection( - view.id, - doc.selection(view.id).clone().transform(|range| { - let text = doc.text(); - let line_idx = text.char_to_line(range.head); - - if let Some(pos) = find_first_non_whitespace_char(text.line(line_idx)) { - let pos = pos + text.line_to_char(line_idx); - Range::new(range.anchor, pos) - } else { - range - } - }), - ); -} - fn replace(cx: &mut Context) { let mut buf = [0u8; 4]; // To hold utf8 encoded char. @@ -880,38 +937,6 @@ fn extend_line_down(cx: &mut Context) { ); } -fn extend_line_end(cx: &mut Context) { - let (view, doc) = current!(cx.editor); - doc.set_selection( - view.id, - doc.selection(view.id).clone().transform(|range| { - let text = doc.text().slice(..); - let line = text.char_to_line(range.head); - - let pos = line_end_char_index(&text, line); - let pos = graphemes::nth_prev_grapheme_boundary(text, pos, 1); - let pos = range.head.max(pos).max(text.line_to_char(line)); - - Range::new(range.anchor, pos) - }), - ); -} - -fn extend_line_start(cx: &mut Context) { - let (view, doc) = current!(cx.editor); - doc.set_selection( - view.id, - doc.selection(view.id).clone().transform(|range| { - let text = doc.text(); - let line = text.char_to_line(range.head); - - // adjust to start of the line - let pos = text.line_to_char(line); - Range::new(range.anchor, pos) - }), - ); -} - fn select_all(cx: &mut Context) { let (view, doc) = current!(cx.editor); @@ -1055,6 +1080,27 @@ fn extend_line(cx: &mut Context) { doc.set_selection(view.id, Selection::single(start, end)); } +fn extend_to_line_bounds(cx: &mut Context) { + let (view, doc) = current!(cx.editor); + + doc.set_selection( + view.id, + doc.selection(view.id).clone().transform(|range| { + let text = doc.text(); + let start = text.line_to_char(text.char_to_line(range.from())); + let end = text + .line_to_char(text.char_to_line(range.to()) + 1) + .saturating_sub(1); + + if range.anchor < range.head { + Range::new(start, end) + } else { + Range::new(end, start) + } + }), + ); +} + fn delete_selection_impl(reg: &mut Register, doc: &mut Document, view_id: ViewId) { let text = doc.text().slice(..); let selection = doc.selection(view_id).clone().min_width_1(text); @@ -1580,6 +1626,24 @@ mod cmd { } } + /// Sets the [`Document`]'s encoding.. + fn set_encoding(cx: &mut compositor::Context, args: &[&str], _: PromptEvent) { + let (_, doc) = current!(cx.editor); + if let Some(label) = args.first() { + doc.set_encoding(label) + .unwrap_or_else(|e| cx.editor.set_error(e.to_string())); + } else { + let encoding = doc.encoding().name().to_string(); + cx.editor.set_status(encoding) + } + } + + /// Reload the [`Document`] from its source file. + fn reload(cx: &mut compositor::Context, _args: &[&str], _: PromptEvent) { + let (view, doc) = current!(cx.editor); + doc.reload(view.id).unwrap(); + } + pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ TypableCommand { name: "quit", @@ -1763,6 +1827,20 @@ mod cmd { fun: show_current_directory, completer: None, }, + TypableCommand { + name: "encoding", + alias: None, + doc: "Set encoding based on `https://encoding.spec.whatwg.org`", + fun: set_encoding, + completer: None, + }, + TypableCommand { + name: "reload", + alias: None, + doc: "Discard changes and reload from the source file.", + fun: reload, + completer: None, + } ]; pub static COMMANDS: Lazy<HashMap<&'static str, &'static TypableCommand>> = Lazy::new(|| { @@ -1955,7 +2033,7 @@ fn symbol_picker(cx: &mut Context) { // I inserts at the first nonwhitespace character of each line with a selection fn prepend_to_line(cx: &mut Context) { - move_first_nonwhitespace(cx); + goto_first_nonwhitespace(cx); let doc = doc_mut!(cx.editor); enter_insert_mode(doc); } @@ -2124,7 +2202,7 @@ fn push_jump(editor: &mut Editor) { view.jumps.push(jump); } -fn switch_to_last_accessed_file(cx: &mut Context) { +fn goto_last_accessed_file(cx: &mut Context) { let alternate_file = view!(cx.editor).last_accessed_doc; if let Some(alt) = alternate_file { cx.editor.switch(alt, Action::Replace); @@ -2133,65 +2211,6 @@ fn switch_to_last_accessed_file(cx: &mut Context) { } } -fn goto_mode(cx: &mut Context) { - if let Some(count) = cx.count { - push_jump(cx.editor); - - let (view, doc) = current!(cx.editor); - let line_idx = std::cmp::min(count.get() - 1, doc.text().len_lines().saturating_sub(2)); - let pos = doc.text().line_to_char(line_idx); - doc.set_selection(view.id, Selection::point(pos)); - return; - } - - cx.on_next_key(move |cx, event| { - if let KeyEvent { - code: KeyCode::Char(ch), - .. - } = event - { - // TODO: temporarily show GOTO in the mode list - let doc = doc_mut!(cx.editor); - match (doc.mode, ch) { - (_, 'g') => move_file_start(cx), - (_, 'e') => move_file_end(cx), - (_, 'a') => switch_to_last_accessed_file(cx), - (Mode::Normal, 'h') => move_line_start(cx), - (Mode::Normal, 'l') => move_line_end(cx), - (Mode::Select, 'h') => extend_line_start(cx), - (Mode::Select, 'l') => extend_line_end(cx), - (_, 'd') => goto_definition(cx), - (_, 'y') => goto_type_definition(cx), - (_, 'r') => goto_reference(cx), - (_, 'i') => goto_implementation(cx), - (Mode::Normal, 's') => move_first_nonwhitespace(cx), - (Mode::Select, 's') => extend_first_nonwhitespace(cx), - - (_, 't') | (_, 'm') | (_, 'b') => { - let (view, doc) = current!(cx.editor); - - let scrolloff = PADDING.min(view.area.height as usize / 2); // TODO: user pref - - let last_line = view.last_line(doc); - - let line = match ch { - 't' => (view.first_line + scrolloff), - 'm' => (view.first_line + (view.area.height as usize / 2)), - 'b' => last_line.saturating_sub(scrolloff), - _ => unreachable!(), - } - .min(last_line.saturating_sub(scrolloff)); - - let pos = doc.text().line_to_char(line); - - doc.set_selection(view.id, Selection::point(pos)); - } - _ => (), - } - } - }) -} - fn select_mode(cx: &mut Context) { let (view, doc) = current!(cx.editor); @@ -2211,13 +2230,27 @@ fn select_mode(cx: &mut Context) { }), ); - doc.mode = Mode::Select; + doc_mut!(cx.editor).mode = Mode::Select; } fn exit_select_mode(cx: &mut Context) { doc_mut!(cx.editor).mode = Mode::Normal; } +fn goto_prehook(cx: &mut Context) -> bool { + if let Some(count) = cx.count { + push_jump(cx.editor); + + let (view, doc) = current!(cx.editor); + let line_idx = std::cmp::min(count.get() - 1, doc.text().len_lines().saturating_sub(1)); + let pos = doc.text().line_to_char(line_idx); + doc.set_selection(view.id, Selection::point(pos)); + true + } else { + false + } +} + fn goto_impl( editor: &mut Editor, compositor: &mut Compositor, @@ -3457,33 +3490,6 @@ fn select_register(cx: &mut Context) { }) } -fn space_mode(cx: &mut Context) { - cx.on_next_key(move |cx, event| { - if let KeyEvent { - code: KeyCode::Char(ch), - .. - } = event - { - // TODO: temporarily show SPC in the mode list - match ch { - 'f' => file_picker(cx), - 'b' => buffer_picker(cx), - 's' => symbol_picker(cx), - 'w' => window_mode(cx), - 'y' => yank_joined_to_clipboard(cx), - 'Y' => yank_main_selection_to_clipboard(cx), - 'p' => paste_clipboard_after(cx), - 'P' => paste_clipboard_before(cx), - 'R' => replace_selections_with_clipboard(cx), - // ' ' => toggle_alternate_buffer(cx), - // TODO: temporary since space mode took its old key - ' ' => keep_primary_selection(cx), - _ => (), - } - } - }) -} - fn view_mode(cx: &mut Context) { cx.on_next_key(move |cx, event| { if let KeyEvent { @@ -3559,6 +3565,9 @@ fn right_bracket_mode(cx: &mut Context) { }) } +use helix_core::surround; +use helix_core::textobject; + fn match_mode(cx: &mut Context) { let count = cx.count; cx.on_next_key(move |cx, event| { @@ -3574,13 +3583,41 @@ fn match_mode(cx: &mut Context) { 's' => surround_add(cx), 'r' => surround_replace(cx), 'd' => surround_delete(cx), + 'a' => select_textobject(cx, textobject::TextObject::Around), + 'i' => select_textobject(cx, textobject::TextObject::Inside), _ => (), } } }) } -use helix_core::surround; +fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) { + let count = cx.count(); + cx.on_next_key(move |cx, event| { + if let KeyEvent { + code: KeyCode::Char(ch), + .. + } = event + { + let (view, doc) = current!(cx.editor); + + doc.set_selection( + view.id, + doc.selection(view.id).clone().transform(|range| { + let text = doc.text().slice(..); + match ch { + 'w' => textobject::textobject_word(text, range, objtype, count), + // TODO: cancel new ranges if inconsistent surround matches across lines + ch if !ch.is_ascii_alphanumeric() => { + textobject::textobject_surround(text, range, objtype, ch, count) + } + _ => range, + } + }), + ); + } + }) +} fn surround_add(cx: &mut Context) { cx.on_next_key(move |cx, event| { @@ -3671,3 +3708,132 @@ fn surround_delete(cx: &mut Context) { } }) } + +/// Do nothing, just for modeinfo. +fn noop(_cx: &mut Context) -> bool { + false +} + +/// Generate modeinfo. +/// +/// If prehook returns true then it will stop the rest. +macro_rules! mode_info { + // TODO: reuse $mode for $stat + (@join $first:expr $(,$rest:expr)*) => { + concat!($first, $(", ", $rest),*) + }; + (@name #[doc = $name:literal] $(#[$rest:meta])*) => { + $name + }; + { + #[doc = $name:literal] $(#[$doc:meta])* $mode:ident, $stat:ident, + $(#[doc = $desc:literal] $($key:tt)|+ => $func:expr),+, + } => { + mode_info! { + #[doc = $name] + $(#[$doc])* + $mode, $stat, noop, + $( + #[doc = $desc] + $($key)|+ => $func + ),+, + } + }; + { + #[doc = $name:literal] $(#[$doc:meta])* $mode:ident, $stat:ident, $prehook:expr, + $(#[doc = $desc:literal] $($key:tt)|+ => $func:expr),+, + } => { + #[doc = $name] + $(#[$doc])* + #[doc = ""] + #[doc = "<table><tr><th>key</th><th>desc</th></tr><tbody>"] + $( + #[doc = "<tr><td>"] + // TODO switch to this once we use rust 1.54 + // right now it will produce multiple rows + // #[doc = mode_info!(@join $($key),+)] + $( + #[doc = $key] + )+ + // <- + #[doc = "</td><td>"] + #[doc = $desc] + #[doc = "</td></tr>"] + )+ + #[doc = "</tbody></table>"] + pub fn $mode(cx: &mut Context) { + if $prehook(cx) { + return; + } + static $stat: OnceCell<Info> = OnceCell::new(); + cx.editor.autoinfo = Some($stat.get_or_init(|| Info::key( + $name.trim(), + vec![$((&[$($key.parse().unwrap()),+], $desc)),+], + ))); + use helix_core::hashmap; + // TODO: try and convert this to match later + let map = hashmap! { + $($($key.parse::<KeyEvent>().unwrap() => $func as for<'r, 's> fn(&'r mut Context<'s>)),+),* + }; + cx.on_next_key_mode(map); + } + }; +} + +mode_info! { + /// space mode + space_mode, SPACE_MODE, + /// file picker + "f" => file_picker, + /// buffer picker + "b" => buffer_picker, + /// symbol picker + "s" => symbol_picker, + /// window mode + "w" => window_mode, + /// yank joined to clipboard + "y" => yank_joined_to_clipboard, + /// yank main selection to clipboard + "Y" => yank_main_selection_to_clipboard, + /// paste system clipboard after selections + "p" => paste_clipboard_after, + /// paste system clipboard before selections + "P" => paste_clipboard_before, + /// replace selections with clipboard + "R" => replace_selections_with_clipboard, + /// keep primary selection + "space" => keep_primary_selection, +} + +mode_info! { + /// goto mode + /// + /// When specified with a count, it will go to that line without entering the mode. + goto_mode, GOTO_MODE, goto_prehook, + /// file start + "g" => goto_file_start, + /// file end + "e" => goto_file_end, + /// line start + "h" => goto_line_start, + /// line end + "l" => goto_line_end, + /// line first non blank + "s" => goto_first_nonwhitespace, + /// definition + "d" => goto_definition, + /// type references + "y" => goto_type_definition, + /// references + "r" => goto_reference, + /// implementation + "i" => goto_implementation, + /// window top + "t" => goto_window_top, + /// window middle + "m" => goto_window_middle, + /// window bottom + "b" => goto_window_bottom, + /// last accessed file + "a" => goto_last_accessed_file, +} diff --git a/helix-term/src/job.rs b/helix-term/src/job.rs index c2873513..2ac41926 100644 --- a/helix-term/src/job.rs +++ b/helix-term/src/job.rs @@ -16,9 +16,9 @@ pub struct Job { #[derive(Default)] pub struct Jobs { - futures: FuturesUnordered<JobFuture>, + pub futures: FuturesUnordered<JobFuture>, /// These are the ones that need to complete before we exit. - wait_futures: FuturesUnordered<JobFuture>, + pub wait_futures: FuturesUnordered<JobFuture>, } impl Job { @@ -77,11 +77,11 @@ impl Jobs { } } - pub fn next_job( - &mut self, - ) -> impl Future<Output = Option<anyhow::Result<Option<Callback>>>> + '_ { - future::select(self.futures.next(), self.wait_futures.next()) - .map(|either| either.factor_first().0) + pub async fn next_job(&mut self) -> Option<anyhow::Result<Option<Callback>>> { + tokio::select! { + event = self.futures.next() => { event } + event = self.wait_futures.next() => { event } + } } pub fn add(&mut self, j: Job) { diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 53588a2b..d815e006 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -1,118 +1,25 @@ pub use crate::commands::Command; use crate::config::Config; use helix_core::hashmap; -use helix_view::{ - document::Mode, - input::KeyEvent, - keyboard::{KeyCode, KeyModifiers}, -}; +use helix_view::{document::Mode, input::KeyEvent}; use serde::Deserialize; use std::{ collections::HashMap, ops::{Deref, DerefMut}, }; -// Kakoune-inspired: -// mode = { -// normal = { -// q = record_macro -// w = (next) word -// W = next WORD -// e = end of word -// E = end of WORD -// r = replace -// R = replace with yanked -// t = 'till char -// y = yank -// u = undo -// U = redo -// i = insert -// I = INSERT (start of line) -// o = open below (insert on new line below) -// O = open above (insert on new line above) -// p = paste (before cursor) -// P = PASTE (after cursor) -// ` = -// [ = select to text object start (alt = select whole object) -// ] = select to text object end -// { = extend to inner object start -// } = extend to inner object end -// a = append -// A = APPEND (end of line) -// s = split -// S = select -// d = delete() -// f = find_char() -// g = goto (gg, G, gc, gd, etc) -// -// h = move_char_left(n) || arrow-left = move_char_left(n) -// j = move_line_down(n) || arrow-down = move_line_down(n) -// k = move_line_up(n) || arrow_up = move_line_up(n) -// l = move_char_right(n) || arrow-right = move_char_right(n) -// : = command line -// ; = collapse selection to cursor -// " = use register -// ` = convert case? (to lower) (alt = swap case) -// ~ = convert to upper case -// . = repeat last command -// \ = disable hook? -// / = search -// > = indent -// < = deindent -// % = select whole buffer (in vim = jump to matching bracket) -// * = search pattern in selection -// ( = rotate main selection backward -// ) = rotate main selection forward -// - = trim selections? (alt = merge contiguous sel together) -// @ = convert tabs to spaces -// & = align cursor -// ? = extend to next given regex match (alt = to prev) -// -// in kakoune these are alt-h alt-l / gh gl -// select from curs to begin end / move curs to begin end -// 0 = start of line -// ^ = start of line(first non blank char) || Home = start of line(first non blank char) -// $ = end of line || End = end of line -// -// z = save selections -// Z = restore selections -// x = select line -// X = extend line -// c = change selected text -// C = copy selection? -// v = view menu (viewport manipulation) -// b = select to previous word start -// B = select to previous WORD start -// -// -// -// -// -// -// = = align? -// + = -// } -// -// gd = goto definition -// gr = goto reference -// [d = previous diagnostic -// d] = next diagnostic -// [D = first diagnostic -// D] = last diagnostic -// } - #[macro_export] macro_rules! key { ($key:ident) => { KeyEvent { - code: KeyCode::$key, - modifiers: KeyModifiers::NONE, + code: ::helix_view::keyboard::KeyCode::$key, + modifiers: ::helix_view::keyboard::KeyModifiers::NONE, } }; ($($ch:tt)*) => { KeyEvent { - code: KeyCode::Char($($ch)*), - modifiers: KeyModifiers::NONE, + code: ::helix_view::keyboard::KeyCode::Char($($ch)*), + modifiers: ::helix_view::keyboard::KeyModifiers::NONE, } }; } @@ -120,8 +27,8 @@ macro_rules! key { macro_rules! ctrl { ($($ch:tt)*) => { KeyEvent { - code: KeyCode::Char($($ch)*), - modifiers: KeyModifiers::CONTROL, + code: ::helix_view::keyboard::KeyCode::Char($($ch)*), + modifiers: ::helix_view::keyboard::KeyModifiers::CONTROL, } }; } @@ -129,8 +36,8 @@ macro_rules! ctrl { macro_rules! alt { ($($ch:tt)*) => { KeyEvent { - code: KeyCode::Char($($ch)*), - modifiers: KeyModifiers::ALT, + code: ::helix_view::keyboard::KeyCode::Char($($ch)*), + modifiers: ::helix_view::keyboard::KeyModifiers::ALT, } }; } @@ -175,8 +82,8 @@ impl Default for Keymaps { key!('r') => Command::replace, key!('R') => Command::replace_with_yanked, - key!(Home) => Command::move_line_start, - key!(End) => Command::move_line_end, + key!(Home) => Command::goto_line_start, + key!(End) => Command::goto_line_end, key!('w') => Command::move_next_word_start, key!('b') => Command::move_prev_word_start, @@ -213,7 +120,9 @@ impl Default for Keymaps { alt!(';') => Command::flip_selections, key!('%') => Command::select_all, key!('x') => Command::extend_line, - // extend_to_whole_line, crop_to_whole_line + key!('x') => Command::extend_line, + key!('X') => Command::extend_to_line_bounds, + // crop_to_whole_line key!('m') => Command::match_mode, @@ -307,8 +216,8 @@ impl Default for Keymaps { key!('T') => Command::extend_till_prev_char, key!('F') => Command::extend_prev_char, - key!(Home) => Command::extend_line_start, - key!(End) => Command::extend_line_end, + key!(Home) => Command::goto_line_start, + key!(End) => Command::goto_line_end, key!(Esc) => Command::exit_select_mode, ) .into_iter(), @@ -331,8 +240,8 @@ impl Default for Keymaps { key!(Right) => Command::move_char_right, key!(PageUp) => Command::page_up, key!(PageDown) => Command::page_down, - key!(Home) => Command::move_line_start, - key!(End) => Command::move_line_end, + key!(Home) => Command::goto_line_start, + key!(End) => Command::goto_line_end_newline, ctrl!('x') => Command::completion, ctrl!('w') => Command::delete_word_backward, ), @@ -352,6 +261,7 @@ pub fn merge_keys(mut config: Config) -> Config { #[test] fn merge_partial_keys() { + use helix_view::keyboard::{KeyCode, KeyModifiers}; let config = Config { keys: Keymaps(hashmap! { Mode::Normal => hashmap! { diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index dab654ad..d374d9b6 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -738,6 +738,11 @@ impl Component for EditorView { self.render_view(doc, view, area, surface, &cx.editor.theme, is_focused); } + if let Some(info) = std::mem::take(&mut cx.editor.autoinfo) { + info.render(area, surface, cx); + cx.editor.autoinfo = Some(info); + } + // render status msg if let Some((status_msg, severity)) = &cx.editor.status_msg { use helix_view::editor::Severity; @@ -756,8 +761,7 @@ impl Component for EditorView { } if let Some(completion) = &self.completion { - completion.render(area, surface, cx) - // render completion here + completion.render(area, surface, cx); } } diff --git a/helix-term/src/ui/info.rs b/helix-term/src/ui/info.rs new file mode 100644 index 00000000..e5f20562 --- /dev/null +++ b/helix-term/src/ui/info.rs @@ -0,0 +1,30 @@ +use crate::compositor::{Component, Context}; +use helix_view::graphics::Rect; +use helix_view::info::Info; +use tui::buffer::Buffer as Surface; +use tui::widgets::{Block, Borders, Widget}; + +impl Component for Info { + fn render(&self, viewport: Rect, surface: &mut Surface, cx: &mut Context) { + let style = cx.editor.theme.get("ui.popup"); + let block = Block::default() + .title(self.title) + .borders(Borders::ALL) + .border_style(style); + let Info { width, height, .. } = self; + let (w, h) = (*width + 2, *height + 2); + // -2 to subtract command line + statusline. a bit of a hack, because of splits. + let area = viewport.intersection(Rect::new( + viewport.width.saturating_sub(w), + viewport.height.saturating_sub(h + 2), + w, + h, + )); + surface.clear_with(area, style); + let Rect { x, y, .. } = block.inner(area); + for (y, line) in (y..).zip(self.text.lines()) { + surface.set_string(x, y, line, style); + } + block.render(area, surface); + } +} diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 7111c968..288d3d2e 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -1,5 +1,6 @@ mod completion; mod editor; +mod info; mod markdown; mod menu; mod picker; |