1+ mod cell_coordinates;
12mod color_scheme;
23pub mod events;
34mod keyboard;
45mod run_calculator;
56
6- use crate :: actions:: PasteAction ;
7+ use crate :: actions:: { CopyAction , PasteAction } ;
8+ use crate :: terminal_screen:: cell_coordinates:: CellCoordinates ;
79use crate :: terminal_screen:: color_scheme:: ColorScheme ;
810use crate :: terminal_screen:: events:: { TerminalScreenCloseEvent , TerminalScreenEvents } ;
911use crate :: terminal_screen:: keyboard:: Keyboard ;
@@ -15,14 +17,16 @@ use contemporary::components::dialog_box::{StandardButton, dialog_box};
1517use contemporary:: platform_support:: cx_platform_extensions:: CxPlatformExtensions ;
1618use gpui:: prelude:: FluentBuilder ;
1719use gpui:: {
18- App , AppContext , AsyncApp , BorderStyle , Bounds , Context , Corners , CursorStyle , Entity ,
19- EntityInputHandler , FocusHandle , Focusable , Hitbox , HitboxBehavior , Hsla , InteractiveElement ,
20- IntoElement , KeyBinding , KeyDownEvent , ParentElement , Pixels , Point , Refineable , Render ,
21- ScrollDelta , ScrollWheelEvent , Style , StyleRefinement , Styled , TextAlign , UTF16Selection ,
22- Window , WrappedLine , actions, canvas, div, point, px, quad, rgb, size, transparent_black,
20+ App , AppContext , AsyncApp , BorderStyle , Bounds , ClipboardItem , Context , Corners , CursorStyle ,
21+ DispatchPhase , Entity , EntityInputHandler , FocusHandle , Focusable , Hitbox , HitboxBehavior ,
22+ Hsla , InteractiveElement , IntoElement , KeyBinding , KeyDownEvent , MouseDownEvent ,
23+ MouseMoveEvent , MouseUpEvent , ParentElement , Pixels , Point , Refineable , Render , ScrollDelta ,
24+ ScrollWheelEvent , Style , StyleRefinement , Styled , TextAlign , UTF16Selection , Window ,
25+ WrappedLine , actions, canvas, div, point, px, quad, rgb, size, transparent_black,
2326} ;
2427use portable_pty:: { CommandBuilder , PtySize , native_pty_system} ;
2528use std:: cell:: RefCell ;
29+ use std:: cmp:: Ordering ;
2630use std:: io:: { Read , Write } ;
2731use std:: ops:: { Range , Rem } ;
2832use std:: rc:: Rc ;
@@ -57,12 +61,15 @@ pub struct TerminalScreen {
5761 timer : Instant ,
5862 close_warning_dialog : Option < Vec < String > > ,
5963 partial_scroll : f32 ,
64+ clicked_cell_coordinates : Option < CellCoordinates > ,
65+ selected_cell_coordinates : Option < Range < CellCoordinates > > ,
6066}
6167
6268pub struct TerminalScreenPrepaint {
6369 screen : Screen ,
6470 screen_hitbox : Hitbox ,
6571 screen_lines : Vec < Vec < WrappedLine > > ,
72+ cell_hitboxes : Vec < ( CellCoordinates , Hitbox ) > ,
6673 style : Style ,
6774 background : Hsla ,
6875 caret_rect : Bounds < Pixels > ,
@@ -208,6 +215,8 @@ impl TerminalScreen {
208215 timer : Instant :: now ( ) ,
209216 close_warning_dialog : None ,
210217 partial_scroll : 0. ,
218+ clicked_cell_coordinates : None ,
219+ selected_cell_coordinates : None ,
211220 }
212221 } )
213222 }
@@ -218,6 +227,39 @@ impl TerminalScreen {
218227
219228 pub fn delete ( & mut self , _: & Delete , window : & mut Window , cx : & mut Context < Self > ) { }
220229
230+ pub fn copy ( & mut self , _: & CopyAction , window : & mut Window , cx : & mut Context < Self > ) {
231+ let Some ( selected_cell_coordinates) = self . selected_cell_coordinates . as_ref ( ) else {
232+ return ;
233+ } ;
234+
235+ let screen = self . screen . read ( cx) ;
236+ let screen = screen. screen ( ) ;
237+ let mut copied_string = String :: new ( ) ;
238+ for line in 0 ..screen. size ( ) . 0 {
239+ for column in 0 ..screen. size ( ) . 1 {
240+ let cell = screen. cell ( line, column) ;
241+ let coordinates = CellCoordinates ( line, column) ;
242+ if selected_cell_coordinates. contains ( & coordinates) {
243+ if column == 0 {
244+ copied_string += "\n " ;
245+ }
246+
247+ copied_string += cell
248+ . map ( |cell| {
249+ if cell. has_contents ( ) {
250+ cell. contents ( )
251+ } else {
252+ " "
253+ }
254+ } )
255+ . unwrap_or_default ( )
256+ }
257+ }
258+ }
259+
260+ cx. write_to_clipboard ( ClipboardItem :: new_string ( copied_string. trim ( ) . to_string ( ) ) ) ;
261+ }
262+
221263 pub fn paste ( & mut self , _: & PasteAction , window : & mut Window , cx : & mut Context < Self > ) {
222264 if let Some ( clipboard_contents) = cx
223265 . read_from_clipboard ( )
@@ -366,18 +408,15 @@ impl TerminalScreen {
366408
367409impl Render for TerminalScreen {
368410 fn render ( & mut self , window : & mut Window , cx : & mut Context < Self > ) -> impl IntoElement {
369- let screen = self . screen . clone ( ) ;
370- let screen_size = self . screen_size . clone ( ) ;
371- let style = self . style . clone ( ) ;
372- let color_scheme = self . color_scheme ;
373- let timer = self . timer ;
374-
375411 div ( )
376412 . h_full ( )
377413 . w_full ( )
378414 . key_context ( "TerminalScreen" )
379415 . track_focus ( & self . focus_handle ( cx) )
380416 . on_action ( cx. listener ( Self :: delete) )
417+ . when ( self . selected_cell_coordinates . is_some ( ) , |div| {
418+ div. on_action ( cx. listener ( Self :: copy) )
419+ } )
381420 . on_action ( cx. listener ( Self :: paste) )
382421 . on_key_down ( cx. listener ( |this, event : & KeyDownEvent , window, cx| {
383422 this. process_key_press ( event, window, cx)
@@ -386,21 +425,14 @@ impl Render for TerminalScreen {
386425 this. process_scroll ( event, window, cx)
387426 } ) )
388427 . child (
389- canvas (
390- move |bounds, window, cx| {
391- prepaint_terminal_screen (
392- screen,
393- screen_size,
394- style,
395- color_scheme,
396- timer,
397- bounds,
398- window,
399- cx,
400- )
401- } ,
402- paint_terminal_screen,
403- )
428+ canvas ( cx. processor ( prepaint_terminal_screen) , {
429+ let entity = cx. entity ( ) ;
430+ move |bounds, prepaint_state, window, cx| {
431+ entity. update ( cx, |_, cx| {
432+ paint_terminal_screen ( bounds, prepaint_state, window, cx)
433+ } )
434+ }
435+ } )
404436 . w_full ( )
405437 . h_full ( ) ,
406438 )
@@ -536,15 +568,18 @@ impl Styled for TerminalScreen {
536568}
537569
538570fn prepaint_terminal_screen (
539- parser_entity : Entity < Parser < TerminalScreenCallbacks > > ,
540- screen_size : Entity < ScreenSize > ,
541- style_refinement : StyleRefinement ,
542- color_scheme : ColorScheme ,
543- timer : Instant ,
571+ terminal_screen : & mut TerminalScreen ,
544572 bounds : Bounds < Pixels > ,
545573 window : & mut Window ,
546- cx : & mut App ,
574+ cx : & mut Context < TerminalScreen > ,
547575) -> TerminalScreenPrepaint {
576+ let parser_entity = terminal_screen. screen . clone ( ) ;
577+ let screen_size = terminal_screen. screen_size . clone ( ) ;
578+ let style_refinement = terminal_screen. style . clone ( ) ;
579+ let color_scheme = terminal_screen. color_scheme ;
580+ let timer = terminal_screen. timer ;
581+ let selected_cell_coordinates = & terminal_screen. selected_cell_coordinates ;
582+
548583 let screen_hitbox = window. insert_hitbox ( bounds, HitboxBehavior :: Normal ) ;
549584
550585 let style = Style :: default ( ) . refined ( style_refinement) ;
@@ -554,7 +589,7 @@ fn prepaint_terminal_screen(
554589 . with_timer ( timer)
555590 . reverse_when ( screen. reverse_video ( ) ) ;
556591
557- let ( screen_lines, caret_rect) =
592+ let ( screen_lines, caret_rect, cell_hitboxes ) =
558593 window. with_text_style ( style. text_style ( ) . cloned ( ) , |window| {
559594 let text_style = window. text_style ( ) ;
560595 let line_height = text_style. line_height_in_pixels ( window. rem_size ( ) ) ;
@@ -578,7 +613,7 @@ fn prepaint_terminal_screen(
578613
579614 if * screen_size. read ( cx) != new_screen_size {
580615 let screen_size = screen_size. clone ( ) ;
581- cx. spawn ( async move |cx : & mut AsyncApp | {
616+ cx. spawn ( async move |_ , cx : & mut AsyncApp | {
582617 cx. update_entity ( & screen_size, |screen_size, cx| {
583618 screen_size. columns = new_screen_size. columns . max ( 1 ) ;
584619 screen_size. lines = new_screen_size. lines . max ( 1 ) ;
@@ -590,6 +625,7 @@ fn prepaint_terminal_screen(
590625 }
591626
592627 let mut screen_lines = Vec :: new ( ) ;
628+ let mut cell_hitboxes = Vec :: new ( ) ;
593629 for line in 0 ..screen. size ( ) . 0 {
594630 let mut run_calculator = RunCalculator :: new (
595631 window. text_system ( ) . clone ( ) ,
@@ -600,7 +636,26 @@ fn prepaint_terminal_screen(
600636
601637 for column in 0 ..screen. size ( ) . 1 {
602638 let cell = screen. cell ( line, column) . cloned ( ) ;
603- run_calculator. push_cell ( cell) ;
639+ let cell_bounds = Bounds {
640+ origin : point (
641+ column as f32 * character_size. width ,
642+ line as f32 * character_size. height ,
643+ ) + bounds. origin ,
644+ size : character_size,
645+ } ;
646+
647+ let coordinates = CellCoordinates ( line, column) ;
648+
649+ let hitbox = window. insert_hitbox ( cell_bounds, HitboxBehavior :: Normal ) ;
650+ cell_hitboxes. push ( ( coordinates, hitbox) ) ;
651+ run_calculator. push_cell (
652+ cell,
653+ selected_cell_coordinates
654+ . as_ref ( )
655+ . is_some_and ( |selected_coordinates| {
656+ selected_coordinates. contains ( & coordinates)
657+ } ) ,
658+ ) ;
604659 }
605660
606661 screen_lines. push ( run_calculator. runs ( ) ) ;
@@ -615,7 +670,7 @@ fn prepaint_terminal_screen(
615670 size : size ( px ( 1. ) , character_size. height ) ,
616671 } ;
617672
618- ( screen_lines, caret_rect)
673+ ( screen_lines, caret_rect, cell_hitboxes )
619674 } ) ;
620675
621676 let caret_color = color_scheme. parse_color ( screen. fgcolor ( ) , color_scheme. foreground , true ) ;
@@ -627,6 +682,7 @@ fn prepaint_terminal_screen(
627682 screen,
628683 screen_hitbox,
629684 style,
685+ cell_hitboxes,
630686 screen_lines,
631687 background : color_scheme. background ,
632688 caret_rect,
@@ -638,7 +694,7 @@ fn paint_terminal_screen(
638694 bounds : Bounds < Pixels > ,
639695 prepaint_state : TerminalScreenPrepaint ,
640696 window : & mut Window ,
641- cx : & mut App ,
697+ cx : & mut Context < TerminalScreen > ,
642698) {
643699 window. paint_quad ( quad (
644700 bounds,
@@ -695,6 +751,64 @@ fn paint_terminal_screen(
695751 }
696752 } ) ;
697753
754+ let entity = cx. entity ( ) ;
755+ let cell_hitboxes = prepaint_state. cell_hitboxes . clone ( ) ;
756+ window. on_mouse_event ( move |event : & MouseDownEvent , dispatch_phase, window, cx| {
757+ if dispatch_phase != DispatchPhase :: Bubble {
758+ return ;
759+ }
760+
761+ let cell_hitboxes = cell_hitboxes. clone ( ) ;
762+
763+ entity. update ( cx, move |terminal_screen, cx| {
764+ for ( cell, hitbox) in cell_hitboxes {
765+ if hitbox. is_hovered ( window) {
766+ terminal_screen. clicked_cell_coordinates = Some ( cell) ;
767+ terminal_screen. selected_cell_coordinates = None ;
768+ }
769+ }
770+ cx. notify ( )
771+ } )
772+ } ) ;
773+
774+ let entity_2 = cx. entity ( ) ;
775+ let cell_hitboxes_2 = prepaint_state. cell_hitboxes . clone ( ) ;
776+ window. on_mouse_event ( move |event : & MouseMoveEvent , dispatch_phase, window, cx| {
777+ if dispatch_phase != DispatchPhase :: Bubble {
778+ return ;
779+ }
780+
781+ let cell_hitboxes = cell_hitboxes_2. clone ( ) ;
782+
783+ entity_2. update ( cx, move |terminal_screen, cx| {
784+ if let Some ( clicked_cell_coordinates) = terminal_screen. clicked_cell_coordinates {
785+ for ( cell, hitbox) in cell_hitboxes {
786+ if hitbox. is_hovered ( window) {
787+ if clicked_cell_coordinates < cell {
788+ terminal_screen. selected_cell_coordinates =
789+ Some ( clicked_cell_coordinates..cell) ;
790+ } else {
791+ terminal_screen. selected_cell_coordinates =
792+ Some ( cell..clicked_cell_coordinates) ;
793+ }
794+ }
795+ }
796+ cx. notify ( )
797+ }
798+ } )
799+ } ) ;
800+
801+ let entity_3 = cx. entity ( ) ;
802+ window. on_mouse_event ( move |event : & MouseUpEvent , dispatch_phase, window, cx| {
803+ if dispatch_phase != DispatchPhase :: Bubble {
804+ return ;
805+ }
806+
807+ entity_3. update ( cx, move |terminal_screen, cx| {
808+ terminal_screen. clicked_cell_coordinates = None ;
809+ } )
810+ } ) ;
811+
698812 window. paint_quad ( quad (
699813 prepaint_state. caret_rect ,
700814 Corners :: all ( px ( 0. ) ) ,
0 commit comments