dsh-diff-approval 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/README.zh.md +4 -3
- package/docs/images/pending-panel.png +0 -0
- package/docs/images/pending-panel.zh.png +0 -0
- package/lib/client.js +747 -161
- package/lib/index.js +39 -8
- package/lib/types/client/PendingPanel.d.ts +1 -0
- package/lib/types/client/SettingsTab.d.ts +5 -4
- package/lib/types/client/locales.d.ts +10 -0
- package/lib/types/client/settings.d.ts +19 -0
- package/lib/types/client/whole-file-diff.d.ts +4 -1
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -694,6 +694,35 @@ const inject = [
|
|
|
694
694
|
];
|
|
695
695
|
/** The connection RPC channel this plugin serves. */
|
|
696
696
|
const DIFF_APPROVAL_CHANNEL = "/diff-approval";
|
|
697
|
+
/** The dominant line ending of `text`: CRLF when `\r\n` meets or beats lone
|
|
698
|
+
* `\n`, else LF; a text with no line breaks falls back to LF.
|
|
699
|
+
* @param text - the content to inspect.
|
|
700
|
+
* @returns the dominant line-ending sequence.
|
|
701
|
+
*/
|
|
702
|
+
function detectEol(text) {
|
|
703
|
+
let crlf = 0;
|
|
704
|
+
let lf = 0;
|
|
705
|
+
for (let i = 0; i < text.length; i++) {
|
|
706
|
+
const c = text.charCodeAt(i);
|
|
707
|
+
if (c === 13) {
|
|
708
|
+
if (i + 1 < text.length && text.charCodeAt(i + 1) === 10) {
|
|
709
|
+
crlf++;
|
|
710
|
+
i++;
|
|
711
|
+
}
|
|
712
|
+
} else if (c === 10) lf++;
|
|
713
|
+
}
|
|
714
|
+
if (crlf === 0 && lf === 0) return "\n";
|
|
715
|
+
return crlf >= lf ? "\r\n" : "\n";
|
|
716
|
+
}
|
|
717
|
+
/** Normalize any line ending (`\r\n`, `\r`) to `\n`. */
|
|
718
|
+
function normalizeEol(text) {
|
|
719
|
+
return text.replace(/\r\n?/g, "\n");
|
|
720
|
+
}
|
|
721
|
+
/** Re-encode `text`'s line endings to `eol` (its content is unchanged). */
|
|
722
|
+
function reencodeEol(text, eol) {
|
|
723
|
+
const normalized = normalizeEol(text);
|
|
724
|
+
return eol === "\n" ? normalized : normalized.replace(/\n/g, "\r\n");
|
|
725
|
+
}
|
|
697
726
|
/**
|
|
698
727
|
* Narrow a successful `edit` result value to an operation outcome. The edit
|
|
699
728
|
* tool's output schema declares exactly `{ path, before, after }`; anything
|
|
@@ -1275,7 +1304,8 @@ function apply(ctx, config) {
|
|
|
1275
1304
|
if (entry.kind === "create") await rm(ctx.fs.processPath(resolved), { force: true });
|
|
1276
1305
|
else {
|
|
1277
1306
|
const preWrite = await ctx.fs.readText(resolved, void 0) ?? entry.newText;
|
|
1278
|
-
|
|
1307
|
+
const content = reencodeEol(entry.oldText, detectEol(entry.newText));
|
|
1308
|
+
await writeRevert(resolved, content, target.sessionId, signal);
|
|
1279
1309
|
undo = {
|
|
1280
1310
|
before: {
|
|
1281
1311
|
id: entry.id,
|
|
@@ -1287,7 +1317,7 @@ function apply(ctx, config) {
|
|
|
1287
1317
|
id: entry.id,
|
|
1288
1318
|
path: entry.path,
|
|
1289
1319
|
entry: void 0,
|
|
1290
|
-
fileText:
|
|
1320
|
+
fileText: content
|
|
1291
1321
|
}
|
|
1292
1322
|
};
|
|
1293
1323
|
}
|
|
@@ -1353,25 +1383,26 @@ function apply(ctx, config) {
|
|
|
1353
1383
|
};
|
|
1354
1384
|
const restored = contentRangeOf(entry.oldText, blockTarget.block.oldStart, blockTarget.block.oldEnd);
|
|
1355
1385
|
const updatedNew = replaceContentLines(entry.newText, blockTarget.block.newStart, blockTarget.block.newEnd, restored);
|
|
1386
|
+
const content = reencodeEol(updatedNew, detectEol(entry.newText));
|
|
1356
1387
|
let afterEntry;
|
|
1357
|
-
if (updatedNew === entry.oldText) {
|
|
1388
|
+
if (normalizeEol(updatedNew) === normalizeEol(entry.oldText)) {
|
|
1358
1389
|
store.remove(blockTarget.sessionId, blockTarget.id);
|
|
1359
1390
|
afterEntry = void 0;
|
|
1360
1391
|
} else {
|
|
1361
|
-
store.update(blockTarget.sessionId, blockTarget.id, { newText:
|
|
1392
|
+
store.update(blockTarget.sessionId, blockTarget.id, { newText: content });
|
|
1362
1393
|
afterEntry = {
|
|
1363
1394
|
...entry,
|
|
1364
|
-
newText:
|
|
1395
|
+
newText: content,
|
|
1365
1396
|
updatedAt: Date.now()
|
|
1366
1397
|
};
|
|
1367
1398
|
}
|
|
1368
1399
|
let undo;
|
|
1369
1400
|
try {
|
|
1370
1401
|
const resolved = await ctx.fs.resolve(entry.path, { signal });
|
|
1371
|
-
if (entry.kind === "create" &&
|
|
1402
|
+
if (entry.kind === "create" && content === "") await rm(ctx.fs.processPath(resolved), { force: true });
|
|
1372
1403
|
else {
|
|
1373
1404
|
const preWrite = await ctx.fs.readText(resolved, void 0) ?? entry.newText;
|
|
1374
|
-
await writeRevert(resolved,
|
|
1405
|
+
await writeRevert(resolved, content, blockTarget.sessionId, signal);
|
|
1375
1406
|
undo = {
|
|
1376
1407
|
before: {
|
|
1377
1408
|
id: entry.id,
|
|
@@ -1383,7 +1414,7 @@ function apply(ctx, config) {
|
|
|
1383
1414
|
id: entry.id,
|
|
1384
1415
|
path: entry.path,
|
|
1385
1416
|
entry: afterEntry,
|
|
1386
|
-
fileText:
|
|
1417
|
+
fileText: content
|
|
1387
1418
|
}
|
|
1388
1419
|
};
|
|
1389
1420
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** Sidebar-foot pending-edit review action and the split review panel it opens. */
|
|
2
2
|
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
3
3
|
import type { PendingPanelFace } from './slots.ts';
|
|
4
|
+
export declare function wrapInto(text: string, widthPx: number, measure: (t: string) => number, tabPx: number): string[];
|
|
4
5
|
/** Full panel props composed by the sidebar footer-action slot. */
|
|
5
6
|
export type PendingPanelProps = PropsRuntime<'sidebar.footer.action'> & InjectFace<PendingPanelFace> & PropsLocale<'diff-approval'>;
|
|
6
7
|
/**
|
|
@@ -3,9 +3,10 @@ import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots
|
|
|
3
3
|
/** Full component props assembled by the Settings slot renderer. */
|
|
4
4
|
export type DiffApprovalSettingsTabProps = PropsRuntime<'settings.section'> & PropsLocale<'diff-approval'>;
|
|
5
5
|
/**
|
|
6
|
-
* The plugin's preferences: auto-paste a copied reference into the input,
|
|
7
|
-
* whether importing workspace VCS changes includes untracked files
|
|
8
|
-
* mirrors the harness's Agent-preset row (title +
|
|
9
|
-
* pill picker on the right)
|
|
6
|
+
* The plugin's preferences: auto-paste a copied reference into the input,
|
|
7
|
+
* whether importing workspace VCS changes includes untracked files, and the
|
|
8
|
+
* diff's tab width. Each row mirrors the harness's Agent-preset row (title +
|
|
9
|
+
* description on the left, a pill picker on the right); the tab-width row
|
|
10
|
+
* offers 2 / 4 / 8 spaces.
|
|
10
11
|
*/
|
|
11
12
|
export declare function DiffApprovalSettingsTab({ t }: DiffApprovalSettingsTabProps): import("react").JSX.Element;
|
|
@@ -22,6 +22,8 @@ export declare const zh: {
|
|
|
22
22
|
'panel.pasteOnCopyDesc': string;
|
|
23
23
|
'panel.importUntracked': string;
|
|
24
24
|
'panel.importUntrackedDesc': string;
|
|
25
|
+
'panel.tabWidth': string;
|
|
26
|
+
'panel.tabWidthDesc': string;
|
|
25
27
|
'settings.tabLabel': string;
|
|
26
28
|
'row.create': string;
|
|
27
29
|
'row.failed': string;
|
|
@@ -36,11 +38,14 @@ export declare const zh: {
|
|
|
36
38
|
'action.busy': string;
|
|
37
39
|
'action.prevDiff': string;
|
|
38
40
|
'action.nextDiff': string;
|
|
41
|
+
'action.showFileList': string;
|
|
42
|
+
'action.hideFileList': string;
|
|
39
43
|
'action.copyHint': string;
|
|
40
44
|
'action.copied': string;
|
|
41
45
|
'action.langAuto': string;
|
|
42
46
|
'action.langAutoDetected': string;
|
|
43
47
|
'action.langSelect': string;
|
|
48
|
+
'action.wrap': string;
|
|
44
49
|
'action.toggleOn': string;
|
|
45
50
|
'action.toggleOff': string;
|
|
46
51
|
'action.importVcs': string;
|
|
@@ -88,6 +93,8 @@ export declare const en: {
|
|
|
88
93
|
'panel.pasteOnCopyDesc': string;
|
|
89
94
|
'panel.importUntracked': string;
|
|
90
95
|
'panel.importUntrackedDesc': string;
|
|
96
|
+
'panel.tabWidth': string;
|
|
97
|
+
'panel.tabWidthDesc': string;
|
|
91
98
|
'settings.tabLabel': string;
|
|
92
99
|
'row.create': string;
|
|
93
100
|
'row.failed': string;
|
|
@@ -102,11 +109,14 @@ export declare const en: {
|
|
|
102
109
|
'action.busy': string;
|
|
103
110
|
'action.prevDiff': string;
|
|
104
111
|
'action.nextDiff': string;
|
|
112
|
+
'action.showFileList': string;
|
|
113
|
+
'action.hideFileList': string;
|
|
105
114
|
'action.copyHint': string;
|
|
106
115
|
'action.copied': string;
|
|
107
116
|
'action.langAuto': string;
|
|
108
117
|
'action.langAutoDetected': string;
|
|
109
118
|
'action.langSelect': string;
|
|
119
|
+
'action.wrap': string;
|
|
110
120
|
'action.toggleOn': string;
|
|
111
121
|
'action.toggleOff': string;
|
|
112
122
|
'action.importVcs': string;
|
|
@@ -17,3 +17,22 @@ export declare function setPasteOnCopyEnabled(value: boolean): void;
|
|
|
17
17
|
export declare function includeUntrackedEnabled(): boolean;
|
|
18
18
|
/** Persist the import-untracked preference. */
|
|
19
19
|
export declare function setIncludeUntrackedEnabled(value: boolean): void;
|
|
20
|
+
/**
|
|
21
|
+
* Whether lines wrap (auto-wrap) in the diff for one highlight language.
|
|
22
|
+
* Defaults to off; only an explicit `'1'` enables it. Stored per language, so
|
|
23
|
+
* a language's preference never leaks into another's.
|
|
24
|
+
* @param lang - the highlight language (or `''` for the auto/default bucket).
|
|
25
|
+
* @returns whether lines wrap.
|
|
26
|
+
*/
|
|
27
|
+
export declare function wrapEnabled(lang: string): boolean;
|
|
28
|
+
/** Persist the per-language auto-wrap preference. */
|
|
29
|
+
export declare function setWrapEnabled(lang: string, value: boolean): void;
|
|
30
|
+
/**
|
|
31
|
+
* The diff's tab width in spaces. Defaults to 4; the settings UI offers 2/4/8,
|
|
32
|
+
* but any positive integer is accepted. This drives both the rendered
|
|
33
|
+
* `tab-size` and the wrapped-line tab measurement, so they always agree.
|
|
34
|
+
* @returns the number of spaces one tab advances.
|
|
35
|
+
*/
|
|
36
|
+
export declare function tabWidth(): number;
|
|
37
|
+
/** Persist the diff's tab width (in spaces). */
|
|
38
|
+
export declare function setTabWidth(value: number): void;
|
|
@@ -29,7 +29,10 @@ export interface WholeFileDiff {
|
|
|
29
29
|
* Compute the whole-file view between two contents. The context budget is the
|
|
30
30
|
* side lengths, so every hunk covers the file and unchanged lines survive as
|
|
31
31
|
* context rows; the `` patch marker is annotation
|
|
32
|
-
* and never becomes a row.
|
|
32
|
+
* and never becomes a row. Both sides are line-ending normalized (`\r\n?` →
|
|
33
|
+
* `\n`) first, so a repo baseline stored with one EOL and a worktree with
|
|
34
|
+
* another never show as a whole-file delete+add — the diff is about approved
|
|
35
|
+
* content, not line-ending noise.
|
|
33
36
|
* @param oldText - the file content before the pending change.
|
|
34
37
|
* @param newText - the file content after the pending change.
|
|
35
38
|
* @returns the complete row list with totals.
|
package/package.json
CHANGED