fix: honor configured wrap bindings in shell output
This commit is contained in:
+1
-1
@@ -202,7 +202,7 @@ Use `:!COMMAND` (for example `:!pwd` or `:!echo "$PATH"`) to run a shell command
|
|||||||
|
|
||||||
While running, the default top bar shows the first three command graphemes, `...` when truncated, and an animated spinner to the right of the battery. Completion opens a read-only floating window with the command, exit status (or launch error), stdout, and separately marked stderr. Arrows/Ctrl+h/j/k/l and the mouse wheel scroll; Home resets, End goes to the last line, and Enter/Escape dismisses it and resumes the underlying editing mode. Output is decoded as UTF-8 with replacements; controls are displayed as text, never interpreted as terminal escapes. Capture retains up to 4 MiB per stream with an explicit truncation notice. There is no command timeout; exiting Vamp kills the running process group, and background jobs in that group are also stopped when the command exits. Shell execution currently requires Unix.
|
While running, the default top bar shows the first three command graphemes, `...` when truncated, and an animated spinner to the right of the battery. Completion opens a read-only floating window with the command, exit status (or launch error), stdout, and separately marked stderr. Arrows/Ctrl+h/j/k/l and the mouse wheel scroll; Home resets, End goes to the last line, and Enter/Escape dismisses it and resumes the underlying editing mode. Output is decoded as UTF-8 with replacements; controls are displayed as text, never interpreted as terminal escapes. Capture retains up to 4 MiB per stream with an explicit truncation notice. There is no command timeout; exiting Vamp kills the running process group, and background jobs in that group are also stopped when the command exits. Shell execution currently requires Unix.
|
||||||
|
|
||||||
In the shell output window, `w` toggles soft wrapping without changing output bytes or the source buffer's wrap setting. A single-key custom binding to `config.core.word_wrap.toggle` also toggles the popup. Wrapping starts off for each command; toggling resets scrolling to the start. With wrapping on, vertical scrolling follows visual rows and horizontal scrolling is disabled.
|
In the shell output window, your configured `config.core.word_wrap.toggle` binding toggles soft wrapping, including leader and multi-key sequences. Bindings use Normal mode while the popup is focused; there is no additional default shortcut. This does not change output bytes or the source buffer's wrap setting. Wrapping starts off for each command; toggling resets scrolling to the start. With wrapping on, vertical scrolling follows visual rows and horizontal scrolling is disabled.
|
||||||
|
|
||||||
The latest reported operation result appears in a non-focusable popup at the workspace's bottom-right, just above the bottom bar when visible, with one column of inner padding on each side when space permits. It remains until dismissed or replaced, with no timer. In Normal mode, the first Escape dismisses it without collapsing carets; a subsequent Escape retains ordinary Normal behavior. Existing modal windows, focused explorers/diff selectors, and pending prefixes retain Escape priority. New operation messages reopen the popup; routine successful buffer switches do not duplicate the buffer/path information already shown in the bar.
|
The latest reported operation result appears in a non-focusable popup at the workspace's bottom-right, just above the bottom bar when visible, with one column of inner padding on each side when space permits. It remains until dismissed or replaced, with no timer. In Normal mode, the first Escape dismisses it without collapsing carets; a subsequent Escape retains ordinary Normal behavior. Existing modal windows, focused explorers/diff selectors, and pending prefixes retain Escape priority. New operation messages reopen the popup; routine successful buffer switches do not duplicate the buffer/path information already shown in the bar.
|
||||||
|
|
||||||
|
|||||||
+47
-14
@@ -4695,6 +4695,9 @@ impl Editor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn binding_mode(&self) -> Mode {
|
fn binding_mode(&self) -> Mode {
|
||||||
|
if self.command_output.is_some() {
|
||||||
|
return Mode::Normal;
|
||||||
|
}
|
||||||
if self.diff.is_some()
|
if self.diff.is_some()
|
||||||
&& !self.diff_interacting()
|
&& !self.diff_interacting()
|
||||||
&& !matches!(self.active.view.mode, Mode::Command | Mode::Search)
|
&& !matches!(self.active.view.mode, Mode::Command | Mode::Search)
|
||||||
@@ -7008,22 +7011,29 @@ impl Editor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handle_command_output(&mut self, input: &Input) -> bool {
|
fn handle_command_output(&mut self, input: &Input) -> bool {
|
||||||
let toggle = input == &Input::Char('w')
|
if self.command_output.is_none() {
|
||||||
|| matches!(
|
|
||||||
self.bindings
|
|
||||||
.get(&self.binding_mode())
|
|
||||||
.and_then(|keys| keys.get(input)),
|
|
||||||
Some(Binding::Action(Action::WordWrapToggle))
|
|
||||||
);
|
|
||||||
let Some(output) = &mut self.command_output else {
|
|
||||||
return false;
|
return false;
|
||||||
};
|
}
|
||||||
if toggle {
|
if self.sequence_pending().is_some() {
|
||||||
output.wrapping = !output.wrapping;
|
if matches!(input, Input::Escape | Input::Paste(_)) {
|
||||||
output.content.top = 0;
|
self.cancel_sequence();
|
||||||
output.content.left = 0;
|
} else {
|
||||||
|
self.handle_sequence(input.clone());
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
let mode = self.binding_mode();
|
||||||
|
if self.start_sequence(mode, input) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if matches!(
|
||||||
|
self.bindings.get(&mode).and_then(|keys| keys.get(input)),
|
||||||
|
Some(Binding::Action(Action::WordWrapToggle))
|
||||||
|
) {
|
||||||
|
self.act(Action::WordWrapToggle);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let output = self.command_output.as_mut().unwrap();
|
||||||
let rows = if output.wrapping {
|
let rows = if output.wrapping {
|
||||||
output.wrapped_rows.get()
|
output.wrapped_rows.get()
|
||||||
} else {
|
} else {
|
||||||
@@ -8537,8 +8547,18 @@ impl Editor {
|
|||||||
self.pending_sequence.keys.push(input);
|
self.pending_sequence.keys.push(input);
|
||||||
let bindings = self.sequence_bindings.get(&self.binding_mode());
|
let bindings = self.sequence_bindings.get(&self.binding_mode());
|
||||||
match bindings.and_then(|keys| keys.get(&self.pending_sequence)) {
|
match bindings.and_then(|keys| keys.get(&self.pending_sequence)) {
|
||||||
Some(Binding::Action(action)) => return self.act(*action),
|
Some(Binding::Action(action)) => {
|
||||||
|
if self.command_output.is_some() && *action != Action::WordWrapToggle {
|
||||||
|
self.cancel_sequence();
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
return self.act(*action);
|
||||||
|
}
|
||||||
Some(Binding::Command(command)) => {
|
Some(Binding::Command(command)) => {
|
||||||
|
if self.command_output.is_some() {
|
||||||
|
self.cancel_sequence();
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let command = command.clone();
|
let command = command.clone();
|
||||||
return self.execute(&command);
|
return self.execute(&command);
|
||||||
}
|
}
|
||||||
@@ -9749,6 +9769,19 @@ impl Editor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn execute_action(&mut self, entry: &CoreAction, args: &str) -> Option<Invocation> {
|
fn execute_action(&mut self, entry: &CoreAction, args: &str) -> Option<Invocation> {
|
||||||
|
if entry.action == Action::WordWrapToggle
|
||||||
|
&& let Some(output) = &mut self.command_output
|
||||||
|
{
|
||||||
|
if args.is_empty() {
|
||||||
|
output.wrapping = !output.wrapping;
|
||||||
|
output.content.top = 0;
|
||||||
|
output.content.left = 0;
|
||||||
|
} else {
|
||||||
|
self.set_message("The word_wrap.toggle command takes no arguments");
|
||||||
|
}
|
||||||
|
self.clear_sequence();
|
||||||
|
return None;
|
||||||
|
}
|
||||||
if matches!(
|
if matches!(
|
||||||
entry.action,
|
entry.action,
|
||||||
Action::DiffComment
|
Action::DiffComment
|
||||||
|
|||||||
+1
-1
@@ -4715,7 +4715,7 @@ fn draw_diagnostic_panel(
|
|||||||
Paragraph::new(if startup {
|
Paragraph::new(if startup {
|
||||||
"Arrows scroll · Home reset · Enter/Esc continue"
|
"Arrows scroll · Home reset · Enter/Esc continue"
|
||||||
} else {
|
} else {
|
||||||
"w wrap · Arrows scroll · Home reset · Enter/Esc close"
|
"Arrows scroll · Home reset · Enter/Esc close"
|
||||||
})
|
})
|
||||||
.style(Style::default().fg(Color::DarkGray)),
|
.style(Style::default().fg(Color::DarkGray)),
|
||||||
help_area,
|
help_area,
|
||||||
|
|||||||
+46
-1
@@ -15454,6 +15454,10 @@ fn shell_output_panel_clips_sanitized_content_in_offset_and_tiny_areas() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn shell_output_wrap_toggle_reflows_and_scrolls_without_changing_source() {
|
fn shell_output_wrap_toggle_reflows_and_scrolls_without_changing_source() {
|
||||||
let mut session = EditorSession::open(None, vec![], None).unwrap();
|
let mut session = EditorSession::open(None, vec![], None).unwrap();
|
||||||
|
session
|
||||||
|
.editor_mut()
|
||||||
|
.bind("normal", "leader z w", Some("core.word_wrap.toggle".into()))
|
||||||
|
.unwrap();
|
||||||
let source_wrap = session.editor().word_wrap();
|
let source_wrap = session.editor().word_wrap();
|
||||||
let source = session.editor().lines().to_vec();
|
let source = session.editor().lines().to_vec();
|
||||||
session.editor_mut().show_command_output(
|
session.editor_mut().show_command_output(
|
||||||
@@ -15467,6 +15471,10 @@ fn shell_output_wrap_toggle_reflows_and_scrolls_without_changing_source() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(!screen_text(&terminal).contains("TAIL"));
|
assert!(!screen_text(&terminal).contains("TAIL"));
|
||||||
session.handle(Input::Char('w'));
|
session.handle(Input::Char('w'));
|
||||||
|
assert!(!session.editor().command_output().unwrap().wrapping);
|
||||||
|
for ch in " zw".chars() {
|
||||||
|
session.handle(Input::Char(ch));
|
||||||
|
}
|
||||||
terminal
|
terminal
|
||||||
.draw(|frame| views.draw_session(frame, &mut session))
|
.draw(|frame| views.draw_session(frame, &mut session))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -15475,7 +15483,9 @@ fn shell_output_wrap_toggle_reflows_and_scrolls_without_changing_source() {
|
|||||||
assert_eq!(session.editor().command_output().unwrap().content.top, 1);
|
assert_eq!(session.editor().command_output().unwrap().content.top, 1);
|
||||||
session.handle(Input::Right);
|
session.handle(Input::Right);
|
||||||
assert_eq!(session.editor().command_output().unwrap().content.left, 0);
|
assert_eq!(session.editor().command_output().unwrap().content.left, 0);
|
||||||
session.handle(Input::Char('w'));
|
for ch in " zw".chars() {
|
||||||
|
session.handle(Input::Char(ch));
|
||||||
|
}
|
||||||
terminal
|
terminal
|
||||||
.draw(|frame| views.draw_session(frame, &mut session))
|
.draw(|frame| views.draw_session(frame, &mut session))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -15483,3 +15493,38 @@ fn shell_output_wrap_toggle_reflows_and_scrolls_without_changing_source() {
|
|||||||
assert_eq!(session.editor().word_wrap(), source_wrap);
|
assert_eq!(session.editor().word_wrap(), source_wrap);
|
||||||
assert_eq!(session.editor().lines(), source);
|
assert_eq!(session.editor().lines(), source);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn shell_output_wrap_bindings_respect_remaps_disabled_keys_and_sequence_cancellation() {
|
||||||
|
let mut editor = Editor::open(None, vec![]).unwrap();
|
||||||
|
editor
|
||||||
|
.bind("normal", "ctrl+y", Some("core.word_wrap.toggle".into()))
|
||||||
|
.unwrap();
|
||||||
|
editor
|
||||||
|
.bind("normal", "ctrl+w z", Some("core.word_wrap.toggle".into()))
|
||||||
|
.unwrap();
|
||||||
|
editor
|
||||||
|
.bind("normal", "leader x", Some("core.delete".into()))
|
||||||
|
.unwrap();
|
||||||
|
editor.show_command_output("command".into(), Err("output".into()));
|
||||||
|
let source = editor.lines().to_vec();
|
||||||
|
editor.handle(Input::Ctrl('y'));
|
||||||
|
assert!(editor.command_output().unwrap().wrapping);
|
||||||
|
editor.bind("normal", "ctrl+y", None).unwrap();
|
||||||
|
editor.handle(Input::Ctrl('y'));
|
||||||
|
assert!(editor.command_output().unwrap().wrapping);
|
||||||
|
editor.handle(Input::Ctrl('w'));
|
||||||
|
editor.handle(Input::Escape);
|
||||||
|
assert!(editor.command_output().is_some());
|
||||||
|
assert!(editor.sequence_pending().is_none());
|
||||||
|
editor.handle(Input::Ctrl('w'));
|
||||||
|
editor.handle(Input::Char('z'));
|
||||||
|
assert!(!editor.command_output().unwrap().wrapping);
|
||||||
|
editor.handle(Input::Char(' '));
|
||||||
|
editor.handle(Input::Char('x'));
|
||||||
|
assert_eq!(editor.lines(), source);
|
||||||
|
assert!(editor.command_output().is_some());
|
||||||
|
assert!(editor.sequence_pending().is_none());
|
||||||
|
editor.handle(Input::Escape);
|
||||||
|
assert!(editor.command_output().is_none());
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user