dsh-diff-approval 0.19.3 → 0.20.1
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/client.js +2046 -601
- package/lib/index.js +1251 -60
- package/lib/types/client/PathPicker.d.ts +43 -0
- package/lib/types/client/PendingPanel.d.ts +9 -17
- package/lib/types/client/highlight.d.ts +51 -8
- package/lib/types/client/lang.d.ts +8 -0
- package/lib/types/client/locales.d.ts +43 -0
- package/lib/types/client/port.d.ts +5 -1
- package/lib/types/client/settings.d.ts +16 -0
- package/lib/types/client/slots.d.ts +5 -1
- package/lib/types/client/store.d.ts +5 -1
- package/lib/types/client/whole-file-diff.d.ts +21 -0
- package/lib/types/client/windowed-highlight.d.ts +66 -0
- package/lib/types/pending.d.ts +10 -0
- package/lib/types/types.d.ts +51 -0
- package/package.json +1 -13
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The add-path dialog: pick one path in a lazily-loaded workspace tree — or type
|
|
3
|
+
* it outright — then press Add. Nothing is added by pointing at it: the path and
|
|
4
|
+
* the no-change box are prepared first, and the host judges them once the button
|
|
5
|
+
* is pressed.
|
|
6
|
+
*
|
|
7
|
+
* The path box is the single source of truth for the selection: a tree row is
|
|
8
|
+
* highlighted while its path is the box's text, and editing the text by hand
|
|
9
|
+
* simply leaves no row highlighted.
|
|
10
|
+
*
|
|
11
|
+
* The dialog is a modal inside the panel: it owns Escape while it is on screen
|
|
12
|
+
* (the panel's own chords stand down, see `pathPickerOpen`).
|
|
13
|
+
* @module dsh-diff-approval/client/PathPicker
|
|
14
|
+
*/
|
|
15
|
+
import type { ReactElement } from 'react';
|
|
16
|
+
import type { DiffApprovalAddValue, DiffApprovalBrowseEntry } from '../types.ts';
|
|
17
|
+
import type { Translator } from './locales.ts';
|
|
18
|
+
/** Whether this panel's add-path dialog is on screen. The panel's global chords
|
|
19
|
+
* consult this so the modal owns the keyboard while it is open. */
|
|
20
|
+
export declare function pathPickerOpen(): boolean;
|
|
21
|
+
/** Full props of the add-path dialog. */
|
|
22
|
+
export interface PathPickerProps {
|
|
23
|
+
/** The session's workspace root (absolute), for the tree's top row. */
|
|
24
|
+
rootPath?: string | undefined;
|
|
25
|
+
/** List one workspace directory level (absolute path; undefined is the root). */
|
|
26
|
+
onBrowse: (path?: string) => Promise<{
|
|
27
|
+
path: string;
|
|
28
|
+
entries: DiffApprovalBrowseEntry[];
|
|
29
|
+
truncated: boolean;
|
|
30
|
+
}>;
|
|
31
|
+
/** Add the chosen path; resolves to what the host's scan did. */
|
|
32
|
+
onAdd: (path: string, includeUnchanged: boolean) => Promise<DiffApprovalAddValue>;
|
|
33
|
+
/** Transient banner for an outcome the dialog does not stay open for. */
|
|
34
|
+
onToast: (text: string) => void;
|
|
35
|
+
onClose: () => void;
|
|
36
|
+
t: Translator;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The add-path dialog.
|
|
40
|
+
* @param props - the workspace root, the host calls, and the close/toast sinks.
|
|
41
|
+
* @returns the modal (rendered only while open by its parent).
|
|
42
|
+
*/
|
|
43
|
+
export declare function PathPicker({ rootPath, onBrowse, onAdd, onToast, onClose, t }: PathPickerProps): ReactElement;
|
|
@@ -3,10 +3,11 @@ import type { SessionId } from '@deepseek-ai/dsh-client-connection/client';
|
|
|
3
3
|
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
4
4
|
import type { DiffApprovalBlockRange, PendingFileDiff } from '../types.ts';
|
|
5
5
|
import type { PendingPanelFace } from './slots.ts';
|
|
6
|
-
import type {
|
|
6
|
+
import type { Translator } from './locales.ts';
|
|
7
7
|
import { computeWholeFileDiff } from './whole-file-diff.ts';
|
|
8
|
-
import type { IntraRun } from './whole-file-diff.ts';
|
|
9
|
-
import {
|
|
8
|
+
import type { ChangeBlock, IntraRun } from './whole-file-diff.ts';
|
|
9
|
+
import type { HighlightSides } from './highlight.ts';
|
|
10
|
+
import type { VisibleLines } from './windowed-highlight.ts';
|
|
10
11
|
/** Normalize a path for comparison: forward slashes, no trailing slash. */
|
|
11
12
|
export declare function normalizeDiffPath(p: string): string;
|
|
12
13
|
/** Whether a produced-file chip path and a pending file path refer to the same
|
|
@@ -23,8 +24,6 @@ export declare const SIDEBAR_AUTO_COLLAPSE_PX = 1024;
|
|
|
23
24
|
export declare function wrapInto(text: string, widthPx: number, measure: (t: string) => number, tabPx: number): string[];
|
|
24
25
|
/** Full panel props composed by the sidebar footer-action slot. */
|
|
25
26
|
export type PendingPanelProps = PropsRuntime<'sidebar.footer.action'> & InjectFace<PendingPanelFace> & PropsLocale<'diff-approval'>;
|
|
26
|
-
/** Locale translator used by the panel and its rows. */
|
|
27
|
-
type Translator = (key: DiffApprovalKey, params?: Record<string, unknown>) => string;
|
|
28
27
|
/**
|
|
29
28
|
* Open the DSH settings dialog and switch to this plugin's section. The
|
|
30
29
|
* settings shell keeps its open state and the active section id as
|
|
@@ -43,16 +42,6 @@ interface RowModel {
|
|
|
43
42
|
/** Intra-line runs keyed by row index, present only for annotated del/add rows. */
|
|
44
43
|
intra: Map<number, IntraRun[]>;
|
|
45
44
|
}
|
|
46
|
-
/** One file's deferred syntax-highlight runs, one entry per side. */
|
|
47
|
-
interface HighlightRuns {
|
|
48
|
-
oldRuns: ReturnType<typeof highlightLines>;
|
|
49
|
-
newRuns: ReturnType<typeof highlightLines>;
|
|
50
|
-
}
|
|
51
|
-
/** One contiguous run of changed rows, treated as a single modification. */
|
|
52
|
-
interface ChangeBlock {
|
|
53
|
-
start: number;
|
|
54
|
-
end: number;
|
|
55
|
-
}
|
|
56
45
|
/** One selected line range in row indices, normalized low-to-high. */
|
|
57
46
|
interface RowRange {
|
|
58
47
|
start: number;
|
|
@@ -76,7 +65,7 @@ export interface SplitDiffHandle {
|
|
|
76
65
|
export declare const SplitDiff: import("react").ForwardRefExoticComponent<{
|
|
77
66
|
file: PendingFileDiff;
|
|
78
67
|
model: RowModel;
|
|
79
|
-
runs:
|
|
68
|
+
runs: HighlightSides | undefined;
|
|
80
69
|
langWrap: boolean;
|
|
81
70
|
tabWidthSpaces: number;
|
|
82
71
|
busy: boolean;
|
|
@@ -87,6 +76,9 @@ export declare const SplitDiff: import("react").ForwardRefExoticComponent<{
|
|
|
87
76
|
onBlockRevert: (sessionId: SessionId, id: string, block: DiffApprovalBlockRange) => Promise<void>;
|
|
88
77
|
/** Notify the parent to toast a block-wrap boundary / single-block (Ctrl+Up/Down). */
|
|
89
78
|
onWrapToast: (text: string) => void;
|
|
79
|
+
/** Report which source lines this view is showing, so the parent's windowed
|
|
80
|
+
* highlighter follows this view's own scroller (it has its own virtual window). */
|
|
81
|
+
onVisibleLines: (visible: VisibleLines) => void;
|
|
90
82
|
} & import("react").RefAttributes<SplitDiffHandle>>;
|
|
91
83
|
/**
|
|
92
84
|
* Reconstruct the plain text of the current selection so auto-wrap's visual
|
|
@@ -97,5 +89,5 @@ export declare const SplitDiff: import("react").ForwardRefExoticComponent<{
|
|
|
97
89
|
*/
|
|
98
90
|
export declare function selectedPlainText(): string | undefined;
|
|
99
91
|
/** Render the pending-edit review panel and its unified footer action. */
|
|
100
|
-
export declare function PendingPanel({ wide, useSessions, usePending, onRefresh, onKeep, onRevert, onBlockKeep, onBlockRevert, onOpen, onPreviewImage, onPasteReference, onUndo, onRedo, onImportVcs, onRefreshVcs, onKeepAll, onRevertAll, onAckRedoCleared, collapseSidebar, t, }: PendingPanelProps): import("react").JSX.Element;
|
|
92
|
+
export declare function PendingPanel({ wide, useSessions, usePending, onRefresh, onKeep, onRevert, onBlockKeep, onBlockRevert, onOpen, onPreviewImage, onPasteReference, onUndo, onRedo, onImportVcs, onRefreshVcs, onBrowse, onAddPath, onKeepAll, onRevertAll, onAckRedoCleared, collapseSidebar, t, }: PendingPanelProps): import("react").JSX.Element;
|
|
101
93
|
export {};
|
|
@@ -10,11 +10,42 @@
|
|
|
10
10
|
* @module dsh-diff-approval/client/highlight
|
|
11
11
|
*/
|
|
12
12
|
import type { CSSProperties } from 'react';
|
|
13
|
+
import type { HighlighterCore } from 'shiki/core';
|
|
13
14
|
/** One highlighted run of a line: literal text plus a color style. */
|
|
14
15
|
export interface HighlightSpan {
|
|
15
16
|
text: string;
|
|
16
17
|
style: CSSProperties;
|
|
17
18
|
}
|
|
19
|
+
/** One file's highlight runs, one entry per side and line (index = line - 1). A
|
|
20
|
+
* hole means that line has not been highlighted — the viewer renders it plain,
|
|
21
|
+
* which is the honest state of a windowed highlighter: only what has been looked
|
|
22
|
+
* at is tokenized. */
|
|
23
|
+
export interface HighlightSides {
|
|
24
|
+
oldRuns: HighlightSpan[][];
|
|
25
|
+
newRuns: HighlightSpan[][];
|
|
26
|
+
}
|
|
27
|
+
/** A saved grammar state: resuming from one continues tokenization *exactly*
|
|
28
|
+
* where it stopped, instead of assuming the window's first line starts the file.
|
|
29
|
+
* Opaque to callers — only `highlightWindow` produces and consumes it, and
|
|
30
|
+
* `undefined` means "no saved state" (the file's first line is top-level). */
|
|
31
|
+
export type HighlightState = Parameters<HighlighterCore['codeToTokens']>[1]['grammarState'];
|
|
32
|
+
/** One highlighted window: the requested lines' runs and the grammar state after
|
|
33
|
+
* the last of them, for an exact continuation by the next window. */
|
|
34
|
+
export interface HighlightWindow {
|
|
35
|
+
runs: HighlightSpan[][];
|
|
36
|
+
state: HighlightState;
|
|
37
|
+
}
|
|
38
|
+
/** Options for {@link highlightWindow}. */
|
|
39
|
+
export interface HighlightWindowOptions {
|
|
40
|
+
/** Lines of preceding context to run the grammar over without returning them,
|
|
41
|
+
* so a window starting inside a multi-line construct (a block comment, a
|
|
42
|
+
* template literal, a fenced code block) still colours correctly. Ignored when
|
|
43
|
+
* `state` is given: that one is exact. */
|
|
44
|
+
context?: number;
|
|
45
|
+
/** An exact state saved at this window's first line (a previous window's
|
|
46
|
+
* `state`). */
|
|
47
|
+
state?: HighlightState;
|
|
48
|
+
}
|
|
18
49
|
/**
|
|
19
50
|
* Primary grammar ids offered in the viewer's language selector, in picker
|
|
20
51
|
* order (alphabetical). Kept explicit instead of deriving from `LANGS`: some
|
|
@@ -28,13 +59,25 @@ export declare const HIGHLIGHT_LANGS: string[];
|
|
|
28
59
|
/** Conventional display name for a grammar id, falling back to the id itself. */
|
|
29
60
|
export declare function languageDisplayName(id: string): string;
|
|
30
61
|
/**
|
|
31
|
-
* Tokenize `
|
|
32
|
-
* registered grammar; `undefined` means the caller renders its plain
|
|
33
|
-
* Each run's color is a `--shiki-*` custom property, keeping token
|
|
34
|
-
* the harness theme's sheets. The trailing newline shiki appends as a
|
|
35
|
-
* empty line is dropped so the run count matches the caller's own
|
|
36
|
-
*
|
|
62
|
+
* Tokenize the lines `[from, to)` into per-line highlighted runs when `lang`
|
|
63
|
+
* names a registered grammar; `undefined` means the caller renders its plain
|
|
64
|
+
* fallback. Each run's color is a `--shiki-*` custom property, keeping token
|
|
65
|
+
* colors on the harness theme's sheets. The trailing newline shiki appends as a
|
|
66
|
+
* final empty line is dropped so the run count matches the caller's own array.
|
|
67
|
+
*
|
|
68
|
+
* This is the viewer's only entry point, and it is deliberately windowed: the
|
|
69
|
+
* code view renders a virtual window of rows, so highlighting a whole file to
|
|
70
|
+
* show one screenful is almost all waste (measured on a 3818-line file: ~945 ms
|
|
71
|
+
* for both sides whole-file against ~8 ms for one window). A window is continued
|
|
72
|
+
* *exactly* from a previous one's `state`, or approximately from `context` lines
|
|
73
|
+
* when no state is known yet — which is what lets a jump straight to the middle
|
|
74
|
+
* of a file colour correctly without tokenizing anything above it.
|
|
75
|
+
* @param lines - the side's lines, indexed from 0 as the caller's line numbers are.
|
|
37
76
|
* @param lang - the Shiki grammar id, or `undefined` for plain text.
|
|
38
|
-
* @
|
|
77
|
+
* @param from - the first line index to highlight (inclusive, clamped).
|
|
78
|
+
* @param to - one past the last line index to highlight (clamped).
|
|
79
|
+
* @param options - the grammar state to resume from and/or context lines for the grammar.
|
|
80
|
+
* @returns the window's runs and its end state, or `undefined` when the language
|
|
81
|
+
* is unknown, the range is empty, or the window's own text is too large.
|
|
39
82
|
*/
|
|
40
|
-
export declare function
|
|
83
|
+
export declare function highlightWindow(lines: readonly string[], lang: string | undefined, from: number, to: number, options?: HighlightWindowOptions): HighlightWindow | undefined;
|
|
@@ -10,3 +10,11 @@
|
|
|
10
10
|
* @returns the language id, or `undefined` when unknown.
|
|
11
11
|
*/
|
|
12
12
|
export declare function langFromPath(path: string): string | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* The lowercase extension of a file path, without the dot — the key a manual
|
|
15
|
+
* language choice is remembered under. A name with no dot has no suffix, so a
|
|
16
|
+
* per-suffix preference never leaks onto every extension-less file at once.
|
|
17
|
+
* @param path - the file path, any separator style.
|
|
18
|
+
* @returns the suffix, or `undefined` when the name carries none.
|
|
19
|
+
*/
|
|
20
|
+
export declare function suffixOfPath(path: string): string | undefined;
|
|
@@ -130,9 +130,32 @@ export declare const zh: {
|
|
|
130
130
|
'panel.refreshNone': string;
|
|
131
131
|
'panel.refreshUntrackedHint': string;
|
|
132
132
|
'panel.refreshFailed': string;
|
|
133
|
+
'action.addPath': string;
|
|
134
|
+
'panel.addTitle': string;
|
|
135
|
+
'panel.addPathPlaceholder': string;
|
|
136
|
+
'panel.addPathGo': string;
|
|
137
|
+
'panel.addHint': string;
|
|
138
|
+
'panel.addIncludeUnchanged': string;
|
|
139
|
+
'panel.addRoot': string;
|
|
140
|
+
'panel.addTruncated': string;
|
|
141
|
+
'panel.addDone': string;
|
|
142
|
+
'panel.addDoneTruncated': string;
|
|
143
|
+
'panel.addDuplicate': string;
|
|
144
|
+
'panel.addUnchanged': string;
|
|
145
|
+
'panel.addEmpty': string;
|
|
146
|
+
'panel.addNoEntries': string;
|
|
147
|
+
'panel.addMissing': string;
|
|
148
|
+
'panel.addOutside': string;
|
|
149
|
+
'panel.addFailed': string;
|
|
150
|
+
'action.cancel': string;
|
|
151
|
+
'action.collapseRow': string;
|
|
152
|
+
'action.expandRow': string;
|
|
133
153
|
};
|
|
134
154
|
/** Translation keys owned by the pending-edit review namespace. */
|
|
135
155
|
export type DiffApprovalKey = keyof typeof zh;
|
|
156
|
+
/** Locale translator for this plugin's namespace: the panel's and the settings
|
|
157
|
+
* section's `t`, typed against the dictionary keys. */
|
|
158
|
+
export type Translator = (key: DiffApprovalKey, params?: Record<string, unknown>) => string;
|
|
136
159
|
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
137
160
|
interface LocaleNamespaceMap {
|
|
138
161
|
/** Pending-edit review UI copy. */
|
|
@@ -269,4 +292,24 @@ export declare const en: {
|
|
|
269
292
|
'panel.refreshNone': string;
|
|
270
293
|
'panel.refreshUntrackedHint': string;
|
|
271
294
|
'panel.refreshFailed': string;
|
|
295
|
+
'action.addPath': string;
|
|
296
|
+
'panel.addTitle': string;
|
|
297
|
+
'panel.addPathPlaceholder': string;
|
|
298
|
+
'panel.addPathGo': string;
|
|
299
|
+
'panel.addHint': string;
|
|
300
|
+
'panel.addIncludeUnchanged': string;
|
|
301
|
+
'panel.addRoot': string;
|
|
302
|
+
'panel.addTruncated': string;
|
|
303
|
+
'panel.addDone': string;
|
|
304
|
+
'panel.addDoneTruncated': string;
|
|
305
|
+
'panel.addDuplicate': string;
|
|
306
|
+
'panel.addUnchanged': string;
|
|
307
|
+
'panel.addEmpty': string;
|
|
308
|
+
'panel.addNoEntries': string;
|
|
309
|
+
'panel.addMissing': string;
|
|
310
|
+
'panel.addOutside': string;
|
|
311
|
+
'panel.addFailed': string;
|
|
312
|
+
'action.cancel': string;
|
|
313
|
+
'action.collapseRow': string;
|
|
314
|
+
'action.expandRow': string;
|
|
272
315
|
};
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* @module dsh-diff-approval/client/port
|
|
6
6
|
*/
|
|
7
7
|
import type { ClientConnectionRpc, SessionId } from '@deepseek-ai/dsh-client-connection/client';
|
|
8
|
-
import type { DiffApprovalActionValue, DiffApprovalBlockRange, DiffApprovalBulkValue, DiffApprovalListValue, DiffApprovalOpenAction, DiffApprovalOpenValue, DiffApprovalPreviewImageValue, DiffApprovalRefreshValue, VcsImportValue } from '../types.ts';
|
|
8
|
+
import type { DiffApprovalActionValue, DiffApprovalAddValue, DiffApprovalBlockRange, DiffApprovalBrowseValue, DiffApprovalBulkValue, DiffApprovalListValue, DiffApprovalOpenAction, DiffApprovalOpenValue, DiffApprovalPreviewImageValue, DiffApprovalRefreshValue, VcsImportValue } from '../types.ts';
|
|
9
9
|
/** The channel the host half registers and this port calls. */
|
|
10
10
|
export declare const DIFF_APPROVAL_CHANNEL = "/diff-approval";
|
|
11
11
|
/** This package's business verbs over the review channel. */
|
|
@@ -28,6 +28,10 @@ export interface DiffApprovalPort {
|
|
|
28
28
|
importVcs(sessionId: SessionId, includeUntracked: boolean): Promise<VcsImportValue>;
|
|
29
29
|
/** Replace one entry's diff with the file's current local VCS change. */
|
|
30
30
|
refreshVcs(sessionId: SessionId, id: string, includeUntracked: boolean): Promise<DiffApprovalRefreshValue>;
|
|
31
|
+
/** List one workspace directory level (workspace-relative; `''` is the root). */
|
|
32
|
+
browse(sessionId: SessionId, path?: string): Promise<DiffApprovalBrowseValue>;
|
|
33
|
+
/** Add one named path (a file, or a directory's whole subtree) to the list. */
|
|
34
|
+
addPath(sessionId: SessionId, path: string, includeUnchanged: boolean): Promise<DiffApprovalAddValue>;
|
|
31
35
|
/** Open one file with its default application or reveal it in the folder. */
|
|
32
36
|
open(sessionId: SessionId, id: string, action: DiffApprovalOpenAction): Promise<DiffApprovalOpenValue>;
|
|
33
37
|
/** Keep every pending entry of one session in a single host call (one batch). */
|
|
@@ -108,6 +108,22 @@ export declare function setDiffDelColor(value: string): void;
|
|
|
108
108
|
export declare function currentDiffAddColor(): string;
|
|
109
109
|
/** The theme's removed-line base color, for the settings swatch default. */
|
|
110
110
|
export declare function currentDiffDelColor(): string;
|
|
111
|
+
/**
|
|
112
|
+
* The highlight language chosen by hand for one file suffix, remembered so the
|
|
113
|
+
* choice sticks for every file with that suffix. Not a Settings row: it is a
|
|
114
|
+
* per-suffix consequence of using the language picker, so it lives here as
|
|
115
|
+
* storage only.
|
|
116
|
+
* @param suffix - the lowercase extension without the dot (see `suffixOfPath`).
|
|
117
|
+
* @returns the remembered language id, or undefined for "auto".
|
|
118
|
+
*/
|
|
119
|
+
export declare function languageForSuffix(suffix: string): string | undefined;
|
|
120
|
+
/**
|
|
121
|
+
* Remember (or clear) the language chosen for one suffix. A `null` language
|
|
122
|
+
* forgets the suffix, which is what picking "auto" means.
|
|
123
|
+
* @param suffix - the lowercase extension without the dot.
|
|
124
|
+
* @param language - the language id, or null to forget the suffix.
|
|
125
|
+
*/
|
|
126
|
+
export declare function setLanguageForSuffix(suffix: string, language: string | null): void;
|
|
111
127
|
/**
|
|
112
128
|
* Whether the whole-file diff view uses the two-column (side-by-side) layout.
|
|
113
129
|
* Default off (single column): the unified diff. Only an explicit `'1'` enables
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** The panel's injected business face and its observable snapshot. */
|
|
2
2
|
import type { HostObservable } from '@deepseek-ai/dsh-client-ui-slots';
|
|
3
3
|
import type { SessionId } from '@deepseek-ai/dsh-client-connection/client';
|
|
4
|
-
import type { DiffApprovalBlockRange, DiffApprovalOpenAction, DiffApprovalRefreshValue, PendingFileDiff, VcsImportValue } from '../types.ts';
|
|
4
|
+
import type { DiffApprovalAddValue, DiffApprovalBlockRange, DiffApprovalBrowseValue, DiffApprovalOpenAction, DiffApprovalRefreshValue, PendingFileDiff, VcsImportValue } from '../types.ts';
|
|
5
5
|
/** What the panel reads and drives: the pending list plus in-flight entries. */
|
|
6
6
|
export interface PendingDiffSnapshot {
|
|
7
7
|
/** Whether a list read has completed at least once. */
|
|
@@ -54,6 +54,10 @@ export interface PendingPanelFace {
|
|
|
54
54
|
/** Replace one entry's diff with the file's current local VCS change, then
|
|
55
55
|
* refresh the list; resolves to what the scan found. */
|
|
56
56
|
onRefreshVcs: (sessionId: SessionId, id: string, includeUntracked: boolean) => Promise<DiffApprovalRefreshValue>;
|
|
57
|
+
/** List one workspace directory level for the add-path dialog. */
|
|
58
|
+
onBrowse: (sessionId: SessionId, path?: string) => Promise<DiffApprovalBrowseValue>;
|
|
59
|
+
/** Add one named path (a file, or a directory's whole subtree) to the list. */
|
|
60
|
+
onAddPath: (sessionId: SessionId, path: string, includeUnchanged: boolean) => Promise<DiffApprovalAddValue>;
|
|
57
61
|
/** Keep every pending entry of one session in a single host call (bulk). */
|
|
58
62
|
onKeepAll: (sessionId: SessionId) => Promise<void>;
|
|
59
63
|
/** Revert every pending entry of one session in a single host call (bulk). */
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { SessionId } from '@deepseek-ai/dsh-client-connection/client';
|
|
9
9
|
import type { HostObservable } from '@deepseek-ai/dsh-client-ui-slots';
|
|
10
|
-
import type { DiffApprovalBlockRange, DiffApprovalOpenAction, DiffApprovalRefreshValue, VcsImportValue } from '../types.ts';
|
|
10
|
+
import type { DiffApprovalAddValue, DiffApprovalBlockRange, DiffApprovalBrowseValue, DiffApprovalOpenAction, DiffApprovalRefreshValue, VcsImportValue } from '../types.ts';
|
|
11
11
|
import type { PendingDiffSnapshot } from './slots.ts';
|
|
12
12
|
import type { DiffApprovalPort } from './port.ts';
|
|
13
13
|
/** The observable the panel reads and the plugin body drives. */
|
|
@@ -31,6 +31,10 @@ export interface PendingDiffStore extends HostObservable<PendingDiffSnapshot> {
|
|
|
31
31
|
/** Replace one entry's diff with the file's current local VCS change, then
|
|
32
32
|
* refresh; resolves to what the scan found. */
|
|
33
33
|
refreshVcs: (sessionId: SessionId, id: string, includeUntracked: boolean) => Promise<DiffApprovalRefreshValue>;
|
|
34
|
+
/** List one workspace directory level for the add-path dialog. */
|
|
35
|
+
browse: (sessionId: SessionId, path?: string) => Promise<DiffApprovalBrowseValue>;
|
|
36
|
+
/** Add one named path to the list, then refresh; resolves to what the scan did. */
|
|
37
|
+
addPath: (sessionId: SessionId, path: string, includeUnchanged: boolean) => Promise<DiffApprovalAddValue>;
|
|
34
38
|
/** Open one file with its default application or reveal it in the folder. */
|
|
35
39
|
open: (sessionId: SessionId, id: string, action: DiffApprovalOpenAction) => Promise<void>;
|
|
36
40
|
/** Keep every pending entry of one session in a single host call, then refresh. */
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* derivation; the panel owns rendering. The `diff` package is browser-safe.
|
|
6
6
|
* @module dsh-diff-approval/client/whole-file-diff
|
|
7
7
|
*/
|
|
8
|
+
import type { DiffApprovalBlockRange } from '../types.ts';
|
|
8
9
|
/** One rendered body line of the whole-file view. */
|
|
9
10
|
export interface WholeFileDiffRow {
|
|
10
11
|
/** `context` lines are unchanged, `del`/`add` mark the removed/added sides. */
|
|
@@ -25,6 +26,26 @@ export interface WholeFileDiff {
|
|
|
25
26
|
/** Number of added lines. */
|
|
26
27
|
added: number;
|
|
27
28
|
}
|
|
29
|
+
/** One contiguous run of changed rows, treated as a single modification. */
|
|
30
|
+
export interface ChangeBlock {
|
|
31
|
+
start: number;
|
|
32
|
+
end: number;
|
|
33
|
+
}
|
|
34
|
+
/** Split a row list into maximal runs of non-context rows. */
|
|
35
|
+
export declare function changeBlocksOf(diff: WholeFileDiff): ChangeBlock[];
|
|
36
|
+
/**
|
|
37
|
+
* One diff block's old/new line ranges, 1-based inclusive, for block-level
|
|
38
|
+
* keep/revert. A side with no lines (a pure addition or deletion) is empty; its
|
|
39
|
+
* start is that side's insertion point — the line after the surrounding context
|
|
40
|
+
* — so the host can insert there.
|
|
41
|
+
*
|
|
42
|
+
* Shared by the source view and the Markdown preview: both derive blocks from
|
|
43
|
+
* the same rows, so a preview element and a source row name the same target.
|
|
44
|
+
* @param rows - the whole-file diff rows.
|
|
45
|
+
* @param block - the block's row range.
|
|
46
|
+
* @returns the old and new line ranges.
|
|
47
|
+
*/
|
|
48
|
+
export declare function blockRangesOf(rows: readonly WholeFileDiffRow[], block: ChangeBlock): DiffApprovalBlockRange;
|
|
28
49
|
/**
|
|
29
50
|
* Compute the whole-file view between two contents. The context budget is the
|
|
30
51
|
* side lengths, so every hunk covers the file and unchanged lines survive as
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Windowed syntax highlighting for the diff viewer.
|
|
3
|
+
*
|
|
4
|
+
* The code view renders a virtual window of rows, so the viewer only ever needs
|
|
5
|
+
* the colors of the lines near the viewport. Highlighting a whole file to show
|
|
6
|
+
* one screenful is almost all waste — measured on a 3818-line file, the two
|
|
7
|
+
* sides whole-file cost ~945 ms of main-thread time against ~8 ms for one window
|
|
8
|
+
* — so this hook keeps a per-side store of highlighted lines and fills it:
|
|
9
|
+
*
|
|
10
|
+
* 1. **The window first.** Whenever the visible line range changes, the lines
|
|
11
|
+
* that are missing are highlighted (with a margin either side, debounced, so a
|
|
12
|
+
* fast scroll computes the window it lands on instead of every window it
|
|
13
|
+
* passes). A window with no state yet gets `context` lines above it so a
|
|
14
|
+
* construct that started earlier still colours correctly.
|
|
15
|
+
* 2. **Already-highlighted lines are kept.** Scrolling back, or nudging the
|
|
16
|
+
* window by a few rows, finds the lines present and does no work at all.
|
|
17
|
+
* 3. **An idle backfill** walks the file top-down in chunks when the pane reports
|
|
18
|
+
* a real viewport, recording the exact grammar state at each chunk boundary.
|
|
19
|
+
* Windows inside a backfilled region then resume exactly (no context guess),
|
|
20
|
+
* and a random scroll position is coloured before the user gets there.
|
|
21
|
+
*
|
|
22
|
+
* Lines that have never been highlighted are holes in the returned arrays: the
|
|
23
|
+
* viewer renders them plain, which is the honest state of the work done so far.
|
|
24
|
+
* @module dsh-diff-approval/client/windowed-highlight
|
|
25
|
+
*/
|
|
26
|
+
import type { HighlightSides } from './highlight.ts';
|
|
27
|
+
/** One visible window, in source lines (1-based, inclusive). */
|
|
28
|
+
export interface LineRange {
|
|
29
|
+
from: number;
|
|
30
|
+
to: number;
|
|
31
|
+
}
|
|
32
|
+
/** The source lines one view is showing, plus whether that view measured a real
|
|
33
|
+
* viewport. Reported by whichever view is mounted (the single column computes its
|
|
34
|
+
* own window; the split view reports its own, which is the only one that matches
|
|
35
|
+
* its scroll container). */
|
|
36
|
+
export interface VisibleLines {
|
|
37
|
+
oldRange: LineRange | undefined;
|
|
38
|
+
newRange: LineRange | undefined;
|
|
39
|
+
live: boolean;
|
|
40
|
+
}
|
|
41
|
+
/** What the hook needs to know about the file and the pane. */
|
|
42
|
+
export interface WindowedHighlightInput {
|
|
43
|
+
/** Identity of the content and language: a change resets everything. */
|
|
44
|
+
key: unknown;
|
|
45
|
+
/** The old side's lines (index 0 = line 1). */
|
|
46
|
+
oldLines: readonly string[];
|
|
47
|
+
/** The new side's lines (index 0 = line 1). */
|
|
48
|
+
newLines: readonly string[];
|
|
49
|
+
/** The Shiki grammar id, or undefined for plain text. */
|
|
50
|
+
lang: string | undefined;
|
|
51
|
+
/** The visible source lines of the old side, if any are on screen. */
|
|
52
|
+
oldRange: LineRange | undefined;
|
|
53
|
+
/** The visible source lines of the new side, if any are on screen. */
|
|
54
|
+
newRange: LineRange | undefined;
|
|
55
|
+
/** Whether the pane reports a real viewport. The idle backfill only runs with
|
|
56
|
+
* one: without a viewport (a test DOM, a pane that has not measured yet) there
|
|
57
|
+
* is no "idle" to spend and nothing to prefetch for. */
|
|
58
|
+
live: boolean;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Keep syntax highlighting for the pane's visible window, plus (with a live
|
|
62
|
+
* viewport) an idle top-down backfill of the rest.
|
|
63
|
+
* @param input - the file's lines, language, and the visible line ranges.
|
|
64
|
+
* @returns the runs per side, indexed by line - 1, with holes for unhighlighted lines.
|
|
65
|
+
*/
|
|
66
|
+
export declare function useWindowedHighlight(input: WindowedHighlightInput): HighlightSides;
|
package/lib/types/pending.d.ts
CHANGED
|
@@ -74,6 +74,16 @@ export declare class PendingDiffStore {
|
|
|
74
74
|
* @returns whether the store changed.
|
|
75
75
|
*/
|
|
76
76
|
restore(entry: PendingEntry): boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Admit one entry into the list without the change guard {@link fold} applies.
|
|
79
|
+
* A listed entry may carry no diff at all: a path the user added by hand has
|
|
80
|
+
* no local change until one appears, and a fully-resolved file has already
|
|
81
|
+
* folded its diff away. This is the guard-free put {@link restore} performs,
|
|
82
|
+
* named for its other caller.
|
|
83
|
+
* @param entry - the entry to insert or replace by path.
|
|
84
|
+
* @returns whether the store changed.
|
|
85
|
+
*/
|
|
86
|
+
insert(entry: PendingEntry): boolean;
|
|
77
87
|
/**
|
|
78
88
|
* Merge persisted entries into the store, one per path after folding. A live
|
|
79
89
|
* entry wins over a persisted one only when its time is newer (folders are
|
package/lib/types/types.d.ts
CHANGED
|
@@ -149,3 +149,54 @@ export interface DiffApprovalActionValue {
|
|
|
149
149
|
* block (the entry stays listed with no pending diff). */
|
|
150
150
|
resolved?: boolean | undefined;
|
|
151
151
|
}
|
|
152
|
+
/** One row of a browsed directory level. */
|
|
153
|
+
export interface DiffApprovalBrowseEntry {
|
|
154
|
+
/** Base name inside the listed directory. */
|
|
155
|
+
name: string;
|
|
156
|
+
/** What the child is; `other` covers anything neither file nor directory. */
|
|
157
|
+
type: 'file' | 'directory' | 'other';
|
|
158
|
+
/** Absolute host path — the same value `add-path` takes, so the panel can show
|
|
159
|
+
* (and add) exactly what a row names. */
|
|
160
|
+
path: string;
|
|
161
|
+
/** Byte size, present only for a regular file the backend reports. */
|
|
162
|
+
size?: number | undefined;
|
|
163
|
+
}
|
|
164
|
+
/** Value returned by the channel's list-path endpoint: one directory level. */
|
|
165
|
+
export interface DiffApprovalBrowseValue {
|
|
166
|
+
/** The listed directory's absolute path. */
|
|
167
|
+
path: string;
|
|
168
|
+
/** Direct children: directories first, then files, each name-sorted. */
|
|
169
|
+
entries: DiffApprovalBrowseEntry[];
|
|
170
|
+
/** The response hit the entry cap, so children are missing from `entries`. */
|
|
171
|
+
truncated: boolean;
|
|
172
|
+
}
|
|
173
|
+
/** What one hand-added path did. */
|
|
174
|
+
export type DiffApprovalAddOutcome =
|
|
175
|
+
/** At least one entry landed in the list (a directory can add many). */
|
|
176
|
+
'added'
|
|
177
|
+
/** Everything scanned was already listed; nothing changed. */
|
|
178
|
+
| 'duplicate'
|
|
179
|
+
/** The path has no local VCS change and the caller did not ask to add those. */
|
|
180
|
+
| 'unchanged'
|
|
181
|
+
/** A directory scan found nothing to add and nothing was listed already. */
|
|
182
|
+
| 'empty'
|
|
183
|
+
/** The path does not exist (or is neither a file nor a directory). */
|
|
184
|
+
| 'missing'
|
|
185
|
+
/** The path lies outside the session's workspace. */
|
|
186
|
+
| 'outside'
|
|
187
|
+
/** The workspace is not inside a git/svn/p4 checkout. */
|
|
188
|
+
| 'no-vcs'
|
|
189
|
+
/** The scan itself failed; `message` carries the reason. */
|
|
190
|
+
| 'failed';
|
|
191
|
+
/** Value returned by the channel's add-path endpoint. */
|
|
192
|
+
export interface DiffApprovalAddValue {
|
|
193
|
+
outcome: DiffApprovalAddOutcome;
|
|
194
|
+
/** How many entries the add put into the list. */
|
|
195
|
+
added: number;
|
|
196
|
+
/** How many scanned paths were already listed (left untouched). */
|
|
197
|
+
duplicates: number;
|
|
198
|
+
/** Whether the no-change walk hit its file cap, so some files were not added. */
|
|
199
|
+
truncated?: boolean | undefined;
|
|
200
|
+
/** Failure detail for `outcome: 'failed'`. */
|
|
201
|
+
message?: string | undefined;
|
|
202
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-diff-approval",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.1",
|
|
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",
|
|
@@ -67,18 +67,6 @@
|
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
69
|
"@deepseek-ai/cordis": ">=4.0.1",
|
|
70
|
-
"@deepseek-ai/dsh-client-connection": ">=0.1.0-rc.5",
|
|
71
|
-
"@deepseek-ai/dsh-client-locale": ">=0.1.0-rc.5",
|
|
72
|
-
"@deepseek-ai/dsh-client-runtime": ">=0.1.0-rc.5",
|
|
73
|
-
"@deepseek-ai/dsh-client-ui-primitives": ">=0.1.0-rc.5",
|
|
74
|
-
"@deepseek-ai/dsh-client-ui-settings": ">=0.1.0-rc.5",
|
|
75
|
-
"@deepseek-ai/dsh-client-ui-sidebar": ">=0.1.0-rc.5",
|
|
76
|
-
"@deepseek-ai/dsh-client-ui-slots": ">=0.1.0-rc.5",
|
|
77
|
-
"@deepseek-ai/dsh-fs": ">=0.1.0-rc.5",
|
|
78
|
-
"@deepseek-ai/dsh-host-apiproxy": ">=0.1.0-rc.5",
|
|
79
|
-
"@deepseek-ai/dsh-session": ">=0.1.0-rc.5",
|
|
80
|
-
"@deepseek-ai/dsh-tools": ">=0.1.0-rc.5",
|
|
81
|
-
"@deepseek-ai/dsh-workspace": ">=0.1.0-rc.5",
|
|
82
70
|
"react": "^18.2.0"
|
|
83
71
|
},
|
|
84
72
|
"devDependencies": {
|