diff options
Diffstat (limited to 'helix-term/src/ui')
-rw-r--r-- | helix-term/src/ui/editor.rs | 41 | ||||
-rw-r--r-- | helix-term/src/ui/markdown.rs | 14 | ||||
-rw-r--r-- | helix-term/src/ui/menu.rs | 57 | ||||
-rw-r--r-- | helix-term/src/ui/mod.rs | 2 | ||||
-rw-r--r-- | helix-term/src/ui/picker.rs | 73 | ||||
-rw-r--r-- | helix-term/src/ui/popup.rs | 28 | ||||
-rw-r--r-- | helix-term/src/ui/prompt.rs | 158 |
7 files changed, 128 insertions, 245 deletions
diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 2ee7f0ea..01554c64 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -17,7 +17,7 @@ use helix_core::{ }; use helix_dap::{Breakpoint, SourceBreakpoint, StackFrame}; use helix_view::{ - document::Mode, + document::{Mode, SCRATCH_BUFFER_NAME}, editor::LineNumber, graphics::{Color, CursorKind, Modifier, Rect, Style}, info::Info, @@ -610,7 +610,7 @@ impl EditorView { use tui::{ layout::Alignment, text::Text, - widgets::{Paragraph, Widget}, + widgets::{Paragraph, Widget, Wrap}, }; let cursor = doc @@ -665,8 +665,10 @@ impl EditorView { } } - let paragraph = Paragraph::new(lines).alignment(Alignment::Right); - let width = 80.min(viewport.width); + let paragraph = Paragraph::new(lines) + .alignment(Alignment::Right) + .wrap(Wrap { trim: true }); + let width = 100.min(viewport.width); let height = 15.min(viewport.height); paragraph.render( Rect::new(viewport.right() - width, viewport.y + 1, width, height), @@ -716,18 +718,20 @@ impl EditorView { } surface.set_string(viewport.x + 5, viewport.y, progress, base_style); - if let Some(path) = doc.relative_path() { - let path = path.to_string_lossy(); + let rel_path = doc.relative_path(); + let path = rel_path + .as_ref() + .map(|p| p.to_string_lossy()) + .unwrap_or_else(|| SCRATCH_BUFFER_NAME.into()); - let title = format!("{}{}", path, if doc.is_modified() { "[+]" } else { "" }); - surface.set_stringn( - viewport.x + 8, - viewport.y, - title, - viewport.width.saturating_sub(6) as usize, - base_style, - ); - } + let title = format!("{}{}", path, if doc.is_modified() { "[+]" } else { "" }); + surface.set_stringn( + viewport.x + 8, + viewport.y, + title, + viewport.width.saturating_sub(6) as usize, + base_style, + ); //------------------------------- // Right side of the status line. @@ -830,6 +834,11 @@ impl EditorView { match &key_result.kind { KeymapResultKind::Matched(command) => command.execute(cxt), KeymapResultKind::Pending(node) => self.autoinfo = Some(node.infobox()), + KeymapResultKind::MatchedSequence(commands) => { + for command in commands { + command.execute(cxt); + } + } KeymapResultKind::NotFound | KeymapResultKind::Cancelled(_) => return Some(key_result), } None @@ -871,7 +880,7 @@ impl EditorView { std::num::NonZeroUsize::new(cxt.editor.count.map_or(i, |c| c.get() * 10 + i)); } // special handling for repeat operator - key!('.') => { + 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 diff --git a/helix-term/src/ui/markdown.rs b/helix-term/src/ui/markdown.rs index 4144ed3c..61630d55 100644 --- a/helix-term/src/ui/markdown.rs +++ b/helix-term/src/ui/markdown.rs @@ -13,7 +13,7 @@ use helix_core::{ Rope, }; use helix_view::{ - graphics::{Color, Margin, Rect, Style}, + graphics::{Margin, Rect}, Theme, }; @@ -61,9 +61,15 @@ fn parse<'a>( }) } - let text_style = Style::default().fg(Color::Rgb(164, 160, 232)); // lavender - let code_style = Style::default().fg(Color::Rgb(255, 255, 255)); // white - let heading_style = Style::default().fg(Color::Rgb(219, 191, 239)); // lilac + let text_style = theme.map(|theme| theme.get("ui.text")).unwrap_or_default(); + + // TODO: use better scopes for these, `markup.raw.block`, `markup.heading` + let code_style = theme + .map(|theme| theme.get("ui.text.focus")) + .unwrap_or_default(); // white + let heading_style = theme + .map(|theme| theme.get("ui.linenr.selected")) + .unwrap_or_default(); // lilac for event in parser { match event { diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs index 3c492d14..e891c149 100644 --- a/helix-term/src/ui/menu.rs +++ b/helix-term/src/ui/menu.rs @@ -1,5 +1,8 @@ -use crate::compositor::{Component, Compositor, Context, EventResult}; -use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers}; +use crate::{ + compositor::{Component, Compositor, Context, EventResult}, + ctrl, key, shift, +}; +use crossterm::event::Event; use tui::{buffer::Buffer as Surface, widgets::Table}; pub use tui::widgets::{Cell, Row}; @@ -192,63 +195,25 @@ impl<T: Item + 'static> Component for Menu<T> { compositor.pop(); }))); - match event { + match event.into() { // esc or ctrl-c aborts the completion and closes the menu - KeyEvent { - code: KeyCode::Esc, .. - } - | KeyEvent { - code: KeyCode::Char('c'), - modifiers: KeyModifiers::CONTROL, - } => { + key!(Esc) | ctrl!('c') => { (self.callback_fn)(cx.editor, self.selection(), MenuEvent::Abort); return close_fn; } // arrow up/ctrl-p/shift-tab prev completion choice (including updating the doc) - KeyEvent { - code: KeyCode::BackTab, - .. - } - | KeyEvent { - code: KeyCode::Up, .. - } - | KeyEvent { - code: KeyCode::Char('p'), - modifiers: KeyModifiers::CONTROL, - } - | KeyEvent { - code: KeyCode::Char('k'), - modifiers: KeyModifiers::CONTROL, - } => { + shift!(BackTab) | key!(Up) | ctrl!('p') | ctrl!('k') => { self.move_up(); (self.callback_fn)(cx.editor, self.selection(), MenuEvent::Update); return EventResult::Consumed(None); } - // arrow down/ctrl-n/tab advances completion choice (including updating the doc) - KeyEvent { - code: KeyCode::Tab, - modifiers: KeyModifiers::NONE, - } - | KeyEvent { - code: KeyCode::Down, - .. - } - | KeyEvent { - code: KeyCode::Char('n'), - modifiers: KeyModifiers::CONTROL, - } - | KeyEvent { - code: KeyCode::Char('j'), - modifiers: KeyModifiers::CONTROL, - } => { + key!(Tab) | key!(Down) | ctrl!('n') | ctrl!('j') => { + // arrow down/ctrl-n/tab advances completion choice (including updating the doc) self.move_down(); (self.callback_fn)(cx.editor, self.selection(), MenuEvent::Update); return EventResult::Consumed(None); } - KeyEvent { - code: KeyCode::Enter, - .. - } => { + key!(Enter) => { if let Some(selection) = self.selection() { (self.callback_fn)(cx.editor, Some(selection), MenuEvent::Validate); } diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index d634bc4a..0915937d 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -35,6 +35,7 @@ pub fn regex_prompt( let (view, doc) = current!(cx.editor); let view_id = view.id; let snapshot = doc.selection(view_id).clone(); + let offset_snapshot = view.offset; Prompt::new( prompt, @@ -45,6 +46,7 @@ pub fn regex_prompt( PromptEvent::Abort => { let (view, doc) = current!(cx.editor); doc.set_selection(view.id, snapshot.clone()); + view.offset = offset_snapshot; } PromptEvent::Validate => { // TODO: push_jump to store selection just before jump diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 01e5f6c3..2b2e47a4 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -1,8 +1,9 @@ use crate::{ compositor::{Component, Compositor, Context, EventResult}, + ctrl, key, shift, ui::EditorView, }; -use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers}; +use crossterm::event::Event; use tui::{ buffer::Buffer as Surface, widgets::{Block, BorderType, Borders}, @@ -36,6 +37,7 @@ type FileLocation = (PathBuf, Option<(usize, usize)>); pub struct FilePicker<T> { picker: Picker<T>, + pub truncate_start: bool, /// Caches paths to documents preview_cache: HashMap<PathBuf, CachedPreview>, read_buffer: Vec<u8>, @@ -89,6 +91,7 @@ impl<T> FilePicker<T> { ) -> Self { Self { picker: Picker::new(false, options, format_fn, callback_fn), + truncate_start: true, preview_cache: HashMap::new(), read_buffer: Vec::with_capacity(1024), file_fn: Box::new(preview_fn), @@ -171,6 +174,7 @@ impl<T: 'static> Component for FilePicker<T> { }; let picker_area = area.with_width(picker_width); + self.picker.truncate_start = self.truncate_start; self.picker.render(picker_area, surface, cx); if !render_preview { @@ -276,6 +280,8 @@ pub struct Picker<T> { prompt: Prompt, /// Whether to render in the middle of the area render_centered: bool, + /// Wheather to truncate the start (default true) + pub truncate_start: bool, format_fn: Box<dyn Fn(&T) -> Cow<str>>, callback_fn: Box<dyn Fn(&mut Context, &T, Action)>, @@ -306,6 +312,7 @@ impl<T> Picker<T> { cursor: 0, prompt, render_centered, + truncate_start: true, format_fn: Box::new(format_fn), callback_fn: Box::new(callback_fn), }; @@ -403,81 +410,35 @@ impl<T: 'static> Component for Picker<T> { compositor.last_picker = compositor.pop(); }))); - match key_event { - KeyEvent { - code: KeyCode::Up, .. - } - | KeyEvent { - code: KeyCode::BackTab, - .. - } - | KeyEvent { - code: KeyCode::Char('k'), - modifiers: KeyModifiers::CONTROL, - } - | KeyEvent { - code: KeyCode::Char('p'), - modifiers: KeyModifiers::CONTROL, - } => { + match key_event.into() { + shift!(BackTab) | key!(Up) | ctrl!('p') | ctrl!('k') => { self.move_up(); } - KeyEvent { - code: KeyCode::Down, - .. - } - | KeyEvent { - code: KeyCode::Tab, .. - } - | KeyEvent { - code: KeyCode::Char('j'), - modifiers: KeyModifiers::CONTROL, - } - | KeyEvent { - code: KeyCode::Char('n'), - modifiers: KeyModifiers::CONTROL, - } => { + key!(Tab) | key!(Down) | ctrl!('n') | ctrl!('j') => { self.move_down(); } - KeyEvent { - code: KeyCode::Esc, .. - } - | KeyEvent { - code: KeyCode::Char('c'), - modifiers: KeyModifiers::CONTROL, - } => { + key!(Esc) | ctrl!('c') => { return close_fn; } - KeyEvent { - code: KeyCode::Enter, - .. - } => { + key!(Enter) => { if let Some(option) = self.selection() { (self.callback_fn)(cx, option, Action::Replace); } return close_fn; } - KeyEvent { - code: KeyCode::Char('s'), - modifiers: KeyModifiers::CONTROL, - } => { + ctrl!('s') => { if let Some(option) = self.selection() { (self.callback_fn)(cx, option, Action::HorizontalSplit); } return close_fn; } - KeyEvent { - code: KeyCode::Char('v'), - modifiers: KeyModifiers::CONTROL, - } => { + ctrl!('v') => { if let Some(option) = self.selection() { (self.callback_fn)(cx, option, Action::VerticalSplit); } return close_fn; } - KeyEvent { - code: KeyCode::Char(' '), - modifiers: KeyModifiers::CONTROL, - } => { + ctrl!(' ') => { self.save_filter(); } _ => { @@ -567,7 +528,7 @@ impl<T: 'static> Component for Picker<T> { text_style }, true, - true, + self.truncate_start, ); } } diff --git a/helix-term/src/ui/popup.rs b/helix-term/src/ui/popup.rs index 1bab1eae..8f7921a1 100644 --- a/helix-term/src/ui/popup.rs +++ b/helix-term/src/ui/popup.rs @@ -1,5 +1,8 @@ -use crate::compositor::{Component, Compositor, Context, EventResult}; -use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers}; +use crate::{ + compositor::{Component, Compositor, Context, EventResult}, + ctrl, key, +}; +use crossterm::event::Event; use tui::buffer::Buffer as Surface; use helix_core::Position; @@ -95,27 +98,14 @@ impl<T: Component> Component for Popup<T> { compositor.pop(); }))); - match key { + match key.into() { // esc or ctrl-c aborts the completion and closes the menu - KeyEvent { - code: KeyCode::Esc, .. - } - | KeyEvent { - code: KeyCode::Char('c'), - modifiers: KeyModifiers::CONTROL, - } => close_fn, - - KeyEvent { - code: KeyCode::Char('d'), - modifiers: KeyModifiers::CONTROL, - } => { + key!(Esc) | ctrl!('c') => close_fn, + ctrl!('d') => { self.scroll(self.size.1 as usize / 2, true); EventResult::Consumed(None) } - KeyEvent { - code: KeyCode::Char('u'), - modifiers: KeyModifiers::CONTROL, - } => { + ctrl!('u') => { self.scroll(self.size.1 as usize / 2, false); EventResult::Consumed(None) } diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index 593fd934..00ffdccf 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -1,6 +1,8 @@ use crate::compositor::{Component, Compositor, Context, EventResult}; -use crate::ui; -use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers}; +use crate::{alt, ctrl, key, shift, ui}; +use crossterm::event::Event; +use helix_view::input::KeyEvent; +use helix_view::keyboard::{KeyCode, KeyModifiers}; use std::{borrow::Cow, ops::RangeFrom}; use tui::buffer::Buffer as Surface; @@ -213,6 +215,14 @@ impl Prompt { self.completion = (self.completion_fn)(&self.line); } + pub fn delete_char_forwards(&mut self) { + let pos = self.eval_movement(Movement::ForwardChar(1)); + self.line.replace_range(self.cursor..pos, ""); + + self.exit_selection(); + self.completion = (self.completion_fn)(&self.line); + } + pub fn delete_word_backwards(&mut self) { let pos = self.eval_movement(Movement::BackwardWord(1)); self.line.replace_range(pos..self.cursor, ""); @@ -222,6 +232,23 @@ impl Prompt { self.completion = (self.completion_fn)(&self.line); } + pub fn delete_word_forwards(&mut self) { + let pos = self.eval_movement(Movement::ForwardWord(1)); + self.line.replace_range(self.cursor..pos, ""); + + self.exit_selection(); + self.completion = (self.completion_fn)(&self.line); + } + + pub fn kill_to_start_of_line(&mut self) { + let pos = self.eval_movement(Movement::StartOfLine); + self.line.replace_range(pos..self.cursor, ""); + self.cursor = pos; + + self.exit_selection(); + self.completion = (self.completion_fn)(&self.line); + } + pub fn kill_to_end_of_line(&mut self) { let pos = self.eval_movement(Movement::EndOfLine); self.line.replace_range(self.cursor..pos, ""); @@ -405,84 +432,30 @@ impl Component for Prompt { compositor.pop(); }))); - match event { - KeyEvent { - code: KeyCode::Char('c'), - modifiers: KeyModifiers::CONTROL, - } - | KeyEvent { - code: KeyCode::Esc, .. - } => { + match event.into() { + ctrl!('c') | key!(Esc) => { (self.callback_fn)(cx, &self.line, PromptEvent::Abort); return close_fn; } - KeyEvent { - code: KeyCode::Left, - modifiers: KeyModifiers::ALT, - } - | KeyEvent { - code: KeyCode::Char('b'), - modifiers: KeyModifiers::ALT, - } => self.move_cursor(Movement::BackwardWord(1)), - KeyEvent { - code: KeyCode::Right, - modifiers: KeyModifiers::ALT, - } - | KeyEvent { - code: KeyCode::Char('f'), - modifiers: KeyModifiers::ALT, - } => self.move_cursor(Movement::ForwardWord(1)), - KeyEvent { - code: KeyCode::Char('f'), - modifiers: KeyModifiers::CONTROL, - } - | KeyEvent { - code: KeyCode::Right, - .. - } => self.move_cursor(Movement::ForwardChar(1)), - KeyEvent { - code: KeyCode::Char('b'), - modifiers: KeyModifiers::CONTROL, - } - | KeyEvent { - code: KeyCode::Left, - .. - } => self.move_cursor(Movement::BackwardChar(1)), - KeyEvent { - code: KeyCode::End, - modifiers: KeyModifiers::NONE, - } - | KeyEvent { - code: KeyCode::Char('e'), - modifiers: KeyModifiers::CONTROL, - } => self.move_end(), - KeyEvent { - code: KeyCode::Home, - modifiers: KeyModifiers::NONE, - } - | KeyEvent { - code: KeyCode::Char('a'), - modifiers: KeyModifiers::CONTROL, - } => self.move_start(), - KeyEvent { - code: KeyCode::Char('w'), - modifiers: KeyModifiers::CONTROL, - } => self.delete_word_backwards(), - KeyEvent { - code: KeyCode::Char('k'), - modifiers: KeyModifiers::CONTROL, - } => self.kill_to_end_of_line(), - KeyEvent { - code: KeyCode::Backspace, - modifiers: KeyModifiers::NONE, - } => { + alt!('b') | alt!(Left) => self.move_cursor(Movement::BackwardWord(1)), + alt!('f') | alt!(Right) => self.move_cursor(Movement::ForwardWord(1)), + ctrl!('b') | key!(Left) => self.move_cursor(Movement::BackwardChar(1)), + ctrl!('f') | key!(Right) => self.move_cursor(Movement::ForwardChar(1)), + ctrl!('e') | key!(End) => self.move_end(), + ctrl!('a') | key!(Home) => self.move_start(), + ctrl!('w') => self.delete_word_backwards(), + alt!('d') => self.delete_word_forwards(), + ctrl!('k') => self.kill_to_end_of_line(), + ctrl!('u') => self.kill_to_start_of_line(), + ctrl!('h') | key!(Backspace) => { self.delete_char_backwards(); (self.callback_fn)(cx, &self.line, PromptEvent::Update); } - KeyEvent { - code: KeyCode::Char('s'), - modifiers: KeyModifiers::CONTROL, - } => { + ctrl!('d') | key!(Delete) => { + self.delete_char_forwards(); + (self.callback_fn)(cx, &self.line, PromptEvent::Update); + } + ctrl!('s') => { let (view, doc) = current!(cx.editor); let text = doc.text().slice(..); @@ -492,6 +465,7 @@ impl Component for Prompt { doc.selection(view.id).primary(), textobject::TextObject::Inside, 1, + false, ); let line = text.slice(range.from()..range.to()).to_string(); if !line.is_empty() { @@ -499,10 +473,7 @@ impl Component for Prompt { (self.callback_fn)(cx, &self.line, PromptEvent::Update); } } - KeyEvent { - code: KeyCode::Enter, - .. - } => { + key!(Enter) => { if self.selection.is_some() && self.line.ends_with('/') { self.completion = (self.completion_fn)(&self.line); self.exit_selection(); @@ -517,50 +488,29 @@ impl Component for Prompt { return close_fn; } } - KeyEvent { - code: KeyCode::Char('p'), - modifiers: KeyModifiers::CONTROL, - } - | KeyEvent { - code: KeyCode::Up, .. - } => { + ctrl!('p') | key!(Up) => { if let Some(register) = self.history_register { let register = cx.editor.registers.get_mut(register); self.change_history(register.read(), CompletionDirection::Backward); (self.callback_fn)(cx, &self.line, PromptEvent::Update); } } - KeyEvent { - code: KeyCode::Char('n'), - modifiers: KeyModifiers::CONTROL, - } - | KeyEvent { - code: KeyCode::Down, - .. - } => { + ctrl!('n') | key!(Down) => { if let Some(register) = self.history_register { let register = cx.editor.registers.get_mut(register); self.change_history(register.read(), CompletionDirection::Forward); (self.callback_fn)(cx, &self.line, PromptEvent::Update); } } - KeyEvent { - code: KeyCode::Tab, .. - } => { + key!(Tab) => { self.change_completion_selection(CompletionDirection::Forward); (self.callback_fn)(cx, &self.line, PromptEvent::Update) } - KeyEvent { - code: KeyCode::BackTab, - .. - } => { + shift!(BackTab) => { self.change_completion_selection(CompletionDirection::Backward); (self.callback_fn)(cx, &self.line, PromptEvent::Update) } - KeyEvent { - code: KeyCode::Char('q'), - modifiers: KeyModifiers::CONTROL, - } => self.exit_selection(), + ctrl!('q') => self.exit_selection(), // any char event that's not combined with control or mapped to any other combo KeyEvent { code: KeyCode::Char(c), |