aboutsummaryrefslogtreecommitdiff
path: root/helix-term/src/ui
diff options
context:
space:
mode:
Diffstat (limited to 'helix-term/src/ui')
-rw-r--r--helix-term/src/ui/editor.rs41
-rw-r--r--helix-term/src/ui/markdown.rs14
-rw-r--r--helix-term/src/ui/menu.rs57
-rw-r--r--helix-term/src/ui/mod.rs2
-rw-r--r--helix-term/src/ui/picker.rs73
-rw-r--r--helix-term/src/ui/popup.rs28
-rw-r--r--helix-term/src/ui/prompt.rs158
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),