dsh-code 1.0.7 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +70 -24
- package/README.md +71 -25
- package/bin/deepseek.mjs +202 -39
- package/cordis.patch.yml +13 -4
- package/lib/index.mjs +5002 -1061
- package/lib/session-query.mjs +3 -2
- package/lib/startup.mjs +4 -4
- package/lib/{theme-DCT8Y2xf.mjs → theme-B3orFUYz.mjs} +665 -20
- package/lib/types/app.d.ts +120 -63
- package/lib/types/attachments.d.ts +16 -7
- package/lib/types/authorization-panel.d.ts +3 -3
- package/lib/types/git-workflow.d.ts +91 -2
- package/lib/types/history.d.ts +10 -0
- package/lib/types/i18n.d.ts +39 -0
- package/lib/types/index.d.ts +74 -2
- package/lib/types/input-split.d.ts +1 -1
- package/lib/types/kernel-panels.d.ts +89 -32
- package/lib/types/language-panel.d.ts +12 -0
- package/lib/types/locales/en.d.ts +498 -0
- package/lib/types/locales/zh.d.ts +9 -0
- package/lib/types/mentions.d.ts +7 -3
- package/lib/types/models.d.ts +14 -0
- package/lib/types/panel-accent.d.ts +28 -0
- package/lib/types/rainbow.d.ts +69 -0
- package/lib/types/render/animations.d.ts +42 -0
- package/lib/types/render/inspector.d.ts +26 -0
- package/lib/types/render/lines.d.ts +21 -1
- package/lib/types/render/markdown.d.ts +1 -1
- package/lib/types/render/projection.d.ts +95 -4
- package/lib/types/render/status.d.ts +12 -9
- package/lib/types/render/text.d.ts +6 -0
- package/lib/types/render/usage.d.ts +113 -0
- package/lib/types/session-directory.d.ts +42 -1
- package/lib/types/session-switch.d.ts +8 -0
- package/lib/types/startup.d.ts +1 -1
- package/lib/types/terminal-title.d.ts +8 -0
- package/lib/types/theme-panel.d.ts +2 -2
- package/lib/types/theme.d.ts +271 -52
- package/lib/types/update-panel.d.ts +27 -6
- package/lib/types/update.d.ts +10 -1
- package/lib/types/version.d.ts +6 -3
- package/package.json +26 -7
- package/src/app.ts +1426 -627
- package/src/approval.ts +166 -166
- package/src/attachments.ts +65 -19
- package/src/authorization-panel.ts +24 -18
- package/src/editor-keys.ts +371 -371
- package/src/fork.ts +11 -7
- package/src/git-workflow.ts +229 -3
- package/src/history.ts +14 -0
- package/src/i18n.ts +68 -0
- package/src/index.ts +503 -128
- package/src/input-split.ts +27 -7
- package/src/kernel-panels.ts +528 -113
- package/src/keyboard.ts +5 -4
- package/src/language-panel.ts +53 -0
- package/src/locales/en.ts +538 -0
- package/src/locales/zh.ts +537 -0
- package/src/mentions.ts +8 -4
- package/src/models.ts +264 -212
- package/src/panel-accent.ts +41 -0
- package/src/presets.ts +1 -1
- package/src/provider-settings.ts +1 -1
- package/src/rainbow.ts +218 -0
- package/src/render/animations.ts +104 -6
- package/src/render/editor.ts +20 -20
- package/src/render/export.ts +116 -95
- package/src/render/inspector.ts +42 -0
- package/src/render/lines.ts +628 -415
- package/src/render/markdown.ts +15 -3
- package/src/render/projection.ts +429 -19
- package/src/render/status.ts +119 -62
- package/src/render/text.ts +15 -0
- package/src/render/tool-preview.ts +77 -77
- package/src/render/usage.ts +430 -0
- package/src/render/width.ts +2 -2
- package/src/session-directory.ts +90 -9
- package/src/session-query.ts +8 -4
- package/src/session-switch.ts +14 -0
- package/src/startup.ts +3 -3
- package/src/store.ts +19 -1
- package/src/subagents.ts +229 -229
- package/src/terminal-title.ts +22 -5
- package/src/theme-panel.ts +17 -21
- package/src/theme.ts +281 -33
- package/src/update-panel.ts +148 -31
- package/src/update.ts +19 -3
- package/src/version.ts +63 -20
- package/src/whale-glyph.ts +23 -23
package/src/rainbow.ts
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The rainbow theme's per-launch roll: a seeded shuffle over a twelve-color
|
|
3
|
+
* bright-tier spectrum that fills every palette role, the status-bar tone map
|
|
4
|
+
* (adjacent tones in canonical on-screen order never share a color, the
|
|
5
|
+
* row1/row2 boundary included), the panel accent ring, and full-spectrum
|
|
6
|
+
* flow anchors with a random phase. The roll happens once per launch and
|
|
7
|
+
* stays stable for the whole run; setting RAINBOW_SEED=<uint32> pins it for
|
|
8
|
+
* reproduction (the /theme notification prints the seed of the current roll).
|
|
9
|
+
*
|
|
10
|
+
* All pool colors clear AA body text (≥4.5:1) on a black terminal by a wide
|
|
11
|
+
* margin (11-17:1); diff rows pair a bright hue with its own 22% dark tint
|
|
12
|
+
* so the foreground stays 7.7-10.2:1 on its background.
|
|
13
|
+
*
|
|
14
|
+
* @module @deepseek-ai/dsh-tui/rainbow
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { StatusTone } from './render/status.ts'
|
|
18
|
+
import type { RgbTriple, ThemePalette } from './theme.ts'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The bright-tier spectrum: twelve Tailwind *-300 hues covering red through
|
|
22
|
+
* fuchsia, every one ≥11:1 on black. The roll samples this pool by shuffle;
|
|
23
|
+
* nothing outside it is ever painted.
|
|
24
|
+
*/
|
|
25
|
+
export const RAINBOW_POOL: readonly RgbTriple[] = [
|
|
26
|
+
[252, 165, 165],
|
|
27
|
+
[253, 186, 116],
|
|
28
|
+
[253, 230, 138],
|
|
29
|
+
[253, 224, 71],
|
|
30
|
+
[190, 242, 100],
|
|
31
|
+
[134, 239, 172],
|
|
32
|
+
[94, 234, 212],
|
|
33
|
+
[103, 232, 249],
|
|
34
|
+
[125, 211, 252],
|
|
35
|
+
[147, 197, 253],
|
|
36
|
+
[196, 181, 253],
|
|
37
|
+
[240, 171, 252],
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Status tones in canonical on-screen order (row one left-to-right, then row
|
|
42
|
+
* two): consecutive entries here are the pairs the "adjacent spans differ"
|
|
43
|
+
* guarantee covers, including the accent→value step across the row boundary.
|
|
44
|
+
*/
|
|
45
|
+
const TONE_ORDER: readonly StatusTone[] = [
|
|
46
|
+
'live', 'model', 'path', 'branch', 'accent',
|
|
47
|
+
'value', 'label', 'meta', 'ctxFill', 'success', 'plan', 'warn', 'error',
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
/** One per-launch carnival roll: everything the rainbow theme paints from. */
|
|
51
|
+
export interface RainbowRoll {
|
|
52
|
+
/** The seed this roll came from (print it to reproduce). */
|
|
53
|
+
readonly seed: number
|
|
54
|
+
/** The full 15-token palette for this launch. */
|
|
55
|
+
readonly palette: ThemePalette
|
|
56
|
+
/** The four-color panel ring (random order and start). */
|
|
57
|
+
readonly ring: readonly RgbTriple[]
|
|
58
|
+
/** Status-tone → color: adjacent tones in {@link TONE_ORDER} never match. */
|
|
59
|
+
readonly toneColors: Readonly<Record<StatusTone, RgbTriple>>
|
|
60
|
+
/** Full-spectrum flow anchors in hue order (a different subset every launch). */
|
|
61
|
+
readonly flowAnchors: readonly RgbTriple[]
|
|
62
|
+
/** Random phase offset into the flow lap, milliseconds. */
|
|
63
|
+
readonly flowPhaseMs: number
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Parse a RAINBOW_SEED value: a non-negative decimal uint32, else nothing. */
|
|
67
|
+
export function parseRainbowSeed(value: string | undefined): number | undefined {
|
|
68
|
+
if (value === undefined || !/^\d{1,10}$/.test(value.trim())) return undefined
|
|
69
|
+
const parsed = Number(value.trim())
|
|
70
|
+
return Number.isSafeInteger(parsed) ? parsed >>> 0 : undefined
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* One parsed `/rainbow` argument: empty means a fresh random roll; a
|
|
75
|
+
* decimal uint32 pins that seed; anything else is a usage error.
|
|
76
|
+
* @param argument - the raw text after `/rainbow`.
|
|
77
|
+
* @returns `{ seed }`, `'random'`, or `'usage'`.
|
|
78
|
+
*/
|
|
79
|
+
export function parseRainbowArgument(argument: string): { seed: number } | 'random' | 'usage' {
|
|
80
|
+
const trimmed = argument.trim()
|
|
81
|
+
if (trimmed === '') return 'random'
|
|
82
|
+
const parsed = parseRainbowSeed(trimmed)
|
|
83
|
+
return parsed === undefined ? 'usage' : { seed: parsed }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Deterministic 32-bit RNG (mulberry32) — one roll, one stream. */
|
|
87
|
+
function mulberry32(seed: number): () => number {
|
|
88
|
+
let state = seed >>> 0
|
|
89
|
+
return () => {
|
|
90
|
+
state = (state + 0x6d2b79f5) | 0
|
|
91
|
+
let t = Math.imul(state ^ (state >>> 15), 1 | state)
|
|
92
|
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t
|
|
93
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Fisher–Yates shuffle over a copy, driven by the roll's RNG. */
|
|
98
|
+
function shuffle<T>(items: readonly T[], rng: () => number): T[] {
|
|
99
|
+
const out = [...items]
|
|
100
|
+
for (let i = out.length - 1; i > 0; i -= 1) {
|
|
101
|
+
const j = Math.floor(rng() * (i + 1))
|
|
102
|
+
;[out[i], out[j]] = [out[j], out[i]]
|
|
103
|
+
}
|
|
104
|
+
return out
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Approximate HSL hue of one RGB triple, degrees in [0, 360). */
|
|
108
|
+
function hueOf([r, g, b]: RgbTriple): number {
|
|
109
|
+
const max = Math.max(r, g, b)
|
|
110
|
+
const min = Math.min(r, g, b)
|
|
111
|
+
if (max === min) return 0
|
|
112
|
+
const span = max - min
|
|
113
|
+
if (max === r) return (60 * (g - b) / span + 360) % 360
|
|
114
|
+
if (max === g) return (60 * (b - r) / span + 120) % 360
|
|
115
|
+
return (60 * (r - g) / span + 240) % 360
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Strict RGB equality for the wrap-collision repair. */
|
|
119
|
+
function sameRgb(left: RgbTriple, right: RgbTriple): boolean {
|
|
120
|
+
return left[0] === right[0] && left[1] === right[1] && left[2] === right[2]
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** A dark row tint for diff backgrounds: the hue at 22% strength over black. */
|
|
124
|
+
function darkTint(hue: RgbTriple): RgbTriple {
|
|
125
|
+
return [Math.round(hue[0] * 0.22), Math.round(hue[1] * 0.22), Math.round(hue[2] * 0.22)]
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Readability anchors shared by every roll: captions, text, band stay calm. */
|
|
129
|
+
const DIM: RgbTriple = [166, 163, 184]
|
|
130
|
+
const TEXT: RgbTriple = [240, 238, 249]
|
|
131
|
+
const BAND: RgbTriple = [46, 46, 52]
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Build the roll for one seed: pure and deterministic, so tests pin exact
|
|
135
|
+
* outcomes and RAINBOW_SEED reproduces a lucky launch exactly.
|
|
136
|
+
* @param seed - the uint32 seed to roll from.
|
|
137
|
+
* @returns the complete carnival roll.
|
|
138
|
+
*/
|
|
139
|
+
export function rollRainbow(seed: number): RainbowRoll {
|
|
140
|
+
const rng = mulberry32(seed)
|
|
141
|
+
const pool = shuffle(RAINBOW_POOL, rng)
|
|
142
|
+
const [brand, brandBright, brandMid, brandDeep, code, success, error, warn, addHue, delHue] = pool
|
|
143
|
+
const palette: ThemePalette = {
|
|
144
|
+
brand: brand,
|
|
145
|
+
brandBright: brandBright,
|
|
146
|
+
brandMid: brandMid,
|
|
147
|
+
brandDeep: brandDeep,
|
|
148
|
+
dim: DIM,
|
|
149
|
+
success: success,
|
|
150
|
+
error: error,
|
|
151
|
+
warn: warn,
|
|
152
|
+
text: TEXT,
|
|
153
|
+
code: code,
|
|
154
|
+
composerBand: BAND,
|
|
155
|
+
diffAdd: darkTint(addHue),
|
|
156
|
+
diffDel: darkTint(delHue),
|
|
157
|
+
diffAddFg: addHue,
|
|
158
|
+
diffDelFg: delHue,
|
|
159
|
+
// Prompt-row bars ride the roll's own colors: the bright brand hue for an
|
|
160
|
+
// ordinary prompt, the rolled warning hue for a queued one, and the rolled
|
|
161
|
+
// code hue for a steered one. Each bar is that color over the surface.
|
|
162
|
+
surface: [0, 0, 0],
|
|
163
|
+
prompt: brandBright,
|
|
164
|
+
queued: warn,
|
|
165
|
+
steered: code,
|
|
166
|
+
}
|
|
167
|
+
const ring = shuffle(RAINBOW_POOL, rng).slice(0, 4)
|
|
168
|
+
// Consecutive tones take consecutive slots of a 12-distinct shuffle, so
|
|
169
|
+
// on-screen neighbors never match. 13 tones wrap: live (index 0) and error
|
|
170
|
+
// (index 12) would share a color. Reassign error to a pool hue that is
|
|
171
|
+
// neither live nor its neighbor warn — error must not look like the busy dot.
|
|
172
|
+
const offset = Math.floor(rng() * RAINBOW_POOL.length)
|
|
173
|
+
const toneColors = {} as Record<StatusTone, RgbTriple>
|
|
174
|
+
TONE_ORDER.forEach((tone, index) => {
|
|
175
|
+
toneColors[tone] = pool[(offset + index) % pool.length]!
|
|
176
|
+
})
|
|
177
|
+
if (sameRgb(toneColors.live, toneColors.error)) {
|
|
178
|
+
const replacement = pool.find(hue => !sameRgb(hue, toneColors.live) && !sameRgb(hue, toneColors.warn))
|
|
179
|
+
if (replacement !== undefined) toneColors.error = replacement
|
|
180
|
+
}
|
|
181
|
+
const flowAnchors = [...RAINBOW_POOL].sort((a, b) => hueOf(a) - hueOf(b))
|
|
182
|
+
const flowPhaseMs = Math.floor(rng() * 2400)
|
|
183
|
+
return { seed, palette, ring, toneColors, flowAnchors, flowPhaseMs }
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** The launch seed: RAINBOW_SEED when parseable, else a fresh random roll. */
|
|
187
|
+
function launchSeed(): number {
|
|
188
|
+
const pinned = parseRainbowSeed(process.env.RAINBOW_SEED)
|
|
189
|
+
if (pinned !== undefined) return pinned
|
|
190
|
+
return (Date.now() ^ Math.floor(Math.random() * 0x100000000)) >>> 0
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
let rolled: RainbowRoll | undefined
|
|
194
|
+
|
|
195
|
+
/** The memoized per-launch roll; computed once, stable for the whole run. */
|
|
196
|
+
export function rainbowRoll(): RainbowRoll {
|
|
197
|
+
if (rolled === undefined) rolled = rollRainbow(launchSeed())
|
|
198
|
+
return rolled
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Replace the memoized roll: omit the seed for a fresh random one, or pass
|
|
203
|
+
* a uint32 to pin it. `/rainbow` uses this so a mid-session reroll actually
|
|
204
|
+
* recolors; {@link setTheme}(`'rainbow'`) must run afterwards so the
|
|
205
|
+
* palette accessor picks the new values up.
|
|
206
|
+
* @param seed - the seed to pin, or undefined for a new random roll.
|
|
207
|
+
* @returns the roll now in force.
|
|
208
|
+
*/
|
|
209
|
+
export function rerollRainbow(seed?: number): RainbowRoll {
|
|
210
|
+
const next = seed ?? ((Date.now() ^ Math.floor(Math.random() * 0x100000000)) >>> 0)
|
|
211
|
+
rolled = rollRainbow(next)
|
|
212
|
+
return rolled
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** The current roll's seed as display copy (also the RAINBOW_SEED value). */
|
|
216
|
+
export function rainbowSeedLabel(): string {
|
|
217
|
+
return String(rainbowRoll().seed)
|
|
218
|
+
}
|
package/src/render/animations.ts
CHANGED
|
@@ -91,6 +91,31 @@ export function deepDivingSparkColor(tick: number, base: RgbTriple, highlight: R
|
|
|
91
91
|
return blendRgb(highlight, base, deepDivingSparkIntensity(tick))
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
/** One full prismatic flow lap — lively: violet → fuchsia → cyan → violet in 2.4s. */
|
|
95
|
+
export const FLOW_PERIOD_MS = 2_400
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The prismatic flow color at one elapsed-time sample: a smoothstep walk
|
|
99
|
+
* around the anchor triangle, one full lap per {@link FLOW_PERIOD_MS}. Pure —
|
|
100
|
+
* the Ink layer owns the timer and passes its tick scaled by its own cadence
|
|
101
|
+
* (tick × tickMs). Callers gate on the animations preference and fall back to
|
|
102
|
+
* a static palette color when animation is off.
|
|
103
|
+
* @param elapsedMs - milliseconds since the flow started (any sign or size).
|
|
104
|
+
* @param anchors - the colors to walk, in order (theme.ts FLOW_ANCHORS).
|
|
105
|
+
* @returns the interpolated anchor color at this instant.
|
|
106
|
+
*/
|
|
107
|
+
export function flowColor(elapsedMs: number, anchors: readonly RgbTriple[]): RgbTriple {
|
|
108
|
+
if (anchors.length === 0) throw new Error('flowColor needs at least one anchor')
|
|
109
|
+
if (anchors.length === 1) return anchors[0]
|
|
110
|
+
const lap = ((elapsedMs % FLOW_PERIOD_MS) + FLOW_PERIOD_MS) % FLOW_PERIOD_MS
|
|
111
|
+
const span = FLOW_PERIOD_MS / anchors.length
|
|
112
|
+
const at = lap / span
|
|
113
|
+
const index = Math.floor(at)
|
|
114
|
+
const phase = at - index
|
|
115
|
+
const eased = phase * phase * (3 - 2 * phase)
|
|
116
|
+
return blendRgb(anchors[(index + 1) % anchors.length], anchors[index % anchors.length], eased)
|
|
117
|
+
}
|
|
118
|
+
|
|
94
119
|
/** Caret blink cadence: one blink step (on or off) per tick. */
|
|
95
120
|
export const CARET_BLINK_TICK_MS = 530
|
|
96
121
|
|
|
@@ -332,6 +357,79 @@ export function envelope(elapsed: number, total: number, fadeIn: number, fadeOut
|
|
|
332
357
|
return Math.min(Math.max(Math.min(rise, fall), 0), 1)
|
|
333
358
|
}
|
|
334
359
|
|
|
360
|
+
/**
|
|
361
|
+
* The /rainbow celebration on the three-row composer band: a FIXED
|
|
362
|
+
* seven-color spectrum (not the rolled palette) that slides across every
|
|
363
|
+
* row as one ribbon. Triggered when switching to rainbow or rerolling the
|
|
364
|
+
* seed; independent of RAINBOW_SEED so the burst always reads as a prism.
|
|
365
|
+
*/
|
|
366
|
+
export const RAINBOW_BURST_TICK_MS = 33
|
|
367
|
+
export const RAINBOW_BURST_DURATION_MS = 1_800
|
|
368
|
+
export const RAINBOW_BURST_HUES: readonly RgbTriple[] = [
|
|
369
|
+
[255, 59, 48],
|
|
370
|
+
[255, 149, 0],
|
|
371
|
+
[255, 204, 0],
|
|
372
|
+
[52, 199, 89],
|
|
373
|
+
[0, 199, 190],
|
|
374
|
+
[10, 132, 255],
|
|
375
|
+
[175, 82, 222],
|
|
376
|
+
]
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Smoothstep in 0..1, then wrap-lerp around the seven burst hues.
|
|
380
|
+
* Position 0 is red, wrapping back toward red at 1. Shared by the
|
|
381
|
+
* composer burst and the static rainbow whale header.
|
|
382
|
+
*/
|
|
383
|
+
export function rainbowSpectrumHue(position: number): RgbTriple {
|
|
384
|
+
const hues = RAINBOW_BURST_HUES
|
|
385
|
+
const x = ((position % 1) + 1) % 1
|
|
386
|
+
const scaled = x * hues.length
|
|
387
|
+
const index = Math.floor(scaled)
|
|
388
|
+
const t = scaled - index
|
|
389
|
+
const eased = t * t * (3 - 2 * t)
|
|
390
|
+
const a = hues[index % hues.length]
|
|
391
|
+
const b = hues[(index + 1) % hues.length]
|
|
392
|
+
return [
|
|
393
|
+
Math.round(a[0] + (b[0] - a[0]) * eased),
|
|
394
|
+
Math.round(a[1] + (b[1] - a[1]) * eased),
|
|
395
|
+
Math.round(a[2] + (b[2] - a[2]) * eased),
|
|
396
|
+
]
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Background tint for one composer-band cell during the rainbow burst.
|
|
401
|
+
* Every row of a column shares the same hue (a solid ribbon); edge rows
|
|
402
|
+
* are slightly dimmer so the middle editor row reads as the crest. The
|
|
403
|
+
* spectrum slides ~1.2 widths over the burst, then fades to the band base.
|
|
404
|
+
* @param tick - frame index at {@link RAINBOW_BURST_TICK_MS}.
|
|
405
|
+
* @param column - band column (0..width-1).
|
|
406
|
+
* @param width - band width in columns.
|
|
407
|
+
* @param base - the theme's composerBand color to blend toward.
|
|
408
|
+
* @param row - band row (0..rows-1).
|
|
409
|
+
* @param rows - band height (composer is three rows: pad, editor, pad).
|
|
410
|
+
* @returns the blended RGB, or null after the burst (or at zero alpha).
|
|
411
|
+
*/
|
|
412
|
+
export function rainbowBurstColumnBg(
|
|
413
|
+
tick: number,
|
|
414
|
+
column: number,
|
|
415
|
+
width: number,
|
|
416
|
+
base: RgbTriple,
|
|
417
|
+
row = 0,
|
|
418
|
+
rows = 1,
|
|
419
|
+
): RgbTriple | null {
|
|
420
|
+
const elapsed = tick * RAINBOW_BURST_TICK_MS / 1000
|
|
421
|
+
const total = RAINBOW_BURST_DURATION_MS / 1000
|
|
422
|
+
const fade = envelope(elapsed, total, 0.18, 0.45)
|
|
423
|
+
if (fade <= 0) return null
|
|
424
|
+
const span = Math.max(1, width - 1)
|
|
425
|
+
const slide = elapsed / total * 1.2
|
|
426
|
+
const mixed = rainbowSpectrumHue(column / span + slide)
|
|
427
|
+
const edge = rows > 1 && (row === 0 || row === rows - 1)
|
|
428
|
+
const alpha = Math.min(0.72 * fade * (edge ? 0.78 : 1), 0.70)
|
|
429
|
+
if (alpha < 0.02) return null
|
|
430
|
+
return blendRgb(mixed, base, alpha)
|
|
431
|
+
}
|
|
432
|
+
|
|
335
433
|
/** Per-band sampling context: everything geometry needs beyond the timeline. */
|
|
336
434
|
interface BandContext {
|
|
337
435
|
/** Aspect-weighted row offset from the band's center row (Pulse 2-D ring). */
|
|
@@ -522,19 +620,19 @@ export function deepseekWaveColumnBg(
|
|
|
522
620
|
let bandIndex = 0
|
|
523
621
|
for (const band of DEEPSEEK_WAVE_BANDS[style][tier]) {
|
|
524
622
|
for (const [hue, strength] of bandSample(style, band, elapsed, column, width, { dy, u, undulating, pulseSpan, bandIndex, total })) {
|
|
525
|
-
weights[hue] = style === 'aurora' ? weights[hue]
|
|
623
|
+
weights[hue] = style === 'aurora' ? weights[hue] + strength : Math.max(weights[hue], strength)
|
|
526
624
|
}
|
|
527
625
|
bandIndex += 1
|
|
528
626
|
}
|
|
529
|
-
const weight = weights[0]
|
|
627
|
+
const weight = weights[0] + weights[1] + weights[2]
|
|
530
628
|
if (weight <= 0.01) return null
|
|
531
629
|
let red = 0
|
|
532
630
|
let green = 0
|
|
533
631
|
let blue = 0
|
|
534
632
|
for (let index = 0; index < 3; index += 1) {
|
|
535
|
-
red += weights[index]
|
|
536
|
-
green += weights[index]
|
|
537
|
-
blue += weights[index]
|
|
633
|
+
red += weights[index] * hues[index][0]
|
|
634
|
+
green += weights[index] * hues[index][1]
|
|
635
|
+
blue += weights[index] * hues[index][2]
|
|
538
636
|
}
|
|
539
637
|
const mixed: RgbTriple = [
|
|
540
638
|
Math.round(red / weight),
|
|
@@ -590,7 +688,7 @@ export function deepseekWaveWordVisible(tick: number, tier: DeepseekWaveTier, st
|
|
|
590
688
|
* @returns the hue for that character.
|
|
591
689
|
*/
|
|
592
690
|
export function deepseekWaveWordHue(index: number, hues: readonly [RgbTriple, RgbTriple, RgbTriple]): RgbTriple {
|
|
593
|
-
return hues[index % hues.length]
|
|
691
|
+
return hues[index % hues.length]
|
|
594
692
|
}
|
|
595
693
|
|
|
596
694
|
/**
|
package/src/render/editor.ts
CHANGED
|
@@ -58,7 +58,7 @@ export function splitGraphemes(text: string): readonly GraphemeSpan[] {
|
|
|
58
58
|
/** Round one UTF-16 offset down to the grapheme boundary at or before it. */
|
|
59
59
|
function floorBoundary(spans: readonly GraphemeSpan[], offset: number): number {
|
|
60
60
|
for (let index = spans.length - 1; index >= 0; index -= 1) {
|
|
61
|
-
const span = spans[index]
|
|
61
|
+
const span = spans[index]
|
|
62
62
|
if (span.end <= offset) return span.end
|
|
63
63
|
if (span.start < offset) return span.start
|
|
64
64
|
}
|
|
@@ -85,7 +85,7 @@ export function clampCursor(value: string, offset: number): number {
|
|
|
85
85
|
export function deleteLastGrapheme(text: string): string {
|
|
86
86
|
if (text === '') return ''
|
|
87
87
|
const spans = splitGraphemes(text)
|
|
88
|
-
return text.slice(0, spans[spans.length - 1]
|
|
88
|
+
return text.slice(0, spans[spans.length - 1].start)
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
/**
|
|
@@ -100,7 +100,7 @@ export function moveCursorBy(value: string, offset: number, delta: number): numb
|
|
|
100
100
|
const current = boundaries.indexOf(clampCursor(value, offset))
|
|
101
101
|
if (current === -1) return clampCursor(value, offset)
|
|
102
102
|
const next = Math.max(0, Math.min(boundaries.length - 1, current + delta))
|
|
103
|
-
return boundaries[next]
|
|
103
|
+
return boundaries[next]
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
/**
|
|
@@ -221,9 +221,9 @@ export function editorRowParts(
|
|
|
221
221
|
if (!caretEnabled || rowIndex !== caretRow) return { before: '', caret: '', after: row.text, hasCaret: false }
|
|
222
222
|
const at = row.offsets.indexOf(cursor)
|
|
223
223
|
if (at < 0) return { before: '', caret: '', after: row.text, hasCaret: false }
|
|
224
|
-
const before = at > 0 ? row.text.slice(0, row.cuts[at]
|
|
225
|
-
const caret = at < row.cuts.length - 1 ? row.text.slice(row.cuts[at]
|
|
226
|
-
const after = at < row.cuts.length - 1 ? row.text.slice(row.cuts[at + 1]
|
|
224
|
+
const before = at > 0 ? row.text.slice(0, row.cuts[at]) : ''
|
|
225
|
+
const caret = at < row.cuts.length - 1 ? row.text.slice(row.cuts[at], row.cuts[at + 1]) : ' '
|
|
226
|
+
const after = at < row.cuts.length - 1 ? row.text.slice(row.cuts[at + 1]) : ''
|
|
227
227
|
return { before, caret, after, hasCaret: true }
|
|
228
228
|
}
|
|
229
229
|
|
|
@@ -231,12 +231,12 @@ export function editorRowParts(
|
|
|
231
231
|
export function caretSite(model: EditorModel, offset: number): CaretSite {
|
|
232
232
|
const target = Math.max(0, Math.min(model.length, offset))
|
|
233
233
|
for (let index = model.rows.length - 1; index >= 0; index -= 1) {
|
|
234
|
-
const row = model.rows[index]
|
|
234
|
+
const row = model.rows[index]
|
|
235
235
|
const at = row.offsets.indexOf(target)
|
|
236
|
-
if (at >= 0) return { row: index, column: row.columns[at]
|
|
236
|
+
if (at >= 0) return { row: index, column: row.columns[at] }
|
|
237
237
|
}
|
|
238
238
|
const last = model.rows[model.rows.length - 1]
|
|
239
|
-
return { row: model.rows.length - 1, column: last === undefined ? 0 : last.columns[last.columns.length - 1]
|
|
239
|
+
return { row: model.rows.length - 1, column: last === undefined ? 0 : last.columns[last.columns.length - 1] }
|
|
240
240
|
}
|
|
241
241
|
|
|
242
242
|
/**
|
|
@@ -250,14 +250,14 @@ export function moveCursorVertically(model: EditorModel, offset: number, preferr
|
|
|
250
250
|
if (delta === 0) return offset
|
|
251
251
|
if (target < 0) return 0
|
|
252
252
|
if (target >= model.rows.length) return model.length
|
|
253
|
-
const row = model.rows[target]
|
|
254
|
-
const wanted = Math.max(0, Math.min(preferredColumn, row.columns[row.columns.length - 1]
|
|
253
|
+
const row = model.rows[target]
|
|
254
|
+
const wanted = Math.max(0, Math.min(preferredColumn, row.columns[row.columns.length - 1]))
|
|
255
255
|
let best = 0
|
|
256
256
|
for (let index = 1; index < row.columns.length; index += 1) {
|
|
257
|
-
if (row.columns[index]
|
|
257
|
+
if (row.columns[index] <= wanted) best = index
|
|
258
258
|
else break
|
|
259
259
|
}
|
|
260
|
-
return row.offsets[best]
|
|
260
|
+
return row.offsets[best]
|
|
261
261
|
}
|
|
262
262
|
|
|
263
263
|
/** The start/end offsets of the logical line containing the cursor. */
|
|
@@ -309,13 +309,13 @@ export function moveWordLeft(value: string, offset: number): number {
|
|
|
309
309
|
// left-hand fragment, instead of skipping the whole containing run.
|
|
310
310
|
let index = runs.findLastIndex(run => run.start < cursor)
|
|
311
311
|
if (index < 0) return 0
|
|
312
|
-
if (runs[index]
|
|
312
|
+
if (runs[index].class === 'space') {
|
|
313
313
|
index -= 1
|
|
314
314
|
if (index < 0) return 0
|
|
315
315
|
}
|
|
316
316
|
let target = index
|
|
317
|
-
while (target > 0 && runs[target]
|
|
318
|
-
return runs[target]
|
|
317
|
+
while (target > 0 && runs[target].class === 'punct' && runs[target - 1].class === 'punct') target -= 1
|
|
318
|
+
return runs[target].start
|
|
319
319
|
}
|
|
320
320
|
|
|
321
321
|
/**
|
|
@@ -329,13 +329,13 @@ export function moveWordRight(value: string, offset: number): number {
|
|
|
329
329
|
// right-hand fragment, instead of jumping straight to the following word.
|
|
330
330
|
let index = runs.findIndex(run => run.end > cursor)
|
|
331
331
|
if (index >= runs.length) return value.length
|
|
332
|
-
if (runs[index]
|
|
332
|
+
if (runs[index].class === 'space') {
|
|
333
333
|
index += 1
|
|
334
334
|
if (index >= runs.length) return value.length
|
|
335
335
|
}
|
|
336
336
|
let target = index
|
|
337
|
-
while (target < runs.length - 1 && runs[target]
|
|
338
|
-
return runs[target]
|
|
337
|
+
while (target < runs.length - 1 && runs[target].class === 'punct' && runs[target + 1].class === 'punct') target += 1
|
|
338
|
+
return runs[target].end
|
|
339
339
|
}
|
|
340
340
|
|
|
341
341
|
/** One edit outcome: the next draft value, cursor, and killed span (if any). */
|
|
@@ -362,7 +362,7 @@ function replaceRange(value: string, cursor: number, start: number, end: number,
|
|
|
362
362
|
export function deleteBackward(value: string, cursor: number): EditResult {
|
|
363
363
|
if (cursor === 0) return { value, cursor, killed: undefined }
|
|
364
364
|
const spans = splitGraphemes(value.slice(0, cursor))
|
|
365
|
-
const start = spans.length === 0 ? 0 : spans[spans.length - 1]
|
|
365
|
+
const start = spans.length === 0 ? 0 : spans[spans.length - 1].start
|
|
366
366
|
return replaceRange(value, cursor, start, cursor, false)
|
|
367
367
|
}
|
|
368
368
|
|