dsh-diff-approval 0.11.0 → 0.13.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 +9 -3
- package/README.zh.md +9 -3
- package/lib/client.js +1650 -313
- package/lib/index.js +24 -30
- package/lib/types/client/PendingPanel.d.ts +47 -1
- package/lib/types/client/conversation-access.d.ts +67 -0
- package/lib/types/client/index.d.ts +2 -1
- package/lib/types/client/locales.d.ts +16 -0
- package/lib/types/client/reference.d.ts +32 -2
- package/lib/types/client/remap-sync.d.ts +44 -0
- package/lib/types/client/settings.d.ts +29 -0
- package/lib/types/client/slots.d.ts +8 -0
- package/lib/types/client/split-diff.d.ts +51 -0
- package/lib/types/client/store.d.ts +2 -0
- package/lib/types/types.d.ts +3 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -1343,18 +1343,12 @@ function apply(ctx, config) {
|
|
|
1343
1343
|
};
|
|
1344
1344
|
const accepted = contentRangeOf(entry.newText, blockTarget.block.newStart, blockTarget.block.newEnd);
|
|
1345
1345
|
const updatedOld = replaceContentLines(entry.oldText, blockTarget.block.oldStart, blockTarget.block.oldEnd, accepted);
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
afterEntry = {
|
|
1353
|
-
...entry,
|
|
1354
|
-
oldText: updatedOld,
|
|
1355
|
-
updatedAt: Date.now()
|
|
1356
|
-
};
|
|
1357
|
-
}
|
|
1346
|
+
store.update(blockTarget.sessionId, blockTarget.id, { oldText: updatedOld });
|
|
1347
|
+
const afterEntry = {
|
|
1348
|
+
...entry,
|
|
1349
|
+
oldText: updatedOld,
|
|
1350
|
+
updatedAt: Date.now()
|
|
1351
|
+
};
|
|
1358
1352
|
pushUndo(blockTarget.sessionId, {
|
|
1359
1353
|
id: entry.id,
|
|
1360
1354
|
path: entry.path,
|
|
@@ -1369,7 +1363,10 @@ function apply(ctx, config) {
|
|
|
1369
1363
|
await persistSession(blockTarget.sessionId);
|
|
1370
1364
|
return {
|
|
1371
1365
|
ok: true,
|
|
1372
|
-
value:
|
|
1366
|
+
value: updatedOld === entry.newText ? {
|
|
1367
|
+
outcome: "kept",
|
|
1368
|
+
resolved: true
|
|
1369
|
+
} : { outcome: "kept" }
|
|
1373
1370
|
};
|
|
1374
1371
|
}
|
|
1375
1372
|
case "block-revert": {
|
|
@@ -1384,25 +1381,19 @@ function apply(ctx, config) {
|
|
|
1384
1381
|
const restored = contentRangeOf(entry.oldText, blockTarget.block.oldStart, blockTarget.block.oldEnd);
|
|
1385
1382
|
const updatedNew = replaceContentLines(entry.newText, blockTarget.block.newStart, blockTarget.block.newEnd, restored);
|
|
1386
1383
|
const content = reencodeEol(updatedNew, detectEol(entry.newText));
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
afterEntry = {
|
|
1394
|
-
...entry,
|
|
1395
|
-
newText: content,
|
|
1396
|
-
updatedAt: Date.now()
|
|
1397
|
-
};
|
|
1398
|
-
}
|
|
1384
|
+
store.update(blockTarget.sessionId, blockTarget.id, { newText: content });
|
|
1385
|
+
const afterEntry = {
|
|
1386
|
+
...entry,
|
|
1387
|
+
newText: content,
|
|
1388
|
+
updatedAt: Date.now()
|
|
1389
|
+
};
|
|
1399
1390
|
let undo;
|
|
1400
1391
|
try {
|
|
1401
|
-
const
|
|
1402
|
-
if (entry.kind === "create" && content === "") await rm(ctx.fs.processPath(
|
|
1392
|
+
const target = await ctx.fs.resolve(entry.path, { signal });
|
|
1393
|
+
if (entry.kind === "create" && content === "") await rm(ctx.fs.processPath(target), { force: true });
|
|
1403
1394
|
else {
|
|
1404
|
-
const preWrite = await ctx.fs.readText(
|
|
1405
|
-
await writeRevert(
|
|
1395
|
+
const preWrite = await ctx.fs.readText(target, void 0) ?? entry.newText;
|
|
1396
|
+
await writeRevert(target, content, blockTarget.sessionId, signal);
|
|
1406
1397
|
undo = {
|
|
1407
1398
|
before: {
|
|
1408
1399
|
id: entry.id,
|
|
@@ -1425,7 +1416,10 @@ function apply(ctx, config) {
|
|
|
1425
1416
|
await persistSession(blockTarget.sessionId);
|
|
1426
1417
|
return {
|
|
1427
1418
|
ok: true,
|
|
1428
|
-
value:
|
|
1419
|
+
value: normalizeEol(updatedNew) === normalizeEol(entry.oldText) ? {
|
|
1420
|
+
outcome: "reverted",
|
|
1421
|
+
resolved: true
|
|
1422
|
+
} : { outcome: "reverted" }
|
|
1429
1423
|
};
|
|
1430
1424
|
}
|
|
1431
1425
|
case "undo": {
|
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
/** Sidebar-foot pending-edit review action and the split review panel it opens. */
|
|
2
|
+
import type { SessionId } from '@deepseek-ai/dsh-client-connection/client';
|
|
2
3
|
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
4
|
+
import type { DiffApprovalBlockRange, PendingFileDiff } from '../types.ts';
|
|
3
5
|
import type { PendingPanelFace } from './slots.ts';
|
|
6
|
+
import type { DiffApprovalKey } from './locales.ts';
|
|
7
|
+
import { computeWholeFileDiff } from './whole-file-diff.ts';
|
|
8
|
+
import { highlightLines } from './highlight.ts';
|
|
9
|
+
/** The dsh shell's sidebar auto-collapse breakpoint (ui-layout columns.ts):
|
|
10
|
+
* below it the sidebar auto-collapses, and the file list floats on the same
|
|
11
|
+
* breakpoint so the two stay consistent. */
|
|
12
|
+
export declare const SIDEBAR_AUTO_COLLAPSE_PX = 1024;
|
|
4
13
|
export declare function wrapInto(text: string, widthPx: number, measure: (t: string) => number, tabPx: number): string[];
|
|
5
14
|
/** Full panel props composed by the sidebar footer-action slot. */
|
|
6
15
|
export type PendingPanelProps = PropsRuntime<'sidebar.footer.action'> & InjectFace<PendingPanelFace> & PropsLocale<'diff-approval'>;
|
|
16
|
+
/** Locale translator used by the panel and its rows. */
|
|
17
|
+
type Translator = (key: DiffApprovalKey, params?: Record<string, unknown>) => string;
|
|
7
18
|
/**
|
|
8
19
|
* Open the DSH settings dialog and switch to this plugin's section. The
|
|
9
20
|
* settings shell keeps its open state and the active section id as
|
|
@@ -14,5 +25,40 @@ export type PendingPanelProps = PropsRuntime<'sidebar.footer.action'> & InjectFa
|
|
|
14
25
|
* @param sectionLabel - the nav label of this plugin's settings section.
|
|
15
26
|
*/
|
|
16
27
|
export declare function openSettingsSection(sectionLabel: string): void;
|
|
28
|
+
/** One file's synchronous view: diff rows and change blocks. */
|
|
29
|
+
interface RowModel {
|
|
30
|
+
diff: ReturnType<typeof computeWholeFileDiff>;
|
|
31
|
+
/** Maximal runs of changed rows (inclusive row indices); one per modification. */
|
|
32
|
+
blocks: ChangeBlock[];
|
|
33
|
+
}
|
|
34
|
+
/** One file's deferred syntax-highlight runs, one entry per side. */
|
|
35
|
+
interface HighlightRuns {
|
|
36
|
+
oldRuns: ReturnType<typeof highlightLines>;
|
|
37
|
+
newRuns: ReturnType<typeof highlightLines>;
|
|
38
|
+
}
|
|
39
|
+
/** One contiguous run of changed rows, treated as a single modification. */
|
|
40
|
+
interface ChangeBlock {
|
|
41
|
+
start: number;
|
|
42
|
+
end: number;
|
|
43
|
+
}
|
|
44
|
+
/** Imperative surface the parent uses to drive block navigation from the
|
|
45
|
+
* shared toolbar/keyboard in split mode (its own `focus` is private here). */
|
|
46
|
+
export interface SplitDiffHandle {
|
|
47
|
+
jump: (direction: -1 | 1) => void;
|
|
48
|
+
openSearch: () => void;
|
|
49
|
+
}
|
|
50
|
+
/** The two-column (side-by-side) whole-file diff view. */
|
|
51
|
+
export declare const SplitDiff: import("react").ForwardRefExoticComponent<{
|
|
52
|
+
file: PendingFileDiff;
|
|
53
|
+
model: RowModel;
|
|
54
|
+
runs: HighlightRuns | undefined;
|
|
55
|
+
langWrap: boolean;
|
|
56
|
+
tabWidthSpaces: number;
|
|
57
|
+
busy: boolean;
|
|
58
|
+
t: Translator;
|
|
59
|
+
onBlockKeep: (sessionId: SessionId, id: string, block: DiffApprovalBlockRange) => Promise<void>;
|
|
60
|
+
onBlockRevert: (sessionId: SessionId, id: string, block: DiffApprovalBlockRange) => Promise<void>;
|
|
61
|
+
} & import("react").RefAttributes<SplitDiffHandle>>;
|
|
17
62
|
/** Render the pending-edit review panel and its unified footer action. */
|
|
18
|
-
export declare function PendingPanel({ wide, useSessions, usePending, onRefresh, onKeep, onRevert, onBlockKeep, onBlockRevert, onOpen, onPasteReference, onUndo, onRedo, onImportVcs, onAckRedoCleared, t, }: PendingPanelProps): import("react").JSX.Element;
|
|
63
|
+
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;
|
|
64
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope-addressed access to the session's composer draft and pending queue.
|
|
3
|
+
* Extracted from the plugin entry so the wiring (not just the remap math) is
|
|
4
|
+
* unit-testable against a faithful fake of the DSH conversation service.
|
|
5
|
+
* @module dsh-diff-approval/client/conversation-access
|
|
6
|
+
*/
|
|
7
|
+
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client';
|
|
8
|
+
import type { SessionId } from '@deepseek-ai/dsh-client-connection/client';
|
|
9
|
+
/** One queued message's minimal shape read from the session input state. */
|
|
10
|
+
export interface QueueMessageView {
|
|
11
|
+
id: string;
|
|
12
|
+
placement: 'queued' | 'steering' | 'context';
|
|
13
|
+
content: readonly {
|
|
14
|
+
type: string;
|
|
15
|
+
text?: string;
|
|
16
|
+
}[];
|
|
17
|
+
}
|
|
18
|
+
/** The conversation service slice the reference remap addresses through a session scope. */
|
|
19
|
+
export interface ConversationFace {
|
|
20
|
+
input: {
|
|
21
|
+
for(actx: ClientContext): {
|
|
22
|
+
setDraft(text: string): void;
|
|
23
|
+
state: {
|
|
24
|
+
getSnapshot(): {
|
|
25
|
+
queue: readonly QueueMessageView[];
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
updateQueue(itemId: string, action: {
|
|
31
|
+
kind: 'edit';
|
|
32
|
+
content: readonly {
|
|
33
|
+
type: string;
|
|
34
|
+
text?: string;
|
|
35
|
+
}[];
|
|
36
|
+
}): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Resolve the current session's scoped conversation face. The scoped context
|
|
40
|
+
* (`sessions.scope(id)`) carries no `conversation` inject of its own, so reading
|
|
41
|
+
* it as a property (`actx.conversation`) trips Cordis's inject check and throws
|
|
42
|
+
* `cannot get property "conversation" without inject`. `actx.get('conversation')`
|
|
43
|
+
* bypasses that check yet still returns the traceable bound to `actx`, so its
|
|
44
|
+
* verbs (e.g. `updateQueue`) resolve the right session scope.
|
|
45
|
+
*/
|
|
46
|
+
export declare function resolveConversation(ctx: ClientContext, sessionId: SessionId | undefined): {
|
|
47
|
+
actx: ClientContext;
|
|
48
|
+
conversation: ConversationFace;
|
|
49
|
+
} | undefined;
|
|
50
|
+
/** Draft/queue verbs bound to the current session, resolved lazily per call. */
|
|
51
|
+
export interface ConversationAccess {
|
|
52
|
+
/** Write the current session's composer draft. */
|
|
53
|
+
writeDraft: (text: string) => void;
|
|
54
|
+
/** Read the current session's still-queued messages (queued placement only). */
|
|
55
|
+
readQueue: () => readonly QueueMessageView[];
|
|
56
|
+
/** Apply an edit mutation to one queued message's full content. */
|
|
57
|
+
writeQueue: (itemId: string, content: readonly {
|
|
58
|
+
type: string;
|
|
59
|
+
text?: string;
|
|
60
|
+
}[]) => void;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Build the per-call-resolved draft/queue accessor. `sessionId` is a function
|
|
64
|
+
* so the accessor always addresses the *current* session, even though it is
|
|
65
|
+
* handed to the remap sync once.
|
|
66
|
+
*/
|
|
67
|
+
export declare function conversationAccess(ctx: ClientContext, sessionId: () => SessionId | undefined): ConversationAccess;
|
|
@@ -4,7 +4,8 @@ export type { PendingPanelProps } from './PendingPanel.tsx';
|
|
|
4
4
|
export type { PendingDiffSnapshot, PendingPanelFace } from './slots.ts';
|
|
5
5
|
export type { DiffApprovalKey } from './locales.ts';
|
|
6
6
|
export { DIFF_APPROVAL_CHANNEL } from './port.ts';
|
|
7
|
-
/** Required services: locale, slots, the wire channel,
|
|
7
|
+
/** Required services: locale, slots, the wire channel, the current session, and
|
|
8
|
+
* the layout controller (this plugin collapses the sidebar before its modal opens). */
|
|
8
9
|
export declare const inject: string[];
|
|
9
10
|
/**
|
|
10
11
|
* Mount the pending-edit review panel.
|
|
@@ -24,9 +24,15 @@ export declare const zh: {
|
|
|
24
24
|
'panel.importUntrackedDesc': string;
|
|
25
25
|
'panel.tabWidth': string;
|
|
26
26
|
'panel.tabWidthDesc': string;
|
|
27
|
+
'panel.splitMode': string;
|
|
28
|
+
'panel.splitModeDesc': string;
|
|
29
|
+
'panel.quickSummon': string;
|
|
30
|
+
'panel.quickSummonDesc': string;
|
|
31
|
+
'panel.recordShortcut': string;
|
|
27
32
|
'settings.tabLabel': string;
|
|
28
33
|
'row.create': string;
|
|
29
34
|
'row.failed': string;
|
|
35
|
+
'row.dismiss': string;
|
|
30
36
|
'row.added': string;
|
|
31
37
|
'row.removed': string;
|
|
32
38
|
'action.keep': string;
|
|
@@ -62,6 +68,8 @@ export declare const zh: {
|
|
|
62
68
|
'status.kept': string;
|
|
63
69
|
'status.reverted': string;
|
|
64
70
|
'status.missing': string;
|
|
71
|
+
'panel.resolvedAsk': string;
|
|
72
|
+
'panel.keepInList': string;
|
|
65
73
|
};
|
|
66
74
|
/** Translation keys owned by the pending-edit review namespace. */
|
|
67
75
|
export type DiffApprovalKey = keyof typeof zh;
|
|
@@ -95,9 +103,15 @@ export declare const en: {
|
|
|
95
103
|
'panel.importUntrackedDesc': string;
|
|
96
104
|
'panel.tabWidth': string;
|
|
97
105
|
'panel.tabWidthDesc': string;
|
|
106
|
+
'panel.splitMode': string;
|
|
107
|
+
'panel.splitModeDesc': string;
|
|
108
|
+
'panel.quickSummon': string;
|
|
109
|
+
'panel.quickSummonDesc': string;
|
|
110
|
+
'panel.recordShortcut': string;
|
|
98
111
|
'settings.tabLabel': string;
|
|
99
112
|
'row.create': string;
|
|
100
113
|
'row.failed': string;
|
|
114
|
+
'row.dismiss': string;
|
|
101
115
|
'row.added': string;
|
|
102
116
|
'row.removed': string;
|
|
103
117
|
'action.keep': string;
|
|
@@ -133,4 +147,6 @@ export declare const en: {
|
|
|
133
147
|
'status.kept': string;
|
|
134
148
|
'status.reverted': string;
|
|
135
149
|
'status.missing': string;
|
|
150
|
+
'panel.resolvedAsk': string;
|
|
151
|
+
'panel.keepInList': string;
|
|
136
152
|
};
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* the panel.
|
|
6
6
|
* @module dsh-diff-approval/client/reference
|
|
7
7
|
*/
|
|
8
|
+
/** The marker that replaces a reference's line number when its lines are gone. */
|
|
9
|
+
export declare const LINE_MISSING_LABEL = "LINE_MISSING";
|
|
8
10
|
/**
|
|
9
11
|
* Last path segment of a file path, any separator style.
|
|
10
12
|
* @param path - the path to shorten.
|
|
@@ -30,11 +32,39 @@ export declare function referencePathOf(path: string, workspacePath: string | un
|
|
|
30
32
|
*/
|
|
31
33
|
export declare function lineRangeLabel(start: number, end: number): string;
|
|
32
34
|
/**
|
|
33
|
-
* Build the clipboard text for a selected line range
|
|
35
|
+
* Build the clipboard text for a selected line range, wrapped in parentheses so
|
|
36
|
+
* the reference reads as one unambiguous token (and can be matched precisely).
|
|
34
37
|
* @param path - the selected file's path.
|
|
35
38
|
* @param workspacePath - the current workspace root, or `undefined`.
|
|
36
39
|
* @param start - first selected line number.
|
|
37
40
|
* @param end - last selected line number.
|
|
38
|
-
* @returns the `path:range` reference text.
|
|
41
|
+
* @returns the `(path:range)` reference text.
|
|
39
42
|
*/
|
|
40
43
|
export declare function referenceOf(path: string, workspacePath: string | undefined, start: number, end: number): string;
|
|
44
|
+
/**
|
|
45
|
+
* Map one referenced line range from `oldContent` coordinates to `newContent`
|
|
46
|
+
* coordinates. Lines that survive (unchanged context) map to their new line
|
|
47
|
+
* numbers, and the surviving lines' min/max span is returned. When every line
|
|
48
|
+
* in the range was removed, returns `undefined` (the reference is expired).
|
|
49
|
+
* @param oldContent - the file content the reference was made against.
|
|
50
|
+
* @param newContent - the file content now.
|
|
51
|
+
* @param start - first referenced line (1-based, inclusive).
|
|
52
|
+
* @param end - last referenced line (1-based, inclusive).
|
|
53
|
+
* @returns the surviving range, or `undefined` when nothing survives.
|
|
54
|
+
*/
|
|
55
|
+
export declare function remapReferenceRange(oldContent: string, newContent: string, start: number, end: number): {
|
|
56
|
+
start: number;
|
|
57
|
+
end: number;
|
|
58
|
+
} | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* Rewrite every `(referencePath:line)` / `(referencePath:start-end)` occurrence
|
|
61
|
+
* in `text`, remapping each range from `oldContent` to `newContent`. A range
|
|
62
|
+
* whose lines all survived becomes the new range; one whose lines were all
|
|
63
|
+
* removed becomes `(referencePath:LINE_MISSING)`.
|
|
64
|
+
* @param text - the free text (composer draft, queued message) to rewrite.
|
|
65
|
+
* @param referencePath - the file's reference path (workspace-relative or absolute).
|
|
66
|
+
* @param oldContent - the file content the references were made against.
|
|
67
|
+
* @param newContent - the file content now.
|
|
68
|
+
* @returns the rewritten text.
|
|
69
|
+
*/
|
|
70
|
+
export declare function remapReferences(text: string, referencePath: string, oldContent: string, newContent: string): string;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reference remapping sync: track each pending file's last-seen content and,
|
|
3
|
+
* when it changes (an agent edit, a block revert, or an external adoption),
|
|
4
|
+
* rewrite stale references in the current composer draft and the pending queue.
|
|
5
|
+
* Pure orchestration over the pending store; the actual line remapping lives in
|
|
6
|
+
* `reference.ts`.
|
|
7
|
+
* @module dsh-diff-approval/client/remap-sync
|
|
8
|
+
*/
|
|
9
|
+
import type { PendingDiffStore } from './store.ts';
|
|
10
|
+
/** One queued message's minimal content block; text blocks carry `text`, and every other kind passes through verbatim. */
|
|
11
|
+
interface QueueBlock {
|
|
12
|
+
type: string;
|
|
13
|
+
text?: string;
|
|
14
|
+
}
|
|
15
|
+
/** A queued message's minimal shape for reference remapping. */
|
|
16
|
+
interface QueueMessage {
|
|
17
|
+
id: string;
|
|
18
|
+
content: readonly QueueBlock[];
|
|
19
|
+
}
|
|
20
|
+
/** One remapping sync's inputs; `readDraft`/`writeDraft` address the current composer. */
|
|
21
|
+
export interface ReferenceRemapOpts {
|
|
22
|
+
store: PendingDiffStore;
|
|
23
|
+
/** Read the current composer draft text (undefined when there is none). */
|
|
24
|
+
readDraft: () => string | undefined;
|
|
25
|
+
/** Write the current composer draft text. */
|
|
26
|
+
writeDraft: (text: string) => void;
|
|
27
|
+
/** Read the current session's still-queued messages (full content blocks). */
|
|
28
|
+
readQueue: () => readonly QueueMessage[];
|
|
29
|
+
/** Apply an edit mutation to one queued message's full content. */
|
|
30
|
+
writeQueue: (itemId: string, content: readonly QueueBlock[]) => void;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Subscribe to the pending store and remap references as each file's content
|
|
34
|
+
* changes. The first observation of a file only seeds the baseline; a later
|
|
35
|
+
* `newText` change remaps (draft + queue) and advances the baseline. Files that
|
|
36
|
+
* leave the list drop their baseline so a future re-appearance re-baselines.
|
|
37
|
+
* @param opts - the sync inputs.
|
|
38
|
+
* @returns an unsubscribe function plus a direct remap verb for whole-file reverts.
|
|
39
|
+
*/
|
|
40
|
+
export declare function attachReferenceRemap(opts: ReferenceRemapOpts): {
|
|
41
|
+
unsubscribe: () => void;
|
|
42
|
+
remapFile: (path: string, oldText: string, newText: string) => void;
|
|
43
|
+
};
|
|
44
|
+
export {};
|
|
@@ -36,3 +36,32 @@ export declare function setWrapEnabled(lang: string, value: boolean): void;
|
|
|
36
36
|
export declare function tabWidth(): number;
|
|
37
37
|
/** Persist the diff's tab width (in spaces). */
|
|
38
38
|
export declare function setTabWidth(value: number): void;
|
|
39
|
+
/**
|
|
40
|
+
* Whether the whole-file diff view uses the two-column (side-by-side) layout.
|
|
41
|
+
* Default off (single column): the unified diff. Only an explicit `'1'` enables
|
|
42
|
+
* split mode.
|
|
43
|
+
* @returns whether the split (two-column) diff view is used.
|
|
44
|
+
*/
|
|
45
|
+
export declare function splitMode(): boolean;
|
|
46
|
+
/** Persist the split-view preference. */
|
|
47
|
+
export declare function setSplitMode(value: boolean): void;
|
|
48
|
+
/** Default quick-summon chord (toggle the review panel open/closed). */
|
|
49
|
+
export declare const DEFAULT_QUICK_SUMMON = "Ctrl+D";
|
|
50
|
+
/**
|
|
51
|
+
* The quick-summon chord. Stored as `Modifier+...+Key`; falls back to
|
|
52
|
+
* {@link DEFAULT_QUICK_SUMMON}.
|
|
53
|
+
* @returns the chord string.
|
|
54
|
+
*/
|
|
55
|
+
export declare function quickSummonKey(): string;
|
|
56
|
+
/** Persist the quick-summon chord. */
|
|
57
|
+
export declare function setQuickSummonKey(value: string): void;
|
|
58
|
+
/**
|
|
59
|
+
* Whether a keyboard event matches a chord string like `Ctrl+D`. Modifier
|
|
60
|
+
* names are matched case-insensitively (`Ctrl`/`Control`, `Alt`/`Option`,
|
|
61
|
+
* `Shift`, `Meta`/`Cmd`/`Command`/`Win`); the final part is the key. Exact
|
|
62
|
+
* modifier set is required (extra modifiers do not match).
|
|
63
|
+
* @param event - the keydown event.
|
|
64
|
+
* @param shortcut - the chord string.
|
|
65
|
+
* @returns whether the event matches.
|
|
66
|
+
*/
|
|
67
|
+
export declare function matchesShortcut(event: KeyboardEvent, shortcut: string): boolean;
|
|
@@ -20,6 +20,9 @@ export interface PendingDiffSnapshot {
|
|
|
20
20
|
* superseded the redo history; the panel surfaces it once (deferred if the
|
|
21
21
|
* panel is closed) via a bottom-right notice. */
|
|
22
22
|
redoCleared?: boolean;
|
|
23
|
+
/** Latched file id whose last block just resolved; the panel prompts once to
|
|
24
|
+
* remove-or-keep it, then acknowledges to clear the latch. */
|
|
25
|
+
justResolved?: string | undefined;
|
|
23
26
|
}
|
|
24
27
|
/** The injected face the panel component receives from the plugin body. */
|
|
25
28
|
export interface PendingPanelFace {
|
|
@@ -49,4 +52,9 @@ export interface PendingPanelFace {
|
|
|
49
52
|
onImportVcs: (sessionId: SessionId, includeUntracked: boolean) => Promise<VcsImportValue>;
|
|
50
53
|
/** Acknowledge the redo-cleared notice so it is only surfaced once. */
|
|
51
54
|
onAckRedoCleared: () => void;
|
|
55
|
+
/** Acknowledge the just-resolved prompt so it is only surfaced once. */
|
|
56
|
+
onAckJustResolved: () => void;
|
|
57
|
+
/** Collapse the DSH sidebar (no-op when already collapsed) before the modal
|
|
58
|
+
* opens or fullscreens, so an expanded sidebar can't overlap the modal. */
|
|
59
|
+
collapseSidebar: () => void;
|
|
52
60
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Side-by-side (split) diff model: the unified whole-file diff rows are
|
|
3
|
+
* regrouped into line-aligned pairs for a two-column "before | current" view.
|
|
4
|
+
* Pure derivation; the view owns rendering. A context row becomes a pair with
|
|
5
|
+
* both sides, a deletion a left-only pair, an addition a right-only pair, and
|
|
6
|
+
* an adjacent deletion/addition run is paired line-by-line into a single
|
|
7
|
+
* replacement pair so the two columns line up.
|
|
8
|
+
* @module dsh-diff-approval/client/split-diff
|
|
9
|
+
*/
|
|
10
|
+
import type { WholeFileDiffRow } from './whole-file-diff.ts';
|
|
11
|
+
/** One side of a split pair: text plus its 1-based line number (absent on a pure add). */
|
|
12
|
+
export interface SplitSide {
|
|
13
|
+
/** The side's source line content, without the terminating newline. */
|
|
14
|
+
text: string;
|
|
15
|
+
/** 1-based line number on this side, or undefined. */
|
|
16
|
+
line: number | undefined;
|
|
17
|
+
}
|
|
18
|
+
/** One aligned left/right pair of the split view. */
|
|
19
|
+
export interface SplitPair {
|
|
20
|
+
/** What the pair is: unchanged, deleted (left only), added (right only), or a replaced line. */
|
|
21
|
+
kind: 'context' | 'del' | 'add' | 'replace';
|
|
22
|
+
/** The old (left) side; absent on a pure addition. */
|
|
23
|
+
left: SplitSide | undefined;
|
|
24
|
+
/** The new (right) side; absent on a pure deletion. */
|
|
25
|
+
right: SplitSide | undefined;
|
|
26
|
+
}
|
|
27
|
+
/** The derived split view: aligned pairs plus the row→pair index map. */
|
|
28
|
+
export interface SplitDiff {
|
|
29
|
+
/** Aligned pairs in file order. */
|
|
30
|
+
pairs: SplitPair[];
|
|
31
|
+
/** Original whole-file row index → pair index, so block ranges keep working. */
|
|
32
|
+
pairOfRow: ReadonlyMap<number, number>;
|
|
33
|
+
}
|
|
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.
|
|
38
|
+
* @param rows - the whole-file diff rows.
|
|
39
|
+
* @returns the split pairs plus the row→pair index map.
|
|
40
|
+
*/
|
|
41
|
+
export declare function computeSideBySideDiff(rows: readonly WholeFileDiffRow[]): SplitDiff;
|
|
42
|
+
/**
|
|
43
|
+
* Pair indices whose left or right text contains the query (case-insensitive).
|
|
44
|
+
* A pair counts once however many times the query appears, so split search
|
|
45
|
+
* highlights the whole pair on both columns and a single "current" pair is
|
|
46
|
+
* stepped through — not each individual left/right occurrence.
|
|
47
|
+
* @param pairs - the split pairs.
|
|
48
|
+
* @param query - the query text; an empty query matches nothing.
|
|
49
|
+
* @returns matching pair indices, in file order.
|
|
50
|
+
*/
|
|
51
|
+
export declare function searchPairs(pairs: readonly SplitPair[], query: string): number[];
|
|
@@ -34,6 +34,8 @@ export interface PendingDiffStore extends HostObservable<PendingDiffSnapshot> {
|
|
|
34
34
|
reset: () => void;
|
|
35
35
|
/** Acknowledge a redo-cleared notice so the panel surfaces it only once. */
|
|
36
36
|
clearRedoCleared: () => void;
|
|
37
|
+
/** Acknowledge the just-resolved prompt so it is only surfaced once. */
|
|
38
|
+
clearJustResolved: () => void;
|
|
37
39
|
}
|
|
38
40
|
/**
|
|
39
41
|
* Create the store over the review-channel port.
|
package/lib/types/types.d.ts
CHANGED
|
@@ -103,4 +103,7 @@ export interface DiffApprovalActionValue {
|
|
|
103
103
|
outcome: DiffApprovalActionOutcome;
|
|
104
104
|
/** Entry id the undo/redo affected; absent for a no-op or a non-undo action. */
|
|
105
105
|
id?: string | undefined;
|
|
106
|
+
/** Set when a block keep/revert just cleared the entry's last remaining
|
|
107
|
+
* block (the entry stays listed with no pending diff). */
|
|
108
|
+
resolved?: boolean | undefined;
|
|
106
109
|
}
|
package/package.json
CHANGED