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
7 changes: 6 additions & 1 deletion src/core/loaders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ function buildDiffFile(
index: number,
sourcePrefix: string,
agentContext: AgentContext | null,
isUntracked?: boolean,
previousPath?: string,
): DiffFile {
return {
Expand All @@ -150,6 +151,7 @@ function buildDiffFile(
stats: countDiffStats(metadata),
metadata,
agent: findAgentFileContext(agentContext, metadata.name, metadata.prevName),
isUntracked,
};
}

Expand Down Expand Up @@ -235,6 +237,7 @@ function buildUntrackedDiffFile(
index,
sourcePrefix,
agentContext,
true, // isUntracked
);
}

Expand Down Expand Up @@ -363,7 +366,9 @@ async function loadFileDiffChangeset(
sourceLabel: input.kind === "difftool" ? "git difftool" : "file compare",
title,
agentSummary: agentContext?.summary,
files: [buildDiffFile(metadata, patch, 0, displayPath, agentContext, basename(input.left))],
files: [
buildDiffFile(metadata, patch, 0, displayPath, agentContext, undefined, basename(input.left)),
],
} satisfies Changeset;
}

Expand Down
1 change: 1 addition & 0 deletions src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export interface DiffFile {
};
metadata: FileDiffMetadata;
agent: AgentFileContext | null;
isUntracked?: boolean;
}

