@churivibhav/reqex 0.1.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/LICENSE +674 -0
- package/README.md +57 -0
- package/dist/cli.js +1936 -0
- package/dist/cli.js.map +1 -0
- package/package.json +51 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1936 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
#!/usr/bin/env node
|
|
3
|
+
|
|
4
|
+
// src/cli.ts
|
|
5
|
+
import path4 from "path";
|
|
6
|
+
import process2 from "process";
|
|
7
|
+
import { createNodeApp } from "@rezi-ui/node";
|
|
8
|
+
|
|
9
|
+
// src/config/keybindings.ts
|
|
10
|
+
import fs from "fs";
|
|
11
|
+
import os from "os";
|
|
12
|
+
import path from "path";
|
|
13
|
+
var VSCODE_DEFAULTS = {
|
|
14
|
+
F5: "request.send",
|
|
15
|
+
"ctrl+enter": "request.send",
|
|
16
|
+
"alt+enter": "request.send",
|
|
17
|
+
tab: "pane.focusNext",
|
|
18
|
+
"shift+tab": "pane.focusPrev",
|
|
19
|
+
"ctrl+1": "pane.focusFiles",
|
|
20
|
+
"ctrl+2": "pane.focusEditor",
|
|
21
|
+
"ctrl+3": "pane.focusResponse",
|
|
22
|
+
"alt+1": "pane.focusFiles",
|
|
23
|
+
"alt+2": "pane.focusEditor",
|
|
24
|
+
"alt+3": "pane.focusResponse",
|
|
25
|
+
"ctrl+b": "sidebar.toggle",
|
|
26
|
+
"ctrl+p": "palette.files",
|
|
27
|
+
"ctrl+shift+p": "palette.commands",
|
|
28
|
+
F2: "palette.commands",
|
|
29
|
+
"ctrl+s": "file.save",
|
|
30
|
+
"ctrl+e": "env.switcher",
|
|
31
|
+
F11: "pane.zoom",
|
|
32
|
+
z: "pane.zoom",
|
|
33
|
+
F1: "help.show",
|
|
34
|
+
"?": "help.show",
|
|
35
|
+
"ctrl+/": "keybindings.show",
|
|
36
|
+
escape: "overlay.close",
|
|
37
|
+
"ctrl+q": "app.quit",
|
|
38
|
+
"ctrl+x": "request.cancel",
|
|
39
|
+
"ctrl+shift+c": "response.copy",
|
|
40
|
+
"ctrl+f": "response.search",
|
|
41
|
+
"ctrl+tab": "response.tab.next",
|
|
42
|
+
"ctrl+shift+tab": "response.tab.prev"
|
|
43
|
+
};
|
|
44
|
+
var VIM_DEFAULTS = {
|
|
45
|
+
...VSCODE_DEFAULTS,
|
|
46
|
+
"ctrl+w h": "pane.focusFiles",
|
|
47
|
+
"ctrl+w l": "pane.focusResponse",
|
|
48
|
+
"ctrl+w k": "pane.focusEditor"
|
|
49
|
+
};
|
|
50
|
+
var COMMAND_LABELS = {
|
|
51
|
+
"request.send": "Send request",
|
|
52
|
+
"request.cancel": "Cancel request",
|
|
53
|
+
"pane.focusNext": "Focus next pane",
|
|
54
|
+
"pane.focusPrev": "Focus previous pane",
|
|
55
|
+
"pane.focusFiles": "Focus files pane",
|
|
56
|
+
"pane.focusEditor": "Focus editor pane",
|
|
57
|
+
"pane.focusResponse": "Focus response pane",
|
|
58
|
+
"sidebar.toggle": "Toggle sidebar",
|
|
59
|
+
"file.save": "Save file",
|
|
60
|
+
"env.switcher": "Environment switcher",
|
|
61
|
+
"env.selectNext": "Next environment",
|
|
62
|
+
"env.selectPrev": "Previous environment",
|
|
63
|
+
"env.apply": "Apply environment",
|
|
64
|
+
"overlay.close": "Close overlay",
|
|
65
|
+
"app.quit": "Quit",
|
|
66
|
+
"palette.commands": "Command palette",
|
|
67
|
+
"palette.files": "Open file",
|
|
68
|
+
"help.show": "Help",
|
|
69
|
+
"keybindings.show": "Show keybindings",
|
|
70
|
+
"pane.zoom": "Zoom pane",
|
|
71
|
+
"response.tab.next": "Next response tab",
|
|
72
|
+
"response.tab.prev": "Previous response tab",
|
|
73
|
+
"response.copy": "Copy response",
|
|
74
|
+
"response.search": "Search response",
|
|
75
|
+
"editor.searchNext": "Find next in editor"
|
|
76
|
+
};
|
|
77
|
+
var CHORD_PART_LABELS = {
|
|
78
|
+
ctrl: "Ctrl",
|
|
79
|
+
shift: "Shift",
|
|
80
|
+
alt: "Alt",
|
|
81
|
+
meta: "Meta",
|
|
82
|
+
escape: "Esc",
|
|
83
|
+
tab: "Tab",
|
|
84
|
+
enter: "Enter",
|
|
85
|
+
space: "Space",
|
|
86
|
+
backspace: "Backspace",
|
|
87
|
+
delete: "Delete",
|
|
88
|
+
up: "Up",
|
|
89
|
+
down: "Down",
|
|
90
|
+
left: "Left",
|
|
91
|
+
right: "Right"
|
|
92
|
+
};
|
|
93
|
+
function formatKeyChord(key) {
|
|
94
|
+
if (/^F\d+$/u.test(key)) {
|
|
95
|
+
return key;
|
|
96
|
+
}
|
|
97
|
+
if (key.length === 1) {
|
|
98
|
+
return key === "?" ? "?" : key.toUpperCase();
|
|
99
|
+
}
|
|
100
|
+
return key.split("+").map((part) => CHORD_PART_LABELS[part] ?? part.toUpperCase()).join("+");
|
|
101
|
+
}
|
|
102
|
+
function buildKeybindingsViewLines(bindings, maxLines) {
|
|
103
|
+
const byCommand = /* @__PURE__ */ new Map();
|
|
104
|
+
for (const [key, command] of Object.entries(bindings)) {
|
|
105
|
+
const keys = byCommand.get(command) ?? [];
|
|
106
|
+
keys.push(key);
|
|
107
|
+
byCommand.set(command, keys);
|
|
108
|
+
}
|
|
109
|
+
const rows = [...byCommand.entries()].map(([command, keys]) => {
|
|
110
|
+
const formattedKeys = keys.sort((a, b) => a.localeCompare(b)).map(formatKeyChord).join(" / ");
|
|
111
|
+
const label = COMMAND_LABELS[command] ?? command;
|
|
112
|
+
return { label, row: `${formattedKeys.padEnd(28)} ${label}` };
|
|
113
|
+
}).sort((a, b) => a.label.localeCompare(b.label)).map((entry) => entry.row);
|
|
114
|
+
if (maxLines !== void 0 && rows.length > maxLines) {
|
|
115
|
+
const hidden = rows.length - maxLines + 1;
|
|
116
|
+
return [...rows.slice(0, maxLines - 1), `\u2026 ${hidden} more`];
|
|
117
|
+
}
|
|
118
|
+
return rows;
|
|
119
|
+
}
|
|
120
|
+
function getConfigDir() {
|
|
121
|
+
if (process.env.REQEX_CONFIG_DIR) {
|
|
122
|
+
return process.env.REQEX_CONFIG_DIR;
|
|
123
|
+
}
|
|
124
|
+
if (process.platform === "win32") {
|
|
125
|
+
const appData = process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming");
|
|
126
|
+
return path.join(appData, "reqex");
|
|
127
|
+
}
|
|
128
|
+
if (process.platform === "darwin") {
|
|
129
|
+
return path.join(os.homedir(), "Library", "Application Support", "reqex");
|
|
130
|
+
}
|
|
131
|
+
const xdg = process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config");
|
|
132
|
+
return path.join(xdg, "reqex");
|
|
133
|
+
}
|
|
134
|
+
function getProjectConfigDir(workspaceRoot) {
|
|
135
|
+
return path.join(workspaceRoot, ".reqex");
|
|
136
|
+
}
|
|
137
|
+
function readJsonIfExists(filePath) {
|
|
138
|
+
try {
|
|
139
|
+
if (!fs.existsSync(filePath)) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
143
|
+
return JSON.parse(raw);
|
|
144
|
+
} catch {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function loadKeybindings(workspaceRoot) {
|
|
149
|
+
const userConfig = readJsonIfExists(path.join(getConfigDir(), "keybindings.json"));
|
|
150
|
+
const projectConfig = readJsonIfExists(
|
|
151
|
+
path.join(getProjectConfigDir(workspaceRoot), "keybindings.json")
|
|
152
|
+
);
|
|
153
|
+
const preset = projectConfig?.preset ?? userConfig?.preset ?? "vscode";
|
|
154
|
+
const defaults = preset === "vim" ? VIM_DEFAULTS : VSCODE_DEFAULTS;
|
|
155
|
+
return {
|
|
156
|
+
preset,
|
|
157
|
+
bindings: {
|
|
158
|
+
...defaults,
|
|
159
|
+
...userConfig?.bindings,
|
|
160
|
+
...projectConfig?.bindings
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function watchKeybindings(workspaceRoot, onChange) {
|
|
165
|
+
const dirs = [getConfigDir(), getProjectConfigDir(workspaceRoot)];
|
|
166
|
+
const watchers = dirs.map((dir) => {
|
|
167
|
+
try {
|
|
168
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
169
|
+
} catch {
|
|
170
|
+
}
|
|
171
|
+
return fs.watch(dir, { persistent: false }, () => onChange());
|
|
172
|
+
});
|
|
173
|
+
return () => {
|
|
174
|
+
for (const watcher of watchers) {
|
|
175
|
+
watcher.close();
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
var FOOTER_HINTS = {
|
|
180
|
+
editor: ["F5 Send", "Ctrl+S Save", "Tab Panes", "F2 Palette"],
|
|
181
|
+
response: ["Tab Panes", "Ctrl+Shift+C Copy", "F5 Send", "F2 Palette"],
|
|
182
|
+
files: ["Enter Open", "Tab Panes", "Ctrl+P Files", "F2 Palette"],
|
|
183
|
+
overlay: ["\u2191\u2193 Navigate", "Enter Select", "Esc Close"],
|
|
184
|
+
sending: ["Ctrl+X Cancel", "Tab Panes", "Ctrl+Q Quit"]
|
|
185
|
+
};
|
|
186
|
+
function footerHints(context) {
|
|
187
|
+
let hints;
|
|
188
|
+
if (context.overlay !== "none") {
|
|
189
|
+
hints = FOOTER_HINTS.overlay;
|
|
190
|
+
} else if (context.sending) {
|
|
191
|
+
hints = FOOTER_HINTS.sending;
|
|
192
|
+
} else {
|
|
193
|
+
hints = FOOTER_HINTS[context.focusPane] ?? FOOTER_HINTS.editor;
|
|
194
|
+
}
|
|
195
|
+
const leftBudget = 48;
|
|
196
|
+
const maxWidth = Math.max(20, context.viewportWidth - leftBudget);
|
|
197
|
+
let text = hints.join(" \xB7 ");
|
|
198
|
+
if (text.length > maxWidth) {
|
|
199
|
+
text = `${text.slice(0, maxWidth - 1)}\u2026`;
|
|
200
|
+
}
|
|
201
|
+
return text;
|
|
202
|
+
}
|
|
203
|
+
var HELP_HINT_LINES = [
|
|
204
|
+
"F5 Send request under cursor",
|
|
205
|
+
"Tab / Shift+Tab Cycle panes",
|
|
206
|
+
"Ctrl+S Save file",
|
|
207
|
+
"Ctrl+E Environment switcher",
|
|
208
|
+
"F2 / Ctrl+Shift+P Command palette",
|
|
209
|
+
"Ctrl+Shift+C Copy response tab",
|
|
210
|
+
"Ctrl+X Cancel request \xB7 Ctrl+Q Quit",
|
|
211
|
+
"Ctrl+/ Full keybindings \xB7 F1 Quick help"
|
|
212
|
+
];
|
|
213
|
+
|
|
214
|
+
// src/engine/io-provider.ts
|
|
215
|
+
import { cli, io } from "httpyac";
|
|
216
|
+
var promptHandler = async () => void 0;
|
|
217
|
+
function setPromptHandler(handler) {
|
|
218
|
+
promptHandler = handler;
|
|
219
|
+
}
|
|
220
|
+
function initEngineProviders() {
|
|
221
|
+
cli.initFileProvider();
|
|
222
|
+
io.userInteractionProvider.isTrusted = () => true;
|
|
223
|
+
io.userInteractionProvider.showNote = async (message) => {
|
|
224
|
+
const result = await promptHandler({ kind: "confirm", message });
|
|
225
|
+
return result === true;
|
|
226
|
+
};
|
|
227
|
+
io.userInteractionProvider.showInputPrompt = async (message, defaultValue, masked) => {
|
|
228
|
+
const result = await promptHandler({
|
|
229
|
+
kind: "input",
|
|
230
|
+
message,
|
|
231
|
+
defaultValue,
|
|
232
|
+
masked: masked ?? false
|
|
233
|
+
});
|
|
234
|
+
return typeof result === "string" ? result : void 0;
|
|
235
|
+
};
|
|
236
|
+
io.userInteractionProvider.showListPrompt = async (message, values) => {
|
|
237
|
+
const result = await promptHandler({ kind: "list", message, values });
|
|
238
|
+
return typeof result === "string" ? result : void 0;
|
|
239
|
+
};
|
|
240
|
+
io.userInteractionProvider.showInformationMessage = async (message) => {
|
|
241
|
+
await promptHandler({ kind: "confirm", message });
|
|
242
|
+
return void 0;
|
|
243
|
+
};
|
|
244
|
+
io.userInteractionProvider.showWarnMessage = async (message) => {
|
|
245
|
+
await promptHandler({ kind: "confirm", message });
|
|
246
|
+
return void 0;
|
|
247
|
+
};
|
|
248
|
+
io.userInteractionProvider.showErrorMessage = async (message) => {
|
|
249
|
+
await promptHandler({ kind: "confirm", message });
|
|
250
|
+
return void 0;
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// src/engine/region-resolver.ts
|
|
255
|
+
function resolveRegionAtLine(regions, line) {
|
|
256
|
+
const candidates = regions.filter(
|
|
257
|
+
(region) => region.hasRequest && !region.isGlobal && region.startLine <= line && line <= region.endLine
|
|
258
|
+
);
|
|
259
|
+
if (candidates.length === 0) {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
return candidates.reduce((best, current) => {
|
|
263
|
+
const bestSpan = best.endLine - best.startLine;
|
|
264
|
+
const currentSpan = current.endLine - current.startLine;
|
|
265
|
+
if (currentSpan < bestSpan) {
|
|
266
|
+
return current;
|
|
267
|
+
}
|
|
268
|
+
if (currentSpan === bestSpan && current.startLine > best.startLine) {
|
|
269
|
+
return current;
|
|
270
|
+
}
|
|
271
|
+
return best;
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
var HTTP_METHOD_PREFIX = /^\s*(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS|CONNECT|TRACE|GRAPHQL)\b/u;
|
|
275
|
+
function markerColumnAfterMethod(line) {
|
|
276
|
+
const match = HTTP_METHOD_PREFIX.exec(line);
|
|
277
|
+
return match ? match[0].length : 0;
|
|
278
|
+
}
|
|
279
|
+
function buildRegionDiagnostics(regions, activeRegionId, fileLines) {
|
|
280
|
+
const markers = [];
|
|
281
|
+
for (const region of regions) {
|
|
282
|
+
if (!region.hasRequest || region.isGlobal) {
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
const isActive = region.id === activeRegionId;
|
|
286
|
+
const line = fileLines[region.startLine] ?? "";
|
|
287
|
+
const markerCol = markerColumnAfterMethod(line);
|
|
288
|
+
markers.push({
|
|
289
|
+
line: region.startLine,
|
|
290
|
+
startColumn: markerCol,
|
|
291
|
+
endColumn: markerCol + 1,
|
|
292
|
+
severity: isActive ? "hint" : "info",
|
|
293
|
+
message: `${region.method ?? "REQ"} ${region.name}`
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
return markers;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// src/engine/store.ts
|
|
300
|
+
import { store as httpyacStoreModule } from "httpyac";
|
|
301
|
+
var store = new httpyacStoreModule.HttpFileStore();
|
|
302
|
+
var versions = /* @__PURE__ */ new Map();
|
|
303
|
+
function toRegion(region) {
|
|
304
|
+
return {
|
|
305
|
+
id: region.id,
|
|
306
|
+
name: region.symbol.name,
|
|
307
|
+
method: region.request?.method,
|
|
308
|
+
url: region.request?.url,
|
|
309
|
+
startLine: region.symbol.startLine,
|
|
310
|
+
endLine: region.symbol.endLine,
|
|
311
|
+
isGlobal: region.isGlobal(),
|
|
312
|
+
hasRequest: Boolean(region.request)
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
function bumpParseVersion(filePath) {
|
|
316
|
+
const next = (versions.get(filePath) ?? 0) + 1;
|
|
317
|
+
versions.set(filePath, next);
|
|
318
|
+
return next;
|
|
319
|
+
}
|
|
320
|
+
function getParseVersion(filePath) {
|
|
321
|
+
return versions.get(filePath) ?? 0;
|
|
322
|
+
}
|
|
323
|
+
async function parseFile(filePath, getText, workingDir, version) {
|
|
324
|
+
const parseVersion = version ?? getParseVersion(filePath);
|
|
325
|
+
const httpFile = await store.getOrCreate(filePath, getText, parseVersion, {
|
|
326
|
+
workingDir
|
|
327
|
+
});
|
|
328
|
+
const regions = httpFile.httpRegions.map(toRegion);
|
|
329
|
+
return {
|
|
330
|
+
path: filePath,
|
|
331
|
+
regions,
|
|
332
|
+
version: parseVersion
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
function getHttpFile(filePath) {
|
|
336
|
+
return store.get(filePath);
|
|
337
|
+
}
|
|
338
|
+
function getHttpRegion(filePath, regionId) {
|
|
339
|
+
const httpFile = store.get(filePath);
|
|
340
|
+
return httpFile?.httpRegions.find((region) => region.id === regionId);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// src/engine/send.ts
|
|
344
|
+
import { getEnvironments, getVariables, send } from "httpyac";
|
|
345
|
+
function formatBody(body) {
|
|
346
|
+
if (body === void 0 || body === null) {
|
|
347
|
+
return "";
|
|
348
|
+
}
|
|
349
|
+
if (typeof body === "string") {
|
|
350
|
+
return body;
|
|
351
|
+
}
|
|
352
|
+
if (Buffer.isBuffer(body)) {
|
|
353
|
+
return body.toString("utf8");
|
|
354
|
+
}
|
|
355
|
+
try {
|
|
356
|
+
return JSON.stringify(body, null, 2);
|
|
357
|
+
} catch {
|
|
358
|
+
return String(body);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
function prettyBody(response) {
|
|
362
|
+
if (!response) {
|
|
363
|
+
return "";
|
|
364
|
+
}
|
|
365
|
+
if (response.prettyPrintBody) {
|
|
366
|
+
return response.prettyPrintBody;
|
|
367
|
+
}
|
|
368
|
+
return formatBody(response.body);
|
|
369
|
+
}
|
|
370
|
+
function toHeaders(response) {
|
|
371
|
+
if (!response?.headers) {
|
|
372
|
+
return [];
|
|
373
|
+
}
|
|
374
|
+
return Object.entries(response.headers).map(([name, value]) => ({
|
|
375
|
+
name,
|
|
376
|
+
value: Array.isArray(value) ? value.join(", ") : String(value ?? "")
|
|
377
|
+
}));
|
|
378
|
+
}
|
|
379
|
+
function toTestResults(region) {
|
|
380
|
+
if (!region.testResults) {
|
|
381
|
+
return [];
|
|
382
|
+
}
|
|
383
|
+
return region.testResults.map((result) => ({
|
|
384
|
+
message: result.message,
|
|
385
|
+
status: result.status,
|
|
386
|
+
detail: result.error?.displayMessage
|
|
387
|
+
}));
|
|
388
|
+
}
|
|
389
|
+
async function sendRegion(options) {
|
|
390
|
+
const httpFile = getHttpFile(options.filePath);
|
|
391
|
+
const httpRegion = getHttpRegion(options.filePath, options.regionId);
|
|
392
|
+
if (!httpFile || !httpRegion) {
|
|
393
|
+
return {
|
|
394
|
+
headers: [],
|
|
395
|
+
body: "",
|
|
396
|
+
prettyBody: "",
|
|
397
|
+
testResults: [],
|
|
398
|
+
error: "Request region not found"
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
delete httpRegion.response;
|
|
402
|
+
delete httpRegion.testResults;
|
|
403
|
+
let capturedResponse;
|
|
404
|
+
const logResponse = async (response) => {
|
|
405
|
+
capturedResponse = response;
|
|
406
|
+
};
|
|
407
|
+
try {
|
|
408
|
+
await send({
|
|
409
|
+
httpFile,
|
|
410
|
+
httpRegion,
|
|
411
|
+
activeEnvironment: options.activeEnvironment,
|
|
412
|
+
variables: options.variables,
|
|
413
|
+
logResponse
|
|
414
|
+
});
|
|
415
|
+
const response = capturedResponse ?? httpRegion.response;
|
|
416
|
+
return {
|
|
417
|
+
statusCode: response?.statusCode,
|
|
418
|
+
statusMessage: response?.statusMessage,
|
|
419
|
+
protocol: response?.protocol,
|
|
420
|
+
headers: toHeaders(response),
|
|
421
|
+
body: formatBody(response?.body),
|
|
422
|
+
prettyBody: prettyBody(response),
|
|
423
|
+
durationMs: response?.timings?.total,
|
|
424
|
+
testResults: toTestResults(httpRegion)
|
|
425
|
+
};
|
|
426
|
+
} catch (error) {
|
|
427
|
+
return {
|
|
428
|
+
headers: [],
|
|
429
|
+
body: "",
|
|
430
|
+
prettyBody: "",
|
|
431
|
+
testResults: toTestResults(httpRegion),
|
|
432
|
+
error: error instanceof Error ? error.message : String(error)
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
async function listEnvironments(filePath) {
|
|
437
|
+
const httpFile = getHttpFile(filePath);
|
|
438
|
+
if (!httpFile) {
|
|
439
|
+
return [];
|
|
440
|
+
}
|
|
441
|
+
return getEnvironments({ httpFile });
|
|
442
|
+
}
|
|
443
|
+
async function listVariables(filePath, activeEnvironment) {
|
|
444
|
+
const httpFile = getHttpFile(filePath);
|
|
445
|
+
if (!httpFile) {
|
|
446
|
+
return {};
|
|
447
|
+
}
|
|
448
|
+
return getVariables({ httpFile, activeEnvironment });
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// src/keymap/dispatcher.ts
|
|
452
|
+
function buildBindingMap(bindings, execute) {
|
|
453
|
+
const map = {};
|
|
454
|
+
for (const [key, command] of Object.entries(bindings)) {
|
|
455
|
+
map[key] = () => execute(command);
|
|
456
|
+
}
|
|
457
|
+
return map;
|
|
458
|
+
}
|
|
459
|
+
function commandFromPaletteId(id) {
|
|
460
|
+
const known = {
|
|
461
|
+
"request.send": "request.send",
|
|
462
|
+
"request.cancel": "request.cancel",
|
|
463
|
+
"file.save": "file.save",
|
|
464
|
+
"env.switcher": "env.switcher",
|
|
465
|
+
"palette.commands": "palette.commands",
|
|
466
|
+
"help.show": "help.show",
|
|
467
|
+
"keybindings.show": "keybindings.show",
|
|
468
|
+
"pane.zoom": "pane.zoom",
|
|
469
|
+
"sidebar.toggle": "sidebar.toggle"
|
|
470
|
+
};
|
|
471
|
+
return known[id] ?? null;
|
|
472
|
+
}
|
|
473
|
+
var COMMAND_ITEMS = [
|
|
474
|
+
{ id: "request.send", label: "Send Request", description: "Send request under cursor", shortcut: "F5" },
|
|
475
|
+
{ id: "request.cancel", label: "Cancel Request", description: "Cancel in-flight request", shortcut: "Ctrl+X" },
|
|
476
|
+
{ id: "file.save", label: "Save File", description: "Write editor to disk", shortcut: "Ctrl+S" },
|
|
477
|
+
{ id: "env.switcher", label: "Switch Environment", description: "Choose active environment", shortcut: "Ctrl+E" },
|
|
478
|
+
{ id: "sidebar.toggle", label: "Toggle Sidebar", description: "Show/hide file tree", shortcut: "Ctrl+B" },
|
|
479
|
+
{ id: "pane.zoom", label: "Zoom Pane", description: "Zoom focused pane", shortcut: "F11" },
|
|
480
|
+
{ id: "help.show", label: "Help", description: "Show quick help", shortcut: "F1" },
|
|
481
|
+
{ id: "keybindings.show", label: "Keybindings", description: "Show all keybindings", shortcut: "Ctrl+/" }
|
|
482
|
+
];
|
|
483
|
+
|
|
484
|
+
// src/workspace/index.ts
|
|
485
|
+
import { readFile, writeFile } from "fs/promises";
|
|
486
|
+
import chokidar from "chokidar";
|
|
487
|
+
import { EventEmitter } from "events";
|
|
488
|
+
|
|
489
|
+
// src/workspace/discovery.ts
|
|
490
|
+
import { readdir, stat } from "fs/promises";
|
|
491
|
+
import path2 from "path";
|
|
492
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
493
|
+
".git",
|
|
494
|
+
"node_modules",
|
|
495
|
+
".reqex",
|
|
496
|
+
"dist",
|
|
497
|
+
".cursor"
|
|
498
|
+
]);
|
|
499
|
+
var FILE_KINDS = [
|
|
500
|
+
{ ext: ".http", kind: "http" },
|
|
501
|
+
{ ext: ".rest", kind: "rest" },
|
|
502
|
+
{ ext: ".env", kind: "env" },
|
|
503
|
+
{ ext: ".env.json", kind: "env-json" }
|
|
504
|
+
];
|
|
505
|
+
function classifyFile(filePath) {
|
|
506
|
+
const base = path2.basename(filePath);
|
|
507
|
+
for (const { ext, kind } of FILE_KINDS) {
|
|
508
|
+
if (base === ext || base.endsWith(ext)) {
|
|
509
|
+
return kind;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
if (base.endsWith(".env.json")) {
|
|
513
|
+
return "env-json";
|
|
514
|
+
}
|
|
515
|
+
return null;
|
|
516
|
+
}
|
|
517
|
+
async function discoverFileTree(rootDir) {
|
|
518
|
+
return discoverDirectory(rootDir, rootDir);
|
|
519
|
+
}
|
|
520
|
+
async function discoverDirectory(rootDir, currentDir) {
|
|
521
|
+
let entries;
|
|
522
|
+
try {
|
|
523
|
+
entries = await readdir(currentDir);
|
|
524
|
+
} catch {
|
|
525
|
+
return [];
|
|
526
|
+
}
|
|
527
|
+
entries.sort((a, b) => a.localeCompare(b));
|
|
528
|
+
const nodes = [];
|
|
529
|
+
for (const entry of entries) {
|
|
530
|
+
if (entry.startsWith(".") && entry !== ".env" && !entry.endsWith(".env.json")) {
|
|
531
|
+
if (SKIP_DIRS.has(entry)) {
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
const fullPath = path2.join(currentDir, entry);
|
|
536
|
+
let entryStat;
|
|
537
|
+
try {
|
|
538
|
+
entryStat = await stat(fullPath);
|
|
539
|
+
} catch {
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
if (entryStat.isDirectory()) {
|
|
543
|
+
if (SKIP_DIRS.has(entry)) {
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
const children = await discoverDirectory(rootDir, fullPath);
|
|
547
|
+
if (children.length > 0) {
|
|
548
|
+
nodes.push({
|
|
549
|
+
name: entry,
|
|
550
|
+
path: fullPath,
|
|
551
|
+
kind: "directory",
|
|
552
|
+
children
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
const fileKind = classifyFile(fullPath);
|
|
558
|
+
if (!fileKind) {
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
nodes.push({
|
|
562
|
+
name: entry,
|
|
563
|
+
path: fullPath,
|
|
564
|
+
kind: "file",
|
|
565
|
+
fileKind
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
return nodes;
|
|
569
|
+
}
|
|
570
|
+
function flattenFiles(nodes) {
|
|
571
|
+
const files = [];
|
|
572
|
+
for (const node of nodes) {
|
|
573
|
+
if (node.kind === "file") {
|
|
574
|
+
files.push(node);
|
|
575
|
+
} else if (node.children) {
|
|
576
|
+
files.push(...flattenFiles(node.children));
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
return files;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// src/workspace/index.ts
|
|
583
|
+
var Workspace = class extends EventEmitter {
|
|
584
|
+
rootDir;
|
|
585
|
+
watcher = null;
|
|
586
|
+
tree = [];
|
|
587
|
+
constructor(rootDir) {
|
|
588
|
+
super();
|
|
589
|
+
this.rootDir = rootDir;
|
|
590
|
+
}
|
|
591
|
+
async open() {
|
|
592
|
+
this.tree = await discoverFileTree(this.rootDir);
|
|
593
|
+
await this.startWatcher();
|
|
594
|
+
this.emitChange({ type: "ready" });
|
|
595
|
+
return this.tree;
|
|
596
|
+
}
|
|
597
|
+
getTree() {
|
|
598
|
+
return this.tree;
|
|
599
|
+
}
|
|
600
|
+
async refresh() {
|
|
601
|
+
this.tree = await discoverFileTree(this.rootDir);
|
|
602
|
+
return this.tree;
|
|
603
|
+
}
|
|
604
|
+
async readFile(filePath) {
|
|
605
|
+
return readFile(filePath, "utf8");
|
|
606
|
+
}
|
|
607
|
+
async writeFile(filePath, content) {
|
|
608
|
+
await writeFile(filePath, content, "utf8");
|
|
609
|
+
}
|
|
610
|
+
async close() {
|
|
611
|
+
await this.watcher?.close();
|
|
612
|
+
this.watcher = null;
|
|
613
|
+
}
|
|
614
|
+
async startWatcher() {
|
|
615
|
+
await this.watcher?.close();
|
|
616
|
+
this.watcher = chokidar.watch(this.rootDir, {
|
|
617
|
+
ignoreInitial: true,
|
|
618
|
+
ignored: [
|
|
619
|
+
/(^|[/\\])\../,
|
|
620
|
+
"**/node_modules/**",
|
|
621
|
+
"**/.git/**",
|
|
622
|
+
"**/dist/**"
|
|
623
|
+
],
|
|
624
|
+
awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 }
|
|
625
|
+
});
|
|
626
|
+
const handle = async (type, path5) => {
|
|
627
|
+
try {
|
|
628
|
+
this.tree = await discoverFileTree(this.rootDir);
|
|
629
|
+
this.emitChange({ type, path: path5 });
|
|
630
|
+
} catch (error) {
|
|
631
|
+
this.emit("error", error instanceof Error ? error : new Error(String(error)));
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
this.watcher.on("add", (path5) => void handle("add", path5));
|
|
635
|
+
this.watcher.on("change", (path5) => void handle("change", path5));
|
|
636
|
+
this.watcher.on("unlink", (path5) => void handle("unlink", path5));
|
|
637
|
+
this.watcher.on("error", (error) => {
|
|
638
|
+
this.emit("error", error instanceof Error ? error : new Error(String(error)));
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
emitChange(event) {
|
|
642
|
+
this.emit("change", event);
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
// src/state/types.ts
|
|
647
|
+
var FOCUS_PANES = ["files", "editor", "response"];
|
|
648
|
+
var RESPONSE_TABS = [
|
|
649
|
+
"pretty",
|
|
650
|
+
"raw",
|
|
651
|
+
"headers",
|
|
652
|
+
"variables",
|
|
653
|
+
"tests"
|
|
654
|
+
];
|
|
655
|
+
function createInitialState(workspaceRoot) {
|
|
656
|
+
return {
|
|
657
|
+
workspaceRoot,
|
|
658
|
+
fileTree: [],
|
|
659
|
+
expandedPaths: [],
|
|
660
|
+
selectedFilePath: null,
|
|
661
|
+
fileContent: "",
|
|
662
|
+
fileLines: [""],
|
|
663
|
+
dirty: false,
|
|
664
|
+
parseVersion: 0,
|
|
665
|
+
parsedFile: null,
|
|
666
|
+
activeRegion: null,
|
|
667
|
+
responseEditor: {
|
|
668
|
+
scrollTop: 0,
|
|
669
|
+
scrollLeft: 0,
|
|
670
|
+
cursor: { line: 0, column: 0 },
|
|
671
|
+
selection: null
|
|
672
|
+
},
|
|
673
|
+
resultGeneration: 0,
|
|
674
|
+
editor: {
|
|
675
|
+
cursor: { line: 0, column: 0 },
|
|
676
|
+
selection: null,
|
|
677
|
+
scrollTop: 0,
|
|
678
|
+
scrollLeft: 0,
|
|
679
|
+
searchQuery: ""
|
|
680
|
+
},
|
|
681
|
+
request: {
|
|
682
|
+
sending: false,
|
|
683
|
+
error: null,
|
|
684
|
+
result: null,
|
|
685
|
+
activeEnvironment: [],
|
|
686
|
+
environments: [],
|
|
687
|
+
variables: {}
|
|
688
|
+
},
|
|
689
|
+
ui: {
|
|
690
|
+
focusPane: "editor",
|
|
691
|
+
zoomPane: null,
|
|
692
|
+
sidebarVisible: true,
|
|
693
|
+
layoutMode: "three-pane",
|
|
694
|
+
viewportWidth: 120,
|
|
695
|
+
viewportHeight: 40,
|
|
696
|
+
overlay: "none",
|
|
697
|
+
responseTab: "pretty",
|
|
698
|
+
splitSizes: [22, 40, 38],
|
|
699
|
+
envSelectedIndex: 0,
|
|
700
|
+
commandPalette: { open: false, query: "", selectedIndex: 0 },
|
|
701
|
+
pendingPrompt: null,
|
|
702
|
+
statusMessage: null,
|
|
703
|
+
gitBranch: null
|
|
704
|
+
},
|
|
705
|
+
settings: {
|
|
706
|
+
keymapPreset: "vscode",
|
|
707
|
+
keybindings: {}
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
function linesFromContent(content) {
|
|
712
|
+
if (content.length === 0) {
|
|
713
|
+
return [""];
|
|
714
|
+
}
|
|
715
|
+
return content.split(/\r?\n/u);
|
|
716
|
+
}
|
|
717
|
+
function contentFromLines(lines) {
|
|
718
|
+
return lines.join("\n");
|
|
719
|
+
}
|
|
720
|
+
function resolveLayoutMode(width) {
|
|
721
|
+
if (width < 80) {
|
|
722
|
+
return "stacked";
|
|
723
|
+
}
|
|
724
|
+
if (width < 120) {
|
|
725
|
+
return "sidebar-overlay";
|
|
726
|
+
}
|
|
727
|
+
return "three-pane";
|
|
728
|
+
}
|
|
729
|
+
function nextPane(current) {
|
|
730
|
+
const index = FOCUS_PANES.indexOf(current);
|
|
731
|
+
return FOCUS_PANES[(index + 1) % FOCUS_PANES.length] ?? "editor";
|
|
732
|
+
}
|
|
733
|
+
function prevPane(current) {
|
|
734
|
+
const index = FOCUS_PANES.indexOf(current);
|
|
735
|
+
return FOCUS_PANES[(index + FOCUS_PANES.length - 1) % FOCUS_PANES.length] ?? "editor";
|
|
736
|
+
}
|
|
737
|
+
function nextResponseTab(current) {
|
|
738
|
+
const index = RESPONSE_TABS.indexOf(current);
|
|
739
|
+
return RESPONSE_TABS[(index + 1) % RESPONSE_TABS.length] ?? "pretty";
|
|
740
|
+
}
|
|
741
|
+
function prevResponseTab(current) {
|
|
742
|
+
const index = RESPONSE_TABS.indexOf(current);
|
|
743
|
+
return RESPONSE_TABS[(index + RESPONSE_TABS.length - 1) % RESPONSE_TABS.length] ?? "pretty";
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// src/state/send-controller.ts
|
|
747
|
+
function createSendController() {
|
|
748
|
+
let sendGeneration = 0;
|
|
749
|
+
return {
|
|
750
|
+
beginSend() {
|
|
751
|
+
sendGeneration += 1;
|
|
752
|
+
return sendGeneration;
|
|
753
|
+
},
|
|
754
|
+
cancelSend() {
|
|
755
|
+
sendGeneration += 1;
|
|
756
|
+
return sendGeneration;
|
|
757
|
+
},
|
|
758
|
+
isCurrent(generation) {
|
|
759
|
+
return generation === sendGeneration;
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// src/state/commands.ts
|
|
765
|
+
function createCommandContext(deps) {
|
|
766
|
+
setPromptHandler(async (request) => {
|
|
767
|
+
return new Promise((resolve) => {
|
|
768
|
+
deps.update((state) => ({
|
|
769
|
+
...state,
|
|
770
|
+
ui: {
|
|
771
|
+
...state.ui,
|
|
772
|
+
overlay: "none",
|
|
773
|
+
pendingPrompt: {
|
|
774
|
+
kind: request.kind,
|
|
775
|
+
message: request.message,
|
|
776
|
+
values: request.kind === "list" ? request.values : void 0,
|
|
777
|
+
defaultValue: request.kind === "input" ? request.defaultValue : void 0,
|
|
778
|
+
masked: request.kind === "input" ? request.masked : void 0
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}));
|
|
782
|
+
promptResolvers.push(resolve);
|
|
783
|
+
});
|
|
784
|
+
});
|
|
785
|
+
const promptResolvers = [];
|
|
786
|
+
const sendController = createSendController();
|
|
787
|
+
const resolvePrompt = (value) => {
|
|
788
|
+
const resolver = promptResolvers.pop();
|
|
789
|
+
resolver?.(value);
|
|
790
|
+
deps.update((state) => ({
|
|
791
|
+
...state,
|
|
792
|
+
ui: { ...state.ui, pendingPrompt: null }
|
|
793
|
+
}));
|
|
794
|
+
};
|
|
795
|
+
const openFile = async (path5) => {
|
|
796
|
+
const content = await deps.workspace.readFile(path5);
|
|
797
|
+
const lines = linesFromContent(content);
|
|
798
|
+
const parsed = await parseFile(path5, async () => content, deps.workspace.rootDir);
|
|
799
|
+
const environments = await listEnvironments(path5);
|
|
800
|
+
const variables = await listVariables(path5, [...deps.getState().request.activeEnvironment]);
|
|
801
|
+
const activeRegion = resolveRegionAtLine(parsed.regions, 0);
|
|
802
|
+
deps.update((state) => ({
|
|
803
|
+
...state,
|
|
804
|
+
selectedFilePath: path5,
|
|
805
|
+
fileContent: content,
|
|
806
|
+
fileLines: lines,
|
|
807
|
+
dirty: false,
|
|
808
|
+
parseVersion: parsed.version,
|
|
809
|
+
parsedFile: parsed,
|
|
810
|
+
activeRegion,
|
|
811
|
+
editor: {
|
|
812
|
+
...state.editor,
|
|
813
|
+
cursor: { line: activeRegion?.startLine ?? 0, column: 0 },
|
|
814
|
+
selection: null,
|
|
815
|
+
scrollTop: activeRegion?.startLine ?? 0
|
|
816
|
+
},
|
|
817
|
+
request: {
|
|
818
|
+
...state.request,
|
|
819
|
+
error: null,
|
|
820
|
+
result: null,
|
|
821
|
+
environments,
|
|
822
|
+
variables
|
|
823
|
+
},
|
|
824
|
+
ui: {
|
|
825
|
+
...state.ui,
|
|
826
|
+
focusPane: "editor",
|
|
827
|
+
statusMessage: null
|
|
828
|
+
}
|
|
829
|
+
}));
|
|
830
|
+
};
|
|
831
|
+
const refreshWorkspace = async () => {
|
|
832
|
+
const tree = await deps.workspace.refresh();
|
|
833
|
+
deps.update((state) => ({ ...state, fileTree: tree }));
|
|
834
|
+
};
|
|
835
|
+
const saveFile = async () => {
|
|
836
|
+
const state = deps.getState();
|
|
837
|
+
if (!state.selectedFilePath || !state.dirty) {
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
const content = contentFromLines(state.fileLines);
|
|
841
|
+
await deps.workspace.writeFile(state.selectedFilePath, content);
|
|
842
|
+
const version = bumpParseVersion(state.selectedFilePath);
|
|
843
|
+
const parsed = await parseFile(
|
|
844
|
+
state.selectedFilePath,
|
|
845
|
+
async () => content,
|
|
846
|
+
deps.workspace.rootDir,
|
|
847
|
+
version
|
|
848
|
+
);
|
|
849
|
+
const activeRegion = state.activeRegion ? parsed.regions.find((region) => region.id === state.activeRegion?.id) ?? resolveRegionAtLine(parsed.regions, state.editor.cursor.line) : resolveRegionAtLine(parsed.regions, state.editor.cursor.line);
|
|
850
|
+
deps.update((s) => ({
|
|
851
|
+
...s,
|
|
852
|
+
fileContent: content,
|
|
853
|
+
dirty: false,
|
|
854
|
+
parsedFile: parsed,
|
|
855
|
+
activeRegion: activeRegion ?? null,
|
|
856
|
+
ui: { ...s.ui, statusMessage: "Saved" }
|
|
857
|
+
}));
|
|
858
|
+
};
|
|
859
|
+
const runSend = async () => {
|
|
860
|
+
const state = deps.getState();
|
|
861
|
+
if (!state.selectedFilePath || !state.parsedFile) {
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
const region = resolveRegionAtLine(state.parsedFile.regions, state.editor.cursor.line);
|
|
865
|
+
if (!region) {
|
|
866
|
+
deps.update((s) => ({
|
|
867
|
+
...s,
|
|
868
|
+
request: { ...s.request, error: "No request region under cursor" },
|
|
869
|
+
ui: { ...s.ui, focusPane: "response" }
|
|
870
|
+
}));
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
const gen = sendController.beginSend();
|
|
874
|
+
deps.update((s) => ({
|
|
875
|
+
...s,
|
|
876
|
+
activeRegion: region,
|
|
877
|
+
request: { ...s.request, sending: true, error: null, result: null },
|
|
878
|
+
responseEditor: {
|
|
879
|
+
scrollTop: 0,
|
|
880
|
+
scrollLeft: 0,
|
|
881
|
+
cursor: { line: 0, column: 0 },
|
|
882
|
+
selection: null
|
|
883
|
+
},
|
|
884
|
+
resultGeneration: s.resultGeneration + 1,
|
|
885
|
+
ui: { ...s.ui, focusPane: "response", responseTab: "pretty" }
|
|
886
|
+
}));
|
|
887
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
888
|
+
const result = await sendRegion({
|
|
889
|
+
filePath: state.selectedFilePath,
|
|
890
|
+
regionId: region.id,
|
|
891
|
+
workingDir: deps.workspace.rootDir,
|
|
892
|
+
activeEnvironment: [...state.request.activeEnvironment],
|
|
893
|
+
variables: state.request.variables
|
|
894
|
+
});
|
|
895
|
+
if (!sendController.isCurrent(gen)) {
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
const variables = await listVariables(state.selectedFilePath, [
|
|
899
|
+
...deps.getState().request.activeEnvironment
|
|
900
|
+
]);
|
|
901
|
+
if (!sendController.isCurrent(gen)) {
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
deps.update((s) => ({
|
|
905
|
+
...s,
|
|
906
|
+
request: {
|
|
907
|
+
...s.request,
|
|
908
|
+
sending: false,
|
|
909
|
+
result,
|
|
910
|
+
error: result.error ?? null,
|
|
911
|
+
variables
|
|
912
|
+
}
|
|
913
|
+
}));
|
|
914
|
+
};
|
|
915
|
+
const focusPane = (pane) => {
|
|
916
|
+
deps.update((state) => ({
|
|
917
|
+
...state,
|
|
918
|
+
ui: { ...state.ui, focusPane: pane }
|
|
919
|
+
}));
|
|
920
|
+
};
|
|
921
|
+
const execute = (command) => {
|
|
922
|
+
const state = deps.getState();
|
|
923
|
+
switch (command) {
|
|
924
|
+
case "request.send":
|
|
925
|
+
void runSend();
|
|
926
|
+
break;
|
|
927
|
+
case "request.cancel":
|
|
928
|
+
if (!state.request.sending) {
|
|
929
|
+
break;
|
|
930
|
+
}
|
|
931
|
+
sendController.cancelSend();
|
|
932
|
+
deps.update((s) => ({
|
|
933
|
+
...s,
|
|
934
|
+
request: {
|
|
935
|
+
...s.request,
|
|
936
|
+
sending: false,
|
|
937
|
+
error: "Request cancelled",
|
|
938
|
+
result: null
|
|
939
|
+
},
|
|
940
|
+
ui: { ...s.ui, statusMessage: "Request cancelled" }
|
|
941
|
+
}));
|
|
942
|
+
break;
|
|
943
|
+
case "pane.focusNext":
|
|
944
|
+
focusPane(nextPane(state.ui.focusPane));
|
|
945
|
+
break;
|
|
946
|
+
case "pane.focusPrev":
|
|
947
|
+
focusPane(prevPane(state.ui.focusPane));
|
|
948
|
+
break;
|
|
949
|
+
case "pane.focusFiles":
|
|
950
|
+
focusPane("files");
|
|
951
|
+
break;
|
|
952
|
+
case "pane.focusEditor":
|
|
953
|
+
focusPane("editor");
|
|
954
|
+
break;
|
|
955
|
+
case "pane.focusResponse":
|
|
956
|
+
focusPane("response");
|
|
957
|
+
break;
|
|
958
|
+
case "sidebar.toggle":
|
|
959
|
+
deps.update((s) => ({
|
|
960
|
+
...s,
|
|
961
|
+
ui: { ...s.ui, sidebarVisible: !s.ui.sidebarVisible }
|
|
962
|
+
}));
|
|
963
|
+
break;
|
|
964
|
+
case "file.save":
|
|
965
|
+
void saveFile();
|
|
966
|
+
break;
|
|
967
|
+
case "env.switcher":
|
|
968
|
+
deps.update((s) => ({
|
|
969
|
+
...s,
|
|
970
|
+
ui: {
|
|
971
|
+
...s.ui,
|
|
972
|
+
overlay: s.ui.overlay === "env" ? "none" : "env",
|
|
973
|
+
envSelectedIndex: Math.max(
|
|
974
|
+
0,
|
|
975
|
+
s.request.environments.indexOf(s.request.activeEnvironment.join(",")) || 0
|
|
976
|
+
)
|
|
977
|
+
}
|
|
978
|
+
}));
|
|
979
|
+
break;
|
|
980
|
+
case "env.selectNext":
|
|
981
|
+
if (state.ui.overlay === "env") {
|
|
982
|
+
deps.update((s) => ({
|
|
983
|
+
...s,
|
|
984
|
+
ui: {
|
|
985
|
+
...s.ui,
|
|
986
|
+
envSelectedIndex: Math.min(
|
|
987
|
+
s.request.environments.length,
|
|
988
|
+
s.ui.envSelectedIndex + 1
|
|
989
|
+
)
|
|
990
|
+
}
|
|
991
|
+
}));
|
|
992
|
+
}
|
|
993
|
+
break;
|
|
994
|
+
case "env.selectPrev":
|
|
995
|
+
if (state.ui.overlay === "env") {
|
|
996
|
+
deps.update((s) => ({
|
|
997
|
+
...s,
|
|
998
|
+
ui: {
|
|
999
|
+
...s.ui,
|
|
1000
|
+
envSelectedIndex: Math.max(0, s.ui.envSelectedIndex - 1)
|
|
1001
|
+
}
|
|
1002
|
+
}));
|
|
1003
|
+
}
|
|
1004
|
+
break;
|
|
1005
|
+
case "env.apply": {
|
|
1006
|
+
const envName = state.request.environments[state.ui.envSelectedIndex];
|
|
1007
|
+
void (async () => {
|
|
1008
|
+
const activeEnvironment = envName ? [envName] : [];
|
|
1009
|
+
const variables = state.selectedFilePath ? await listVariables(state.selectedFilePath, activeEnvironment) : {};
|
|
1010
|
+
deps.update((s) => ({
|
|
1011
|
+
...s,
|
|
1012
|
+
request: { ...s.request, activeEnvironment, variables },
|
|
1013
|
+
ui: { ...s.ui, overlay: "none" }
|
|
1014
|
+
}));
|
|
1015
|
+
})();
|
|
1016
|
+
break;
|
|
1017
|
+
}
|
|
1018
|
+
case "overlay.close":
|
|
1019
|
+
deps.update((s) => ({
|
|
1020
|
+
...s,
|
|
1021
|
+
ui: {
|
|
1022
|
+
...s.ui,
|
|
1023
|
+
overlay: "none",
|
|
1024
|
+
commandPalette: { ...s.ui.commandPalette, open: false },
|
|
1025
|
+
pendingPrompt: null
|
|
1026
|
+
}
|
|
1027
|
+
}));
|
|
1028
|
+
resolvePrompt(void 0);
|
|
1029
|
+
break;
|
|
1030
|
+
case "app.quit":
|
|
1031
|
+
deps.quit();
|
|
1032
|
+
break;
|
|
1033
|
+
case "palette.commands":
|
|
1034
|
+
deps.update((s) => ({
|
|
1035
|
+
...s,
|
|
1036
|
+
ui: {
|
|
1037
|
+
...s.ui,
|
|
1038
|
+
overlay: "commandPalette",
|
|
1039
|
+
commandPalette: { open: true, query: "", selectedIndex: 0 }
|
|
1040
|
+
}
|
|
1041
|
+
}));
|
|
1042
|
+
break;
|
|
1043
|
+
case "palette.files": {
|
|
1044
|
+
const files = flattenFiles(state.fileTree).filter((node) => node.kind === "file");
|
|
1045
|
+
const first = files[0]?.path;
|
|
1046
|
+
if (first) {
|
|
1047
|
+
void openFile(first);
|
|
1048
|
+
}
|
|
1049
|
+
break;
|
|
1050
|
+
}
|
|
1051
|
+
case "help.show":
|
|
1052
|
+
deps.update((s) => ({
|
|
1053
|
+
...s,
|
|
1054
|
+
ui: { ...s.ui, overlay: s.ui.overlay === "help" ? "none" : "help" }
|
|
1055
|
+
}));
|
|
1056
|
+
break;
|
|
1057
|
+
case "keybindings.show":
|
|
1058
|
+
deps.update((s) => ({
|
|
1059
|
+
...s,
|
|
1060
|
+
ui: {
|
|
1061
|
+
...s.ui,
|
|
1062
|
+
overlay: s.ui.overlay === "keybindings" ? "none" : "keybindings"
|
|
1063
|
+
}
|
|
1064
|
+
}));
|
|
1065
|
+
break;
|
|
1066
|
+
case "pane.zoom":
|
|
1067
|
+
deps.update((s) => ({
|
|
1068
|
+
...s,
|
|
1069
|
+
ui: {
|
|
1070
|
+
...s.ui,
|
|
1071
|
+
zoomPane: s.ui.zoomPane ? null : s.ui.focusPane
|
|
1072
|
+
}
|
|
1073
|
+
}));
|
|
1074
|
+
break;
|
|
1075
|
+
case "response.tab.next":
|
|
1076
|
+
deps.update((s) => ({
|
|
1077
|
+
...s,
|
|
1078
|
+
ui: { ...s.ui, responseTab: nextResponseTab(s.ui.responseTab) }
|
|
1079
|
+
}));
|
|
1080
|
+
break;
|
|
1081
|
+
case "response.tab.prev":
|
|
1082
|
+
deps.update((s) => ({
|
|
1083
|
+
...s,
|
|
1084
|
+
ui: { ...s.ui, responseTab: prevResponseTab(s.ui.responseTab) }
|
|
1085
|
+
}));
|
|
1086
|
+
break;
|
|
1087
|
+
case "response.copy":
|
|
1088
|
+
deps.update((s) => ({
|
|
1089
|
+
...s,
|
|
1090
|
+
ui: { ...s.ui, statusMessage: "Copy requested (see clipboard handler)" }
|
|
1091
|
+
}));
|
|
1092
|
+
break;
|
|
1093
|
+
case "response.search":
|
|
1094
|
+
deps.update((s) => ({
|
|
1095
|
+
...s,
|
|
1096
|
+
ui: { ...s.ui, focusPane: "response", responseTab: "pretty" }
|
|
1097
|
+
}));
|
|
1098
|
+
break;
|
|
1099
|
+
case "editor.searchNext":
|
|
1100
|
+
break;
|
|
1101
|
+
default:
|
|
1102
|
+
break;
|
|
1103
|
+
}
|
|
1104
|
+
};
|
|
1105
|
+
const context = {
|
|
1106
|
+
get state() {
|
|
1107
|
+
return deps.getState();
|
|
1108
|
+
},
|
|
1109
|
+
update: deps.update,
|
|
1110
|
+
runSend,
|
|
1111
|
+
openFile,
|
|
1112
|
+
saveFile,
|
|
1113
|
+
refreshWorkspace,
|
|
1114
|
+
reloadKeybindings: deps.reloadKeybindings,
|
|
1115
|
+
quit: deps.quit,
|
|
1116
|
+
execute
|
|
1117
|
+
};
|
|
1118
|
+
return context;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// src/ui/app-view.ts
|
|
1122
|
+
import path3 from "path";
|
|
1123
|
+
import {
|
|
1124
|
+
rgb,
|
|
1125
|
+
ui
|
|
1126
|
+
} from "@rezi-ui/core";
|
|
1127
|
+
|
|
1128
|
+
// src/utils/http-syntax.ts
|
|
1129
|
+
var KEYWORDS = /* @__PURE__ */ new Set([
|
|
1130
|
+
"GET",
|
|
1131
|
+
"POST",
|
|
1132
|
+
"PUT",
|
|
1133
|
+
"PATCH",
|
|
1134
|
+
"DELETE",
|
|
1135
|
+
"HEAD",
|
|
1136
|
+
"OPTIONS",
|
|
1137
|
+
"CONNECT",
|
|
1138
|
+
"TRACE",
|
|
1139
|
+
"GRAPHQL"
|
|
1140
|
+
]);
|
|
1141
|
+
function tokenizeHttpLine(line, _context) {
|
|
1142
|
+
const tokens = [];
|
|
1143
|
+
const methodMatch = /^\s*(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS|CONNECT|TRACE|GRAPHQL)\b/u.exec(
|
|
1144
|
+
line
|
|
1145
|
+
);
|
|
1146
|
+
if (methodMatch) {
|
|
1147
|
+
const method = methodMatch[1] ?? "";
|
|
1148
|
+
tokens.push({ text: method, kind: "keyword" });
|
|
1149
|
+
const rest = line.slice(methodMatch[0].length);
|
|
1150
|
+
if (rest.length > 0) {
|
|
1151
|
+
tokens.push({ text: rest, kind: "string" });
|
|
1152
|
+
}
|
|
1153
|
+
return tokens;
|
|
1154
|
+
}
|
|
1155
|
+
if (/^\s*#/u.test(line)) {
|
|
1156
|
+
return [{ text: line, kind: "comment" }];
|
|
1157
|
+
}
|
|
1158
|
+
if (/^\s*\/\//u.test(line)) {
|
|
1159
|
+
return [{ text: line, kind: "comment" }];
|
|
1160
|
+
}
|
|
1161
|
+
if (/^\s*@/u.test(line)) {
|
|
1162
|
+
return [{ text: line, kind: "type" }];
|
|
1163
|
+
}
|
|
1164
|
+
const headerMatch = /^\s*([!#$%&'*+\-.^_`|~0-9A-Za-z]+)(\s*:\s*)(.*)$/u.exec(line);
|
|
1165
|
+
if (headerMatch) {
|
|
1166
|
+
tokens.push({ text: headerMatch[1] ?? "", kind: "function" });
|
|
1167
|
+
tokens.push({ text: headerMatch[2] ?? "", kind: "operator" });
|
|
1168
|
+
tokens.push({ text: headerMatch[3] ?? "", kind: "string" });
|
|
1169
|
+
return tokens;
|
|
1170
|
+
}
|
|
1171
|
+
const words = line.split(/(\s+)/u);
|
|
1172
|
+
for (const word of words) {
|
|
1173
|
+
if (KEYWORDS.has(word)) {
|
|
1174
|
+
tokens.push({ text: word, kind: "keyword" });
|
|
1175
|
+
} else if (word.length > 0) {
|
|
1176
|
+
tokens.push({ text: word, kind: "plain" });
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
return tokens.length > 0 ? tokens : [{ text: line, kind: "plain" }];
|
|
1180
|
+
}
|
|
1181
|
+
function prettyJsonIfPossible(text) {
|
|
1182
|
+
const trimmed = text.trim();
|
|
1183
|
+
if (!trimmed) {
|
|
1184
|
+
return text;
|
|
1185
|
+
}
|
|
1186
|
+
try {
|
|
1187
|
+
return JSON.stringify(JSON.parse(trimmed), null, 2);
|
|
1188
|
+
} catch {
|
|
1189
|
+
return text;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
function statusTone(statusCode) {
|
|
1193
|
+
if (!statusCode) {
|
|
1194
|
+
return "cyan";
|
|
1195
|
+
}
|
|
1196
|
+
if (statusCode >= 200 && statusCode < 300) {
|
|
1197
|
+
return "green";
|
|
1198
|
+
}
|
|
1199
|
+
if (statusCode >= 400 && statusCode < 500) {
|
|
1200
|
+
return "yellow";
|
|
1201
|
+
}
|
|
1202
|
+
if (statusCode >= 500) {
|
|
1203
|
+
return "red";
|
|
1204
|
+
}
|
|
1205
|
+
return "cyan";
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
// src/ui/app-view.ts
|
|
1209
|
+
function paneStyle(focused) {
|
|
1210
|
+
return focused ? { fg: rgb(180, 220, 255), bold: true } : { fg: rgb(120, 120, 120) };
|
|
1211
|
+
}
|
|
1212
|
+
function renderFileTree(state, deps) {
|
|
1213
|
+
return ui.panel(
|
|
1214
|
+
{
|
|
1215
|
+
id: "pane-files",
|
|
1216
|
+
title: state.ui.focusPane === "files" ? "\u25CF Files" : "Files",
|
|
1217
|
+
style: paneStyle(state.ui.focusPane === "files")
|
|
1218
|
+
},
|
|
1219
|
+
[
|
|
1220
|
+
ui.tree({
|
|
1221
|
+
id: "file-tree",
|
|
1222
|
+
data: state.fileTree,
|
|
1223
|
+
expanded: state.expandedPaths,
|
|
1224
|
+
selected: state.selectedFilePath ?? void 0,
|
|
1225
|
+
getKey: (node) => node.path,
|
|
1226
|
+
getChildren: (node) => node.kind === "directory" ? node.children : void 0,
|
|
1227
|
+
onChange: (node, expanded) => deps.onTreeToggle(node, expanded),
|
|
1228
|
+
onSelect: (node) => deps.onTreeSelect(node),
|
|
1229
|
+
onPress: (node) => deps.onTreePress(node),
|
|
1230
|
+
renderNode: (node, _depth, nodeState) => ui.row({ gap: 1 }, [
|
|
1231
|
+
ui.text(nodeState.selected ? `\u25B8 ${node.name}` : node.name, {
|
|
1232
|
+
style: nodeState.selected ? { fg: rgb(255, 220, 120), bold: true } : state.dirty && node.path === state.selectedFilePath ? { fg: rgb(255, 180, 80) } : void 0
|
|
1233
|
+
})
|
|
1234
|
+
]),
|
|
1235
|
+
flex: 1,
|
|
1236
|
+
minHeight: 8
|
|
1237
|
+
})
|
|
1238
|
+
]
|
|
1239
|
+
);
|
|
1240
|
+
}
|
|
1241
|
+
function renderEditor(state, deps, readOnly) {
|
|
1242
|
+
const activeRegion = state.activeRegion ?? (state.parsedFile ? resolveRegionAtLine(state.parsedFile.regions, state.editor.cursor.line) : null);
|
|
1243
|
+
const titleParts = [
|
|
1244
|
+
state.ui.focusPane === "editor" ? "\u25CF Editor" : "Editor",
|
|
1245
|
+
state.selectedFilePath ? state.selectedFilePath.split("/").pop() : "No file",
|
|
1246
|
+
state.dirty ? " \u25CF" : "",
|
|
1247
|
+
activeRegion ? ` | ${activeRegion.method ?? "?"} ${activeRegion.name}` : ""
|
|
1248
|
+
];
|
|
1249
|
+
const diagnostics = state.parsedFile ? buildRegionDiagnostics(
|
|
1250
|
+
state.parsedFile.regions,
|
|
1251
|
+
activeRegion?.id ?? null,
|
|
1252
|
+
state.fileLines
|
|
1253
|
+
) : [];
|
|
1254
|
+
return ui.panel(
|
|
1255
|
+
{
|
|
1256
|
+
id: "pane-editor",
|
|
1257
|
+
title: titleParts.join(""),
|
|
1258
|
+
style: paneStyle(state.ui.focusPane === "editor")
|
|
1259
|
+
},
|
|
1260
|
+
[
|
|
1261
|
+
ui.codeEditor({
|
|
1262
|
+
id: "editor",
|
|
1263
|
+
lines: state.fileLines,
|
|
1264
|
+
cursor: state.editor.cursor,
|
|
1265
|
+
selection: state.editor.selection,
|
|
1266
|
+
scrollTop: state.editor.scrollTop,
|
|
1267
|
+
scrollLeft: state.editor.scrollLeft,
|
|
1268
|
+
readOnly,
|
|
1269
|
+
lineNumbers: true,
|
|
1270
|
+
syntaxLanguage: "plain",
|
|
1271
|
+
tokenizeLine: tokenizeHttpLine,
|
|
1272
|
+
diagnostics,
|
|
1273
|
+
onChange: (lines, cursor) => deps.onEditorChange(lines, cursor),
|
|
1274
|
+
onSelectionChange: deps.onEditorSelection,
|
|
1275
|
+
onScroll: deps.onEditorScroll,
|
|
1276
|
+
flex: 1,
|
|
1277
|
+
minHeight: 8
|
|
1278
|
+
})
|
|
1279
|
+
]
|
|
1280
|
+
);
|
|
1281
|
+
}
|
|
1282
|
+
function responseScrollProps(state, deps) {
|
|
1283
|
+
return {
|
|
1284
|
+
scrollTop: state.responseEditor.scrollTop,
|
|
1285
|
+
scrollLeft: state.responseEditor.scrollLeft,
|
|
1286
|
+
onScroll: deps.onResponseScroll
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
function responseCursorProps(state, deps) {
|
|
1290
|
+
return {
|
|
1291
|
+
cursor: state.responseEditor.cursor,
|
|
1292
|
+
selection: state.responseEditor.selection,
|
|
1293
|
+
onChange: (_lines, cursor) => {
|
|
1294
|
+
deps.onResponseChange(cursor);
|
|
1295
|
+
},
|
|
1296
|
+
onSelectionChange: deps.onResponseSelection
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1299
|
+
function renderResponseBody(state, deps) {
|
|
1300
|
+
const result = state.request.result;
|
|
1301
|
+
const scroll = responseScrollProps(state, deps);
|
|
1302
|
+
const cursor = responseCursorProps(state, deps);
|
|
1303
|
+
const gen = state.resultGeneration;
|
|
1304
|
+
if (state.request.sending) {
|
|
1305
|
+
return ui.center(ui.spinner({ label: "Waiting for response\u2026" }));
|
|
1306
|
+
}
|
|
1307
|
+
if (state.request.error && !result) {
|
|
1308
|
+
return ui.errorDisplay(state.request.error);
|
|
1309
|
+
}
|
|
1310
|
+
if (!result) {
|
|
1311
|
+
return ui.empty("No response", {
|
|
1312
|
+
description: "Press F5 to send the request under cursor"
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
switch (state.ui.responseTab) {
|
|
1316
|
+
case "headers":
|
|
1317
|
+
return ui.table({
|
|
1318
|
+
id: `response-headers-${gen}`,
|
|
1319
|
+
columns: [
|
|
1320
|
+
{ key: "name", header: "Header", width: 24 },
|
|
1321
|
+
{ key: "value", header: "Value", flex: 1 }
|
|
1322
|
+
],
|
|
1323
|
+
data: [...result.headers],
|
|
1324
|
+
getRowKey: (row) => row.name,
|
|
1325
|
+
flex: 1,
|
|
1326
|
+
minHeight: 6
|
|
1327
|
+
});
|
|
1328
|
+
case "raw":
|
|
1329
|
+
return ui.codeEditor({
|
|
1330
|
+
id: `response-raw-${gen}`,
|
|
1331
|
+
lines: result.body ? result.body.split("\n") : [""],
|
|
1332
|
+
readOnly: true,
|
|
1333
|
+
lineNumbers: true,
|
|
1334
|
+
syntaxLanguage: "plain",
|
|
1335
|
+
...cursor,
|
|
1336
|
+
...scroll,
|
|
1337
|
+
flex: 1
|
|
1338
|
+
});
|
|
1339
|
+
case "variables":
|
|
1340
|
+
return ui.codeEditor({
|
|
1341
|
+
id: `response-vars-${gen}`,
|
|
1342
|
+
lines: [JSON.stringify(state.request.variables, null, 2)],
|
|
1343
|
+
readOnly: true,
|
|
1344
|
+
syntaxLanguage: "json",
|
|
1345
|
+
...cursor,
|
|
1346
|
+
...scroll,
|
|
1347
|
+
flex: 1
|
|
1348
|
+
});
|
|
1349
|
+
case "tests":
|
|
1350
|
+
return ui.column({ gap: 1, flex: 1 }, [
|
|
1351
|
+
...result.testResults.map(
|
|
1352
|
+
(test) => ui.text(`${test.status === "SUCCESS" ? "\u2713" : "\u2717"} ${test.message}`, {
|
|
1353
|
+
style: {
|
|
1354
|
+
fg: test.status === "SUCCESS" ? rgb(120, 220, 120) : test.status === "SKIPPED" ? rgb(220, 220, 120) : rgb(220, 120, 120)
|
|
1355
|
+
}
|
|
1356
|
+
})
|
|
1357
|
+
)
|
|
1358
|
+
]);
|
|
1359
|
+
case "pretty":
|
|
1360
|
+
default:
|
|
1361
|
+
return ui.codeEditor({
|
|
1362
|
+
id: `response-pretty-${gen}`,
|
|
1363
|
+
lines: prettyJsonIfPossible(result.prettyBody || result.body).split("\n"),
|
|
1364
|
+
readOnly: true,
|
|
1365
|
+
lineNumbers: true,
|
|
1366
|
+
syntaxLanguage: "json",
|
|
1367
|
+
searchQuery: state.editor.searchQuery || void 0,
|
|
1368
|
+
...cursor,
|
|
1369
|
+
...scroll,
|
|
1370
|
+
flex: 1
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
function renderResponse(state, deps) {
|
|
1375
|
+
const result = state.request.result;
|
|
1376
|
+
const statusLine = state.request.sending || !result ? null : `${result.protocol ?? "HTTP"} ${result.statusCode ?? "?"} ${result.statusMessage ?? ""} \xB7 ${result.durationMs ?? "?"} ms`;
|
|
1377
|
+
const tabItems = [
|
|
1378
|
+
{ key: "pretty", label: "Pretty" },
|
|
1379
|
+
{ key: "raw", label: "Raw" },
|
|
1380
|
+
{ key: "headers", label: "Headers" },
|
|
1381
|
+
{ key: "variables", label: "Vars" },
|
|
1382
|
+
{ key: "tests", label: "Tests" }
|
|
1383
|
+
];
|
|
1384
|
+
return ui.panel(
|
|
1385
|
+
{
|
|
1386
|
+
id: "pane-response",
|
|
1387
|
+
title: state.ui.focusPane === "response" ? "\u25CF Response" : "Response",
|
|
1388
|
+
style: paneStyle(state.ui.focusPane === "response")
|
|
1389
|
+
},
|
|
1390
|
+
[
|
|
1391
|
+
ui.row({ gap: 2 }, [
|
|
1392
|
+
state.request.sending ? ui.spinner({ label: "Sending request\u2026" }) : ui.text(statusLine ?? "Ready", {
|
|
1393
|
+
style: { fg: rgb(...statusColor(result?.statusCode)), bold: true }
|
|
1394
|
+
}),
|
|
1395
|
+
result?.error && !state.request.sending ? ui.badge(result.error, { variant: "error" }) : null
|
|
1396
|
+
]),
|
|
1397
|
+
ui.row({ gap: 1 }, [
|
|
1398
|
+
...tabItems.map(
|
|
1399
|
+
(tab) => ui.button({
|
|
1400
|
+
id: `tab-${tab.key}`,
|
|
1401
|
+
label: tab.label,
|
|
1402
|
+
disabled: state.request.sending,
|
|
1403
|
+
onPress: () => deps.onResponseTab(tab.key)
|
|
1404
|
+
})
|
|
1405
|
+
)
|
|
1406
|
+
]),
|
|
1407
|
+
renderResponseBody(state, deps)
|
|
1408
|
+
]
|
|
1409
|
+
);
|
|
1410
|
+
}
|
|
1411
|
+
function statusColor(code) {
|
|
1412
|
+
const tone = statusTone(code);
|
|
1413
|
+
switch (tone) {
|
|
1414
|
+
case "green":
|
|
1415
|
+
return [120, 220, 140];
|
|
1416
|
+
case "yellow":
|
|
1417
|
+
return [240, 200, 100];
|
|
1418
|
+
case "red":
|
|
1419
|
+
return [240, 120, 120];
|
|
1420
|
+
default:
|
|
1421
|
+
return [140, 200, 240];
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
function renderMainLayout(state, deps) {
|
|
1425
|
+
const layoutMode = resolveLayoutMode(state.ui.viewportWidth);
|
|
1426
|
+
const zoom = state.ui.zoomPane;
|
|
1427
|
+
if (zoom) {
|
|
1428
|
+
if (zoom === "files") return renderFileTree(state, deps);
|
|
1429
|
+
if (zoom === "editor") return renderEditor(state, deps, false);
|
|
1430
|
+
return renderResponse(state, deps);
|
|
1431
|
+
}
|
|
1432
|
+
if (layoutMode === "stacked") {
|
|
1433
|
+
const pane = state.ui.focusPane === "files" ? renderFileTree(state, deps) : state.ui.focusPane === "response" ? renderResponse(state, deps) : renderEditor(state, deps, false);
|
|
1434
|
+
return pane;
|
|
1435
|
+
}
|
|
1436
|
+
if (layoutMode === "sidebar-overlay") {
|
|
1437
|
+
return ui.row({ gap: 1, flex: 1 }, [
|
|
1438
|
+
state.ui.sidebarVisible ? ui.box({ width: 28, flex: 0 }, [renderFileTree(state, deps)]) : null,
|
|
1439
|
+
ui.column({ gap: 1, flex: 1 }, [
|
|
1440
|
+
renderEditor(state, deps, false),
|
|
1441
|
+
renderResponse(state, deps)
|
|
1442
|
+
])
|
|
1443
|
+
]);
|
|
1444
|
+
}
|
|
1445
|
+
return ui.column({ gap: 1, flex: 1 }, [
|
|
1446
|
+
ui.splitPane(
|
|
1447
|
+
{
|
|
1448
|
+
id: "main-split",
|
|
1449
|
+
direction: "horizontal",
|
|
1450
|
+
sizes: [...state.ui.splitSizes],
|
|
1451
|
+
minSizes: [18, 24, 24],
|
|
1452
|
+
onChange: deps.onSplitChange
|
|
1453
|
+
},
|
|
1454
|
+
[
|
|
1455
|
+
renderFileTree(state, deps),
|
|
1456
|
+
renderEditor(state, deps, false),
|
|
1457
|
+
renderResponse(state, deps)
|
|
1458
|
+
]
|
|
1459
|
+
)
|
|
1460
|
+
]);
|
|
1461
|
+
}
|
|
1462
|
+
function renderFooter(state) {
|
|
1463
|
+
const env = state.request.activeEnvironment.length > 0 ? state.request.activeEnvironment.join(",") : "none";
|
|
1464
|
+
const dirName = path3.basename(state.workspaceRoot);
|
|
1465
|
+
const branch = state.ui.gitBranch ?? "\u2014";
|
|
1466
|
+
const fileName = state.selectedFilePath ? path3.basename(state.selectedFilePath) : null;
|
|
1467
|
+
const hints = footerHints({
|
|
1468
|
+
focusPane: state.ui.focusPane,
|
|
1469
|
+
overlay: state.ui.overlay,
|
|
1470
|
+
viewportWidth: state.ui.viewportWidth,
|
|
1471
|
+
sending: state.request.sending
|
|
1472
|
+
});
|
|
1473
|
+
return ui.statusBar({
|
|
1474
|
+
id: "status-bar",
|
|
1475
|
+
left: [
|
|
1476
|
+
ui.text(dirName, { style: { fg: rgb(180, 220, 255), bold: true } }),
|
|
1477
|
+
ui.text(` \u2387 ${branch}`, { style: { fg: rgb(160, 220, 160) } }),
|
|
1478
|
+
fileName ? ui.text(` | ${fileName}`) : null,
|
|
1479
|
+
state.dirty ? ui.text(" \u25CF", { style: { fg: rgb(255, 180, 80) } }) : null,
|
|
1480
|
+
ui.text(` | env: ${env}`, { style: { fg: rgb(160, 200, 255) } }),
|
|
1481
|
+
state.ui.statusMessage ? ui.text(` | ${state.ui.statusMessage}`) : null
|
|
1482
|
+
].filter(Boolean),
|
|
1483
|
+
right: [ui.text(hints)]
|
|
1484
|
+
});
|
|
1485
|
+
}
|
|
1486
|
+
function renderOverlayContent(state, deps) {
|
|
1487
|
+
switch (state.ui.overlay) {
|
|
1488
|
+
case "env":
|
|
1489
|
+
return ui.modal({
|
|
1490
|
+
id: "env-modal",
|
|
1491
|
+
title: "Environment",
|
|
1492
|
+
content: ui.column({ gap: 1 }, [
|
|
1493
|
+
ui.text("Select environment (Enter to apply, Esc to close)"),
|
|
1494
|
+
ui.text("(none)", {
|
|
1495
|
+
style: state.ui.envSelectedIndex === 0 ? { fg: rgb(255, 220, 120), bold: true } : void 0
|
|
1496
|
+
}),
|
|
1497
|
+
...state.request.environments.map(
|
|
1498
|
+
(env, index) => ui.text(env, {
|
|
1499
|
+
style: index + 1 === state.ui.envSelectedIndex ? { fg: rgb(255, 220, 120), bold: true } : void 0
|
|
1500
|
+
})
|
|
1501
|
+
)
|
|
1502
|
+
]),
|
|
1503
|
+
onClose: deps.onOverlayClose,
|
|
1504
|
+
width: 50,
|
|
1505
|
+
height: 16
|
|
1506
|
+
});
|
|
1507
|
+
case "help":
|
|
1508
|
+
return ui.modal({
|
|
1509
|
+
id: "help-modal",
|
|
1510
|
+
title: "reqex help",
|
|
1511
|
+
content: ui.column({ gap: 1 }, HELP_HINT_LINES.map((line) => ui.text(line))),
|
|
1512
|
+
onClose: deps.onOverlayClose,
|
|
1513
|
+
width: 70,
|
|
1514
|
+
height: 14
|
|
1515
|
+
});
|
|
1516
|
+
case "keybindings": {
|
|
1517
|
+
const maxLines = Math.max(8, Math.min(26, state.ui.viewportHeight - 8));
|
|
1518
|
+
const lines = buildKeybindingsViewLines(
|
|
1519
|
+
state.settings.keybindings,
|
|
1520
|
+
maxLines
|
|
1521
|
+
);
|
|
1522
|
+
return ui.modal({
|
|
1523
|
+
id: "keybindings-modal",
|
|
1524
|
+
title: "Keybindings",
|
|
1525
|
+
content: ui.column({ gap: 0 }, lines.map((line) => ui.text(line))),
|
|
1526
|
+
onClose: deps.onOverlayClose,
|
|
1527
|
+
width: 72,
|
|
1528
|
+
height: Math.min(maxLines + 4, 28)
|
|
1529
|
+
});
|
|
1530
|
+
}
|
|
1531
|
+
case "commandPalette":
|
|
1532
|
+
return ui.center(
|
|
1533
|
+
ui.commandPalette({
|
|
1534
|
+
id: "command-palette",
|
|
1535
|
+
open: true,
|
|
1536
|
+
query: state.ui.commandPalette.query,
|
|
1537
|
+
selectedIndex: state.ui.commandPalette.selectedIndex,
|
|
1538
|
+
sources: [
|
|
1539
|
+
{
|
|
1540
|
+
id: "commands",
|
|
1541
|
+
name: "Commands",
|
|
1542
|
+
getItems: (query) => COMMAND_ITEMS.filter(
|
|
1543
|
+
(item) => item.label.toLowerCase().includes(query.toLowerCase())
|
|
1544
|
+
).map((item) => ({
|
|
1545
|
+
id: item.id,
|
|
1546
|
+
label: item.label,
|
|
1547
|
+
description: item.description,
|
|
1548
|
+
shortcut: item.shortcut,
|
|
1549
|
+
sourceId: "commands"
|
|
1550
|
+
}))
|
|
1551
|
+
}
|
|
1552
|
+
],
|
|
1553
|
+
onChange: deps.onCommandPaletteChange,
|
|
1554
|
+
onSelect: (item) => deps.onCommandPaletteSelect(item.id),
|
|
1555
|
+
onClose: deps.onOverlayClose,
|
|
1556
|
+
onSelectionChange: deps.onCommandPaletteSelectionChange,
|
|
1557
|
+
width: 60
|
|
1558
|
+
})
|
|
1559
|
+
);
|
|
1560
|
+
default:
|
|
1561
|
+
return ui.text("");
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
function renderApp(state, deps) {
|
|
1565
|
+
const base = ui.column({ gap: 1, flex: 1 }, [
|
|
1566
|
+
renderMainLayout(state, deps),
|
|
1567
|
+
renderFooter(state)
|
|
1568
|
+
]);
|
|
1569
|
+
if (state.ui.overlay === "none") {
|
|
1570
|
+
return base;
|
|
1571
|
+
}
|
|
1572
|
+
return ui.layers([
|
|
1573
|
+
base,
|
|
1574
|
+
ui.layer({
|
|
1575
|
+
id: "overlay-layer",
|
|
1576
|
+
modal: true,
|
|
1577
|
+
backdrop: "dim",
|
|
1578
|
+
closeOnEscape: true,
|
|
1579
|
+
onClose: deps.onOverlayClose,
|
|
1580
|
+
content: renderOverlayContent(state, deps)
|
|
1581
|
+
})
|
|
1582
|
+
]);
|
|
1583
|
+
}
|
|
1584
|
+
function focusPaneId(pane) {
|
|
1585
|
+
switch (pane) {
|
|
1586
|
+
case "files":
|
|
1587
|
+
return "pane-files";
|
|
1588
|
+
case "editor":
|
|
1589
|
+
return "pane-editor";
|
|
1590
|
+
case "response":
|
|
1591
|
+
return "pane-response";
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
// src/utils/clipboard.ts
|
|
1596
|
+
import clipboard from "clipboardy";
|
|
1597
|
+
async function copyToClipboard(text) {
|
|
1598
|
+
try {
|
|
1599
|
+
await clipboard.write(text);
|
|
1600
|
+
return true;
|
|
1601
|
+
} catch {
|
|
1602
|
+
if (process.stdout.write(`\x1B]52;c;${Buffer.from(text, "utf8").toString("base64")}\x07`)) {
|
|
1603
|
+
return true;
|
|
1604
|
+
}
|
|
1605
|
+
return false;
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
function disableFlowControl() {
|
|
1609
|
+
if (process.stdin.isTTY) {
|
|
1610
|
+
try {
|
|
1611
|
+
process.stdin.setRawMode?.(true);
|
|
1612
|
+
} catch {
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
// src/utils/git.ts
|
|
1618
|
+
import { execFile } from "child_process";
|
|
1619
|
+
import { promisify } from "util";
|
|
1620
|
+
var execFileAsync = promisify(execFile);
|
|
1621
|
+
async function getGitBranch(root) {
|
|
1622
|
+
try {
|
|
1623
|
+
const { stdout } = await execFileAsync(
|
|
1624
|
+
"git",
|
|
1625
|
+
["-C", root, "rev-parse", "--abbrev-ref", "HEAD"],
|
|
1626
|
+
{ timeout: 2e3 }
|
|
1627
|
+
);
|
|
1628
|
+
const branch = stdout.trim();
|
|
1629
|
+
return branch.length > 0 ? branch : null;
|
|
1630
|
+
} catch {
|
|
1631
|
+
return null;
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
// src/cli.ts
|
|
1636
|
+
async function main() {
|
|
1637
|
+
initEngineProviders();
|
|
1638
|
+
const workspaceRoot = path4.resolve(process2.argv[2] ?? process2.cwd());
|
|
1639
|
+
const workspace = new Workspace(workspaceRoot);
|
|
1640
|
+
const tree = await workspace.open();
|
|
1641
|
+
let currentState = createInitialState(workspaceRoot);
|
|
1642
|
+
currentState = {
|
|
1643
|
+
...currentState,
|
|
1644
|
+
fileTree: tree,
|
|
1645
|
+
expandedPaths: tree.filter((n) => n.kind === "directory").map((n) => n.path),
|
|
1646
|
+
ui: {
|
|
1647
|
+
...currentState.ui,
|
|
1648
|
+
gitBranch: await getGitBranch(workspaceRoot)
|
|
1649
|
+
}
|
|
1650
|
+
};
|
|
1651
|
+
let app = null;
|
|
1652
|
+
let bus = null;
|
|
1653
|
+
const refreshGitBranch = async () => {
|
|
1654
|
+
const branch = await getGitBranch(workspaceRoot);
|
|
1655
|
+
app?.update((prev) => ({
|
|
1656
|
+
...prev,
|
|
1657
|
+
ui: { ...prev.ui, gitBranch: branch }
|
|
1658
|
+
}));
|
|
1659
|
+
};
|
|
1660
|
+
const reloadKeybindings = () => {
|
|
1661
|
+
if (!app || !bus) {
|
|
1662
|
+
return;
|
|
1663
|
+
}
|
|
1664
|
+
const loaded = loadKeybindings(workspaceRoot);
|
|
1665
|
+
app.keys({
|
|
1666
|
+
...buildBindingMap(loaded.bindings, (command) => {
|
|
1667
|
+
if (command === "response.copy") {
|
|
1668
|
+
void handleCopy(app, currentState);
|
|
1669
|
+
}
|
|
1670
|
+
bus.execute(command);
|
|
1671
|
+
}),
|
|
1672
|
+
enter: {
|
|
1673
|
+
handler: () => bus.execute("env.apply"),
|
|
1674
|
+
when: (ctx) => ctx.state.ui.overlay === "env",
|
|
1675
|
+
description: "Apply selected environment"
|
|
1676
|
+
},
|
|
1677
|
+
up: {
|
|
1678
|
+
handler: () => {
|
|
1679
|
+
if (currentState.ui.overlay === "env") {
|
|
1680
|
+
bus.execute("env.selectPrev");
|
|
1681
|
+
}
|
|
1682
|
+
},
|
|
1683
|
+
when: (ctx) => ctx.state.ui.overlay === "env" || ctx.state.ui.overlay === "commandPalette"
|
|
1684
|
+
},
|
|
1685
|
+
down: {
|
|
1686
|
+
handler: () => {
|
|
1687
|
+
if (currentState.ui.overlay === "env") {
|
|
1688
|
+
bus.execute("env.selectNext");
|
|
1689
|
+
}
|
|
1690
|
+
},
|
|
1691
|
+
when: (ctx) => ctx.state.ui.overlay === "env" || ctx.state.ui.overlay === "commandPalette"
|
|
1692
|
+
}
|
|
1693
|
+
});
|
|
1694
|
+
app.update((state) => ({
|
|
1695
|
+
...state,
|
|
1696
|
+
settings: {
|
|
1697
|
+
keymapPreset: loaded.preset,
|
|
1698
|
+
keybindings: loaded.bindings
|
|
1699
|
+
}
|
|
1700
|
+
}));
|
|
1701
|
+
};
|
|
1702
|
+
bus = createCommandContext({
|
|
1703
|
+
workspace,
|
|
1704
|
+
getState: () => currentState,
|
|
1705
|
+
update: (updater) => {
|
|
1706
|
+
app?.update((prev) => {
|
|
1707
|
+
currentState = typeof updater === "function" ? updater(prev) : updater;
|
|
1708
|
+
return currentState;
|
|
1709
|
+
});
|
|
1710
|
+
},
|
|
1711
|
+
quit: () => {
|
|
1712
|
+
void workspace.close().finally(() => {
|
|
1713
|
+
app?.stop().finally(() => process2.exit(0));
|
|
1714
|
+
});
|
|
1715
|
+
},
|
|
1716
|
+
reloadKeybindings
|
|
1717
|
+
});
|
|
1718
|
+
app = createNodeApp({ initialState: currentState });
|
|
1719
|
+
reloadKeybindings();
|
|
1720
|
+
disableFlowControl();
|
|
1721
|
+
const stopWatch = watchKeybindings(workspaceRoot, reloadKeybindings);
|
|
1722
|
+
workspace.on("change", () => {
|
|
1723
|
+
void bus.refreshWorkspace();
|
|
1724
|
+
void refreshGitBranch();
|
|
1725
|
+
});
|
|
1726
|
+
app.view(
|
|
1727
|
+
(state) => renderApp(state, {
|
|
1728
|
+
onEditorChange: (lines, cursor) => {
|
|
1729
|
+
app?.update((prev) => {
|
|
1730
|
+
const activeRegion = prev.parsedFile ? resolveRegionAtLine(prev.parsedFile.regions, cursor.line) : null;
|
|
1731
|
+
return {
|
|
1732
|
+
...prev,
|
|
1733
|
+
fileLines: [...lines],
|
|
1734
|
+
dirty: contentFromLines(lines) !== prev.fileContent,
|
|
1735
|
+
activeRegion,
|
|
1736
|
+
ui: { ...prev.ui, focusPane: "editor" },
|
|
1737
|
+
editor: { ...prev.editor, cursor }
|
|
1738
|
+
};
|
|
1739
|
+
});
|
|
1740
|
+
},
|
|
1741
|
+
onEditorSelection: (selection) => {
|
|
1742
|
+
app?.update((prev) => {
|
|
1743
|
+
const cursorLine = selection?.active.line ?? prev.editor.cursor.line;
|
|
1744
|
+
const activeRegion = prev.parsedFile ? resolveRegionAtLine(prev.parsedFile.regions, cursorLine) : null;
|
|
1745
|
+
return {
|
|
1746
|
+
...prev,
|
|
1747
|
+
activeRegion,
|
|
1748
|
+
ui: { ...prev.ui, focusPane: "editor" },
|
|
1749
|
+
editor: {
|
|
1750
|
+
...prev.editor,
|
|
1751
|
+
selection,
|
|
1752
|
+
cursor: selection?.active ?? prev.editor.cursor
|
|
1753
|
+
}
|
|
1754
|
+
};
|
|
1755
|
+
});
|
|
1756
|
+
},
|
|
1757
|
+
onEditorScroll: (scrollTop, scrollLeft) => {
|
|
1758
|
+
app?.update((prev) => ({
|
|
1759
|
+
...prev,
|
|
1760
|
+
editor: { ...prev.editor, scrollTop, scrollLeft }
|
|
1761
|
+
}));
|
|
1762
|
+
},
|
|
1763
|
+
onTreeSelect: (node) => {
|
|
1764
|
+
if (node.kind === "file") {
|
|
1765
|
+
void bus.openFile(node.path);
|
|
1766
|
+
}
|
|
1767
|
+
},
|
|
1768
|
+
onTreeToggle: (node, expanded) => {
|
|
1769
|
+
app?.update((prev) => ({
|
|
1770
|
+
...prev,
|
|
1771
|
+
expandedPaths: expanded ? [...prev.expandedPaths, node.path] : prev.expandedPaths.filter((p) => p !== node.path)
|
|
1772
|
+
}));
|
|
1773
|
+
},
|
|
1774
|
+
onTreePress: (node) => {
|
|
1775
|
+
if (node.kind === "file") {
|
|
1776
|
+
void bus.openFile(node.path);
|
|
1777
|
+
}
|
|
1778
|
+
},
|
|
1779
|
+
onResponseTab: (tab) => {
|
|
1780
|
+
app?.update((prev) => ({
|
|
1781
|
+
...prev,
|
|
1782
|
+
ui: { ...prev.ui, responseTab: tab },
|
|
1783
|
+
responseEditor: {
|
|
1784
|
+
...prev.responseEditor,
|
|
1785
|
+
scrollTop: 0,
|
|
1786
|
+
scrollLeft: 0,
|
|
1787
|
+
cursor: { line: 0, column: 0 },
|
|
1788
|
+
selection: null
|
|
1789
|
+
}
|
|
1790
|
+
}));
|
|
1791
|
+
},
|
|
1792
|
+
onResponseScroll: (scrollTop, scrollLeft) => {
|
|
1793
|
+
app?.update((prev) => ({
|
|
1794
|
+
...prev,
|
|
1795
|
+
responseEditor: { ...prev.responseEditor, scrollTop, scrollLeft }
|
|
1796
|
+
}));
|
|
1797
|
+
},
|
|
1798
|
+
onResponseSelection: (selection) => {
|
|
1799
|
+
app?.update((prev) => ({
|
|
1800
|
+
...prev,
|
|
1801
|
+
ui: { ...prev.ui, focusPane: "response" },
|
|
1802
|
+
responseEditor: {
|
|
1803
|
+
...prev.responseEditor,
|
|
1804
|
+
selection,
|
|
1805
|
+
cursor: selection?.active ?? prev.responseEditor.cursor
|
|
1806
|
+
}
|
|
1807
|
+
}));
|
|
1808
|
+
},
|
|
1809
|
+
onResponseChange: (cursor) => {
|
|
1810
|
+
app?.update((prev) => ({
|
|
1811
|
+
...prev,
|
|
1812
|
+
ui: { ...prev.ui, focusPane: "response" },
|
|
1813
|
+
responseEditor: { ...prev.responseEditor, cursor }
|
|
1814
|
+
}));
|
|
1815
|
+
},
|
|
1816
|
+
onSplitChange: (sizes) => {
|
|
1817
|
+
if (sizes.length === 3) {
|
|
1818
|
+
app?.update((prev) => ({
|
|
1819
|
+
...prev,
|
|
1820
|
+
ui: { ...prev.ui, splitSizes: [sizes[0], sizes[1], sizes[2]] }
|
|
1821
|
+
}));
|
|
1822
|
+
}
|
|
1823
|
+
},
|
|
1824
|
+
onCommandPaletteChange: (query) => {
|
|
1825
|
+
app?.update((prev) => ({
|
|
1826
|
+
...prev,
|
|
1827
|
+
ui: {
|
|
1828
|
+
...prev.ui,
|
|
1829
|
+
commandPalette: { ...prev.ui.commandPalette, query, selectedIndex: 0 }
|
|
1830
|
+
}
|
|
1831
|
+
}));
|
|
1832
|
+
},
|
|
1833
|
+
onCommandPaletteSelectionChange: (index) => {
|
|
1834
|
+
app?.update((prev) => ({
|
|
1835
|
+
...prev,
|
|
1836
|
+
ui: {
|
|
1837
|
+
...prev.ui,
|
|
1838
|
+
commandPalette: { ...prev.ui.commandPalette, selectedIndex: index }
|
|
1839
|
+
}
|
|
1840
|
+
}));
|
|
1841
|
+
},
|
|
1842
|
+
onCommandPaletteSelect: (id) => {
|
|
1843
|
+
bus?.execute("overlay.close");
|
|
1844
|
+
const command = commandFromPaletteId(id);
|
|
1845
|
+
if (command && command !== "palette.commands") {
|
|
1846
|
+
bus?.execute(command);
|
|
1847
|
+
}
|
|
1848
|
+
},
|
|
1849
|
+
onOverlayClose: () => {
|
|
1850
|
+
bus?.execute("overlay.close");
|
|
1851
|
+
},
|
|
1852
|
+
onEnvSelect: (index) => {
|
|
1853
|
+
app?.update((prev) => ({
|
|
1854
|
+
...prev,
|
|
1855
|
+
ui: { ...prev.ui, envSelectedIndex: index }
|
|
1856
|
+
}));
|
|
1857
|
+
},
|
|
1858
|
+
onResponseSearch: (query) => {
|
|
1859
|
+
app?.update((prev) => ({
|
|
1860
|
+
...prev,
|
|
1861
|
+
editor: { ...prev.editor, searchQuery: query }
|
|
1862
|
+
}));
|
|
1863
|
+
}
|
|
1864
|
+
})
|
|
1865
|
+
);
|
|
1866
|
+
app.onEvent((event) => handleUiEvent(event, app));
|
|
1867
|
+
const files = flattenFiles(tree).filter((node) => node.kind === "file");
|
|
1868
|
+
if (files[0]) {
|
|
1869
|
+
await bus.openFile(files[0].path);
|
|
1870
|
+
}
|
|
1871
|
+
await app.run();
|
|
1872
|
+
stopWatch();
|
|
1873
|
+
}
|
|
1874
|
+
async function handleCopy(app, state) {
|
|
1875
|
+
const result = state.request.result;
|
|
1876
|
+
if (!result) {
|
|
1877
|
+
return;
|
|
1878
|
+
}
|
|
1879
|
+
let text = result.body;
|
|
1880
|
+
if (state.ui.responseTab === "pretty") {
|
|
1881
|
+
text = result.prettyBody || result.body;
|
|
1882
|
+
} else if (state.ui.responseTab === "headers") {
|
|
1883
|
+
text = result.headers.map((h) => `${h.name}: ${h.value}`).join("\n");
|
|
1884
|
+
}
|
|
1885
|
+
const ok = await copyToClipboard(text);
|
|
1886
|
+
app.update((prev) => ({
|
|
1887
|
+
...prev,
|
|
1888
|
+
ui: { ...prev.ui, statusMessage: ok ? "Copied" : "Copy failed" }
|
|
1889
|
+
}));
|
|
1890
|
+
}
|
|
1891
|
+
function handleUiEvent(event, app) {
|
|
1892
|
+
if (event.kind === "engine" && event.event.kind === "resize") {
|
|
1893
|
+
const resize = event.event;
|
|
1894
|
+
app.update((prev) => ({
|
|
1895
|
+
...prev,
|
|
1896
|
+
ui: {
|
|
1897
|
+
...prev.ui,
|
|
1898
|
+
viewportWidth: resize.cols,
|
|
1899
|
+
viewportHeight: resize.rows,
|
|
1900
|
+
layoutMode: resolveLayoutMode(resize.cols),
|
|
1901
|
+
sidebarVisible: resolveLayoutMode(resize.cols) === "sidebar-overlay" ? prev.ui.sidebarVisible : true
|
|
1902
|
+
}
|
|
1903
|
+
}));
|
|
1904
|
+
return;
|
|
1905
|
+
}
|
|
1906
|
+
if (event.kind !== "engine" || event.event.kind !== "mouse") {
|
|
1907
|
+
return;
|
|
1908
|
+
}
|
|
1909
|
+
const { x, y } = event.event;
|
|
1910
|
+
const panes = [
|
|
1911
|
+
{ id: "pane-files", pane: "files" },
|
|
1912
|
+
{ id: "pane-editor", pane: "editor" },
|
|
1913
|
+
{ id: "pane-response", pane: "response" }
|
|
1914
|
+
];
|
|
1915
|
+
for (const entry of panes) {
|
|
1916
|
+
const rect = app.measureElement(entry.id);
|
|
1917
|
+
if (!rect) {
|
|
1918
|
+
continue;
|
|
1919
|
+
}
|
|
1920
|
+
if (x >= rect.x && x < rect.x + rect.w && y >= rect.y && y < rect.y + rect.h) {
|
|
1921
|
+
app.update((prev) => ({
|
|
1922
|
+
...prev,
|
|
1923
|
+
ui: { ...prev.ui, focusPane: entry.pane }
|
|
1924
|
+
}));
|
|
1925
|
+
break;
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
void main().catch((error) => {
|
|
1930
|
+
console.error(error);
|
|
1931
|
+
process2.exit(1);
|
|
1932
|
+
});
|
|
1933
|
+
export {
|
|
1934
|
+
focusPaneId
|
|
1935
|
+
};
|
|
1936
|
+
//# sourceMappingURL=cli.js.map
|