Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,9 @@ Layer 5 (binary): code_assistant — CLI, config, feature-gated frontend
- GPUI frontend based on gpui-kit (gpui via `gpui_kit::*`, gpui-component via `gpui_kit::component`) with custom components
- GPUI API reference (contexts, entities, tasks, elements, actions, events):
`docs/gpui-reference.md`
- Text is cut at the available width, never at a character count: `.truncate()`
or `.line_clamp(n).text_ellipsis()`, with `min_w_0()` on flex items around it
and `shared::text::single_line` for text that may hold line breaks
- The right panel shows the agent's browser live (CDP screencast, feature
`browser-panel`, default): `docs/browser-panel-plan.md`. GPUI must not run
inside a tokio `block_on` (tokio's cooperation budget would never renew on
Expand Down
51 changes: 31 additions & 20 deletions crates/ui_gpui/src/blocks/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
//! thinking, tool-use, compaction, image).

use super::{BlockData, BlockView, ToolBlockState, ToolUseBlock};
use crate::shared::text::single_line;
use crate::shared::{file_icons, frame_profile};
use code_assistant_core::ui::ToolStatus;

Expand Down Expand Up @@ -106,7 +107,7 @@ impl BlockView {
};
let header_text_color = theme.muted_foreground;
let icon = file_icons::get().get_tool_icon(&block.name);
let label = renderer.describe(block);
let label = single_line(&renderer.describe(block));

div()
.w_full()
Expand All @@ -131,6 +132,8 @@ impl BlockView {
))
.child(
div()
.min_w_0()
.truncate()
.text_size(rems(0.75))
.text_color(header_text_color)
.child(label),
Expand Down Expand Up @@ -171,15 +174,13 @@ impl BlockView {
};

// Description text
let description = if block.status == ToolStatus::Error {
if let Some(ref msg) = block.status_message {
format!("{} — {}", renderer.describe(block), msg)
} else {
renderer.describe(block)
}
} else {
renderer.describe(block)
};
let description = single_line(&renderer.describe(block));
// Its own element, so a long description can't push it out of view.
let error_message = block
.status_message
.as_deref()
.filter(|_| block.status == ToolStatus::Error)
.map(|message| format!("— {}", single_line(message)));

// Optional right-aligned pill (e.g. the MCP server name).
let header_tag = renderer.header_tag(block);
Expand Down Expand Up @@ -283,14 +284,22 @@ impl BlockView {
// Description text
.child(
div()
.min_w_0()
.truncate()
.text_size(rems(0.8125))
.text_color(desc_color)
.overflow_hidden()
.text_overflow(gpui_kit::TextOverflow::Truncate(SharedString::from(
"…",
)))
.child(description),
),
)
.when_some(error_message, |d, message| {
d.child(
div()
.min_w_0()
.truncate()
.text_size(rems(0.8125))
.text_color(desc_color)
.child(message),
)
}),
)
// Optional pill (e.g. MCP server name), right-aligned before the
// chevron.
Expand Down Expand Up @@ -399,6 +408,8 @@ impl BlockView {
.child(
div()
.flex_1()
.min_w_0()
.truncate()
.text_size(rems(0.8125))
.text_color(color)
.child(label),
Expand Down Expand Up @@ -665,10 +676,8 @@ impl BlockView {
.text_size(rems(0.8125))
.italic()
.text_color(muted)
.overflow_hidden()
.text_overflow(gpui_kit::TextOverflow::Truncate(
SharedString::from("…"),
))
.min_w_0()
.truncate()
.child(header_text),
),
)
Expand Down Expand Up @@ -996,13 +1005,15 @@ impl BlockView {
.hover(|s| s.text_color(theme.foreground))
.child(
gpui_kit::svg()
.flex_none()
.size(px(14.))
.path("icons/message_bubbles.svg")
.text_color(theme.muted_foreground),
)
.child(label)
.child(div().min_w_0().truncate().child(label))
.child(
gpui_kit::svg()
.flex_none()
.size(px(10.))
.path(if expanded {
"icons/chevron_down.svg"
Expand Down
9 changes: 4 additions & 5 deletions crates/ui_gpui/src/diff/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,14 @@ pub(crate) fn render_diff_chunk(
return rows;
}
let (_, ctx_color) = unchanged_row_colors(theme);
let separator = div().w_full().flex().text_color(ctx_color.opacity(0.5));
let separator = div().w_full().text_color(ctx_color.opacity(0.5));
let separator = match header {
// Not a flex container: text directly in one doesn't shrink.
Some(header) => separator
.px_3()
.overflow_hidden()
.whitespace_nowrap()
.text_ellipsis()
.truncate()
.child(SharedString::from(format!("⋯ {header}"))),
None => separator.justify_center().child("⋯"),
None => separator.flex().justify_center().child("⋯"),
};
div()
.flex()
Expand Down
2 changes: 1 addition & 1 deletion crates/ui_gpui/src/input/attachment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ impl AttachmentView {
div()
.text_xs()
.text_color(cx.theme().muted_foreground)
.text_ellipsis()
.truncate()
.w_full()
.text_center()
.child(filename.clone()),
Expand Down
26 changes: 8 additions & 18 deletions crates/ui_gpui/src/input/comments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

use super::{InputArea, InputAreaEvent};
use crate::shared::dismiss::dismiss_layer;
use crate::shared::text::single_line;
use code_assistant_core::line_comments::LineComment;
use code_assistant_core::persistence::DraftAttachment;
use gpui_kit::component::{ActiveTheme, Icon, Sizable, Size, ThemeStyled};
Expand All @@ -17,9 +18,6 @@ use gpui_kit::{
};
use std::collections::BTreeSet;

/// Characters of a comment's text shown in the chip.
const PREVIEW_CHARS: usize = 40;

impl InputArea {
/// The draft's line comments, in the order they were made.
pub fn comments(&self) -> &[LineComment] {
Expand Down Expand Up @@ -96,13 +94,7 @@ impl InputArea {
match comments {
[] => String::new(),
[c] => {
let text: String = c.text.chars().take(PREVIEW_CHARS).collect();
let ellipsis = if c.text.chars().count() > PREVIEW_CHARS {
"…"
} else {
""
};
format!("{} · {text}{ellipsis}", c.location_label())
format!("{} · {}", c.location_label(), single_line(&c.text))
}
_ if comments.iter().any(|c| c.on_message) => {
format!("{} comments", comments.len())
Expand Down Expand Up @@ -161,9 +153,7 @@ impl InputArea {
.child(
div()
.min_w_0()
.overflow_hidden()
.whitespace_nowrap()
.text_ellipsis()
.truncate()
.text_xs()
.text_color(theme.foreground)
.child(label),
Expand Down Expand Up @@ -248,9 +238,7 @@ impl InputArea {
)
.child(
div()
.overflow_hidden()
.whitespace_nowrap()
.text_ellipsis()
.truncate()
.text_xs()
.font_family("Menlo")
.text_color(theme.muted_foreground)
Expand Down Expand Up @@ -346,8 +334,10 @@ mod tests {
InputArea::comments_label(&[comment("/p/src/lib.rs", "no unwrap here")]),
"lib.rs:3-5 · no unwrap here"
);
let long = "a".repeat(50);
assert!(InputArea::comments_label(&[comment("/p/a.rs", &long)]).ends_with("a…"));
assert_eq!(
InputArea::comments_label(&[comment("/p/src/lib.rs", "first\nsecond")]),
"lib.rs:3-5 · first second"
);
assert_eq!(
InputArea::comments_label(&[comment("/p/a.rs", "x"), comment("/p/a.rs", "y")]),
"2 comments in a.rs"
Expand Down
5 changes: 4 additions & 1 deletion crates/ui_gpui/src/input/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -728,13 +728,16 @@ impl InputArea {
}))
.child(
div()
.min_w_0()
.flex()
.items_baseline()
.gap_2()
.text_sm()
.child(item.label.clone())
.child(div().flex_none().child(item.label.clone()))
.child(
div()
.min_w_0()
.truncate()
.text_xs()
.text_color(cx.theme().muted_foreground)
.child(item.detail.clone()),
Expand Down
2 changes: 1 addition & 1 deletion crates/ui_gpui/src/input/model_selector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ impl SelectItem for ModelItem {
.min_w_0()
.flex()
.items_center()
.child(div().min_w_0().text_ellipsis().child(self.name.clone())),
.child(div().min_w_0().truncate().child(self.name.clone())),
);

Some(row.into_any_element())
Expand Down
8 changes: 2 additions & 6 deletions crates/ui_gpui/src/main_screen/agents_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,7 @@ fn sub_agent_row(
div()
.flex_1()
.min_w_0()
.overflow_hidden()
.text_ellipsis()
.whitespace_nowrap()
.truncate()
.text_size(rems(0.8125))
.text_color(foreground)
.child(agent.title.clone()),
Expand All @@ -208,9 +206,7 @@ fn sub_agent_row(
div()
.flex_none()
.w(px(160.))
.overflow_hidden()
.text_ellipsis()
.whitespace_nowrap()
.truncate()
.text_size(rems(0.75))
.text_color(muted)
.map(|column| {
Expand Down
1 change: 1 addition & 0 deletions crates/ui_gpui/src/main_screen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1469,6 +1469,7 @@ impl MainScreen {
.text_xs()
.text_color(cx.theme().muted_foreground)
.line_clamp(4)
.text_ellipsis()
.child(request.summary.clone()),
),
)
Expand Down
6 changes: 2 additions & 4 deletions crates/ui_gpui/src/main_screen/project_dialog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,7 @@ impl Render for NewProjectDialog {
div()
.text_xs()
.text_color(cx.theme().danger.opacity(0.8))
.overflow_hidden()
.text_ellipsis()
.truncate()
.child(SharedString::from(existing_path.clone())),
),
),
Expand Down Expand Up @@ -236,8 +235,7 @@ impl Render for NewProjectDialog {
div()
.text_xs()
.text_color(cx.theme().muted_foreground)
.overflow_hidden()
.text_ellipsis()
.truncate()
.child(SharedString::from(path_display)),
)
// Name input
Expand Down
17 changes: 5 additions & 12 deletions crates/ui_gpui/src/main_screen/right_panel/browser/toolbar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

use super::BrowserPanel;
use crate::Gpui;
use crate::shared::text::single_line;
use code_assistant_core::session::browsers::BrowserKey;
use gpui_kit::component::button::{Button, ButtonVariants};
use gpui_kit::component::input::{Input, InputEvent, InputState};
Expand Down Expand Up @@ -49,7 +50,7 @@ impl BrowserPanel {
let label = if t.title.is_empty() { &t.url } else { &t.title };
chip(
SharedString::from(format!("tab-{}", t.id)),
truncate(label, 28),
single_line(label),
Some(t.id.as_str()) == watched_tab,
cx,
)
Expand Down Expand Up @@ -84,9 +85,7 @@ impl BrowserPanel {
.bg(theme.muted)
.text_xs()
.text_color(theme.foreground)
.overflow_hidden()
.whitespace_nowrap()
.text_ellipsis()
.truncate()
.when(in_control, |el| el.cursor_text())
.on_click(cx.listener(|this, _, window, cx| this.edit_address(window, cx)))
.child(tab.map(|t| t.url.clone()).unwrap_or_default())
Expand Down Expand Up @@ -246,6 +245,8 @@ fn chip(
let theme = cx.theme();
div()
.id(id)
.max_w(px(220.))
.truncate()
.px_2()
.py_0p5()
.rounded(px(4.))
Expand All @@ -269,14 +270,6 @@ fn browser_label(key: &BrowserKey) -> String {
}
}

fn truncate(text: &str, max: usize) -> String {
if text.chars().count() <= max {
return text.to_string();
}
let cut: String = text.chars().take(max.saturating_sub(1)).collect();
format!("{cut}…")
}

/// What the user typed as an address, with `https://` when it has no scheme.
fn with_scheme(typed: &str) -> String {
if typed.contains("://") || typed.starts_with("about:") || typed.starts_with("data:") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ impl Render for RasterPreview {
let muted = theme.muted_foreground;
let notice = move |text: String| {
div()
.min_w_0()
.text_sm()
.text_color(muted)
.child(text)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ impl Render for SvgPreview {
let themed = self.themed_background;
let notice = |text: String| {
div()
.min_w_0()
.text_sm()
.text_color(muted)
.child(text)
Expand Down
2 changes: 1 addition & 1 deletion crates/ui_gpui/src/main_screen/right_panel/file_viewer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -765,7 +765,7 @@ impl FileViewer {
.bg(theme.warning.opacity(0.12))
.text_xs()
.text_color(theme.foreground)
.child(div().flex_1().child(text))
.child(div().flex_1().min_w_0().child(text))
.when(actions, |d| {
d.child(
Button::new("file-reload-disk")
Expand Down
Loading
Loading