|
|
@@ -88,6 +88,7 @@ type PromptInput = {
|
|
|
export type PromptState = {
|
|
|
placeholder: Accessor<StyledText | string>
|
|
|
bindings: Accessor<KeyBinding[]>
|
|
|
+ shell: Accessor<boolean>
|
|
|
visible: Accessor<boolean>
|
|
|
options: Accessor<PromptOption[]>
|
|
|
selected: Accessor<number>
|
|
|
@@ -110,9 +111,14 @@ function clonePrompt(prompt: RunPrompt): RunPrompt {
|
|
|
return {
|
|
|
text: prompt.text,
|
|
|
parts: structuredClone(prompt.parts),
|
|
|
+ ...(prompt.mode ? { mode: prompt.mode } : {}),
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+function emptyPrompt(shell: boolean): RunPrompt {
|
|
|
+ return shell ? { text: "", parts: [], mode: "shell" } : { text: "", parts: [] }
|
|
|
+}
|
|
|
+
|
|
|
function removeLineRange(input: string) {
|
|
|
const hash = input.lastIndexOf("#")
|
|
|
return hash === -1 ? input : input.slice(0, hash)
|
|
|
@@ -274,7 +280,14 @@ export function RunPromptBody(props: {
|
|
|
export function createPromptState(input: PromptInput): PromptState {
|
|
|
const keys = createMemo(() => promptKeys(input.keybinds))
|
|
|
const bindings = createMemo(() => keys().bindings)
|
|
|
+ const [shell, setShell] = createSignal(false)
|
|
|
const placeholder = createMemo(() => {
|
|
|
+ if (shell()) {
|
|
|
+ return new StyledText([
|
|
|
+ bg(input.theme().surface)(fg(input.theme().muted)('Run a command... "git status"')),
|
|
|
+ ])
|
|
|
+ }
|
|
|
+
|
|
|
if (!input.state().first) {
|
|
|
return ""
|
|
|
}
|
|
|
@@ -301,6 +314,11 @@ export function createPromptState(input: PromptInput): PromptState {
|
|
|
const [query, setQuery] = createSignal("")
|
|
|
const visible = createMemo(() => mode() !== false)
|
|
|
|
|
|
+ const setShellMode = (value: boolean) => {
|
|
|
+ setShell(value)
|
|
|
+ draft = value ? { ...draft, mode: "shell" } : { text: draft.text, parts: structuredClone(draft.parts) }
|
|
|
+ }
|
|
|
+
|
|
|
const width = createMemo(() => Math.max(20, input.width() - 8))
|
|
|
const agents = createMemo<Auto[]>(() => {
|
|
|
return input
|
|
|
@@ -577,6 +595,7 @@ export function createPromptState(input: PromptInput): PromptState {
|
|
|
|
|
|
const restore = (value: RunPrompt, cursor = Bun.stringWidth(value.text)) => {
|
|
|
draft = clonePrompt(value)
|
|
|
+ setShell(value.mode === "shell")
|
|
|
if (!area || area.isDestroyed) {
|
|
|
return
|
|
|
}
|
|
|
@@ -596,7 +615,7 @@ export function createPromptState(input: PromptInput): PromptState {
|
|
|
|
|
|
clearParts()
|
|
|
hide()
|
|
|
- draft = { text: "", parts: [] }
|
|
|
+ draft = emptyPrompt(shell())
|
|
|
if (!area || area.isDestroyed) {
|
|
|
return
|
|
|
}
|
|
|
@@ -606,7 +625,7 @@ export function createPromptState(input: PromptInput): PromptState {
|
|
|
}
|
|
|
|
|
|
const replaceDraft = (text: string) => {
|
|
|
- draft = { text, parts: [] }
|
|
|
+ draft = shell() ? { text, parts: [], mode: "shell" } : { text, parts: [] }
|
|
|
if (!area || area.isDestroyed) {
|
|
|
return
|
|
|
}
|
|
|
@@ -614,7 +633,7 @@ export function createPromptState(input: PromptInput): PromptState {
|
|
|
hide()
|
|
|
area.setText(text)
|
|
|
clearParts()
|
|
|
- draft = { text: area.plainText, parts: [] }
|
|
|
+ draft = shell() ? { text: area.plainText, parts: [], mode: "shell" } : { text: area.plainText, parts: [] }
|
|
|
area.cursorOffset = Math.min(Bun.stringWidth(text), Bun.stringWidth(area.plainText))
|
|
|
scheduleRows()
|
|
|
area.focus()
|
|
|
@@ -705,10 +724,16 @@ export function createPromptState(input: PromptInput): PromptState {
|
|
|
}
|
|
|
|
|
|
syncParts()
|
|
|
- draft = {
|
|
|
- text: area.plainText,
|
|
|
- parts: structuredClone(parts),
|
|
|
- }
|
|
|
+ draft = shell()
|
|
|
+ ? {
|
|
|
+ text: area.plainText,
|
|
|
+ parts: structuredClone(parts),
|
|
|
+ mode: "shell",
|
|
|
+ }
|
|
|
+ : {
|
|
|
+ text: area.plainText,
|
|
|
+ parts: structuredClone(parts),
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
const push = (value: RunPrompt) => {
|
|
|
@@ -943,6 +968,35 @@ export function createPromptState(input: PromptInput): PromptState {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ if (
|
|
|
+ key.name === "!" &&
|
|
|
+ !shell() &&
|
|
|
+ !event.ctrl &&
|
|
|
+ !event.meta &&
|
|
|
+ !event.super &&
|
|
|
+ area &&
|
|
|
+ !area.isDestroyed &&
|
|
|
+ area.cursorOffset === 0
|
|
|
+ ) {
|
|
|
+ event.preventDefault()
|
|
|
+ setShellMode(true)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if (shell() && !visible()) {
|
|
|
+ if (key.name === "escape") {
|
|
|
+ event.preventDefault()
|
|
|
+ setShellMode(false)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if (key.name === "backspace" && area && !area.isDestroyed && area.cursorOffset === 0) {
|
|
|
+ event.preventDefault()
|
|
|
+ setShellMode(false)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
if (promptHit(keys().clear, key)) {
|
|
|
const handled = requestExit()
|
|
|
if (handled) {
|
|
|
@@ -1028,23 +1082,28 @@ export function createPromptState(input: PromptInput): PromptState {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- if (isExitCommand(next.text)) {
|
|
|
+ if (next.mode !== "shell" && isExitCommand(next.text)) {
|
|
|
input.onExit()
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- const parsed = isNewCommand(next.text) ? undefined : parseSlashCommand(next.text, input.commands())
|
|
|
+ const parsed = next.mode === "shell" || isNewCommand(next.text) ? undefined : parseSlashCommand(next.text, input.commands())
|
|
|
if (parsed?.type === "pending") {
|
|
|
input.onStatus("loading commands")
|
|
|
return
|
|
|
}
|
|
|
|
|
|
const submit = parsed?.type === "command" ? { ...next, command: parsed.command } : next
|
|
|
+ const shellMode = next.mode === "shell"
|
|
|
|
|
|
resetDraft()
|
|
|
queueMicrotask(async () => {
|
|
|
if (await input.onSubmit(submit)) {
|
|
|
push(next)
|
|
|
+ if (shellMode) {
|
|
|
+ setShellMode(false)
|
|
|
+ draft = emptyPrompt(false)
|
|
|
+ }
|
|
|
return
|
|
|
}
|
|
|
|
|
|
@@ -1121,6 +1180,7 @@ export function createPromptState(input: PromptInput): PromptState {
|
|
|
return {
|
|
|
placeholder,
|
|
|
bindings,
|
|
|
+ shell,
|
|
|
visible,
|
|
|
options,
|
|
|
selected: menu.selected,
|