@@ -12,18 +12,19 @@ use rat_cursor::HasScreenCursor;
1212use rat_widget:: {
1313 event:: { HandleEvent , Outcome , TextOutcome , ct_event} ,
1414 focus:: { FocusBuilder , FocusFlag , HasFocus , Navigation } ,
15+ line_number:: { LineNumberState , LineNumbers } ,
1516 list:: { ListState , selection:: RowSelection } ,
1617 paragraph:: { Paragraph , ParagraphState } ,
1718 textarea:: { TextArea , TextAreaState , TextWrap } ,
1819} ;
1920use ratatui:: {
2021 buffer:: Buffer ,
21- layout:: Rect ,
22- style:: { Color , Modifier , Style , Stylize } ,
22+ layout:: { Rect , Spacing } ,
23+ style:: { Color , Modifier , Style } ,
2324 text:: { Line , Span , Text } ,
24- widgets:: { self , Block , ListItem , StatefulWidget , Widget } ,
25+ widgets:: { self , Block , Borders , ListItem , Padding , StatefulWidget , Widget } ,
2526} ;
26- use ratatui_macros:: { horizontal, line, span , vertical} ;
27+ use ratatui_macros:: { horizontal, line, vertical} ;
2728use std:: {
2829 collections:: { HashMap , HashSet } ,
2930 sync:: { Arc , OnceLock , RwLock } ,
@@ -170,7 +171,7 @@ pub struct TimelineEventView {
170171}
171172
172173impl TimelineEventView {
173- fn from_api ( event : TimelineEvent , fallback_id : u64 ) -> Option < Self > {
174+ pub ( crate ) fn from_api ( event : TimelineEvent , fallback_id : u64 ) -> Option < Self > {
174175 if matches ! (
175176 event. event,
176177 IssueEvent :: Commented | IssueEvent :: LineCommented | IssueEvent :: CommentDeleted
@@ -210,6 +211,7 @@ impl TimelineEventView {
210211
211212pub struct IssueConversation {
212213 title : Option < Arc < str > > ,
214+ ln_state : LineNumberState ,
213215 action_tx : Option < tokio:: sync:: mpsc:: Sender < Action > > ,
214216 current : Option < IssueConversationSeed > ,
215217 cache_number : Option < u64 > ,
@@ -264,13 +266,13 @@ enum MessageKey {
264266}
265267
266268#[ derive( Debug , Clone , Default ) ]
267- struct MarkdownRender {
268- lines : Vec < Line < ' static > > ,
269- links : Vec < RenderedLink > ,
269+ pub ( crate ) struct MarkdownRender {
270+ pub ( crate ) lines : Vec < Line < ' static > > ,
271+ pub ( crate ) links : Vec < RenderedLink > ,
270272}
271273
272274#[ derive( Debug , Clone ) ]
273- struct RenderedLink {
275+ pub ( crate ) struct RenderedLink {
274276 line : usize ,
275277 col : usize ,
276278 label : String ,
@@ -314,6 +316,7 @@ impl IssueConversation {
314316 action_tx : None ,
315317 current : None ,
316318 cache_number : None ,
319+ ln_state : LineNumberState :: default ( ) ,
317320 cache_comments : Vec :: new ( ) ,
318321 timeline_cache_number : None ,
319322 cache_timeline : Vec :: new ( ) ,
@@ -358,48 +361,40 @@ impl IssueConversation {
358361 return ;
359362 }
360363 self . area = area. main_content ;
361- let title = self . title . clone ( ) . unwrap_or_default ( ) ;
362- let wrapped_title = wrap ( & title, area. main_content . width . saturating_sub ( 2 ) as usize ) ;
363- let title_para_height = wrapped_title. len ( ) as u16 + 2 ;
364- let last_item = wrapped_title. last ( ) ;
365- let last_line = last_item
366- . as_ref ( )
367- . map ( |l| {
368- line ! [
369- l. to_string( ) ,
370- span!(
371- " #{}" ,
372- self . current. as_ref( ) . map( |s| s. number) . unwrap_or_default( )
373- )
374- . dim( )
375- ]
376- } )
377- . unwrap_or_else ( || Line :: from ( "" ) ) ;
378- let wrapped_title_len = wrapped_title. len ( ) as u16 ;
379- let title_para = Text :: from_iter (
380- wrapped_title
381- . into_iter ( )
382- . take ( wrapped_title_len as usize - 1 )
383- . map ( Line :: from)
384- . chain ( std:: iter:: once ( last_line) ) ,
385- ) ;
364+ let mut title = self . title . clone ( ) . unwrap_or_default ( ) . to_string ( ) ;
365+ title. push_str ( & format ! (
366+ " #{}" ,
367+ self . current. as_ref( ) . map( |s| s. number) . unwrap_or_default( )
368+ ) ) ;
369+ let title = title. trim ( ) ;
370+ let wrapped_title = wrap ( title, area. main_content . width . saturating_sub ( 2 ) as usize ) ;
371+ let title_para_height = wrapped_title. len ( ) as u16 + 1 ;
372+ let title_para = Text :: from_iter ( wrapped_title) ;
386373
387374 let areas = vertical ! [ ==title_para_height, *=1 , ==5 ] . split ( area. main_content ) ;
388375 let title_area = areas[ 0 ] ;
389376 let content_area = areas[ 1 ] ;
390377 let input_area = areas[ 2 ] ;
391- let content_split = horizontal ! [ *=1 , *=1 ] . split ( content_area) ;
378+ let content_split = horizontal ! [ *=1 , *=1 ]
379+ . spacing ( Spacing :: Overlap ( 1 ) )
380+ . split ( content_area) ;
392381 let list_area = content_split[ 0 ] ;
393382 let body_area = content_split[ 1 ] ;
394383 let items = self . build_items ( list_area, body_area) ;
395384
396385 let title_widget = widgets:: Paragraph :: new ( title_para)
397- . block ( Block :: bordered ( ) . border_type ( ratatui:: widgets:: BorderType :: Rounded ) )
386+ . block (
387+ Block :: default ( )
388+ . padding ( Padding :: horizontal ( 1 ) )
389+ . borders ( Borders :: BOTTOM )
390+ . merge_borders ( ratatui:: symbols:: merge:: MergeStrategy :: Exact ) ,
391+ )
398392 . style ( Style :: default ( ) . add_modifier ( Modifier :: BOLD ) ) ;
399393 title_widget. render ( title_area, buf) ;
400394
401- let mut list_block = Block :: bordered ( )
402- . border_type ( ratatui:: widgets:: BorderType :: Rounded )
395+ let mut list_block = Block :: default ( )
396+ . borders ( Borders :: RIGHT )
397+ . merge_borders ( ratatui:: symbols:: merge:: MergeStrategy :: Exact )
403398 . border_style ( get_border_style ( & self . list_state ) ) ;
404399
405400 if !self . is_loading_current ( ) {
@@ -449,13 +444,26 @@ impl IssueConversation {
449444
450445 match self . textbox_state {
451446 InputState :: Input => {
447+ let [ line_numbers, input_area] = horizontal ! [ ==self . input_state. len_lines( ) . checked_ilog10( ) . unwrap_or( 0 ) as u16 + 2 , *=1 ] . areas ( input_area) ;
448+ let ln_block = Block :: default ( )
449+ . borders ( Borders :: TOP )
450+ . merge_borders ( ratatui:: symbols:: merge:: MergeStrategy :: Exact )
451+ . border_style ( get_border_style ( & self . input_state ) ) ;
452+ let ln = LineNumbers :: new ( )
453+ . with_textarea ( & self . input_state )
454+ . block ( ln_block)
455+ . style ( Style :: default ( ) . dim ( ) ) ;
456+ ln. render ( line_numbers, buf, & mut self . ln_state ) ;
457+
452458 let input_title = if let Some ( err) = & self . post_error {
453459 format ! ( "Comment (Ctrl+Enter to send) | {err}" )
454460 } else {
455461 "Comment (Ctrl+Enter to send)" . to_string ( )
456462 } ;
457- let mut input_block = Block :: bordered ( )
458- . border_type ( ratatui:: widgets:: BorderType :: Rounded )
463+ let mut input_block = Block :: default ( )
464+ . borders ( Borders :: TOP )
465+ . merge_borders ( ratatui:: symbols:: merge:: MergeStrategy :: Exact )
466+ . padding ( Padding :: horizontal ( 1 ) )
459467 . border_style ( get_border_style ( & self . input_state ) ) ;
460468 if !self . posting {
461469 input_block = input_block. title ( input_title) ;
@@ -470,8 +478,10 @@ impl IssueConversation {
470478 render_markdown_lines ( & self . input_state . text ( ) , self . markdown_width , 2 ) ;
471479 let para = Paragraph :: new ( rendered)
472480 . block (
473- Block :: bordered ( )
474- . border_type ( ratatui:: widgets:: BorderType :: Rounded )
481+ Block :: default ( )
482+ . borders ( Borders :: TOP )
483+ . merge_borders ( ratatui:: symbols:: merge:: MergeStrategy :: Exact )
484+ . padding ( Padding :: horizontal ( 1 ) )
475485 . border_style ( get_border_style ( & self . paragraph_state ) )
476486 . title ( "Preview" ) ,
477487 )
@@ -636,8 +646,9 @@ impl IssueConversation {
636646
637647 let body = Paragraph :: new ( body_lines)
638648 . block (
639- Block :: bordered ( )
640- . border_type ( ratatui:: widgets:: BorderType :: Rounded )
649+ Block :: default ( )
650+ . borders ( Borders :: LEFT )
651+ . merge_borders ( ratatui:: symbols:: merge:: MergeStrategy :: Exact )
641652 . border_style ( get_border_style ( & self . body_paragraph_state ) )
642653 . title ( if self . screen == MainScreen :: DetailsFullscreen {
643654 "Message Body (PageUp/PageDown/Home/End | f/Esc: exit fullscreen)"
@@ -2441,7 +2452,7 @@ pub(crate) fn render_markdown_lines(text: &str, width: usize, indent: usize) ->
24412452 render_markdown ( text, width, indent) . lines
24422453}
24432454
2444- fn render_markdown ( text : & str , width : usize , indent : usize ) -> MarkdownRender {
2455+ pub ( crate ) fn render_markdown ( text : & str , width : usize , indent : usize ) -> MarkdownRender {
24452456 let mut renderer = MarkdownRenderer :: new ( width, indent) ;
24462457 let options = Options :: ENABLE_GFM
24472458 | Options :: ENABLE_STRIKETHROUGH
0 commit comments