Restore compact Changes diff controls (#1945)
* feat(app): restore compact Changes diff controls Keep the folder tree behind a view toggle while preserving the flat file list as the default. Move secondary diff options into the overflow menu so the Changes toolbar stays compact. * fix(app): mirror whitespace diff option copy * fix(app): polish diff controls review follow-up
This commit is contained in:
@@ -218,6 +218,38 @@ test("changes diff keeps code rows aligned with the gutter", async ({ page }) =>
|
||||
});
|
||||
});
|
||||
|
||||
test("changes diff switches between flat and tree file lists", async ({ page }) => {
|
||||
const workspace = await createWorkspaceWithMountedTabDiff();
|
||||
await useUnwrappedDiffLines(page);
|
||||
await openWorkspaceChanges(page, workspace);
|
||||
|
||||
await expectFlatFileList(page);
|
||||
await expect(page.getByTestId("changes-toggle-layout")).toBeVisible();
|
||||
await expect(page.getByTestId("changes-layout-unified")).toHaveCount(0);
|
||||
await expect(page.getByTestId("changes-layout-split")).toHaveCount(0);
|
||||
|
||||
await page.getByTestId("changes-options-menu").click();
|
||||
await expect(page.getByTestId("changes-options-menu-content")).toBeVisible();
|
||||
await expect(page.getByTestId("changes-toggle-whitespace")).toContainText("Hide whitespace");
|
||||
await expect(page.getByTestId("changes-toggle-wrap-lines")).toContainText("Wrap long lines");
|
||||
await expect(page.getByTestId("changes-refresh")).toContainText("Refresh");
|
||||
await page.getByTestId("changes-toggle-whitespace").click();
|
||||
await page.getByTestId("changes-options-menu").click();
|
||||
await expect(page.getByTestId("changes-toggle-whitespace")).toContainText("Show whitespace");
|
||||
await page.keyboard.press("Escape");
|
||||
|
||||
await scrollToLowerUnwrappedDiffRows(page);
|
||||
await page.getByTestId("changes-toggle-view-mode").click();
|
||||
await expect(page.getByTestId("diff-folder-src")).toBeVisible();
|
||||
await expect(page.getByTestId("diff-file-0")).toBeVisible();
|
||||
|
||||
await page.getByTestId("diff-folder-src-toggle").click();
|
||||
await expect(page.getByTestId("diff-file-0")).toHaveCount(0);
|
||||
|
||||
await page.getByTestId("changes-toggle-view-mode").click();
|
||||
await expectFlatFileList(page);
|
||||
});
|
||||
|
||||
test("changes diff keeps unwrapped gutter and code rows aligned after code size changes", async ({
|
||||
page,
|
||||
}) => {
|
||||
@@ -266,13 +298,24 @@ async function useUnwrappedDiffLines(page: Page): Promise<void> {
|
||||
({ preferencesKey }) => {
|
||||
localStorage.setItem(
|
||||
preferencesKey,
|
||||
JSON.stringify({ layout: "unified", wrapLines: false, hideWhitespace: false }),
|
||||
JSON.stringify({
|
||||
layout: "unified",
|
||||
viewMode: "flat",
|
||||
wrapLines: false,
|
||||
hideWhitespace: false,
|
||||
}),
|
||||
);
|
||||
},
|
||||
{ preferencesKey: CHANGES_PREFERENCES_KEY },
|
||||
);
|
||||
}
|
||||
|
||||
async function expectFlatFileList(page: Page): Promise<void> {
|
||||
await expect(page.locator('[data-testid^="diff-folder-"]')).toHaveCount(0);
|
||||
await expect(page.getByTestId("diff-file-0")).toContainText("use-mounted-tab-set.ts");
|
||||
await expect(page.getByTestId("diff-file-0")).toContainText("src");
|
||||
}
|
||||
|
||||
async function expectDiffCodeFontSize(page: Page, fontSize: number): Promise<void> {
|
||||
await expect
|
||||
.poll(async () => {
|
||||
|
||||
@@ -22,6 +22,7 @@ describe("buildDiffFlatItems", () => {
|
||||
it("emits a body and a sticky index for each expanded file", () => {
|
||||
const { items, stickyHeaderIndices } = buildDiffFlatItems({
|
||||
files: [createFile("a.ts"), createFile("b.ts")],
|
||||
viewMode: "tree",
|
||||
collapsedFolders: new Set(),
|
||||
expandedPaths: new Set(["a.ts"]),
|
||||
});
|
||||
@@ -32,9 +33,23 @@ describe("buildDiffFlatItems", () => {
|
||||
expect(items[0].type).toBe("header");
|
||||
});
|
||||
|
||||
it("emits the old flat list without folder rows or indentation", () => {
|
||||
const { items, stickyHeaderIndices } = buildDiffFlatItems({
|
||||
files,
|
||||
viewMode: "flat",
|
||||
collapsedFolders: new Set(),
|
||||
expandedPaths: new Set(["src/app/a.ts"]),
|
||||
});
|
||||
|
||||
expect(summarize(items)).toEqual(["a.ts", "body:a.ts", "b.ts"]);
|
||||
expect(stickyHeaderIndices).toEqual([0]);
|
||||
expect(items.every((item) => item.type !== "folder")).toBe(true);
|
||||
});
|
||||
|
||||
it("groups files under compressed folder rows, all expanded by default", () => {
|
||||
const { items } = buildDiffFlatItems({
|
||||
files,
|
||||
viewMode: "tree",
|
||||
collapsedFolders: new Set(),
|
||||
expandedPaths: new Set(),
|
||||
});
|
||||
@@ -45,6 +60,7 @@ describe("buildDiffFlatItems", () => {
|
||||
it("collapsing a folder hides its descendants but keeps the row", () => {
|
||||
const { items } = buildDiffFlatItems({
|
||||
files,
|
||||
viewMode: "tree",
|
||||
collapsedFolders: new Set(["src/app/nested"]),
|
||||
expandedPaths: new Set(),
|
||||
});
|
||||
@@ -54,6 +70,7 @@ describe("buildDiffFlatItems", () => {
|
||||
it("collapsing an ancestor hides everything below it", () => {
|
||||
const { items } = buildDiffFlatItems({
|
||||
files,
|
||||
viewMode: "tree",
|
||||
collapsedFolders: new Set(["src/app"]),
|
||||
expandedPaths: new Set(),
|
||||
});
|
||||
@@ -65,6 +82,7 @@ describe("buildDiffFlatItems", () => {
|
||||
// header at index 3, with the body right after.
|
||||
const { items, stickyHeaderIndices } = buildDiffFlatItems({
|
||||
files,
|
||||
viewMode: "tree",
|
||||
collapsedFolders: new Set(),
|
||||
expandedPaths: new Set(["src/app/a.ts"]),
|
||||
});
|
||||
@@ -84,6 +102,7 @@ describe("buildDiffFlatItems", () => {
|
||||
it("maps tree file rows back to their original files array index", () => {
|
||||
const { items } = buildDiffFlatItems({
|
||||
files,
|
||||
viewMode: "tree",
|
||||
collapsedFolders: new Set(),
|
||||
expandedPaths: new Set(),
|
||||
});
|
||||
@@ -96,6 +115,7 @@ describe("buildDiffFlatItems", () => {
|
||||
describe("sumHeightsBefore", () => {
|
||||
const items = buildDiffFlatItems({
|
||||
files: [createFile("src/a.ts"), createFile("src/b.ts")],
|
||||
viewMode: "tree",
|
||||
collapsedFolders: new Set(),
|
||||
expandedPaths: new Set(),
|
||||
}).items; // [folder, a.ts, b.ts]
|
||||
|
||||
@@ -30,6 +30,7 @@ export interface DiffFlatItemsResult {
|
||||
|
||||
export interface BuildDiffFlatItemsInput {
|
||||
files: ParsedDiffFile[];
|
||||
viewMode: "flat" | "tree";
|
||||
/** Full uncompressed directory paths that are collapsed (empty = all expanded). */
|
||||
collapsedFolders: ReadonlySet<string>;
|
||||
/** File paths whose diff body is expanded. */
|
||||
@@ -52,6 +53,7 @@ export interface BuildDiffFlatItemsInput {
|
||||
*/
|
||||
export function buildDiffFlatItems({
|
||||
files,
|
||||
viewMode,
|
||||
collapsedFolders,
|
||||
expandedPaths,
|
||||
tree,
|
||||
@@ -68,6 +70,13 @@ export function buildDiffFlatItems({
|
||||
}
|
||||
};
|
||||
|
||||
if (viewMode === "flat") {
|
||||
for (const [fileIndex, file] of files.entries()) {
|
||||
pushFile(file, fileIndex, 0);
|
||||
}
|
||||
return { items, stickyHeaderIndices };
|
||||
}
|
||||
|
||||
const indexByPath = new Map(files.map((file, index) => [file.path, index]));
|
||||
const compressedTree = tree ?? compressSingleChildChains(buildDiffTree(files));
|
||||
const rows = flattenDiffTree(compressedTree, collapsedFolders);
|
||||
|
||||
+214
-300
@@ -39,6 +39,7 @@ import {
|
||||
FolderTree,
|
||||
GitCommitHorizontal,
|
||||
GitMerge,
|
||||
List,
|
||||
ListChevronsDownUp,
|
||||
ListChevronsUpDown,
|
||||
Pilcrow,
|
||||
@@ -1219,7 +1220,6 @@ type PressableStyleFn = (
|
||||
state: PressableStateCallbackType & { hovered?: boolean; open?: boolean },
|
||||
) => StyleProp<ViewStyle>;
|
||||
|
||||
const foregroundIconColorMapping = (theme: Theme) => ({ color: theme.colors.foreground });
|
||||
const foregroundMutedIconColorMapping = (theme: Theme) => ({ color: theme.colors.foregroundMuted });
|
||||
|
||||
const ThemedActivityIndicator = withUnistyles(ActivityIndicator);
|
||||
@@ -1230,6 +1230,7 @@ const ThemedWrapText = withUnistyles(WrapText);
|
||||
const ThemedListChevronsDownUp = withUnistyles(ListChevronsDownUp);
|
||||
const ThemedListChevronsUpDown = withUnistyles(ListChevronsUpDown);
|
||||
const ThemedFolderTree = withUnistyles(FolderTree);
|
||||
const ThemedList = withUnistyles(List);
|
||||
const ThemedGitCommitHorizontal = withUnistyles(GitCommitHorizontal);
|
||||
const ThemedDownload = withUnistyles(Download);
|
||||
const ThemedUpload = withUnistyles(Upload);
|
||||
@@ -1240,101 +1241,89 @@ const ThemedRefreshCcw = withUnistyles(RefreshCcw);
|
||||
const ThemedArchive = withUnistyles(Archive);
|
||||
const ThemedChevronDown = withUnistyles(ChevronDown);
|
||||
|
||||
interface DiffLayoutToggleGroupProps {
|
||||
const DIFF_OPTIONS_WHITESPACE_ICON = (
|
||||
<ThemedPilcrow size={14} uniProps={foregroundMutedIconColorMapping} />
|
||||
);
|
||||
const DIFF_OPTIONS_WRAP_ICON = (
|
||||
<ThemedWrapText size={14} uniProps={foregroundMutedIconColorMapping} />
|
||||
);
|
||||
|
||||
interface DiffLayoutToggleProps {
|
||||
layout: "unified" | "split";
|
||||
unifiedToggleStyle: PressableStyleFn;
|
||||
splitToggleStyle: PressableStyleFn;
|
||||
onUnified: () => void;
|
||||
onSplit: () => void;
|
||||
}
|
||||
|
||||
function DiffLayoutToggleGroup({
|
||||
layout,
|
||||
unifiedToggleStyle,
|
||||
splitToggleStyle,
|
||||
onUnified,
|
||||
onSplit,
|
||||
}: DiffLayoutToggleGroupProps) {
|
||||
const { t } = useTranslation();
|
||||
const unifiedLabel = t("workspace.git.diff.unified");
|
||||
const splitLabel = t("workspace.git.diff.split");
|
||||
return (
|
||||
<View style={styles.toggleButtonGroup}>
|
||||
<Tooltip delayDuration={300}>
|
||||
<TooltipTrigger asChild>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={unifiedLabel}
|
||||
testID="changes-layout-unified"
|
||||
onPress={onUnified}
|
||||
style={unifiedToggleStyle}
|
||||
>
|
||||
<ThemedAlignJustify
|
||||
size={14}
|
||||
uniProps={
|
||||
layout === "unified" ? foregroundIconColorMapping : foregroundMutedIconColorMapping
|
||||
}
|
||||
/>
|
||||
</Pressable>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">
|
||||
<Text style={styles.tooltipText}>{unifiedLabel}</Text>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip delayDuration={300}>
|
||||
<TooltipTrigger asChild>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={splitLabel}
|
||||
testID="changes-layout-split"
|
||||
onPress={onSplit}
|
||||
style={splitToggleStyle}
|
||||
>
|
||||
<ThemedColumns2
|
||||
size={14}
|
||||
uniProps={
|
||||
layout === "split" ? foregroundIconColorMapping : foregroundMutedIconColorMapping
|
||||
}
|
||||
/>
|
||||
</Pressable>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">
|
||||
<Text style={styles.tooltipText}>{splitLabel}</Text>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
interface DiffWhitespaceToggleProps {
|
||||
hideWhitespace: boolean;
|
||||
isMobile: boolean;
|
||||
toggleStyle: PressableStyleFn;
|
||||
onToggle: () => void;
|
||||
}
|
||||
|
||||
function DiffWhitespaceToggle({
|
||||
hideWhitespace,
|
||||
isMobile,
|
||||
toggleStyle,
|
||||
onToggle,
|
||||
}: DiffWhitespaceToggleProps) {
|
||||
function DiffLayoutToggle({ layout, isMobile, toggleStyle, onToggle }: DiffLayoutToggleProps) {
|
||||
const { t } = useTranslation();
|
||||
const label = t("workspace.git.diff.hideWhitespace");
|
||||
const label =
|
||||
layout === "unified"
|
||||
? t("workspace.git.diff.switchToSplit")
|
||||
: t("workspace.git.diff.switchToUnified");
|
||||
return (
|
||||
<Tooltip delayDuration={300}>
|
||||
<TooltipTrigger asChild>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={label}
|
||||
testID="changes-toggle-whitespace"
|
||||
testID="changes-toggle-layout"
|
||||
onPress={onToggle}
|
||||
style={toggleStyle}
|
||||
>
|
||||
{layout === "unified" ? (
|
||||
<ThemedColumns2 size={isMobile ? 18 : 14} uniProps={foregroundMutedIconColorMapping} />
|
||||
) : (
|
||||
<ThemedAlignJustify
|
||||
size={isMobile ? 18 : 14}
|
||||
uniProps={foregroundMutedIconColorMapping}
|
||||
/>
|
||||
)}
|
||||
</Pressable>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">
|
||||
<Text style={styles.tooltipText}>{label}</Text>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
interface DiffViewModeToggleProps {
|
||||
viewMode: "flat" | "tree";
|
||||
isMobile: boolean;
|
||||
toggleStyle: PressableStyleFn;
|
||||
onToggle: () => void;
|
||||
}
|
||||
|
||||
function DiffViewModeToggle({
|
||||
viewMode,
|
||||
isMobile,
|
||||
toggleStyle,
|
||||
onToggle,
|
||||
}: DiffViewModeToggleProps) {
|
||||
const { t } = useTranslation();
|
||||
const label =
|
||||
viewMode === "flat"
|
||||
? t("workspace.git.diff.showTreeView")
|
||||
: t("workspace.git.diff.showFlatView");
|
||||
return (
|
||||
<Tooltip delayDuration={300}>
|
||||
<TooltipTrigger asChild>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={label}
|
||||
testID="changes-toggle-view-mode"
|
||||
style={toggleStyle}
|
||||
onPress={onToggle}
|
||||
>
|
||||
<ThemedPilcrow
|
||||
size={isMobile ? 18 : 14}
|
||||
uniProps={hideWhitespace ? foregroundIconColorMapping : foregroundMutedIconColorMapping}
|
||||
/>
|
||||
{viewMode === "flat" ? (
|
||||
<ThemedFolderTree
|
||||
size={isMobile ? 18 : 14}
|
||||
uniProps={foregroundMutedIconColorMapping}
|
||||
/>
|
||||
) : (
|
||||
<ThemedList size={isMobile ? 18 : 14} uniProps={foregroundMutedIconColorMapping} />
|
||||
)}
|
||||
</Pressable>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">
|
||||
@@ -1345,82 +1334,24 @@ function DiffWhitespaceToggle({
|
||||
}
|
||||
|
||||
interface DiffFilesToolbarProps {
|
||||
wrapLines: boolean;
|
||||
// Controls file-DIFF bodies only (not folders). Named explicitly so the
|
||||
// folder control below stays unambiguous (Codex item 4).
|
||||
allFileDiffsExpanded: boolean;
|
||||
showFolderControl: boolean;
|
||||
allFoldersCollapsed: boolean;
|
||||
isMobile: boolean;
|
||||
wrapLinesToggleStyle: PressableStyleFn;
|
||||
expandAllToggleStyle: PressableStyleFn;
|
||||
folderToggleStyle: PressableStyleFn;
|
||||
onToggleWrapLines: () => void;
|
||||
onToggleExpandAll: () => void;
|
||||
onToggleCollapseAllFolders: () => void;
|
||||
}
|
||||
|
||||
function DiffFilesToolbar({
|
||||
wrapLines,
|
||||
allFileDiffsExpanded,
|
||||
showFolderControl,
|
||||
allFoldersCollapsed,
|
||||
isMobile,
|
||||
wrapLinesToggleStyle,
|
||||
expandAllToggleStyle,
|
||||
folderToggleStyle,
|
||||
onToggleWrapLines,
|
||||
onToggleExpandAll,
|
||||
onToggleCollapseAllFolders,
|
||||
}: DiffFilesToolbarProps) {
|
||||
const { t } = useTranslation();
|
||||
const wrapLinesLabel = wrapLines
|
||||
? t("workspace.git.diff.scrollLongLines")
|
||||
: t("workspace.git.diff.wrapLongLines");
|
||||
const expandAllLabel = allFileDiffsExpanded
|
||||
? t("workspace.git.diff.collapseAll")
|
||||
: t("workspace.git.diff.expandAll");
|
||||
const folderLabel = allFoldersCollapsed
|
||||
? t("workspace.git.diff.expandAllFolders")
|
||||
: t("workspace.git.diff.collapseAllFolders");
|
||||
return (
|
||||
<View style={styles.diffStatusButtons}>
|
||||
{showFolderControl ? (
|
||||
<Tooltip delayDuration={300}>
|
||||
<TooltipTrigger asChild>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={folderLabel}
|
||||
testID="changes-toggle-folders"
|
||||
style={folderToggleStyle}
|
||||
onPress={onToggleCollapseAllFolders}
|
||||
>
|
||||
<ThemedFolderTree
|
||||
size={isMobile ? 18 : 14}
|
||||
uniProps={
|
||||
allFoldersCollapsed ? foregroundIconColorMapping : foregroundMutedIconColorMapping
|
||||
}
|
||||
/>
|
||||
</Pressable>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">
|
||||
<Text style={styles.tooltipText}>{folderLabel}</Text>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
<Tooltip delayDuration={300}>
|
||||
<TooltipTrigger asChild>
|
||||
<Pressable style={wrapLinesToggleStyle} onPress={onToggleWrapLines}>
|
||||
<ThemedWrapText
|
||||
size={isMobile ? 18 : 14}
|
||||
uniProps={wrapLines ? foregroundIconColorMapping : foregroundMutedIconColorMapping}
|
||||
/>
|
||||
</Pressable>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">
|
||||
<Text style={styles.tooltipText}>{wrapLinesLabel}</Text>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip delayDuration={300}>
|
||||
<TooltipTrigger asChild>
|
||||
<Pressable
|
||||
@@ -1450,50 +1381,105 @@ function DiffFilesToolbar({
|
||||
);
|
||||
}
|
||||
|
||||
interface DiffRefreshButtonProps {
|
||||
interface DiffOptionsMenuProps {
|
||||
hideWhitespace: boolean;
|
||||
isMobile: boolean;
|
||||
isRefreshing: boolean;
|
||||
toggleStyle: PressableStyleFn;
|
||||
onPress: () => void;
|
||||
overflowToggleStyle: PressableStyleFn;
|
||||
refreshSupported: boolean;
|
||||
wrapLines: boolean;
|
||||
onRefresh: () => void;
|
||||
onToggleHideWhitespace: () => void;
|
||||
onToggleWrapLines: () => void;
|
||||
}
|
||||
|
||||
function DiffOptionsMenu({
|
||||
hideWhitespace,
|
||||
isMobile,
|
||||
isRefreshing,
|
||||
overflowToggleStyle,
|
||||
refreshSupported,
|
||||
wrapLines,
|
||||
onRefresh,
|
||||
onToggleHideWhitespace,
|
||||
onToggleWrapLines,
|
||||
}: DiffOptionsMenuProps) {
|
||||
const { t } = useTranslation();
|
||||
const whitespaceLabel = hideWhitespace
|
||||
? t("workspace.git.diff.showWhitespace")
|
||||
: t("workspace.git.diff.hideWhitespace");
|
||||
const wrapLinesLabel = wrapLines
|
||||
? t("workspace.git.diff.scrollLongLines")
|
||||
: t("workspace.git.diff.wrapLongLines");
|
||||
const optionsLabel = t("workspace.git.diff.options");
|
||||
const refreshIcon = useMemo(
|
||||
() =>
|
||||
isRefreshing ? (
|
||||
<ThemedLoadingSpinner size={ICON_SIZE.sm} uniProps={foregroundMutedIconColorMapping} />
|
||||
) : (
|
||||
<ThemedRotateCw size={ICON_SIZE.sm} uniProps={foregroundMutedIconColorMapping} />
|
||||
),
|
||||
[isRefreshing],
|
||||
);
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<Tooltip delayDuration={300}>
|
||||
<TooltipTrigger asChild>
|
||||
<DropdownMenuTrigger
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={optionsLabel}
|
||||
testID="changes-options-menu"
|
||||
style={overflowToggleStyle}
|
||||
>
|
||||
<ThemedChevronDown
|
||||
size={isMobile ? 18 : 14}
|
||||
uniProps={foregroundMutedIconColorMapping}
|
||||
/>
|
||||
</DropdownMenuTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">
|
||||
<Text style={styles.tooltipText}>{optionsLabel}</Text>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<DropdownMenuContent align="end" width={240} testID="changes-options-menu-content">
|
||||
<DropdownMenuItem
|
||||
leading={DIFF_OPTIONS_WHITESPACE_ICON}
|
||||
selected={hideWhitespace}
|
||||
testID="changes-toggle-whitespace"
|
||||
onSelect={onToggleHideWhitespace}
|
||||
>
|
||||
{whitespaceLabel}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
leading={DIFF_OPTIONS_WRAP_ICON}
|
||||
selected={wrapLines}
|
||||
testID="changes-toggle-wrap-lines"
|
||||
onSelect={onToggleWrapLines}
|
||||
>
|
||||
{wrapLinesLabel}
|
||||
</DropdownMenuItem>
|
||||
{refreshSupported ? (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
leading={refreshIcon}
|
||||
disabled={isRefreshing}
|
||||
testID="changes-refresh"
|
||||
onSelect={onRefresh}
|
||||
>
|
||||
{isRefreshing ? t("workspace.git.diff.refreshing") : t("workspace.git.diff.refresh")}
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
) : null}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
|
||||
const ThemedRotateCw = withUnistyles(RotateCw);
|
||||
const ThemedLoadingSpinner = withUnistyles(LoadingSpinner);
|
||||
|
||||
function DiffRefreshButton({ isRefreshing, toggleStyle, onPress }: DiffRefreshButtonProps) {
|
||||
const { t } = useTranslation();
|
||||
const refreshLabel = t("workspace.git.diff.refresh");
|
||||
return (
|
||||
<Tooltip delayDuration={300}>
|
||||
<TooltipTrigger asChild>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={
|
||||
isRefreshing ? t("workspace.git.diff.refreshing") : t("workspace.git.diff.refreshState")
|
||||
}
|
||||
testID="changes-refresh"
|
||||
style={toggleStyle}
|
||||
onPress={onPress}
|
||||
disabled={isRefreshing}
|
||||
>
|
||||
<View style={styles.refreshIcon}>
|
||||
{isRefreshing ? (
|
||||
<ThemedLoadingSpinner
|
||||
size={ICON_SIZE.sm}
|
||||
uniProps={foregroundMutedIconColorMapping}
|
||||
/>
|
||||
) : (
|
||||
<ThemedRotateCw size={ICON_SIZE.sm} uniProps={foregroundMutedIconColorMapping} />
|
||||
)}
|
||||
</View>
|
||||
</Pressable>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">
|
||||
<Text style={styles.tooltipText}>{refreshLabel}</Text>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
type DiffFlatItemLayoutGetter = NonNullable<FlatListProps<DiffFlatItem>["getItemLayout"]>;
|
||||
|
||||
function getUnifiedDiffLineCount(file: ParsedDiffFile): number {
|
||||
@@ -1756,30 +1742,22 @@ export function GitDiffPane({ serverId, workspaceId, cwd, enabled }: GitDiffPane
|
||||
const { preferences: changesPreferences, updatePreferences: updateChangesPreferences } =
|
||||
useChangesPreferences();
|
||||
const wrapLines = changesPreferences.wrapLines;
|
||||
const viewMode = changesPreferences.viewMode;
|
||||
const effectiveLayout = canUseSplitLayout ? changesPreferences.layout : "unified";
|
||||
|
||||
const handleToggleWrapLines = useCallback(() => {
|
||||
void updateChangesPreferences({ wrapLines: !wrapLines });
|
||||
}, [updateChangesPreferences, wrapLines]);
|
||||
|
||||
const handleLayoutChange = useCallback(
|
||||
(nextLayout: "unified" | "split") => {
|
||||
void updateChangesPreferences({ layout: nextLayout });
|
||||
},
|
||||
[updateChangesPreferences],
|
||||
);
|
||||
|
||||
const handleToggleHideWhitespace = useCallback(() => {
|
||||
void updateChangesPreferences({ hideWhitespace: !changesPreferences.hideWhitespace });
|
||||
}, [changesPreferences.hideWhitespace, updateChangesPreferences]);
|
||||
|
||||
const handleLayoutUnified = useCallback(() => {
|
||||
handleLayoutChange("unified");
|
||||
}, [handleLayoutChange]);
|
||||
|
||||
const handleLayoutSplit = useCallback(() => {
|
||||
handleLayoutChange("split");
|
||||
}, [handleLayoutChange]);
|
||||
const handleToggleLayout = useCallback(() => {
|
||||
void updateChangesPreferences({
|
||||
layout: changesPreferences.layout === "unified" ? "split" : "unified",
|
||||
});
|
||||
}, [changesPreferences.layout, updateChangesPreferences]);
|
||||
|
||||
const codeFontSize = appSettings.codeFontSize;
|
||||
const diffBodyLineHeight = Math.round(codeFontSize * 1.5);
|
||||
@@ -1796,39 +1774,19 @@ export function GitDiffPane({ serverId, workspaceId, cwd, enabled }: GitDiffPane
|
||||
}, [appSettings.monoFontFamily, codeFontSize, diffBodyLineHeight]);
|
||||
const diffModeTriggerStyle = useMemo(() => buildDiffModeTriggerStyle(), []);
|
||||
|
||||
const unifiedToggleStyle = useMemo(
|
||||
() =>
|
||||
buildToggleButtonStyle(changesPreferences.layout === "unified", [
|
||||
styles.toggleButton,
|
||||
styles.toggleButtonGroupStart,
|
||||
]),
|
||||
[changesPreferences.layout],
|
||||
const layoutToggleStyle = useMemo(
|
||||
() => buildToggleButtonStyle(false, styles.expandAllButton),
|
||||
[],
|
||||
);
|
||||
|
||||
const splitToggleStyle = useMemo(
|
||||
() =>
|
||||
buildToggleButtonStyle(changesPreferences.layout === "split", [
|
||||
styles.toggleButton,
|
||||
styles.toggleButtonGroupEnd,
|
||||
]),
|
||||
[changesPreferences.layout],
|
||||
);
|
||||
|
||||
const hideWhitespaceToggleStyle = useMemo(
|
||||
() => buildToggleButtonStyle(changesPreferences.hideWhitespace, styles.expandAllButton),
|
||||
[changesPreferences.hideWhitespace],
|
||||
);
|
||||
|
||||
const wrapLinesToggleStyle = useMemo(
|
||||
() => buildToggleButtonStyle(wrapLines, styles.expandAllButton),
|
||||
[wrapLines],
|
||||
const viewModeToggleStyle = useMemo(
|
||||
() => buildToggleButtonStyle(viewMode === "tree", styles.expandAllButton),
|
||||
[viewMode],
|
||||
);
|
||||
|
||||
const expandAllToggleStyle = useMemo(() => buildExpandAllButtonStyle(), []);
|
||||
|
||||
const folderToggleStyle = useMemo(() => buildExpandAllButtonStyle(), []);
|
||||
|
||||
const refreshToggleStyle = useMemo(() => buildExpandAllButtonStyle(), []);
|
||||
const overflowToggleStyle = useMemo(() => buildExpandAllButtonStyle(), []);
|
||||
|
||||
const toast = useToast();
|
||||
const refreshSupported = useSessionStore(
|
||||
@@ -1995,6 +1953,16 @@ export function GitDiffPane({ serverId, workspaceId, cwd, enabled }: GitDiffPane
|
||||
[collapsedFoldersArray, allFolderPathSet],
|
||||
);
|
||||
const diffListRef = useRef<FlatList<DiffFlatItem>>(null);
|
||||
const handleToggleViewMode = useCallback(() => {
|
||||
const nextViewMode = viewMode === "flat" ? "tree" : "flat";
|
||||
if (nextViewMode === "tree") {
|
||||
diffListRef.current?.scrollToOffset({ offset: 0, animated: false });
|
||||
if (workspaceStateKey) {
|
||||
setDiffCollapsedFoldersForWorkspace(workspaceStateKey, []);
|
||||
}
|
||||
}
|
||||
void updateChangesPreferences({ viewMode: nextViewMode });
|
||||
}, [setDiffCollapsedFoldersForWorkspace, updateChangesPreferences, viewMode, workspaceStateKey]);
|
||||
const scrollbar = useWebScrollViewScrollbar(diffListRef, {
|
||||
enabled: showDesktopWebScrollbar,
|
||||
});
|
||||
@@ -2012,12 +1980,13 @@ export function GitDiffPane({ serverId, workspaceId, cwd, enabled }: GitDiffPane
|
||||
const { flatItems, stickyHeaderIndices } = useMemo(() => {
|
||||
const { items, stickyHeaderIndices: stickyIndices } = buildDiffFlatItems({
|
||||
files,
|
||||
viewMode,
|
||||
tree: compressedTree,
|
||||
collapsedFolders,
|
||||
expandedPaths,
|
||||
});
|
||||
return { flatItems: items, stickyHeaderIndices: stickyIndices };
|
||||
}, [compressedTree, collapsedFolders, expandedPaths, files]);
|
||||
}, [compressedTree, collapsedFolders, expandedPaths, files, viewMode]);
|
||||
|
||||
const getBodyHeightKey = useCallback(
|
||||
(file: ParsedDiffFile): string => {
|
||||
@@ -2248,25 +2217,6 @@ export function GitDiffPane({ serverId, workspaceId, cwd, enabled }: GitDiffPane
|
||||
}
|
||||
}, [allFileDiffsExpanded, files, setDiffExpandedPathsForWorkspace, workspaceStateKey]);
|
||||
|
||||
// Membership, not count: collapsedFolders is already filtered to present folders, so
|
||||
// "all collapsed" means every present folder path is in it.
|
||||
const allFoldersCollapsed =
|
||||
allFolderPaths.length > 0 && allFolderPaths.every((path) => collapsedFolders.has(path));
|
||||
|
||||
const handleToggleCollapseAllFolders = useCallback(() => {
|
||||
if (!workspaceStateKey) {
|
||||
return;
|
||||
}
|
||||
// Anchor to the top before collapsing everything so the viewport isn't stranded.
|
||||
if (!allFoldersCollapsed) {
|
||||
diffListRef.current?.scrollToOffset({ offset: 0, animated: false });
|
||||
}
|
||||
setDiffCollapsedFoldersForWorkspace(
|
||||
workspaceStateKey,
|
||||
allFoldersCollapsed ? [] : allFolderPaths,
|
||||
);
|
||||
}, [allFolderPaths, allFoldersCollapsed, setDiffCollapsedFoldersForWorkspace, workspaceStateKey]);
|
||||
|
||||
const renderFlatItem = useCallback(
|
||||
({ item }: { item: DiffFlatItem }) => {
|
||||
if (item.type === "folder") {
|
||||
@@ -2290,7 +2240,7 @@ export function GitDiffPane({ serverId, workspaceId, cwd, enabled }: GitDiffPane
|
||||
file={item.file}
|
||||
isExpanded={item.isExpanded}
|
||||
depth={item.depth}
|
||||
showDir={false}
|
||||
showDir={viewMode === "flat"}
|
||||
onToggle={handleToggleExpanded}
|
||||
onHeaderHeightChange={handleHeaderHeightChange}
|
||||
testID={`diff-file-${item.fileIndex}`}
|
||||
@@ -2320,6 +2270,7 @@ export function GitDiffPane({ serverId, workspaceId, cwd, enabled }: GitDiffPane
|
||||
handleToggleExpanded,
|
||||
handleToggleFolder,
|
||||
reviewActions,
|
||||
viewMode,
|
||||
wrapLines,
|
||||
],
|
||||
);
|
||||
@@ -2347,6 +2298,7 @@ export function GitDiffPane({ serverId, workspaceId, cwd, enabled }: GitDiffPane
|
||||
effectiveLayout,
|
||||
diffBodyTypographyKey,
|
||||
heightVersion,
|
||||
viewMode,
|
||||
wrapLines,
|
||||
reviewActions,
|
||||
}),
|
||||
@@ -2356,6 +2308,7 @@ export function GitDiffPane({ serverId, workspaceId, cwd, enabled }: GitDiffPane
|
||||
effectiveLayout,
|
||||
diffBodyTypographyKey,
|
||||
heightVersion,
|
||||
viewMode,
|
||||
wrapLines,
|
||||
reviewActions,
|
||||
],
|
||||
@@ -2485,42 +2438,40 @@ export function GitDiffPane({ serverId, workspaceId, cwd, enabled }: GitDiffPane
|
||||
</DropdownMenu>
|
||||
<View style={styles.diffStatusButtons}>
|
||||
{canUseSplitLayout ? (
|
||||
<DiffLayoutToggleGroup
|
||||
<DiffLayoutToggle
|
||||
layout={changesPreferences.layout}
|
||||
unifiedToggleStyle={unifiedToggleStyle}
|
||||
splitToggleStyle={splitToggleStyle}
|
||||
onUnified={handleLayoutUnified}
|
||||
onSplit={handleLayoutSplit}
|
||||
isMobile={isMobile}
|
||||
toggleStyle={layoutToggleStyle}
|
||||
onToggle={handleToggleLayout}
|
||||
/>
|
||||
) : null}
|
||||
{files.length > 0 ? (
|
||||
<DiffViewModeToggle
|
||||
viewMode={viewMode}
|
||||
isMobile={isMobile}
|
||||
toggleStyle={viewModeToggleStyle}
|
||||
onToggle={handleToggleViewMode}
|
||||
/>
|
||||
) : null}
|
||||
<DiffWhitespaceToggle
|
||||
hideWhitespace={changesPreferences.hideWhitespace}
|
||||
isMobile={isMobile}
|
||||
toggleStyle={hideWhitespaceToggleStyle}
|
||||
onToggle={handleToggleHideWhitespace}
|
||||
/>
|
||||
{files.length > 0 ? (
|
||||
<DiffFilesToolbar
|
||||
wrapLines={wrapLines}
|
||||
allFileDiffsExpanded={allFileDiffsExpanded}
|
||||
showFolderControl={allFolderPaths.length > 0}
|
||||
allFoldersCollapsed={allFoldersCollapsed}
|
||||
isMobile={isMobile}
|
||||
wrapLinesToggleStyle={wrapLinesToggleStyle}
|
||||
expandAllToggleStyle={expandAllToggleStyle}
|
||||
folderToggleStyle={folderToggleStyle}
|
||||
onToggleWrapLines={handleToggleWrapLines}
|
||||
onToggleExpandAll={handleToggleExpandAll}
|
||||
onToggleCollapseAllFolders={handleToggleCollapseAllFolders}
|
||||
/>
|
||||
) : null}
|
||||
{refreshSupported ? (
|
||||
<DiffRefreshButton
|
||||
isRefreshing={isRefreshing}
|
||||
toggleStyle={refreshToggleStyle}
|
||||
onPress={handleRefresh}
|
||||
/>
|
||||
) : null}
|
||||
<DiffOptionsMenu
|
||||
hideWhitespace={changesPreferences.hideWhitespace}
|
||||
isMobile={isMobile}
|
||||
isRefreshing={isRefreshing}
|
||||
overflowToggleStyle={overflowToggleStyle}
|
||||
refreshSupported={refreshSupported}
|
||||
wrapLines={wrapLines}
|
||||
onRefresh={handleRefresh}
|
||||
onToggleHideWhitespace={handleToggleHideWhitespace}
|
||||
onToggleWrapLines={handleToggleWrapLines}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
@@ -2602,46 +2553,9 @@ const styles = StyleSheet.create((theme) => ({
|
||||
gap: theme.spacing[1],
|
||||
flexWrap: "wrap",
|
||||
},
|
||||
toggleButtonGroup: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
},
|
||||
toggleButton: {
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
minWidth: {
|
||||
xs: 32,
|
||||
sm: 32,
|
||||
md: 24,
|
||||
},
|
||||
height: {
|
||||
xs: 32,
|
||||
sm: 32,
|
||||
md: 24,
|
||||
},
|
||||
paddingHorizontal: {
|
||||
xs: theme.spacing[2],
|
||||
sm: theme.spacing[2],
|
||||
md: theme.spacing[1],
|
||||
},
|
||||
},
|
||||
toggleButtonGroupStart: {
|
||||
borderTopLeftRadius: theme.borderRadius.base,
|
||||
borderBottomLeftRadius: theme.borderRadius.base,
|
||||
},
|
||||
toggleButtonGroupEnd: {
|
||||
borderTopRightRadius: theme.borderRadius.base,
|
||||
borderBottomRightRadius: theme.borderRadius.base,
|
||||
},
|
||||
toggleButtonSelected: {
|
||||
backgroundColor: theme.colors.surface2,
|
||||
},
|
||||
refreshIcon: {
|
||||
width: ICON_SIZE.md,
|
||||
height: ICON_SIZE.md,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
expandAllButton: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
|
||||
@@ -28,6 +28,7 @@ describe("loadChangesPreferencesFromStorage", () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
layout: "unified",
|
||||
viewMode: "flat",
|
||||
wrapLines: true,
|
||||
hideWhitespace: false,
|
||||
});
|
||||
@@ -37,6 +38,7 @@ describe("loadChangesPreferencesFromStorage", () => {
|
||||
it("loads persisted layout and whitespace preferences without rewriting storage", async () => {
|
||||
const persisted = JSON.stringify({
|
||||
layout: "split",
|
||||
viewMode: "tree",
|
||||
hideWhitespace: true,
|
||||
wrapLines: false,
|
||||
});
|
||||
@@ -48,6 +50,7 @@ describe("loadChangesPreferencesFromStorage", () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
layout: "split",
|
||||
viewMode: "tree",
|
||||
hideWhitespace: true,
|
||||
wrapLines: false,
|
||||
});
|
||||
@@ -64,11 +67,16 @@ describe("saveChangesPreferences", () => {
|
||||
|
||||
await saveChangesPreferences({
|
||||
queryClient,
|
||||
updates: { layout: "split", hideWhitespace: true },
|
||||
updates: { layout: "split", viewMode: "tree", hideWhitespace: true },
|
||||
storage,
|
||||
});
|
||||
|
||||
const expected = { ...DEFAULT_CHANGES_PREFERENCES, layout: "split", hideWhitespace: true };
|
||||
const expected = {
|
||||
...DEFAULT_CHANGES_PREFERENCES,
|
||||
layout: "split",
|
||||
viewMode: "tree",
|
||||
hideWhitespace: true,
|
||||
};
|
||||
expect(queryClient.getQueryData(CHANGES_PREFERENCES_QUERY_KEY)).toEqual(expected);
|
||||
expect(storage.entries.get(CHANGES_PREFERENCES_STORAGE_KEY)).toBe(JSON.stringify(expected));
|
||||
});
|
||||
|
||||
@@ -7,18 +7,21 @@ export const CHANGES_PREFERENCES_QUERY_KEY = ["changes-preferences"];
|
||||
|
||||
const changesPreferencesSchema = z.object({
|
||||
layout: z.enum(["unified", "split"]).optional(),
|
||||
viewMode: z.enum(["flat", "tree"]).optional(),
|
||||
wrapLines: z.boolean().optional(),
|
||||
hideWhitespace: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export interface ChangesPreferences {
|
||||
layout: "unified" | "split";
|
||||
viewMode: "flat" | "tree";
|
||||
wrapLines: boolean;
|
||||
hideWhitespace: boolean;
|
||||
}
|
||||
|
||||
export const DEFAULT_CHANGES_PREFERENCES: ChangesPreferences = {
|
||||
layout: "unified",
|
||||
viewMode: "flat",
|
||||
wrapLines: false,
|
||||
hideWhitespace: false,
|
||||
};
|
||||
|
||||
@@ -707,7 +707,13 @@ export const ar: TranslationResources = {
|
||||
tooLarge: "الفرق كبير جدًا بحيث لا يمكن عرضه",
|
||||
unified: "الفرق الموحدة",
|
||||
split: "فرق جنبًا إلى جنب",
|
||||
switchToUnified: "التبديل إلى الفرق الموحد",
|
||||
switchToSplit: "التبديل إلى الفرق جنبًا إلى جنب",
|
||||
showTreeView: "إظهار شجرة المجلدات",
|
||||
showFlatView: "إظهار قائمة الملفات المسطحة",
|
||||
options: "خيارات الفرق",
|
||||
hideWhitespace: "إخفاء المسافة البيضاء",
|
||||
showWhitespace: "إظهار المسافة البيضاء",
|
||||
scrollLongLines: "قم بتمرير الخطوط الطويلة",
|
||||
wrapLongLines: "لف الخطوط الطويلة",
|
||||
collapseAll: "طي كافة الملفات",
|
||||
|
||||
@@ -714,7 +714,13 @@ export const en = {
|
||||
tooLarge: "Diff too large to display",
|
||||
unified: "Unified diff",
|
||||
split: "Side-by-side diff",
|
||||
switchToUnified: "Switch to unified diff",
|
||||
switchToSplit: "Switch to side-by-side diff",
|
||||
showTreeView: "Show folder tree",
|
||||
showFlatView: "Show flat file list",
|
||||
options: "Diff options",
|
||||
hideWhitespace: "Hide whitespace",
|
||||
showWhitespace: "Show whitespace",
|
||||
scrollLongLines: "Scroll long lines",
|
||||
wrapLongLines: "Wrap long lines",
|
||||
collapseAll: "Collapse all files",
|
||||
|
||||
@@ -734,7 +734,13 @@ export const es: TranslationResources = {
|
||||
tooLarge: "La diferencia es demasiado grande para mostrarse",
|
||||
unified: "Diferencia unificada",
|
||||
split: "Diferencia de lado a lado",
|
||||
switchToUnified: "Cambiar a diferencia unificada",
|
||||
switchToSplit: "Cambiar a diferencia lado a lado",
|
||||
showTreeView: "Mostrar árbol de carpetas",
|
||||
showFlatView: "Mostrar lista plana de archivos",
|
||||
options: "Opciones de diferencia",
|
||||
hideWhitespace: "Ocultar espacios en blanco",
|
||||
showWhitespace: "Mostrar espacios en blanco",
|
||||
scrollLongLines: "Desplazarse por largas filas",
|
||||
wrapLongLines: "Envolver largas filas",
|
||||
collapseAll: "Contraer todos los archivos",
|
||||
|
||||
@@ -733,7 +733,13 @@ export const fr: TranslationResources = {
|
||||
tooLarge: "Diff trop grand pour être affiché",
|
||||
unified: "Différentiel unifié",
|
||||
split: "Différent côte à côte",
|
||||
switchToUnified: "Passer au diff unifié",
|
||||
switchToSplit: "Passer au diff côte à côte",
|
||||
showTreeView: "Afficher l'arborescence des dossiers",
|
||||
showFlatView: "Afficher la liste de fichiers à plat",
|
||||
options: "Options du diff",
|
||||
hideWhitespace: "Masquer les espaces",
|
||||
showWhitespace: "Afficher les espaces",
|
||||
scrollLongLines: "Faire défiler les longues lignes",
|
||||
wrapLongLines: "Enroulez les longues lignes",
|
||||
collapseAll: "Réduire tous les fichiers",
|
||||
|
||||
@@ -719,7 +719,13 @@ export const ja: TranslationResources = {
|
||||
tooLarge: "差分が大きすぎて表示できません",
|
||||
unified: "ユニファイド差分",
|
||||
split: "左右比較",
|
||||
switchToUnified: "ユニファイド差分に切り替え",
|
||||
switchToSplit: "左右比較差分に切り替え",
|
||||
showTreeView: "フォルダツリーを表示",
|
||||
showFlatView: "フラットなファイル一覧を表示",
|
||||
options: "差分オプション",
|
||||
hideWhitespace: "空白を非表示",
|
||||
showWhitespace: "空白を表示",
|
||||
scrollLongLines: "長い行をスクロール",
|
||||
wrapLongLines: "長い行を折り返す",
|
||||
collapseAll: "すべて折りたたむ",
|
||||
|
||||
@@ -725,7 +725,13 @@ export const ptBR: TranslationResources = {
|
||||
tooLarge: "Diff grande demais para exibir",
|
||||
unified: "Diff unificado",
|
||||
split: "Diff lado a lado",
|
||||
switchToUnified: "Alternar para diff unificado",
|
||||
switchToSplit: "Alternar para diff lado a lado",
|
||||
showTreeView: "Mostrar árvore de pastas",
|
||||
showFlatView: "Mostrar lista plana de arquivos",
|
||||
options: "Opções do diff",
|
||||
hideWhitespace: "Ocultar espaços em branco",
|
||||
showWhitespace: "Mostrar espaços em branco",
|
||||
scrollLongLines: "Rolar linhas longas",
|
||||
wrapLongLines: "Quebrar linhas longas",
|
||||
collapseAll: "Recolher todos os arquivos",
|
||||
|
||||
@@ -726,7 +726,13 @@ export const ru: TranslationResources = {
|
||||
tooLarge: "Разница слишком велика для отображения",
|
||||
unified: "Единый дифференциал",
|
||||
split: "Параллельная разница",
|
||||
switchToUnified: "Переключить на объединенный diff",
|
||||
switchToSplit: "Переключить на diff рядом",
|
||||
showTreeView: "Показать дерево папок",
|
||||
showFlatView: "Показать плоский список файлов",
|
||||
options: "Параметры diff",
|
||||
hideWhitespace: "Скрыть пробелы",
|
||||
showWhitespace: "Показать пробелы",
|
||||
scrollLongLines: "Прокручивать длинные строки",
|
||||
wrapLongLines: "Перенос длинных строк",
|
||||
collapseAll: "Свернуть все файлы",
|
||||
|
||||
@@ -701,7 +701,13 @@ export const zhCN: TranslationResources = {
|
||||
tooLarge: "Diff 过大,无法显示",
|
||||
unified: "Unified diff",
|
||||
split: "Side-by-side diff",
|
||||
switchToUnified: "切换到统一差异视图",
|
||||
switchToSplit: "切换到并排差异视图",
|
||||
showTreeView: "显示文件夹树",
|
||||
showFlatView: "显示平铺文件列表",
|
||||
options: "Diff 选项",
|
||||
hideWhitespace: "隐藏空白差异",
|
||||
showWhitespace: "显示空白差异",
|
||||
scrollLongLines: "滚动长行",
|
||||
wrapLongLines: "自动换行长行",
|
||||
collapseAll: "折叠所有文件",
|
||||
|
||||
Reference in New Issue
Block a user