@oh-my-pi/pi-coding-agent 17.1.1 → 17.1.3
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 +40 -0
- package/dist/cli.js +6317 -4085
- 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 +4 -0
- package/dist/types/launch/broker-list-order.test.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +2 -6
- package/dist/types/modes/types.d.ts +8 -5
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -6
- 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/tools/auto-generated-guard.d.ts +5 -2
- 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/memory-render.d.ts +1 -4
- package/dist/types/tools/resolve.d.ts +7 -0
- package/dist/types/tools/todo.d.ts +4 -1
- 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/bench-cli.ts +12 -1
- package/src/cli/web-search-cli.ts +7 -0
- package/src/config/model-resolver.ts +17 -6
- package/src/config/settings-schema.ts +5 -0
- package/src/debug/raw-sse-buffer.ts +157 -30
- package/src/edit/hashline/filesystem.ts +1 -1
- package/src/edit/modes/patch.ts +1 -1
- package/src/export/share.ts +4 -3
- package/src/launch/broker-list-order.test.ts +89 -0
- package/src/launch/broker.ts +49 -8
- package/src/modes/controllers/input-controller.ts +8 -8
- package/src/modes/interactive-mode.ts +60 -5
- package/src/modes/types.ts +9 -4
- package/src/modes/utils/ui-helpers.ts +7 -8
- 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 +2 -1
- package/src/prompts/tools/bash.md +1 -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 +8 -4
- package/src/session/agent-session.ts +14 -0
- package/src/session/queued-messages.ts +7 -1
- package/src/session/stream-guards.ts +1 -1
- 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/tools/ast-edit.ts +4 -1
- package/src/tools/auto-generated-guard.ts +18 -5
- package/src/tools/bash.ts +13 -0
- package/src/tools/browser/render.ts +2 -1
- 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/memory-render.ts +11 -2
- package/src/tools/render-utils.ts +8 -3
- package/src/tools/resolve.ts +10 -1
- package/src/tools/todo.ts +58 -6
- package/src/tools/write.ts +1 -1
- 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
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
type HeaderKind =
|
|
2
|
+
| "def"
|
|
3
|
+
| "class"
|
|
4
|
+
| "if"
|
|
5
|
+
| "elif"
|
|
6
|
+
| "else"
|
|
7
|
+
| "for"
|
|
8
|
+
| "while"
|
|
9
|
+
| "try"
|
|
10
|
+
| "except"
|
|
11
|
+
| "finally"
|
|
12
|
+
| "with"
|
|
13
|
+
| "match"
|
|
14
|
+
| "case"
|
|
15
|
+
| "async def"
|
|
16
|
+
| "async for"
|
|
17
|
+
| "async with";
|
|
18
|
+
|
|
19
|
+
type ChainKind = "if" | "loop" | "try" | "match";
|
|
20
|
+
|
|
21
|
+
interface Word {
|
|
22
|
+
text: string;
|
|
23
|
+
end: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface Header {
|
|
27
|
+
kind: HeaderKind;
|
|
28
|
+
end: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface BlockFrame {
|
|
32
|
+
kind: HeaderKind;
|
|
33
|
+
chain: ChainKind | null;
|
|
34
|
+
headerIndent: number;
|
|
35
|
+
sourceIndent: number;
|
|
36
|
+
previousChain: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface DelimiterFrame {
|
|
40
|
+
opener: string;
|
|
41
|
+
outputIndent: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface PendingSuiteColon {
|
|
45
|
+
kind: HeaderKind;
|
|
46
|
+
chain: ChainKind | null;
|
|
47
|
+
headerIndent: number;
|
|
48
|
+
sourceIndent: number;
|
|
49
|
+
afterColon: string[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface StringState {
|
|
53
|
+
quote: string;
|
|
54
|
+
triple: boolean;
|
|
55
|
+
escaped: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface ClauseAlignment {
|
|
59
|
+
indent: number;
|
|
60
|
+
chain: ChainKind | null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function isWordStart(char: string | undefined): boolean {
|
|
64
|
+
if (!char) return false;
|
|
65
|
+
const code = char.charCodeAt(0);
|
|
66
|
+
return char === "_" || (code >= 65 && code <= 90) || (code >= 97 && code <= 122);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function isWordPart(char: string): boolean {
|
|
70
|
+
const code = char.charCodeAt(0);
|
|
71
|
+
return isWordStart(char) || (code >= 48 && code <= 57);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function readWord(source: string, start: number): Word {
|
|
75
|
+
let end = start;
|
|
76
|
+
while (end < source.length && isWordPart(source[end])) end++;
|
|
77
|
+
return { text: source.slice(start, end), end };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function simpleHeader(word: Word): Header | null {
|
|
81
|
+
switch (word.text) {
|
|
82
|
+
case "def":
|
|
83
|
+
case "class":
|
|
84
|
+
case "if":
|
|
85
|
+
case "elif":
|
|
86
|
+
case "else":
|
|
87
|
+
case "for":
|
|
88
|
+
case "while":
|
|
89
|
+
case "try":
|
|
90
|
+
case "except":
|
|
91
|
+
case "finally":
|
|
92
|
+
case "with":
|
|
93
|
+
case "match":
|
|
94
|
+
case "case":
|
|
95
|
+
return { kind: word.text, end: word.end };
|
|
96
|
+
default:
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function readHeader(source: string, start: number): Header | null {
|
|
102
|
+
if (!isWordStart(source[start])) return null;
|
|
103
|
+
const first = readWord(source, start);
|
|
104
|
+
if (first.text !== "async") return simpleHeader(first);
|
|
105
|
+
|
|
106
|
+
let next = first.end;
|
|
107
|
+
while (source[next] === " " || source[next] === "\t") next++;
|
|
108
|
+
if (!isWordStart(source[next])) return null;
|
|
109
|
+
const second = readWord(source, next);
|
|
110
|
+
switch (second.text) {
|
|
111
|
+
case "def":
|
|
112
|
+
return { kind: "async def", end: second.end };
|
|
113
|
+
case "for":
|
|
114
|
+
return { kind: "async for", end: second.end };
|
|
115
|
+
case "with":
|
|
116
|
+
return { kind: "async with", end: second.end };
|
|
117
|
+
default:
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function defaultChain(kind: HeaderKind): ChainKind | null {
|
|
123
|
+
switch (kind) {
|
|
124
|
+
case "if":
|
|
125
|
+
case "elif":
|
|
126
|
+
case "else":
|
|
127
|
+
return "if";
|
|
128
|
+
case "for":
|
|
129
|
+
case "while":
|
|
130
|
+
case "async for":
|
|
131
|
+
return "loop";
|
|
132
|
+
case "try":
|
|
133
|
+
case "except":
|
|
134
|
+
case "finally":
|
|
135
|
+
return "try";
|
|
136
|
+
case "match":
|
|
137
|
+
case "case":
|
|
138
|
+
return "match";
|
|
139
|
+
default:
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function canOpenSuite(kind: HeaderKind, hasPayload: boolean): boolean {
|
|
145
|
+
switch (kind) {
|
|
146
|
+
case "else":
|
|
147
|
+
case "try":
|
|
148
|
+
case "finally":
|
|
149
|
+
return !hasPayload;
|
|
150
|
+
case "except":
|
|
151
|
+
return true;
|
|
152
|
+
default:
|
|
153
|
+
return hasPayload;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function matchingCloser(opener: string, closer: string): boolean {
|
|
158
|
+
return (
|
|
159
|
+
(opener === "(" && closer === ")") || (opener === "[" && closer === "]") || (opener === "{" && closer === "}")
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function formatPythonPrefix(source: string): string {
|
|
164
|
+
const chunks: string[] = [];
|
|
165
|
+
const blocks: BlockFrame[] = [];
|
|
166
|
+
const delimiters: DelimiterFrame[] = [];
|
|
167
|
+
const chainTops: Record<ChainKind, number> = { if: -1, loop: -1, try: -1, match: -1 };
|
|
168
|
+
const pendingHorizontal: string[] = [];
|
|
169
|
+
|
|
170
|
+
let outputLineStart = true;
|
|
171
|
+
let lineIndent: number | null = null;
|
|
172
|
+
let currentOutputIndent = 0;
|
|
173
|
+
let sourceLineStart = true;
|
|
174
|
+
let sourceIndent = 0;
|
|
175
|
+
let currentSourceIndent = 0;
|
|
176
|
+
let skipGeneratedNewline = false;
|
|
177
|
+
|
|
178
|
+
let statementPrepared = false;
|
|
179
|
+
let statementKind: HeaderKind | null = null;
|
|
180
|
+
let statementHeaderEnd = -1;
|
|
181
|
+
let statementHasPayload = false;
|
|
182
|
+
let statementIndent = 0;
|
|
183
|
+
let statementSourceIndent = 0;
|
|
184
|
+
let statementChain: ChainKind | null = null;
|
|
185
|
+
|
|
186
|
+
let pendingColon: PendingSuiteColon | null = null;
|
|
187
|
+
let stringState: StringState | null = null;
|
|
188
|
+
let inComment = false;
|
|
189
|
+
|
|
190
|
+
function currentBlockIndent(): number {
|
|
191
|
+
const top = blocks[blocks.length - 1];
|
|
192
|
+
return top ? top.headerIndent + 1 : 0;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function popBlock(): void {
|
|
196
|
+
const index = blocks.length - 1;
|
|
197
|
+
const frame = blocks.pop();
|
|
198
|
+
if (frame?.chain && chainTops[frame.chain] === index) chainTops[frame.chain] = frame.previousChain;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function popThrough(index: number): void {
|
|
202
|
+
while (blocks.length > index) popBlock();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function pushBlock(frame: PendingSuiteColon): void {
|
|
206
|
+
const previousChain = frame.chain ? chainTops[frame.chain] : -1;
|
|
207
|
+
blocks.push({
|
|
208
|
+
kind: frame.kind,
|
|
209
|
+
chain: frame.chain,
|
|
210
|
+
headerIndent: frame.headerIndent,
|
|
211
|
+
sourceIndent: frame.sourceIndent,
|
|
212
|
+
previousChain,
|
|
213
|
+
});
|
|
214
|
+
if (frame.chain) chainTops[frame.chain] = blocks.length - 1;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function resetStatement(): void {
|
|
218
|
+
statementPrepared = false;
|
|
219
|
+
statementKind = null;
|
|
220
|
+
statementHeaderEnd = -1;
|
|
221
|
+
statementHasPayload = false;
|
|
222
|
+
statementIndent = currentBlockIndent();
|
|
223
|
+
statementSourceIndent = currentSourceIndent;
|
|
224
|
+
statementChain = null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function flushHorizontal(): void {
|
|
228
|
+
if (pendingHorizontal.length === 0) return;
|
|
229
|
+
chunks.push(pendingHorizontal.join("").replaceAll("\t", " "));
|
|
230
|
+
pendingHorizontal.length = 0;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function appendNormal(text: string): void {
|
|
234
|
+
if (outputLineStart) {
|
|
235
|
+
const indent = lineIndent ?? currentBlockIndent();
|
|
236
|
+
if (indent > 0) chunks.push(" ".repeat(indent));
|
|
237
|
+
currentOutputIndent = indent;
|
|
238
|
+
outputLineStart = false;
|
|
239
|
+
}
|
|
240
|
+
flushHorizontal();
|
|
241
|
+
chunks.push(text);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function appendRaw(text: string): void {
|
|
245
|
+
chunks.push(text);
|
|
246
|
+
if (outputLineStart) {
|
|
247
|
+
outputLineStart = false;
|
|
248
|
+
currentOutputIndent = 0;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function finishOutputLine(): void {
|
|
253
|
+
pendingHorizontal.length = 0;
|
|
254
|
+
chunks.push("\n");
|
|
255
|
+
outputLineStart = true;
|
|
256
|
+
lineIndent = null;
|
|
257
|
+
currentOutputIndent = 0;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function consumeSourceNewline(): void {
|
|
261
|
+
sourceLineStart = true;
|
|
262
|
+
sourceIndent = 0;
|
|
263
|
+
currentSourceIndent = 0;
|
|
264
|
+
skipGeneratedNewline = false;
|
|
265
|
+
if (delimiters.length === 0) resetStatement();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function popSourceDedents(indent: number): void {
|
|
269
|
+
let top = blocks[blocks.length - 1];
|
|
270
|
+
while (top && indent <= top.sourceIndent) {
|
|
271
|
+
popBlock();
|
|
272
|
+
top = blocks[blocks.length - 1];
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function alignTo(index: number, fallback: ChainKind): ClauseAlignment {
|
|
277
|
+
if (index < 0) return { indent: currentBlockIndent(), chain: fallback };
|
|
278
|
+
const frame = blocks[index];
|
|
279
|
+
const alignment = { indent: frame.headerIndent, chain: frame.chain ?? fallback };
|
|
280
|
+
popThrough(index);
|
|
281
|
+
return alignment;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function alignClause(kind: HeaderKind): ClauseAlignment | null {
|
|
285
|
+
switch (kind) {
|
|
286
|
+
case "elif":
|
|
287
|
+
return alignTo(chainTops.if, "if");
|
|
288
|
+
case "except":
|
|
289
|
+
case "finally":
|
|
290
|
+
return alignTo(chainTops.try, "try");
|
|
291
|
+
case "else": {
|
|
292
|
+
const target = Math.max(chainTops.if, chainTops.loop, chainTops.try);
|
|
293
|
+
return alignTo(target, "if");
|
|
294
|
+
}
|
|
295
|
+
case "case": {
|
|
296
|
+
const target = chainTops.match;
|
|
297
|
+
if (target < 0) return { indent: currentBlockIndent(), chain: "match" };
|
|
298
|
+
const frame = blocks[target];
|
|
299
|
+
if (frame.kind === "match") {
|
|
300
|
+
while (blocks.length > target + 1) popBlock();
|
|
301
|
+
return { indent: frame.headerIndent + 1, chain: "match" };
|
|
302
|
+
}
|
|
303
|
+
const indent = frame.headerIndent;
|
|
304
|
+
popThrough(target);
|
|
305
|
+
return { indent, chain: "match" };
|
|
306
|
+
}
|
|
307
|
+
default:
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function prepareStatement(index: number, physicalLineStart: boolean): void {
|
|
313
|
+
if (physicalLineStart) popSourceDedents(currentSourceIndent);
|
|
314
|
+
const header = readHeader(source, index);
|
|
315
|
+
const alignment = header ? alignClause(header.kind) : null;
|
|
316
|
+
statementPrepared = true;
|
|
317
|
+
statementKind = header?.kind ?? null;
|
|
318
|
+
statementHeaderEnd = header?.end ?? -1;
|
|
319
|
+
statementHasPayload = false;
|
|
320
|
+
statementIndent = alignment?.indent ?? currentBlockIndent();
|
|
321
|
+
statementSourceIndent = currentSourceIndent;
|
|
322
|
+
statementChain = alignment?.chain ?? (header ? defaultChain(header.kind) : null);
|
|
323
|
+
lineIndent = statementIndent;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function prepareToken(index: number, char: string, comment: boolean): void {
|
|
327
|
+
const physicalLineStart = sourceLineStart;
|
|
328
|
+
if (physicalLineStart) {
|
|
329
|
+
currentSourceIndent = sourceIndent;
|
|
330
|
+
sourceLineStart = false;
|
|
331
|
+
}
|
|
332
|
+
if (skipGeneratedNewline) skipGeneratedNewline = false;
|
|
333
|
+
if (!outputLineStart) return;
|
|
334
|
+
|
|
335
|
+
const delimiter = delimiters[delimiters.length - 1];
|
|
336
|
+
if (delimiter && statementPrepared) {
|
|
337
|
+
const closes = matchingCloser(delimiter.opener, char);
|
|
338
|
+
const structuralIndent = delimiter.outputIndent + (closes ? 0 : 1);
|
|
339
|
+
lineIndent = Math.max(structuralIndent, Math.ceil(currentSourceIndent / 4));
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (comment) {
|
|
344
|
+
lineIndent = physicalLineStart
|
|
345
|
+
? Math.min(currentBlockIndent(), Math.ceil(currentSourceIndent / 4))
|
|
346
|
+
: currentBlockIndent();
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (!statementPrepared) prepareStatement(index, physicalLineStart);
|
|
350
|
+
else lineIndent = statementIndent;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function notePayload(index: number): void {
|
|
354
|
+
if (statementKind && index >= statementHeaderEnd) statementHasPayload = true;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function openPendingSuite(frame: PendingSuiteColon): void {
|
|
358
|
+
pushBlock(frame);
|
|
359
|
+
resetStatement();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
let index = 0;
|
|
363
|
+
while (index < source.length) {
|
|
364
|
+
let char = source[index];
|
|
365
|
+
|
|
366
|
+
if (stringState) {
|
|
367
|
+
const newline = char === "\n" || char === "\r";
|
|
368
|
+
if (newline) {
|
|
369
|
+
const width = char === "\r" && source[index + 1] === "\n" ? 2 : 1;
|
|
370
|
+
appendRaw(source.slice(index, index + width));
|
|
371
|
+
outputLineStart = true;
|
|
372
|
+
lineIndent = null;
|
|
373
|
+
currentOutputIndent = 0;
|
|
374
|
+
sourceLineStart = true;
|
|
375
|
+
sourceIndent = 0;
|
|
376
|
+
stringState.escaped = false;
|
|
377
|
+
index += width;
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (
|
|
382
|
+
stringState.triple &&
|
|
383
|
+
!stringState.escaped &&
|
|
384
|
+
char === stringState.quote &&
|
|
385
|
+
source[index + 1] === char &&
|
|
386
|
+
source[index + 2] === char
|
|
387
|
+
) {
|
|
388
|
+
appendRaw(source.slice(index, index + 3));
|
|
389
|
+
sourceLineStart = false;
|
|
390
|
+
stringState = null;
|
|
391
|
+
index += 3;
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
appendRaw(char);
|
|
396
|
+
sourceLineStart = false;
|
|
397
|
+
if (stringState.escaped) stringState.escaped = false;
|
|
398
|
+
else if (char === "\\") stringState.escaped = true;
|
|
399
|
+
else if (!stringState.triple && char === stringState.quote) stringState = null;
|
|
400
|
+
index++;
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (inComment) {
|
|
405
|
+
if (char === "\n" || char === "\r") {
|
|
406
|
+
const width = char === "\r" && source[index + 1] === "\n" ? 2 : 1;
|
|
407
|
+
finishOutputLine();
|
|
408
|
+
inComment = false;
|
|
409
|
+
consumeSourceNewline();
|
|
410
|
+
index += width;
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
appendRaw(char);
|
|
414
|
+
index++;
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (pendingColon) {
|
|
419
|
+
if (char === " " || char === "\t") {
|
|
420
|
+
pendingColon.afterColon.push(char);
|
|
421
|
+
index++;
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
if (char === "=" && pendingColon.afterColon.length === 0) {
|
|
425
|
+
appendNormal(":");
|
|
426
|
+
pendingColon = null;
|
|
427
|
+
} else if (char === "#") {
|
|
428
|
+
const frame = pendingColon;
|
|
429
|
+
appendNormal(":");
|
|
430
|
+
if (frame.afterColon.length > 0) chunks.push(frame.afterColon.join("").replaceAll("\t", " "));
|
|
431
|
+
openPendingSuite(frame);
|
|
432
|
+
pendingColon = null;
|
|
433
|
+
} else if (char === "\n" || char === "\r") {
|
|
434
|
+
const frame = pendingColon;
|
|
435
|
+
const width = char === "\r" && source[index + 1] === "\n" ? 2 : 1;
|
|
436
|
+
appendNormal(":");
|
|
437
|
+
openPendingSuite(frame);
|
|
438
|
+
pendingColon = null;
|
|
439
|
+
finishOutputLine();
|
|
440
|
+
consumeSourceNewline();
|
|
441
|
+
index += width;
|
|
442
|
+
continue;
|
|
443
|
+
} else {
|
|
444
|
+
const frame = pendingColon;
|
|
445
|
+
appendNormal(":");
|
|
446
|
+
openPendingSuite(frame);
|
|
447
|
+
pendingColon = null;
|
|
448
|
+
finishOutputLine();
|
|
449
|
+
skipGeneratedNewline = true;
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
char = source[index];
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (sourceLineStart && (char === " " || char === "\t")) {
|
|
456
|
+
if (char === "\t") sourceIndent += 4 - (sourceIndent % 4);
|
|
457
|
+
else sourceIndent++;
|
|
458
|
+
index++;
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (char === "\n" || char === "\r") {
|
|
463
|
+
const width = char === "\r" && source[index + 1] === "\n" ? 2 : 1;
|
|
464
|
+
pendingHorizontal.length = 0;
|
|
465
|
+
if (!skipGeneratedNewline) finishOutputLine();
|
|
466
|
+
consumeSourceNewline();
|
|
467
|
+
index += width;
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (char === " " || char === "\t") {
|
|
472
|
+
if (!skipGeneratedNewline && (!outputLineStart || statementPrepared)) pendingHorizontal.push(char);
|
|
473
|
+
index++;
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
prepareToken(index, char, char === "#");
|
|
478
|
+
|
|
479
|
+
if (char === "#") {
|
|
480
|
+
appendNormal(char);
|
|
481
|
+
inComment = true;
|
|
482
|
+
index++;
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (char === "'" || char === '"') {
|
|
487
|
+
notePayload(index);
|
|
488
|
+
const triple = source[index + 1] === char && source[index + 2] === char;
|
|
489
|
+
appendNormal(triple ? source.slice(index, index + 3) : char);
|
|
490
|
+
stringState = { quote: char, triple, escaped: false };
|
|
491
|
+
index += triple ? 3 : 1;
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (
|
|
496
|
+
char === ":" &&
|
|
497
|
+
delimiters.length === 0 &&
|
|
498
|
+
statementKind &&
|
|
499
|
+
canOpenSuite(statementKind, statementHasPayload)
|
|
500
|
+
) {
|
|
501
|
+
flushHorizontal();
|
|
502
|
+
pendingColon = {
|
|
503
|
+
kind: statementKind,
|
|
504
|
+
chain: statementChain,
|
|
505
|
+
headerIndent: statementIndent,
|
|
506
|
+
sourceIndent: statementSourceIndent,
|
|
507
|
+
afterColon: [],
|
|
508
|
+
};
|
|
509
|
+
index++;
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (char === ";" && delimiters.length === 0) {
|
|
514
|
+
appendNormal(char);
|
|
515
|
+
finishOutputLine();
|
|
516
|
+
resetStatement();
|
|
517
|
+
skipGeneratedNewline = true;
|
|
518
|
+
index++;
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
notePayload(index);
|
|
523
|
+
appendNormal(char);
|
|
524
|
+
if (char === "(" || char === "[" || char === "{") {
|
|
525
|
+
delimiters.push({ opener: char, outputIndent: currentOutputIndent });
|
|
526
|
+
} else {
|
|
527
|
+
const delimiter = delimiters[delimiters.length - 1];
|
|
528
|
+
if (delimiter && matchingCloser(delimiter.opener, char)) delimiters.pop();
|
|
529
|
+
}
|
|
530
|
+
index++;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (pendingColon) appendNormal(":");
|
|
534
|
+
return chunks.join("");
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/** Formats an arbitrary Python source prefix for stable, readable display. */
|
|
538
|
+
export function formatPythonForDisplay(source: string): string {
|
|
539
|
+
try {
|
|
540
|
+
return formatPythonPrefix(source);
|
|
541
|
+
} catch {
|
|
542
|
+
return source;
|
|
543
|
+
}
|
|
544
|
+
}
|