feat: run shell commands asynchronously from command mode

This commit is contained in:
2026-10-18 00:30:24 +02:00
parent f942f881e6
commit a5f92452b3
16 changed files with 817 additions and 13 deletions
+42 -6
View File
@@ -1142,7 +1142,10 @@ impl BufferViewports {
let Some(kind) = wheel_kind(&mouse) else {
return false;
};
if editor.palette().is_some() || editor.startup_diagnostic().is_some() {
if editor.palette().is_some()
|| editor.startup_diagnostic().is_some()
|| editor.command_output().is_some()
{
return true;
}
let Some((buffer_id, viewport)) = self.document.geometry else {
@@ -1781,6 +1784,7 @@ fn chrome_items<'a>(
String::new()
}),
"battery" if editor.label(name).is_empty() => Cow::Borrowed(editor.battery_label()),
"shell" if editor.label(name).is_empty() => Cow::Borrowed(editor.shell_label()),
_ => Cow::Borrowed(editor.label(name)),
};
// Empty values remain hidden even when a title or decoration is configured.
@@ -2839,6 +2843,7 @@ fn draw_overlays(
|| editor.palette().is_some()
|| editor.hover().is_some()
|| editor.startup_diagnostic().is_some()
|| editor.command_output().is_some()
|| editor
.file_picker()
.is_some_and(|picker| picker.presentation == FilePresentation::Floating);
@@ -2852,6 +2857,7 @@ fn draw_overlays(
}
}
if !matches!(editor.mode(), Mode::Command | Mode::Search)
&& editor.command_output().is_none()
&& !editor.file_picker().is_some_and(|picker| picker.focused)
&& let Some((_, position)) = caret
&& !panels
@@ -2899,6 +2905,9 @@ fn draw_overlays(
if let Some(diagnostic) = editor.startup_diagnostic() {
draw_startup_diagnostic(frame, diagnostic, area);
}
if let Some(output) = editor.command_output() {
draw_diagnostic_panel(frame, &output.content, area, &output.title, false);
}
}
struct DiffGeometry {
@@ -4622,6 +4631,16 @@ fn floating_panel(area: Rect) -> Rect {
}
fn draw_startup_diagnostic(frame: &mut Frame, diagnostic: &StartupDiagnostic, area: Rect) {
draw_diagnostic_panel(frame, diagnostic, area, "Configuration error", true);
}
fn draw_diagnostic_panel(
frame: &mut Frame,
diagnostic: &StartupDiagnostic,
area: Rect,
title: &str,
startup: bool,
) {
let panel = floating_panel(area);
frame.render_widget(Clear, panel);
let bordered = panel.width >= 4 && panel.height >= 4;
@@ -4631,8 +4650,8 @@ fn draw_startup_diagnostic(frame: &mut Frame, diagnostic: &StartupDiagnostic, ar
} else {
Borders::NONE
})
.border_style(Style::default().fg(Color::Red))
.title(" Configuration error ");
.border_style(Style::default().fg(if startup { Color::Red } else { Color::DarkGray }))
.title(format!(" {} ", display_text(title)));
let content = if bordered { block.inner(panel) } else { panel };
if bordered {
frame.render_widget(block, panel);
@@ -4658,11 +4677,15 @@ fn draw_startup_diagnostic(frame: &mut Frame, diagnostic: &StartupDiagnostic, ar
.collect();
frame.render_widget(Paragraph::new(lines), text_area);
frame.render_widget(
Paragraph::new("Arrows scroll · Home reset · Enter/Esc continue")
.style(Style::default().fg(Color::DarkGray)),
Paragraph::new(if startup {
"Arrows scroll · Home reset · Enter/Esc continue"
} else {
"Arrows scroll · Home reset · Enter/Esc close"
})
.style(Style::default().fg(Color::DarkGray)),
help_area,
);
if text_area.width > 0 && text_area.height > 0 {
if startup && text_area.width > 0 && text_area.height > 0 {
frame.set_cursor_position((text_area.x, text_area.y));
}
}
@@ -5546,6 +5569,19 @@ impl WheelPanels {
let Some(kind) = wheel_kind(&mouse) else {
return false;
};
if session.editor().command_output().is_some() {
let input = match kind {
MouseEventKind::ScrollUp => Input::Up,
MouseEventKind::ScrollDown => Input::Down,
MouseEventKind::ScrollLeft => Input::Left,
MouseEventKind::ScrollRight => Input::Right,
_ => unreachable!(),
};
for _ in 0..3 {
session.handle(input.clone());
}
return true;
}
let horizontal = matches!(
kind,
MouseEventKind::ScrollLeft | MouseEventKind::ScrollRight