dsh-code 1.0.1 → 1.0.2
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.en.md +4 -4
- package/README.md +4 -4
- package/lib/index.mjs +754 -380
- package/lib/types/app.d.ts +2 -13
- package/lib/types/attachments.d.ts +1 -1
- package/lib/types/keyboard.d.ts +31 -0
- package/lib/types/mentions.d.ts +2 -0
- package/lib/types/render/animations.d.ts +27 -11
- package/lib/types/render/editor.d.ts +32 -7
- package/lib/types/render/lines.d.ts +1 -1
- package/package.json +1 -1
- package/src/app.ts +596 -503
- package/src/attachments.ts +7 -0
- package/src/index.ts +1 -1
- package/src/internals.ts +26 -9
- package/src/keyboard.ts +131 -7
- package/src/mentions.ts +6 -1
- package/src/render/animations.ts +64 -17
- package/src/render/editor.ts +125 -25
- package/src/render/lines.ts +16 -2
package/lib/index.mjs
CHANGED
|
@@ -14,12 +14,12 @@ import { PassThrough, Stream } from "node:stream";
|
|
|
14
14
|
import process$1, { cwd, env } from "node:process";
|
|
15
15
|
import { EventEmitter } from "node:events";
|
|
16
16
|
import { Buffer as Buffer$1 } from "node:buffer";
|
|
17
|
+
import { DEFAULT_FILE_SEARCH_EXCLUDED_DIRECTORIES, DEFAULT_FILE_SEARCH_MAX_ENTRIES, DEFAULT_FILE_SEARCH_MAX_RESULTS, WorkspaceFileSearch } from "@deepseek-ai/dsh-file-reference-local";
|
|
18
|
+
import { formatSessionReferenceMention, parseSessionReferenceText } from "@deepseek-ai/dsh-session-reference";
|
|
17
19
|
import { execFile, spawn } from "node:child_process";
|
|
18
20
|
import { credentialKeyId, credentialKeyScope } from "@deepseek-ai/dsh-credentials";
|
|
19
21
|
import { AuthorizationDeclinedError } from "@deepseek-ai/dsh-authorization";
|
|
20
22
|
import { fileURLToPath } from "node:url";
|
|
21
|
-
import { DEFAULT_FILE_SEARCH_EXCLUDED_DIRECTORIES, DEFAULT_FILE_SEARCH_MAX_ENTRIES, DEFAULT_FILE_SEARCH_MAX_RESULTS, WorkspaceFileSearch } from "@deepseek-ai/dsh-file-reference-local";
|
|
22
|
-
import { formatSessionReferenceMention, parseSessionReferenceText } from "@deepseek-ai/dsh-session-reference";
|
|
23
23
|
import { UserQuestionError } from "@deepseek-ai/dsh-user-questions";
|
|
24
24
|
import { isUserInvocable } from "@deepseek-ai/dsh-skill";
|
|
25
25
|
//#region node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js
|
|
@@ -26633,11 +26633,7 @@ function renderMarkdown(text, width, options = {}) {
|
|
|
26633
26633
|
}
|
|
26634
26634
|
//#endregion
|
|
26635
26635
|
//#region src/render/animations.ts
|
|
26636
|
-
/**
|
|
26637
|
-
* The web StateDot "ongoing" chase in terminal form: three cells of the 3×3
|
|
26638
|
-
* ring trail clockwise around the eight outer positions, one braille glyph
|
|
26639
|
-
* per step — 8 frames × 125ms = the web's 1s cycle.
|
|
26640
|
-
*/
|
|
26636
|
+
/** Original terminal StateDot chase frames. */
|
|
26641
26637
|
const BUSY_CHASE_FRAMES = [
|
|
26642
26638
|
"⣾",
|
|
26643
26639
|
"⣽",
|
|
@@ -26648,10 +26644,40 @@ const BUSY_CHASE_FRAMES = [
|
|
|
26648
26644
|
"⣯",
|
|
26649
26645
|
"⣷"
|
|
26650
26646
|
];
|
|
26651
|
-
/** Chase frame for a monotonic tick
|
|
26647
|
+
/** Chase frame for a monotonic tick. */
|
|
26652
26648
|
function busyChaseFrame(tick) {
|
|
26653
26649
|
return BUSY_CHASE_FRAMES[tick % BUSY_CHASE_FRAMES.length] ?? BUSY_CHASE_FRAMES[0];
|
|
26654
26650
|
}
|
|
26651
|
+
/** Codex shimmer timing and geometry. */
|
|
26652
|
+
const DEEP_DIVING_SHIMMER_DURATION_MS = 2e3;
|
|
26653
|
+
const DEEP_DIVING_SPARK_BREATH_DURATION_MS = 2e3;
|
|
26654
|
+
/**
|
|
26655
|
+
* Codex's 2-second shimmer sweep, expressed in terminal ticks. The sweep has
|
|
26656
|
+
* ten virtual columns of padding on either side and a five-column cosine
|
|
26657
|
+
* highlight band, so the text changes gently rather than cycling rapidly.
|
|
26658
|
+
*/
|
|
26659
|
+
function deepDivingShimmerIntensity(index, tick, graphemeCount) {
|
|
26660
|
+
if (graphemeCount <= 0) return 0;
|
|
26661
|
+
const period = graphemeCount + 20;
|
|
26662
|
+
const position = (tick * 33 % DEEP_DIVING_SHIMMER_DURATION_MS + DEEP_DIVING_SHIMMER_DURATION_MS) % DEEP_DIVING_SHIMMER_DURATION_MS / DEEP_DIVING_SHIMMER_DURATION_MS * period;
|
|
26663
|
+
const distance = Math.abs(index + 10 - position);
|
|
26664
|
+
if (distance > 5) return 0;
|
|
26665
|
+
const angle = Math.PI * distance / 5;
|
|
26666
|
+
return .5 * (1 + Math.cos(angle));
|
|
26667
|
+
}
|
|
26668
|
+
/** Blue RGB color for one grapheme in the Codex-style shimmer. */
|
|
26669
|
+
function deepDivingGradientColor(index, tick, graphemeCount, base, highlight) {
|
|
26670
|
+
return blendRgb(highlight, base, deepDivingShimmerIntensity(index, tick, graphemeCount));
|
|
26671
|
+
}
|
|
26672
|
+
/** Smooth breathing intensity for the always-visible Deep diving sparkle. */
|
|
26673
|
+
function deepDivingSparkIntensity(tick) {
|
|
26674
|
+
const phase = (tick * 33 % DEEP_DIVING_SPARK_BREATH_DURATION_MS + DEEP_DIVING_SPARK_BREATH_DURATION_MS) % DEEP_DIVING_SPARK_BREATH_DURATION_MS / DEEP_DIVING_SPARK_BREATH_DURATION_MS;
|
|
26675
|
+
return .2 + .8 * (.5 + .5 * Math.cos(Math.PI * 2 * phase));
|
|
26676
|
+
}
|
|
26677
|
+
/** Blue RGB color for the breathing Deep diving sparkle. */
|
|
26678
|
+
function deepDivingSparkColor(tick, base, highlight) {
|
|
26679
|
+
return blendRgb(highlight, base, deepDivingSparkIntensity(tick));
|
|
26680
|
+
}
|
|
26655
26681
|
/** Caret visibility: half the ticks on, half off (530ms blink). */
|
|
26656
26682
|
function caretVisible(tick) {
|
|
26657
26683
|
return tick % 2 === 0;
|
|
@@ -27041,6 +27067,87 @@ function effortAboveHigh(effort) {
|
|
|
27041
27067
|
return rank !== void 0 && rank > 3;
|
|
27042
27068
|
}
|
|
27043
27069
|
//#endregion
|
|
27070
|
+
//#region src/mentions.ts
|
|
27071
|
+
/** Menu cap on file rows; the service owns ranking and default rows. */
|
|
27072
|
+
const MAX_FILE_ROWS = 20;
|
|
27073
|
+
/** Whether a mention token is already navigating a filesystem path. */
|
|
27074
|
+
function isPathLikeMentionQuery(query) {
|
|
27075
|
+
return /[\\/]/u.test(query);
|
|
27076
|
+
}
|
|
27077
|
+
/**
|
|
27078
|
+
* Create the mention API for one agent's workspace. A missing
|
|
27079
|
+
* `fileReferences` service (with an agent present) or `sessionReferenceResolver`
|
|
27080
|
+
* degrades that half to empty rows; `prepare` passes text through untouched
|
|
27081
|
+
* without references. An undefined agent (a bare launch before any session
|
|
27082
|
+
* exists) runs the official WorkspaceFileSearch over the launch cwd — the
|
|
27083
|
+
* same class the mounted service uses per agent — so `@` file completion
|
|
27084
|
+
* works from the first keystroke; session references wait for the session.
|
|
27085
|
+
*
|
|
27086
|
+
* `candidates` never reaches for `this` — the runner hands it to the input
|
|
27087
|
+
* editor as a detached callback, and a `this`-bound method would throw on
|
|
27088
|
+
* every `@` key.
|
|
27089
|
+
* @param ctx - context carrying the optional `fileReferences` and
|
|
27090
|
+
* `sessionReferenceResolver` services.
|
|
27091
|
+
* @param agent - the session owner; excluded from its own session candidates.
|
|
27092
|
+
* @param cwd - launch working directory; bounds the pre-session search.
|
|
27093
|
+
*/
|
|
27094
|
+
function createMentions(ctx, agent, cwd) {
|
|
27095
|
+
const resolver = ctx.get("sessionReferenceResolver");
|
|
27096
|
+
const fileReferences = ctx.get("fileReferences");
|
|
27097
|
+
const sessionCapable = agent !== void 0 && resolver !== void 0;
|
|
27098
|
+
let preSessionSearch;
|
|
27099
|
+
const preSessionFiles = (query, signal) => {
|
|
27100
|
+
preSessionSearch ??= new WorkspaceFileSearch(cwd, {
|
|
27101
|
+
maxResults: DEFAULT_FILE_SEARCH_MAX_RESULTS,
|
|
27102
|
+
maxEntries: DEFAULT_FILE_SEARCH_MAX_ENTRIES,
|
|
27103
|
+
excludedDirectories: [...DEFAULT_FILE_SEARCH_EXCLUDED_DIRECTORIES]
|
|
27104
|
+
});
|
|
27105
|
+
return preSessionSearch.list(query, signal ?? new AbortController().signal);
|
|
27106
|
+
};
|
|
27107
|
+
return {
|
|
27108
|
+
async candidates(query, signal) {
|
|
27109
|
+
const needle = query.trim();
|
|
27110
|
+
const [files, sessions] = await Promise.all([agent !== void 0 && fileReferences !== void 0 ? fileReferences.list(agent, needle, signal ?? new AbortController().signal).catch(() => []) : agent === void 0 ? preSessionFiles(needle, signal).catch(() => []) : Promise.resolve([]), sessionCapable && needle !== "" && !isPathLikeMentionQuery(needle) && agent !== void 0 ? resolver.listCandidates(agent, needle, 10, signal).catch(() => []) : Promise.resolve([])]);
|
|
27111
|
+
const fileRows = files.slice(0, MAX_FILE_ROWS).map((candidate) => ({
|
|
27112
|
+
label: candidate.path,
|
|
27113
|
+
description: candidate.kind === "directory" ? "Folder" : "File",
|
|
27114
|
+
kind: candidate.kind,
|
|
27115
|
+
...candidate.kind === "file" ? { path: isAbsolute(candidate.path) ? candidate.path : resolve(cwd, candidate.path) } : {}
|
|
27116
|
+
}));
|
|
27117
|
+
const sessionRows = sessions.map((candidate) => ({
|
|
27118
|
+
label: formatSessionReferenceMention(candidate),
|
|
27119
|
+
description: `Session · ${candidate.cwd ?? "(no cwd)"}`,
|
|
27120
|
+
kind: "session"
|
|
27121
|
+
}));
|
|
27122
|
+
return [...fileRows, ...sessionRows];
|
|
27123
|
+
},
|
|
27124
|
+
parse(text) {
|
|
27125
|
+
return parseSessionReferenceText(text);
|
|
27126
|
+
},
|
|
27127
|
+
async prepare(parsed, signal) {
|
|
27128
|
+
if (parsed.references.length === 0 || resolver === void 0 || agent === void 0) return {
|
|
27129
|
+
text: parsed.text,
|
|
27130
|
+
references: parsed.references
|
|
27131
|
+
};
|
|
27132
|
+
const prepared = await resolver.prepare(agent, [{
|
|
27133
|
+
type: "text",
|
|
27134
|
+
text: parsed.text
|
|
27135
|
+
}], parsed.references, signal);
|
|
27136
|
+
return {
|
|
27137
|
+
text: prepared.content.filter((block) => block.type === "text").map((block) => block.text).join(""),
|
|
27138
|
+
references: parsed.references,
|
|
27139
|
+
additionalContext: prepared.additionalContext
|
|
27140
|
+
};
|
|
27141
|
+
},
|
|
27142
|
+
sessionMention(candidate) {
|
|
27143
|
+
return formatSessionReferenceMention({
|
|
27144
|
+
sessionId: candidate.sessionId,
|
|
27145
|
+
label: candidate.label
|
|
27146
|
+
});
|
|
27147
|
+
}
|
|
27148
|
+
};
|
|
27149
|
+
}
|
|
27150
|
+
//#endregion
|
|
27044
27151
|
//#region src/session-directory.ts
|
|
27045
27152
|
/** Lightweight session-directory projection for the /resume picker. */
|
|
27046
27153
|
/** Case-insensitive filesystems (Windows, macOS) compare paths by lowercased form. */
|
|
@@ -27970,13 +28077,20 @@ function toolDetailLines(detail, columns) {
|
|
|
27970
28077
|
default: return detail;
|
|
27971
28078
|
}
|
|
27972
28079
|
}
|
|
28080
|
+
/** Default compact tool-card window used while the Ctrl+R fold is closed. */
|
|
28081
|
+
const DEFAULT_TOOL_ROWS = 3;
|
|
28082
|
+
/** Keep the invocation visible while making hidden tool output discoverable. */
|
|
28083
|
+
function compactToolLines(lines, columns) {
|
|
28084
|
+
if (lines.length <= DEFAULT_TOOL_ROWS) return lines;
|
|
28085
|
+
return [...lines.slice(0, 2), ...textLines(" … output hidden · Ctrl+R", columns, "dim").slice(0, 1)];
|
|
28086
|
+
}
|
|
27973
28087
|
/**
|
|
27974
28088
|
* Convert one durable transcript entry to its complete scrollable row model.
|
|
27975
28089
|
* The source entry stays intact; only the caller's visible slice is rendered.
|
|
27976
28090
|
* Wrapped continuations keep a hanging indent aligned under each row's
|
|
27977
28091
|
* content (Codex history-cell alignment) instead of resetting to column 0.
|
|
27978
28092
|
*/
|
|
27979
|
-
function transcriptEntryLines(entry, columns, showReasoning = true, reasoningToggleHint = true) {
|
|
28093
|
+
function transcriptEntryLines(entry, columns, showReasoning = true, reasoningToggleHint = true, showToolDetails = showReasoning) {
|
|
27980
28094
|
const width = Math.max(1, Math.floor(columns));
|
|
27981
28095
|
switch (entry.kind) {
|
|
27982
28096
|
case "user": return entry.notice ? hangingStyledLines([lineSegment(promptDisplayText(entry), "dim")], width, "⤷ ", "dim", " ", "dim") : hangingStyledLines([lineSegment(promptDisplayText(entry), "plain")], width, "❯ ", "brand", " ", "plain");
|
|
@@ -27998,7 +28112,7 @@ function transcriptEntryLines(entry, columns, showReasoning = true, reasoningTog
|
|
|
27998
28112
|
const mark = entry.state === "running" ? "●" : entry.state === "error" ? "⨯" : "⏺";
|
|
27999
28113
|
const markStyle = entry.state === "running" ? "brand" : entry.state === "error" ? "error" : "success";
|
|
28000
28114
|
const summaryStyle = entry.state === "error" ? "error" : "dim";
|
|
28001
|
-
|
|
28115
|
+
const lines = [
|
|
28002
28116
|
...hangingStyledLines([
|
|
28003
28117
|
lineSegment(`[${entry.ordinal}] `, "dim"),
|
|
28004
28118
|
lineSegment(entry.name, "brand"),
|
|
@@ -28008,6 +28122,7 @@ function transcriptEntryLines(entry, columns, showReasoning = true, reasoningTog
|
|
|
28008
28122
|
...entry.summary === "" ? [] : hangingTextLines(entry.state === "error" ? `call ${entry.ordinal}: ${entry.summary}` : entry.summary, width, " ⎿ ", summaryStyle, " ", summaryStyle),
|
|
28009
28123
|
...entry.detail === void 0 ? [] : toolDetailLines(entry.detail, width)
|
|
28010
28124
|
];
|
|
28125
|
+
return showToolDetails ? lines : compactToolLines(lines, width);
|
|
28011
28126
|
}
|
|
28012
28127
|
case "command": {
|
|
28013
28128
|
const mark = entry.state === "running" ? "●" : entry.state === "error" ? "⨯" : "⏺";
|
|
@@ -28025,7 +28140,7 @@ function transcriptEntryLines(entry, columns, showReasoning = true, reasoningTog
|
|
|
28025
28140
|
}
|
|
28026
28141
|
/** Settled-history variant carrying the Ctrl+R reasoning fold. */
|
|
28027
28142
|
function settledEntryLines(entry, columns, showReasoning) {
|
|
28028
|
-
return transcriptEntryLines(entry, columns, showReasoning, false);
|
|
28143
|
+
return transcriptEntryLines(entry, columns, showReasoning, false, showReasoning);
|
|
28029
28144
|
}
|
|
28030
28145
|
//#endregion
|
|
28031
28146
|
//#region src/render/editor.ts
|
|
@@ -28039,9 +28154,8 @@ function settledEntryLines(entry, columns, showReasoning) {
|
|
|
28039
28154
|
* grapheme boundaries) so the React state stays two primitives
|
|
28040
28155
|
* (value, cursor) and every operation here stays pure and testable.
|
|
28041
28156
|
*
|
|
28042
|
-
* Word motion
|
|
28043
|
-
*
|
|
28044
|
-
* single word (two hanzi are one Alt+B step, not two).
|
|
28157
|
+
* Word motion follows Codex's piece semantics: whitespace separates runs,
|
|
28158
|
+
* punctuation runs stay atomic, and each Han grapheme is its own boundary.
|
|
28045
28159
|
*
|
|
28046
28160
|
* @module @deepseek-ai/dsh-code/render/editor
|
|
28047
28161
|
*/
|
|
@@ -28194,6 +28308,28 @@ function editorModel(value, columns) {
|
|
|
28194
28308
|
length: value.length
|
|
28195
28309
|
};
|
|
28196
28310
|
}
|
|
28311
|
+
/** Split one row around the authoritative caret; every other row stays whole. */
|
|
28312
|
+
function editorRowParts(row, rowIndex, caretRow, cursor, caretEnabled = true) {
|
|
28313
|
+
if (!caretEnabled || rowIndex !== caretRow) return {
|
|
28314
|
+
before: "",
|
|
28315
|
+
caret: "",
|
|
28316
|
+
after: row.text,
|
|
28317
|
+
hasCaret: false
|
|
28318
|
+
};
|
|
28319
|
+
const at = row.offsets.indexOf(cursor);
|
|
28320
|
+
if (at < 0) return {
|
|
28321
|
+
before: "",
|
|
28322
|
+
caret: "",
|
|
28323
|
+
after: row.text,
|
|
28324
|
+
hasCaret: false
|
|
28325
|
+
};
|
|
28326
|
+
return {
|
|
28327
|
+
before: at > 0 ? row.text.slice(0, row.cuts[at]) : "",
|
|
28328
|
+
caret: at < row.cuts.length - 1 ? row.text.slice(row.cuts[at], row.cuts[at + 1]) : " ",
|
|
28329
|
+
after: at < row.cuts.length - 1 ? row.text.slice(row.cuts[at + 1]) : "",
|
|
28330
|
+
hasCaret: true
|
|
28331
|
+
};
|
|
28332
|
+
}
|
|
28197
28333
|
/** Map a cursor offset to its caret site on the wrapped rows. */
|
|
28198
28334
|
function caretSite(model, offset) {
|
|
28199
28335
|
const target = Math.max(0, Math.min(model.length, offset));
|
|
@@ -28218,7 +28354,9 @@ function caretSite(model, offset) {
|
|
|
28218
28354
|
*/
|
|
28219
28355
|
function moveCursorVertically(model, offset, preferredColumn, delta) {
|
|
28220
28356
|
const target = caretSite(model, offset).row + delta;
|
|
28221
|
-
if (
|
|
28357
|
+
if (delta === 0) return offset;
|
|
28358
|
+
if (target < 0) return 0;
|
|
28359
|
+
if (target >= model.rows.length) return model.length;
|
|
28222
28360
|
const row = model.rows[target];
|
|
28223
28361
|
const wanted = Math.max(0, Math.min(preferredColumn, row.columns[row.columns.length - 1]));
|
|
28224
28362
|
let best = 0;
|
|
@@ -28238,24 +28376,28 @@ function lineBounds(value, offset) {
|
|
|
28238
28376
|
}
|
|
28239
28377
|
/** Codex WORD_SEPARATORS: punctuation runs are their own word pieces. */
|
|
28240
28378
|
const WORD_SEPARATORS = /* @__PURE__ */ new Set("`~!@#$%^&*()-=+[{]}\\|;:'\",.<>/?");
|
|
28379
|
+
const HAN_GRAPHEME = /^\p{Script=Han}(?:\p{Mark}|\uFE0F)*$/u;
|
|
28380
|
+
const UNICODE_PUNCTUATION = /^\p{P}+$/u;
|
|
28241
28381
|
function classifyGrapheme(text) {
|
|
28242
28382
|
if (/^\s$/u.test(text)) return "space";
|
|
28243
|
-
return WORD_SEPARATORS.has(text) ? "punct" : "word";
|
|
28383
|
+
return WORD_SEPARATORS.has(text) || UNICODE_PUNCTUATION.test(text) ? "punct" : "word";
|
|
28244
28384
|
}
|
|
28245
|
-
/** Maximal same-class runs
|
|
28385
|
+
/** Maximal same-class runs; Han graphemes deliberately stay one run each. */
|
|
28246
28386
|
function pieceRuns(value) {
|
|
28247
28387
|
const runs = [];
|
|
28248
28388
|
let current;
|
|
28249
28389
|
for (const span of splitGraphemes(value)) {
|
|
28250
28390
|
const klass = span.text === "\n" ? "space" : classifyGrapheme(span.text);
|
|
28251
|
-
|
|
28391
|
+
const atomic = klass === "word" && HAN_GRAPHEME.test(span.text);
|
|
28392
|
+
if (current !== void 0 && current.class === klass && !current.atomic && !atomic) {
|
|
28252
28393
|
current.end = span.end;
|
|
28253
28394
|
continue;
|
|
28254
28395
|
}
|
|
28255
28396
|
current = {
|
|
28256
28397
|
start: span.start,
|
|
28257
28398
|
end: span.end,
|
|
28258
|
-
class: klass
|
|
28399
|
+
class: klass,
|
|
28400
|
+
atomic
|
|
28259
28401
|
};
|
|
28260
28402
|
runs.push(current);
|
|
28261
28403
|
}
|
|
@@ -28266,10 +28408,9 @@ function pieceRuns(value) {
|
|
|
28266
28408
|
* START of the trailing non-space piece (extending over separator pieces).
|
|
28267
28409
|
*/
|
|
28268
28410
|
function moveWordLeft(value, offset) {
|
|
28269
|
-
const cursor =
|
|
28411
|
+
const cursor = clampCursor(value, offset);
|
|
28270
28412
|
const runs = pieceRuns(value);
|
|
28271
|
-
let index = runs.
|
|
28272
|
-
while (index >= 0 && runs[index].end > cursor) index -= 1;
|
|
28413
|
+
let index = runs.findLastIndex((run) => run.start < cursor);
|
|
28273
28414
|
if (index < 0) return 0;
|
|
28274
28415
|
if (runs[index].class === "space") {
|
|
28275
28416
|
index -= 1;
|
|
@@ -28284,10 +28425,9 @@ function moveWordLeft(value, offset) {
|
|
|
28284
28425
|
* the leading non-space piece (extending over separator pieces).
|
|
28285
28426
|
*/
|
|
28286
28427
|
function moveWordRight(value, offset) {
|
|
28287
|
-
const cursor =
|
|
28428
|
+
const cursor = clampCursor(value, offset);
|
|
28288
28429
|
const runs = pieceRuns(value);
|
|
28289
|
-
let index =
|
|
28290
|
-
while (index < runs.length && runs[index].start < cursor) index += 1;
|
|
28430
|
+
let index = runs.findIndex((run) => run.end > cursor);
|
|
28291
28431
|
if (index >= runs.length) return value.length;
|
|
28292
28432
|
if (runs[index].class === "space") {
|
|
28293
28433
|
index += 1;
|
|
@@ -28376,6 +28516,62 @@ function insertText(value, cursor, text) {
|
|
|
28376
28516
|
killed: void 0
|
|
28377
28517
|
};
|
|
28378
28518
|
}
|
|
28519
|
+
/** Ctrl+A: current logical line start, then the previous line start on repeat. */
|
|
28520
|
+
function moveToLineStart(value, cursor, crossOnRepeat) {
|
|
28521
|
+
const site = clampCursor(value, cursor);
|
|
28522
|
+
const bounds = lineBounds(value, site);
|
|
28523
|
+
if (!crossOnRepeat || site !== bounds.start || bounds.start === 0) return bounds.start;
|
|
28524
|
+
return lineBounds(value, bounds.start - 1).start;
|
|
28525
|
+
}
|
|
28526
|
+
/** Ctrl+E: current logical line end, then the next line end on repeat. */
|
|
28527
|
+
function moveToLineEnd(value, cursor, crossOnRepeat) {
|
|
28528
|
+
const site = clampCursor(value, cursor);
|
|
28529
|
+
const bounds = lineBounds(value, site);
|
|
28530
|
+
if (!crossOnRepeat || site !== bounds.end || bounds.end >= value.length) return bounds.end;
|
|
28531
|
+
return lineBounds(value, bounds.end + 1).end;
|
|
28532
|
+
}
|
|
28533
|
+
/**
|
|
28534
|
+
* Remap a captured range when all intervening edits are wholly before or
|
|
28535
|
+
* wholly after it. An edit overlapping either boundary invalidates the
|
|
28536
|
+
* anchor instead of guessing and inserting content at a surprising place.
|
|
28537
|
+
*/
|
|
28538
|
+
function remapStableRange(original, current, range) {
|
|
28539
|
+
const start = Math.max(0, Math.min(original.length, range.start));
|
|
28540
|
+
const end = Math.max(start, Math.min(original.length, range.end));
|
|
28541
|
+
if (original === current) return {
|
|
28542
|
+
start,
|
|
28543
|
+
end
|
|
28544
|
+
};
|
|
28545
|
+
let prefix = 0;
|
|
28546
|
+
const shared = Math.min(original.length, current.length);
|
|
28547
|
+
while (prefix < shared && original[prefix] === current[prefix]) prefix += 1;
|
|
28548
|
+
let suffix = 0;
|
|
28549
|
+
while (suffix < original.length - prefix && suffix < current.length - prefix && original[original.length - 1 - suffix] === current[current.length - 1 - suffix]) suffix += 1;
|
|
28550
|
+
const oldChangedEnd = original.length - suffix;
|
|
28551
|
+
if (prefix >= end) return {
|
|
28552
|
+
start,
|
|
28553
|
+
end
|
|
28554
|
+
};
|
|
28555
|
+
if (oldChangedEnd <= start) {
|
|
28556
|
+
const delta = current.length - original.length;
|
|
28557
|
+
return {
|
|
28558
|
+
start: start + delta,
|
|
28559
|
+
end: end + delta
|
|
28560
|
+
};
|
|
28561
|
+
}
|
|
28562
|
+
}
|
|
28563
|
+
/** Replace a current range while preserving a cursor moved after capture. */
|
|
28564
|
+
function replaceRangePreservingCursor(value, cursor, range, replacement) {
|
|
28565
|
+
const start = Math.max(0, Math.min(value.length, range.start));
|
|
28566
|
+
const end = Math.max(start, Math.min(value.length, range.end));
|
|
28567
|
+
const site = clampCursor(value, cursor);
|
|
28568
|
+
const nextCursor = site <= start ? site : site >= end ? site + replacement.length - (end - start) : start + replacement.length;
|
|
28569
|
+
return {
|
|
28570
|
+
value: value.slice(0, start) + replacement + value.slice(end),
|
|
28571
|
+
cursor: nextCursor,
|
|
28572
|
+
killed: void 0
|
|
28573
|
+
};
|
|
28574
|
+
}
|
|
28379
28575
|
/**
|
|
28380
28576
|
* Composer editor row budget: the editor itself never grows past this many
|
|
28381
28577
|
* physical rows; deeper drafts scroll internally to keep the caret visible.
|
|
@@ -28385,14 +28581,73 @@ function composerMaxRows(terminalRows) {
|
|
|
28385
28581
|
return Math.max(1, Math.min(6, Math.floor((Math.max(1, terminalRows) - 10) / 3)));
|
|
28386
28582
|
}
|
|
28387
28583
|
/**
|
|
28388
|
-
*
|
|
28389
|
-
*
|
|
28390
|
-
*
|
|
28584
|
+
* History navigation starts with Up on an empty draft, or after visual
|
|
28585
|
+
* movement has reached the directional text edge of an unchanged recalled
|
|
28586
|
+
* entry. Every other position remains under textarea movement.
|
|
28391
28587
|
*/
|
|
28392
|
-
function shouldRecallNavigate(value, cursor, lastRecalled) {
|
|
28393
|
-
if (value === "") return
|
|
28394
|
-
if (
|
|
28395
|
-
return
|
|
28588
|
+
function shouldRecallNavigate(value, cursor, lastRecalled, direction) {
|
|
28589
|
+
if (value === "") return direction < 0;
|
|
28590
|
+
if (lastRecalled !== value) return false;
|
|
28591
|
+
return direction < 0 ? cursor === 0 : cursor === value.length;
|
|
28592
|
+
}
|
|
28593
|
+
//#endregion
|
|
28594
|
+
//#region src/keyboard.ts
|
|
28595
|
+
/**
|
|
28596
|
+
* Keyboard enhancement protocol (Codex `keyboard_modes` parity) and the
|
|
28597
|
+
* kitty CSI-u normalization layer.
|
|
28598
|
+
*
|
|
28599
|
+
* The TUI pushes the kitty keyboard protocol with DISAMBIGUATE_ESCAPE_CODES
|
|
28600
|
+
* and REPORT_ALTERNATE_KEYS (flags 1|4 = `\x1b[>5u`). Event types are
|
|
28601
|
+
* deliberately NOT requested: Ink 5's parser cannot decode the
|
|
28602
|
+
* `:event-type` suffix, and repeat/release reporting buys this surface
|
|
28603
|
+
* nothing.
|
|
28604
|
+
*
|
|
28605
|
+
* Ink 5 also cannot parse most CSI-u forms at all — they fall through its
|
|
28606
|
+
* regex as unnamed sequences and get INSERTED AS DRAFT TEXT. The composer's
|
|
28607
|
+
* stdin read patch therefore rewrites every CSI-u form it can decode back
|
|
28608
|
+
* to the legacy byte or canonical sequence the existing key handling
|
|
28609
|
+
* already understands, before Ink ever parses the chunk.
|
|
28610
|
+
* @module @deepseek-ai/dsh-code/keyboard
|
|
28611
|
+
*/
|
|
28612
|
+
/** Push keyboard enhancement (modifyOtherKeys off, kitty flags 1|4). */
|
|
28613
|
+
const KEYBOARD_ENHANCE_ENABLE = "\x1B[>4;0m\x1B[>5u";
|
|
28614
|
+
/** Pop the enhancement stack and reset modifyOtherKeys (exit path). */
|
|
28615
|
+
const KEYBOARD_ENHANCE_DISABLE = "\x1B[<u\x1B[>4;0m";
|
|
28616
|
+
const DSH_ENABLE_KEYBOARD_ENHANCEMENT = "DSH_ENABLE_KEYBOARD_ENHANCEMENT";
|
|
28617
|
+
function parseBooleanEnv(value) {
|
|
28618
|
+
if (value === void 0) return void 0;
|
|
28619
|
+
const normalized = value.trim().toLowerCase();
|
|
28620
|
+
if (normalized === "1" || normalized === "true" || normalized === "yes") return true;
|
|
28621
|
+
if (normalized === "0" || normalized === "false" || normalized === "no") return false;
|
|
28622
|
+
}
|
|
28623
|
+
/** True when the process is running inside the VS Code integrated terminal. */
|
|
28624
|
+
function isVsCodeTerminalEnv(env = process.env) {
|
|
28625
|
+
return env.TERM_PROGRAM?.trim().toLowerCase() === "vscode" || env.VSCODE_INJECTION === "1";
|
|
28626
|
+
}
|
|
28627
|
+
/** Whether to push Kitty keyboard enhancement for the current terminal. */
|
|
28628
|
+
function shouldEnableKeyboardEnhancement(env = process.env) {
|
|
28629
|
+
if (parseBooleanEnv(env["DSH_DISABLE_KEYBOARD_ENHANCEMENT"]) === true) return false;
|
|
28630
|
+
const explicitEnable = parseBooleanEnv(env[DSH_ENABLE_KEYBOARD_ENHANCEMENT]);
|
|
28631
|
+
if (explicitEnable !== void 0) return explicitEnable;
|
|
28632
|
+
return !isVsCodeTerminalEnv(env);
|
|
28633
|
+
}
|
|
28634
|
+
/** Enable bracketed paste reporting. */
|
|
28635
|
+
const BRACKETED_PASTE_ENABLE = "\x1B[?2004h";
|
|
28636
|
+
/** Disable bracketed paste reporting. */
|
|
28637
|
+
const BRACKETED_PASTE_DISABLE = "\x1B[?2004l";
|
|
28638
|
+
/** Enable terminal focus-in/focus-out reporting (xterm focus protocol). */
|
|
28639
|
+
const TERMINAL_FOCUS_REPORT_ENABLE = "\x1B[?1004h";
|
|
28640
|
+
/** Disable terminal focus-in/focus-out reporting. */
|
|
28641
|
+
const TERMINAL_FOCUS_REPORT_DISABLE = "\x1B[?1004l";
|
|
28642
|
+
/**
|
|
28643
|
+
* Remove xterm focus reports from one input chunk and update the caller's
|
|
28644
|
+
* focus state. Focus reports are terminal protocol, not composer text.
|
|
28645
|
+
*/
|
|
28646
|
+
function stripTerminalFocusEvents(chunk, onFocus) {
|
|
28647
|
+
return chunk.replace(/\x1b\[(I|O)/gu, (_whole, event) => {
|
|
28648
|
+
onFocus(event === "I");
|
|
28649
|
+
return "";
|
|
28650
|
+
});
|
|
28396
28651
|
}
|
|
28397
28652
|
/** Bracketed paste markers as Ink delivers them (it strips the leading ESC). */
|
|
28398
28653
|
const PASTE_START_MARKER = "[200~";
|
|
@@ -28464,6 +28719,98 @@ function normalizeKeyboardChunk(chunk) {
|
|
|
28464
28719
|
}) ?? whole;
|
|
28465
28720
|
});
|
|
28466
28721
|
}
|
|
28722
|
+
const HOME_SEQUENCES = [
|
|
28723
|
+
"\x1B[H",
|
|
28724
|
+
"\x1B[1~",
|
|
28725
|
+
"\x1B[7~",
|
|
28726
|
+
"\x1BOH"
|
|
28727
|
+
];
|
|
28728
|
+
const END_SEQUENCES = [
|
|
28729
|
+
"\x1B[F",
|
|
28730
|
+
"\x1B[4~",
|
|
28731
|
+
"\x1B[8~",
|
|
28732
|
+
"\x1BOF"
|
|
28733
|
+
];
|
|
28734
|
+
/** Parse one CSI functional-key sequence at `offset`. */
|
|
28735
|
+
function functionalToken(chunk, offset) {
|
|
28736
|
+
const tail = chunk.slice(offset);
|
|
28737
|
+
for (const sequence of HOME_SEQUENCES) if (tail.startsWith(sequence)) return {
|
|
28738
|
+
token: { kind: "home" },
|
|
28739
|
+
length: sequence.length
|
|
28740
|
+
};
|
|
28741
|
+
for (const sequence of END_SEQUENCES) if (tail.startsWith(sequence)) return {
|
|
28742
|
+
token: { kind: "end" },
|
|
28743
|
+
length: sequence.length
|
|
28744
|
+
};
|
|
28745
|
+
const modifiedHome = /^\x1b\[1;(\d+)H/u.exec(tail);
|
|
28746
|
+
if (modifiedHome !== null) return {
|
|
28747
|
+
token: { kind: "home" },
|
|
28748
|
+
length: modifiedHome[0].length
|
|
28749
|
+
};
|
|
28750
|
+
const modifiedEnd = /^\x1b\[1;(\d+)F/u.exec(tail);
|
|
28751
|
+
if (modifiedEnd !== null) return {
|
|
28752
|
+
token: { kind: "end" },
|
|
28753
|
+
length: modifiedEnd[0].length
|
|
28754
|
+
};
|
|
28755
|
+
const modifiedDelete = /^\x1b\[3(?:;(\d+))?~/u.exec(tail);
|
|
28756
|
+
if (modifiedDelete !== null) {
|
|
28757
|
+
const modifiers = Number.parseInt(modifiedDelete[1] ?? "1", 10) - 1;
|
|
28758
|
+
return {
|
|
28759
|
+
token: { kind: (modifiers & 2) !== 0 || (modifiers & 4) !== 0 ? "delete-word-forward" : "delete-forward" },
|
|
28760
|
+
length: modifiedDelete[0].length
|
|
28761
|
+
};
|
|
28762
|
+
}
|
|
28763
|
+
}
|
|
28764
|
+
/**
|
|
28765
|
+
* Tokenize a stdin chunk containing at least one editor-only key. Ink calls
|
|
28766
|
+
* `useInput` once for a pasted/batched chunk, so preserving each action here
|
|
28767
|
+
* prevents repeated Backspace/Home/End/Delete presses from collapsing into
|
|
28768
|
+
* one blurred key event. Unknown escape sequences return undefined and stay
|
|
28769
|
+
* under Ink's ownership.
|
|
28770
|
+
*/
|
|
28771
|
+
function tokenizeRawEditorChunk(chunk) {
|
|
28772
|
+
const tokens = [];
|
|
28773
|
+
let text = "";
|
|
28774
|
+
let special = false;
|
|
28775
|
+
const flushText = () => {
|
|
28776
|
+
if (text === "") return;
|
|
28777
|
+
tokens.push({
|
|
28778
|
+
kind: "text",
|
|
28779
|
+
text
|
|
28780
|
+
});
|
|
28781
|
+
text = "";
|
|
28782
|
+
};
|
|
28783
|
+
for (let offset = 0; offset < chunk.length;) {
|
|
28784
|
+
if (chunk.startsWith("\x1B", offset) || chunk.startsWith("\x1B\b", offset)) {
|
|
28785
|
+
flushText();
|
|
28786
|
+
tokens.push({ kind: "delete-word-backward" });
|
|
28787
|
+
special = true;
|
|
28788
|
+
offset += 2;
|
|
28789
|
+
continue;
|
|
28790
|
+
}
|
|
28791
|
+
const functional = functionalToken(chunk, offset);
|
|
28792
|
+
if (functional !== void 0) {
|
|
28793
|
+
flushText();
|
|
28794
|
+
tokens.push(functional.token);
|
|
28795
|
+
special = true;
|
|
28796
|
+
offset += functional.length;
|
|
28797
|
+
continue;
|
|
28798
|
+
}
|
|
28799
|
+
const char = chunk[offset];
|
|
28800
|
+
if (char === "" || char === "\b") {
|
|
28801
|
+
flushText();
|
|
28802
|
+
tokens.push({ kind: "delete-backward" });
|
|
28803
|
+
special = true;
|
|
28804
|
+
offset += 1;
|
|
28805
|
+
continue;
|
|
28806
|
+
}
|
|
28807
|
+
if (char === "\x1B") return void 0;
|
|
28808
|
+
text += char;
|
|
28809
|
+
offset += 1;
|
|
28810
|
+
}
|
|
28811
|
+
flushText();
|
|
28812
|
+
return special ? tokens : void 0;
|
|
28813
|
+
}
|
|
28467
28814
|
//#endregion
|
|
28468
28815
|
//#region src/kernel-panels.ts
|
|
28469
28816
|
/** Bounded, composer-safe panels for preset, session, and plugin kernel views. */
|
|
@@ -29923,11 +30270,15 @@ async function inspectImagePaths(paths, attachments, cwd = process.cwd()) {
|
|
|
29923
30270
|
return inspected;
|
|
29924
30271
|
}
|
|
29925
30272
|
/** Read, validate, and persist an ordered image path list as model content blocks. */
|
|
29926
|
-
async function saveImagePaths(paths, attachments) {
|
|
30273
|
+
async function saveImagePaths(paths, attachments, signal) {
|
|
29927
30274
|
if (paths.length === 0) return [];
|
|
29928
30275
|
if (attachments === void 0) throw new Error("image attachments are unavailable in this profile");
|
|
30276
|
+
const checkCancelled = () => {
|
|
30277
|
+
if (signal?.aborted === true) throw new Error("image submission cancelled");
|
|
30278
|
+
};
|
|
29929
30279
|
const inputs = [];
|
|
29930
30280
|
for (const path of paths) {
|
|
30281
|
+
checkCancelled();
|
|
29931
30282
|
let data;
|
|
29932
30283
|
try {
|
|
29933
30284
|
data = await readFile(path);
|
|
@@ -29942,7 +30293,10 @@ async function saveImagePaths(paths, attachments) {
|
|
|
29942
30293
|
name: basename(path)
|
|
29943
30294
|
});
|
|
29944
30295
|
}
|
|
29945
|
-
|
|
30296
|
+
checkCancelled();
|
|
30297
|
+
const refs = await attachments.saveImages(inputs);
|
|
30298
|
+
checkCancelled();
|
|
30299
|
+
return refs.map((attachment) => ({
|
|
29946
30300
|
type: "image",
|
|
29947
30301
|
attachment
|
|
29948
30302
|
}));
|
|
@@ -30098,14 +30452,15 @@ function padColumns(text, width) {
|
|
|
30098
30452
|
return clipped + " ".repeat(Math.max(0, width - visibleColumns(clipped)));
|
|
30099
30453
|
}
|
|
30100
30454
|
/** Interval-driven frame counter for one self-contained animated leaf. */
|
|
30101
|
-
function useFrames(intervalMs) {
|
|
30455
|
+
function useFrames(intervalMs, active = true) {
|
|
30102
30456
|
const [tick, setTick] = (0, import_react.useState)(0);
|
|
30103
30457
|
(0, import_react.useEffect)(() => {
|
|
30458
|
+
if (!active) return;
|
|
30104
30459
|
const id = setInterval(() => setTick((current) => current + 1), intervalMs);
|
|
30105
30460
|
return () => {
|
|
30106
30461
|
clearInterval(id);
|
|
30107
30462
|
};
|
|
30108
|
-
}, [intervalMs]);
|
|
30463
|
+
}, [active, intervalMs]);
|
|
30109
30464
|
return tick;
|
|
30110
30465
|
}
|
|
30111
30466
|
/**
|
|
@@ -30122,12 +30477,7 @@ function useStableInput(handler, active) {
|
|
|
30122
30477
|
}, []);
|
|
30123
30478
|
useInput(stableHandler, { isActive: active });
|
|
30124
30479
|
}
|
|
30125
|
-
/**
|
|
30126
|
-
* The web StateDot "ongoing" chase in terminal form: three cells of the 3×3
|
|
30127
|
-
* ring trail clockwise around the eight outer positions (8 frames × 125ms =
|
|
30128
|
-
* the web's 1s cycle). Replaces the plain busy ellipsis as the composer's
|
|
30129
|
-
* prompt marker and leads the Deep-diving line.
|
|
30130
|
-
*/
|
|
30480
|
+
/** The original web StateDot chase used by the busy composer marker. */
|
|
30131
30481
|
function BusyChase() {
|
|
30132
30482
|
const tick = useFrames(125);
|
|
30133
30483
|
return (0, import_react.createElement)(Text, { color: inkColor(getPalette().brandBright) }, busyChaseFrame(tick) + " ");
|
|
@@ -30137,21 +30487,46 @@ function Caret() {
|
|
|
30137
30487
|
const tick = useFrames(530);
|
|
30138
30488
|
return (0, import_react.createElement)(Text, null, caretVisible(tick) ? "▍" : " ");
|
|
30139
30489
|
}
|
|
30140
|
-
/**
|
|
30141
|
-
function
|
|
30142
|
-
const
|
|
30143
|
-
|
|
30490
|
+
/** One resettable input-caret phase shared by the entire composer. */
|
|
30491
|
+
function useCursorBlink(active) {
|
|
30492
|
+
const [epoch, setEpoch] = (0, import_react.useState)(0);
|
|
30493
|
+
const [visible, setVisible] = (0, import_react.useState)(true);
|
|
30494
|
+
(0, import_react.useEffect)(() => {
|
|
30495
|
+
setVisible(true);
|
|
30496
|
+
if (!active) return;
|
|
30497
|
+
const id = setInterval(() => setVisible((current) => !current), 530);
|
|
30498
|
+
return () => {
|
|
30499
|
+
clearInterval(id);
|
|
30500
|
+
};
|
|
30501
|
+
}, [active, epoch]);
|
|
30502
|
+
return {
|
|
30503
|
+
visible,
|
|
30504
|
+
reset: (0, import_react.useCallback)(() => {
|
|
30505
|
+
setVisible(true);
|
|
30506
|
+
setEpoch((current) => current + 1);
|
|
30507
|
+
}, [])
|
|
30508
|
+
};
|
|
30144
30509
|
}
|
|
30145
30510
|
/**
|
|
30146
|
-
* The busy line, web TurnStatus contract:
|
|
30147
|
-
* `Deep diving...` label, with the elapsed clock appended
|
|
30148
|
-
* has clearly been running (15s) — anchored to `turn/start`
|
|
30149
|
-
* mid-turn keeps the real time.
|
|
30511
|
+
* The busy line, web TurnStatus contract: a continuously moving blue gradient
|
|
30512
|
+
* paints the complete `Deep diving...` label, with the elapsed clock appended
|
|
30513
|
+
* only once the turn has clearly been running (15s) — anchored to `turn/start`
|
|
30514
|
+
* so a resumed mid-turn keeps the real time.
|
|
30150
30515
|
*/
|
|
30151
30516
|
function DeepDivingLine({ since }) {
|
|
30152
|
-
useFrames(
|
|
30517
|
+
const tick = useFrames(33);
|
|
30153
30518
|
const elapsed = since === 0 ? 0 : Date.now() - since;
|
|
30154
|
-
|
|
30519
|
+
const text = elapsed >= 15e3 ? `✻ Deep diving... ${runClock(elapsed)}` : "✻ Deep diving...";
|
|
30520
|
+
const palette = getPalette();
|
|
30521
|
+
const graphemes = splitGraphemes(text);
|
|
30522
|
+
return (0, import_react.createElement)(Text, { wrap: "truncate-end" }, ...graphemes.map((grapheme, index) => {
|
|
30523
|
+
const sparkle = grapheme.text === "✻";
|
|
30524
|
+
return (0, import_react.createElement)(Text, {
|
|
30525
|
+
key: `${grapheme.start}-${grapheme.end}`,
|
|
30526
|
+
color: inkColor(sparkle ? deepDivingSparkColor(tick, palette.brandDeep, palette.brandBright) : deepDivingGradientColor(index, tick, graphemes.length, palette.brandDeep, palette.brandBright)),
|
|
30527
|
+
bold: sparkle || void 0
|
|
30528
|
+
}, grapheme.text);
|
|
30529
|
+
}));
|
|
30155
30530
|
}
|
|
30156
30531
|
/**
|
|
30157
30532
|
* The streaming buffer rendered with a hard size cap: the live region must
|
|
@@ -31730,49 +32105,6 @@ function HelpPanel({ descriptors, skills, commandError, skillError, onClose }) {
|
|
|
31730
32105
|
function verboseLine(text, columns) {
|
|
31731
32106
|
return truncateColumns(displayText(text).replace(/\n/gu, " ↵ ").replace(/\t/gu, " "), Math.max(1, columns));
|
|
31732
32107
|
}
|
|
31733
|
-
/** Identify one whole-chunk key sequence Ink drops or blurs. */
|
|
31734
|
-
function annotateRawKey(chunk) {
|
|
31735
|
-
switch (chunk) {
|
|
31736
|
-
case "": return "delete-backward";
|
|
31737
|
-
case "\x1B":
|
|
31738
|
-
case "\x1B\b": return "delete-word-backward";
|
|
31739
|
-
case "\x1B[3~":
|
|
31740
|
-
case "\x1B[3;2~": return "delete-forward";
|
|
31741
|
-
case "\x1B[3;3~":
|
|
31742
|
-
case "\x1B[3;5~": return "delete-word-forward";
|
|
31743
|
-
case "\x1B[H":
|
|
31744
|
-
case "\x1B[1~":
|
|
31745
|
-
case "\x1B[7~":
|
|
31746
|
-
case "\x1BOH": return "home";
|
|
31747
|
-
case "\x1B[F":
|
|
31748
|
-
case "\x1B[4~":
|
|
31749
|
-
case "\x1B[8~":
|
|
31750
|
-
case "\x1BOF": return "end";
|
|
31751
|
-
default: return;
|
|
31752
|
-
}
|
|
31753
|
-
}
|
|
31754
|
-
/**
|
|
31755
|
-
* One-row editor window keeping the logical cursor visible in long drafts.
|
|
31756
|
-
* The caret and its surroundings slice at grapheme boundaries: splitting a
|
|
31757
|
-
* star-plane surrogate pair would render an isolated half under the block
|
|
31758
|
-
* caret with a width the terminal never draws.
|
|
31759
|
-
*/
|
|
31760
|
-
function editorWindow(value, cursor, columns) {
|
|
31761
|
-
const width = Math.max(1, columns);
|
|
31762
|
-
const normalize = (text) => displayText(text).replace(/\n/gu, "↵").replace(/\t/gu, " ");
|
|
31763
|
-
const site = clampCursor(value, cursor);
|
|
31764
|
-
const caretSpan = splitGraphemes(value).find((span) => span.start === site);
|
|
31765
|
-
const caret = caretSpan === void 0 ? " " : normalize(caretSpan.text);
|
|
31766
|
-
const rest = value.slice(caretSpan === void 0 ? site : caretSpan.end);
|
|
31767
|
-
const remaining = Math.max(0, width - visibleColumns(caret));
|
|
31768
|
-
const afterBudget = Math.min(Math.floor(remaining / 3), visibleColumns(normalize(rest)));
|
|
31769
|
-
const beforeBudget = Math.max(0, remaining - afterBudget);
|
|
31770
|
-
return {
|
|
31771
|
-
before: beforeBudget === 0 ? "" : displayTail(normalize(value.slice(0, site)), beforeBudget, 1).text,
|
|
31772
|
-
caret,
|
|
31773
|
-
after: afterBudget === 0 ? "" : truncateColumns(normalize(rest), afterBudget)
|
|
31774
|
-
};
|
|
31775
|
-
}
|
|
31776
32108
|
/** The empty-composer placeholder text (shared by the static and wave paths). */
|
|
31777
32109
|
const COMPOSER_PLACEHOLDER = "type a message · / commands · @ mentions";
|
|
31778
32110
|
/** Adjacent cells with identical styling merge into one styled Text span. */
|
|
@@ -32023,7 +32355,9 @@ function CompletionMenu({ active, mention, index, rows }) {
|
|
|
32023
32355
|
*/
|
|
32024
32356
|
function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, interrupt, quit, openModel, openEffort, openHelp, openMode, openPermission, openResume, openPlugin, openJobs, openStatusline, openTheme, openHistory, openAgents, openSubagent, openTodos, openDelete, openDiff, reviewChanges, deleteConfirm, confirmDelete, cancelDelete, createSession, forkSession, cancelSessionSwitch, notify, hasNotice, dismissNotice, toggleReasoning, openVerbose, clearView, refresh, loadMentions, inspectImages, prepareImages, cyclePermission, exportTranscript, renameTitle, copyLastResponse, recallSpace, recordLocal, recordHistory, queued, cancelQueued, historyFill, historyConsumed, waveTier, waveStyle, maxRows, onEditorRows }) {
|
|
32025
32357
|
const columns = useStdout().stdout?.columns ?? 80;
|
|
32358
|
+
const editorColumns = Math.max(1, columns - 6);
|
|
32026
32359
|
const stdin = useStdin().stdin;
|
|
32360
|
+
const focusReporting = isVsCodeTerminalEnv();
|
|
32027
32361
|
const [value, setValue] = (0, import_react.useState)("");
|
|
32028
32362
|
const [cursor, setCursor] = (0, import_react.useState)(0);
|
|
32029
32363
|
const valueRef = (0, import_react.useRef)(value);
|
|
@@ -32034,22 +32368,37 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32034
32368
|
const draftImagesRef = (0, import_react.useRef)(draftImages);
|
|
32035
32369
|
draftImagesRef.current = draftImages;
|
|
32036
32370
|
const [preparingImages, setPreparingImages] = (0, import_react.useState)(false);
|
|
32371
|
+
const prepareAbortRef = (0, import_react.useRef)(void 0);
|
|
32372
|
+
const prepareEpochRef = (0, import_react.useRef)(0);
|
|
32373
|
+
const { visible: cursorVisible, reset: resetCursorBlink } = useCursorBlink(active && !frozen && !preparingImages);
|
|
32374
|
+
(0, import_react.useEffect)(() => () => {
|
|
32375
|
+
prepareEpochRef.current += 1;
|
|
32376
|
+
prepareAbortRef.current?.abort();
|
|
32377
|
+
}, []);
|
|
32037
32378
|
const killRef = (0, import_react.useRef)("");
|
|
32038
32379
|
const preferredColumnRef = (0, import_react.useRef)(null);
|
|
32039
32380
|
const editorScrollRef = (0, import_react.useRef)(0);
|
|
32040
32381
|
const pasteBracketRef = (0, import_react.useRef)(false);
|
|
32041
32382
|
/** Cancels the pending lost-paste safety timer (undefined when disarmed). */
|
|
32042
32383
|
const pasteBracketCancelRef = (0, import_react.useRef)(void 0);
|
|
32043
|
-
/**
|
|
32044
|
-
const
|
|
32384
|
+
/** Ordered editor tokens from the stdin chunk Ink is about to deliver. */
|
|
32385
|
+
const rawEditorTokens = (0, import_react.useRef)(void 0);
|
|
32386
|
+
/** VS Code focus state from xterm focus-report events; starts focused. */
|
|
32387
|
+
const terminalFocusedRef = (0, import_react.useRef)(true);
|
|
32045
32388
|
const recall = (0, import_react.useRef)(beginRecall([], ""));
|
|
32389
|
+
(0, import_react.useEffect)(() => {
|
|
32390
|
+
preferredColumnRef.current = null;
|
|
32391
|
+
}, [editorColumns]);
|
|
32046
32392
|
(0, import_react.useEffect)(() => {
|
|
32047
32393
|
if (historyFill === void 0) return;
|
|
32048
32394
|
const safe = sanitizeDraftText(historyFill.text);
|
|
32049
32395
|
draftImagesRef.current = [];
|
|
32050
32396
|
setDraftImages([]);
|
|
32397
|
+
valueRef.current = safe;
|
|
32398
|
+
cursorRef.current = safe.length;
|
|
32051
32399
|
setValue(safe);
|
|
32052
32400
|
setCursor(safe.length);
|
|
32401
|
+
resetCursorBlink();
|
|
32053
32402
|
preferredColumnRef.current = null;
|
|
32054
32403
|
setDismissedMenuValue(void 0);
|
|
32055
32404
|
recall.current = {
|
|
@@ -32062,7 +32411,8 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32062
32411
|
}, [
|
|
32063
32412
|
historyFill,
|
|
32064
32413
|
recallSpace,
|
|
32065
|
-
historyConsumed
|
|
32414
|
+
historyConsumed,
|
|
32415
|
+
resetCursorBlink
|
|
32066
32416
|
]);
|
|
32067
32417
|
(0, import_react.useEffect)(() => {
|
|
32068
32418
|
setDraftImages((current) => {
|
|
@@ -32078,13 +32428,16 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32078
32428
|
const chunk = originalRead(...args);
|
|
32079
32429
|
if (chunk === null) return chunk;
|
|
32080
32430
|
const normalized = normalizeKeyboardChunk(typeof chunk === "string" ? chunk : String(chunk));
|
|
32081
|
-
|
|
32082
|
-
|
|
32431
|
+
const input = focusReporting ? stripTerminalFocusEvents(normalized, (focused) => {
|
|
32432
|
+
terminalFocusedRef.current = focused;
|
|
32433
|
+
}) : normalized;
|
|
32434
|
+
rawEditorTokens.current = tokenizeRawEditorChunk(input);
|
|
32435
|
+
return input;
|
|
32083
32436
|
};
|
|
32084
32437
|
return () => {
|
|
32085
32438
|
stdin.read = originalRead;
|
|
32086
32439
|
};
|
|
32087
|
-
}, [stdin]);
|
|
32440
|
+
}, [focusReporting, stdin]);
|
|
32088
32441
|
if (recall.current.entries !== recallSpace) {
|
|
32089
32442
|
const index = recall.current.index === null || recall.current.index < recallSpace.length ? recall.current.index : null;
|
|
32090
32443
|
recall.current = {
|
|
@@ -32106,6 +32459,7 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32106
32459
|
};
|
|
32107
32460
|
const mentionActive = mentionToken !== void 0;
|
|
32108
32461
|
const [mentionRows, setMentionRows] = (0, import_react.useState)([]);
|
|
32462
|
+
const mentionRequestRef = (0, import_react.useRef)(0);
|
|
32109
32463
|
const sameImagePath = (left, right) => process.platform === "win32" ? left.toLowerCase() === right.toLowerCase() : left === right;
|
|
32110
32464
|
const uniqueImageMarker = (name, source, reserved = []) => {
|
|
32111
32465
|
const safeName = singleLineText(sanitizeDraftText(name));
|
|
@@ -32131,6 +32485,8 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32131
32485
|
return true;
|
|
32132
32486
|
};
|
|
32133
32487
|
const insertDroppedImages = (paths) => {
|
|
32488
|
+
const originalValue = valueRef.current;
|
|
32489
|
+
const originalCursor = cursorRef.current;
|
|
32134
32490
|
notify(`checking ${paths.length} image${paths.length === 1 ? "" : "s"}…`);
|
|
32135
32491
|
inspectImages(paths).then((inspected) => {
|
|
32136
32492
|
const additions = [];
|
|
@@ -32148,14 +32504,24 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32148
32504
|
notify("those images are already attached", "warning");
|
|
32149
32505
|
return;
|
|
32150
32506
|
}
|
|
32151
|
-
const at = cursorRef.current;
|
|
32152
32507
|
const current = valueRef.current;
|
|
32508
|
+
const anchor = remapStableRange(originalValue, current, {
|
|
32509
|
+
start: originalCursor,
|
|
32510
|
+
end: originalCursor
|
|
32511
|
+
});
|
|
32512
|
+
if (anchor === void 0) {
|
|
32513
|
+
notify("draft changed at the image drop point; drop the images again", "warning");
|
|
32514
|
+
return;
|
|
32515
|
+
}
|
|
32516
|
+
const at = anchor.start;
|
|
32153
32517
|
const insertion = `${at > 0 && !/\s$/u.test(current.slice(0, at)) ? " " : ""}${markers.join(" ")}${current.slice(at) === "" ? "" : " "}`;
|
|
32154
|
-
const
|
|
32155
|
-
|
|
32156
|
-
|
|
32157
|
-
|
|
32158
|
-
|
|
32518
|
+
const edit = replaceRangePreservingCursor(current, cursorRef.current, anchor, insertion);
|
|
32519
|
+
const nextCursor = current === originalValue && cursorRef.current === originalCursor ? at + insertion.length : edit.cursor;
|
|
32520
|
+
valueRef.current = edit.value;
|
|
32521
|
+
cursorRef.current = nextCursor;
|
|
32522
|
+
setValue(edit.value);
|
|
32523
|
+
setCursor(nextCursor);
|
|
32524
|
+
resetCursorBlink();
|
|
32159
32525
|
const nextImages = [...draftImagesRef.current, ...additions];
|
|
32160
32526
|
draftImagesRef.current = nextImages;
|
|
32161
32527
|
setDraftImages(nextImages);
|
|
@@ -32165,14 +32531,21 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32165
32531
|
});
|
|
32166
32532
|
};
|
|
32167
32533
|
(0, import_react.useEffect)(() => {
|
|
32534
|
+
const requestId = mentionRequestRef.current + 1;
|
|
32535
|
+
mentionRequestRef.current = requestId;
|
|
32168
32536
|
if (!active || !mentionActive) {
|
|
32169
32537
|
setMentionRows([]);
|
|
32170
32538
|
return;
|
|
32171
32539
|
}
|
|
32172
32540
|
const controller = new AbortController();
|
|
32173
|
-
|
|
32174
|
-
|
|
32541
|
+
const query = mentionToken.query;
|
|
32542
|
+
const timer = setTimeout(() => {
|
|
32543
|
+
loadMentions(query, controller.signal).then((rows) => {
|
|
32544
|
+
if (!controller.signal.aborted && mentionRequestRef.current === requestId) setMentionRows(rows);
|
|
32545
|
+
}, () => {});
|
|
32546
|
+
}, 50);
|
|
32175
32547
|
return () => {
|
|
32548
|
+
clearTimeout(timer);
|
|
32176
32549
|
controller.abort();
|
|
32177
32550
|
};
|
|
32178
32551
|
}, [
|
|
@@ -32180,8 +32553,9 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32180
32553
|
mentionActive,
|
|
32181
32554
|
mentionToken?.query
|
|
32182
32555
|
]);
|
|
32183
|
-
const menuActive = (slashActive || mentionActive) && dismissedMenuValue !== value;
|
|
32184
|
-
const
|
|
32556
|
+
const menuActive = !preparingImages && (slashActive || mentionActive) && dismissedMenuValue !== value;
|
|
32557
|
+
const visibleMentionRows = mentionToken !== void 0 && isPathLikeMentionQuery(mentionToken.query) ? mentionRows.filter((row) => row.kind !== "session") : mentionRows;
|
|
32558
|
+
const menuRows = mentionActive ? visibleMentionRows.map((row) => ({
|
|
32185
32559
|
label: row.label.startsWith("@") ? row.label : `@${row.label}${row.kind === "directory" ? "/" : ""}`,
|
|
32186
32560
|
description: row.description,
|
|
32187
32561
|
origin: "mention"
|
|
@@ -32189,34 +32563,45 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32189
32563
|
/** Accept the highlighted completion-menu candidate into the draft. */
|
|
32190
32564
|
const acceptMenuCandidate = () => {
|
|
32191
32565
|
if (mentionActive && mentionToken !== void 0) {
|
|
32192
|
-
|
|
32566
|
+
if (visibleMentionRows.length === 0) return;
|
|
32567
|
+
const row = visibleMentionRows[completionIndex % visibleMentionRows.length];
|
|
32193
32568
|
if (row !== void 0) {
|
|
32194
32569
|
if (row.kind === "file" && row.path !== void 0 && looksLikeImagePath(row.path)) {
|
|
32195
32570
|
const tokenText = value.slice(mentionToken.start, cursor);
|
|
32196
32571
|
const start = mentionToken.start;
|
|
32572
|
+
const originalValue = value;
|
|
32197
32573
|
notify(`checking image ${basename(row.path)}…`);
|
|
32198
32574
|
inspectImages([row.path]).then((inspected) => {
|
|
32199
32575
|
const inspection = inspected[0];
|
|
32200
32576
|
if (inspection === void 0) return;
|
|
32201
32577
|
const current = valueRef.current;
|
|
32202
|
-
|
|
32578
|
+
const anchor = remapStableRange(originalValue, current, {
|
|
32579
|
+
start,
|
|
32580
|
+
end: start + tokenText.length
|
|
32581
|
+
});
|
|
32582
|
+
if (anchor === void 0 || current.slice(anchor.start, anchor.end) !== tokenText) {
|
|
32583
|
+
notify("draft changed around the image mention; select it again", "warning");
|
|
32584
|
+
return;
|
|
32585
|
+
}
|
|
32203
32586
|
if (draftImagesRef.current.some((image) => sameImagePath(image.path, inspection.path))) {
|
|
32204
|
-
const
|
|
32205
|
-
valueRef.current =
|
|
32206
|
-
cursorRef.current =
|
|
32207
|
-
setValue(
|
|
32208
|
-
setCursor(
|
|
32209
|
-
|
|
32587
|
+
const edit = replaceRangePreservingCursor(current, cursorRef.current, anchor, "");
|
|
32588
|
+
valueRef.current = edit.value;
|
|
32589
|
+
cursorRef.current = edit.cursor;
|
|
32590
|
+
setValue(edit.value);
|
|
32591
|
+
setCursor(edit.cursor);
|
|
32592
|
+
resetCursorBlink();
|
|
32593
|
+
setDismissedMenuValue(edit.value);
|
|
32210
32594
|
notify(`${inspection.name} is already attached`, "warning");
|
|
32211
32595
|
return;
|
|
32212
32596
|
}
|
|
32213
32597
|
const marker = uniqueImageMarker(inspection.name, "mention");
|
|
32214
|
-
const
|
|
32215
|
-
valueRef.current =
|
|
32216
|
-
cursorRef.current =
|
|
32217
|
-
setValue(
|
|
32218
|
-
setCursor(
|
|
32219
|
-
|
|
32598
|
+
const edit = replaceRangePreservingCursor(current, cursorRef.current, anchor, marker);
|
|
32599
|
+
valueRef.current = edit.value;
|
|
32600
|
+
cursorRef.current = edit.cursor;
|
|
32601
|
+
setValue(edit.value);
|
|
32602
|
+
setCursor(edit.cursor);
|
|
32603
|
+
resetCursorBlink();
|
|
32604
|
+
setDismissedMenuValue(edit.value);
|
|
32220
32605
|
registerDraftImage(inspection, marker);
|
|
32221
32606
|
notify(`${inspection.name} ready for the next message`);
|
|
32222
32607
|
}, (reason) => {
|
|
@@ -32227,14 +32612,25 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32227
32612
|
return;
|
|
32228
32613
|
}
|
|
32229
32614
|
const insertion = row.label.startsWith("@") ? row.label : `@${row.label}${row.kind === "directory" ? "/" : ""}`;
|
|
32230
|
-
|
|
32231
|
-
|
|
32615
|
+
const nextValue = value.slice(0, mentionToken.start) + insertion + value.slice(cursor);
|
|
32616
|
+
const nextCursor = mentionToken.start + insertion.length;
|
|
32617
|
+
valueRef.current = nextValue;
|
|
32618
|
+
cursorRef.current = nextCursor;
|
|
32619
|
+
setValue(nextValue);
|
|
32620
|
+
setCursor(nextCursor);
|
|
32621
|
+
resetCursorBlink();
|
|
32232
32622
|
}
|
|
32233
32623
|
} else {
|
|
32624
|
+
if (candidates.length === 0) return;
|
|
32234
32625
|
const candidate = candidates[completionIndex % candidates.length];
|
|
32235
32626
|
if (candidate !== void 0) {
|
|
32236
|
-
|
|
32237
|
-
|
|
32627
|
+
const nextValue = `${candidate.label} `;
|
|
32628
|
+
const nextCursor = candidate.label.length + 1;
|
|
32629
|
+
valueRef.current = nextValue;
|
|
32630
|
+
cursorRef.current = nextCursor;
|
|
32631
|
+
setValue(nextValue);
|
|
32632
|
+
setCursor(nextCursor);
|
|
32633
|
+
resetCursorBlink();
|
|
32238
32634
|
}
|
|
32239
32635
|
}
|
|
32240
32636
|
setCompletionIndex(0);
|
|
@@ -32243,21 +32639,101 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32243
32639
|
/** Apply one editor edit: draft, cursor, kill buffer, menu reset. */
|
|
32244
32640
|
const applyEdit = (edit) => {
|
|
32245
32641
|
if (edit.killed !== void 0 && edit.killed !== "") killRef.current = edit.killed;
|
|
32642
|
+
valueRef.current = edit.value;
|
|
32643
|
+
cursorRef.current = edit.cursor;
|
|
32246
32644
|
setValue(edit.value);
|
|
32247
32645
|
setCursor(edit.cursor);
|
|
32646
|
+
resetCursorBlink();
|
|
32248
32647
|
preferredColumnRef.current = null;
|
|
32249
32648
|
setCompletionIndex(0);
|
|
32250
32649
|
setDismissedMenuValue(void 0);
|
|
32251
32650
|
};
|
|
32252
32651
|
/** Move the cursor without editing; horizontal moves clear the column preference. */
|
|
32253
32652
|
const moveCursorTo = (next) => {
|
|
32254
|
-
|
|
32653
|
+
resetCursorBlink();
|
|
32654
|
+
if (next === cursorRef.current) return;
|
|
32655
|
+
cursorRef.current = next;
|
|
32255
32656
|
setCursor(next);
|
|
32256
32657
|
preferredColumnRef.current = null;
|
|
32257
32658
|
};
|
|
32258
|
-
|
|
32659
|
+
/** Apply an ordered raw-key batch against one current draft snapshot. */
|
|
32660
|
+
const applyRawEditorTokens = (tokens) => {
|
|
32661
|
+
let nextValue = valueRef.current;
|
|
32662
|
+
let nextCursor = cursorRef.current;
|
|
32663
|
+
for (const token of tokens) {
|
|
32664
|
+
if (token.kind === "text") {
|
|
32665
|
+
const edit = insertText(nextValue, nextCursor, token.text);
|
|
32666
|
+
nextValue = edit.value;
|
|
32667
|
+
nextCursor = edit.cursor;
|
|
32668
|
+
continue;
|
|
32669
|
+
}
|
|
32670
|
+
if (token.kind === "home") {
|
|
32671
|
+
nextCursor = moveToLineStart(nextValue, nextCursor, false);
|
|
32672
|
+
continue;
|
|
32673
|
+
}
|
|
32674
|
+
if (token.kind === "end") {
|
|
32675
|
+
nextCursor = moveToLineEnd(nextValue, nextCursor, false);
|
|
32676
|
+
continue;
|
|
32677
|
+
}
|
|
32678
|
+
const edit = token.kind === "delete-backward" ? deleteBackward(nextValue, nextCursor) : token.kind === "delete-word-backward" ? deleteWordBackward(nextValue, nextCursor) : token.kind === "delete-forward" ? deleteForward(nextValue, nextCursor) : deleteWordForward(nextValue, nextCursor);
|
|
32679
|
+
if (edit.killed !== void 0 && edit.killed !== "") killRef.current = edit.killed;
|
|
32680
|
+
nextValue = edit.value;
|
|
32681
|
+
nextCursor = edit.cursor;
|
|
32682
|
+
}
|
|
32683
|
+
valueRef.current = nextValue;
|
|
32684
|
+
cursorRef.current = nextCursor;
|
|
32685
|
+
setValue(nextValue);
|
|
32686
|
+
setCursor(nextCursor);
|
|
32687
|
+
resetCursorBlink();
|
|
32688
|
+
preferredColumnRef.current = null;
|
|
32689
|
+
setCompletionIndex(0);
|
|
32690
|
+
setDismissedMenuValue(void 0);
|
|
32691
|
+
};
|
|
32692
|
+
const cancelImageSubmission = () => {
|
|
32693
|
+
prepareEpochRef.current += 1;
|
|
32694
|
+
prepareAbortRef.current?.abort();
|
|
32695
|
+
prepareAbortRef.current = void 0;
|
|
32696
|
+
setPreparingImages(false);
|
|
32697
|
+
dismissNotice();
|
|
32698
|
+
notify("image submission cancelled", "warning");
|
|
32699
|
+
};
|
|
32700
|
+
/** Move through visual rows first, then cross history at the true edge. */
|
|
32701
|
+
const navigateVertical = (direction) => {
|
|
32702
|
+
const currentValue = valueRef.current;
|
|
32703
|
+
const currentCursor = cursorRef.current;
|
|
32704
|
+
const model = editorModel(currentValue, editorColumns);
|
|
32705
|
+
const preferred = preferredColumnRef.current ?? caretSite(model, currentCursor).column;
|
|
32706
|
+
const next = moveCursorVertically(model, currentCursor, preferred, direction);
|
|
32707
|
+
if (next !== currentCursor) {
|
|
32708
|
+
cursorRef.current = next;
|
|
32709
|
+
setCursor(next);
|
|
32710
|
+
resetCursorBlink();
|
|
32711
|
+
preferredColumnRef.current = preferred;
|
|
32712
|
+
return;
|
|
32713
|
+
}
|
|
32714
|
+
if (recall.current.entries.length > 0 && shouldRecallNavigate(currentValue, currentCursor, recall.current.lastRecalled, direction)) {
|
|
32715
|
+
const step = direction < 0 ? recallOlder(recall.current, currentValue) : recallNewer(recall.current);
|
|
32716
|
+
recall.current = step.state;
|
|
32717
|
+
if (step.entry !== void 0) {
|
|
32718
|
+
const safe = sanitizeDraftText(step.entry);
|
|
32719
|
+
valueRef.current = safe;
|
|
32720
|
+
cursorRef.current = safe.length;
|
|
32721
|
+
setValue(safe);
|
|
32722
|
+
setCursor(safe.length);
|
|
32723
|
+
preferredColumnRef.current = null;
|
|
32724
|
+
setDismissedMenuValue(void 0);
|
|
32725
|
+
}
|
|
32726
|
+
}
|
|
32727
|
+
resetCursorBlink();
|
|
32728
|
+
};
|
|
32729
|
+
useStableInput((input, key) => {
|
|
32259
32730
|
if (!active) return;
|
|
32260
|
-
|
|
32731
|
+
const liveValue = valueRef.current;
|
|
32732
|
+
const liveCursor = cursorRef.current;
|
|
32733
|
+
if (preparingImages) {
|
|
32734
|
+
if (key.escape || key.ctrl && input === "c") cancelImageSubmission();
|
|
32735
|
+
return;
|
|
32736
|
+
}
|
|
32261
32737
|
if (deleteConfirm !== void 0) {
|
|
32262
32738
|
if (input === "y" || input === "Y") confirmDelete();
|
|
32263
32739
|
else cancelDelete();
|
|
@@ -32273,6 +32749,7 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32273
32749
|
return;
|
|
32274
32750
|
}
|
|
32275
32751
|
if (key.ctrl && input === "r") {
|
|
32752
|
+
if (focusReporting && !terminalFocusedRef.current) return;
|
|
32276
32753
|
toggleReasoning();
|
|
32277
32754
|
return;
|
|
32278
32755
|
}
|
|
@@ -32282,9 +32759,12 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32282
32759
|
}
|
|
32283
32760
|
if (key.ctrl && input === "c") {
|
|
32284
32761
|
if (busy) interrupt();
|
|
32285
|
-
else if (
|
|
32762
|
+
else if (liveValue !== "") {
|
|
32763
|
+
valueRef.current = "";
|
|
32764
|
+
cursorRef.current = 0;
|
|
32286
32765
|
setValue("");
|
|
32287
32766
|
setCursor(0);
|
|
32767
|
+
resetCursorBlink();
|
|
32288
32768
|
draftImagesRef.current = [];
|
|
32289
32769
|
setDraftImages([]);
|
|
32290
32770
|
setCompletionIndex(0);
|
|
@@ -32293,8 +32773,8 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32293
32773
|
return;
|
|
32294
32774
|
}
|
|
32295
32775
|
if (key.ctrl && input === "d") {
|
|
32296
|
-
if (
|
|
32297
|
-
applyEdit(deleteForward(
|
|
32776
|
+
if (liveValue !== "") {
|
|
32777
|
+
applyEdit(deleteForward(liveValue, liveCursor));
|
|
32298
32778
|
return;
|
|
32299
32779
|
}
|
|
32300
32780
|
if (busy) notify("cancel the running turn before exiting (Esc or Ctrl+C)", "warning");
|
|
@@ -32303,7 +32783,7 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32303
32783
|
}
|
|
32304
32784
|
if (key.escape) {
|
|
32305
32785
|
if (menuActive) {
|
|
32306
|
-
setDismissedMenuValue(
|
|
32786
|
+
setDismissedMenuValue(liveValue);
|
|
32307
32787
|
return;
|
|
32308
32788
|
}
|
|
32309
32789
|
if (hasNotice) {
|
|
@@ -32313,27 +32793,33 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32313
32793
|
if (busy) interrupt();
|
|
32314
32794
|
return;
|
|
32315
32795
|
}
|
|
32316
|
-
if (key.delete &&
|
|
32796
|
+
if (key.delete && liveValue === "" && queued.length > 0) {
|
|
32317
32797
|
cancelQueued(queued[queued.length - 1].messageId);
|
|
32318
32798
|
return;
|
|
32319
32799
|
}
|
|
32320
32800
|
if (key.return) {
|
|
32321
32801
|
if (pasteBracketRef.current) {
|
|
32322
|
-
applyEdit(insertText(
|
|
32802
|
+
applyEdit(insertText(liveValue, liveCursor, "\n"));
|
|
32323
32803
|
return;
|
|
32324
32804
|
}
|
|
32325
32805
|
if (menuActive) {
|
|
32326
|
-
if (!(!mentionActive && candidates.some((candidate) => candidate.label ===
|
|
32806
|
+
if (!(!mentionActive && candidates.some((candidate) => candidate.label === liveValue))) {
|
|
32327
32807
|
acceptMenuCandidate();
|
|
32328
32808
|
return;
|
|
32329
32809
|
}
|
|
32330
32810
|
}
|
|
32331
|
-
const text =
|
|
32811
|
+
const text = liveValue.trim();
|
|
32332
32812
|
if (draftImagesRef.current.length > 0) {
|
|
32813
|
+
const controller = new AbortController();
|
|
32814
|
+
const epoch = prepareEpochRef.current + 1;
|
|
32815
|
+
prepareEpochRef.current = epoch;
|
|
32816
|
+
prepareAbortRef.current = controller;
|
|
32333
32817
|
setPreparingImages(true);
|
|
32334
32818
|
notify(`processing ${draftImagesRef.current.length} image${draftImagesRef.current.length === 1 ? "" : "s"}…`);
|
|
32335
32819
|
const snapshot = draftImagesRef.current;
|
|
32336
|
-
prepareImages(snapshot.map((image) => image.path)).then((images) => {
|
|
32820
|
+
prepareImages(snapshot.map((image) => image.path), controller.signal).then((images) => {
|
|
32821
|
+
if (controller.signal.aborted || prepareEpochRef.current !== epoch) return;
|
|
32822
|
+
prepareAbortRef.current = void 0;
|
|
32337
32823
|
setPreparingImages(false);
|
|
32338
32824
|
valueRef.current = "";
|
|
32339
32825
|
cursorRef.current = 0;
|
|
@@ -32352,13 +32838,18 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32352
32838
|
if (busy) steer(text, images);
|
|
32353
32839
|
else dispatch(text, images);
|
|
32354
32840
|
}, (reason) => {
|
|
32841
|
+
if (controller.signal.aborted || prepareEpochRef.current !== epoch) return;
|
|
32842
|
+
prepareAbortRef.current = void 0;
|
|
32355
32843
|
setPreparingImages(false);
|
|
32356
32844
|
notify(`image submission failed: ${reason instanceof Error ? reason.message : String(reason)}`, "error");
|
|
32357
32845
|
});
|
|
32358
32846
|
return;
|
|
32359
32847
|
}
|
|
32848
|
+
valueRef.current = "";
|
|
32849
|
+
cursorRef.current = 0;
|
|
32360
32850
|
setValue("");
|
|
32361
32851
|
setCursor(0);
|
|
32852
|
+
resetCursorBlink();
|
|
32362
32853
|
setCompletionIndex(0);
|
|
32363
32854
|
setDismissedMenuValue(void 0);
|
|
32364
32855
|
if (text === "") return;
|
|
@@ -32485,6 +32976,12 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32485
32976
|
return;
|
|
32486
32977
|
}
|
|
32487
32978
|
if (input === "\n" || input === "\r") return;
|
|
32979
|
+
if (menuActive && (key.tab || input.startsWith(" "))) {
|
|
32980
|
+
const remainder = key.tab ? "" : input.slice(1);
|
|
32981
|
+
acceptMenuCandidate();
|
|
32982
|
+
if (remainder !== "") applyEdit(insertText(valueRef.current, cursorRef.current, remainder));
|
|
32983
|
+
return;
|
|
32984
|
+
}
|
|
32488
32985
|
if (menuActive && key.upArrow) {
|
|
32489
32986
|
setCompletionIndex((index) => (index + menuRows.length - 1) % menuRows.length);
|
|
32490
32987
|
return;
|
|
@@ -32493,119 +32990,74 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32493
32990
|
setCompletionIndex((index) => (index + 1) % menuRows.length);
|
|
32494
32991
|
return;
|
|
32495
32992
|
}
|
|
32496
|
-
const
|
|
32497
|
-
|
|
32498
|
-
|
|
32499
|
-
|
|
32500
|
-
else if (rawKey === "delete-backward") applyEdit(deleteBackward(value, cursor));
|
|
32501
|
-
else if (rawKey === "delete-word-backward") applyEdit(deleteWordBackward(value, cursor));
|
|
32502
|
-
else if (rawKey === "delete-forward") applyEdit(deleteForward(value, cursor));
|
|
32503
|
-
else applyEdit(deleteWordForward(value, cursor));
|
|
32993
|
+
const rawTokens = rawEditorTokens.current;
|
|
32994
|
+
rawEditorTokens.current = void 0;
|
|
32995
|
+
if (rawTokens !== void 0) {
|
|
32996
|
+
applyRawEditorTokens(rawTokens);
|
|
32504
32997
|
return;
|
|
32505
32998
|
}
|
|
32506
32999
|
if (key.upArrow || key.downArrow) {
|
|
32507
|
-
|
|
32508
|
-
const step = key.upArrow ? recallOlder(recall.current, value) : recallNewer(recall.current);
|
|
32509
|
-
recall.current = step.state;
|
|
32510
|
-
if (step.entry !== void 0) {
|
|
32511
|
-
const safe = sanitizeDraftText(step.entry);
|
|
32512
|
-
setValue(safe);
|
|
32513
|
-
setCursor(safe.length);
|
|
32514
|
-
preferredColumnRef.current = null;
|
|
32515
|
-
setDismissedMenuValue(void 0);
|
|
32516
|
-
}
|
|
32517
|
-
return;
|
|
32518
|
-
}
|
|
32519
|
-
const model = editorModel(value, Math.max(1, columns - 6));
|
|
32520
|
-
const preferred = preferredColumnRef.current ?? caretSite(model, cursor).column;
|
|
32521
|
-
const next = moveCursorVertically(model, cursor, preferred, key.upArrow ? -1 : 1);
|
|
32522
|
-
if (next !== cursor) {
|
|
32523
|
-
setCursor(next);
|
|
32524
|
-
preferredColumnRef.current = preferred;
|
|
32525
|
-
}
|
|
33000
|
+
navigateVertical(key.upArrow ? -1 : 1);
|
|
32526
33001
|
return;
|
|
32527
33002
|
}
|
|
32528
33003
|
if (key.ctrl && (input === "p" || input === "n")) {
|
|
32529
|
-
|
|
32530
|
-
if (recall.current.entries.length > 0 && shouldRecallNavigate(value, cursor, recall.current.lastRecalled)) {
|
|
32531
|
-
const step = up ? recallOlder(recall.current, value) : recallNewer(recall.current);
|
|
32532
|
-
recall.current = step.state;
|
|
32533
|
-
if (step.entry !== void 0) {
|
|
32534
|
-
const safe = sanitizeDraftText(step.entry);
|
|
32535
|
-
setValue(safe);
|
|
32536
|
-
setCursor(safe.length);
|
|
32537
|
-
preferredColumnRef.current = null;
|
|
32538
|
-
setDismissedMenuValue(void 0);
|
|
32539
|
-
}
|
|
32540
|
-
return;
|
|
32541
|
-
}
|
|
32542
|
-
const model = editorModel(value, Math.max(1, columns - 6));
|
|
32543
|
-
const preferred = preferredColumnRef.current ?? caretSite(model, cursor).column;
|
|
32544
|
-
const next = moveCursorVertically(model, cursor, preferred, up ? -1 : 1);
|
|
32545
|
-
if (next !== cursor) {
|
|
32546
|
-
setCursor(next);
|
|
32547
|
-
preferredColumnRef.current = preferred;
|
|
32548
|
-
}
|
|
32549
|
-
return;
|
|
32550
|
-
}
|
|
32551
|
-
if (key.tab && menuActive) {
|
|
32552
|
-
acceptMenuCandidate();
|
|
33004
|
+
navigateVertical(input === "p" ? -1 : 1);
|
|
32553
33005
|
return;
|
|
32554
33006
|
}
|
|
32555
33007
|
if (key.leftArrow) {
|
|
32556
|
-
moveCursorTo(key.meta || key.ctrl ? moveWordLeft(
|
|
33008
|
+
moveCursorTo(key.meta || key.ctrl ? moveWordLeft(liveValue, liveCursor) : moveCursorBy(liveValue, liveCursor, -1));
|
|
32557
33009
|
return;
|
|
32558
33010
|
}
|
|
32559
33011
|
if (key.rightArrow) {
|
|
32560
|
-
moveCursorTo(key.meta || key.ctrl ? moveWordRight(
|
|
33012
|
+
moveCursorTo(key.meta || key.ctrl ? moveWordRight(liveValue, liveCursor) : moveCursorBy(liveValue, liveCursor, 1));
|
|
32561
33013
|
return;
|
|
32562
33014
|
}
|
|
32563
33015
|
if (key.meta && input === "b") {
|
|
32564
|
-
moveCursorTo(moveWordLeft(
|
|
33016
|
+
moveCursorTo(moveWordLeft(liveValue, liveCursor));
|
|
32565
33017
|
return;
|
|
32566
33018
|
}
|
|
32567
33019
|
if (key.meta && input === "f") {
|
|
32568
|
-
moveCursorTo(moveWordRight(
|
|
33020
|
+
moveCursorTo(moveWordRight(liveValue, liveCursor));
|
|
32569
33021
|
return;
|
|
32570
33022
|
}
|
|
32571
33023
|
if (key.ctrl && input === "b") {
|
|
32572
|
-
moveCursorTo(moveCursorBy(
|
|
33024
|
+
moveCursorTo(moveCursorBy(liveValue, liveCursor, -1));
|
|
32573
33025
|
return;
|
|
32574
33026
|
}
|
|
32575
33027
|
if (key.ctrl && input === "f") {
|
|
32576
|
-
moveCursorTo(moveCursorBy(
|
|
33028
|
+
moveCursorTo(moveCursorBy(liveValue, liveCursor, 1));
|
|
32577
33029
|
return;
|
|
32578
33030
|
}
|
|
32579
33031
|
if (key.ctrl && input === "w") {
|
|
32580
|
-
applyEdit(deleteWordBackward(
|
|
33032
|
+
applyEdit(deleteWordBackward(liveValue, liveCursor));
|
|
32581
33033
|
return;
|
|
32582
33034
|
}
|
|
32583
33035
|
if (key.meta && input === "d") {
|
|
32584
|
-
applyEdit(deleteWordForward(
|
|
33036
|
+
applyEdit(deleteWordForward(liveValue, liveCursor));
|
|
32585
33037
|
return;
|
|
32586
33038
|
}
|
|
32587
33039
|
if (key.backspace || key.delete) {
|
|
32588
|
-
applyEdit(deleteBackward(
|
|
33040
|
+
applyEdit(deleteBackward(liveValue, liveCursor));
|
|
32589
33041
|
return;
|
|
32590
33042
|
}
|
|
32591
33043
|
if (key.ctrl && input === "a") {
|
|
32592
|
-
moveCursorTo(
|
|
33044
|
+
moveCursorTo(moveToLineStart(liveValue, liveCursor, true));
|
|
32593
33045
|
return;
|
|
32594
33046
|
}
|
|
32595
33047
|
if (key.ctrl && input === "e") {
|
|
32596
|
-
moveCursorTo(
|
|
33048
|
+
moveCursorTo(moveToLineEnd(liveValue, liveCursor, true));
|
|
32597
33049
|
return;
|
|
32598
33050
|
}
|
|
32599
33051
|
if (key.ctrl && input === "u") {
|
|
32600
|
-
applyEdit(killToLineStart(
|
|
33052
|
+
applyEdit(killToLineStart(liveValue, liveCursor));
|
|
32601
33053
|
return;
|
|
32602
33054
|
}
|
|
32603
33055
|
if (key.ctrl && input === "k") {
|
|
32604
|
-
applyEdit(killToLineEnd(
|
|
33056
|
+
applyEdit(killToLineEnd(liveValue, liveCursor));
|
|
32605
33057
|
return;
|
|
32606
33058
|
}
|
|
32607
33059
|
if (key.ctrl && input === "y") {
|
|
32608
|
-
if (killRef.current !== "") applyEdit(insertText(
|
|
33060
|
+
if (killRef.current !== "") applyEdit(insertText(liveValue, liveCursor, killRef.current));
|
|
32609
33061
|
return;
|
|
32610
33062
|
}
|
|
32611
33063
|
if (key.ctrl && input === "l") {
|
|
@@ -32638,9 +33090,9 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32638
33090
|
insertDroppedImages(droppedPaths);
|
|
32639
33091
|
return;
|
|
32640
33092
|
}
|
|
32641
|
-
applyEdit(insertText(
|
|
33093
|
+
applyEdit(insertText(valueRef.current, cursorRef.current, text));
|
|
32642
33094
|
}
|
|
32643
|
-
});
|
|
33095
|
+
}, active);
|
|
32644
33096
|
const [waveTick, setWaveTick] = (0, import_react.useState)(null);
|
|
32645
33097
|
const wavePrevious = (0, import_react.useRef)({
|
|
32646
33098
|
tier: null,
|
|
@@ -32658,7 +33110,7 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32658
33110
|
}
|
|
32659
33111
|
if (previous.tier !== waveTier || previous.style !== waveStyle) setWaveTick(0);
|
|
32660
33112
|
}, [waveTier, waveStyle]);
|
|
32661
|
-
const waveActive = waveTick !== null && waveTier !== null && waveStyle !== null && waveTick * 33 < deepseekWaveDuration(waveTier, waveStyle);
|
|
33113
|
+
const waveActive = !preparingImages && waveTick !== null && waveTier !== null && waveStyle !== null && waveTick * 33 < deepseekWaveDuration(waveTier, waveStyle);
|
|
32662
33114
|
(0, import_react.useEffect)(() => {
|
|
32663
33115
|
if (!waveActive) return;
|
|
32664
33116
|
const id = setInterval(() => {
|
|
@@ -32679,7 +33131,7 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32679
33131
|
const tierHues = waveTier === null ? null : deepseekWaveHues(waveTier);
|
|
32680
33132
|
const promptColor = tierHues === null ? inkColor(getPalette().brand) : inkColor(tierHues[0]);
|
|
32681
33133
|
const promptGlyph = waveTier === "flash" ? "›" : waveTier === "deepseek" ? "»" : "❯";
|
|
32682
|
-
const editorViewModel = editorModel(value,
|
|
33134
|
+
const editorViewModel = editorModel(value, editorColumns);
|
|
32683
33135
|
const clampedCursor = clampCursor(value, cursor);
|
|
32684
33136
|
const caret = caretSite(editorViewModel, clampedCursor);
|
|
32685
33137
|
const editorWindowRows = Math.min(editorViewModel.rows.length, Math.max(1, maxRows));
|
|
@@ -32730,123 +33182,120 @@ function Input({ active, frozen, busy, descriptors, skills, dispatch, steer, int
|
|
|
32730
33182
|
const editorRows = [];
|
|
32731
33183
|
for (let index = editorWindowStart; index < Math.min(editorViewModel.rows.length, editorWindowStart + editorWindowRows); index += 1) {
|
|
32732
33184
|
const row = editorViewModel.rows[index];
|
|
32733
|
-
const
|
|
32734
|
-
const
|
|
32735
|
-
const
|
|
32736
|
-
const
|
|
32737
|
-
const placeholder = index === 0 && value === "" && !busy;
|
|
32738
|
-
const tail = placeholder ? COMPOSER_PLACEHOLDER : after;
|
|
32739
|
-
const consumed = 2 + visibleColumns(before) + visibleColumns(caretChar) + visibleColumns(tail);
|
|
33185
|
+
const parts = editorRowParts(row, index, caret.row, clampedCursor, !preparingImages);
|
|
33186
|
+
const placeholder = index === 0 && value === "" && !busy && !preparingImages;
|
|
33187
|
+
const tail = placeholder ? COMPOSER_PLACEHOLDER : parts.after;
|
|
33188
|
+
const consumed = 2 + visibleColumns(parts.before) + visibleColumns(parts.caret) + visibleColumns(tail);
|
|
32740
33189
|
editorRows.push((0, import_react.createElement)(Text, {
|
|
32741
33190
|
key: index,
|
|
32742
33191
|
backgroundColor: bandBg,
|
|
32743
33192
|
wrap: "truncate-end"
|
|
32744
|
-
}, index === 0 ?
|
|
33193
|
+
}, index === 0 ? preparingImages ? (0, import_react.createElement)(Text, {
|
|
33194
|
+
color: inkColor(getPalette().warn),
|
|
33195
|
+
bold: true
|
|
33196
|
+
}, "… ") : busy ? (0, import_react.createElement)(BusyChase) : (0, import_react.createElement)(Text, {
|
|
32745
33197
|
color: promptColor,
|
|
32746
33198
|
bold: tierActive ? true : void 0
|
|
32747
|
-
}, `${promptGlyph} `) : " ", before, (0, import_react.createElement)(
|
|
33199
|
+
}, `${promptGlyph} `) : " ", parts.before, parts.hasCaret ? (0, import_react.createElement)(Text, {
|
|
32748
33200
|
key: "caret",
|
|
32749
|
-
|
|
32750
|
-
}), placeholder ? (0, import_react.createElement)(Text, { dimColor: true }, COMPOSER_PLACEHOLDER) : after, bandFill(consumed)));
|
|
33201
|
+
inverse: cursorVisible || void 0
|
|
33202
|
+
}, parts.caret) : null, placeholder ? (0, import_react.createElement)(Text, { dimColor: true }, COMPOSER_PLACEHOLDER) : parts.after, bandFill(consumed)));
|
|
32751
33203
|
}
|
|
32752
33204
|
const staticEditor = (0, import_react.createElement)(Box, { flexDirection: "column" }, ...editorRows);
|
|
32753
33205
|
const waveRow = () => {
|
|
32754
33206
|
const hues = deepseekWaveHues(waveTier);
|
|
32755
33207
|
const style = waveStyle;
|
|
32756
33208
|
const bandRgb = getPalette().composerBand;
|
|
33209
|
+
const visibleRows = editorViewModel.rows.slice(editorWindowStart, editorWindowStart + editorWindowRows);
|
|
33210
|
+
const totalBandRows = visibleRows.length + 2;
|
|
32757
33211
|
const waveBg = (row, column) => {
|
|
32758
|
-
const rgb = deepseekWaveColumnBg(waveTick, column, bandWidth, waveTier, style, hues, bandRgb, row,
|
|
33212
|
+
const rgb = deepseekWaveColumnBg(waveTick, column, bandWidth, waveTier, style, hues, bandRgb, row, totalBandRows);
|
|
32759
33213
|
return rgb === null ? bandBg : inkColor(rgb);
|
|
32760
33214
|
};
|
|
32761
33215
|
const blankBandRow = (row) => {
|
|
32762
33216
|
const blanks = [];
|
|
32763
|
-
|
|
33217
|
+
for (let column = 0; column < bandWidth; column += 1) blanks.push({
|
|
32764
33218
|
char: " ",
|
|
32765
|
-
|
|
33219
|
+
width: 1,
|
|
33220
|
+
backgroundColor: waveBg(row, column)
|
|
32766
33221
|
});
|
|
32767
|
-
return (0, import_react.createElement)(Text, { key: row }, ...waveRowSpans(blanks));
|
|
33222
|
+
return (0, import_react.createElement)(Text, { key: `blank-${row}` }, ...waveRowSpans(blanks));
|
|
32768
33223
|
};
|
|
32769
|
-
const
|
|
32770
|
-
|
|
32771
|
-
{
|
|
32772
|
-
|
|
32773
|
-
|
|
32774
|
-
|
|
32775
|
-
{
|
|
32776
|
-
char: " ",
|
|
32777
|
-
backgroundColor: waveBg(1, 1)
|
|
32778
|
-
},
|
|
32779
|
-
{
|
|
32780
|
-
char: promptGlyph,
|
|
32781
|
-
color: promptColor,
|
|
32782
|
-
bold: true,
|
|
32783
|
-
backgroundColor: waveBg(1, 2)
|
|
32784
|
-
},
|
|
32785
|
-
{
|
|
32786
|
-
char: " ",
|
|
32787
|
-
color: promptColor,
|
|
32788
|
-
backgroundColor: waveBg(1, 3)
|
|
33224
|
+
const cellIndexAtColumn = (cells, target) => {
|
|
33225
|
+
let column = 0;
|
|
33226
|
+
for (let index = 0; index < cells.length; index += 1) {
|
|
33227
|
+
if (column === target) return index;
|
|
33228
|
+
column += cells[index].width ?? visibleColumns(cells[index].char);
|
|
33229
|
+
if (column > target) return void 0;
|
|
32789
33230
|
}
|
|
32790
|
-
|
|
32791
|
-
|
|
32792
|
-
|
|
32793
|
-
|
|
32794
|
-
|
|
32795
|
-
|
|
32796
|
-
|
|
32797
|
-
|
|
32798
|
-
|
|
32799
|
-
|
|
32800
|
-
|
|
32801
|
-
|
|
32802
|
-
|
|
32803
|
-
|
|
32804
|
-
|
|
32805
|
-
|
|
32806
|
-
|
|
32807
|
-
|
|
32808
|
-
|
|
32809
|
-
|
|
32810
|
-
|
|
32811
|
-
|
|
33231
|
+
};
|
|
33232
|
+
const editorWaveRows = visibleRows.map((row, visibleIndex) => {
|
|
33233
|
+
const sourceIndex = editorWindowStart + visibleIndex;
|
|
33234
|
+
const bandRow = visibleIndex + 1;
|
|
33235
|
+
const parts = editorRowParts(row, sourceIndex, caret.row, clampedCursor);
|
|
33236
|
+
const placeholder = sourceIndex === 0 && value === "" && !busy;
|
|
33237
|
+
const cells = [];
|
|
33238
|
+
let usedColumns = 0;
|
|
33239
|
+
const push = (char, extra = {}) => {
|
|
33240
|
+
const width = visibleColumns(char);
|
|
33241
|
+
cells.push({
|
|
33242
|
+
char,
|
|
33243
|
+
width,
|
|
33244
|
+
backgroundColor: waveBg(bandRow, usedColumns),
|
|
33245
|
+
...extra
|
|
33246
|
+
});
|
|
33247
|
+
usedColumns += width;
|
|
33248
|
+
};
|
|
33249
|
+
if (sourceIndex === 0) {
|
|
33250
|
+
push(promptGlyph, {
|
|
33251
|
+
color: promptColor,
|
|
33252
|
+
bold: true
|
|
33253
|
+
});
|
|
33254
|
+
push(" ", { color: promptColor });
|
|
33255
|
+
} else {
|
|
33256
|
+
push(" ");
|
|
33257
|
+
push(" ");
|
|
33258
|
+
}
|
|
33259
|
+
for (const span of splitGraphemes(parts.before)) push(span.text);
|
|
33260
|
+
if (parts.hasCaret) push(parts.caret, { inverse: cursorVisible });
|
|
33261
|
+
const tail = placeholder ? COMPOSER_PLACEHOLDER : parts.after;
|
|
33262
|
+
for (const span of splitGraphemes(tail)) push(span.text, placeholder ? { dim: true } : {});
|
|
33263
|
+
while (usedColumns < bandWidth) push(" ");
|
|
33264
|
+
const middleBandRow = Math.floor(totalBandRows / 2);
|
|
33265
|
+
if (bandRow === middleBandRow && deepseekWaveWordVisible(waveTick, waveTier, style)) {
|
|
33266
|
+
const word = waveTier === "unknown" ? "Into the Unknown" : "deepseek";
|
|
33267
|
+
const start = Math.max(2, Math.floor((bandWidth - word.length) / 2));
|
|
33268
|
+
const indices = Array.from({ length: word.length }, (_, at) => cellIndexAtColumn(cells, start + at));
|
|
33269
|
+
if (indices.every((index) => index !== void 0 && (cells[index].char === " " || cells[index].dim === true))) for (let at = 0; at < word.length; at += 1) {
|
|
33270
|
+
const cell = cells[indices[at]];
|
|
33271
|
+
cell.char = word[at];
|
|
33272
|
+
cell.width = 1;
|
|
33273
|
+
cell.color = inkColor(deepseekWaveWordHue(at, hues));
|
|
33274
|
+
cell.bold = true;
|
|
33275
|
+
cell.dim = false;
|
|
33276
|
+
}
|
|
33277
|
+
}
|
|
33278
|
+
if (bandRow === middleBandRow && (waveTier === "deepseek" || waveTier === "unknown") && style === "wave") {
|
|
33279
|
+
const spark = deepseekWaveSpark(waveTick);
|
|
33280
|
+
const lastIndex = cellIndexAtColumn(cells, bandWidth - 1);
|
|
33281
|
+
if (spark !== null && lastIndex !== void 0 && cells[lastIndex].char === " ") {
|
|
33282
|
+
cells[lastIndex].char = spark;
|
|
33283
|
+
cells[lastIndex].color = promptColor;
|
|
33284
|
+
cells[lastIndex].bold = true;
|
|
33285
|
+
cells[lastIndex].dim = false;
|
|
33286
|
+
}
|
|
33287
|
+
}
|
|
33288
|
+
return (0, import_react.createElement)(Text, {
|
|
33289
|
+
key: `editor-${sourceIndex}`,
|
|
33290
|
+
wrap: "truncate-end"
|
|
33291
|
+
}, ...waveRowSpans(cells));
|
|
32812
33292
|
});
|
|
32813
|
-
if (deepseekWaveWordVisible(waveTick, waveTier, style)) {
|
|
32814
|
-
const word = waveTier === "unknown" ? "Into the Unknown" : "deepseek";
|
|
32815
|
-
const start = Math.max(2, Math.floor((bandWidth - word.length) / 2));
|
|
32816
|
-
let clear = true;
|
|
32817
|
-
for (let at = 0; at < word.length; at += 1) {
|
|
32818
|
-
const cell = cells[start + at];
|
|
32819
|
-
if (cell === void 0 || cell.char !== " " && cell.dim !== true) {
|
|
32820
|
-
clear = false;
|
|
32821
|
-
break;
|
|
32822
|
-
}
|
|
32823
|
-
}
|
|
32824
|
-
if (clear) for (let at = 0; at < word.length; at += 1) {
|
|
32825
|
-
const cell = cells[start + at];
|
|
32826
|
-
cell.char = word[at];
|
|
32827
|
-
cell.color = inkColor(deepseekWaveWordHue(at, hues));
|
|
32828
|
-
cell.bold = true;
|
|
32829
|
-
cell.dim = false;
|
|
32830
|
-
}
|
|
32831
|
-
}
|
|
32832
|
-
if ((waveTier === "deepseek" || waveTier === "unknown") && style === "wave") {
|
|
32833
|
-
const spark = deepseekWaveSpark(waveTick);
|
|
32834
|
-
if (spark !== null) {
|
|
32835
|
-
const last = cells[cells.length - 1];
|
|
32836
|
-
if (last !== void 0 && last.char === " ") {
|
|
32837
|
-
last.char = spark;
|
|
32838
|
-
last.color = promptColor;
|
|
32839
|
-
last.bold = true;
|
|
32840
|
-
last.dim = false;
|
|
32841
|
-
}
|
|
32842
|
-
}
|
|
32843
|
-
}
|
|
32844
33293
|
return (0, import_react.createElement)(Box, {
|
|
32845
33294
|
flexDirection: "column",
|
|
32846
33295
|
width: bandWidth
|
|
32847
|
-
}, blankBandRow(0),
|
|
33296
|
+
}, blankBandRow(0), ...editorWaveRows, blankBandRow(totalBandRows - 1));
|
|
32848
33297
|
};
|
|
32849
|
-
return (0, import_react.createElement)(Box, { flexDirection: "column" }, menu, waveTick !== null && waveTier !== null && !busy ? waveRow() : band(staticEditor));
|
|
33298
|
+
return (0, import_react.createElement)(Box, { flexDirection: "column" }, menu, waveTick !== null && waveTier !== null && waveStyle !== null && !busy && !preparingImages ? waveRow() : band(staticEditor));
|
|
32850
33299
|
}
|
|
32851
33300
|
/** Build one settled row (row Box plus its roomy-prompt spacers and row count). */
|
|
32852
33301
|
function buildSettledRow(entry, index, showReasoning, columns) {
|
|
@@ -33992,7 +34441,9 @@ function isSlashLine(line) {
|
|
|
33992
34441
|
/** Substitutable runner effects; production values write to the real terminal. */
|
|
33993
34442
|
const internals = {
|
|
33994
34443
|
mount: (element) => {
|
|
33995
|
-
|
|
34444
|
+
const keyboardEnhanced = shouldEnableKeyboardEnhancement();
|
|
34445
|
+
const focusReporting = isVsCodeTerminalEnv();
|
|
34446
|
+
process.stdout.write((keyboardEnhanced ? KEYBOARD_ENHANCE_ENABLE : "") + BRACKETED_PASTE_ENABLE + (focusReporting ? TERMINAL_FOCUS_REPORT_ENABLE : ""));
|
|
33996
34447
|
const instance = render(element, { exitOnCtrlC: false });
|
|
33997
34448
|
return {
|
|
33998
34449
|
rerender(element) {
|
|
@@ -34000,7 +34451,7 @@ const internals = {
|
|
|
34000
34451
|
},
|
|
34001
34452
|
unmount() {
|
|
34002
34453
|
instance.unmount();
|
|
34003
|
-
process.stdout.write("
|
|
34454
|
+
process.stdout.write((keyboardEnhanced ? KEYBOARD_ENHANCE_DISABLE : "") + BRACKETED_PASTE_DISABLE + (focusReporting ? TERMINAL_FOCUS_REPORT_DISABLE : ""));
|
|
34004
34455
|
}
|
|
34005
34456
|
};
|
|
34006
34457
|
},
|
|
@@ -34504,83 +34955,6 @@ async function removeProviderSettings(ctx, target) {
|
|
|
34504
34955
|
}
|
|
34505
34956
|
}
|
|
34506
34957
|
//#endregion
|
|
34507
|
-
//#region src/mentions.ts
|
|
34508
|
-
/** Menu cap on file rows; the service owns ranking and default rows. */
|
|
34509
|
-
const MAX_FILE_ROWS = 20;
|
|
34510
|
-
/**
|
|
34511
|
-
* Create the mention API for one agent's workspace. A missing
|
|
34512
|
-
* `fileReferences` service (with an agent present) or `sessionReferenceResolver`
|
|
34513
|
-
* degrades that half to empty rows; `prepare` passes text through untouched
|
|
34514
|
-
* without references. An undefined agent (a bare launch before any session
|
|
34515
|
-
* exists) runs the official WorkspaceFileSearch over the launch cwd — the
|
|
34516
|
-
* same class the mounted service uses per agent — so `@` file completion
|
|
34517
|
-
* works from the first keystroke; session references wait for the session.
|
|
34518
|
-
*
|
|
34519
|
-
* `candidates` never reaches for `this` — the runner hands it to the input
|
|
34520
|
-
* editor as a detached callback, and a `this`-bound method would throw on
|
|
34521
|
-
* every `@` key.
|
|
34522
|
-
* @param ctx - context carrying the optional `fileReferences` and
|
|
34523
|
-
* `sessionReferenceResolver` services.
|
|
34524
|
-
* @param agent - the session owner; excluded from its own session candidates.
|
|
34525
|
-
* @param cwd - launch working directory; bounds the pre-session search.
|
|
34526
|
-
*/
|
|
34527
|
-
function createMentions(ctx, agent, cwd) {
|
|
34528
|
-
const resolver = ctx.get("sessionReferenceResolver");
|
|
34529
|
-
const fileReferences = ctx.get("fileReferences");
|
|
34530
|
-
const sessionCapable = agent !== void 0 && resolver !== void 0;
|
|
34531
|
-
let preSessionSearch;
|
|
34532
|
-
const preSessionFiles = (query, signal) => {
|
|
34533
|
-
preSessionSearch ??= new WorkspaceFileSearch(cwd, {
|
|
34534
|
-
maxResults: DEFAULT_FILE_SEARCH_MAX_RESULTS,
|
|
34535
|
-
maxEntries: DEFAULT_FILE_SEARCH_MAX_ENTRIES,
|
|
34536
|
-
excludedDirectories: [...DEFAULT_FILE_SEARCH_EXCLUDED_DIRECTORIES]
|
|
34537
|
-
});
|
|
34538
|
-
return preSessionSearch.list(query, signal ?? new AbortController().signal);
|
|
34539
|
-
};
|
|
34540
|
-
return {
|
|
34541
|
-
async candidates(query, signal) {
|
|
34542
|
-
const needle = query.trim();
|
|
34543
|
-
const [files, sessions] = await Promise.all([agent !== void 0 && fileReferences !== void 0 ? fileReferences.list(agent, needle, signal ?? new AbortController().signal).catch(() => []) : agent === void 0 ? preSessionFiles(needle, signal).catch(() => []) : Promise.resolve([]), sessionCapable && needle !== "" && agent !== void 0 ? resolver.listCandidates(agent, needle, 10, signal).catch(() => []) : Promise.resolve([])]);
|
|
34544
|
-
const fileRows = files.slice(0, MAX_FILE_ROWS).map((candidate) => ({
|
|
34545
|
-
label: candidate.path,
|
|
34546
|
-
description: candidate.kind === "directory" ? "Folder" : "File",
|
|
34547
|
-
kind: candidate.kind,
|
|
34548
|
-
...candidate.kind === "file" ? { path: isAbsolute(candidate.path) ? candidate.path : resolve(cwd, candidate.path) } : {}
|
|
34549
|
-
}));
|
|
34550
|
-
const sessionRows = sessions.map((candidate) => ({
|
|
34551
|
-
label: formatSessionReferenceMention(candidate),
|
|
34552
|
-
description: `Session · ${candidate.cwd ?? "(no cwd)"}`,
|
|
34553
|
-
kind: "session"
|
|
34554
|
-
}));
|
|
34555
|
-
return [...fileRows, ...sessionRows];
|
|
34556
|
-
},
|
|
34557
|
-
parse(text) {
|
|
34558
|
-
return parseSessionReferenceText(text);
|
|
34559
|
-
},
|
|
34560
|
-
async prepare(parsed, signal) {
|
|
34561
|
-
if (parsed.references.length === 0 || resolver === void 0 || agent === void 0) return {
|
|
34562
|
-
text: parsed.text,
|
|
34563
|
-
references: parsed.references
|
|
34564
|
-
};
|
|
34565
|
-
const prepared = await resolver.prepare(agent, [{
|
|
34566
|
-
type: "text",
|
|
34567
|
-
text: parsed.text
|
|
34568
|
-
}], parsed.references, signal);
|
|
34569
|
-
return {
|
|
34570
|
-
text: prepared.content.filter((block) => block.type === "text").map((block) => block.text).join(""),
|
|
34571
|
-
references: parsed.references,
|
|
34572
|
-
additionalContext: prepared.additionalContext
|
|
34573
|
-
};
|
|
34574
|
-
},
|
|
34575
|
-
sessionMention(candidate) {
|
|
34576
|
-
return formatSessionReferenceMention({
|
|
34577
|
-
sessionId: candidate.sessionId,
|
|
34578
|
-
label: candidate.label
|
|
34579
|
-
});
|
|
34580
|
-
}
|
|
34581
|
-
};
|
|
34582
|
-
}
|
|
34583
|
-
//#endregion
|
|
34584
34958
|
//#region src/questions.ts
|
|
34585
34959
|
const ABORT_ERROR = new UserQuestionError("ask_user_question was interrupted before the user answered", "ASK_ABORTED");
|
|
34586
34960
|
/**
|
|
@@ -36331,7 +36705,7 @@ async function run(ctx, startup, io) {
|
|
|
36331
36705
|
copyTextValue: copyText,
|
|
36332
36706
|
loadMentions: (query, signal) => mentions.candidates(query, signal),
|
|
36333
36707
|
inspectImages: (paths) => inspectImagePaths(paths, ctx.get("attachments"), session?.header.cwd ?? cwd),
|
|
36334
|
-
prepareImages: (paths) => saveImagePaths(paths, ctx.get("attachments")),
|
|
36708
|
+
prepareImages: (paths, signal) => saveImagePaths(paths, ctx.get("attachments"), signal),
|
|
36335
36709
|
cyclePermission: cyclePermission$1,
|
|
36336
36710
|
setPermission: setPermissionAction,
|
|
36337
36711
|
selectModel,
|