@@ -24,6 +24,7 @@ import type {
2424 ComposerSuggestion ,
2525} from "../types"
2626import type { ComposerEditorModel , ComposerSelectControl } from "./interaction"
27+ import { handleComposerKeyDown , type ComposerKeybinds } from "./keybind"
2728import "../attachments/attachments.css"
2829import "./editor.css"
2930
@@ -49,6 +50,8 @@ export type ComposerEditorProps = {
4950 attachShortcut ?: string
5051 alternateKeybind ?: string [ ]
5152 exitShellKeybind ?: string [ ]
53+ keybinds ?: ComposerKeybinds
54+ submitKeybind ?: string [ ]
5255}
5356
5457export function ComposerEditor ( props : ComposerEditorProps ) {
@@ -75,6 +78,7 @@ export function ComposerEditor(props: ComposerEditorProps) {
7578 onCleanup ( ( ) => observer . disconnect ( ) )
7679 } )
7780 let localInput = false
81+ let composing = false
7882 const updateCursor = ( ) => {
7983 if ( ! editor || ! window . getSelection ( ) ?. isCollapsed ) return
8084 props . controller . onCursor ( composerCursor ( editor ) )
@@ -85,7 +89,6 @@ export function ComposerEditor(props: ComposerEditorProps) {
8589 "pointer-events" : mode ( ) === "normal" ? ( "auto" as const ) : ( "none" as const ) ,
8690 transition : "opacity 200ms ease" ,
8791 } ) )
88-
8992 createEffect ( ( ) => {
9093 const parts = props . controller . parts ( )
9194 if ( ! editor ) return
@@ -197,18 +200,25 @@ export function ComposerEditor(props: ComposerEditorProps) {
197200 props . controller . onInput ( prompt . map ( ( part ) => part . content ) . join ( "" ) , [ ...prompt , ...images ] , cursor )
198201 } }
199202 onKeyDown = { ( event ) => {
200- if ( ! view . draftOnly && props . controller . onKeyDown ( event ) ) return
201- const mod = event . metaKey || event . ctrlKey
202- if ( mod && event . key === "ArrowUp" && ! event . shiftKey && ! event . altKey ) {
203- if ( view . submit . queue ?. editFirst ( ) ) event . preventDefault ( )
204- return
205- }
206- if ( event . key === "Enter" && ! event . shiftKey && ! event . isComposing ) {
207- event . preventDefault ( )
208- if ( event . repeat ) return
209- props . controller . submit ( mod ? { alternate : true } : undefined )
210- }
203+ handleComposerKeyDown ( {
204+ event,
205+ keybinds : props . keybinds ,
206+ composing,
207+ selectSuggestion : ( ) => {
208+ const popover = state . popover
209+ if ( popover . type === "closed" ) return false
210+ const item = props . controller . suggestions ( ) . find ( ( entry ) => entry . id === popover . activeID )
211+ if ( item ) props . controller . dispatch ( { type : "popover.select" , item } )
212+ return true
213+ } ,
214+ handleController : ( ) => ! view . draftOnly && props . controller . onKeyDown ( event ) ,
215+ editFirst : view . submit . queue ?. editFirst ,
216+ insertNewline : props . controller . insertNewline ,
217+ submit : ( alternate ) => props . controller . submit ( alternate ? { alternate : true } : undefined ) ,
218+ } )
211219 } }
220+ onCompositionStart = { ( ) => ( composing = true ) }
221+ onCompositionEnd = { ( ) => ( composing = false ) }
212222 onKeyUp = { updateCursor }
213223 onPointerUp = { updateCursor }
214224 onPaste = { ( event ) => {
@@ -333,6 +343,7 @@ export function ComposerEditor(props: ComposerEditorProps) {
333343 disabled = { ! props . controller . canSubmit ( ) }
334344 sendLabel = { i18n . t ( "ui.promptInput.send" ) }
335345 stopLabel = { i18n . t ( "ui.promptInput.stop" ) }
346+ sendKeybind = { props . submitKeybind }
336347 onSubmit = { ( ) => props . controller . submit ( ) }
337348 onStop = { props . controller . stop }
338349 />
@@ -777,8 +788,8 @@ export function ComposerEditorPopover(props: {
777788 )
778789}
779790
780- // " Steer ⌘⏎" / " Queue ⌘⏎" hint next to the submit button : submits with the
781- // delivery opposite to what plain Enter does . Visible only while the queue
791+ // Steer / Queue action with its configured shortcut : submits with the
792+ // delivery opposite to normal submission . Visible only while the queue
782793// exposes an alternate (turn running and composer holding a value), so it
783794// disappears on its own when the current turn ends.
784795function ComposerEditorAlternateDelivery ( props : { controller : ComposerEditorModel ; keybind : string [ ] } ) {
@@ -826,14 +837,26 @@ export function ComposerEditorSubmitButton(props: {
826837 disabled : boolean
827838 sendLabel : string
828839 stopLabel : string
840+ sendKeybind ?: string [ ]
829841 onSubmit : ( ) => void
830842 onStop : ( ) => void
831843} ) {
832844 return (
833845 < Tooltip
834846 placement = "top"
835847 inactive = { ! props . stopping && props . disabled }
836- value = { props . stopping ? props . stopLabel : props . sendLabel }
848+ value = {
849+ props . stopping ? (
850+ props . stopLabel
851+ ) : (
852+ < >
853+ { props . sendLabel }
854+ < Show when = { props . sendKeybind ?. length } >
855+ < Keybind keys = { props . sendKeybind ?? [ ] } variant = "neutral" />
856+ </ Show >
857+ </ >
858+ )
859+ }
837860 >
838861 < IconButton
839862 data-action = "composer-submit"
0 commit comments