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
40 changes: 28 additions & 12 deletions src/tui/components/at-mention/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,18 @@ export async function listPathSuggestions(
// prefix as filter (the `@` alone case, like `ls`).
const lastSlash = rest.lastIndexOf("/");
const hasSlash = lastSlash !== -1;
const dir = endsWithSep ? rest || "." : hasSlash ? dirname(rest) : ".";
const fragment = endsWithSep ? "" : hasSlash ? basename(rest) : rest;
let dir: string;
let fragment: string;
if (endsWithSep) {
dir = rest || ".";
fragment = "";
} else if (hasSlash) {
dir = dirname(rest);
fragment = basename(rest);
} else {
dir = ".";
fragment = rest;
}
const realDir = await resolveDirectory(dir, base);
if (realDir === null) return [];

Expand All @@ -56,16 +66,22 @@ export async function listPathSuggestions(
// Reconstruct the path the user would type: bare fragments are shown
// relative to cwd (dirPrefix ""), and home-relative results keep the
// `~/` form so the completion inserts text the submit path expands.
const innerPrefix = endsWithSep
? rest
: hasSlash
? rest.slice(0, rest.length - fragment.length)
: "";
const dirPrefix = homeRelative
? `~/${innerPrefix}`
: endsWithSep
? prefix
: innerPrefix;
let innerPrefix: string;
if (endsWithSep) {
innerPrefix = rest;
} else if (hasSlash) {
innerPrefix = rest.slice(0, rest.length - fragment.length);
} else {
innerPrefix = "";
}
let dirPrefix: string;
if (homeRelative) {
dirPrefix = `~/${innerPrefix}`;
} else if (endsWithSep) {
dirPrefix = prefix;
} else {
dirPrefix = innerPrefix;
}
matched.push(dirPrefix + entry.name + (entry.isDirectory() ? "/" : ""));
}

Expand Down
8 changes: 2 additions & 6 deletions src/tui/copy-path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import { diffPlainText } from "./diff.js";
import type { StreamRow } from "./stream.js";
import { abbreviate } from "./tool-formatter.js";

export type CopyKind = "message" | "tool" | "diff" | "system";

Expand Down Expand Up @@ -72,11 +73,6 @@ export function createRecordingClipboard(): ClipboardPort & {
};
}

function oneLine(text: string, max = 56): string {
const collapsed = text.replace(/\s+/g, " ").trim();
return collapsed.length > max ? `${collapsed.slice(0, max - 1)}…` : collapsed;
}

/**
* Infer copy kind from a stream row.
* Rows carrying a rendered edit diff win; otherwise assistant/tool rows whose
Expand Down Expand Up @@ -154,7 +150,7 @@ export function buildCopyTargets(log: readonly StreamRow[]): CopyTarget[] {
targets.push({
id: `row-${i}`,
label: copyRowLabel(row),
preview: oneLine(payload.text),
preview: abbreviate(payload.text, 56),
text: payload.text,
});
}
Expand Down
34 changes: 24 additions & 10 deletions src/tui/markdown-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,20 @@ export interface MarkdownSplit {
readonly gapRows: number;
}

/** Build a split whose live tail starts at `firstContent`; one blank line
* between the frozen boundary and the live tail collapses to a single gap row. */
function settledSplit(
frozen: string,
rest: string[],
firstContent: number,
): MarkdownSplit {
return {
frozen,
live: rest.slice(firstContent).join("\n"),
gapRows: firstContent > 0 ? 1 : 0,
};
}

export function splitAtSettledHeading(text: string): MarkdownSplit | null {
const lines = text.split("\n");
const insideFence = fencedLineMask(lines);
Expand All @@ -797,11 +811,11 @@ export function splitAtSettledHeading(text: string): MarkdownSplit | null {
const firstContent = rest.findIndex((line) => line.trim().length > 0);
// Heading closed but nothing has started under it yet.
if (firstContent === -1) return null;
return {
frozen: lines.slice(0, boundary + 1).join("\n"),
live: rest.slice(firstContent).join("\n"),
gapRows: firstContent > 0 ? 1 : 0,
};
return settledSplit(
lines.slice(0, boundary + 1).join("\n"),
rest,
firstContent,
);
}

/**
Expand Down Expand Up @@ -870,11 +884,11 @@ export function splitAtSettledBlock(withheld: string): MarkdownSplit | null {
const rest = lines.slice(boundary + 1);
const firstContent = rest.findIndex((entry) => entry.trim().length > 0);
if (firstContent === -1) continue;
return {
frozen: lines.slice(0, boundary + 1).join("\n"),
live: rest.slice(firstContent).join("\n"),
gapRows: firstContent > 0 ? 1 : 0,
};
return settledSplit(
lines.slice(0, boundary + 1).join("\n"),
rest,
firstContent,
);
}
return null;
}
Expand Down
1 change: 0 additions & 1 deletion src/tui/notice-line.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {

const state = (over: Partial<NoticeState> = {}): NoticeState => ({
waitingOn: null,
interrupt: false,
pinned: false,
flash: null,
attachments: 0,
Expand Down
1 change: 0 additions & 1 deletion src/tui/notice-line.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ export interface NoticeState {
* Gated by `resolveWaitingOn`; this field only controls wording.
*/
readonly waitingOn: string | null;
readonly interrupt: boolean;
/** Transcript scrolled off the tail (non-default follow state). */
readonly pinned: boolean;
/** Transient feedback (copy result, attach failure, exit arming). */
Expand Down
1 change: 0 additions & 1 deletion src/tui/shell/chrome.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,6 @@ export function noticeText(shell: AppShell): string {
shell.inFlightTool,
shell.lockupNowMs,
),
interrupt: shell.session.interruptFlash,
pinned: !isTranscriptFollowing(shell),
flash: shell.statusFlash,
attachments: shell.pendingAttachments.length,
Expand Down
4 changes: 2 additions & 2 deletions src/tui/tool-formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,8 @@ export function describeToolCall(
const subject = description.length > 0 ? description : prompt;
const first = agentName?.[0];
const display =
first != null && agentName !== undefined
? first.toUpperCase() + agentName.slice(1)
first != null
? first.toUpperCase() + (agentName ?? "").slice(1)
: "Worker";
// Collapsed row uses the abbreviated subject; Alt+E expands to the full text.
return {
Expand Down
Loading