summaryrefslogtreecommitdiff
path: root/helix-term
diff options
context:
space:
mode:
authorNathan Vegdahl2021-07-06 03:27:49 +0000
committerNathan Vegdahl2021-07-06 03:27:49 +0000
commit85d5b399de70ff075a455ce2858549d1ed012fe3 (patch)
tree4824fcb47f39551d3b31a62931adaf0ee406c02b /helix-term
parent6e15c9b8745e9708ee5271c8701d41a8393cb038 (diff)
parent3c31f501164080998975883eb6f93c49bd8d3efb (diff)
Merge branch 'master' into great_line_ending_and_cursor_range_cleanup
Diffstat (limited to 'helix-term')
-rw-r--r--helix-term/src/application.rs6
-rw-r--r--helix-term/src/commands.rs478
-rw-r--r--helix-term/src/job.rs14
-rw-r--r--helix-term/src/keymap.rs128
-rw-r--r--helix-term/src/ui/editor.rs8
-rw-r--r--helix-term/src/ui/info.rs30
-rw-r--r--helix-term/src/ui/mod.rs1
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;