@oh-my-pi/pi-coding-agent 17.1.0 → 17.1.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/CHANGELOG.md +55 -0
- package/dist/cli.js +6325 -4420
- package/dist/types/cli/bench-cli.d.ts +1 -0
- package/dist/types/config/model-resolver.d.ts +6 -3
- package/dist/types/config/settings-schema.d.ts +62 -0
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +3 -0
- package/dist/types/launch/broker-list-order.test.d.ts +1 -0
- package/dist/types/live/attestation.d.ts +2 -0
- package/dist/types/live/controller.d.ts +10 -2
- package/dist/types/live/protocol.d.ts +1 -1
- package/dist/types/live/transport.d.ts +6 -19
- package/dist/types/live/visualizer.d.ts +8 -11
- package/dist/types/modes/components/assistant-message.d.ts +1 -0
- package/dist/types/modes/components/custom-message.d.ts +1 -1
- package/dist/types/modes/components/message-frame.d.ts +8 -4
- package/dist/types/modes/components/session-account-selector.d.ts +11 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +3 -6
- package/dist/types/modes/types.d.ts +9 -5
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -6
- package/dist/types/session/agent-session-types.d.ts +8 -1
- package/dist/types/session/agent-session.d.ts +20 -1
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/session/eval-runner.d.ts +2 -0
- package/dist/types/session/messages.d.ts +2 -0
- package/dist/types/session/session-tools.d.ts +15 -0
- package/dist/types/session/streaming-output.d.ts +8 -0
- package/dist/types/slash-commands/helpers/session-pin.d.ts +9 -0
- package/dist/types/stt/index.d.ts +0 -2
- package/dist/types/stt/stt-controller.d.ts +7 -0
- package/dist/types/task/executor.d.ts +3 -1
- package/dist/types/task/structured-subagent.d.ts +3 -0
- package/dist/types/task/types.d.ts +11 -11
- package/dist/types/thinking.d.ts +13 -0
- package/dist/types/tiny/title-client.d.ts +10 -0
- package/dist/types/tools/builtin-names.d.ts +1 -1
- package/dist/types/tools/computer/protocol.d.ts +43 -0
- package/dist/types/tools/computer/supervisor.d.ts +32 -0
- package/dist/types/tools/computer/worker-entry.d.ts +1 -0
- package/dist/types/tools/computer/worker.d.ts +15 -0
- package/dist/types/tools/computer-renderer.d.ts +22 -0
- package/dist/types/tools/computer.d.ts +71 -0
- package/dist/types/tools/context.d.ts +2 -0
- package/dist/types/tools/default-renderer.d.ts +21 -0
- package/dist/types/tools/eval-format/index.d.ts +7 -0
- package/dist/types/tools/eval-format/javascript.d.ts +2 -0
- package/dist/types/tools/eval-format/julia.d.ts +2 -0
- package/dist/types/tools/eval-format/python.d.ts +2 -0
- package/dist/types/tools/eval-format/ruby.d.ts +2 -0
- package/dist/types/tools/index.d.ts +2 -0
- package/dist/types/tools/resolve.d.ts +7 -0
- package/dist/types/tools/todo.d.ts +4 -1
- package/dist/types/tts/streaming-player.d.ts +10 -43
- package/dist/types/utils/tools-manager.d.ts +1 -2
- package/dist/types/web/search/providers/base.d.ts +16 -0
- package/dist/types/web/search/providers/brave.d.ts +2 -0
- package/dist/types/web/search/providers/firecrawl.d.ts +2 -0
- package/dist/types/web/search/providers/gemini.d.ts +3 -0
- package/dist/types/web/search/providers/jina.d.ts +2 -0
- package/dist/types/web/search/providers/kagi.d.ts +2 -0
- package/dist/types/web/search/providers/kimi.d.ts +2 -0
- package/dist/types/web/search/providers/parallel.d.ts +2 -0
- package/dist/types/web/search/providers/perplexity.d.ts +3 -0
- package/dist/types/web/search/providers/searxng.d.ts +10 -0
- package/dist/types/web/search/providers/tavily.d.ts +8 -0
- package/dist/types/web/search/query.d.ts +190 -0
- package/package.json +12 -12
- package/src/cli/args.ts +1 -0
- package/src/cli/bench-cli.ts +12 -1
- package/src/cli/setup-cli.ts +2 -14
- package/src/cli/web-search-cli.ts +7 -0
- package/src/cli.ts +8 -0
- package/src/config/model-registry.ts +6 -0
- package/src/config/model-resolver.ts +17 -6
- package/src/config/settings-schema.ts +66 -0
- package/src/debug/raw-sse-buffer.ts +157 -30
- package/src/eval/executor-base.ts +1 -0
- package/src/eval/js/executor.ts +2 -0
- package/src/exec/bash-executor.ts +1 -0
- package/src/export/share.ts +4 -3
- package/src/extensibility/extensions/wrapper.ts +68 -12
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +7 -1
- package/src/launch/broker-list-order.test.ts +89 -0
- package/src/launch/broker.ts +49 -8
- package/src/live/attestation.ts +91 -0
- package/src/live/controller.ts +76 -23
- package/src/live/protocol.test.ts +3 -3
- package/src/live/protocol.ts +1 -1
- package/src/live/transport.ts +72 -140
- package/src/live/visualizer.ts +114 -134
- package/src/modes/components/assistant-message.ts +7 -2
- package/src/modes/components/custom-message.ts +4 -1
- package/src/modes/components/message-frame.ts +14 -8
- package/src/modes/components/session-account-selector.ts +62 -0
- package/src/modes/components/tool-execution.ts +17 -110
- package/src/modes/controllers/input-controller.ts +55 -47
- package/src/modes/controllers/live-command-controller.ts +82 -5
- package/src/modes/controllers/selector-controller.ts +62 -0
- package/src/modes/interactive-mode.ts +79 -5
- package/src/modes/types.ts +10 -4
- package/src/modes/utils/ui-helpers.ts +7 -8
- package/src/prompts/system/computer-safety.md +14 -0
- package/src/prompts/system/resolve-device-reminder.md +1 -1
- package/src/prompts/system/workflow-notice.md +1 -1
- package/src/prompts/tools/ast-edit.md +1 -0
- package/src/prompts/tools/bash.md +1 -0
- package/src/prompts/tools/computer.md +26 -0
- package/src/prompts/tools/eval.md +2 -2
- package/src/prompts/tools/task.md +5 -2
- package/src/prompts/tools/web-search.md +2 -0
- package/src/sdk.ts +13 -4
- package/src/session/agent-session-types.ts +9 -0
- package/src/session/agent-session.ts +70 -0
- package/src/session/auth-storage.ts +1 -0
- package/src/session/eval-runner.ts +5 -0
- package/src/session/messages.ts +3 -0
- package/src/session/queued-messages.ts +7 -1
- package/src/session/session-tools.ts +37 -0
- package/src/session/streaming-output.ts +52 -5
- package/src/slash-commands/builtin-registry.ts +165 -9
- package/src/slash-commands/helpers/session-pin.ts +44 -0
- package/src/stt/downloader.ts +0 -2
- package/src/stt/index.ts +0 -2
- package/src/stt/stt-controller.ts +57 -146
- package/src/system-prompt.ts +4 -0
- package/src/task/executor.ts +17 -9
- package/src/task/index.ts +14 -34
- package/src/task/structured-subagent.ts +4 -0
- package/src/task/types.ts +15 -13
- package/src/thinking.ts +27 -0
- package/src/tiny/title-client.ts +22 -0
- package/src/tools/ast-edit.ts +4 -1
- package/src/tools/bash-interactive.ts +90 -86
- package/src/tools/bash.ts +13 -0
- package/src/tools/browser/render.ts +2 -1
- package/src/tools/builtin-names.ts +1 -0
- package/src/tools/computer/protocol.ts +28 -0
- package/src/tools/computer/supervisor.ts +258 -0
- package/src/tools/computer/worker-entry.ts +25 -0
- package/src/tools/computer/worker.ts +135 -0
- package/src/tools/computer-renderer.ts +108 -0
- package/src/tools/computer.ts +433 -0
- package/src/tools/context.ts +2 -0
- package/src/tools/default-renderer.ts +139 -0
- package/src/tools/essential-tools.ts +1 -0
- package/src/tools/eval-format/index.ts +24 -0
- package/src/tools/eval-format/javascript.ts +952 -0
- package/src/tools/eval-format/julia.ts +446 -0
- package/src/tools/eval-format/python.ts +544 -0
- package/src/tools/eval-format/ruby.ts +380 -0
- package/src/tools/eval-render.ts +12 -6
- package/src/tools/index.ts +5 -0
- package/src/tools/renderers.ts +2 -0
- package/src/tools/resolve.ts +10 -1
- package/src/tools/todo.ts +58 -6
- package/src/tools/xdev.ts +54 -26
- package/src/tts/streaming-player.ts +81 -340
- package/src/utils/clipboard.ts +1 -30
- package/src/utils/mac-file-urls.applescript +37 -0
- package/src/utils/tool-choice.ts +14 -0
- package/src/utils/tools-manager.ts +1 -19
- package/src/web/search/index.ts +28 -5
- package/src/web/search/providers/anthropic.ts +62 -4
- package/src/web/search/providers/base.ts +16 -0
- package/src/web/search/providers/brave.ts +30 -3
- package/src/web/search/providers/codex.ts +14 -1
- package/src/web/search/providers/duckduckgo.ts +23 -1
- package/src/web/search/providers/ecosia.ts +5 -1
- package/src/web/search/providers/exa.ts +28 -1
- package/src/web/search/providers/firecrawl.ts +37 -3
- package/src/web/search/providers/gemini.ts +12 -2
- package/src/web/search/providers/google.ts +2 -1
- package/src/web/search/providers/jina.ts +31 -8
- package/src/web/search/providers/kagi.ts +10 -1
- package/src/web/search/providers/kimi.ts +16 -1
- package/src/web/search/providers/mojeek.ts +14 -1
- package/src/web/search/providers/parallel.ts +48 -2
- package/src/web/search/providers/perplexity.ts +94 -6
- package/src/web/search/providers/searxng.ts +145 -9
- package/src/web/search/providers/startpage.ts +8 -2
- package/src/web/search/providers/synthetic.ts +7 -1
- package/src/web/search/providers/tavily.ts +52 -3
- package/src/web/search/providers/tinyfish.ts +6 -1
- package/src/web/search/providers/xai.ts +49 -2
- package/src/web/search/providers/zai.ts +19 -1
- package/src/web/search/query.ts +850 -0
- package/dist/types/stt/recorder.d.ts +0 -30
- package/dist/types/stt/transcriber.d.ts +0 -14
- package/dist/types/stt/wav.d.ts +0 -29
- package/dist/types/tts/player.d.ts +0 -32
- package/src/live/audio-worklet.txt +0 -59
- package/src/live/browser-runtime.txt +0 -221
- package/src/stt/recorder.ts +0 -551
- package/src/stt/transcriber.ts +0 -60
- package/src/stt/wav.ts +0 -173
- package/src/tts/player.ts +0 -137
|
@@ -0,0 +1,952 @@
|
|
|
1
|
+
type PendingBreak = "brace" | "statement" | "close";
|
|
2
|
+
|
|
3
|
+
interface ParenFrame {
|
|
4
|
+
forHeader: boolean;
|
|
5
|
+
controlHeader: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface TemplateTextFrame {
|
|
9
|
+
kind: "text";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface TemplateExpressionFrame {
|
|
13
|
+
kind: "expression";
|
|
14
|
+
braceDepth: number;
|
|
15
|
+
regexAllowed: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type TemplateFrame = TemplateTextFrame | TemplateExpressionFrame;
|
|
19
|
+
|
|
20
|
+
const CONTROL_HEADER_WORDS: Record<string, true> = {
|
|
21
|
+
catch: true,
|
|
22
|
+
for: true,
|
|
23
|
+
if: true,
|
|
24
|
+
switch: true,
|
|
25
|
+
while: true,
|
|
26
|
+
with: true,
|
|
27
|
+
};
|
|
28
|
+
const REGEX_PREFIX_WORDS: Record<string, true> = {
|
|
29
|
+
await: true,
|
|
30
|
+
case: true,
|
|
31
|
+
delete: true,
|
|
32
|
+
do: true,
|
|
33
|
+
else: true,
|
|
34
|
+
extends: true,
|
|
35
|
+
in: true,
|
|
36
|
+
instanceof: true,
|
|
37
|
+
new: true,
|
|
38
|
+
of: true,
|
|
39
|
+
return: true,
|
|
40
|
+
throw: true,
|
|
41
|
+
typeof: true,
|
|
42
|
+
void: true,
|
|
43
|
+
yield: true,
|
|
44
|
+
};
|
|
45
|
+
const CLOSE_CONTINUATIONS = ["else", "catch", "finally"];
|
|
46
|
+
|
|
47
|
+
function isIdentifierStart(char: string): boolean {
|
|
48
|
+
if (!char) return false;
|
|
49
|
+
const code = char.charCodeAt(0);
|
|
50
|
+
return char === "$" || char === "_" || (code >= 65 && code <= 90) || (code >= 97 && code <= 122) || code >= 128;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isIdentifierPart(char: string): boolean {
|
|
54
|
+
if (!char) return false;
|
|
55
|
+
const code = char.charCodeAt(0);
|
|
56
|
+
return isIdentifierStart(char) || (code >= 48 && code <= 57);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function scanIdentifier(source: string, start: number): number {
|
|
60
|
+
let index = start + 1;
|
|
61
|
+
while (index < source.length && isIdentifierPart(source[index])) index++;
|
|
62
|
+
return index;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function scanNumber(source: string, start: number): number {
|
|
66
|
+
let index = start + 1;
|
|
67
|
+
while (index < source.length && /[\w.]/.test(source[index])) index++;
|
|
68
|
+
return index;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function scanQuoted(source: string, start: number): number {
|
|
72
|
+
const quote = source[start];
|
|
73
|
+
let index = start + 1;
|
|
74
|
+
while (index < source.length) {
|
|
75
|
+
if (source[index] === "\\") {
|
|
76
|
+
index += index + 1 < source.length ? 2 : 1;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (source[index] === quote) return index + 1;
|
|
80
|
+
index++;
|
|
81
|
+
}
|
|
82
|
+
return source.length;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function scanLineComment(source: string, start: number): number {
|
|
86
|
+
let index = start + 2;
|
|
87
|
+
while (index < source.length && source[index] !== "\n" && source[index] !== "\r") index++;
|
|
88
|
+
return index;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function scanBlockComment(source: string, start: number): number {
|
|
92
|
+
let index = start + 2;
|
|
93
|
+
while (index < source.length) {
|
|
94
|
+
if (source[index] === "*" && source[index + 1] === "/") return index + 2;
|
|
95
|
+
index++;
|
|
96
|
+
}
|
|
97
|
+
return source.length;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function scanRegex(source: string, start: number): number {
|
|
101
|
+
let index = start + 1;
|
|
102
|
+
let inCharacterClass = false;
|
|
103
|
+
while (index < source.length) {
|
|
104
|
+
const char = source[index];
|
|
105
|
+
if (char === "\\") {
|
|
106
|
+
index += index + 1 < source.length ? 2 : 1;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (char === "[") inCharacterClass = true;
|
|
110
|
+
else if (char === "]") inCharacterClass = false;
|
|
111
|
+
else if (char === "/" && !inCharacterClass) {
|
|
112
|
+
index++;
|
|
113
|
+
while (index < source.length && isIdentifierPart(source[index])) index++;
|
|
114
|
+
return index;
|
|
115
|
+
}
|
|
116
|
+
index++;
|
|
117
|
+
}
|
|
118
|
+
return source.length;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function scanTemplate(source: string, start: number): number {
|
|
122
|
+
const frames: TemplateFrame[] = [{ kind: "text" }];
|
|
123
|
+
let index = start + 1;
|
|
124
|
+
|
|
125
|
+
while (index < source.length) {
|
|
126
|
+
const frame = frames[frames.length - 1];
|
|
127
|
+
if (!frame) return index;
|
|
128
|
+
const char = source[index];
|
|
129
|
+
const next = source[index + 1];
|
|
130
|
+
|
|
131
|
+
if (frame.kind === "text") {
|
|
132
|
+
if (char === "\\") {
|
|
133
|
+
index += index + 1 < source.length ? 2 : 1;
|
|
134
|
+
} else if (char === "`") {
|
|
135
|
+
frames.pop();
|
|
136
|
+
index++;
|
|
137
|
+
if (frames.length === 0) return index;
|
|
138
|
+
const parent = frames[frames.length - 1];
|
|
139
|
+
if (parent?.kind === "expression") parent.regexAllowed = false;
|
|
140
|
+
} else if (char === "$" && next === "{") {
|
|
141
|
+
frames.push({ kind: "expression", braceDepth: 1, regexAllowed: true });
|
|
142
|
+
index += 2;
|
|
143
|
+
} else {
|
|
144
|
+
index++;
|
|
145
|
+
}
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (char === "'" || char === '"') {
|
|
150
|
+
index = scanQuoted(source, index);
|
|
151
|
+
frame.regexAllowed = false;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (char === "`") {
|
|
155
|
+
frames.push({ kind: "text" });
|
|
156
|
+
index++;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (char === "/" && next === "/") {
|
|
160
|
+
index = scanLineComment(source, index);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (char === "/" && next === "*") {
|
|
164
|
+
index = scanBlockComment(source, index);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if (char === "/" && frame.regexAllowed) {
|
|
168
|
+
index = scanRegex(source, index);
|
|
169
|
+
frame.regexAllowed = false;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (isIdentifierStart(char)) {
|
|
173
|
+
const end = scanIdentifier(source, index);
|
|
174
|
+
frame.regexAllowed = REGEX_PREFIX_WORDS[source.slice(index, end)] === true;
|
|
175
|
+
index = end;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (char >= "0" && char <= "9") {
|
|
179
|
+
index = scanNumber(source, index);
|
|
180
|
+
frame.regexAllowed = false;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (char === "{") {
|
|
184
|
+
frame.braceDepth++;
|
|
185
|
+
frame.regexAllowed = true;
|
|
186
|
+
index++;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (char === "}") {
|
|
190
|
+
frame.braceDepth--;
|
|
191
|
+
index++;
|
|
192
|
+
if (frame.braceDepth === 0) frames.pop();
|
|
193
|
+
else frame.regexAllowed = false;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if ((char === "+" && next === "+") || (char === "-" && next === "-")) {
|
|
197
|
+
frame.regexAllowed = false;
|
|
198
|
+
index += 2;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (char === ")" || char === "]" || char === ".") frame.regexAllowed = false;
|
|
202
|
+
else if (!/\s/.test(char)) frame.regexAllowed = true;
|
|
203
|
+
index++;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return source.length;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function canJoinCloseWithWord(word: string, atSourceEnd: boolean): boolean {
|
|
210
|
+
return CLOSE_CONTINUATIONS.some(keyword => keyword === word || (atSourceEnd && keyword.startsWith(word)));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function canAttachToClose(char: string): boolean {
|
|
214
|
+
return "();,.)]:?+-*/%&|^<>=!".includes(char);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const JS_DISPLAY_MAX_LINE_WIDTH = 100;
|
|
218
|
+
const JS_DISPLAY_INDENT = " ";
|
|
219
|
+
const BLOCK_BRACE_WORDS: Record<string, true> = {
|
|
220
|
+
catch: true,
|
|
221
|
+
class: true,
|
|
222
|
+
do: true,
|
|
223
|
+
else: true,
|
|
224
|
+
finally: true,
|
|
225
|
+
for: true,
|
|
226
|
+
function: true,
|
|
227
|
+
if: true,
|
|
228
|
+
switch: true,
|
|
229
|
+
try: true,
|
|
230
|
+
while: true,
|
|
231
|
+
with: true,
|
|
232
|
+
};
|
|
233
|
+
const OBJECT_PREFIX_WORDS: Record<string, true> = {
|
|
234
|
+
const: true,
|
|
235
|
+
default: true,
|
|
236
|
+
let: true,
|
|
237
|
+
return: true,
|
|
238
|
+
throw: true,
|
|
239
|
+
var: true,
|
|
240
|
+
yield: true,
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
interface DisplayBraceNode {
|
|
244
|
+
start: number;
|
|
245
|
+
end?: number;
|
|
246
|
+
object: boolean;
|
|
247
|
+
children: DisplayBraceNode[];
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function classifySourceBraces(source: string): boolean[] {
|
|
251
|
+
const objects: boolean[] = [];
|
|
252
|
+
const statementWords: string[] = [];
|
|
253
|
+
let previousToken = "";
|
|
254
|
+
let previousWord = "";
|
|
255
|
+
let regexAllowed = true;
|
|
256
|
+
|
|
257
|
+
for (let index = 0; index < source.length; ) {
|
|
258
|
+
const char = source[index];
|
|
259
|
+
const next = source[index + 1];
|
|
260
|
+
if (/\s/.test(char)) {
|
|
261
|
+
index++;
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (char === "/" && next === "/") {
|
|
265
|
+
index = scanLineComment(source, index);
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (char === "/" && next === "*") {
|
|
269
|
+
index = scanBlockComment(source, index);
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
if (char === "'" || char === '"') {
|
|
273
|
+
index = scanQuoted(source, index);
|
|
274
|
+
regexAllowed = false;
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
if (char === "`") {
|
|
278
|
+
index = scanTemplate(source, index);
|
|
279
|
+
regexAllowed = false;
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
if (char === "/" && regexAllowed) {
|
|
283
|
+
index = scanRegex(source, index);
|
|
284
|
+
regexAllowed = false;
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
if (isIdentifierStart(char)) {
|
|
288
|
+
const end = scanIdentifier(source, index);
|
|
289
|
+
const word = source.slice(index, end);
|
|
290
|
+
statementWords.push(word);
|
|
291
|
+
previousWord = word;
|
|
292
|
+
previousToken = word;
|
|
293
|
+
regexAllowed = REGEX_PREFIX_WORDS[word] === true;
|
|
294
|
+
index = end;
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
if (char >= "0" && char <= "9") {
|
|
298
|
+
index = scanNumber(source, index);
|
|
299
|
+
previousToken = "value";
|
|
300
|
+
previousWord = "";
|
|
301
|
+
regexAllowed = false;
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
if (char === "{") {
|
|
305
|
+
const isBlock =
|
|
306
|
+
previousToken === "=>" ||
|
|
307
|
+
previousToken === ")" ||
|
|
308
|
+
previousToken === "}" ||
|
|
309
|
+
BLOCK_BRACE_WORDS[previousWord] === true ||
|
|
310
|
+
statementWords.some(word => word === "class" || word === "function" || word === "switch");
|
|
311
|
+
const isObject =
|
|
312
|
+
!isBlock &&
|
|
313
|
+
(previousToken === "=" ||
|
|
314
|
+
previousToken === ":" ||
|
|
315
|
+
previousToken === "," ||
|
|
316
|
+
previousToken === "(" ||
|
|
317
|
+
previousToken === "[" ||
|
|
318
|
+
previousToken === ";" ||
|
|
319
|
+
previousToken === "" ||
|
|
320
|
+
OBJECT_PREFIX_WORDS[previousWord] === true);
|
|
321
|
+
objects.push(isObject);
|
|
322
|
+
if (isBlock) statementWords.length = 0;
|
|
323
|
+
previousToken = "{";
|
|
324
|
+
previousWord = "";
|
|
325
|
+
regexAllowed = true;
|
|
326
|
+
index++;
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
if (char === "}") {
|
|
330
|
+
previousToken = "}";
|
|
331
|
+
previousWord = "";
|
|
332
|
+
regexAllowed = false;
|
|
333
|
+
index++;
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
if (char === ";") {
|
|
337
|
+
statementWords.length = 0;
|
|
338
|
+
previousToken = ";";
|
|
339
|
+
previousWord = "";
|
|
340
|
+
regexAllowed = true;
|
|
341
|
+
index++;
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
if (char === ")") {
|
|
345
|
+
previousToken = ")";
|
|
346
|
+
previousWord = "";
|
|
347
|
+
regexAllowed = false;
|
|
348
|
+
index++;
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
if (char === "]" || char === ".") {
|
|
352
|
+
previousToken = char;
|
|
353
|
+
previousWord = "";
|
|
354
|
+
regexAllowed = false;
|
|
355
|
+
index++;
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
if (char === "=" && next === ">") {
|
|
359
|
+
previousToken = "=>";
|
|
360
|
+
previousWord = "";
|
|
361
|
+
regexAllowed = true;
|
|
362
|
+
index += 2;
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
previousToken = char;
|
|
366
|
+
previousWord = "";
|
|
367
|
+
regexAllowed = char !== ")" && char !== "]" && char !== ".";
|
|
368
|
+
index++;
|
|
369
|
+
}
|
|
370
|
+
return objects;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function collectDisplayBraceNodes(text: string, objectFlags: readonly boolean[]): DisplayBraceNode[] {
|
|
374
|
+
const roots: DisplayBraceNode[] = [];
|
|
375
|
+
const stack: DisplayBraceNode[] = [];
|
|
376
|
+
let openingIndex = 0;
|
|
377
|
+
|
|
378
|
+
for (let index = 0; index < text.length; ) {
|
|
379
|
+
const char = text[index];
|
|
380
|
+
const next = text[index + 1];
|
|
381
|
+
if (char === "/" && next === "/") {
|
|
382
|
+
index = scanLineComment(text, index);
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
if (char === "/" && next === "*") {
|
|
386
|
+
index = scanBlockComment(text, index);
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
if (char === "'" || char === '"') {
|
|
390
|
+
index = scanQuoted(text, index);
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
if (char === "`") {
|
|
394
|
+
index = scanTemplate(text, index);
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
if (char === "{") {
|
|
398
|
+
const node: DisplayBraceNode = {
|
|
399
|
+
start: index,
|
|
400
|
+
object: objectFlags[openingIndex] === true,
|
|
401
|
+
children: [],
|
|
402
|
+
};
|
|
403
|
+
openingIndex++;
|
|
404
|
+
const parent = stack[stack.length - 1];
|
|
405
|
+
if (parent) parent.children.push(node);
|
|
406
|
+
else roots.push(node);
|
|
407
|
+
stack.push(node);
|
|
408
|
+
index++;
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
if (char === "}") {
|
|
412
|
+
const node = stack.pop();
|
|
413
|
+
if (node) node.end = index;
|
|
414
|
+
index++;
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
index++;
|
|
418
|
+
}
|
|
419
|
+
return roots;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function collapseDisplayWhitespace(text: string): string | undefined {
|
|
423
|
+
const output: string[] = [];
|
|
424
|
+
let pendingSpace = false;
|
|
425
|
+
for (let index = 0; index < text.length; ) {
|
|
426
|
+
const char = text[index];
|
|
427
|
+
const next = text[index + 1];
|
|
428
|
+
if (char === "\n" || char === "\r" || /\s/.test(char)) {
|
|
429
|
+
pendingSpace = true;
|
|
430
|
+
index++;
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
if (char === "/" && next === "/") return undefined;
|
|
434
|
+
if (char === "/" && next === "*") {
|
|
435
|
+
const end = scanBlockComment(text, index);
|
|
436
|
+
if (pendingSpace && output.length > 0) output.push(" ");
|
|
437
|
+
output.push(text.slice(index, end));
|
|
438
|
+
pendingSpace = false;
|
|
439
|
+
index = end;
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
if (char === "'" || char === '"' || char === "`") {
|
|
443
|
+
const end = char === "`" ? scanTemplate(text, index) : scanQuoted(text, index);
|
|
444
|
+
const literal = text.slice(index, end);
|
|
445
|
+
if (literal.includes("\n") || literal.includes("\r")) return undefined;
|
|
446
|
+
if (pendingSpace && output.length > 0) output.push(" ");
|
|
447
|
+
output.push(literal);
|
|
448
|
+
pendingSpace = false;
|
|
449
|
+
index = end;
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
if (pendingSpace && output.length > 0) output.push(" ");
|
|
453
|
+
pendingSpace = false;
|
|
454
|
+
output.push(char);
|
|
455
|
+
index++;
|
|
456
|
+
}
|
|
457
|
+
return output.join("").trim();
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function splitDisplayObjectProperties(text: string): string[] {
|
|
461
|
+
const properties: string[] = [];
|
|
462
|
+
let start = 0;
|
|
463
|
+
let parenDepth = 0;
|
|
464
|
+
let bracketDepth = 0;
|
|
465
|
+
let braceDepth = 0;
|
|
466
|
+
for (let index = 0; index < text.length; index++) {
|
|
467
|
+
const char = text[index];
|
|
468
|
+
if (char === "(") parenDepth++;
|
|
469
|
+
else if (char === ")") parenDepth = Math.max(0, parenDepth - 1);
|
|
470
|
+
else if (char === "[") bracketDepth++;
|
|
471
|
+
else if (char === "]") bracketDepth = Math.max(0, bracketDepth - 1);
|
|
472
|
+
else if (char === "{") braceDepth++;
|
|
473
|
+
else if (char === "}") braceDepth = Math.max(0, braceDepth - 1);
|
|
474
|
+
else if (char === "," && parenDepth === 0 && bracketDepth === 0 && braceDepth === 0) {
|
|
475
|
+
properties.push(text.slice(start, index + 1));
|
|
476
|
+
start = index + 1;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
properties.push(text.slice(start));
|
|
480
|
+
return properties;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function formatDisplayObjectLiterals(formatted: string, source: string): string {
|
|
484
|
+
const roots = collectDisplayBraceNodes(formatted, classifySourceBraces(source));
|
|
485
|
+
|
|
486
|
+
function renderRange(
|
|
487
|
+
start: number,
|
|
488
|
+
end: number,
|
|
489
|
+
children: readonly DisplayBraceNode[],
|
|
490
|
+
childIndent: string,
|
|
491
|
+
): string {
|
|
492
|
+
const output: string[] = [];
|
|
493
|
+
let cursor = start;
|
|
494
|
+
for (const child of children) {
|
|
495
|
+
if (child.start < start || child.start >= end) continue;
|
|
496
|
+
output.push(formatted.slice(cursor, child.start));
|
|
497
|
+
output.push(renderNode(child, childIndent));
|
|
498
|
+
cursor = child.end === undefined ? end : child.end + 1;
|
|
499
|
+
}
|
|
500
|
+
output.push(formatted.slice(cursor, end));
|
|
501
|
+
return output.join("");
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function renderNode(node: DisplayBraceNode, parentIndent: string): string {
|
|
505
|
+
const lineStart = Math.max(0, formatted.lastIndexOf("\n", node.start - 1) + 1);
|
|
506
|
+
const linePrefix = formatted.slice(lineStart, node.start);
|
|
507
|
+
const ownIndent = /^[ \t]*$/.test(linePrefix) ? linePrefix : parentIndent;
|
|
508
|
+
const propertyIndent = ownIndent + JS_DISPLAY_INDENT;
|
|
509
|
+
const closed = node.end !== undefined;
|
|
510
|
+
const end = node.end ?? formatted.length;
|
|
511
|
+
if (!node.object) {
|
|
512
|
+
return `{${renderRange(node.start + 1, end, node.children, propertyIndent)}${closed ? "}" : ""}`;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Walk the body left to right and decide the layout at the FIRST decisive
|
|
516
|
+
// event, so the decision is a pure function of the source prefix and never
|
|
517
|
+
// flips as more code streams in:
|
|
518
|
+
// - a raw newline or a multi-line block child commits lines with the
|
|
519
|
+
// original inline layout -> verbatim forever;
|
|
520
|
+
// - a nested object exploding, or the flat width passing the cap,
|
|
521
|
+
// explodes this object (and, transitively, every enclosing inline
|
|
522
|
+
// object in this same render pass).
|
|
523
|
+
let mode: "inline" | "verbatim" | "explode" = "inline";
|
|
524
|
+
let width = node.start - lineStart + 4;
|
|
525
|
+
const pieces: string[] = [];
|
|
526
|
+
let cursor = node.start + 1;
|
|
527
|
+
const consume = (text: string, blockLike: boolean) => {
|
|
528
|
+
pieces.push(text);
|
|
529
|
+
if (mode !== "inline" || text.length === 0) return;
|
|
530
|
+
if (text.includes("\n") || text.includes("\r")) {
|
|
531
|
+
mode = blockLike ? "verbatim" : "explode";
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
width += text.length;
|
|
535
|
+
if (width > JS_DISPLAY_MAX_LINE_WIDTH) mode = "explode";
|
|
536
|
+
};
|
|
537
|
+
for (const child of node.children) {
|
|
538
|
+
if (child.start < node.start + 1 || child.start >= end) continue;
|
|
539
|
+
consume(formatted.slice(cursor, child.start), true);
|
|
540
|
+
consume(renderNode(child, propertyIndent), !child.object);
|
|
541
|
+
cursor = child.end === undefined ? end : child.end + 1;
|
|
542
|
+
}
|
|
543
|
+
consume(formatted.slice(cursor, end), true);
|
|
544
|
+
const body = pieces.join("");
|
|
545
|
+
|
|
546
|
+
if (mode === "inline") {
|
|
547
|
+
const flat = collapseDisplayWhitespace(body);
|
|
548
|
+
if (flat === undefined) mode = "verbatim";
|
|
549
|
+
else if (!closed) return flat.length > 0 ? `{ ${flat}` : "{";
|
|
550
|
+
else return flat.length > 0 ? `{ ${flat} }` : "{}";
|
|
551
|
+
}
|
|
552
|
+
if (mode === "verbatim") {
|
|
553
|
+
return `{${body}${closed ? "}" : ""}`;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const properties = splitDisplayObjectProperties(body)
|
|
557
|
+
.map(property => property.trim())
|
|
558
|
+
.filter(property => property.length > 0);
|
|
559
|
+
if (properties.length === 0) return closed ? "{}" : "{";
|
|
560
|
+
const lines = properties.map(property => `${propertyIndent}${property}`);
|
|
561
|
+
return `{\n${lines.join("\n")}${closed ? `\n${ownIndent}}` : ""}`;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
return renderRange(0, formatted.length, roots, "");
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Finds the next operator token eligible for spacing normalization. Angle
|
|
569
|
+
* brackets and bare `*` are intentionally excluded: generics (`Map<K, V>`) and
|
|
570
|
+
* generators (`function*`) would be mangled by binary-operator spacing.
|
|
571
|
+
*/
|
|
572
|
+
function scanDisplayOperator(source: string, start: number): string | undefined {
|
|
573
|
+
const three = source.slice(start, start + 3);
|
|
574
|
+
if (three === "===" || three === "!==" || three === "**=" || three === "&&=" || three === "||=" || three === "??=") {
|
|
575
|
+
return three;
|
|
576
|
+
}
|
|
577
|
+
const two = source.slice(start, start + 2);
|
|
578
|
+
if (
|
|
579
|
+
two === "=>" ||
|
|
580
|
+
two === "==" ||
|
|
581
|
+
two === "!=" ||
|
|
582
|
+
two === "&&" ||
|
|
583
|
+
two === "||" ||
|
|
584
|
+
two === "??" ||
|
|
585
|
+
two === "++" ||
|
|
586
|
+
two === "--" ||
|
|
587
|
+
two === "+=" ||
|
|
588
|
+
two === "-=" ||
|
|
589
|
+
two === "*=" ||
|
|
590
|
+
two === "/=" ||
|
|
591
|
+
two === "%=" ||
|
|
592
|
+
two === "&=" ||
|
|
593
|
+
two === "|=" ||
|
|
594
|
+
two === "^=" ||
|
|
595
|
+
two === "**"
|
|
596
|
+
) {
|
|
597
|
+
return two;
|
|
598
|
+
}
|
|
599
|
+
return "=+-/%&|^!?:,".includes(source[start]) ? source[start] : undefined;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function operatorSpacing(token: string, unary: boolean, ternaryPending: boolean): { before: boolean; after: boolean } {
|
|
603
|
+
if (token === ",") return { before: false, after: true };
|
|
604
|
+
if (token === ":") return { before: ternaryPending, after: true };
|
|
605
|
+
if (token === "?") return { before: true, after: true };
|
|
606
|
+
if (token === "!" || unary || token === "++" || token === "--") {
|
|
607
|
+
return { before: false, after: false };
|
|
608
|
+
}
|
|
609
|
+
return { before: true, after: true };
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/** Formats JavaScript/TypeScript-like eval source for safe, stable display without requiring valid syntax. */
|
|
613
|
+
export function formatJavaScriptForDisplay(source: string): string {
|
|
614
|
+
const output: string[] = [];
|
|
615
|
+
const parens: ParenFrame[] = [];
|
|
616
|
+
const sourceObjectBraces = classifySourceBraces(source);
|
|
617
|
+
const braceKinds: Array<"object" | "block"> = [];
|
|
618
|
+
let sourceBraceIndex = 0;
|
|
619
|
+
let index = 0;
|
|
620
|
+
let indent = 0;
|
|
621
|
+
let atLineStart = true;
|
|
622
|
+
let lastChar = "";
|
|
623
|
+
let pendingWhitespace = "";
|
|
624
|
+
let pendingBreak: PendingBreak | undefined;
|
|
625
|
+
let pendingOperatorSpace = false;
|
|
626
|
+
let ternaryPending = false;
|
|
627
|
+
let afterForSemicolon = false;
|
|
628
|
+
let regexAllowed = true;
|
|
629
|
+
let pendingFor = false;
|
|
630
|
+
let lastWord = "";
|
|
631
|
+
let lastTokenWasWord = false;
|
|
632
|
+
|
|
633
|
+
function append(text: string): void {
|
|
634
|
+
if (!text) return;
|
|
635
|
+
output.push(text);
|
|
636
|
+
lastChar = text[text.length - 1];
|
|
637
|
+
const newline = Math.max(text.lastIndexOf("\n"), text.lastIndexOf("\r"));
|
|
638
|
+
atLineStart = newline >= 0 ? newline === text.length - 1 : false;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function newline(): void {
|
|
642
|
+
output.push("\n");
|
|
643
|
+
lastChar = "\n";
|
|
644
|
+
atLineStart = true;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function whitespaceWidth(text: string): number {
|
|
648
|
+
let width = 0;
|
|
649
|
+
for (const char of text) width += char === "\t" ? 4 - (width % 4) : 1;
|
|
650
|
+
return width;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function trimTrailingHorizontalWhitespace(): void {
|
|
654
|
+
for (let index = output.length - 1; index >= 0; index--) {
|
|
655
|
+
const chunk = output[index];
|
|
656
|
+
const trimmed = chunk.replace(/[ \t]+$/, "");
|
|
657
|
+
if (trimmed !== chunk) {
|
|
658
|
+
if (trimmed.length > 0) output[index] = trimmed;
|
|
659
|
+
else output.splice(index, 1);
|
|
660
|
+
}
|
|
661
|
+
if (trimmed.length > 0 || chunk.includes("\n") || chunk.includes("\r")) break;
|
|
662
|
+
}
|
|
663
|
+
for (let index = output.length - 1; index >= 0; index--) {
|
|
664
|
+
const chunk = output[index];
|
|
665
|
+
if (chunk.length > 0) {
|
|
666
|
+
lastChar = chunk[chunk.length - 1];
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
lastChar = "";
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function flushWhitespace(): void {
|
|
674
|
+
if (atLineStart) {
|
|
675
|
+
const width = Math.max(indent * 4, whitespaceWidth(pendingWhitespace));
|
|
676
|
+
if (width > 0) append(" ".repeat(width));
|
|
677
|
+
} else if (pendingWhitespace.length > 0) {
|
|
678
|
+
append(" ");
|
|
679
|
+
}
|
|
680
|
+
pendingWhitespace = "";
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function flushOperatorSpace(nextText: string): void {
|
|
684
|
+
if (!pendingOperatorSpace) return;
|
|
685
|
+
if (!atLineStart && lastChar !== " " && !")]},.;".includes(nextText[0] ?? "")) append(" ");
|
|
686
|
+
pendingWhitespace = "";
|
|
687
|
+
pendingOperatorSpace = false;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function appendOperator(token: string, before: boolean, after: boolean): void {
|
|
691
|
+
trimTrailingHorizontalWhitespace();
|
|
692
|
+
if (before && !atLineStart && lastChar !== " " && lastChar !== "\n") append(" ");
|
|
693
|
+
append(token);
|
|
694
|
+
pendingOperatorSpace = after;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function forceBreak(): void {
|
|
698
|
+
pendingWhitespace = "";
|
|
699
|
+
pendingOperatorSpace = false;
|
|
700
|
+
if (!atLineStart) newline();
|
|
701
|
+
pendingBreak = undefined;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function prepareToken(kind: "word" | "punctuation" | "value", text: string, end: number): void {
|
|
705
|
+
flushOperatorSpace(text);
|
|
706
|
+
if (pendingBreak === "close") {
|
|
707
|
+
if (kind === "word" && canJoinCloseWithWord(text, end === source.length)) {
|
|
708
|
+
pendingWhitespace = "";
|
|
709
|
+
if (!atLineStart && lastChar !== " ") append(" ");
|
|
710
|
+
pendingBreak = undefined;
|
|
711
|
+
} else if (kind === "punctuation" && canAttachToClose(text[0])) {
|
|
712
|
+
pendingWhitespace = "";
|
|
713
|
+
pendingBreak = undefined;
|
|
714
|
+
} else {
|
|
715
|
+
forceBreak();
|
|
716
|
+
}
|
|
717
|
+
} else if (pendingBreak) {
|
|
718
|
+
forceBreak();
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
if (afterForSemicolon) {
|
|
722
|
+
if (text !== ";" && text !== ")" && !atLineStart && pendingWhitespace.length === 0) append(" ");
|
|
723
|
+
afterForSemicolon = false;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function appendComment(end: number): void {
|
|
728
|
+
const comment = source.slice(index, end);
|
|
729
|
+
if (pendingBreak) {
|
|
730
|
+
if (pendingWhitespace.length > 0) append(pendingWhitespace);
|
|
731
|
+
else if (!atLineStart) append(" ");
|
|
732
|
+
pendingWhitespace = "";
|
|
733
|
+
} else {
|
|
734
|
+
flushWhitespace();
|
|
735
|
+
}
|
|
736
|
+
append(comment);
|
|
737
|
+
if (comment.includes("\n") || comment.includes("\r")) pendingBreak = undefined;
|
|
738
|
+
if (afterForSemicolon) afterForSemicolon = false;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
while (index < source.length) {
|
|
742
|
+
const char = source[index];
|
|
743
|
+
const next = source[index + 1];
|
|
744
|
+
|
|
745
|
+
if (char === "\n" || char === "\r") {
|
|
746
|
+
pendingWhitespace = "";
|
|
747
|
+
pendingOperatorSpace = false;
|
|
748
|
+
pendingBreak = undefined;
|
|
749
|
+
afterForSemicolon = false;
|
|
750
|
+
newline();
|
|
751
|
+
index += char === "\r" && next === "\n" ? 2 : 1;
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
if (/\s/.test(char)) {
|
|
755
|
+
const start = index;
|
|
756
|
+
while (index < source.length && /[^\S\r\n]/.test(source[index])) index++;
|
|
757
|
+
pendingWhitespace += source.slice(start, index);
|
|
758
|
+
continue;
|
|
759
|
+
}
|
|
760
|
+
if (char === "/" && next === "/") {
|
|
761
|
+
const end = scanLineComment(source, index);
|
|
762
|
+
appendComment(end);
|
|
763
|
+
index = end;
|
|
764
|
+
continue;
|
|
765
|
+
}
|
|
766
|
+
if (char === "/" && next === "*") {
|
|
767
|
+
const end = scanBlockComment(source, index);
|
|
768
|
+
appendComment(end);
|
|
769
|
+
index = end;
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
if (char === "'" || char === '"') {
|
|
773
|
+
const end = scanQuoted(source, index);
|
|
774
|
+
prepareToken("value", char, end);
|
|
775
|
+
flushWhitespace();
|
|
776
|
+
append(source.slice(index, end));
|
|
777
|
+
index = end;
|
|
778
|
+
regexAllowed = false;
|
|
779
|
+
pendingFor = false;
|
|
780
|
+
lastTokenWasWord = false;
|
|
781
|
+
continue;
|
|
782
|
+
}
|
|
783
|
+
if (char === "`") {
|
|
784
|
+
const end = scanTemplate(source, index);
|
|
785
|
+
prepareToken("value", char, end);
|
|
786
|
+
flushWhitespace();
|
|
787
|
+
append(source.slice(index, end));
|
|
788
|
+
index = end;
|
|
789
|
+
regexAllowed = false;
|
|
790
|
+
pendingFor = false;
|
|
791
|
+
lastTokenWasWord = false;
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
if (char === "/" && regexAllowed) {
|
|
795
|
+
const end = scanRegex(source, index);
|
|
796
|
+
prepareToken("value", char, end);
|
|
797
|
+
flushWhitespace();
|
|
798
|
+
append(source.slice(index, end));
|
|
799
|
+
index = end;
|
|
800
|
+
regexAllowed = false;
|
|
801
|
+
pendingFor = false;
|
|
802
|
+
lastTokenWasWord = false;
|
|
803
|
+
continue;
|
|
804
|
+
}
|
|
805
|
+
if (isIdentifierStart(char)) {
|
|
806
|
+
const end = scanIdentifier(source, index);
|
|
807
|
+
const word = source.slice(index, end);
|
|
808
|
+
prepareToken("word", word, end);
|
|
809
|
+
flushWhitespace();
|
|
810
|
+
append(word);
|
|
811
|
+
if (word === "for") pendingFor = true;
|
|
812
|
+
else if (!(pendingFor && word === "await")) pendingFor = false;
|
|
813
|
+
regexAllowed = REGEX_PREFIX_WORDS[word] === true;
|
|
814
|
+
lastWord = word;
|
|
815
|
+
lastTokenWasWord = true;
|
|
816
|
+
index = end;
|
|
817
|
+
continue;
|
|
818
|
+
}
|
|
819
|
+
if (char >= "0" && char <= "9") {
|
|
820
|
+
const end = scanNumber(source, index);
|
|
821
|
+
prepareToken("value", char, end);
|
|
822
|
+
flushWhitespace();
|
|
823
|
+
append(source.slice(index, end));
|
|
824
|
+
index = end;
|
|
825
|
+
regexAllowed = false;
|
|
826
|
+
pendingFor = false;
|
|
827
|
+
lastTokenWasWord = false;
|
|
828
|
+
continue;
|
|
829
|
+
}
|
|
830
|
+
if (char === "{") {
|
|
831
|
+
const objectBrace = sourceObjectBraces[sourceBraceIndex] === true;
|
|
832
|
+
sourceBraceIndex++;
|
|
833
|
+
braceKinds.push(objectBrace ? "object" : "block");
|
|
834
|
+
prepareToken("punctuation", char, index + 1);
|
|
835
|
+
const hadWhitespace = pendingWhitespace.length > 0;
|
|
836
|
+
flushWhitespace();
|
|
837
|
+
if (!atLineStart && !hadWhitespace && !" ([{".includes(lastChar)) append(" ");
|
|
838
|
+
append(char);
|
|
839
|
+
if (!objectBrace) {
|
|
840
|
+
indent++;
|
|
841
|
+
pendingBreak = "brace";
|
|
842
|
+
}
|
|
843
|
+
regexAllowed = true;
|
|
844
|
+
pendingFor = false;
|
|
845
|
+
lastTokenWasWord = false;
|
|
846
|
+
index++;
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
849
|
+
if (char === "}") {
|
|
850
|
+
const braceKind = braceKinds.pop() ?? "block";
|
|
851
|
+
prepareToken("punctuation", char, index + 1);
|
|
852
|
+
if (braceKind === "object") {
|
|
853
|
+
// Multi-line object closers keep their line indentation; inline
|
|
854
|
+
// closers attach tight so the post-pass controls the spacing.
|
|
855
|
+
if (atLineStart) flushWhitespace();
|
|
856
|
+
else {
|
|
857
|
+
pendingWhitespace = "";
|
|
858
|
+
trimTrailingHorizontalWhitespace();
|
|
859
|
+
}
|
|
860
|
+
append(char);
|
|
861
|
+
pendingBreak = undefined;
|
|
862
|
+
} else {
|
|
863
|
+
pendingWhitespace = "";
|
|
864
|
+
if (!atLineStart) newline();
|
|
865
|
+
indent = Math.max(0, indent - 1);
|
|
866
|
+
flushWhitespace();
|
|
867
|
+
append(char);
|
|
868
|
+
pendingBreak = "close";
|
|
869
|
+
}
|
|
870
|
+
regexAllowed = false;
|
|
871
|
+
pendingFor = false;
|
|
872
|
+
lastTokenWasWord = false;
|
|
873
|
+
index++;
|
|
874
|
+
continue;
|
|
875
|
+
}
|
|
876
|
+
if (char === ";") {
|
|
877
|
+
prepareToken("punctuation", char, index + 1);
|
|
878
|
+
flushWhitespace();
|
|
879
|
+
append(char);
|
|
880
|
+
const frame = parens[parens.length - 1];
|
|
881
|
+
if (frame?.forHeader) afterForSemicolon = true;
|
|
882
|
+
else pendingBreak = "statement";
|
|
883
|
+
regexAllowed = true;
|
|
884
|
+
pendingFor = false;
|
|
885
|
+
lastTokenWasWord = false;
|
|
886
|
+
index++;
|
|
887
|
+
continue;
|
|
888
|
+
}
|
|
889
|
+
if (char === "(") {
|
|
890
|
+
const forHeader = pendingFor;
|
|
891
|
+
const controlHeader = forHeader || (lastTokenWasWord && CONTROL_HEADER_WORDS[lastWord] === true);
|
|
892
|
+
prepareToken("punctuation", char, index + 1);
|
|
893
|
+
const needsSpace = controlHeader && pendingWhitespace.length === 0 && !atLineStart;
|
|
894
|
+
flushWhitespace();
|
|
895
|
+
if (needsSpace) append(" ");
|
|
896
|
+
append(char);
|
|
897
|
+
parens.push({ forHeader, controlHeader });
|
|
898
|
+
regexAllowed = true;
|
|
899
|
+
pendingFor = false;
|
|
900
|
+
lastTokenWasWord = false;
|
|
901
|
+
index++;
|
|
902
|
+
continue;
|
|
903
|
+
}
|
|
904
|
+
if (char === ")") {
|
|
905
|
+
prepareToken("punctuation", char, index + 1);
|
|
906
|
+
flushWhitespace();
|
|
907
|
+
append(char);
|
|
908
|
+
const frame = parens.pop();
|
|
909
|
+
regexAllowed = frame?.controlHeader ?? false;
|
|
910
|
+
pendingFor = false;
|
|
911
|
+
lastTokenWasWord = false;
|
|
912
|
+
index++;
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
const operator = scanDisplayOperator(source, index);
|
|
917
|
+
if (operator && !(operator === "?" && (next === "." || next === ":"))) {
|
|
918
|
+
// In a regex-eligible position the previous token was an operator or
|
|
919
|
+
// keyword, so `+`/`-` here are unary signs, not binary operators.
|
|
920
|
+
const unary = (operator === "+" || operator === "-") && regexAllowed;
|
|
921
|
+
prepareToken("punctuation", operator, index + operator.length);
|
|
922
|
+
flushWhitespace();
|
|
923
|
+
const spacing = operatorSpacing(operator, unary, ternaryPending);
|
|
924
|
+
appendOperator(operator, spacing.before, spacing.after);
|
|
925
|
+
if (operator === "?") ternaryPending = true;
|
|
926
|
+
else if (operator === ":") ternaryPending = false;
|
|
927
|
+
regexAllowed = operator !== "++" && operator !== "--";
|
|
928
|
+
pendingFor = false;
|
|
929
|
+
lastTokenWasWord = false;
|
|
930
|
+
index += operator.length;
|
|
931
|
+
continue;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
const doubledPostfix = (char === "+" && next === "+") || (char === "-" && next === "-");
|
|
935
|
+
const token = doubledPostfix ? source.slice(index, index + 2) : char;
|
|
936
|
+
prepareToken("punctuation", token, index + token.length);
|
|
937
|
+
flushWhitespace();
|
|
938
|
+
append(token);
|
|
939
|
+
if (doubledPostfix || char === "]" || char === ".") regexAllowed = false;
|
|
940
|
+
else regexAllowed = true;
|
|
941
|
+
pendingFor = false;
|
|
942
|
+
lastTokenWasWord = false;
|
|
943
|
+
index += token.length;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
const formatted = output.join("");
|
|
947
|
+
try {
|
|
948
|
+
return formatDisplayObjectLiterals(formatted, source);
|
|
949
|
+
} catch {
|
|
950
|
+
return formatted;
|
|
951
|
+
}
|
|
952
|
+
}
|