Merge remote-tracking branch 'origin/main'
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
import React from 'react';
|
||||
|
||||
import type { FileDiffMetadata } from '@pierre/diffs';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Icon } from '@/components/icon/Icon';
|
||||
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { extractHunkPatch } from '@/lib/diff/patchFileDiff';
|
||||
|
||||
type HunkAction = 'stage' | 'unstage' | 'discard';
|
||||
|
||||
interface DiffHunkActionsProps {
|
||||
patch: string;
|
||||
fileDiff: FileDiffMetadata | undefined;
|
||||
directory: string;
|
||||
filePath: string;
|
||||
staged: boolean;
|
||||
onApplied: (action: HunkAction) => void;
|
||||
}
|
||||
|
||||
export const DiffHunkActions = React.memo<DiffHunkActionsProps>(({
|
||||
patch,
|
||||
fileDiff,
|
||||
directory,
|
||||
filePath,
|
||||
staged,
|
||||
onApplied,
|
||||
}) => {
|
||||
const { t } = useI18n();
|
||||
const { git } = useRuntimeAPIs();
|
||||
const [busyKey, setBusyKey] = React.useState<string | null>(null);
|
||||
const [error, setError] = React.useState<string | null>(null);
|
||||
|
||||
const hunks = fileDiff?.hunks;
|
||||
if (!hunks || hunks.length === 0 || !patch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const run = async (hunkIndex: number, action: HunkAction) => {
|
||||
const hunkPatch = extractHunkPatch(patch, hunkIndex);
|
||||
if (!hunkPatch) {
|
||||
setError(t('diffView.hunk.unavailable'));
|
||||
return;
|
||||
}
|
||||
|
||||
const key = `${hunkIndex}:${action}`;
|
||||
setBusyKey(key);
|
||||
setError(null);
|
||||
try {
|
||||
if (action === 'stage') {
|
||||
if (!git.stageGitHunk) throw new Error(t('diffView.hunk.unsupported'));
|
||||
await git.stageGitHunk(directory, filePath, hunkPatch);
|
||||
} else if (action === 'unstage') {
|
||||
if (!git.unstageGitHunk) throw new Error(t('diffView.hunk.unsupported'));
|
||||
await git.unstageGitHunk(directory, filePath, hunkPatch);
|
||||
} else {
|
||||
if (!git.revertGitHunk) throw new Error(t('diffView.hunk.unsupported'));
|
||||
await git.revertGitHunk(directory, filePath, hunkPatch);
|
||||
}
|
||||
onApplied(action);
|
||||
} catch (actionError) {
|
||||
setError(actionError instanceof Error ? actionError.message : String(actionError));
|
||||
} finally {
|
||||
setBusyKey((current) => (current === key ? null : current));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-1 border-b border-[var(--interactive-border)]/40 bg-[var(--surface-elevated)]/40 px-3 py-1.5">
|
||||
<div className="flex items-center gap-1.5 overflow-x-auto">
|
||||
<span className="typography-micro shrink-0 text-muted-foreground uppercase">
|
||||
{t('diffView.hunk.label')}
|
||||
</span>
|
||||
{hunks.map((hunk, index) => {
|
||||
const additions = hunk.additionLines;
|
||||
const deletions = hunk.deletionLines;
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="flex shrink-0 items-center gap-1 rounded-md border border-[var(--interactive-border)]/50 bg-background/60 px-1.5 py-0.5"
|
||||
>
|
||||
<span className="typography-micro font-semibold text-muted-foreground">
|
||||
{String(index + 1).padStart(2, '0')}
|
||||
</span>
|
||||
{additions > 0 ? (
|
||||
<span className="typography-micro" style={{ color: 'var(--status-success)' }}>
|
||||
+{additions}
|
||||
</span>
|
||||
) : null}
|
||||
{deletions > 0 ? (
|
||||
<span className="typography-micro" style={{ color: 'var(--status-error)' }}>
|
||||
−{deletions}
|
||||
</span>
|
||||
) : null}
|
||||
{staged ? (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
className="h-5 gap-1 px-1.5"
|
||||
disabled={busyKey !== null}
|
||||
onClick={() => void run(index, 'unstage')}
|
||||
title={t('diffView.hunk.unstageTitle', { index: index + 1 })}
|
||||
>
|
||||
{busyKey === `${index}:unstage` ? (
|
||||
<Icon name="loader-4" className="size-3 animate-spin" />
|
||||
) : (
|
||||
<Icon name="arrow-go-back" className="size-3" />
|
||||
)}
|
||||
{t('diffView.hunk.unstage')}
|
||||
</Button>
|
||||
) : (
|
||||
<>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
className="h-5 gap-1 px-1.5"
|
||||
disabled={busyKey !== null}
|
||||
onClick={() => void run(index, 'stage')}
|
||||
title={t('diffView.hunk.stageTitle', { index: index + 1 })}
|
||||
>
|
||||
{busyKey === `${index}:stage` ? (
|
||||
<Icon name="loader-4" className="size-3 animate-spin" />
|
||||
) : (
|
||||
<Icon name="add" className="size-3" />
|
||||
)}
|
||||
{t('diffView.hunk.stage')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
className="h-5 gap-1 px-1.5 text-muted-foreground hover:text-[var(--status-error)]"
|
||||
disabled={busyKey !== null}
|
||||
onClick={() => void run(index, 'discard')}
|
||||
title={t('diffView.hunk.discardTitle', { index: index + 1 })}
|
||||
>
|
||||
{busyKey === `${index}:discard` ? (
|
||||
<Icon name="loader-4" className="size-3 animate-spin" />
|
||||
) : (
|
||||
<Icon name="close" className="size-3" />
|
||||
)}
|
||||
{t('diffView.hunk.discard')}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{error ? (
|
||||
<div className="flex items-center gap-1.5 typography-meta" style={{ color: 'var(--status-error)' }}>
|
||||
<Icon name="error-warning" className="size-3.5 shrink-0" />
|
||||
<span className={cn('min-w-0')}>{error}</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -26,6 +26,7 @@ import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
|
||||
import { DiffViewToggle } from '@/components/chat/message/DiffViewToggle';
|
||||
import type { DiffViewMode } from '@/components/chat/message/types';
|
||||
import { PierreDiffViewer } from './PierreDiffViewer';
|
||||
import { DiffHunkActions } from './DiffHunkActions';
|
||||
import { useDeviceInfo } from '@/lib/device';
|
||||
import { FileTypeIcon } from '@/components/icons/FileTypeIcon';
|
||||
import { Icon } from "@/components/icon/Icon";
|
||||
@@ -389,51 +390,67 @@ const InlineImageDiffViewer = React.memo<InlineImageDiffViewerProps>(({
|
||||
});
|
||||
|
||||
interface InlineDiffViewerProps {
|
||||
filePath: string;
|
||||
diff: DiffData;
|
||||
renderSideBySide: boolean;
|
||||
wrapLines: boolean;
|
||||
filePath: string;
|
||||
diff: DiffData;
|
||||
renderSideBySide: boolean;
|
||||
wrapLines: boolean;
|
||||
directory: string;
|
||||
staged: boolean;
|
||||
onHunkApplied: (action: 'stage' | 'unstage' | 'discard') => void;
|
||||
}
|
||||
|
||||
const InlineDiffViewer = React.memo<InlineDiffViewerProps>(({
|
||||
filePath,
|
||||
diff,
|
||||
renderSideBySide,
|
||||
wrapLines,
|
||||
const InlineDiffViewer = React.memo<InlineDiffViewerProps>(({
|
||||
filePath,
|
||||
diff,
|
||||
renderSideBySide,
|
||||
wrapLines,
|
||||
directory,
|
||||
staged,
|
||||
onHunkApplied,
|
||||
}) => {
|
||||
const language = React.useMemo(
|
||||
() => getLanguageFromExtension(filePath) || 'text',
|
||||
[filePath]
|
||||
);
|
||||
const language = React.useMemo(
|
||||
() => getLanguageFromExtension(filePath) || 'text',
|
||||
[filePath]
|
||||
);
|
||||
|
||||
if (diff.isBinary) {
|
||||
return <BinaryDiffPlaceholder />;
|
||||
}
|
||||
if (diff.isBinary) {
|
||||
return <BinaryDiffPlaceholder />;
|
||||
}
|
||||
|
||||
if (isImageFile(filePath)) {
|
||||
return (
|
||||
if (isImageFile(filePath)) {
|
||||
return (
|
||||
<InlineImageDiffViewer
|
||||
filePath={filePath}
|
||||
diff={diff}
|
||||
renderSideBySide={renderSideBySide}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full" style={{ contain: 'layout' }}>
|
||||
<PierreDiffViewer
|
||||
original={diff.original}
|
||||
modified={diff.modified}
|
||||
fileDiff={diff.fileDiff}
|
||||
language={language}
|
||||
fileName={filePath}
|
||||
renderSideBySide={renderSideBySide}
|
||||
wrapLines={wrapLines}
|
||||
layout="inline"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full" style={{ contain: 'layout' }}>
|
||||
{diff.patch && diff.fileDiff ? (
|
||||
<DiffHunkActions
|
||||
patch={diff.patch}
|
||||
fileDiff={diff.fileDiff}
|
||||
directory={directory}
|
||||
filePath={filePath}
|
||||
staged={staged}
|
||||
onApplied={onHunkApplied}
|
||||
/>
|
||||
) : null}
|
||||
<PierreDiffViewer
|
||||
original={diff.original}
|
||||
modified={diff.modified}
|
||||
fileDiff={diff.fileDiff}
|
||||
language={language}
|
||||
fileName={filePath}
|
||||
renderSideBySide={renderSideBySide}
|
||||
wrapLines={wrapLines}
|
||||
layout="inline"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
interface MultiFileDiffEntryProps {
|
||||
@@ -481,6 +498,7 @@ const MultiFileDiffEntry = React.memo<MultiFileDiffEntryProps>(({
|
||||
}, [directory, file.path])
|
||||
);
|
||||
const setDiff = useGitStore((state) => state.setDiff);
|
||||
const fetchStatus = useGitStore((state) => state.fetchStatus);
|
||||
const setDiffFileLayout = useUIStore((state) => state.setDiffFileLayout);
|
||||
|
||||
const [diffRetryNonce, setDiffRetryNonce] = React.useState(0);
|
||||
@@ -601,6 +619,13 @@ const MultiFileDiffEntry = React.memo<MultiFileDiffEntryProps>(({
|
||||
handleSelect();
|
||||
}, [handleOpenChange, handleSelect, isExpanded]);
|
||||
|
||||
const handleHunkApplied = React.useCallback(() => {
|
||||
setDiffRetryNonce((nonce) => nonce + 1);
|
||||
if (directory) {
|
||||
void fetchStatus(directory, git);
|
||||
}
|
||||
}, [directory, fetchStatus, git]);
|
||||
|
||||
return (
|
||||
<div ref={setSectionRef} className="scroll-mt-9 border-b border-[var(--interactive-border)]/40 last:border-b-0">
|
||||
<div className="sticky top-0 z-10 border-b border-[var(--interactive-border)]/35 bg-[var(--surface-elevated)]/90 backdrop-blur-md supports-[backdrop-filter]:bg-[var(--surface-elevated)]/80">
|
||||
@@ -755,6 +780,9 @@ const MultiFileDiffEntry = React.memo<MultiFileDiffEntryProps>(({
|
||||
diff={diffData}
|
||||
renderSideBySide={renderSideBySide}
|
||||
wrapLines={wrapLines}
|
||||
directory={directory}
|
||||
staged={staged}
|
||||
onHunkApplied={handleHunkApplied}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -473,6 +473,9 @@ export interface GitAPI {
|
||||
stageGitFiles?(directory: string, filePaths: string[]): Promise<void>;
|
||||
unstageGitFile(directory: string, filePath: string): Promise<void>;
|
||||
unstageGitFiles?(directory: string, filePaths: string[]): Promise<void>;
|
||||
stageGitHunk?(directory: string, filePath: string, patch: string): Promise<void>;
|
||||
unstageGitHunk?(directory: string, filePath: string, patch: string): Promise<void>;
|
||||
revertGitHunk?(directory: string, filePath: string, patch: string): Promise<void>;
|
||||
isLinkedWorktree(directory: string): Promise<boolean>;
|
||||
getGitBranches(directory: string): Promise<GitBranch>;
|
||||
deleteGitBranch(directory: string, payload: GitDeleteBranchPayload): Promise<{ success: boolean }>;
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { extractHunkPatch, splitPatchIntoHunks } from "./patchFileDiff";
|
||||
|
||||
const SAMPLE_PATCH = `diff --git a/foo.txt b/foo.txt
|
||||
index 1111111..2222222 100644
|
||||
--- a/foo.txt
|
||||
+++ b/foo.txt
|
||||
@@ -1,4 +1,5 @@
|
||||
line1
|
||||
+added-top
|
||||
line2
|
||||
line3
|
||||
line4
|
||||
@@ -10,3 +11,4 @@
|
||||
line10
|
||||
-deleted-mid
|
||||
line11
|
||||
+added-bottom
|
||||
`;
|
||||
|
||||
describe("splitPatchIntoHunks", () => {
|
||||
test("splits a multi-hunk patch into standalone per-hunk patches", () => {
|
||||
const hunks = splitPatchIntoHunks(SAMPLE_PATCH);
|
||||
expect(hunks.length).toBe(2);
|
||||
|
||||
expect(hunks[0]).toContain("diff --git a/foo.txt b/foo.txt");
|
||||
expect(hunks[0]).toContain("--- a/foo.txt");
|
||||
expect(hunks[0]).toContain("+++ b/foo.txt");
|
||||
expect(hunks[0]).toContain("@@ -1,4 +1,5 @@");
|
||||
expect(hunks[0]).toContain("+added-top");
|
||||
expect(hunks[0]).not.toContain("@@ -10,3 +11,4 @@");
|
||||
expect(hunks[0]).not.toContain("added-bottom");
|
||||
|
||||
expect(hunks[1]).toContain("@@ -10,3 +11,4 @@");
|
||||
expect(hunks[1]).toContain("-deleted-mid");
|
||||
expect(hunks[1]).toContain("+added-bottom");
|
||||
expect(hunks[1]).not.toContain("added-top");
|
||||
});
|
||||
|
||||
test("each hunk keeps the file header so it applies on its own", () => {
|
||||
const hunks = splitPatchIntoHunks(SAMPLE_PATCH);
|
||||
for (const hunk of hunks) {
|
||||
expect(hunk.startsWith("diff --git a/foo.txt b/foo.txt\n")).toBe(true);
|
||||
expect(hunk.match(/^--- a\/foo.txt$/m)).not.toBeNull();
|
||||
expect(hunk.match(/^\+\+\+ b\/foo.txt$/m)).not.toBeNull();
|
||||
expect(hunk.match(/^@@\s/m)).not.toBeNull();
|
||||
}
|
||||
});
|
||||
|
||||
test("returns [] for an empty patch or a patch without hunks", () => {
|
||||
expect(splitPatchIntoHunks("")).toEqual([]);
|
||||
expect(splitPatchIntoHunks("diff --git a/foo b/foo\n--- a/foo\n+++ b/foo\n")).toEqual([]);
|
||||
});
|
||||
|
||||
test("handles a single-hunk patch", () => {
|
||||
const single = `diff --git a/a b/a
|
||||
--- a/a
|
||||
+++ b/a
|
||||
@@ -1,1 +1,2 @@
|
||||
a
|
||||
+b
|
||||
`;
|
||||
const hunks = splitPatchIntoHunks(single);
|
||||
expect(hunks.length).toBe(1);
|
||||
expect(hunks[0]).toContain("+b");
|
||||
});
|
||||
});
|
||||
|
||||
describe("extractHunkPatch", () => {
|
||||
test("returns the standalone patch for the requested index", () => {
|
||||
const second = extractHunkPatch(SAMPLE_PATCH, 1);
|
||||
expect(second).not.toBeNull();
|
||||
expect(second).toContain("@@ -10,3 +11,4 @@");
|
||||
expect(second).toContain("diff --git a/foo.txt b/foo.txt");
|
||||
});
|
||||
|
||||
test("returns null for out-of-range or invalid indices", () => {
|
||||
expect(extractHunkPatch(SAMPLE_PATCH, -1)).toBeNull();
|
||||
expect(extractHunkPatch(SAMPLE_PATCH, 2)).toBeNull();
|
||||
expect(extractHunkPatch(SAMPLE_PATCH, 1.5)).toBeNull();
|
||||
expect(extractHunkPatch("", 0)).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -142,3 +142,54 @@ const joinPatchLines = (lines: Array<{ text: string; newline: boolean }>): strin
|
||||
|
||||
const emptyFileDiff = (file: string): FileDiffMetadata =>
|
||||
parseDiffFromFile({ name: file, contents: '' }, { name: file, contents: '' });
|
||||
|
||||
/**
|
||||
* Split a unified diff patch for a single file into standalone per-hunk patches.
|
||||
*
|
||||
* Each returned patch preserves the original file header (everything before the
|
||||
* first `@@` hunk header) plus exactly one hunk, producing a patch that can be
|
||||
* fed to `git apply` on its own.
|
||||
*
|
||||
* Returns an empty array when no hunk headers are present.
|
||||
*/
|
||||
export const splitPatchIntoHunks = (patch: string): string[] => {
|
||||
if (!patch) return [];
|
||||
|
||||
const lines = patch.split(/\r?\n/);
|
||||
const hunkHeaderRegex = /^@@\s/;
|
||||
const headerLines: string[] = [];
|
||||
let firstHunk = 0;
|
||||
while (firstHunk < lines.length && !hunkHeaderRegex.test(lines[firstHunk] ?? '')) {
|
||||
headerLines.push(lines[firstHunk]);
|
||||
firstHunk += 1;
|
||||
}
|
||||
|
||||
if (firstHunk >= lines.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const hunks: string[][] = [];
|
||||
for (let index = firstHunk; index < lines.length; index += 1) {
|
||||
const line = lines[index];
|
||||
if (hunkHeaderRegex.test(line ?? '')) {
|
||||
hunks.push([...headerLines, line]);
|
||||
} else if (hunks.length > 0) {
|
||||
hunks[hunks.length - 1].push(line ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
return hunks.map((hunkLines) => hunkLines.join('\n'))
|
||||
.filter((hunk) => hunk.trim().length > 0)
|
||||
.map((hunk) => (hunk.endsWith('\n') ? hunk : `${hunk}\n`));
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract a standalone patch for a single hunk by zero-based index.
|
||||
*
|
||||
* Returns `null` when the index is out of range or the patch has no hunks.
|
||||
*/
|
||||
export const extractHunkPatch = (patch: string, hunkIndex: number): string | null => {
|
||||
if (!Number.isInteger(hunkIndex) || hunkIndex < 0) return null;
|
||||
const hunks = splitPatchIntoHunks(patch);
|
||||
return hunks[hunkIndex] ?? null;
|
||||
};
|
||||
|
||||
@@ -168,6 +168,24 @@ export async function unstageGitFiles(directory: string, filePaths: string[]): P
|
||||
return gitHttp.unstageGitFiles(directory, filePaths);
|
||||
}
|
||||
|
||||
export async function stageGitHunk(directory: string, filePath: string, patch: string): Promise<void> {
|
||||
const runtime = getRuntimeGit();
|
||||
if (runtime?.stageGitHunk) return runtime.stageGitHunk(directory, filePath, patch);
|
||||
return gitHttp.stageGitHunk(directory, filePath, patch);
|
||||
}
|
||||
|
||||
export async function unstageGitHunk(directory: string, filePath: string, patch: string): Promise<void> {
|
||||
const runtime = getRuntimeGit();
|
||||
if (runtime?.unstageGitHunk) return runtime.unstageGitHunk(directory, filePath, patch);
|
||||
return gitHttp.unstageGitHunk(directory, filePath, patch);
|
||||
}
|
||||
|
||||
export async function revertGitHunk(directory: string, filePath: string, patch: string): Promise<void> {
|
||||
const runtime = getRuntimeGit();
|
||||
if (runtime?.revertGitHunk) return runtime.revertGitHunk(directory, filePath, patch);
|
||||
return gitHttp.revertGitHunk(directory, filePath, patch);
|
||||
}
|
||||
|
||||
export async function isLinkedWorktree(directory: string): Promise<boolean> {
|
||||
const runtime = getRuntimeGit();
|
||||
if (runtime) return runtime.isLinkedWorktree(directory);
|
||||
|
||||
@@ -289,6 +289,43 @@ export async function unstageGitFiles(directory: string, filePaths: string[]): P
|
||||
}
|
||||
}
|
||||
|
||||
export async function stageGitHunk(directory: string, filePath: string, patch: string): Promise<void> {
|
||||
await applyGitHunk(directory, filePath, patch, 'stage');
|
||||
}
|
||||
|
||||
export async function unstageGitHunk(directory: string, filePath: string, patch: string): Promise<void> {
|
||||
await applyGitHunk(directory, filePath, patch, 'unstage');
|
||||
}
|
||||
|
||||
export async function revertGitHunk(directory: string, filePath: string, patch: string): Promise<void> {
|
||||
await applyGitHunk(directory, filePath, patch, 'discard');
|
||||
}
|
||||
|
||||
async function applyGitHunk(
|
||||
directory: string,
|
||||
filePath: string,
|
||||
patch: string,
|
||||
action: 'stage' | 'unstage' | 'discard',
|
||||
): Promise<void> {
|
||||
if (!filePath) {
|
||||
throw new Error('path is required to apply a git hunk');
|
||||
}
|
||||
if (typeof patch !== 'string' || !patch.trim()) {
|
||||
throw new Error('patch is required to apply a git hunk');
|
||||
}
|
||||
|
||||
const response = await runtimeFetch(buildUrl(`${API_BASE}/apply-hunk`, directory), {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ path: filePath, patch, action }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const message = await response.json().catch(() => ({ error: response.statusText }));
|
||||
throw new Error(message.error || 'Failed to apply git hunk');
|
||||
}
|
||||
}
|
||||
|
||||
export async function isLinkedWorktree(directory: string): Promise<boolean> {
|
||||
if (!directory) {
|
||||
return false;
|
||||
|
||||
@@ -1215,6 +1215,15 @@ export const dict = {
|
||||
'diffView.actions.enableLineWrap': 'Enable line wrap',
|
||||
'diffView.actions.openFileInEditorAtChange': 'Open this file in editor at change',
|
||||
'diffView.actions.openFileAtFirstChangedLine': 'Open this file at first changed line',
|
||||
'diffView.hunk.label': 'Hunks',
|
||||
'diffView.hunk.stage': 'Stage',
|
||||
'diffView.hunk.unstage': 'Unstage',
|
||||
'diffView.hunk.discard': 'Discard',
|
||||
'diffView.hunk.stageTitle': 'Stage hunk {index}',
|
||||
'diffView.hunk.unstageTitle': 'Unstage hunk {index}',
|
||||
'diffView.hunk.discardTitle': 'Discard hunk {index}',
|
||||
'diffView.hunk.unavailable': 'This hunk is no longer available. Refresh the diff and try again.',
|
||||
'diffView.hunk.unsupported': 'Staging individual hunks is not supported in this runtime.',
|
||||
'rightSidebar.contextNotesTodo.plan.defaultTitle': 'Plan',
|
||||
'rightSidebar.contextNotesTodo.empty.selectProject': 'Select a project to add notes and todos.',
|
||||
'rightSidebar.contextNotesTodo.notes.title': 'Quick notes - {project}',
|
||||
|
||||
@@ -1181,6 +1181,15 @@ export const dict: Record<I18nKey, string> = {
|
||||
"diffView.actions.enableLineWrap": "Activar ajuste de línea",
|
||||
"diffView.actions.openFileInEditorAtChange": "Abrir este archivo en el editor en el cambio",
|
||||
"diffView.actions.openFileAtFirstChangedLine": "Abrir este archivo en la primera línea modificada",
|
||||
"diffView.hunk.label": "Fragmentos",
|
||||
"diffView.hunk.stage": "Preparar",
|
||||
"diffView.hunk.unstage": "Quitar",
|
||||
"diffView.hunk.discard": "Descartar",
|
||||
"diffView.hunk.stageTitle": "Preparar fragmento {index}",
|
||||
"diffView.hunk.unstageTitle": "Quitar fragmento {index} de la preparación",
|
||||
"diffView.hunk.discardTitle": "Descartar fragmento {index}",
|
||||
"diffView.hunk.unavailable": "Este fragmento ya no está disponible. Actualice el diff e inténtelo de nuevo.",
|
||||
"diffView.hunk.unsupported": "Preparar fragmentos individuales no es compatible en este entorno.",
|
||||
"rightSidebar.contextNotesTodo.plan.defaultTitle": "Plan",
|
||||
"rightSidebar.contextNotesTodo.empty.selectProject": "Selecciona un proyecto para añadir notas y tareas pendientes.",
|
||||
"rightSidebar.contextNotesTodo.notes.title": "Notas rápidas — {project}",
|
||||
|
||||
@@ -1088,6 +1088,15 @@ export const dict = {
|
||||
'diffView.actions.enableLineWrap': 'Activer le retour à la ligne',
|
||||
'diffView.actions.openFileInEditorAtChange': 'Ouvrez ce fichier dans l\'éditeur lors du changement',
|
||||
'diffView.actions.openFileAtFirstChangedLine': 'Ouvrez ce fichier à la première ligne modifiée',
|
||||
'diffView.hunk.label': 'Sections',
|
||||
'diffView.hunk.stage': 'Préparer',
|
||||
'diffView.hunk.unstage': 'Retirer',
|
||||
'diffView.hunk.discard': 'Annuler',
|
||||
'diffView.hunk.stageTitle': 'Préparer la section {index}',
|
||||
'diffView.hunk.unstageTitle': 'Retirer la section {index} de la préparation',
|
||||
'diffView.hunk.discardTitle': 'Annuler la section {index}',
|
||||
'diffView.hunk.unavailable': "Cette section n'est plus disponible. Actualisez le diff et réessayez.",
|
||||
'diffView.hunk.unsupported': "La préparation de sections individuelles n'est pas prise en charge dans cet environnement.",
|
||||
'rightSidebar.contextNotesTodo.plan.defaultTitle': 'Plan',
|
||||
'rightSidebar.contextNotesTodo.empty.selectProject': 'Sélectionnez un projet pour ajouter des notes et des tâches.',
|
||||
'rightSidebar.contextNotesTodo.notes.title': 'Notes rapides - {project}',
|
||||
|
||||
@@ -1218,6 +1218,15 @@ export const dict: Record<I18nKey, string> = {
|
||||
'diffView.actions.enableLineWrap': '줄 바꿈 켜기',
|
||||
'diffView.actions.openFileInEditorAtChange': '변경 위치에서 이 파일을 에디터로 열기',
|
||||
'diffView.actions.openFileAtFirstChangedLine': '첫 변경 줄에서 이 파일 열기',
|
||||
'diffView.hunk.label': '허크',
|
||||
'diffView.hunk.stage': '스테이지',
|
||||
'diffView.hunk.unstage': '스테이지 해제',
|
||||
'diffView.hunk.discard': '취소',
|
||||
'diffView.hunk.stageTitle': '허크 {index} 스테이지',
|
||||
'diffView.hunk.unstageTitle': '허크 {index} 스테이지 해제',
|
||||
'diffView.hunk.discardTitle': '허크 {index} 취소',
|
||||
'diffView.hunk.unavailable': '이 허크는 더 이상 사용할 수 없습니다. diff를 새로고침 후 다시 시도하세요.',
|
||||
'diffView.hunk.unsupported': '개별 허크 스테이징은 이 환경에서 지원되지 않습니다.',
|
||||
'rightSidebar.contextNotesTodo.plan.defaultTitle': '플랜',
|
||||
'rightSidebar.contextNotesTodo.empty.selectProject': '메모와 Todo를 추가할 프로젝트를 선택하세요.',
|
||||
'rightSidebar.contextNotesTodo.notes.title': '빠른 메모 - {project}',
|
||||
|
||||
@@ -1415,6 +1415,15 @@ export const dict: Record<I18nKey, string> = {
|
||||
'diffView.actions.loadFullFiles': 'Wczytaj pełne pliki',
|
||||
'diffView.actions.disableFullFiles': 'Nie wczytuj pełnych plików',
|
||||
'diffView.actions.openFileAtFirstChangedLine': 'Otwórz plik na pierwszej zmienionej linii',
|
||||
'diffView.hunk.label': 'Fragmenty',
|
||||
'diffView.hunk.stage': 'Przygotuj',
|
||||
'diffView.hunk.unstage': 'Cofnij',
|
||||
'diffView.hunk.discard': 'Odrzuć',
|
||||
'diffView.hunk.stageTitle': 'Przygotuj fragment {index}',
|
||||
'diffView.hunk.unstageTitle': 'Cofnij przygotowanie fragmentu {index}',
|
||||
'diffView.hunk.discardTitle': 'Odrzuć fragment {index}',
|
||||
'diffView.hunk.unavailable': 'Ten fragment nie jest już dostępny. Odśwież diff i spróbuj ponownie.',
|
||||
'diffView.hunk.unsupported': 'Przygotowywanie pojedynczych fragmentów nie jest obsługiwane w tym środowisku.',
|
||||
'diffView.actions.openFileInEditorAtChange': 'Otwórz plik w edytorze na zmianie',
|
||||
'diffView.actions.renderAnyway': 'Renderuj mimo to',
|
||||
'diffView.actions.retry': 'Ponów',
|
||||
|
||||
@@ -1181,6 +1181,15 @@ export const dict: Record<I18nKey, string> = {
|
||||
"diffView.actions.enableLineWrap": "Ativar ajuste de linha",
|
||||
"diffView.actions.openFileInEditorAtChange": "Abrir este arquivo no editor nesta alteração",
|
||||
"diffView.actions.openFileAtFirstChangedLine": "Abrir este arquivo na primeira linha alterada",
|
||||
"diffView.hunk.label": "Trechos",
|
||||
"diffView.hunk.stage": "Preparar",
|
||||
"diffView.hunk.unstage": "Remover",
|
||||
"diffView.hunk.discard": "Descartar",
|
||||
"diffView.hunk.stageTitle": "Preparar trecho {index}",
|
||||
"diffView.hunk.unstageTitle": "Remover trecho {index} da preparação",
|
||||
"diffView.hunk.discardTitle": "Descartar trecho {index}",
|
||||
"diffView.hunk.unavailable": "Este trecho não está mais disponível. Atualize o diff e tente novamente.",
|
||||
"diffView.hunk.unsupported": "A preparação de trechos individuais não é suportada neste ambiente.",
|
||||
"rightSidebar.contextNotesTodo.plan.defaultTitle": "Plano",
|
||||
"rightSidebar.contextNotesTodo.empty.selectProject": "Selecione um projeto para adicionar notas e tarefas pendentes.",
|
||||
"rightSidebar.contextNotesTodo.notes.title": "Notas rápidas — {project}",
|
||||
|
||||
@@ -1181,6 +1181,15 @@ export const dict: Record<I18nKey, string> = {
|
||||
"diffView.actions.enableLineWrap": "Увімкнути перенос рядків",
|
||||
"diffView.actions.openFileInEditorAtChange": "Відкрити цей файл у редакторі на зміні",
|
||||
"diffView.actions.openFileAtFirstChangedLine": "Відкрити цей файл у першому зміненому рядку",
|
||||
"diffView.hunk.label": "Шматки",
|
||||
"diffView.hunk.stage": "Додати",
|
||||
"diffView.hunk.unstage": "Прибрати",
|
||||
"diffView.hunk.discard": "Відкинути",
|
||||
"diffView.hunk.stageTitle": "Додати шматок {index} до індексу",
|
||||
"diffView.hunk.unstageTitle": "Прибрати шматок {index} з індексу",
|
||||
"diffView.hunk.discardTitle": "Відкинути шматок {index}",
|
||||
"diffView.hunk.unavailable": "Цей шматок більше недоступний. Оновіть diff і спробуйте знову.",
|
||||
"diffView.hunk.unsupported": "Додавання окремих шматків до індексу не підтримується в цьому середовищі.",
|
||||
"rightSidebar.contextNotesTodo.plan.defaultTitle": "План",
|
||||
"rightSidebar.contextNotesTodo.empty.selectProject": "Виберіть проєкт, щоб додати нотатки та завдання.",
|
||||
"rightSidebar.contextNotesTodo.notes.title": "Швидкі нотатки - {project}",
|
||||
|
||||
@@ -1181,6 +1181,15 @@ export const dict: Record<I18nKey, string> = {
|
||||
'diffView.actions.enableLineWrap': '开启自动换行',
|
||||
'diffView.actions.openFileInEditorAtChange': '在编辑器中打开此文件并定位变更',
|
||||
'diffView.actions.openFileAtFirstChangedLine': '在首个变更行打开此文件',
|
||||
'diffView.hunk.label': '代码块',
|
||||
'diffView.hunk.stage': '暂存',
|
||||
'diffView.hunk.unstage': '取消暂存',
|
||||
'diffView.hunk.discard': '放弃',
|
||||
'diffView.hunk.stageTitle': '暂存代码块 {index}',
|
||||
'diffView.hunk.unstageTitle': '取消暂存代码块 {index}',
|
||||
'diffView.hunk.discardTitle': '放弃代码块 {index}',
|
||||
'diffView.hunk.unavailable': '此代码块已不可用。请刷新差异后重试。',
|
||||
'diffView.hunk.unsupported': '此运行环境不支持暂存单个代码块。',
|
||||
'rightSidebar.contextNotesTodo.plan.defaultTitle': '计划',
|
||||
'rightSidebar.contextNotesTodo.empty.selectProject': '请选择一个项目以添加笔记和待办事项。',
|
||||
'rightSidebar.contextNotesTodo.notes.title': '快速笔记 - {project}',
|
||||
|
||||
@@ -1191,6 +1191,15 @@ export const dict: Record<I18nKey, string> = {
|
||||
'diffView.actions.enableLineWrap': '開啟自動換行',
|
||||
'diffView.actions.openFileInEditorAtChange': '在編輯器中開啟此檔案並定位變更',
|
||||
'diffView.actions.openFileAtFirstChangedLine': '在首個變更行開啟此檔案',
|
||||
'diffView.hunk.label': '程式碼區塊',
|
||||
'diffView.hunk.stage': '暫存',
|
||||
'diffView.hunk.unstage': '取消暫存',
|
||||
'diffView.hunk.discard': '捨棄',
|
||||
'diffView.hunk.stageTitle': '暫存程式碼區塊 {index}',
|
||||
'diffView.hunk.unstageTitle': '取消暫存程式碼區塊 {index}',
|
||||
'diffView.hunk.discardTitle': '捨棄程式碼區塊 {index}',
|
||||
'diffView.hunk.unavailable': '此程式碼區塊已不可用。請重新整理差異後重試。',
|
||||
'diffView.hunk.unsupported': '此執行環境不支援暫存個別程式碼區塊。',
|
||||
'rightSidebar.contextNotesTodo.plan.defaultTitle': '計畫',
|
||||
'rightSidebar.contextNotesTodo.empty.selectProject': '請選擇一個專案以新增筆記和待辦事項。',
|
||||
'rightSidebar.contextNotesTodo.notes.title': '快速筆記 - {project}',
|
||||
|
||||
Reference in New Issue
Block a user