fix: place command status left of battery as a separate segment

This commit is contained in:
2026-10-18 01:35:52 +02:00
parent e310b76794
commit d8cfd27ee6
10 changed files with 165 additions and 58 deletions
+6 -3
View File
@@ -130,7 +130,7 @@ impl ChromeLine {
Self {
left: vec!["tabs".into()],
center: Vec::new(),
right: vec!["battery".into(), "shell".into()],
right: vec!["shell".into(), "battery".into()],
segments: BTreeMap::from([
("tabs".into(), default_segment("tabs")),
("battery".into(), default_segment("battery")),
@@ -213,12 +213,15 @@ impl ChromeLine {
fn default_segment(name: &str) -> SegmentConfig {
let mut segment = SegmentConfig::default();
if matches!(name, "tabs" | "position" | "battery") {
if matches!(name, "tabs" | "position" | "battery" | "shell") {
segment.leading_edge = "░▓".into();
}
if matches!(name, "tabs" | "mode") {
if matches!(name, "tabs" | "mode" | "shell") {
segment.trailing_edge = "▓░".into();
}
if name == "shell" {
segment.margin[1] = 1;
}
if name == "tabs" {
segment.title = "{index} {view}{name}{modified}".into();
segment.first_leading_edge = Some(String::new());
+12 -3
View File
@@ -2233,6 +2233,7 @@ pub struct Editor {
battery_label: String,
shell_label: String,
command_output: Option<ShellOutput>,
queued_command_outputs: VecDeque<ShellOutput>,
git_blame: bool,
gutter_style: crate::gutter::GutterStyle,
line_blame: Option<(BlameSource, Arc<crate::blame::LineBlame>, String)>,
@@ -2351,6 +2352,7 @@ impl Editor {
battery_label: String::new(),
shell_label: String::new(),
command_output: None,
queued_command_outputs: VecDeque::new(),
git_blame: true,
gutter_style: crate::gutter::GutterStyle::default(),
line_blame: None,
@@ -6996,7 +6998,7 @@ impl Editor {
.chars()
.map(|ch| if ch.is_control() { '�' } else { ch })
.collect();
self.command_output = Some(ShellOutput {
let output = ShellOutput {
title: format!("{status} · !{command}"),
wrapping: false,
wrapped_rows: Cell::new(1),
@@ -7007,7 +7009,12 @@ impl Editor {
left: 0,
max_column,
},
});
};
if self.command_output.is_some() {
self.queued_command_outputs.push_back(output);
} else {
self.command_output = Some(output);
}
}
fn handle_command_output(&mut self, input: &Input) -> bool {
@@ -7041,7 +7048,9 @@ impl Editor {
};
let content = &mut output.content;
match input {
Input::Escape | Input::Enter => self.command_output = None,
Input::Escape | Input::Enter => {
self.command_output = self.queued_command_outputs.pop_front()
}
Input::Up | Input::Ctrl('k') => content.top = content.top.saturating_sub(1),
Input::Down | Input::Ctrl('j') => {
content.top = content.top.saturating_add(1).min(rows.saturating_sub(1));
+55 -34
View File
@@ -439,6 +439,13 @@ fn text_fits(lines: &[String], limit: usize) -> bool {
.is_some()
}
struct ShellCommand {
command: String,
worker: crate::shell::Worker,
started: Instant,
label: String,
}
/// Owns command eligibility and execution hosts together. Frontends can inspect
/// the editor, but cannot independently mutate its plugin registry.
pub struct EditorSession {
@@ -468,7 +475,7 @@ pub struct EditorSession {
picker_source: Option<PathBuf>,
battery: Option<crate::battery::Battery>,
battery_enabled: bool,
shell: Option<(String, crate::shell::Worker, Instant)>,
shell: Vec<ShellCommand>,
last_file_refresh: Instant,
last_diff_refresh: Instant,
pub(crate) diff_follow_working_copy: bool,
@@ -596,7 +603,7 @@ impl EditorSession {
picker_source: None,
battery: None,
battery_enabled: false,
shell: None,
shell: Vec::new(),
last_file_refresh: Instant::now(),
last_diff_refresh: Instant::now(),
diff_follow_working_copy: config.diff_follow_working_copy,
@@ -830,18 +837,18 @@ impl EditorSession {
fn dispatch(&mut self, invocation: Invocation) {
if invocation.plugin == "core" {
if invocation.command == "shell" {
if self.shell.is_some() {
self.editor
.set_message("A shell command is already running");
return;
}
let command = invocation.args;
match crate::shell::Worker::start(
command.clone(),
self.editor.project_root().to_path_buf(),
) {
Ok(worker) => {
self.shell = Some((command, worker, Instant::now()));
self.shell.push(ShellCommand {
command,
worker,
started: Instant::now(),
label: String::new(),
});
self.poll_shell_at(Instant::now());
}
Err(error) => self.editor.show_command_output(command, Err(error)),
@@ -1772,34 +1779,48 @@ impl EditorSession {
}
fn poll_shell_at(&mut self, now: Instant) -> bool {
let Some((command, worker, started)) = &mut self.shell else {
return false;
};
if let Some(result) = worker.poll() {
let command = command.clone();
self.shell = None;
self.editor.set_shell_label(String::new());
self.editor.show_command_output(command, result);
return true;
}
let elapsed = now.saturating_duration_since(*started);
if elapsed < Duration::from_millis(200) {
return false;
}
const FRAMES: [char; 4] = ['|', '/', '-', '\\'];
let frame = FRAMES[((elapsed.as_millis() / 100) % 4) as usize];
if self.editor.shell_label().ends_with(frame) {
return false;
}
use unicode_segmentation::UnicodeSegmentation;
let mut graphemes = command.trim().graphemes(true);
let mut label: String = graphemes.by_ref().take(3).collect();
if graphemes.next().is_some() {
label.push_str("...");
const FRAMES: [char; 4] = ['|', '/', '-', '\\'];
let mut changed = false;
self.shell.retain_mut(|running| {
if let Some(result) = running.worker.poll() {
self.editor
.show_command_output(std::mem::take(&mut running.command), result);
changed = true;
return false;
}
let elapsed = now.saturating_duration_since(running.started);
if elapsed >= Duration::from_millis(200) {
let frame = FRAMES[((elapsed.as_millis() / 100) % 4) as usize];
if !running.label.ends_with(frame) {
running.label.clear();
let mut graphemes = running.command.trim().graphemes(true);
running.label.extend(graphemes.by_ref().take(3));
if graphemes.next().is_some() {
running.label.push_str("...");
}
running.label.push(' ');
running.label.push(frame);
changed = true;
}
}
true
});
if changed {
let mut label = String::new();
for running in self
.shell
.iter()
.filter(|running| !running.label.is_empty())
{
if !label.is_empty() {
label.push_str(" ");
}
label.push_str(&running.label);
}
self.editor.set_shell_label(label);
}
label.push(' ');
label.push(frame);
self.editor.set_shell_label(label)
changed
}
fn refresh_files_at(&mut self, now: Instant) -> bool {