fix(tui): use available terminal width for session name display (#16109) (#16238)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 19c18977e0
Co-authored-by: robbyczgw-cla <239660374+robbyczgw-cla@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>
Reviewed-by: @steipete
This commit is contained in:
Robby
2026-02-14 18:39:05 +01:00
committed by GitHub
parent 8e5689a84d
commit 5a313c83b7
3 changed files with 33 additions and 14 deletions

View File

@@ -38,6 +38,16 @@ describe("SearchableSelectList", () => {
expect(output[0]).toContain("search");
});
it("does not truncate long labels on wide terminals when description is present", () => {
const tail = "__TAIL__";
const longLabel = `session-${"x".repeat(40)}${tail}`; // > 30 chars; tail would be lost before PR
const items = [{ value: longLabel, label: longLabel, description: "desc" }];
const list = new SearchableSelectList(items, 5, mockTheme);
const output = list.render(120).join("\n");
expect(output).toContain(tail);
});
it("filters items when typing", () => {
const list = new SearchableSelectList(testItems, 5, mockTheme);

View File

@@ -219,20 +219,28 @@ export class SearchableSelectList implements Component {
const displayValue = this.getItemLabel(item);
if (item.description && width > 40) {
const maxValueWidth = Math.min(30, width - prefixWidth - 4);
const truncatedValue = truncateToWidth(displayValue, maxValueWidth, "");
const valueText = this.highlightMatch(truncatedValue, query);
const spacingWidth = Math.max(1, 32 - visibleWidth(valueText));
const spacing = " ".repeat(spacingWidth);
const descriptionStart = prefixWidth + visibleWidth(valueText) + spacing.length;
const remainingWidth = width - descriptionStart - 2;
if (remainingWidth > 10) {
const truncatedDesc = truncateToWidth(item.description, remainingWidth, "");
// Highlight plain text first, then apply theme styling to avoid corrupting ANSI codes
const highlightedDesc = this.highlightMatch(truncatedDesc, query);
const descText = isSelected ? highlightedDesc : this.theme.description(highlightedDesc);
const line = `${prefix}${valueText}${spacing}${descText}`;
return isSelected ? this.theme.selectedText(line) : line;
const minDescriptionWidth = 12;
const spacingWidth = 2;
const availableWidth = Math.max(1, width - prefixWidth - 2);
if (availableWidth > minDescriptionWidth + spacingWidth + 1) {
const maxValueWidth = availableWidth - minDescriptionWidth - spacingWidth;
const truncatedValue = truncateToWidth(displayValue, maxValueWidth, "");
const valueText = this.highlightMatch(truncatedValue, query);
const usedByValue = visibleWidth(valueText);
const remainingWidth = availableWidth - usedByValue;
if (remainingWidth > spacingWidth + 1) {
const descriptionWidth = remainingWidth - spacingWidth;
const spacing = " ".repeat(spacingWidth);
const truncatedDesc = truncateToWidth(item.description, descriptionWidth, "");
// Highlight plain text first, then apply theme styling to avoid corrupting ANSI codes
const highlightedDesc = this.highlightMatch(truncatedDesc, query);
const descText = isSelected ? highlightedDesc : this.theme.description(highlightedDesc);
const line = `${prefix}${valueText}${spacing}${descText}`;
return isSelected ? this.theme.selectedText(line) : line;
}
}
}