feat: add responsive ultrawide work lanes
CI / rust (push) Successful in 2m3s

This commit is contained in:
2026-08-31 17:46:18 -07:00
parent e7ca1d0a73
commit 3f15b13312
11 changed files with 304 additions and 74 deletions
+153 -17
View File
@@ -16,7 +16,7 @@ use lumbridge_spike_model::{
use lumbridge_terminal::{
KeyModifiers, TerminalCellStyle, TerminalColor, TerminalCursorShape, TerminalDimensions,
TerminalEngine, TerminalEngineOptions, TerminalKey, TerminalKeyEvent, TerminalNamedColor,
TerminalSnapshot,
TerminalScroll, TerminalSnapshot,
};
const BG: u32 = 0x090c12;
@@ -43,6 +43,7 @@ const TERMINAL_CHROME_HEIGHT: f32 = 72.0;
const TERMINAL_HORIZONTAL_INSET: f32 = 32.0;
const TERMINAL_CELL_WIDTH: f32 = 8.4;
const TERMINAL_CELL_HEIGHT: f32 = 18.0;
const WORK_LANE_GAP: f32 = 4.0;
const TERMINAL_ROW_STEP: u16 = 2;
const TERMINAL_COLUMN_STEP: u16 = 10;
@@ -279,10 +280,14 @@ fn dim_color(color: u32) -> u32 {
fn terminal_dimensions_for_window(window_size: Size<Pixels>) -> TerminalDimensions {
let width = f32::from(window_size.width);
let height = f32::from(window_size.height);
let lane_count = visible_lane_count(window_size);
let workspace_height =
(height - APP_HEADER_HEIGHT - TAB_BAR_HEIGHT - APP_FOOTER_HEIGHT).max(0.0);
let terminal_height = (workspace_height * 0.60 - TERMINAL_CHROME_HEIGHT).max(0.0);
let terminal_width = (width - SIDEBAR_WIDTH - TERMINAL_HORIZONTAL_INSET).max(0.0);
let workspace_width = (width - SIDEBAR_WIDTH).max(0.0);
let lane_gaps = WORK_LANE_GAP * lane_count.saturating_sub(1) as f32;
let lane_width = ((workspace_width - lane_gaps).max(0.0) / lane_count as f32).max(0.0);
let terminal_width = (lane_width - TERMINAL_HORIZONTAL_INSET).max(0.0);
let rows = (terminal_height / TERMINAL_CELL_HEIGHT)
.floor()
.clamp(2.0, f32::from(u16::MAX));
@@ -298,6 +303,25 @@ fn terminal_dimensions_for_window(window_size: Size<Pixels>) -> TerminalDimensio
.expect("geometry clamps terminal dimensions above zero")
}
fn visible_lane_count(window_size: Size<Pixels>) -> usize {
let workspace_width = (f32::from(window_size.width) - SIDEBAR_WIDTH).max(0.0);
if workspace_width >= 2_800.0 {
5
} else if workspace_width >= 1_100.0 {
3
} else {
1
}
}
fn visible_lane_range(total: usize, selected: usize, lane_count: usize) -> std::ops::Range<usize> {
let count = lane_count.clamp(1, total.max(1)).min(total);
let start = selected
.saturating_sub(count / 2)
.min(total.saturating_sub(count));
start..start + count
}
impl RenderTiming {
fn mark_dispatch(&mut self) {
self.pending_dispatch = Some(Instant::now());
@@ -527,6 +551,14 @@ impl LumbridgeShell {
cx.notify();
}
fn scroll_terminal(&mut self, scroll: TerminalScroll, cx: &mut Context<Self>) {
if !self.terminal.scroll_display(scroll) {
return;
}
self.publish_terminal_snapshot();
cx.notify();
}
fn select_pane(&mut self, pane: PaneId, window: &mut Window, cx: &mut Context<Self>) {
self.dispatch(ShellAction::SelectPane(pane));
window.focus(&self.root_focus);
@@ -615,6 +647,13 @@ impl LumbridgeShell {
return;
}
let modifiers = key_modifiers(event);
if let Some(scroll) = terminal_scroll_from_parts(
event.keystroke.key.as_str(),
modifiers,
) {
self.scroll_terminal(scroll, cx);
return;
}
let Some(event) = terminal_key_from_parts(
event.keystroke.key.as_str(),
event.keystroke.key_char.as_deref(),
@@ -629,6 +668,11 @@ impl LumbridgeShell {
if let Err(error) = self.send_runtime_command(RuntimeCommand::Input(bytes)) {
self.runtime_status = LiveRuntimeStatus::Fault(error.to_string());
}
if self.terminal.display_offset() > 0
&& self.terminal.scroll_display(TerminalScroll::Bottom)
{
self.publish_terminal_snapshot();
}
cx.notify();
return;
}
@@ -807,8 +851,13 @@ impl LumbridgeShell {
.into_any_element()
}
fn work_surface(&self) -> gpui::AnyElement {
let pane = self.model.pane(self.model.selected_pane());
fn work_surface(
&self,
pane: &PaneState,
selected: bool,
cx: &mut Context<Self>,
) -> gpui::AnyElement {
let pane_id = pane.id();
let external = pane.output_source() == OutputSource::External;
let status = if external {
self.runtime_status.badge()
@@ -820,9 +869,17 @@ impl LumbridgeShell {
} else {
pane.fixture().target.to_owned()
};
let surface_status = if external {
let surface_status = if external && self.terminal_snapshot.display_offset > 0 {
format!(
"{} · {}×{} · auto-fit",
"{} · {} · {}×{}",
status,
self.terminal_snapshot.display_offset,
self.terminal.dimensions().columns(),
self.terminal.dimensions().rows()
)
} else if external {
format!(
"{} · {}×{}",
status,
self.terminal.dimensions().columns(),
self.terminal.dimensions().rows()
@@ -870,14 +927,19 @@ impl LumbridgeShell {
};
div()
.id(("work-lane", pane_id.index()))
.on_click(cx.listener(move |shell, _, window, cx| {
shell.select_pane(pane_id, window, cx);
}))
.flex()
.flex_col()
.size_full()
.min_w_0()
.min_h_0()
.overflow_hidden()
.bg(rgb(PANEL))
.border_y_1()
.border_color(rgb(BORDER))
.border_1()
.border_color(rgb(if selected { ACCENT } else { BORDER }))
.child(
div()
.flex()
@@ -891,15 +953,23 @@ impl LumbridgeShell {
div()
.flex()
.items_center()
.gap_3()
.gap_2()
.min_w_0()
.text_sm()
.child(pane.fixture().title)
.child(div().text_xs().text_color(rgb(MUTED)).child(detail)),
.child(div().flex_none().child(pane.fixture().title))
.child(
div()
.truncate()
.text_xs()
.text_color(rgb(MUTED))
.child(detail),
),
)
.child(
div()
.flex_none()
.text_xs()
.text_color(rgb(SUCCESS))
.text_color(rgb(if selected { SUCCESS } else { MUTED }))
.child(surface_status),
),
)
@@ -1068,7 +1138,7 @@ impl LumbridgeShell {
}
impl Render for LumbridgeShell {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let attention = self.model.pane(PaneId::ClaudeUi);
let sidebar = div()
.flex()
@@ -1251,6 +1321,26 @@ impl Render for LumbridgeShell {
})
.collect::<Vec<_>>();
let selected = self.model.pane(self.model.selected_pane());
let lane_count = visible_lane_count(window.bounds().size);
let lane_range = visible_lane_range(
PaneId::ALL.len(),
self.model.selected_pane().index(),
lane_count,
);
let work_lanes = lane_range
.map(|index| {
let pane = self.model.pane(PaneId::ALL[index]);
div()
.flex_1()
.min_w_0()
.min_h_0()
.child(self.work_surface(
pane,
pane.id() == self.model.selected_pane(),
cx,
))
})
.collect::<Vec<_>>();
let workspace_stack = div()
.flex()
.flex_col()
@@ -1310,9 +1400,11 @@ impl Render for LumbridgeShell {
div()
.h(relative(0.60))
.flex_none()
.flex()
.gap_1()
.min_h_0()
.overflow_hidden()
.child(self.work_surface()),
.children(work_lanes),
)
.child(
div()
@@ -1498,6 +1590,19 @@ fn terminal_key_from_parts(
Some(TerminalKeyEvent { key, modifiers })
}
fn terminal_scroll_from_parts(key: &str, modifiers: KeyModifiers) -> Option<TerminalScroll> {
if !modifiers.contains(KeyModifiers::SHIFT) {
return None;
}
match key {
"pageup" => Some(TerminalScroll::PageUp),
"pagedown" => Some(TerminalScroll::PageDown),
"home" => Some(TerminalScroll::Top),
"end" => Some(TerminalScroll::Bottom),
_ => None,
}
}
fn start_live_runtime(dimensions: TerminalDimensions) -> Result<RuntimeActor, RuntimeActorError> {
let command = CommandConfig::new("/bin/sh")
.map_err(RuntimeActorError::Start)?
@@ -1551,8 +1656,9 @@ fn main() {
#[cfg(test)]
mod tests {
use super::{
KeyModifiers, TerminalEngine, TerminalEngineOptions, TerminalKey, indexed_terminal_color,
terminal_dimensions_for_window, terminal_key_from_parts, terminal_paint_rows,
KeyModifiers, TerminalEngine, TerminalEngineOptions, TerminalKey, TerminalScroll,
indexed_terminal_color, terminal_dimensions_for_window, terminal_key_from_parts,
terminal_paint_rows, terminal_scroll_from_parts, visible_lane_count, visible_lane_range,
};
use gpui::{px, size};
@@ -1582,7 +1688,37 @@ mod tests {
fn terminal_geometry_tracks_the_middle_sixty_percent() {
let dimensions = terminal_dimensions_for_window(size(px(1500.0), px(960.0)));
assert_eq!(dimensions.rows(), 24);
assert_eq!(dimensions.columns(), 145);
assert_eq!(dimensions.columns(), 45);
let ultrawide = size(px(3440.0), px(1440.0));
assert_eq!(visible_lane_count(ultrawide), 5);
let dimensions = terminal_dimensions_for_window(ultrawide);
assert_eq!(dimensions.rows(), 40);
assert_eq!(dimensions.columns(), 71);
}
#[test]
fn lane_window_keeps_the_selected_pane_visible() {
assert_eq!(visible_lane_range(6, 0, 5), 0..5);
assert_eq!(visible_lane_range(6, 5, 5), 1..6);
assert_eq!(visible_lane_range(6, 3, 3), 2..5);
assert_eq!(visible_lane_range(6, 4, 1), 4..5);
}
#[test]
fn shift_navigation_controls_scrollback_without_reaching_the_pty() {
assert_eq!(
terminal_scroll_from_parts("pageup", KeyModifiers::SHIFT),
Some(TerminalScroll::PageUp)
);
assert_eq!(
terminal_scroll_from_parts("end", KeyModifiers::SHIFT),
Some(TerminalScroll::Bottom)
);
assert_eq!(
terminal_scroll_from_parts("pageup", KeyModifiers::default()),
None
);
}
#[test]