opencode-snippets 2.1.1 → 2.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/dist/tui.jsx CHANGED
@@ -5,9 +5,11 @@ import { useKeyboard } from "@opentui/solid";
5
5
  import { createEffect, createMemo, createResource, createSignal, Index, onCleanup, Show, } from "solid-js";
6
6
  import { CONFIG } from "./src/constants.js";
7
7
  import { ensureSnippetsDir, listSnippets, loadSnippets } from "./src/loader.js";
8
+ import { addPendingDraft } from "./src/pending-drafts.js";
9
+ import { markSnippetReloadRequested } from "./src/reload-signal.js";
8
10
  import { loadSkills } from "./src/skill-loader.js";
9
11
  import { filterSkills, filterSnippets, highlightMatches, matchedAliases, snippetDescription, } from "./src/tui-search.js";
10
- import { findTrailingHashtagTrigger, insertSkillLoad, insertSnippetTag, insertSnippetTrigger, preferredSnippetTag, stepSelection, } from "./src/tui-trigger.js";
12
+ import { findTrailingHashtagTrigger, insertSkillLoad, insertSnippetTag, insertSnippetTrigger, isReloadCommand, preferredSnippetTag, stepSelection, } from "./src/tui-trigger.js";
11
13
  const id = "opencode-snippets:autocomplete";
12
14
  const PROMPT_SYNC_MS = 50;
13
15
  const MENU_MAX_HEIGHT = 10;
@@ -24,7 +26,6 @@ const EMPTY_SNIPPET = `---
24
26
  description: ""
25
27
  ---
26
28
 
27
-
28
29
  `;
