dsh-diff-approval 0.19.3 → 0.20.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/client.js +2046 -601
- package/lib/index.js +386 -59
- 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 -1
|
@@ -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