feat: restore workspace numbers in expanded sidebar
This commit is contained in:
parent
42e9aec481
commit
d05ba2ea9f
|
|
@ -249,7 +249,11 @@ fn handle_rename_key(state: &mut AppState, key: KeyEvent) {
|
|||
fn handle_resize_key(state: &mut AppState, key: KeyEvent) {
|
||||
if key.code == KeyCode::Esc
|
||||
|| key.code == KeyCode::Enter
|
||||
|| key_matches(&key, state.keybinds.resize_mode.0, state.keybinds.resize_mode.1)
|
||||
|| key_matches(
|
||||
&key,
|
||||
state.keybinds.resize_mode.0,
|
||||
state.keybinds.resize_mode.1,
|
||||
)
|
||||
{
|
||||
if state.active.is_some() {
|
||||
state.mode = Mode::Terminal;
|
||||
|
|
@ -599,8 +603,8 @@ impl AppState {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crossterm::event::KeyModifiers;
|
||||
use crate::workspace::Workspace;
|
||||
use crossterm::event::KeyModifiers;
|
||||
|
||||
fn state_with_workspaces(names: &[&str]) -> AppState {
|
||||
let mut state = AppState::test_new();
|
||||
|
|
@ -619,7 +623,10 @@ mod tests {
|
|||
state.keybinds.rename_workspace = (KeyCode::Char('g'), KeyModifiers::empty());
|
||||
state.keybinds.rename_workspace_label = "g".into();
|
||||
|
||||
handle_navigate_key(&mut state, KeyEvent::new(KeyCode::Char('g'), KeyModifiers::empty()));
|
||||
handle_navigate_key(
|
||||
&mut state,
|
||||
KeyEvent::new(KeyCode::Char('g'), KeyModifiers::empty()),
|
||||
);
|
||||
|
||||
assert_eq!(state.mode, Mode::RenameSession);
|
||||
assert_eq!(state.name_input, "test");
|
||||
|
|
@ -631,7 +638,10 @@ mod tests {
|
|||
state.keybinds.new_workspace = (KeyCode::Char('g'), KeyModifiers::empty());
|
||||
state.keybinds.new_workspace_label = "g".into();
|
||||
|
||||
handle_navigate_key(&mut state, KeyEvent::new(KeyCode::Char('g'), KeyModifiers::empty()));
|
||||
handle_navigate_key(
|
||||
&mut state,
|
||||
KeyEvent::new(KeyCode::Char('g'), KeyModifiers::empty()),
|
||||
);
|
||||
|
||||
assert!(state.request_new_workspace);
|
||||
assert_eq!(state.mode, Mode::Terminal);
|
||||
|
|
@ -644,7 +654,10 @@ mod tests {
|
|||
state.keybinds.toggle_sidebar_label = "g".into();
|
||||
assert!(!state.sidebar_collapsed);
|
||||
|
||||
handle_navigate_key(&mut state, KeyEvent::new(KeyCode::Char('g'), KeyModifiers::empty()));
|
||||
handle_navigate_key(
|
||||
&mut state,
|
||||
KeyEvent::new(KeyCode::Char('g'), KeyModifiers::empty()),
|
||||
);
|
||||
|
||||
assert!(state.sidebar_collapsed);
|
||||
assert_eq!(state.mode, Mode::Terminal);
|
||||
|
|
@ -656,7 +669,10 @@ mod tests {
|
|||
state.keybinds.resize_mode = (KeyCode::Char('g'), KeyModifiers::empty());
|
||||
state.keybinds.resize_mode_label = "g".into();
|
||||
|
||||
handle_navigate_key(&mut state, KeyEvent::new(KeyCode::Char('g'), KeyModifiers::empty()));
|
||||
handle_navigate_key(
|
||||
&mut state,
|
||||
KeyEvent::new(KeyCode::Char('g'), KeyModifiers::empty()),
|
||||
);
|
||||
|
||||
assert_eq!(state.mode, Mode::Resize);
|
||||
}
|
||||
|
|
@ -666,7 +682,10 @@ mod tests {
|
|||
let mut state = state_with_workspaces(&["a", "b"]);
|
||||
state.selected = 0;
|
||||
|
||||
handle_navigate_key(&mut state, KeyEvent::new(KeyCode::Down, KeyModifiers::empty()));
|
||||
handle_navigate_key(
|
||||
&mut state,
|
||||
KeyEvent::new(KeyCode::Down, KeyModifiers::empty()),
|
||||
);
|
||||
|
||||
assert_eq!(state.selected, 1);
|
||||
assert_eq!(state.mode, Mode::Navigate);
|
||||
|
|
@ -679,7 +698,10 @@ mod tests {
|
|||
state.keybinds.fullscreen = (KeyCode::Char('g'), KeyModifiers::empty());
|
||||
state.keybinds.fullscreen_label = "g".into();
|
||||
|
||||
handle_navigate_key(&mut state, KeyEvent::new(KeyCode::Char('g'), KeyModifiers::empty()));
|
||||
handle_navigate_key(
|
||||
&mut state,
|
||||
KeyEvent::new(KeyCode::Char('g'), KeyModifiers::empty()),
|
||||
);
|
||||
|
||||
assert!(state.workspaces[0].zoomed);
|
||||
assert_eq!(state.mode, Mode::Terminal);
|
||||
|
|
@ -692,7 +714,10 @@ mod tests {
|
|||
state.keybinds.resize_mode = (KeyCode::Char('g'), KeyModifiers::empty());
|
||||
state.keybinds.resize_mode_label = "g".into();
|
||||
|
||||
handle_resize_key(&mut state, KeyEvent::new(KeyCode::Char('g'), KeyModifiers::empty()));
|
||||
handle_resize_key(
|
||||
&mut state,
|
||||
KeyEvent::new(KeyCode::Char('g'), KeyModifiers::empty()),
|
||||
);
|
||||
|
||||
assert_eq!(state.mode, Mode::Terminal);
|
||||
}
|
||||
|
|
|
|||
135
src/config.rs
135
src/config.rs
|
|
@ -181,7 +181,10 @@ impl Config {
|
|||
Ok(content) => match toml::from_str::<Config>(&content) {
|
||||
Ok(config) => {
|
||||
let diagnostics = config.collect_diagnostics();
|
||||
return LoadedConfig { config, diagnostics };
|
||||
return LoadedConfig {
|
||||
config,
|
||||
diagnostics,
|
||||
};
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(err = %e, "config parse error, using defaults");
|
||||
|
|
@ -220,7 +223,14 @@ impl Config {
|
|||
prefix_diag.into_iter().chain(keybind_diags).collect()
|
||||
}
|
||||
|
||||
fn validated_keybinds(&self) -> (Option<String>, (KeyCode, KeyModifiers), Vec<String>, Keybinds) {
|
||||
fn validated_keybinds(
|
||||
&self,
|
||||
) -> (
|
||||
Option<String>,
|
||||
(KeyCode, KeyModifiers),
|
||||
Vec<String>,
|
||||
Keybinds,
|
||||
) {
|
||||
#[derive(Clone)]
|
||||
struct Binding<'a> {
|
||||
field: &'a str,
|
||||
|
|
@ -264,15 +274,69 @@ impl Config {
|
|||
}
|
||||
|
||||
let mut bindings = vec![
|
||||
binding("keys.new_workspace", &self.keys.new_workspace, "n", (KeyCode::Char('n'), KeyModifiers::empty()), &mut diagnostics),
|
||||
binding("keys.rename_workspace", &self.keys.rename_workspace, "shift+n", (KeyCode::Char('n'), KeyModifiers::SHIFT), &mut diagnostics),
|
||||
binding("keys.close_workspace", &self.keys.close_workspace, "d", (KeyCode::Char('d'), KeyModifiers::empty()), &mut diagnostics),
|
||||
binding("keys.split_vertical", &self.keys.split_vertical, "v", (KeyCode::Char('v'), KeyModifiers::empty()), &mut diagnostics),
|
||||
binding("keys.split_horizontal", &self.keys.split_horizontal, "-", (KeyCode::Char('-'), KeyModifiers::empty()), &mut diagnostics),
|
||||
binding("keys.close_pane", &self.keys.close_pane, "x", (KeyCode::Char('x'), KeyModifiers::empty()), &mut diagnostics),
|
||||
binding("keys.fullscreen", &self.keys.fullscreen, "f", (KeyCode::Char('f'), KeyModifiers::empty()), &mut diagnostics),
|
||||
binding("keys.resize_mode", &self.keys.resize_mode, "r", (KeyCode::Char('r'), KeyModifiers::empty()), &mut diagnostics),
|
||||
binding("keys.toggle_sidebar", &self.keys.toggle_sidebar, "b", (KeyCode::Char('b'), KeyModifiers::empty()), &mut diagnostics),
|
||||
binding(
|
||||
"keys.new_workspace",
|
||||
&self.keys.new_workspace,
|
||||
"n",
|
||||
(KeyCode::Char('n'), KeyModifiers::empty()),
|
||||
&mut diagnostics,
|
||||
),
|
||||
binding(
|
||||
"keys.rename_workspace",
|
||||
&self.keys.rename_workspace,
|
||||
"shift+n",
|
||||
(KeyCode::Char('n'), KeyModifiers::SHIFT),
|
||||
&mut diagnostics,
|
||||
),
|
||||
binding(
|
||||
"keys.close_workspace",
|
||||
&self.keys.close_workspace,
|
||||
"d",
|
||||
(KeyCode::Char('d'), KeyModifiers::empty()),
|
||||
&mut diagnostics,
|
||||
),
|
||||
binding(
|
||||
"keys.split_vertical",
|
||||
&self.keys.split_vertical,
|
||||
"v",
|
||||
(KeyCode::Char('v'), KeyModifiers::empty()),
|
||||
&mut diagnostics,
|
||||
),
|
||||
binding(
|
||||
"keys.split_horizontal",
|
||||
&self.keys.split_horizontal,
|
||||
"-",
|
||||
(KeyCode::Char('-'), KeyModifiers::empty()),
|
||||
&mut diagnostics,
|
||||
),
|
||||
binding(
|
||||
"keys.close_pane",
|
||||
&self.keys.close_pane,
|
||||
"x",
|
||||
(KeyCode::Char('x'), KeyModifiers::empty()),
|
||||
&mut diagnostics,
|
||||
),
|
||||
binding(
|
||||
"keys.fullscreen",
|
||||
&self.keys.fullscreen,
|
||||
"f",
|
||||
(KeyCode::Char('f'), KeyModifiers::empty()),
|
||||
&mut diagnostics,
|
||||
),
|
||||
binding(
|
||||
"keys.resize_mode",
|
||||
&self.keys.resize_mode,
|
||||
"r",
|
||||
(KeyCode::Char('r'), KeyModifiers::empty()),
|
||||
&mut diagnostics,
|
||||
),
|
||||
binding(
|
||||
"keys.toggle_sidebar",
|
||||
&self.keys.toggle_sidebar,
|
||||
"b",
|
||||
(KeyCode::Char('b'), KeyModifiers::empty()),
|
||||
&mut diagnostics,
|
||||
),
|
||||
];
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
|
@ -477,7 +541,6 @@ fn parse_key_combo_with_diagnostic(
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
@ -569,7 +632,10 @@ mod tests {
|
|||
let config = Config::default();
|
||||
let kb = config.keybinds();
|
||||
assert_eq!(kb.new_workspace.0, KeyCode::Char('n'));
|
||||
assert_eq!(kb.rename_workspace, (KeyCode::Char('n'), KeyModifiers::SHIFT));
|
||||
assert_eq!(
|
||||
kb.rename_workspace,
|
||||
(KeyCode::Char('n'), KeyModifiers::SHIFT)
|
||||
);
|
||||
assert_eq!(kb.close_workspace.0, KeyCode::Char('d'));
|
||||
assert_eq!(kb.split_vertical.0, KeyCode::Char('v'));
|
||||
assert_eq!(kb.split_horizontal.0, KeyCode::Char('-'));
|
||||
|
|
@ -600,9 +666,18 @@ toggle_sidebar = "tab"
|
|||
assert_eq!(mods, KeyModifiers::CONTROL);
|
||||
|
||||
let kb = config.keybinds();
|
||||
assert_eq!(kb.new_workspace, (KeyCode::Char('c'), KeyModifiers::empty()));
|
||||
assert_eq!(kb.rename_workspace, (KeyCode::Char('r'), KeyModifiers::SHIFT));
|
||||
assert_eq!(kb.close_workspace, (KeyCode::Char('d'), KeyModifiers::CONTROL));
|
||||
assert_eq!(
|
||||
kb.new_workspace,
|
||||
(KeyCode::Char('c'), KeyModifiers::empty())
|
||||
);
|
||||
assert_eq!(
|
||||
kb.rename_workspace,
|
||||
(KeyCode::Char('r'), KeyModifiers::SHIFT)
|
||||
);
|
||||
assert_eq!(
|
||||
kb.close_workspace,
|
||||
(KeyCode::Char('d'), KeyModifiers::CONTROL)
|
||||
);
|
||||
assert_eq!(kb.split_vertical.0, KeyCode::Char('s'));
|
||||
assert_eq!(
|
||||
kb.split_horizontal,
|
||||
|
|
@ -622,7 +697,10 @@ split_horizontal = "D"
|
|||
"#;
|
||||
let config: Config = toml::from_str(toml).unwrap();
|
||||
let kb = config.keybinds();
|
||||
assert_eq!(kb.split_horizontal, (KeyCode::Char('d'), KeyModifiers::SHIFT));
|
||||
assert_eq!(
|
||||
kb.split_horizontal,
|
||||
(KeyCode::Char('d'), KeyModifiers::SHIFT)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -635,8 +713,13 @@ rename_workspace = "wat"
|
|||
let diagnostics = config.collect_diagnostics();
|
||||
let kb = config.keybinds();
|
||||
|
||||
assert!(diagnostics.iter().any(|d| d.contains("keys.rename_workspace")));
|
||||
assert_eq!(kb.rename_workspace, (KeyCode::Char('n'), KeyModifiers::SHIFT));
|
||||
assert!(diagnostics
|
||||
.iter()
|
||||
.any(|d| d.contains("keys.rename_workspace")));
|
||||
assert_eq!(
|
||||
kb.rename_workspace,
|
||||
(KeyCode::Char('n'), KeyModifiers::SHIFT)
|
||||
);
|
||||
assert_eq!(kb.rename_workspace_label, "shift+n");
|
||||
}
|
||||
|
||||
|
|
@ -651,9 +734,17 @@ rename_workspace = "g"
|
|||
let diagnostics = config.collect_diagnostics();
|
||||
let kb = config.keybinds();
|
||||
|
||||
assert!(diagnostics.iter().any(|d| d.contains("duplicate keybinding")));
|
||||
assert_eq!(kb.new_workspace, (KeyCode::Char('g'), KeyModifiers::empty()));
|
||||
assert_eq!(kb.rename_workspace, (KeyCode::Char('n'), KeyModifiers::SHIFT));
|
||||
assert!(diagnostics
|
||||
.iter()
|
||||
.any(|d| d.contains("duplicate keybinding")));
|
||||
assert_eq!(
|
||||
kb.new_workspace,
|
||||
(KeyCode::Char('g'), KeyModifiers::empty())
|
||||
);
|
||||
assert_eq!(
|
||||
kb.rename_workspace,
|
||||
(KeyCode::Char('n'), KeyModifiers::SHIFT)
|
||||
);
|
||||
assert_eq!(kb.rename_workspace_label, "shift+n");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -406,7 +406,10 @@ mod tests {
|
|||
let restored: SessionSnapshot = serde_json::from_str(&json).unwrap();
|
||||
|
||||
assert_eq!(restored.workspaces.len(), 1);
|
||||
assert_eq!(restored.workspaces[0].custom_name.as_deref(), Some("pi-mono"));
|
||||
assert_eq!(
|
||||
restored.workspaces[0].custom_name.as_deref(),
|
||||
Some("pi-mono")
|
||||
);
|
||||
assert_eq!(restored.workspaces[0].panes.len(), 2);
|
||||
assert_eq!(
|
||||
restored.workspaces[0].panes[&0].cwd,
|
||||
|
|
|
|||
56
src/ui.rs
56
src/ui.rs
|
|
@ -140,14 +140,16 @@ fn compute_sidebar_width(app: &AppState) -> u16 {
|
|||
let max_line = app
|
||||
.workspaces
|
||||
.iter()
|
||||
.map(|ws| {
|
||||
.enumerate()
|
||||
.map(|(i, ws)| {
|
||||
let name_len = ws.display_name().len();
|
||||
let number_len = (i + 1).to_string().len();
|
||||
let pane_count = if ws.layout.pane_count() > 1 {
|
||||
ws.layout.pane_count()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let line1 = 4 + name_len + pane_count; // marker + dot + spaces + pane dots
|
||||
let line1 = 5 + name_len + number_len + pane_count; // marker + number + name + spaces + pane dots
|
||||
let line2 = ws.agent_summary().map(|s| s.len() + 2).unwrap_or(0);
|
||||
line1.max(line2)
|
||||
})
|
||||
|
|
@ -160,7 +162,11 @@ fn compute_sidebar_width(app: &AppState) -> u16 {
|
|||
fn render_sidebar_collapsed(app: &AppState, frame: &mut Frame, area: Rect) {
|
||||
let is_navigating = matches!(
|
||||
app.mode,
|
||||
Mode::Navigate | Mode::RenameSession | Mode::Resize | Mode::ConfirmClose | Mode::ContextMenu
|
||||
Mode::Navigate
|
||||
| Mode::RenameSession
|
||||
| Mode::Resize
|
||||
| Mode::ConfirmClose
|
||||
| Mode::ContextMenu
|
||||
);
|
||||
|
||||
let sep_style = if is_navigating {
|
||||
|
|
@ -208,7 +214,14 @@ fn render_sidebar_collapsed(app: &AppState, frame: &mut Frame, area: Rect) {
|
|||
Paragraph::new(Line::from(vec![
|
||||
Span::styled(format!("{}", i + 1), num_style),
|
||||
Span::styled(" ", row_style),
|
||||
Span::styled(icon, if is_selected { icon_style.bg(app.accent) } else { icon_style }),
|
||||
Span::styled(
|
||||
icon,
|
||||
if is_selected {
|
||||
icon_style.bg(app.accent)
|
||||
} else {
|
||||
icon_style
|
||||
},
|
||||
),
|
||||
])),
|
||||
Rect::new(area.x, y, content_w, 1),
|
||||
);
|
||||
|
|
@ -220,7 +233,11 @@ fn render_sidebar_collapsed(app: &AppState, frame: &mut Frame, area: Rect) {
|
|||
fn render_sidebar(app: &AppState, frame: &mut Frame, area: Rect) {
|
||||
let is_navigating = matches!(
|
||||
app.mode,
|
||||
Mode::Navigate | Mode::RenameSession | Mode::Resize | Mode::ConfirmClose | Mode::ContextMenu
|
||||
Mode::Navigate
|
||||
| Mode::RenameSession
|
||||
| Mode::Resize
|
||||
| Mode::ConfirmClose
|
||||
| Mode::ContextMenu
|
||||
);
|
||||
let highlight = Style::default().bg(app.accent).fg(Color::Black);
|
||||
let sep_style = if is_navigating {
|
||||
|
|
@ -249,7 +266,11 @@ fn render_sidebar(app: &AppState, frame: &mut Frame, area: Rect) {
|
|||
let marker = if Some(i) == app.active { "▸" } else { " " };
|
||||
let (agg_state, agg_seen) = ws.aggregate_state();
|
||||
let (icon, icon_style) = state_icon_style(agg_state, agg_seen);
|
||||
let text_style = if selected { highlight } else { Style::default() };
|
||||
let text_style = if selected {
|
||||
highlight
|
||||
} else {
|
||||
Style::default()
|
||||
};
|
||||
let dim_style = if selected {
|
||||
highlight
|
||||
} else {
|
||||
|
|
@ -257,24 +278,30 @@ fn render_sidebar(app: &AppState, frame: &mut Frame, area: Rect) {
|
|||
};
|
||||
let mut line1 = vec![
|
||||
Span::styled(marker, text_style),
|
||||
Span::styled(
|
||||
ws.display_name(),
|
||||
text_style.add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::styled(format!("{} ", i + 1), dim_style),
|
||||
Span::styled(ws.display_name(), text_style.add_modifier(Modifier::BOLD)),
|
||||
Span::styled(" ", dim_style),
|
||||
];
|
||||
|
||||
if ws.layout.pane_count() == 1 {
|
||||
line1.push(Span::styled(
|
||||
icon,
|
||||
if selected { icon_style.bg(app.accent) } else { icon_style },
|
||||
if selected {
|
||||
icon_style.bg(app.accent)
|
||||
} else {
|
||||
icon_style
|
||||
},
|
||||
));
|
||||
} else {
|
||||
for (pane_state, pane_seen) in ws.pane_states() {
|
||||
let (pane_icon, pane_style) = state_icon_style(pane_state, pane_seen);
|
||||
line1.push(Span::styled(
|
||||
pane_icon,
|
||||
if selected { pane_style.bg(app.accent) } else { pane_style },
|
||||
if selected {
|
||||
pane_style.bg(app.accent)
|
||||
} else {
|
||||
pane_style
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -288,7 +315,10 @@ fn render_sidebar(app: &AppState, frame: &mut Frame, area: Rect) {
|
|||
}
|
||||
}
|
||||
|
||||
frame.render_widget(Paragraph::new(Line::from(line1)), Rect::new(content.x, row_y, content.width, 1));
|
||||
frame.render_widget(
|
||||
Paragraph::new(Line::from(line1)),
|
||||
Rect::new(content.x, row_y, content.width, 1),
|
||||
);
|
||||
|
||||
if app.mode == Mode::RenameSession && i == app.selected {
|
||||
let text = format!(" {}\u{2588}", app.name_input);
|
||||
|
|
|
|||
|
|
@ -61,7 +61,8 @@ impl Workspace {
|
|||
cwd: Option<PathBuf>,
|
||||
) -> std::io::Result<PaneId> {
|
||||
let new_id = self.layout.split_focused(direction);
|
||||
let actual_cwd = cwd.unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| "/".into()));
|
||||
let actual_cwd =
|
||||
cwd.unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| "/".into()));
|
||||
let runtime = PaneRuntime::spawn(new_id, rows, cols, actual_cwd, self.events.clone())?;
|
||||
self.panes.insert(new_id, PaneState::new());
|
||||
self.runtimes.insert(new_id, runtime);
|
||||
|
|
|
|||
Loading…
Reference in New Issue