29
30
  const INLINE_BORDER = {
30
31
  border: ["left", "right"],
@@ -89,6 +90,49 @@ function normalizeSnippetName(input) {
89
90
  .replace(/-{2,}/g, "-")
90
91
  .replace(/^-+|-+$/g, "");
91
92
  }
93
+ function resolveExternalEditor() {
94
+ const visual = Bun.env.VISUAL?.trim();
95
+ if (visual) {
96
+ return {
97
+ command: visual,
98
+ env: "VISUAL",
99
+ };
100
+ }
101
+ const editor = Bun.env.EDITOR?.trim();
102
+ if (editor) {
103
+ return {
104
+ command: editor,
105
+ env: "EDITOR",
106
+ };
107
+ }
108
+ }
109
+ function editorBinary(editor) {
110
+ return editor.command.trim().split(/\s+/)[0] || "";
111
+ }
112
+ function usesTerminalUi(editor) {
113
+ const bin = editorBinary(editor).split(/[\\/]/).pop()?.toLowerCase();
114
+ if (!bin)
115
+ return true;
116
+ return ![
117
+ "code",
118
+ "code-insiders",
119
+ "cursor",
120
+ "windsurf",
121
+ "subl",
122
+ "zed",
123
+ "mate",
124
+ "idea",
125
+ "webstorm",
126
+ "pycharm",
127
+ "goland",
128
+ "clion",
129
+ "rubymine",
130
+ "fleet",
131
+ "notepad",
132
+ "notepad++",
133
+ "open",
134
+ ].includes(bin);
135
+ }
92
136
  async function ensureSnippetDraft(name, projectDir) {
93
137
  const dir = await ensureSnippetsDir(projectDir);
94
138
  const filePath = join(dir, `${name}${CONFIG.SNIPPET_EXTENSION}`);
@@ -97,27 +141,31 @@ async function ensureSnippetDraft(name, projectDir) {
97
141
  }
98
142
  return filePath;
99
143
  }
100
- async function openExternalEditor(api, filePath) {
101
- const editor = Bun.env.VISUAL || Bun.env.EDITOR;
144
+ async function openExternalEditor(api, filePath, editor) {
102
145
  if (!editor)
103
146
  return false;
104
- api.renderer.suspend();
105
- api.renderer.currentRenderBuffer.clear();
147
+ const interactive = usesTerminalUi(editor);
148
+ if (interactive) {
149
+ api.renderer.suspend();
150
+ api.renderer.currentRenderBuffer.clear();
151
+ }
106
152
  try {
107
153
  const cmd = process.platform === "win32"
108
- ? ["cmd", "/c", `${editor} "${filePath.replace(/"/g, '\\"')}"`]
109
- : [...editor.split(" "), filePath];
154
+ ? ["cmd", "/c", `${editor.command} "${filePath.replace(/"/g, '\\"')}"`]
155
+ : [...editor.command.split(" "), filePath];
110
156
  const proc = Bun.spawn(cmd, {
111
- stdin: "inherit",
112
- stdout: "inherit",
113
- stderr: "inherit",
157
+ stdin: interactive ? "inherit" : "ignore",
158
+ stdout: interactive ? "inherit" : "ignore",
159
+ stderr: interactive ? "inherit" : "ignore",
114
160
  });
115
161
  await proc.exited;
116
162
  return true;
117
163
  }
118
164
  finally {
119
- api.renderer.currentRenderBuffer.clear();
120
- api.renderer.resume();
165
+ if (interactive) {
166
+ api.renderer.currentRenderBuffer.clear();
167
+ api.renderer.resume();
168
+ }
121
169
  api.renderer.requestRender();
122
170
  }
123
171
  }
@@ -136,6 +184,29 @@ async function getSnippets(api) {
136
184
  const registry = await loadSnippets(api.state.path.directory);
137
185
  return sortSnippets(listSnippets(registry));
138
186
  }
187
+ async function reloadSnippetsInTui(api) {
188
+ const registry = await loadSnippets(api.state.path.directory);
189
+ await markSnippetReloadRequested(api.state.path.directory);
190
+ return listSnippets(registry).length;
191
+ }
192
+ function executeReloadInPrompt(api, ref, clear, refresh) {
193
+ void (async () => {
194
+ const count = await reloadSnippetsInTui(api);
195
+ await refresh();
196
+ clear();
197
+ ref.focus();
198
+ api.renderer.requestRender();
199
+ setTimeout(() => {
200
+ api.ui.toast({
201
+ variant: "success",
202
+ title: "Snippets reloaded",
203
+ message: `Reloaded ${count} snippet${count === 1 ? "" : "s"}.`,
204
+ duration: 3000,
205
+ });
206
+ api.renderer.requestRender();
207
+ }, 0);
208
+ })();
209
+ }
139
210
  async function getSkills(api) {
140
211
  const registry = await loadSkills(api.state.path.directory);
141
212
  return sortSkills([...registry.values()]);
@@ -147,11 +218,15 @@ function PromptWithSnippetAutocomplete(props) {
147
218
  const [prompt, setPrompt] = createSignal();
148
219
  const [dismissed, setDismissed] = createSignal();
149
220
  const [selected, setSelected] = createSignal(0);
150
- const [, setInputMode] = createSignal("keyboard");
221
+ const [inputMode, setInputMode] = createSignal("keyboard");
151
222
  const [ignoreMouseUntil, setIgnoreMouseUntil] = createSignal(0);
152
223
  const [lastMousePos, setLastMousePos] = createSignal();
153
224
  const [input, setInput] = createSignal("");
225
+ const [syncingPrompt, setSyncingPrompt] = createSignal(false);
226
+ const [menuEpoch, setMenuEpoch] = createSignal(0);
154
227
  const [creating, setCreating] = createSignal(false);
228
+ const [dialogOpen, setDialogOpen] = createSignal(false);
229
+ const [dialogHandoffUntil, setDialogHandoffUntil] = createSignal(0);
155
230
  const [snippets, { refetch: refetchSnippets }] = createResource(() => props.api.state.path.directory, () => getSnippets(props.api), {
156
231
  initialValue: [],
157
232
  });
@@ -163,11 +238,50 @@ function PromptWithSnippetAutocomplete(props) {
163
238
  props.bindPrompt(ref);
164
239
  props.hostRef?.(ref);
165
240
  };
241
+ const refreshSnippetOptions = async () => {
242
+ await refetchSnippets();
243
+ };
244
+ let pendingPromptSync;
245
+ let pendingPromptFocus;
246
+ let pendingDialogHandoff;
247
+ onCleanup(() => {
248
+ if (pendingPromptSync)
249
+ clearTimeout(pendingPromptSync);
250
+ if (pendingPromptFocus)
251
+ clearTimeout(pendingPromptFocus);
252
+ if (pendingDialogHandoff)
253
+ clearTimeout(pendingDialogHandoff);
254
+ });
166
255
  const lockKeyboardSelection = () => {
167
256
  setInputMode("keyboard");
168
257
  setIgnoreMouseUntil(Date.now() + MOUSE_HOVER_SUPPRESS_MS);
169
258
  };
170
259
  const allowMouseHover = () => Date.now() >= ignoreMouseUntil();
260
+ const dialogBlockingInput = () => dialogOpen() || dialogHandoffUntil() > 0;
261
+ const beginDialogHandoff = () => {
262
+ if (pendingDialogHandoff)
263
+ clearTimeout(pendingDialogHandoff);
264
+ setDialogHandoffUntil(Date.now() + 150);
265
+ pendingDialogHandoff = setTimeout(() => {
266
+ pendingDialogHandoff = undefined;
267
+ setDialogHandoffUntil(0);
268
+ props.api.renderer.requestRender();
269
+ }, 175);
270
+ };
271
+ const handlePromptSubmit = () => {
272
+ if (dialogBlockingInput()) {
273
+ return;
274
+ }
275
+ const ref = prompt();
276
+ if (ref && isReloadCommand(ref.current.input)) {
277
+ executeReloadInPrompt(props.api, ref, () => {
278
+ syncPromptInput(ref, "");
279
+ setDismissed(undefined);
280
+ }, refreshSnippetOptions);
281
+ return;
282
+ }
283
+ props.onSubmit?.();
284
+ };
171
285
  const recordMouseMove = (x, y) => {
172
286
  const last = lastMousePos();
173
287
  if (last?.x === x && last.y === y) {
@@ -176,20 +290,85 @@ function PromptWithSnippetAutocomplete(props) {
176
290
  setLastMousePos({ x, y });
177
291
  return true;
178
292
  };
293
+ const restorePromptFocus = (ref) => {
294
+ if (pendingPromptFocus)
295
+ clearTimeout(pendingPromptFocus);
296
+ pendingPromptFocus = setTimeout(() => {
297
+ pendingPromptFocus = undefined;
298
+ ref.focus();
299
+ }, 175);
300
+ };
179
301
  const syncPromptInput = (ref, nextInput) => {
180
302
  setPromptInput(ref, nextInput);
181
303
  setInput(nextInput);
304
+ setSyncingPrompt(false);
305
+ };
306
+ const optionsForQuery = (value) => {
307
+ const snippetOptions = filterSnippets(snippets(), value).map((snippet) => ({
308
+ kind: "snippet",
309
+ id: `snippet:${snippet.name}`,
310
+ label: `#${snippet.name}`,
311
+ description: snippetDescription(snippet),
312
+ aliases: matchedAliases(snippet, value),
313
+ snippet,
314
+ }));
315
+ const skillOptions = filterSkills(skills(), value).map((skill) => ({
316
+ kind: "skill",
317
+ id: `skill:${skill.name}`,
318
+ label: `#skill(${skill.name})`,
319
+ description: skillDescription(skill),
320
+ aliases: [],
321
+ skill,
322
+ }));
323
+ return [...snippetOptions, ...skillOptions];
324
+ };
325
+ const canCreateForQuery = (value) => {
326
+ const q = value.trim();
327
+ if (snippets.loading || skills.loading)
328
+ return false;
329
+ if (!q)
330
+ return false;
331
+ if (!normalizeSnippetName(q))
332
+ return false;
333
+ return optionsForQuery(q).length === 0;
334
+ };
335
+ const schedulePromptSync = () => {
336
+ const ref = prompt();
337
+ if (!ref)
338
+ return;
339
+ if (dialogBlockingInput())
340
+ return;
341
+ const prev = input();
342
+ setSyncingPrompt(true);
343
+ setMenuEpoch((n) => n + 1);
344
+ if (pendingPromptSync)
345
+ clearTimeout(pendingPromptSync);
346
+ pendingPromptSync = setTimeout(() => {
347
+ pendingPromptSync = undefined;
348
+ const next = ref.current.input;
349
+ setInput((prev) => (prev === next ? prev : next));
350
+ if (next !== prev) {
351
+ setSyncingPrompt(false);
352
+ }
353
+ props.api.renderer.requestRender();
354
+ }, 0);
182
355
  };
183
356
  createEffect(() => {
184
357
  const ref = prompt();
185
358
  if (!ref) {
186
359
  setInput("");
360
+ setSyncingPrompt(false);
187
361
  return;
188
362
  }
189
363
  // The prompt ref exposes current state but not an onInput hook, so mirror it.
190
364
  const sync = () => {
191
365
  const next = ref.current.input;
192
- setInput((prev) => (prev === next ? prev : next));
366
+ setInput((prev) => {
367
+ if (prev === next)
368
+ return prev;
369
+ setSyncingPrompt(false);
370
+ return next;
371
+ });
193
372
  };
194
373
  sync();
195
374
  const timer = setInterval(sync, PROMPT_SYNC_MS);
@@ -205,29 +384,15 @@ function PromptWithSnippetAutocomplete(props) {
205
384
  const next = match();
206
385
  if (!next)
207
386
  return [];
208
- const snippetOptions = filterSnippets(snippets(), next.query.trim()).map((snippet) => ({
209
- kind: "snippet",
210
- id: `snippet:${snippet.name}`,
211
- label: `#${snippet.name}`,
212
- description: snippetDescription(snippet),
213
- aliases: matchedAliases(snippet, next.query.trim()),
214
- snippet,
215
- }));
216
- const skillOptions = filterSkills(skills(), next.query.trim()).map((skill) => ({
217
- kind: "skill",
218
- id: `skill:${skill.name}`,
219
- label: `#skill(${skill.name})`,
220
- description: skillDescription(skill),
221
- aliases: [],
222
- skill,
223
- }));
224
- return [...snippetOptions, ...skillOptions];
387
+ return optionsForQuery(next.query.trim());
225
388
  });
226
389
  const draftName = createMemo(() => normalizeSnippetName(query()));
227
390
  const visible = createMemo(() => {
228
391
  const next = match();
229
392
  if (!next)
230
393
  return false;
394
+ if (syncingPrompt())
395
+ return false;
231
396
  return dismissed() !== next.token;
232
397
  });
233
398
  const canCreate = createMemo(() => {
@@ -249,6 +414,12 @@ function PromptWithSnippetAutocomplete(props) {
249
414
  return undefined;
250
415
  });
251
416
  let scroll;
417
+ createEffect(() => {
418
+ menuEpoch();
419
+ if (visible()) {
420
+ scroll = undefined;
421
+ }
422
+ });
252
423
  createEffect((prev) => {
253
424
  const next = match();
254
425
  if (!next) {
@@ -304,6 +475,19 @@ function PromptWithSnippetAutocomplete(props) {
304
475
  let dispose;
305
476
  const timer = setTimeout(() => {
306
477
  dispose = props.api.command.register(() => [
478
+ {
479
+ title: "Reload snippets",
480
+ value: "snippets.reload",
481
+ description: "Reload snippet files from disk",
482
+ category: "Prompt",
483
+ slash: { name: "snippets:reload" },
484
+ onSelect() {
485
+ executeReloadInPrompt(props.api, ref, () => {
486
+ syncPromptInput(ref, "");
487
+ setDismissed(undefined);
488
+ }, refreshSnippetOptions);
489
+ },
490
+ },
307
491
  {
308
492
  title: "Accept snippet autocomplete",
309
493
  value: "snippets.accept",
@@ -312,11 +496,35 @@ function PromptWithSnippetAutocomplete(props) {
312
496
  hidden: true,
313
497
  enabled: ref.focused,
314
498
  onSelect() {
499
+ if (isReloadCommand(ref.current.input)) {
500
+ executeReloadInPrompt(props.api, ref, () => {
501
+ syncPromptInput(ref, "");
502
+ setDismissed(undefined);
503
+ }, refreshSnippetOptions);
504
+ return;
505
+ }
506
+ if (dialogBlockingInput()) {
507
+ return;
508
+ }
315
509
  const current = findTrailingHashtagTrigger(ref.current.input);
316
510
  if (!current || dismissed() === current.token) {
317
511
  ref.submit();
318
512
  return;
319
513
  }
514
+ const live = optionsForQuery(current.query.trim());
515
+ const index = Math.min(selected(), Math.max(live.length - 1, 0));
516
+ if (syncingPrompt()) {
517
+ if (live.length > 0) {
518
+ chooseItem(live[index] ?? live[0]);
519
+ return;
520
+ }
521
+ if (canCreateForQuery(current.query)) {
522
+ void createSnippetDraft(current.query);
523
+ return;
524
+ }
525
+ ref.submit();
526
+ return;
527
+ }
320
528
  // Prefer the rendered dropdown state so Enter follows what the user can see.
321
529
  if (visible()) {
322
530
  const rendered = options();
@@ -325,23 +533,16 @@ function PromptWithSnippetAutocomplete(props) {
325
533
  chooseItem(rendered[index] ?? rendered[0]);
326
534
  return;
327
535
  }
328
- if (canCreate()) {
329
- void createSnippetDraft();
330
- return;
331
- }
332
536
  }
333
537
  if (snippets.loading || skills.loading) {
334
538
  return;
335
539
  }
336
- const live = options();
337
540
  if (live.length > 0) {
338
- const index = Math.min(selected(), live.length - 1);
339
541
  chooseItem(live[index] ?? live[0]);
340
542
  return;
341
543
  }
342
- const query = current.query.trim();
343
- if (normalizeSnippetName(query)) {
344
- void createSnippetDraft();
544
+ if (canCreateForQuery(current.query)) {
545
+ void createSnippetDraft(current.query);
345
546
  return;
346
547
  }
347
548
  ref.submit();
@@ -354,14 +555,15 @@ function PromptWithSnippetAutocomplete(props) {
354
555
  dispose?.();
355
556
  });
356
557
  });
357
- const createSnippetDraft = async () => {
558
+ const createSnippetDraft = async (rawQuery) => {
358
559
  const ref = prompt();
359
- const name = draftName();
560
+ const name = normalizeSnippetName(rawQuery ?? query());
360
561
  if (!ref || !name || creating())
361
562
  return;
362
563
  const current = findTrailingHashtagTrigger(ref.current.input);
363
564
  const nextInput = current ? `${ref.current.input.slice(0, current.start)}#${name}` : `#${name}`;
364
- const editor = Bun.env.VISUAL || Bun.env.EDITOR;
565
+ const dismissedToken = `#${name}`;
566
+ const editor = resolveExternalEditor();
365
567
  if (!editor) {
366
568
  props.api.ui.toast({
367
569
  variant: "warning",
@@ -369,35 +571,60 @@ function PromptWithSnippetAutocomplete(props) {
369
571
  });
370
572
  return;
371
573
  }
372
- setCreating(true);
373
- try {
374
- syncPromptInput(ref, nextInput);
375
- const filePath = await ensureSnippetDraft(name);
376
- const opened = await openExternalEditor(props.api, filePath);
377
- if (!opened) {
378
- syncPromptInput(ref, nextInput);
379
- return;
380
- }
381
- await refetchSnippets();
382
- syncPromptInput(ref, nextInput);
383
- setDismissed(undefined);
384
- }
385
- catch (error) {
386
- props.api.ui.toast({
387
- variant: "error",
388
- message: `Failed to create snippet: ${error instanceof Error ? error.message : String(error)}`,
389
- });
390
- syncPromptInput(ref, nextInput);
391
- }
392
- finally {
393
- setCreating(false);
394
- ref.focus();
395
- }
574
+ props.api.ui.dialog.setSize("medium");
575
+ setDialogOpen(true);
576
+ props.api.ui.dialog.replace(() => (<props.api.ui.DialogConfirm title={`Create snippet #${name}?`} message={`This will create the snippet draft and open it in $${editor.env} (${editor.command}).`} onCancel={() => {
577
+ setDialogOpen(false);
578
+ beginDialogHandoff();
579
+ props.api.ui.dialog.clear();
580
+ restorePromptFocus(ref);
581
+ }} onConfirm={() => {
582
+ setDialogOpen(false);
583
+ beginDialogHandoff();
584
+ props.api.ui.dialog.clear();
585
+ void (async () => {
586
+ setCreating(true);
587
+ try {
588
+ syncPromptInput(ref, nextInput);
589
+ const filePath = await ensureSnippetDraft(name);
590
+ await addPendingDraft(props.api.state.path.directory, name);
591
+ setDismissed(dismissedToken);
592
+ setCreating(false);
593
+ const opened = await openExternalEditor(props.api, filePath, editor);
594
+ if (!opened)
595
+ return;
596
+ }
597
+ catch (error) {
598
+ props.api.ui.toast({
599
+ variant: "error",
600
+ message: `Failed to create snippet: ${error instanceof Error ? error.message : String(error)}`,
601
+ });
602
+ syncPromptInput(ref, nextInput);
603
+ setDismissed(undefined);
604
+ }
605
+ finally {
606
+ setCreating(false);
607
+ restorePromptFocus(ref);
608
+ }
609
+ })();
610
+ }}/>));
396
611
  };
397
612
  useKeyboard((evt) => {
613
+ const ref = prompt();
614
+ const name = evt.name?.toLowerCase();
615
+ if (ref && isReloadCommand(ref.current.input) && (name === "return" || name === "enter")) {
616
+ executeReloadInPrompt(props.api, ref, () => {
617
+ syncPromptInput(ref, "");
618
+ setDismissed(undefined);
619
+ }, refreshSnippetOptions);
620
+ evt.preventDefault();
621
+ evt.stopPropagation();
622
+ return;
623
+ }
624
+ if (dialogBlockingInput())
625
+ return;
398
626
  if (!visible())
399
627
  return;
400
- const name = evt.name?.toLowerCase();
401
628
  const total = options().length;
402
629
  const actionable = total > 0 || canCreate();
403
630
  const isNavUp = name === "up";
@@ -441,7 +668,10 @@ function PromptWithSnippetAutocomplete(props) {
441
668
  }
442
669
  evt.preventDefault();
443
670
  evt.stopPropagation();
671
+ return;
444
672
  }
673
+ // Mirror the host prompt state right after normal typing so stale matches disappear.
674
+ schedulePromptSync();
445
675
  });
446
676
  const emptyLabel = createMemo(() => {
447
677
  if ((snippets.loading || skills.loading) && options().length === 0) {
@@ -454,7 +684,7 @@ function PromptWithSnippetAutocomplete(props) {
454
684
  const addSnippetLabel = createMemo(() => {
455
685
  if (creating())
456
686
  return "Creating snippet...";
457
- return `Add new Snippet #${draftName()}`;
687
+ return `Add new Snippet: #${draftName()}`;
458
688
  });
459
689
  const selectedFg = createMemo(() => selectedText(props.api.theme.current));
460
690
  return (<box>
@@ -467,9 +697,11 @@ function PromptWithSnippetAutocomplete(props) {
467
697
  <text fg={props.api.theme.current.textMuted}>{emptyLabel()}</text>
468
698
  </box>}>
469
699
  {/* biome-ignore lint/a11y/noStaticElementInteractions: OpenTUI rows intentionally handle mouse selection. */}
470
- <box id="create-snippet" paddingLeft={1} paddingRight={1} backgroundColor={props.api.theme.current.primary} onMouseMove={() => {
700
+ <box id="create-snippet" paddingLeft={1} paddingRight={1} backgroundColor={props.api.theme.current.primary} onMouseMove={(event) => {
471
701
  if (!allowMouseHover())
472
702
  return;
703
+ if (!recordMouseMove(event.x, event.y))
704
+ return;
473
705
  setInputMode("mouse");
474
706
  }} onMouseDown={() => {
475
707
  setInputMode("mouse");
@@ -482,6 +714,7 @@ function PromptWithSnippetAutocomplete(props) {
482
714
  </Show>}>
483
715
  {(option, index) => (
484
716
  // biome-ignore lint/a11y/noStaticElementInteractions: OpenTUI rows intentionally handle mouse selection.
717
+ // biome-ignore lint/a11y/useKeyWithMouseEvents: OpenTUI boxes do not expose DOM-style focus events.
485
718
  <box id={option().id} paddingLeft={1} paddingRight={1} backgroundColor={index === selected() ? props.api.theme.current.primary : undefined} flexDirection="row" onMouseMove={(event) => {
486
719
  if (!allowMouseHover())
487
720
  return;
@@ -489,6 +722,11 @@ function PromptWithSnippetAutocomplete(props) {
489
722
  if (!recordMouseMove(event.x, event.y))
490
723
  return;
491
724
  setInputMode("mouse");
725
+ }} onMouseOver={() => {
726
+ if (!allowMouseHover())
727
+ return;
728
+ if (inputMode() !== "mouse")
729
+ return;
492
730
  setSelected(index);
493
731
  }} onMouseDown={() => {
494
732
  setInputMode("mouse");
@@ -513,7 +751,7 @@ function PromptWithSnippetAutocomplete(props) {
513
751
  </scrollbox>
514
752
  </box>
515
753
  </Show>
516
- <props.api.ui.Prompt sessionID={props.sessionID} workspaceID={props.workspaceID} visible={props.visible} disabled={props.disabled} onSubmit={props.onSubmit} placeholders={props.placeholders} ref={bind} right={props.right}/>
754
+ <props.api.ui.Prompt sessionID={props.sessionID} workspaceID={props.workspaceID} visible={props.visible} disabled={props.disabled || dialogBlockingInput()} onSubmit={handlePromptSubmit} placeholders={props.placeholders} ref={bind} right={props.right}/>
517
755
  </box>);
518
756
  }
519
757
  const tui = async (api) => {