export interface Changeset {
Expand Down
10 changes: 8 additions & 2 deletions src/ui/components/panes/DiffSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { DiffFile, LayoutMode } from "../../../core/types";
import { PierreDiffView } from "../../diff/PierreDiffView";
import { getAnnotatedHunkIndices, type VisibleAgentNote } from "../../lib/agentAnnotations";
import { diffSectionId } from "../../lib/ids";
import { fileLabel } from "../../lib/files";
import { fileLabelParts } from "../../lib/files";
import { fitText } from "../../lib/text";
import type { AppTheme } from "../../themes";

Expand Down Expand Up @@ -52,6 +52,7 @@ function DiffSectionComponent({
const additionsText = `+${file.stats.additions}`;
const deletionsText = `-${file.stats.deletions}`;
const annotatedHunkIndices = getAnnotatedHunkIndices(file);
const { filename, stateLabel } = fileLabelParts(file);

return (
<box
Expand Down Expand Up @@ -90,7 +91,12 @@ function DiffSectionComponent({
onMouseUp={onSelect}
>
{/* Clicking the file header jumps the main stream selection without collapsing to a single-file view. */}
<text fg={theme.text}>{fitText(fileLabel(file), headerLabelWidth)}</text>
<box style={{ flexDirection: "row" }}>
<text fg={theme.text}>
{fitText(filename, Math.max(1, headerLabelWidth - (stateLabel?.length ?? 0)))}
</text>
{stateLabel && <text fg={theme.muted}>{stateLabel}</text>}
</box>
<box
style={{
width: headerStatsWidth,
Expand Down
10 changes: 8 additions & 2 deletions src/ui/components/panes/DiffSectionPlaceholder.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { DiffFile } from "../../../core/types";
import { diffSectionId } from "../../lib/ids";
import { fileLabel } from "../../lib/files";
import { fileLabelParts } from "../../lib/files";
import { fitText } from "../../lib/text";
import type { AppTheme } from "../../themes";

Expand Down Expand Up @@ -28,6 +28,7 @@ export function DiffSectionPlaceholder({
}: DiffSectionPlaceholderProps) {
const additionsText = `+${file.stats.additions}`;
const deletionsText = `-${file.stats.deletions}`;
const { filename, stateLabel } = fileLabelParts(file);

return (
<box
Expand Down Expand Up @@ -64,7 +65,12 @@ export function DiffSectionPlaceholder({
}}
onMouseUp={onSelect}
>
<text fg={theme.text}>{fitText(fileLabel(file), headerLabelWidth)}</text>
<box style={{ flexDirection: "row" }}>
<text fg={theme.text}>
{fitText(filename, Math.max(1, headerLabelWidth - (stateLabel?.length ?? 0)))}
</text>
{stateLabel && <text fg={theme.muted}>{stateLabel}</text>}
</box>
<box
style={{
width: headerStatsWidth,
Expand Down
26 changes: 25 additions & 1 deletion src/ui/components/panes/FileListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,27 @@ import { fitText, padText } from "../../lib/text";
import type { AppTheme } from "../../themes";
import { fileRowId } from "../../lib/ids";

/** Get icon and color for file state using standard git status codes. */
function getFileStateIcon(entry: FileListEntry, theme: AppTheme): { icon: string; color: string } {
if (entry.isUntracked) {
return { icon: "?", color: theme.fileUntracked };
}

switch (entry.changeType) {
case "new":
return { icon: "A", color: theme.fileNew };
case "deleted":
return { icon: "D", color: theme.fileDeleted };
case "rename-pure":
case "rename-changed":
return { icon: "R", color: theme.fileRenamed };
case "change":
return { icon: "M", color: theme.fileModified };
default:
return { icon: "", color: theme.text };
}
}

/** Render one folder header in the navigation sidebar. */
export function FileGroupHeader({
entry,
Expand Down Expand Up @@ -47,7 +68,9 @@ export function FileListItem({
}) {
const rowBackground = selected ? theme.panelAlt : theme.panel;
const statsWidth = additionsWidth + 1 + deletionsWidth;
const nameWidth = Math.max(1, textWidth - 1 - statsWidth - 1);
const { icon, color } = getFileStateIcon(entry, theme);
const iconWidth = icon ? 2 : 0; // icon + space
const nameWidth = Math.max(1, textWidth - 1 - iconWidth - statsWidth - 1);

return (
<box
Expand Down Expand Up @@ -76,6 +99,7 @@ export function FileListItem({
backgroundColor: rowBackground,
}}
>
{icon && <text fg={color}>{icon} </text>}
<text fg={theme.text}>{padText(fitText(entry.name, nameWidth), nameWidth)}</text>
<text fg={theme.badgeAdded}>{entry.additionsText.padStart(additionsWidth, " ")}</text>
<text fg={selected ? theme.text : theme.muted}> </text>
Expand Down
35 changes: 31 additions & 4 deletions src/ui/lib/files.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { basename, dirname } from "node:path/posix";
import type { FileDiffMetadata } from "@pierre/diffs";
import type { AgentAnnotation, DiffFile } from "../../core/types";

export interface FileListEntry {
Expand All @@ -7,6 +8,8 @@ export interface FileListEntry {
name: string;
additionsText: string;
deletionsText: string;
changeType: FileDiffMetadata["type"];
isUntracked: boolean;
}

export interface FileGroupEntry {
Expand Down Expand Up @@ -92,6 +95,8 @@ export function buildSidebarEntries(files: DiffFile[]): SidebarEntry[] {
name: sidebarFileName(file),
additionsText: `+${file.stats.additions}`,
deletionsText: `-${file.stats.deletions}`,
changeType: file.metadata.type,
isUntracked: file.isUntracked ?? false,
});
});

Expand All @@ -100,11 +105,33 @@ export function buildSidebarEntries(files: DiffFile[]): SidebarEntry[] {

/** Build the canonical file label used across headers and note cards. */
export function fileLabel(file: DiffFile | undefined) {
const { filename, stateLabel } = fileLabelParts(file);
return stateLabel ? `${filename}${stateLabel}` : filename;
}

/** Split file label into filename and state label for styled rendering. */
export function fileLabelParts(file: DiffFile | undefined): {
filename: string;
stateLabel: string | null;
} {
if (!file) {
return "No file selected";
return { filename: "No file selected", stateLabel: null };
}

const baseLabel =
file.previousPath && file.previousPath !== file.path
? `${file.previousPath} -> ${file.path}`
: file.path;

// Determine state label for special cases
let stateLabel: string | null = null;
if (file.isUntracked) {
stateLabel = " (untracked)";
} else if (file.metadata.type === "new") {
stateLabel = " (new)";
} else if (file.metadata.type === "deleted") {
stateLabel = " (deleted)";
}

return file.previousPath && file.previousPath !== file.path
? `${file.previousPath} -> ${file.path}`
: file.path;
return { filename: baseLabel, stateLabel };
}
25 changes: 25 additions & 0 deletions src/ui/themes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ export interface AppTheme {
badgeAdded: string;
badgeRemoved: string;
badgeNeutral: string;
fileNew: string;
fileDeleted: string;
fileRenamed: string;
fileModified: string;
fileUntracked: string;
noteBorder: string;
noteBackground: string;
noteTitleBackground: string;
Expand Down Expand Up @@ -110,6 +115,11 @@ export const THEMES: AppTheme[] = [
badgeAdded: "#88d39b",
badgeRemoved: "#f0a0a0",
badgeNeutral: "#a9b4bf",
fileNew: "#88d39b",
fileDeleted: "#f0a0a0",
fileRenamed: "#e6cf98",
fileModified: "#c49bff",
fileUntracked: "#7fd1ff",
noteBorder: "#c6a0ff",
noteBackground: "#241c31",
noteTitleBackground: "#322446",
Expand Down Expand Up @@ -154,6 +164,11 @@ export const THEMES: AppTheme[] = [
badgeAdded: "#5ad188",
badgeRemoved: "#ff8b8b",
badgeNeutral: "#89a5d3",
fileNew: "#5ad188",
fileDeleted: "#ff8b8b",
fileRenamed: "#ffd883",
fileModified: "#b794f6",
fileUntracked: "#7fd1ff",
noteBorder: "#c49bff",
noteBackground: "#211a36",
noteTitleBackground: "#30234f",
Expand Down Expand Up @@ -198,6 +213,11 @@ export const THEMES: AppTheme[] = [
badgeAdded: "#3f8d58",
badgeRemoved: "#b4545b",
badgeNeutral: "#8e7355",
fileNew: "#3f8d58",
fileDeleted: "#b4545b",
fileRenamed: "#9f6c1f",
fileModified: "#7d5bc4",
fileUntracked: "#4a6890",
noteBorder: "#7d5bc4",
noteBackground: "#efe6ff",
noteTitleBackground: "#e3d7ff",
Expand Down Expand Up @@ -242,6 +262,11 @@ export const THEMES: AppTheme[] = [
badgeAdded: "#83d99d",
badgeRemoved: "#ff9d8f",
badgeNeutral: "#f1be9d",
fileNew: "#83d99d",
fileDeleted: "#ff9d8f",
fileRenamed: "#ffd08f",
fileModified: "#d8b4fe",
fileUntracked: "#ffb07a",
noteBorder: "#e1a3ff",
noteBackground: "#311d36",
noteTitleBackground: "#452650",
Expand Down
Loading