dsh-diff-approval 0.14.1 → 0.15.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/lib/index.js CHANGED
@@ -424,6 +424,11 @@ function defaultOpenPath(path, action) {
424
424
  /** Cap on one VCS command's runtime; scans (git status, svn status) can be slow
425
425
  * on large trees but must not hang the import. */
426
426
  const VCS_COMMAND_TIMEOUT_MS = 6e4;
427
+ /** Blobs up to this size are read straight off the executor's buffered stdout
428
+ * (the small-blob path, no temp file); larger blobs go through the checkout-index
429
+ * temp-file path because the executor cap truncates its stdout. Kept below the
430
+ * cap so the small path never truncates. */
431
+ const GIT_BLOB_STDOUT_SAFE = 6e4;
427
432
  /** Quote one argument for a POSIX-ish shell, so paths with spaces or special
428
433
  * characters survive interpolation into VCS command lines. */
429
434
  function shq(value) {
@@ -528,7 +533,24 @@ async function gitChanges(input) {
528
533
  if (worktree !== "M" && worktree !== "D") continue;
529
534
  let oldText = "";
530
535
  try {
531
- oldText = await runShell(shell, `git show :0:${shq(rel)}`, root, signal);
536
+ let size = 0;
537
+ try {
538
+ size = Number.parseInt((await runShell(shell, `git cat-file -s :0:${shq(rel)}`, root, signal)).trim(), 10);
539
+ } catch {
540
+ size = 0;
541
+ }
542
+ if (Number.isFinite(size) && size > 0 && size <= GIT_BLOB_STDOUT_SAFE) oldText = await runShell(shell, `git show :0:${shq(rel)}`, root, signal);
543
+ else {
544
+ const tempRel = (await runShell(shell, `git checkout-index --temp --force -- ${shq(rel)}`, root, signal)).trim().split(/\s+/)[0] ?? "";
545
+ if (tempRel !== "") {
546
+ const tempFile = resolve(root, tempRel);
547
+ try {
548
+ oldText = await readText(tempFile) ?? "";
549
+ } finally {
550
+ await rm(tempFile, { force: true }).catch(() => {});
551
+ }
552
+ }
553
+ }
532
554
  } catch {
533
555
  oldText = "";
534
556
  }
@@ -5,6 +5,7 @@ import type { DiffApprovalBlockRange, PendingFileDiff } from '../types.ts';
5
5
  import type { PendingPanelFace } from './slots.ts';
6
6
  import type { DiffApprovalKey } from './locales.ts';
7
7
  import { computeWholeFileDiff } from './whole-file-diff.ts';
8
+ import type { IntraRun } from './whole-file-diff.ts';
8
9
  import { highlightLines } from './highlight.ts';
9
10
  /** The dsh shell's sidebar auto-collapse breakpoint (ui-layout columns.ts):
10
11
  * below it the sidebar auto-collapses, and the file list floats on the same
@@ -30,6 +31,8 @@ interface RowModel {
30
31
  diff: ReturnType<typeof computeWholeFileDiff>;
31
32
  /** Maximal runs of changed rows (inclusive row indices); one per modification. */
32
33
  blocks: ChangeBlock[];
34
+ /** Intra-line runs keyed by row index, present only for annotated del/add rows. */
35
+ intra: Map<number, IntraRun[]>;
33
36
  }
34
37
  /** One file's deferred syntax-highlight runs, one entry per side. */
35
38
  interface HighlightRuns {
@@ -65,9 +68,18 @@ export declare const SplitDiff: import("react").ForwardRefExoticComponent<{
65
68
  busy: boolean;
66
69
  t: Translator;
67
70
  selection: RowRange | undefined;
71
+ leadRows: number;
68
72
  onBlockKeep: (sessionId: SessionId, id: string, block: DiffApprovalBlockRange) => Promise<void>;
69
73
  onBlockRevert: (sessionId: SessionId, id: string, block: DiffApprovalBlockRange) => Promise<void>;
70
74
  } & import("react").RefAttributes<SplitDiffHandle>>;
75
+ /**
76
+ * Reconstruct the plain text of the current selection so auto-wrap's visual
77
+ * line breaks never leak into the clipboard. A wrapped row renders its code as
78
+ * several `.subline` block elements, and the browser's default copy inserts a
79
+ * newline between them; those segments form one logical line, so they are joined
80
+ * without a newline while the real newline between diff rows is kept.
81
+ */
82
+ export declare function selectedPlainText(): string | undefined;
71
83
  /** Render the pending-edit review panel and its unified footer action. */
72
84
  export declare function PendingPanel({ wide, useSessions, usePending, onRefresh, onKeep, onRevert, onBlockKeep, onBlockRevert, onOpen, onPasteReference, onUndo, onRedo, onImportVcs, onAckRedoCleared, onAckJustResolved, collapseSidebar, t, }: PendingPanelProps): import("react").JSX.Element;
73
85
  export {};
@@ -17,7 +17,6 @@ export declare const zh: {
17
17
  'panel.missingHint': string;
18
18
  'panel.externalChanged': string;
19
19
  'panel.dismiss': string;
20
- 'panel.createHint': string;
21
20
  'panel.pasteOnCopy': string;
22
21
  'panel.pasteOnCopyDesc': string;
23
22
  'panel.importUntracked': string;
@@ -26,6 +25,8 @@ export declare const zh: {
26
25
  'panel.tabWidthDesc': string;
27
26
  'panel.splitMode': string;
28
27
  'panel.splitModeDesc': string;
28
+ 'panel.navLeadRows': string;
29
+ 'panel.navLeadRowsDesc': string;
29
30
  'panel.quickSummon': string;
30
31
  'panel.quickSummonDesc': string;
31
32
  'panel.recordShortcut': string;
@@ -37,6 +38,7 @@ export declare const zh: {
37
38
  'row.removed': string;
38
39
  'action.keep': string;
39
40
  'action.revert': string;
41
+ 'action.delete': string;
40
42
  'action.keepAll': string;
41
43
  'action.revertAll': string;
42
44
  'action.openFile': string;
@@ -54,6 +56,8 @@ export declare const zh: {
54
56
  'action.wrap': string;
55
57
  'action.toggleOn': string;
56
58
  'action.toggleOff': string;
59
+ 'action.decrease': string;
60
+ 'action.increase': string;
57
61
  'action.importVcs': string;
58
62
  'action.importVcsBusy': string;
59
63
  'panel.importNone': string;
@@ -96,7 +100,6 @@ export declare const en: {
96
100
  'panel.missingHint': string;
97
101
  'panel.externalChanged': string;
98
102
  'panel.dismiss': string;
99
- 'panel.createHint': string;
100
103
  'panel.pasteOnCopy': string;
101
104
  'panel.pasteOnCopyDesc': string;
102
105
  'panel.importUntracked': string;
@@ -105,6 +108,8 @@ export declare const en: {
105
108
  'panel.tabWidthDesc': string;
106
109
  'panel.splitMode': string;
107
110
  'panel.splitModeDesc': string;
111
+ 'panel.navLeadRows': string;
112
+ 'panel.navLeadRowsDesc': string;
108
113
  'panel.quickSummon': string;
109
114
  'panel.quickSummonDesc': string;
110
115
  'panel.recordShortcut': string;
@@ -116,6 +121,7 @@ export declare const en: {
116
121
  'row.removed': string;
117
122
  'action.keep': string;
118
123
  'action.revert': string;
124
+ 'action.delete': string;
119
125
  'action.keepAll': string;
120
126
  'action.revertAll': string;
121
127
  'action.openFile': string;
@@ -133,6 +139,8 @@ export declare const en: {
133
139
  'action.wrap': string;
134
140
  'action.toggleOn': string;
135
141
  'action.toggleOff': string;
142
+ 'action.decrease': string;
143
+ 'action.increase': string;
136
144
  'action.importVcs': string;
137
145
  'action.importVcsBusy': string;
138
146
  'panel.importNone': string;
@@ -1,4 +1,8 @@
1
1
  /** Client preferences for the review panel, persisted in localStorage. */
2
+ /** Default lead rows above a jumped-to diff block (kept small and bounded). */
3
+ export declare const NAV_LEAD_ROWS_DEFAULT = 2;
4
+ export declare const NAV_LEAD_ROWS_MIN = 0;
5
+ export declare const NAV_LEAD_ROWS_MAX = 10;
2
6
  /**
3
7
  * Whether copying a reference should also paste it into the chat input and
4
8
  * focus it. Defaults to on; only an explicit `'0'` disables it.
@@ -45,6 +49,15 @@ export declare function setTabWidth(value: number): void;
45
49
  export declare function splitMode(): boolean;
46
50
  /** Persist the split-view preference. */
47
51
  export declare function setSplitMode(value: boolean): void;
52
+ /**
53
+ * How many rows of lead the diff block jump leaves above the jumped-to block,
54
+ * and how far the anchored navigation scans. Defaults to 2; an out-of-range or
55
+ * non-integer value falls back to the default.
56
+ * @returns the lead row count.
57
+ */
58
+ export declare function navLeadRows(): number;
59
+ /** Persist the block-jump lead row count. */
60
+ export declare function setNavLeadRows(value: number): void;
48
61
  /** Default quick-summon chord (toggle the review panel open/closed). */
49
62
  export declare const DEFAULT_QUICK_SUMMON = "Ctrl+D";
50
63
  /**
@@ -32,13 +32,16 @@ export interface SplitDiff {
32
32
  pairOfRow: ReadonlyMap<number, number>;
33
33
  }
34
34
  /**
35
- * Regroup the whole-file rows into aligned split pairs, pairing a deletion run
36
- * with a following addition run line-by-line (so a replaced line is one pair),
37
- * and leaving a stray deletion or addition as a one-sided pair.
35
+ * Regroup the whole-file rows into aligned split pairs. By order, a deletion
36
+ * run pairs line-by-line with a following addition run; with similarity
37
+ * alignment, each deletion pairs with its most-similar addition (order
38
+ * preserved, threshold-bounded) so a mixed insert/delete block does not force a
39
+ * wrong line together. A stray deletion or addition stays a one-sided pair.
38
40
  * @param rows - the whole-file diff rows.
41
+ * @param alignBySimilarity - align a change block by similarity instead of order.
39
42
  * @returns the split pairs plus the row→pair index map.
40
43
  */
41
- export declare function computeSideBySideDiff(rows: readonly WholeFileDiffRow[]): SplitDiff;
44
+ export declare function computeSideBySideDiff(rows: readonly WholeFileDiffRow[], alignBySimilarity?: boolean): SplitDiff;
42
45
  /**
43
46
  * Pair indices whose left or right text contains the query (case-insensitive).
44
47
  * A pair counts once however many times the query appears, so split search
@@ -47,3 +47,61 @@ export interface WholeFileDiff {
47
47
  */
48
48
  export declare function contentKey(text: string): string;
49
49
  export declare function computeWholeFileDiff(oldText: string, newText: string): WholeFileDiff;
50
+ /**
51
+ * One intra-line segment of a changed line, used to highlight which characters
52
+ * within a paired del/add line differ. A run is either unmarked context
53
+ * (`same`) or the characters that were removed (`del`) /
54
+ * added (`add`). Reassembling the runs of a del row in order yields the old
55
+ * line's text; the runs of the paired add row yield the new line's text.
56
+ */
57
+ export interface IntraRun {
58
+ text: string;
59
+ kind: 'same' | 'del' | 'add';
60
+ }
61
+ /** One matched del→add row pair within a change block. */
62
+ interface BlockPair {
63
+ delIndex: number;
64
+ addIndex: number;
65
+ }
66
+ /** The alignment of one del/add change block: matched pairs and excess sides. */
67
+ export interface BlockAlignment {
68
+ /** Matched del→add pairs, in file order. */
69
+ pairs: BlockPair[];
70
+ /** Row indices of unmatched deletions (shown as del-only in the split view). */
71
+ delOnly: number[];
72
+ /** Row indices of unmatched additions (shown as add-only in the split view). */
73
+ addOnly: number[];
74
+ /** True when similarity matching actually ran. False for a by-order block or a
75
+ * block too large to match — the caller then skips the intra-line highlight so
76
+ * alignment and highlighting are all-or-nothing (either both or neither). */
77
+ usedSimilarity: boolean;
78
+ }
79
+ /**
80
+ * Align a del run with the following add run. By order (the baseline) pairs
81
+ * `del[i]`↔`add[i]`. With similarity alignment, an order-preserving best match
82
+ * pairs each del with the add that maximises total similarity, keeping a pair
83
+ * only when it clears the similarity threshold — so a lone insert or delete in
84
+ * a mixed block stays unaligned instead of being forced onto a wrong line.
85
+ * @param delRows - the run's del rows (index + text).
86
+ * @param addRows - the run's add rows (index + text).
87
+ * @param alignBySimilarity - whether to use the similarity-based matching.
88
+ * @returns the aligned pairs and the unmatched sides.
89
+ */
90
+ export declare function alignChangedBlock(delRows: readonly {
91
+ index: number;
92
+ text: string;
93
+ }[], addRows: readonly {
94
+ index: number;
95
+ text: string;
96
+ }[], alignBySimilarity: boolean): BlockAlignment;
97
+ /**
98
+ * Derive intra-line runs for a whole-file row list. Each del/add block is
99
+ * aligned (by order, or by similarity when `alignBySimilarity`), and each
100
+ * matched pair is compared by `intraRunsOf`. Returns a map keyed by the row's
101
+ * index in `rows`, present only for rows that carry a highlight.
102
+ * @param rows - the unified `WholeFileDiff` row list.
103
+ * @param alignBySimilarity - align blocks by similarity instead of by order.
104
+ * @returns per-row-index intra-line runs for annotated del/add rows.
105
+ */
106
+ export declare function computeIntraLineDiff(rows: readonly WholeFileDiffRow[], alignBySimilarity?: boolean): Map<number, IntraRun[]>;
107
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-diff-approval",
3
- "version": "0.14.1",
3
+ "version": "0.15.0",
4
4
  "description": "DeepSeek Harness plugin: pending-edit review with whole-file diff and Keep/Revert",
5
5
  "packageManager": "pnpm@11.21.0",
6
6
  "author": "Wu Zhiwei",