@@ -88,6 +88,7 @@ type PromptInput = {
8888export type PromptState = {
8989 placeholder : Accessor < StyledText | string >
9090 bindings : Accessor < KeyBinding [ ] >
91+ shell : Accessor < boolean >
9192 visible : Accessor < boolean >
9293 options : Accessor < PromptOption [ ] >
9394 selected : Accessor < number >
@@ -110,9 +111,14 @@ function clonePrompt(prompt: RunPrompt): RunPrompt {
110111 return {
111112 text : prompt . text ,
112113 parts : structuredClone ( prompt . parts ) ,
114+ ...( prompt . mode ? { mode : prompt . mode } : { } ) ,
113115 }
114116}
115117
118+ function emptyPrompt ( shell : boolean ) : RunPrompt {
119+ return shell ? { text : "" , parts : [ ] , mode : "shell" } : { text : "" , parts : [ ] }
120+ }
121+
116122function removeLineRange ( input : string ) {
117123 const hash = input . lastIndexOf ( "#" )
118124 return hash === - 1 ? input : input . slice ( 0 , hash )
@@ -274,7 +280,14 @@ export function RunPromptBody(props: {
274280export function createPromptState ( input : PromptInput ) : PromptState {
275281 const keys = createMemo ( ( ) => promptKeys ( input . keybinds ) )
276282 const bindings = createMemo ( ( ) => keys ( ) . bindings )
283+ const [ shell , setShell ] = createSignal ( false )
277284 const placeholder = createMemo ( ( ) => {
285+ if ( shell ( ) ) {
286+ return new StyledText ( [
287+ bg ( input . theme ( ) . surface ) ( fg ( input . theme ( ) . muted ) ( 'Run a command... "git status"' ) ) ,
288+ ] )
289+ }
290+
278291 if ( ! input . state ( ) . first ) {
279292 return ""
280293 }
@@ -301,6 +314,11 @@ export function createPromptState(input: PromptInput): PromptState {
301314 const [ query , setQuery ] = createSignal ( "" )
302315 const visible = createMemo ( ( ) => mode ( ) !== false )
303316
317+ const setShellMode = ( value : boolean ) => {
318+ setShell ( value )
319+ draft = value ? { ...draft , mode : "shell" } : { text : draft . text , parts : structuredClone ( draft . parts ) }
320+ }
321+
304322 const width = createMemo ( ( ) => Math . max ( 20 , input . width ( ) - 8 ) )
305323 const agents = createMemo < Auto [ ] > ( ( ) => {
306324 return input
@@ -577,6 +595,7 @@ export function createPromptState(input: PromptInput): PromptState {
577595
578596 const restore = ( value : RunPrompt , cursor = Bun . stringWidth ( value . text ) ) => {
579597 draft = clonePrompt ( value )
598+ setShell ( value . mode === "shell" )
580599 if ( ! area || area . isDestroyed ) {
581600 return
582601 }
@@ -596,7 +615,7 @@ export function createPromptState(input: PromptInput): PromptState {
596615
597616 clearParts ( )
598617 hide ( )
599- draft = { text : "" , parts : [ ] }
618+ draft = emptyPrompt ( shell ( ) )
600619 if ( ! area || area . isDestroyed ) {
601620 return
602621 }
@@ -606,15 +625,15 @@ export function createPromptState(input: PromptInput): PromptState {
606625 }
607626
608627 const replaceDraft = ( text : string ) => {
609- draft = { text, parts : [ ] }
628+ draft = shell ( ) ? { text , parts : [ ] , mode : "shell" } : { text, parts : [ ] }
610629 if ( ! area || area . isDestroyed ) {
611630 return
612631 }
613632
614633 hide ( )
615634 area . setText ( text )
616635 clearParts ( )
617- draft = { text : area . plainText , parts : [ ] }
636+ draft = shell ( ) ? { text : area . plainText , parts : [ ] , mode : "shell" } : { text : area . plainText , parts : [ ] }
618637 area . cursorOffset = Math . min ( Bun . stringWidth ( text ) , Bun . stringWidth ( area . plainText ) )
619638 scheduleRows ( )
620639 area . focus ( )
@@ -705,10 +724,16 @@ export function createPromptState(input: PromptInput): PromptState {
705724 }
706725
707726 syncParts ( )
708- draft = {
709- text : area . plainText ,
710- parts : structuredClone ( parts ) ,
711- }
727+ draft = shell ( )
728+ ? {
729+ text : area . plainText ,
730+ parts : structuredClone ( parts ) ,
731+ mode : "shell" ,
732+ }
733+ : {
734+ text : area . plainText ,
735+ parts : structuredClone ( parts ) ,
736+ }
712737 }
713738
714739 const push = ( value : RunPrompt ) => {
@@ -943,6 +968,35 @@ export function createPromptState(input: PromptInput): PromptState {
943968 }
944969 }
945970
971+ if (
972+ key . name === "!" &&
973+ ! shell ( ) &&
974+ ! event . ctrl &&
975+ ! event . meta &&
976+ ! event . super &&
977+ area &&
978+ ! area . isDestroyed &&
979+ area . cursorOffset === 0
980+ ) {
981+ event . preventDefault ( )
982+ setShellMode ( true )
983+ return
984+ }
985+
986+ if ( shell ( ) && ! visible ( ) ) {
987+ if ( key . name === "escape" ) {
988+ event . preventDefault ( )
989+ setShellMode ( false )
990+ return
991+ }
992+
993+ if ( key . name === "backspace" && area && ! area . isDestroyed && area . cursorOffset === 0 ) {
994+ event . preventDefault ( )
995+ setShellMode ( false )
996+ return
997+ }
998+ }
999+
9461000 if ( promptHit ( keys ( ) . clear , key ) ) {
9471001 const handled = requestExit ( )
9481002 if ( handled ) {
@@ -1028,23 +1082,28 @@ export function createPromptState(input: PromptInput): PromptState {
10281082 return
10291083 }
10301084
1031- if ( isExitCommand ( next . text ) ) {
1085+ if ( next . mode !== "shell" && isExitCommand ( next . text ) ) {
10321086 input . onExit ( )
10331087 return
10341088 }
10351089
1036- const parsed = isNewCommand ( next . text ) ? undefined : parseSlashCommand ( next . text , input . commands ( ) )
1090+ const parsed = next . mode === "shell" || isNewCommand ( next . text ) ? undefined : parseSlashCommand ( next . text , input . commands ( ) )
10371091 if ( parsed ?. type === "pending" ) {
10381092 input . onStatus ( "loading commands" )
10391093 return
10401094 }
10411095
10421096 const submit = parsed ?. type === "command" ? { ...next , command : parsed . command } : next
1097+ const shellMode = next . mode === "shell"
10431098
10441099 resetDraft ( )
10451100 queueMicrotask ( async ( ) => {
10461101 if ( await input . onSubmit ( submit ) ) {
10471102 push ( next )
1103+ if ( shellMode ) {
1104+ setShellMode ( false )
1105+ draft = emptyPrompt ( false )
1106+ }
10481107 return
10491108 }
10501109
@@ -1121,6 +1180,7 @@ export function createPromptState(input: PromptInput): PromptState {
11211180 return {
11221181 placeholder,
11231182 bindings,
1183+ shell,
11241184 visible,
11251185 options,
11261186 selected : menu . selected ,
0 commit comments