@@ -52,6 +52,9 @@ use crate::bottom_pane::StatusLinePreviewData;
5252use crate :: bottom_pane:: StatusLineSetupView ;
5353use crate :: bottom_pane:: TerminalTitleItem ;
5454use crate :: bottom_pane:: TerminalTitleSetupView ;
55+ use crate :: clipboard_shortcut:: ShortcutPasteAction ;
56+ use crate :: clipboard_shortcut:: ShortcutPasteRequest ;
57+ use crate :: clipboard_shortcut:: resolve_shortcut_paste;
5558use crate :: mention_codec:: LinkedMention ;
5659use crate :: mention_codec:: encode_history_mentions;
5760use crate :: model_catalog:: ModelCatalog ;
@@ -316,7 +319,6 @@ use crate::bottom_pane::SelectionItem;
316319use crate :: bottom_pane:: SelectionViewParams ;
317320use crate :: bottom_pane:: custom_prompt_view:: CustomPromptView ;
318321use crate :: bottom_pane:: popup_consts:: standard_popup_hint_line;
319- use crate :: clipboard_paste:: paste_image_to_temp_png;
320322use crate :: clipboard_text;
321323use crate :: collaboration_modes;
322324use crate :: diff_render:: display_path_for;
@@ -955,6 +957,8 @@ pub(crate) struct ChatWidget {
955957 realtime_conversation : RealtimeConversationUiState ,
956958 last_rendered_user_message_event : Option < RenderedUserMessageEvent > ,
957959 last_non_retry_error : Option < ( String , String ) > ,
960+ #[ cfg( test) ]
961+ shortcut_paste_handler : Option < fn ( ShortcutPasteRequest ) -> ShortcutPasteAction > ,
958962}
959963
960964/// Cached nickname and role for a collab agent thread, used to attach human-readable labels to
@@ -4716,6 +4720,8 @@ impl ChatWidget {
47164720 realtime_conversation : RealtimeConversationUiState :: default ( ) ,
47174721 last_rendered_user_message_event : None ,
47184722 last_non_retry_error : None ,
4723+ #[ cfg( test) ]
4724+ shortcut_paste_handler : None ,
47194725 } ;
47204726
47214727 widget
@@ -4778,33 +4784,6 @@ impl ChatWidget {
47784784 self . quit_shortcut_expires_at = None ;
47794785 self . quit_shortcut_key = None ;
47804786 }
4781- KeyEvent {
4782- code : KeyCode :: Char ( c) ,
4783- modifiers,
4784- kind : KeyEventKind :: Press ,
4785- ..
4786- } if modifiers. intersects ( KeyModifiers :: CONTROL | KeyModifiers :: ALT )
4787- && c. eq_ignore_ascii_case ( & 'v' ) =>
4788- {
4789- match paste_image_to_temp_png ( ) {
4790- Ok ( ( path, info) ) => {
4791- tracing:: debug!(
4792- "pasted image size={}x{} format={}" ,
4793- info. width,
4794- info. height,
4795- info. encoded_format. label( )
4796- ) ;
4797- self . attach_image ( path) ;
4798- }
4799- Err ( err) => {
4800- tracing:: warn!( "failed to paste image: {err}" ) ;
4801- self . add_to_history ( history_cell:: new_error_event ( format ! (
4802- "Failed to paste image: {err}" ,
4803- ) ) ) ;
4804- }
4805- }
4806- return ;
4807- }
48084787 other if other. kind == KeyEventKind :: Press => {
48094788 self . bottom_pane . clear_quit_shortcut_hint ( ) ;
48104789 self . quit_shortcut_expires_at = None ;
@@ -4813,6 +4792,10 @@ impl ChatWidget {
48134792 _ => { }
48144793 }
48154794
4795+ if self . handle_shortcut_paste_key ( key_event) {
4796+ return ;
4797+ }
4798+
48164799 if key_event. kind == KeyEventKind :: Press
48174800 && self . queued_message_edit_binding . is_press ( key_event)
48184801 && self . has_queued_follow_up_messages ( )
@@ -4926,6 +4909,63 @@ impl ChatWidget {
49264909 }
49274910 }
49284911
4912+ fn handle_shortcut_paste_key ( & mut self , key_event : KeyEvent ) -> bool {
4913+ let request = match key_event {
4914+ KeyEvent {
4915+ code : KeyCode :: Char ( c) ,
4916+ modifiers,
4917+ kind : KeyEventKind :: Press ,
4918+ ..
4919+ } if modifiers. intersects ( KeyModifiers :: CONTROL | KeyModifiers :: ALT )
4920+ && c. eq_ignore_ascii_case ( & 'v' ) =>
4921+ {
4922+ Some ( if modifiers == KeyModifiers :: ALT {
4923+ ShortcutPasteRequest :: AltV
4924+ } else {
4925+ ShortcutPasteRequest :: CtrlV
4926+ } )
4927+ }
4928+ _ => None ,
4929+ } ;
4930+
4931+ let Some ( request) = request else {
4932+ return false ;
4933+ } ;
4934+
4935+ let action = {
4936+ #[ cfg( test) ]
4937+ if let Some ( handler) = self . shortcut_paste_handler {
4938+ handler ( request)
4939+ } else {
4940+ resolve_shortcut_paste ( request)
4941+ }
4942+
4943+ #[ cfg( not( test) ) ]
4944+ {
4945+ resolve_shortcut_paste ( request)
4946+ }
4947+ } ;
4948+
4949+ match action {
4950+ ShortcutPasteAction :: Text ( text) => self . handle_paste ( text) ,
4951+ ShortcutPasteAction :: Image { path, info } => {
4952+ tracing:: debug!(
4953+ "pasted image size={}x{} format={}" ,
4954+ info. width,
4955+ info. height,
4956+ info. encoded_format. label( )
4957+ ) ;
4958+ self . attach_image ( path) ;
4959+ }
4960+ ShortcutPasteAction :: Error ( message) => {
4961+ warn ! ( "{message}" ) ;
4962+ self . add_to_history ( history_cell:: new_error_event ( message) ) ;
4963+ }
4964+ }
4965+
4966+ true
4967+ }
4968+
49294969 /// Attach a local image to the composer when the active model supports image inputs.
49304970 ///
49314971 /// When the model does not advertise image support, we keep the draft unchanged and surface a
@@ -10337,6 +10377,14 @@ impl ChatWidget {
1033710377 self . bottom_pane . is_task_running ( )
1033810378 }
1033910379
10380+ #[ cfg( test) ]
10381+ pub ( crate ) fn set_shortcut_paste_handler_for_test (
10382+ & mut self ,
10383+ handler : fn ( ShortcutPasteRequest ) -> ShortcutPasteAction ,
10384+ ) {
10385+ self . shortcut_paste_handler = Some ( handler) ;
10386+ }
10387+
1034010388 pub ( crate ) fn submit_user_message_with_mode (
1034110389 & mut self ,
1034210390 text : String ,
0 commit comments