@@ -20,6 +20,7 @@ import { useExit } from "../../context/exit"
2020import { Clipboard } from "../../util/clipboard"
2121import type { FilePart } from "@opencode-ai/sdk/v2"
2222import { TuiEvent } from "../../event"
23+ import { Ide } from "@/ide"
2324import { iife } from "@/util/iife"
2425import { Locale } from "@/util/locale"
2526import { createColors , createFrames } from "../../ui/spinner.ts"
@@ -44,7 +45,6 @@ export type PromptRef = {
4445 reset ( ) : void
4546 blur ( ) : void
4647 focus ( ) : void
47- submit ( ) : void
4848}
4949
5050const PLACEHOLDERS = [ "Fix a TODO in the codebase" , "What is the tech stack of this project?" , "Fix broken tests" ]
@@ -116,7 +116,7 @@ export function Prompt(props: PromptProps) {
116116 const sync = useSync ( )
117117 const dialog = useDialog ( )
118118 const toast = useToast ( )
119- const status = createMemo ( ( ) => sync . data . session_status ?. [ props . sessionID ?? "" ] ?? { type : "idle" } )
119+ const status = createMemo ( ( ) => sync . data . session_status [ props . sessionID ?? "" ] ?? { type : "idle" } )
120120 const history = usePromptHistory ( )
121121 const command = useCommandDialog ( )
122122 const renderer = useRenderer ( )
@@ -312,6 +312,10 @@ export function Prompt(props: PromptProps) {
312312 input . insertText ( evt . properties . text )
313313 } )
314314
315+ sdk . event . on ( Ide . Event . SelectionChanged . type , ( evt ) => {
316+ updateIdeSelection ( evt . properties . selection )
317+ } )
318+
315319 createEffect ( ( ) => {
316320 if ( props . disabled ) input . cursorColor = theme . backgroundElement
317321 if ( ! props . disabled ) input . cursorColor = theme . text
@@ -342,6 +346,49 @@ export function Prompt(props: PromptProps) {
342346 promptPartTypeId = input . extmarks . registerType ( "prompt-part" )
343347 } )
344348
349+ // Track IDE selection extmark so we can update/remove it
350+ let ideSelectionExtmarkId : number | null = null
351+
352+ function removeExtmark ( extmarkId : number ) {
353+ const allExtmarks = input . extmarks . getAllForTypeId ( promptPartTypeId )
354+ const extmark = allExtmarks . find ( ( e ) => e . id === extmarkId )
355+ const partIndex = store . extmarkToPartIndex . get ( extmarkId )
356+
357+ if ( partIndex !== undefined ) {
358+ setStore (
359+ produce ( ( draft ) => {
360+ draft . prompt . parts . splice ( partIndex , 1 )
361+ draft . extmarkToPartIndex . delete ( extmarkId )
362+ const newMap = new Map < number , number > ( )
363+ for ( const [ id , idx ] of draft . extmarkToPartIndex ) {
364+ newMap . set ( id , idx > partIndex ? idx - 1 : idx )
365+ }
366+ draft . extmarkToPartIndex = newMap
367+ } ) ,
368+ )
369+ }
370+
371+ if ( extmark ) {
372+ const savedOffset = input . cursorOffset
373+ input . cursorOffset = extmark . start
374+ const start = { ...input . logicalCursor }
375+ input . cursorOffset = extmark . end + 1
376+ input . deleteRange ( start . row , start . col , input . logicalCursor . row , input . logicalCursor . col )
377+ input . cursorOffset =
378+ savedOffset > extmark . start
379+ ? Math . max ( extmark . start , savedOffset - ( extmark . end + 1 - extmark . start ) )
380+ : savedOffset
381+ }
382+
383+ input . extmarks . delete ( extmarkId )
384+ }
385+
386+ function updateIdeSelection ( selection : Ide . Selection | null ) {
387+ // Selection is now displayed in footer via local.selection
388+ // No visual insertion in the input needed
389+ // Content will be included at submit time from local.selection
390+ }
391+
345392 function restoreExtmarksFromParts ( parts : PromptInfo [ "parts" ] ) {
346393 input . extmarks . clear ( )
347394 setStore ( "extmarkToPartIndex" , new Map ( ) )
@@ -448,14 +495,11 @@ export function Prompt(props: PromptProps) {
448495 } )
449496 setStore ( "extmarkToPartIndex" , new Map ( ) )
450497 } ,
451- submit ( ) {
452- submit ( )
453- } ,
454498 } )
455499
456500 async function submit ( ) {
457501 if ( props . disabled ) return
458- if ( autocomplete ? .visible ) return
502+ if ( autocomplete . visible ) return
459503 if ( ! store . prompt . input ) return
460504 const trimmed = store . prompt . input . trim ( )
461505 if ( trimmed === "exit" || trimmed === "quit" || trimmed === ":q" ) {
@@ -476,6 +520,8 @@ export function Prompt(props: PromptProps) {
476520 const messageID = Identifier . ascending ( "message" )
477521 let inputText = store . prompt . input
478522
523+ // IDE selection is displayed in footer only - not injected into message
524+
479525 // Expand pasted text inline before submitting
480526 const allExtmarks = input . extmarks . getAllForTypeId ( promptPartTypeId )
481527 const sortedExtmarks = allExtmarks . sort ( ( a : { start : number } , b : { start : number } ) => b . start - a . start )
@@ -495,9 +541,6 @@ export function Prompt(props: PromptProps) {
495541 // Filter out text parts (pasted content) since they're now expanded inline
496542 const nonTextParts = store . prompt . parts . filter ( ( part ) => part . type !== "text" )
497543
498- // Capture mode before it gets reset
499- const currentMode = store . mode
500-
501544 if ( store . mode === "shell" ) {
502545 sdk . client . session . shell ( {
503546 sessionID,
@@ -539,23 +582,27 @@ export function Prompt(props: PromptProps) {
539582 type : "text" ,
540583 text : inputText ,
541584 } ,
585+ ...( local . selection . current ( ) ?. text ? [ {
586+ id : Identifier . ascending ( "part" ) ,
587+ type : "text" as const ,
588+ text : `\n\n[IDE Selection: ${ local . selection . current ( ) ! . filePath . split ( "/" ) . pop ( ) || local . selection . current ( ) ! . filePath } :${ local . selection . current ( ) ! . selection . start . line + 1 } -${ local . selection . current ( ) ! . selection . end . line + 1 } ]\n\`\`\`\n${ local . selection . current ( ) ! . text } \n\`\`\`` ,
589+ synthetic : true ,
590+ } ] : [ ] ) ,
542591 ...nonTextParts . map ( ( x ) => ( {
543592 id : Identifier . ascending ( "part" ) ,
544593 ...x ,
545594 } ) ) ,
546595 ] ,
547596 } )
548597 }
549- history . append ( {
550- ...store . prompt ,
551- mode : currentMode ,
552- } )
598+ history . append ( store . prompt )
553599 input . extmarks . clear ( )
554600 setStore ( "prompt" , {
555601 input : "" ,
556602 parts : [ ] ,
557603 } )
558604 setStore ( "extmarkToPartIndex" , new Map ( ) )
605+ ideSelectionExtmarkId = null
559606 props . onSubmit ?.( )
560607
561608 // temporary hack to make sure the message is sent
@@ -715,8 +762,8 @@ export function Prompt(props: PromptProps) {
715762 >
716763 < textarea
717764 placeholder = { props . sessionID ? undefined : `Ask anything... "${ PLACEHOLDERS [ store . placeholder ] } "` }
718- textColor = { keybind . leader ? theme . textMuted : theme . text }
719- focusedTextColor = { keybind . leader ? theme . textMuted : theme . text }
765+ textColor = { theme . text }
766+ focusedTextColor = { theme . text }
720767 minHeight = { 1 }
721768 maxHeight = { 6 }
722769 onContentChange = { ( ) => {
@@ -742,12 +789,8 @@ export function Prompt(props: PromptProps) {
742789 return
743790 }
744791 if ( keybind . match ( "app_exit" , e ) ) {
745- if ( store . prompt . input === "" ) {
746- await exit ( )
747- // Don't preventDefault - let textarea potentially handle the event
748- e . preventDefault ( )
749- return
750- }
792+ await exit ( )
793+ return
751794 }
752795 if ( e . name === "!" && input . visualCursor . offset === 0 ) {
753796 setStore ( "mode" , "shell" )
@@ -773,7 +816,6 @@ export function Prompt(props: PromptProps) {
773816 if ( item ) {
774817 input . setText ( item . input )
775818 setStore ( "prompt" , item )
776- setStore ( "mode" , item . mode ?? "normal" )
777819 restoreExtmarksFromParts ( item . parts )
778820 e . preventDefault ( )
779821 if ( direction === - 1 ) input . cursorOffset = 0
@@ -865,7 +907,7 @@ export function Prompt(props: PromptProps) {
865907 </ text >
866908 < Show when = { store . mode === "normal" } >
867909 < box flexDirection = "row" gap = { 1 } >
868- < text flexShrink = { 0 } fg = { keybind . leader ? theme . textMuted : theme . text } >
910+ < text flexShrink = { 0 } fg = { theme . text } >
869911 { local . model . parsed ( ) . model }
870912 </ text >
871913 < text fg = { theme . textMuted } > { local . model . parsed ( ) . provider } </ text >
@@ -880,15 +922,16 @@ export function Prompt(props: PromptProps) {
880922 borderColor = { highlight ( ) }
881923 customBorderChars = { {
882924 ...EmptyBorder ,
883- vertical : theme . backgroundElement . a !== 0 ? "╹" : " " ,
925+ // when the background is transparent, don't draw the vertical line
926+ vertical : theme . background . a != 0 ? "╹" : " " ,
884927 } }
885928 >
886929 < box
887930 height = { 1 }
888931 border = { [ "bottom" ] }
889932 borderColor = { theme . backgroundElement }
890933 customBorderChars = {
891- theme . backgroundElement . a != = 0
934+ theme . background . a != 0
892935 ? {
893936 ...EmptyBorder ,
894937 horizontal : "▀" ,
0 commit comments