2018-08-07 15:01:15 +03:00
/*
2020-02-04 15:52:12 +02:00
* meli
2018-08-07 15:01:15 +03:00
*
* Copyright 2017 - 2018 Manos Pitsidianakis
*
* This file is part of meli .
*
* meli is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* meli is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with meli . If not , see < http ://www.gnu.org/licenses/>.
* /
2023-07-01 16:20:59 +03:00
//! Define a `(x, y)` point in the terminal display as a holder of a character,
//! foreground/background colors and attributes.
2019-05-13 22:05:00 +03:00
2023-04-30 19:39:41 +03:00
use std ::{
collections ::HashMap ,
convert ::From ,
ops ::{ Deref , DerefMut , Index , IndexMut } ,
} ;
2019-06-18 21:13:58 +03:00
2023-06-16 20:20:12 +03:00
use melib ::{
log ,
text_processing ::{ search ::KMP , wcwidth } ,
} ;
2019-12-01 22:28:50 +02:00
use serde ::{ de , Deserialize , Deserializer , Serialize , Serializer } ;
terminal: add FormatTag, text format tags
FormatTag describes a set of attributes, colors that exist in a
tag_table field of CellBuffer. The field of tag_associations contains
the hash of a tag and the {start,end} index of the cells that have this
attribute. A tag can thus be used many times.
An example of use is
let t = self.pager.insert_tag(FormatTag {
attrs: Attr::ITALICS,
..Default::default()
});
debug!("FormatTag hash = {}", t);
let (width, height) = self.pager.size();
for i in 0..height {
if self.pager.content[(0, i)].ch() == '>' {
self.pager.set_tag(t, (0, i), (width.saturating_sub(1), i));
}
}
This will set reply lines in text as italics.
This feature interface is not used anywhere yet.
2020-06-01 22:55:35 +03:00
use smallvec ::SmallVec ;
2018-07-10 11:45:30 +03:00
2023-10-23 13:56:13 +03:00
use super ::{ position ::* , Area , Color , ScreenGeneration } ;
2023-04-30 19:39:41 +03:00
use crate ::{ state ::Context , ThemeAttribute } ;
/// In a scroll region up and down cursor movements shift the region vertically.
/// The new lines are empty.
2020-02-04 15:52:12 +02:00
///
2023-04-30 19:39:41 +03:00
/// See `CellBuffer::scroll_up` and `CellBuffer::scroll_down` for an explanation
/// of how `xterm` scrolling works.
2019-11-24 20:42:26 +02:00
#[ derive(Debug, Clone, PartialEq, Eq, Default) ]
pub struct ScrollRegion {
pub top : usize ,
pub bottom : usize ,
pub left : usize ,
pub right : usize ,
2018-07-10 11:45:30 +03:00
}
/// An array of `Cell`s that represents a terminal display.
///
2023-04-30 19:39:41 +03:00
/// A `CellBuffer` is a two-dimensional array of `Cell`s, each pair of indices
/// correspond to a single point on the underlying terminal.
2018-07-10 11:45:30 +03:00
///
2023-04-30 19:39:41 +03:00
/// The first index, `Cellbuffer[y]`, corresponds to a row, and thus the y-axis.
/// The second index, `Cellbuffer[y][x]`, corresponds to a column within a row
/// and thus the x-axis.
2018-08-21 19:51:48 +03:00
#[ derive(Clone, PartialEq, Eq) ]
2018-07-10 11:45:30 +03:00
pub struct CellBuffer {
2022-11-24 16:43:53 +02:00
pub cols : usize ,
pub rows : usize ,
pub buf : Vec < Cell > ,
2020-11-27 23:05:35 +02:00
pub default_cell : Cell ,
2020-02-04 15:52:12 +02:00
/// ASCII-only flag.
2019-10-06 11:28:12 +03:00
pub ascii_drawing : bool ,
2023-08-21 12:45:15 +03:00
/// Use color.
pub use_color : bool ,
2020-02-04 15:52:12 +02:00
/// If printing to this buffer and we run out of space, expand it.
2019-11-06 15:09:15 +02:00
growable : bool ,
terminal: add FormatTag, text format tags
FormatTag describes a set of attributes, colors that exist in a
tag_table field of CellBuffer. The field of tag_associations contains
the hash of a tag and the {start,end} index of the cells that have this
attribute. A tag can thus be used many times.
An example of use is
let t = self.pager.insert_tag(FormatTag {
attrs: Attr::ITALICS,
..Default::default()
});
debug!("FormatTag hash = {}", t);
let (width, height) = self.pager.size();
for i in 0..height {
if self.pager.content[(0, i)].ch() == '>' {
self.pager.set_tag(t, (0, i), (width.saturating_sub(1), i));
}
}
This will set reply lines in text as italics.
This feature interface is not used anywhere yet.
2020-06-01 22:55:35 +03:00
tag_table : HashMap < u64 , FormatTag > ,
tag_associations : SmallVec < [ ( u64 , ( usize , usize ) ) ; 128 ] > ,
2023-10-23 13:56:13 +03:00
pub ( super ) area : Area ,
2018-07-10 11:45:30 +03:00
}
2023-08-11 13:16:47 +03:00
impl std ::fmt ::Debug for CellBuffer {
fn fmt ( & self , f : & mut std ::fmt ::Formatter ) -> std ::fmt ::Result {
2023-08-19 09:22:05 +03:00
f . debug_struct ( stringify! ( CellBuffer ) )
2021-01-05 13:50:23 +02:00
. field ( " cols " , & self . cols )
. field ( " rows " , & self . rows )
. field ( " buf cells " , & self . buf . len ( ) )
. field ( " default_cell " , & self . default_cell )
. field ( " ascii_drawing " , & self . ascii_drawing )
2023-08-21 12:45:15 +03:00
. field ( " use_color " , & self . use_color )
2021-01-05 13:50:23 +02:00
. field ( " growable " , & self . growable )
. field ( " tag_table " , & self . tag_table )
. field ( " tag_associations " , & self . tag_associations )
2023-10-23 13:56:13 +03:00
. field ( " area " , & self . area )
. field ( " generation " , & self . area . generation ( ) )
2021-01-05 13:50:23 +02:00
. finish ( )
2018-08-21 19:51:48 +03:00
}
}
2018-07-10 11:45:30 +03:00
impl CellBuffer {
2023-10-23 13:56:13 +03:00
pub const MAX_SIZE : usize = 1_000_000 ;
pub fn nil ( area : Area ) -> Self {
Self {
cols : 0 ,
rows : 0 ,
buf : vec ! [ ] ,
default_cell : Cell ::new_default ( ) ,
growable : false ,
ascii_drawing : false ,
use_color : false ,
tag_table : Default ::default ( ) ,
tag_associations : SmallVec ::new ( ) ,
area ,
}
2018-08-23 14:39:54 +03:00
}
2023-10-23 13:56:13 +03:00
2018-08-21 19:51:48 +03:00
pub fn set_cols ( & mut self , new_cols : usize ) {
self . cols = new_cols ;
}
2023-04-30 19:39:41 +03:00
/// Constructs a new `CellBuffer` with the given number of columns and rows,
/// using the given `cell` as a blank.
2023-10-23 13:56:13 +03:00
pub fn new ( default_cell : Cell , area : Area ) -> Self {
let cols = area . width ( ) ;
let rows = area . height ( ) ;
2023-08-19 09:22:05 +03:00
Self {
2018-08-07 15:01:15 +03:00
cols ,
rows ,
2020-11-23 00:33:20 +02:00
buf : vec ! [ default_cell ; cols * rows ] ,
default_cell ,
2019-11-06 15:09:15 +02:00
growable : false ,
2019-10-06 11:28:12 +03:00
ascii_drawing : false ,
2023-08-21 12:45:15 +03:00
use_color : true ,
terminal: add FormatTag, text format tags
FormatTag describes a set of attributes, colors that exist in a
tag_table field of CellBuffer. The field of tag_associations contains
the hash of a tag and the {start,end} index of the cells that have this
attribute. A tag can thus be used many times.
An example of use is
let t = self.pager.insert_tag(FormatTag {
attrs: Attr::ITALICS,
..Default::default()
});
debug!("FormatTag hash = {}", t);
let (width, height) = self.pager.size();
for i in 0..height {
if self.pager.content[(0, i)].ch() == '>' {
self.pager.set_tag(t, (0, i), (width.saturating_sub(1), i));
}
}
This will set reply lines in text as italics.
This feature interface is not used anywhere yet.
2020-06-01 22:55:35 +03:00
tag_table : Default ::default ( ) ,
tag_associations : SmallVec ::new ( ) ,
2023-10-23 13:56:13 +03:00
area ,
2018-07-10 11:45:30 +03:00
}
}
2023-10-23 13:56:13 +03:00
pub fn new_with_context ( default_cell : Option < Cell > , area : Area , context : & Context ) -> Self {
2020-11-27 23:05:35 +02:00
let default_cell = default_cell . unwrap_or_else ( | | {
let mut ret = Cell ::default ( ) ;
let theme_default = crate ::conf ::value ( context , " theme_default " ) ;
ret . set_fg ( theme_default . fg )
. set_bg ( theme_default . bg )
. set_attrs ( theme_default . attrs ) ;
ret
} ) ;
2023-08-19 09:22:05 +03:00
Self {
2019-10-06 11:28:12 +03:00
ascii_drawing : context . settings . terminal . ascii_drawing ,
2023-08-21 12:45:15 +03:00
use_color : context . settings . terminal . use_color ( ) ,
2023-10-23 13:56:13 +03:00
.. Self ::new ( default_cell , area )
2019-10-06 11:28:12 +03:00
}
}
pub fn set_ascii_drawing ( & mut self , new_val : bool ) {
self . ascii_drawing = new_val ;
}
2023-08-21 12:45:15 +03:00
pub fn set_use_color ( & mut self , new_val : bool ) {
self . use_color = new_val ;
}
2019-11-06 15:09:15 +02:00
pub fn set_growable ( & mut self , new_val : bool ) {
self . growable = new_val ;
}
2023-04-30 19:39:41 +03:00
/// Resizes `CellBuffer` to the given number of rows and columns, using the
/// given `Cell` as a blank.
2020-12-01 01:02:48 +02:00
#[ must_use ]
2023-10-23 13:56:13 +03:00
pub ( super ) fn resize_with_context (
& mut self ,
newcols : usize ,
newrows : usize ,
context : & Context ,
) -> bool {
self . default_cell = {
let mut ret = Cell ::default ( ) ;
let theme_default = crate ::conf ::value ( context , " theme_default " ) ;
ret . set_fg ( theme_default . fg )
. set_bg ( theme_default . bg )
. set_attrs ( theme_default . attrs ) ;
ret
} ;
self . ascii_drawing = context . settings . terminal . ascii_drawing ;
self . use_color = context . settings . terminal . use_color ( ) ;
2018-07-10 11:45:30 +03:00
let newlen = newcols * newrows ;
2020-12-01 01:02:48 +02:00
if ( self . cols , self . rows ) = = ( newcols , newrows ) | | newlen > = Self ::MAX_SIZE {
2022-08-25 15:17:18 +03:00
return newlen < Self ::MAX_SIZE ;
2019-11-22 14:17:09 +02:00
}
2023-10-23 13:56:13 +03:00
self . buf = vec! [ self . default_cell ; newlen ] ;
self . cols = newcols ;
self . rows = newrows ;
true
}
/// Resizes `CellBuffer` to the given number of rows and columns, using the
/// given `Cell` as a blank.
#[ must_use ]
pub ( super ) fn resize ( & mut self , newcols : usize , newrows : usize , blank : Option < Cell > ) -> bool {
let newlen = newcols * newrows ;
if ( self . cols , self . rows ) = = ( newcols , newrows ) | | newlen > = Self ::MAX_SIZE {
return newlen < Self ::MAX_SIZE ;
2018-07-10 11:45:30 +03:00
}
2023-10-23 13:56:13 +03:00
let blank = blank . unwrap_or ( self . default_cell ) ;
self . buf = vec! [ blank ; newlen ] ;
2018-07-10 11:45:30 +03:00
self . cols = newcols ;
self . rows = newrows ;
2020-12-01 01:02:48 +02:00
true
2018-07-10 11:45:30 +03:00
}
2018-08-21 19:51:48 +03:00
2019-02-18 23:14:06 +02:00
pub fn is_empty ( & self ) -> bool {
self . buf . is_empty ( )
}
2019-03-09 10:24:28 +02:00
pub fn empty ( & mut self ) {
self . buf . clear ( ) ;
self . cols = 0 ;
self . rows = 0 ;
}
2018-07-10 11:45:30 +03:00
2019-11-24 20:42:26 +02:00
/// Clears `self`, using the given `Cell` as a blank.
2021-01-05 13:51:31 +02:00
pub fn clear ( & mut self , blank : Option < Cell > ) {
let blank = blank . unwrap_or ( self . default_cell ) ;
2019-11-24 20:42:26 +02:00
for cell in self . cellvec_mut ( ) . iter_mut ( ) {
* cell = blank ;
}
}
pub fn pos_to_index ( & self , x : usize , y : usize ) -> Option < usize > {
let ( cols , rows ) = self . size ( ) ;
if x < cols & & y < rows {
Some ( ( cols * y ) + x )
} else {
None
}
}
2023-04-30 19:39:41 +03:00
/// Returns a reference to the `Cell` at the given coordinates, or `None` if
/// the index is out of bounds.
2019-11-24 20:42:26 +02:00
pub fn get ( & self , x : usize , y : usize ) -> Option < & Cell > {
match self . pos_to_index ( x , y ) {
Some ( i ) = > self . cellvec ( ) . get ( i ) ,
None = > None ,
}
}
2023-04-30 19:39:41 +03:00
/// Returns a mutable reference to the `Cell` at the given coordinates, or
/// `None` if the index is out of bounds.
2019-11-24 20:42:26 +02:00
pub fn get_mut ( & mut self , x : usize , y : usize ) -> Option < & mut Cell > {
match self . pos_to_index ( x , y ) {
Some ( i ) = > self . cellvec_mut ( ) . get_mut ( i ) ,
None = > None ,
}
}
pub fn size ( & self ) -> ( usize , usize ) {
2018-07-10 11:45:30 +03:00
( self . cols , self . rows )
}
2019-11-24 20:42:26 +02:00
pub fn cellvec ( & self ) -> & Vec < Cell > {
2018-07-10 11:45:30 +03:00
& self . buf
}
2019-11-24 20:42:26 +02:00
pub fn cellvec_mut ( & mut self ) -> & mut Vec < Cell > {
2018-07-10 11:45:30 +03:00
& mut self . buf
}
2019-11-24 20:42:26 +02:00
pub fn cols ( & self ) -> usize {
self . size ( ) . 0
}
pub fn rows ( & self ) -> usize {
self . size ( ) . 1
}
#[ inline(always) ]
/// Performs the normal scroll up motion:
///
/// First clear offset number of lines:
///
/// For offset = 1, top = 1:
///
2019-12-01 17:11:13 +02:00
/// ```text
2019-11-24 20:42:26 +02:00
/// | 111111111111 | | |
/// | 222222222222 | | 222222222222 |
/// | 333333333333 | | 333333333333 |
/// | 444444444444 | --> | 444444444444 |
/// | 555555555555 | | 555555555555 |
/// | 666666666666 | | 666666666666 |
2019-12-01 17:11:13 +02:00
/// ```
2019-11-24 20:42:26 +02:00
///
/// In each step, swap the current line with the next by offset:
///
2019-12-01 17:11:13 +02:00
/// ```text
2019-11-24 20:42:26 +02:00
/// | | | 222222222222 |
/// | 222222222222 | | |
/// | 333333333333 | | 333333333333 |
/// | 444444444444 | --> | 444444444444 |
/// | 555555555555 | | 555555555555 |
/// | 666666666666 | | 666666666666 |
2019-12-01 17:11:13 +02:00
/// ```
2019-11-24 20:42:26 +02:00
///
/// Result:
2019-12-01 17:11:13 +02:00
/// ```text
2019-11-24 20:42:26 +02:00
/// Before After
/// | 111111111111 | | 222222222222 |
/// | 222222222222 | | 333333333333 |
/// | 333333333333 | | 444444444444 |
/// | 444444444444 | | 555555555555 |
/// | 555555555555 | | 666666666666 |
/// | 666666666666 | | |
2019-12-01 17:11:13 +02:00
/// ```
2019-11-24 20:42:26 +02:00
pub fn scroll_up ( & mut self , scroll_region : & ScrollRegion , top : usize , offset : usize ) {
let l = scroll_region . left ;
let r = if scroll_region . right = = 0 {
self . size ( ) . 0
} else {
scroll_region . right
} ;
2020-07-05 15:28:55 +03:00
for y in top .. top + offset {
2019-11-24 20:42:26 +02:00
for x in l .. r {
self [ ( x , y ) ] = Cell ::default ( ) ;
}
}
for y in top ..= ( scroll_region . bottom - offset ) {
for x in l .. r {
let temp = self [ ( x , y ) ] ;
self [ ( x , y ) ] = self [ ( x , y + offset ) ] ;
self [ ( x , y + offset ) ] = temp ;
}
}
}
#[ inline(always) ]
/// Performs the normal scroll down motion:
///
/// First clear offset number of lines:
///
/// For offset = 1, top = 1:
///
2019-12-01 17:11:13 +02:00
/// ```text
2019-11-24 20:42:26 +02:00
/// | 111111111111 | | 111111111111 |
/// | 222222222222 | | 222222222222 |
/// | 333333333333 | | 333333333333 |
/// | 444444444444 | --> | 444444444444 |
/// | 555555555555 | | 555555555555 |
/// | 666666666666 | | |
2019-12-01 17:11:13 +02:00
/// ```
2019-11-24 20:42:26 +02:00
///
/// In each step, swap the current line with the prev by offset:
///
2019-12-01 17:11:13 +02:00
/// ```text
2019-11-24 20:42:26 +02:00
/// | 111111111111 | | 111111111111 |
/// | 222222222222 | | 222222222222 |
/// | 333333333333 | | 333333333333 |
/// | 444444444444 | --> | 444444444444 |
/// | 555555555555 | | |
/// | | | 555555555555 |
2019-12-01 17:11:13 +02:00
/// ```
2019-11-24 20:42:26 +02:00
///
/// Result:
2019-12-01 17:11:13 +02:00
/// ```text
2019-11-24 20:42:26 +02:00
/// Before After
/// | 111111111111 | | |
/// | 222222222222 | | 111111111111 |
/// | 333333333333 | | 222222222222 |
/// | 444444444444 | | 333333333333 |
/// | 555555555555 | | 444444444444 |
/// | 666666666666 | | 555555555555 |
2019-12-01 17:11:13 +02:00
/// ```
2019-11-24 20:42:26 +02:00
pub fn scroll_down ( & mut self , scroll_region : & ScrollRegion , top : usize , offset : usize ) {
for y in ( scroll_region . bottom - offset + 1 ) ..= scroll_region . bottom {
for x in 0 .. self . size ( ) . 0 {
self [ ( x , y ) ] = Cell ::default ( ) ;
}
}
for y in ( ( top + offset ) ..= scroll_region . bottom ) . rev ( ) {
for x in 0 .. self . size ( ) . 0 {
let temp = self [ ( x , y ) ] ;
self [ ( x , y ) ] = self [ ( x , y - offset ) ] ;
self [ ( x , y - offset ) ] = temp ;
}
}
}
2019-12-01 17:04:55 +02:00
2019-12-01 17:11:13 +02:00
/// See `BoundsIterator` documentation.
2019-12-01 17:04:55 +02:00
pub fn bounds_iter ( & self , area : Area ) -> BoundsIterator {
2023-10-23 13:56:13 +03:00
debug_assert_eq! ( self . generation ( ) , area . generation ( ) ) ;
2019-12-01 17:04:55 +02:00
BoundsIterator {
2023-10-23 13:56:13 +03:00
width : area . width ( ) ,
height : area . height ( ) ,
rows : std ::cmp ::min ( self . rows . saturating_sub ( 1 ) , get_y ( area . upper_left ( ) ) )
.. ( std ::cmp ::min ( self . rows , get_y ( area . bottom_right ( ) ) + 1 ) ) ,
2019-12-01 17:04:55 +02:00
cols : (
2023-10-23 13:56:13 +03:00
std ::cmp ::min ( self . cols . saturating_sub ( 1 ) , get_x ( area . upper_left ( ) ) ) ,
std ::cmp ::min ( self . cols , get_x ( area . bottom_right ( ) ) + 1 ) ,
2019-12-01 17:04:55 +02:00
) ,
2023-10-23 13:56:13 +03:00
area ,
2019-12-01 17:04:55 +02:00
}
}
2019-12-01 17:11:13 +02:00
/// See `RowIterator` documentation.
2023-10-23 13:56:13 +03:00
pub fn row_iter (
& self ,
area : Area ,
bounds : std ::ops ::Range < usize > ,
relative_row : usize ,
) -> RowIterator {
debug_assert_eq! ( self . generation ( ) , area . generation ( ) ) ;
if self . generation ( ) ! = area . generation ( ) {
return RowIterator ::empty ( self . generation ( ) ) ;
}
let row = area . offset ( ) . 1 + relative_row ;
2019-12-01 17:04:55 +02:00
if row < self . rows {
2023-10-23 13:56:13 +03:00
let col = std ::cmp ::min ( self . cols . saturating_sub ( 1 ) , area . offset ( ) . 0 + bounds . start )
.. ( std ::cmp ::min ( self . cols , area . offset ( ) . 0 + bounds . end ) ) ;
let area = area
. nth_row ( relative_row )
. skip_cols ( bounds . start )
. take_cols ( bounds . len ( ) ) ;
RowIterator { row , col , area }
2019-12-01 17:04:55 +02:00
} else {
2023-10-23 13:56:13 +03:00
RowIterator ::empty ( self . generation ( ) )
2019-12-01 17:04:55 +02:00
}
}
terminal: add FormatTag, text format tags
FormatTag describes a set of attributes, colors that exist in a
tag_table field of CellBuffer. The field of tag_associations contains
the hash of a tag and the {start,end} index of the cells that have this
attribute. A tag can thus be used many times.
An example of use is
let t = self.pager.insert_tag(FormatTag {
attrs: Attr::ITALICS,
..Default::default()
});
debug!("FormatTag hash = {}", t);
let (width, height) = self.pager.size();
for i in 0..height {
if self.pager.content[(0, i)].ch() == '>' {
self.pager.set_tag(t, (0, i), (width.saturating_sub(1), i));
}
}
This will set reply lines in text as italics.
This feature interface is not used anywhere yet.
2020-06-01 22:55:35 +03:00
pub fn tag_associations ( & self ) -> SmallVec < [ ( usize , u64 , bool ) ; 128 ] > {
let mut ret : SmallVec < [ ( usize , u64 , bool ) ; 128 ] > = self . tag_associations . iter ( ) . fold (
SmallVec ::new ( ) ,
| mut acc , ( tag_hash , ( start , end ) ) | {
acc . push ( ( * start , * tag_hash , true ) ) ;
acc . push ( ( * end , * tag_hash , false ) ) ;
acc
} ,
) ;
ret . sort_by_key ( | el | el . 0 ) ;
ret
}
pub fn tag_table ( & self ) -> & HashMap < u64 , FormatTag > {
& self . tag_table
}
pub fn tag_table_mut ( & mut self ) -> & mut HashMap < u64 , FormatTag > {
& mut self . tag_table
}
pub fn insert_tag ( & mut self , tag : FormatTag ) -> u64 {
2023-04-30 19:39:41 +03:00
use std ::{
collections ::hash_map ::DefaultHasher ,
hash ::{ Hash , Hasher } ,
} ;
terminal: add FormatTag, text format tags
FormatTag describes a set of attributes, colors that exist in a
tag_table field of CellBuffer. The field of tag_associations contains
the hash of a tag and the {start,end} index of the cells that have this
attribute. A tag can thus be used many times.
An example of use is
let t = self.pager.insert_tag(FormatTag {
attrs: Attr::ITALICS,
..Default::default()
});
debug!("FormatTag hash = {}", t);
let (width, height) = self.pager.size();
for i in 0..height {
if self.pager.content[(0, i)].ch() == '>' {
self.pager.set_tag(t, (0, i), (width.saturating_sub(1), i));
}
}
This will set reply lines in text as italics.
This feature interface is not used anywhere yet.
2020-06-01 22:55:35 +03:00
let mut hasher = DefaultHasher ::new ( ) ;
tag . hash ( & mut hasher ) ;
let hash = hasher . finish ( ) ;
self . tag_table . insert ( hash , tag ) ;
hash
}
pub fn set_tag ( & mut self , tag : u64 , start : ( usize , usize ) , end : ( usize , usize ) ) {
let start = self
. pos_to_index ( start . 0 , start . 1 )
2022-08-25 15:17:18 +03:00
. unwrap_or_else ( | | self . buf . len ( ) . saturating_sub ( 1 ) ) ;
terminal: add FormatTag, text format tags
FormatTag describes a set of attributes, colors that exist in a
tag_table field of CellBuffer. The field of tag_associations contains
the hash of a tag and the {start,end} index of the cells that have this
attribute. A tag can thus be used many times.
An example of use is
let t = self.pager.insert_tag(FormatTag {
attrs: Attr::ITALICS,
..Default::default()
});
debug!("FormatTag hash = {}", t);
let (width, height) = self.pager.size();
for i in 0..height {
if self.pager.content[(0, i)].ch() == '>' {
self.pager.set_tag(t, (0, i), (width.saturating_sub(1), i));
}
}
This will set reply lines in text as italics.
This feature interface is not used anywhere yet.
2020-06-01 22:55:35 +03:00
let end = self
. pos_to_index ( end . 0 , end . 1 )
2022-08-25 15:17:18 +03:00
. unwrap_or_else ( | | self . buf . len ( ) . saturating_sub ( 1 ) ) ;
terminal: add FormatTag, text format tags
FormatTag describes a set of attributes, colors that exist in a
tag_table field of CellBuffer. The field of tag_associations contains
the hash of a tag and the {start,end} index of the cells that have this
attribute. A tag can thus be used many times.
An example of use is
let t = self.pager.insert_tag(FormatTag {
attrs: Attr::ITALICS,
..Default::default()
});
debug!("FormatTag hash = {}", t);
let (width, height) = self.pager.size();
for i in 0..height {
if self.pager.content[(0, i)].ch() == '>' {
self.pager.set_tag(t, (0, i), (width.saturating_sub(1), i));
}
}
This will set reply lines in text as italics.
This feature interface is not used anywhere yet.
2020-06-01 22:55:35 +03:00
if start ! = end {
self . tag_associations . push ( ( tag , ( start , end ) ) ) ;
}
}
2023-10-27 11:37:19 +03:00
2023-10-23 13:56:13 +03:00
#[ inline(always) ]
pub fn generation ( & self ) -> ScreenGeneration {
self . area . generation ( )
}
2023-10-27 11:37:19 +03:00
2023-10-23 13:56:13 +03:00
/// Completely clear an `Area` with an empty char and the terminal's default
/// colors.
pub fn clear_area ( & mut self , area : Area , attributes : ThemeAttribute ) {
for row in self . bounds_iter ( area ) {
for c in row {
self [ c ] = Cell ::default ( ) ;
self [ c ]
. set_fg ( attributes . fg )
. set_bg ( attributes . bg )
. set_attrs ( attributes . attrs ) ;
2023-10-27 11:37:19 +03:00
}
}
2023-10-23 13:56:13 +03:00
}
2023-10-27 11:37:19 +03:00
2023-10-23 13:56:13 +03:00
/// Change foreground and background colors in an `Area`
pub fn change_colors ( & mut self , area : Area , fg_color : Color , bg_color : Color ) {
if cfg! ( feature = " debug-tracing " ) {
let bounds = self . size ( ) ;
let upper_left = area . upper_left ( ) ;
let bottom_right = area . bottom_right ( ) ;
let ( x , y ) = upper_left ;
if y > ( get_y ( bottom_right ) )
| | x > get_x ( bottom_right )
| | y > = get_y ( bounds )
| | x > = get_x ( bounds )
{
log ::debug! ( " BUG: Invalid area in change_colors: \n area: {:?} " , area ) ;
return ;
2023-10-27 11:37:19 +03:00
}
}
2023-10-23 13:56:13 +03:00
for row in self . bounds_iter ( area ) {
for c in row {
self [ c ] . set_fg ( fg_color ) . set_bg ( bg_color ) ;
2023-10-27 11:37:19 +03:00
}
}
}
2023-10-27 12:05:36 +03:00
2023-10-23 13:56:13 +03:00
/// Change [`ThemeAttribute`] in an `Area`
pub fn change_theme ( & mut self , area : Area , theme : ThemeAttribute ) {
if cfg! ( feature = " debug-tracing " ) {
let bounds = self . size ( ) ;
let upper_left = area . upper_left ( ) ;
let bottom_right = area . bottom_right ( ) ;
let ( x , y ) = upper_left ;
if y > ( get_y ( bottom_right ) )
| | x > get_x ( bottom_right )
| | y > = get_y ( bounds )
| | x > = get_x ( bounds )
{
log ::debug! ( " BUG: Invalid area in change_theme: \n area: {:?} " , area ) ;
return ;
}
2023-10-27 12:05:36 +03:00
}
2023-10-23 13:56:13 +03:00
for row in self . bounds_iter ( area ) {
for c in row {
self [ c ]
. set_fg ( theme . fg )
. set_bg ( theme . bg )
. set_attrs ( theme . attrs ) ;
2023-10-27 12:05:36 +03:00
}
}
}
/// Copy a source `Area` to a destination.
pub fn copy_area ( & mut self , grid_src : & Self , dest : Area , src : Area ) -> Pos {
2023-10-23 13:56:13 +03:00
debug_assert_eq! ( self . generation ( ) , dest . generation ( ) ) ;
debug_assert_eq! ( grid_src . generation ( ) , src . generation ( ) ) ;
if self . generation ( ) ! = dest . generation ( ) | | grid_src . generation ( ) ! = src . generation ( ) {
2023-10-27 12:05:36 +03:00
log ::debug! (
" BUG: Invalid areas in copy_area: \n src: {:?} \n dest: {:?} " ,
src ,
dest
) ;
2023-10-23 13:56:13 +03:00
return dest . upper_left ( ) ;
2023-10-27 12:05:36 +03:00
}
if grid_src . is_empty ( ) | | self . is_empty ( ) {
2023-10-23 13:56:13 +03:00
return dest . upper_left ( ) ;
2023-10-27 12:05:36 +03:00
}
2023-10-23 13:56:13 +03:00
let mut ret = dest . bottom_right ( ) ;
let mut src_x = get_x ( src . upper_left ( ) ) ;
let mut src_y = get_y ( src . upper_left ( ) ) ;
2023-10-27 12:05:36 +03:00
let ( cols , rows ) = grid_src . size ( ) ;
if src_x > = cols | | src_y > = rows {
log ::debug! ( " BUG: src area outside of grid_src in copy_area " , ) ;
2023-10-23 13:56:13 +03:00
return dest . upper_left ( ) ;
2023-10-27 12:05:36 +03:00
}
let tag_associations = grid_src . tag_associations ( ) ;
let start_idx = grid_src . pos_to_index ( src_x , src_y ) . unwrap ( ) ;
let mut tag_offset : usize = tag_associations
. binary_search_by ( | probe | probe . 0. cmp ( & start_idx ) )
. unwrap_or_else ( | i | i ) ;
let mut stack : std ::collections ::BTreeSet < & FormatTag > =
std ::collections ::BTreeSet ::default ( ) ;
2023-10-23 13:56:13 +03:00
for y in get_y ( dest . upper_left ( ) ) ..= get_y ( dest . bottom_right ( ) ) {
' for_x : for x in get_x ( dest . upper_left ( ) ) ..= get_x ( dest . bottom_right ( ) ) {
2023-10-27 12:05:36 +03:00
let idx = grid_src . pos_to_index ( src_x , src_y ) . unwrap ( ) ;
while tag_offset < tag_associations . len ( ) & & tag_associations [ tag_offset ] . 0 < = idx {
if tag_associations [ tag_offset ] . 2 {
stack . insert ( & grid_src . tag_table ( ) [ & tag_associations [ tag_offset ] . 1 ] ) ;
} else {
stack . remove ( & grid_src . tag_table ( ) [ & tag_associations [ tag_offset ] . 1 ] ) ;
}
tag_offset + = 1 ;
}
self [ ( x , y ) ] = grid_src [ ( src_x , src_y ) ] ;
for t in & stack {
if let Some ( fg ) = t . fg {
self [ ( x , y ) ] . set_fg ( fg ) . set_keep_fg ( true ) ;
}
if let Some ( bg ) = t . bg {
self [ ( x , y ) ] . set_bg ( bg ) . set_keep_bg ( true ) ;
}
if let Some ( attrs ) = t . attrs {
self [ ( x , y ) ] . attrs | = attrs ;
self [ ( x , y ) ] . set_keep_attrs ( true ) ;
}
}
2023-10-23 13:56:13 +03:00
if src_x > = get_x ( src . bottom_right ( ) ) {
2023-10-27 12:05:36 +03:00
break 'for_x ;
}
src_x + = 1 ;
}
2023-10-23 13:56:13 +03:00
src_x = get_x ( src . upper_left ( ) ) ;
2023-10-27 12:05:36 +03:00
src_y + = 1 ;
2023-10-23 13:56:13 +03:00
if src_y > get_y ( src . bottom_right ( ) ) {
for row in self . bounds_iter ( dest . skip_rows ( y + 1 - get_y ( dest . upper_left ( ) ) ) ) {
2023-10-27 12:05:36 +03:00
for c in row {
self [ c ] . set_ch ( ' ' ) ;
}
}
ret . 1 = y ;
break ;
}
}
ret
}
2023-10-23 13:56:13 +03:00
/// Write an `&str` to a `CellBuffer` in a specified `Area` with the passed
/// colors.
pub fn write_string (
& mut self ,
s : & str ,
fg_color : Color ,
bg_color : Color ,
attrs : Attr ,
area : Area ,
// The left-most x coordinate.
line_break : Option < usize > ,
) -> Pos {
debug_assert_eq! ( area . generation ( ) , self . generation ( ) ) ;
if area . generation ( ) ! = self . generation ( ) {
// [ref:TODO] log error
return ( 0 , 0 ) ;
2023-10-27 12:05:36 +03:00
}
2023-10-23 13:56:13 +03:00
let mut bounds = self . size ( ) ;
let upper_left = area . upper_left ( ) ;
let bottom_right = area . bottom_right ( ) ;
let ( mut x , mut y ) = upper_left ;
if y = = get_y ( bounds ) | | x = = get_x ( bounds ) {
if self . growable {
if ! self . resize (
std ::cmp ::max ( self . cols , x + 2 ) ,
std ::cmp ::max ( self . rows , y + 2 ) ,
None ,
) {
return ( x - upper_left . 0 , y - upper_left . 1 ) ;
}
bounds = self . size ( ) ;
} else {
return ( x - upper_left . 0 , y - upper_left . 1 ) ;
2023-10-27 12:05:36 +03:00
}
}
2023-10-23 13:56:13 +03:00
if y > ( get_y ( bottom_right ) )
| | x > get_x ( bottom_right )
| | y > get_y ( bounds )
| | x > get_x ( bounds )
{
if self . growable {
if ! self . resize (
std ::cmp ::max ( self . cols , x + 2 ) ,
std ::cmp ::max ( self . rows , y + 2 ) ,
None ,
) {
return ( x - upper_left . 0 , y - upper_left . 1 ) ;
}
} else {
log ::debug! ( " Invalid area with string {} and area {:?} " , s , area ) ;
return ( x - upper_left . 0 , y - upper_left . 1 ) ;
}
}
for c in s . chars ( ) {
if c = = '\r' {
continue ;
}
if c = = '\n' {
y + = 1 ;
if let Some ( _x ) = line_break {
x = _x + get_x ( upper_left ) ;
continue ;
} else {
break ;
}
}
if y > get_y ( bottom_right )
2023-10-27 12:05:36 +03:00
| | x > get_x ( bottom_right )
2023-10-23 13:56:13 +03:00
| | y > get_y ( bounds )
| | x > get_x ( bounds )
2023-10-27 12:05:36 +03:00
{
2023-10-23 13:56:13 +03:00
if let Some ( _x ) = line_break {
if ! ( y > get_y ( bottom_right ) | | y > get_y ( bounds ) ) {
x = _x + get_x ( upper_left ) ;
y + = 1 ;
continue ;
}
}
break ;
2023-10-27 12:05:36 +03:00
}
2023-10-23 13:56:13 +03:00
if c = = '\t' {
self [ ( x , y ) ] . set_ch ( ' ' ) ;
x + = 1 ;
if let Some ( c ) = self . get_mut ( x , y ) {
c . set_ch ( ' ' ) ;
} else if let Some ( _x ) = line_break {
if ! ( y > get_y ( bottom_right ) | | y > get_y ( bounds ) ) {
x = _x + get_x ( upper_left ) ;
y + = 1 ;
continue ;
} else {
break ;
}
}
} else {
self [ ( x , y ) ] . set_ch ( c ) ;
2023-10-27 12:05:36 +03:00
}
2023-10-23 13:56:13 +03:00
self [ ( x , y ) ]
. set_fg ( fg_color )
. set_bg ( bg_color )
. set_attrs ( attrs ) ;
match wcwidth ( u32 ::from ( c ) ) {
Some ( 0 ) | None = > {
/* Skip drawing zero width characters */
self [ ( x , y ) ] . empty = true ;
}
Some ( 2 ) = > {
/* Grapheme takes more than one column, so the next cell will be
* drawn over . Set it as empty to skip drawing it . * /
x + = 1 ;
self [ ( x , y ) ] = Cell ::default ( ) ;
self [ ( x , y ) ]
. set_fg ( fg_color )
. set_bg ( bg_color )
. set_attrs ( attrs )
. set_empty ( true ) ;
}
_ = > { }
2023-10-27 12:05:36 +03:00
}
2023-10-23 13:56:13 +03:00
x + = 1 ;
2023-10-27 12:05:36 +03:00
}
2023-10-23 13:56:13 +03:00
( x - upper_left . 0 , y - upper_left . 1 )
2023-10-27 12:05:36 +03:00
}
2023-10-23 13:56:13 +03:00
#[ inline ]
pub const fn area ( & self ) -> Area {
self . area
2023-10-27 12:05:36 +03:00
}
2018-07-10 11:45:30 +03:00
}
impl Deref for CellBuffer {
type Target = [ Cell ] ;
2023-05-16 13:17:13 +03:00
fn deref ( & self ) -> & Self ::Target {
2018-07-10 11:45:30 +03:00
& self . buf
}
}
impl DerefMut for CellBuffer {
2023-05-16 13:17:13 +03:00
fn deref_mut ( & mut self ) -> & mut Self ::Target {
2018-07-10 11:45:30 +03:00
& mut self . buf
}
}
impl Index < Pos > for CellBuffer {
type Output = Cell ;
2023-08-19 09:22:05 +03:00
fn index ( & self , index : Pos ) -> & Self ::Output {
2018-07-10 11:45:30 +03:00
let ( x , y ) = index ;
self . get ( x , y ) . expect ( " index out of bounds " )
}
}
impl IndexMut < Pos > for CellBuffer {
2018-08-07 15:01:15 +03:00
fn index_mut ( & mut self , index : Pos ) -> & mut Cell {
2018-07-10 11:45:30 +03:00
let ( x , y ) = index ;
self . get_mut ( x , y ) . expect ( " index out of bounds " )
}
}
2023-08-11 13:16:47 +03:00
impl std ::fmt ::Display for CellBuffer {
fn fmt ( & self , f : & mut std ::fmt ::Formatter ) -> std ::fmt ::Result {
2018-07-24 11:34:44 +03:00
'_ y : for y in 0 .. self . rows {
2018-08-07 15:01:15 +03:00
for x in 0 .. self . cols {
2018-07-27 21:37:56 +03:00
let c : & char = & self [ ( x , y ) ] . ch ( ) ;
2018-07-24 20:20:32 +03:00
write! ( f , " {} " , * c ) . unwrap ( ) ;
2018-07-24 11:34:44 +03:00
if * c = = '\n' {
continue '_y ;
}
}
}
Ok ( ( ) )
}
}
2018-07-10 11:45:30 +03:00
/// A single point on a terminal display.
///
/// A `Cell` contains a character and style.
#[ derive(Debug, Copy, Clone, PartialEq, Eq) ]
pub struct Cell {
ch : char ,
2018-07-24 11:34:44 +03:00
2023-04-30 19:39:41 +03:00
/// Set a `Cell` as empty when a previous cell spans multiple columns and it
/// would "overflow" to this cell.
2019-08-17 12:22:54 +03:00
empty : bool ,
2018-07-10 11:45:30 +03:00
fg : Color ,
bg : Color ,
attrs : Attr ,
2019-11-30 17:43:10 +02:00
keep_fg : bool ,
keep_bg : bool ,
2020-06-07 18:30:30 +03:00
keep_attrs : bool ,
2018-07-10 11:45:30 +03:00
}
impl Cell {
/// Creates a new `Cell` with the given `char`, `Color`s and `Attr`.
///
/// # Examples
///
2020-02-04 15:52:12 +02:00
/// ```no_run
2023-04-30 19:39:41 +03:00
/// use meli::{Attr, Cell, Color};
2022-09-01 21:14:17 +03:00
///
2020-04-05 12:04:25 +03:00
/// let cell = Cell::new('x', Color::Default, Color::Green, Attr::DEFAULT);
2018-07-10 11:45:30 +03:00
/// assert_eq!(cell.ch(), 'x');
/// assert_eq!(cell.fg(), Color::Default);
/// assert_eq!(cell.bg(), Color::Green);
2020-04-05 12:04:25 +03:00
/// assert_eq!(cell.attrs(), Attr::DEFAULT);
2018-07-10 11:45:30 +03:00
/// ```
2023-10-23 13:56:13 +03:00
pub const fn new ( ch : char , fg : Color , bg : Color , attrs : Attr ) -> Self {
2023-08-19 09:22:05 +03:00
Self {
2019-08-17 12:22:54 +03:00
ch ,
fg ,
bg ,
attrs ,
empty : false ,
2019-11-30 17:43:10 +02:00
keep_fg : false ,
keep_bg : false ,
2020-06-07 18:30:30 +03:00
keep_attrs : false ,
2019-08-17 12:22:54 +03:00
}
2018-07-10 11:45:30 +03:00
}
2023-10-23 13:56:13 +03:00
pub const fn new_default ( ) -> Self {
Self ::new ( ' ' , Color ::Default , Color ::Default , Attr ::DEFAULT )
}
2018-07-10 11:45:30 +03:00
/// Creates a new `Cell` with the given `char` and default style.
///
/// # Examples
///
2020-02-04 15:52:12 +02:00
/// ```no_run
2023-04-30 19:39:41 +03:00
/// use meli::{Attr, Cell, Color};
2022-09-01 21:14:17 +03:00
///
2018-07-10 11:45:30 +03:00
/// let mut cell = Cell::with_char('x');
/// assert_eq!(cell.ch(), 'x');
/// assert_eq!(cell.fg(), Color::Default);
/// assert_eq!(cell.bg(), Color::Default);
2020-04-05 12:04:25 +03:00
/// assert_eq!(cell.attrs(), Attr::DEFAULT);
2018-07-10 11:45:30 +03:00
/// ```
2023-08-19 09:22:05 +03:00
pub fn with_char ( ch : char ) -> Self {
Self ::new ( ch , Color ::Default , Color ::Default , Attr ::DEFAULT )
2018-07-10 11:45:30 +03:00
}
/// Creates a new `Cell` with the given style and a blank `char`.
///
/// # Examples
///
2020-02-04 15:52:12 +02:00
/// ```no_run
2023-04-30 19:39:41 +03:00
/// use meli::{Attr, Cell, Color};
2022-09-01 21:14:17 +03:00
///
2020-04-05 12:04:25 +03:00
/// let mut cell = Cell::with_style(Color::Default, Color::Red, Attr::BOLD);
2018-07-10 11:45:30 +03:00
/// assert_eq!(cell.fg(), Color::Default);
/// assert_eq!(cell.bg(), Color::Red);
2020-04-05 12:04:25 +03:00
/// assert_eq!(cell.attrs(), Attr::BOLD);
2018-07-10 11:45:30 +03:00
/// assert_eq!(cell.ch(), ' ');
/// ```
2023-08-19 09:22:05 +03:00
pub fn with_style ( fg : Color , bg : Color , attr : Attr ) -> Self {
Self ::new ( ' ' , fg , bg , attr )
2018-07-10 11:45:30 +03:00
}
/// Returns the `Cell`'s character.
///
/// # Examples
///
2020-02-04 15:52:12 +02:00
/// ```no_run
2022-09-01 21:14:17 +03:00
/// use meli::Cell;
///
2018-07-10 11:45:30 +03:00
/// let mut cell = Cell::with_char('x');
/// assert_eq!(cell.ch(), 'x');
/// ```
pub fn ch ( & self ) -> char {
self . ch
}
/// Sets the `Cell`'s character to the given `char`
///
/// # Examples
///
2020-02-04 15:52:12 +02:00
/// ```no_run
2022-09-01 21:14:17 +03:00
/// use meli::Cell;
///
2018-07-10 11:45:30 +03:00
/// let mut cell = Cell::with_char('x');
/// assert_eq!(cell.ch(), 'x');
///
/// cell.set_ch('y');
/// assert_eq!(cell.ch(), 'y');
/// ```
2023-08-19 09:22:05 +03:00
pub fn set_ch ( & mut self , newch : char ) -> & mut Self {
2018-07-10 11:45:30 +03:00
self . ch = newch ;
2019-11-30 17:43:10 +02:00
self . keep_fg = false ;
self . keep_bg = false ;
2020-06-07 18:30:30 +03:00
self . keep_attrs = false ;
2018-07-10 11:45:30 +03:00
self
}
/// Returns the `Cell`'s foreground `Color`.
///
/// # Examples
///
2020-02-04 15:52:12 +02:00
/// ```no_run
2023-04-30 19:39:41 +03:00
/// use meli::{Attr, Cell, Color};
2022-09-01 21:14:17 +03:00
///
2020-04-05 12:04:25 +03:00
/// let mut cell = Cell::with_style(Color::Blue, Color::Default, Attr::DEFAULT);
2018-07-10 11:45:30 +03:00
/// assert_eq!(cell.fg(), Color::Blue);
/// ```
2018-07-20 12:44:04 +03:00
pub fn fg ( & self ) -> Color {
2018-07-10 11:45:30 +03:00
self . fg
}
/// Sets the `Cell`'s foreground `Color` to the given `Color`.
///
/// # Examples
///
2020-02-04 15:52:12 +02:00
/// ```no_run
2023-04-30 19:39:41 +03:00
/// use meli::{Cell, Color};
2022-09-01 21:14:17 +03:00
///
2018-07-10 11:45:30 +03:00
/// let mut cell = Cell::default();
/// assert_eq!(cell.fg(), Color::Default);
///
/// cell.set_fg(Color::White);
/// assert_eq!(cell.fg(), Color::White);
/// ```
2023-08-19 09:22:05 +03:00
pub fn set_fg ( & mut self , newfg : Color ) -> & mut Self {
2019-11-30 17:43:10 +02:00
if ! self . keep_fg {
self . fg = newfg ;
}
2018-07-10 11:45:30 +03:00
self
}
/// Returns the `Cell`'s background `Color`.
///
/// # Examples
///
2020-02-04 15:52:12 +02:00
/// ```no_run
2023-04-30 19:39:41 +03:00
/// use meli::{Attr, Cell, Color};
2022-09-01 21:14:17 +03:00
///
2020-04-05 12:04:25 +03:00
/// let mut cell = Cell::with_style(Color::Default, Color::Green, Attr::DEFAULT);
2018-07-10 11:45:30 +03:00
/// assert_eq!(cell.bg(), Color::Green);
/// ```
2018-07-20 12:44:04 +03:00
pub fn bg ( & self ) -> Color {
2018-07-10 11:45:30 +03:00
self . bg
}
/// Sets the `Cell`'s background `Color` to the given `Color`.
///
/// # Examples
///
2020-02-04 15:52:12 +02:00
/// ```no_run
2022-09-01 21:14:17 +03:00
/// use meli::{Cell, Color};
///
2018-07-10 11:45:30 +03:00
/// let mut cell = Cell::default();
/// assert_eq!(cell.bg(), Color::Default);
///
/// cell.set_bg(Color::Black);
/// assert_eq!(cell.bg(), Color::Black);
/// ```
2023-08-19 09:22:05 +03:00
pub fn set_bg ( & mut self , newbg : Color ) -> & mut Self {
2019-11-30 17:43:10 +02:00
if ! self . keep_bg {
self . bg = newbg ;
}
2018-07-10 11:45:30 +03:00
self
}
pub fn attrs ( & self ) -> Attr {
self . attrs
}
2023-08-19 09:22:05 +03:00
pub fn set_attrs ( & mut self , newattrs : Attr ) -> & mut Self {
2020-06-07 18:30:30 +03:00
if ! self . keep_attrs {
self . attrs = newattrs ;
}
2018-07-10 11:45:30 +03:00
self
}
2019-08-17 12:22:54 +03:00
2023-04-30 19:39:41 +03:00
/// Set a `Cell` as empty when a previous cell spans multiple columns and it
/// would "overflow" to this cell.
2019-08-17 12:22:54 +03:00
pub fn empty ( & self ) -> bool {
self . empty
}
2019-11-19 20:39:43 +02:00
2023-08-19 09:22:05 +03:00
pub fn set_empty ( & mut self , new_val : bool ) -> & mut Self {
2019-11-19 20:39:43 +02:00
self . empty = new_val ;
2020-01-27 17:07:29 +02:00
self
2019-11-19 20:39:43 +02:00
}
2019-11-30 17:43:10 +02:00
2023-04-30 19:39:41 +03:00
/// Sets `keep_fg` field. If true, the foreground color will not be altered
/// if attempted so until the character content of the cell is changed.
2023-08-19 09:22:05 +03:00
pub fn set_keep_fg ( & mut self , new_val : bool ) -> & mut Self {
2019-11-30 17:43:10 +02:00
self . keep_fg = new_val ;
2020-01-27 17:07:29 +02:00
self
2019-11-30 17:43:10 +02:00
}
2023-04-30 19:39:41 +03:00
/// Sets `keep_bg` field. If true, the background color will not be altered
/// if attempted so until the character content of the cell is changed.
2023-08-19 09:22:05 +03:00
pub fn set_keep_bg ( & mut self , new_val : bool ) -> & mut Self {
2019-11-30 17:43:10 +02:00
self . keep_bg = new_val ;
2020-01-27 17:07:29 +02:00
self
2019-11-30 17:43:10 +02:00
}
2020-06-07 18:30:30 +03:00
2023-04-30 19:39:41 +03:00
/// Sets `keep_attrs` field. If true, the text attributes will not be
/// altered if attempted so until the character content of the cell is
/// changed.
2023-08-19 09:22:05 +03:00
pub fn set_keep_attrs ( & mut self , new_val : bool ) -> & mut Self {
2020-06-07 18:30:30 +03:00
self . keep_attrs = new_val ;
self
}
2018-07-10 11:45:30 +03:00
}
impl Default for Cell {
/// Constructs a new `Cell` with a blank `char` and default `Color`s.
///
/// # Examples
///
2020-02-04 15:52:12 +02:00
/// ```no_run
2023-04-30 19:39:41 +03:00
/// use meli::{Cell, Color};
2022-09-01 21:14:17 +03:00
///
2018-07-10 11:45:30 +03:00
/// let mut cell = Cell::default();
/// assert_eq!(cell.ch(), ' ');
/// assert_eq!(cell.fg(), Color::Default);
/// assert_eq!(cell.bg(), Color::Default);
/// ```
2023-08-19 09:22:05 +03:00
fn default ( ) -> Self {
Self ::new ( ' ' , Color ::Default , Color ::Default , Attr ::DEFAULT )
2018-07-10 11:45:30 +03:00
}
}
2020-04-05 12:04:25 +03:00
bitflags ::bitflags! {
/// The attributes of a `Cell`.
///
/// `Attr` enumerates all combinations of attributes a given style may have.
///
/// `Attr::DEFAULT` represents no attribute.
///
/// # Examples
///
/// ```no_run
2022-09-01 21:14:17 +03:00
/// use meli::Attr;
///
2020-04-05 12:04:25 +03:00
/// // Default attribute.
/// let def = Attr::DEFAULT;
///
/// // Base attribute.
/// let base = Attr::BOLD;
///
/// // Combination.
/// let comb = Attr::UNDERLINE | Attr::REVERSE;
/// ```
2023-08-25 08:17:05 +03:00
#[ derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash) ]
2020-04-05 12:04:25 +03:00
pub struct Attr : u8 {
/// Terminal default.
const DEFAULT = 0b000_0000 ;
const BOLD = 0b000_0001 ;
const DIM = 0b000_0010 ;
const ITALICS = 0b000_0100 ;
const UNDERLINE = 0b000_1000 ;
const BLINK = 0b001_0000 ;
const REVERSE = 0b010_0000 ;
const HIDDEN = 0b100_0000 ;
2020-01-27 17:07:29 +02:00
}
}
2020-04-05 12:04:25 +03:00
impl Default for Attr {
fn default ( ) -> Self {
2023-08-19 09:22:05 +03:00
Self ::DEFAULT
2020-01-27 20:17:46 +02:00
}
}
2023-08-11 13:16:47 +03:00
impl std ::fmt ::Display for Attr {
fn fmt ( & self , f : & mut std ::fmt ::Formatter ) -> std ::fmt ::Result {
2020-04-05 12:04:25 +03:00
match * self {
2023-08-19 09:22:05 +03:00
Self ::DEFAULT = > write! ( f , " Default " ) ,
Self ::BOLD = > write! ( f , " Bold " ) ,
Self ::DIM = > write! ( f , " Dim " ) ,
Self ::ITALICS = > write! ( f , " Italics " ) ,
Self ::UNDERLINE = > write! ( f , " Underline " ) ,
Self ::BLINK = > write! ( f , " Blink " ) ,
Self ::REVERSE = > write! ( f , " Reverse " ) ,
Self ::HIDDEN = > write! ( f , " Hidden " ) ,
2020-04-05 12:04:25 +03:00
combination = > {
let mut ctr = 0 ;
2023-08-19 09:22:05 +03:00
if combination . intersects ( Self ::BOLD ) {
2020-04-05 12:04:25 +03:00
ctr + = 1 ;
2023-08-19 09:22:05 +03:00
Self ::BOLD . fmt ( f ) ? ;
2020-04-05 12:04:25 +03:00
}
2023-08-19 09:22:05 +03:00
if combination . intersects ( Self ::DIM ) {
2020-04-05 12:04:25 +03:00
if ctr > 0 {
write! ( f , " | " ) ? ;
}
ctr + = 1 ;
2023-08-19 09:22:05 +03:00
Self ::DIM . fmt ( f ) ? ;
2020-04-05 12:04:25 +03:00
}
2023-08-19 09:22:05 +03:00
if combination . intersects ( Self ::ITALICS ) {
2020-04-05 12:04:25 +03:00
if ctr > 0 {
write! ( f , " | " ) ? ;
}
ctr + = 1 ;
2023-08-19 09:22:05 +03:00
Self ::ITALICS . fmt ( f ) ? ;
2020-04-05 12:04:25 +03:00
}
2023-08-19 09:22:05 +03:00
if combination . intersects ( Self ::UNDERLINE ) {
2020-04-05 12:04:25 +03:00
if ctr > 0 {
write! ( f , " | " ) ? ;
}
ctr + = 1 ;
2023-08-19 09:22:05 +03:00
Self ::UNDERLINE . fmt ( f ) ? ;
2020-04-05 12:04:25 +03:00
}
2023-08-19 09:22:05 +03:00
if combination . intersects ( Self ::BLINK ) {
2020-04-05 12:04:25 +03:00
if ctr > 0 {
write! ( f , " | " ) ? ;
}
ctr + = 1 ;
2023-08-19 09:22:05 +03:00
Self ::BLINK . fmt ( f ) ? ;
2020-04-05 12:04:25 +03:00
}
2023-08-19 09:22:05 +03:00
if combination . intersects ( Self ::REVERSE ) {
2020-04-05 12:04:25 +03:00
if ctr > 0 {
write! ( f , " | " ) ? ;
}
ctr + = 1 ;
2023-08-19 09:22:05 +03:00
Self ::REVERSE . fmt ( f ) ? ;
2020-04-05 12:04:25 +03:00
}
2023-08-19 09:22:05 +03:00
if combination . intersects ( Self ::HIDDEN ) {
2020-04-05 12:04:25 +03:00
if ctr > 0 {
write! ( f , " | " ) ? ;
}
2023-08-19 09:22:05 +03:00
Self ::HIDDEN . fmt ( f ) ? ;
2020-04-05 12:04:25 +03:00
}
write! ( f , " " )
}
}
2020-01-23 19:52:54 +02:00
}
}
impl < ' de > Deserialize < ' de > for Attr {
fn deserialize < D > ( deserializer : D ) -> std ::result ::Result < Self , D ::Error >
where
D : Deserializer < ' de > ,
{
if let Ok ( s ) = < String > ::deserialize ( deserializer ) {
2023-08-19 09:22:05 +03:00
Self ::from_string_de ::< ' de , D , String > ( s )
2020-01-23 19:52:54 +02:00
} else {
2020-04-05 12:04:25 +03:00
Err ( de ::Error ::custom ( " Attributes value must be a string. " ) )
2020-01-23 19:52:54 +02:00
}
}
}
impl Serialize for Attr {
fn serialize < S > ( & self , serializer : S ) -> std ::result ::Result < S ::Ok , S ::Error >
where
S : Serializer ,
{
2020-04-05 12:04:25 +03:00
serializer . serialize_str ( & self . to_string ( ) )
2020-01-23 19:52:54 +02:00
}
}
2020-01-24 09:19:57 +02:00
impl Attr {
2020-04-05 12:04:25 +03:00
pub fn from_string_de < ' de , D , T : AsRef < str > > ( s : T ) -> std ::result ::Result < Self , D ::Error >
2020-01-24 09:19:57 +02:00
where
D : Deserializer < ' de > ,
{
2020-04-05 12:04:25 +03:00
match s . as_ref ( ) . trim ( ) {
2023-08-19 09:22:05 +03:00
" Default " = > Ok ( Self ::DEFAULT ) ,
" Dim " = > Ok ( Self ::DIM ) ,
" Bold " = > Ok ( Self ::BOLD ) ,
" Italics " = > Ok ( Self ::ITALICS ) ,
" Underline " = > Ok ( Self ::UNDERLINE ) ,
" Blink " = > Ok ( Self ::BLINK ) ,
" Reverse " = > Ok ( Self ::REVERSE ) ,
" Hidden " = > Ok ( Self ::HIDDEN ) ,
2022-08-25 15:17:18 +03:00
combination if combination . contains ( '|' ) = > {
2023-08-19 09:22:05 +03:00
let mut ret = Self ::DEFAULT ;
2022-08-25 15:17:18 +03:00
for c in combination . trim ( ) . split ( '|' ) {
2020-04-05 12:04:25 +03:00
ret | = Self ::from_string_de ::< ' de , D , & str > ( c ) ? ;
}
Ok ( ret )
}
_ = > Err ( de ::Error ::custom (
r # "Text attribute value must either be a single attribute (eg "Bold") or a combination of attributes separated by "|" (eg "Bold|Underline"). Valid attributes are "Default", "Bold", "Italics", "Underline", "Blink", "Reverse" and "Hidden"."# ,
) ) ,
2020-01-24 09:19:57 +02:00
}
}
2023-08-19 09:22:05 +03:00
pub fn write ( self , prev : Self , stdout : & mut crate ::StateStdout ) -> std ::io ::Result < ( ) > {
2020-01-27 17:07:29 +02:00
use std ::io ::Write ;
2023-08-19 09:22:05 +03:00
match ( self . intersects ( Self ::BOLD ) , prev . intersects ( Self ::BOLD ) ) {
2020-01-27 17:07:29 +02:00
( true , true ) | ( false , false ) = > Ok ( ( ) ) ,
( false , true ) = > write! ( stdout , " \x1B [22m " ) ,
( true , false ) = > write! ( stdout , " \x1B [1m " ) ,
}
2020-04-05 12:04:25 +03:00
. and_then (
2023-08-19 09:22:05 +03:00
| _ | match ( self . intersects ( Self ::DIM ) , prev . intersects ( Self ::DIM ) ) {
2020-04-05 12:04:25 +03:00
( true , true ) | ( false , false ) = > Ok ( ( ) ) ,
( false , true ) = > write! ( stdout , " \x1B [22m " ) ,
( true , false ) = > write! ( stdout , " \x1B [2m " ) ,
} ,
)
. and_then ( | _ | {
match (
2023-08-19 09:22:05 +03:00
self . intersects ( Self ::ITALICS ) ,
prev . intersects ( Self ::ITALICS ) ,
2020-04-05 12:04:25 +03:00
) {
( true , true ) | ( false , false ) = > Ok ( ( ) ) ,
( false , true ) = > write! ( stdout , " \x1B [23m " ) ,
( true , false ) = > write! ( stdout , " \x1B [3m " ) ,
}
2020-01-27 17:07:29 +02:00
} )
2020-04-05 12:04:25 +03:00
. and_then ( | _ | {
match (
2023-08-19 09:22:05 +03:00
self . intersects ( Self ::UNDERLINE ) ,
prev . intersects ( Self ::UNDERLINE ) ,
2020-04-05 12:04:25 +03:00
) {
( true , true ) | ( false , false ) = > Ok ( ( ) ) ,
( false , true ) = > write! ( stdout , " \x1B [24m " ) ,
( true , false ) = > write! ( stdout , " \x1B [4m " ) ,
}
} )
. and_then (
2023-08-19 09:22:05 +03:00
| _ | match ( self . intersects ( Self ::BLINK ) , prev . intersects ( Self ::BLINK ) ) {
2020-04-05 12:04:25 +03:00
( true , true ) | ( false , false ) = > Ok ( ( ) ) ,
( false , true ) = > write! ( stdout , " \x1B [25m " ) ,
( true , false ) = > write! ( stdout , " \x1B [5m " ) ,
} ,
)
. and_then ( | _ | {
match (
2023-08-19 09:22:05 +03:00
self . intersects ( Self ::REVERSE ) ,
prev . intersects ( Self ::REVERSE ) ,
2020-04-05 12:04:25 +03:00
) {
( true , true ) | ( false , false ) = > Ok ( ( ) ) ,
( false , true ) = > write! ( stdout , " \x1B [27m " ) ,
( true , false ) = > write! ( stdout , " \x1B [7m " ) ,
}
} )
. and_then ( | _ | {
2023-08-19 09:22:05 +03:00
match ( self . intersects ( Self ::HIDDEN ) , prev . intersects ( Self ::HIDDEN ) ) {
2020-04-05 12:04:25 +03:00
( true , true ) | ( false , false ) = > Ok ( ( ) ) ,
( false , true ) = > write! ( stdout , " \x1B [28m " ) ,
( true , false ) = > write! ( stdout , " \x1B [8m " ) ,
}
2020-01-27 17:07:29 +02:00
} )
}
2018-08-25 02:23:40 +03:00
}
2019-10-03 01:03:20 +03:00
2023-10-23 13:56:13 +03:00
/// Use [`RowIterator`] to iterate the cells of a row without the need to do any
2023-04-30 19:39:41 +03:00
/// bounds checking; the iterator will simply return `None` when it reaches the
2023-10-23 13:56:13 +03:00
/// end of the row. [`RowIterator`] can be created via the
/// [`CellBuffer::row_iter`] method and can be returned by [`BoundsIterator`]
/// which iterates each row.
///
/// ```rust,no_run
/// # use meli::terminal::{Screen, Virtual, Area};
/// # let mut screen = Screen::<Virtual>::new();
/// # assert!(screen.resize(120, 20));
/// # let area = screen.area();
/// for c in screen.grid().row_iter(area, 0..area.width(), 2) {
/// screen.grid_mut()[c].set_ch('g');
2019-12-01 17:04:55 +02:00
/// }
/// ```
2022-11-24 16:43:53 +02:00
#[ derive(Debug) ]
2019-12-01 17:04:55 +02:00
pub struct RowIterator {
row : usize ,
col : std ::ops ::Range < usize > ,
2023-10-23 13:56:13 +03:00
area : Area ,
2019-12-01 17:04:55 +02:00
}
2023-10-23 13:56:13 +03:00
/// [`BoundsIterator`] iterates each row returning a [`RowIterator`].
///
/// ```rust,no_run
/// # use meli::terminal::{Screen, Virtual, Area};
/// # let mut screen = Screen::<Virtual>::new();
/// # assert!(screen.resize(120, 20));
/// # let area = screen.area();
/// for row in screen.grid().bounds_iter(area) {
2023-04-30 19:39:41 +03:00
/// for c in row {
2023-10-23 13:56:13 +03:00
/// screen.grid_mut()[c].set_ch('g');
2022-09-01 21:14:17 +03:00
/// }
2019-12-01 17:11:13 +02:00
/// }
/// ```
2023-10-23 13:56:13 +03:00
#[ derive(Clone) ]
2019-12-01 17:04:55 +02:00
pub struct BoundsIterator {
rows : std ::ops ::Range < usize > ,
cols : ( usize , usize ) ,
2023-10-23 13:56:13 +03:00
width : usize ,
height : usize ,
area : Area ,
2019-12-01 17:04:55 +02:00
}
2023-10-23 13:56:13 +03:00
impl std ::fmt ::Debug for BoundsIterator {
fn fmt ( & self , f : & mut std ::fmt ::Formatter ) -> std ::fmt ::Result {
f . debug_struct ( stringify! ( BoundsIterator ) )
. field ( " rows " , & self . rows )
. field ( " cols " , & self . cols )
. field ( " width " , & self . width )
. field ( " height " , & self . height )
. field ( " is_empty " , & self . is_empty ( ) )
. field ( " bounds_area " , & self . area )
. finish ( )
}
}
2022-11-24 16:43:53 +02:00
2023-10-23 13:56:13 +03:00
impl BoundsIterator {
#[ inline ]
2022-11-24 16:43:53 +02:00
pub fn area ( & self ) -> Area {
2023-10-23 13:56:13 +03:00
self . area
2022-11-24 16:43:53 +02:00
}
2023-10-23 13:56:13 +03:00
#[ inline ]
pub fn width ( & self ) -> usize {
self . width
}
#[ inline ]
pub fn height ( & self ) -> usize {
self . height
}
#[ inline ]
2022-11-24 16:43:53 +02:00
pub fn is_empty ( & self ) -> bool {
2023-10-23 13:56:13 +03:00
self . area . is_empty ( ) | | self . width = = 0 | | self . height = = 0 | | self . rows . len ( ) = = 0
2022-11-24 16:43:53 +02:00
}
2023-10-23 13:56:13 +03:00
pub fn add_x ( & mut self , x : usize ) {
2022-11-24 16:43:53 +02:00
if x = = 0 {
2023-10-23 13:56:13 +03:00
return ;
2022-11-24 16:43:53 +02:00
}
2023-10-23 13:56:13 +03:00
self . width = self . width . saturating_sub ( x ) ;
self . cols . 0 + = x ;
self . cols . 0 = self . cols . 0. min ( self . cols . 1 ) ;
self . area = self . area . skip_cols ( x ) ;
2022-11-24 16:43:53 +02:00
}
}
2019-12-01 17:04:55 +02:00
impl Iterator for BoundsIterator {
type Item = RowIterator ;
fn next ( & mut self ) -> Option < Self ::Item > {
2023-10-23 13:56:13 +03:00
let row = self . rows . next ( ) ? ;
let area = self . area . nth_row ( 0 ) ;
self . area = self . area . skip_rows ( 1 ) ;
self . height = self . area . height ( ) ;
Some ( RowIterator {
row ,
col : self . cols . 0 .. self . cols . 1 ,
area ,
} )
2019-12-01 17:04:55 +02:00
}
}
impl Iterator for RowIterator {
type Item = ( usize , usize ) ;
fn next ( & mut self ) -> Option < Self ::Item > {
2023-10-23 13:56:13 +03:00
let x = self . col . next ( ) ? ;
self . area = self . area . skip_cols ( 1 ) ;
Some ( ( x , self . row ) )
2019-12-01 17:04:55 +02:00
}
}
impl RowIterator {
2023-10-23 13:56:13 +03:00
#[ inline ]
pub const fn area ( & self ) -> Area {
self . area
2019-12-01 17:04:55 +02:00
}
2022-11-24 16:43:53 +02:00
2023-10-23 13:56:13 +03:00
#[ inline ]
pub const fn row_index ( & self ) -> usize {
self . row
}
pub const fn empty ( generation : ScreenGeneration ) -> Self {
Self {
row : 0 ,
col : 0 .. 0 ,
area : Area ::new_empty ( generation ) ,
}
2022-11-24 16:43:53 +02:00
}
2019-12-01 17:04:55 +02:00
}
2020-02-06 21:51:13 +02:00
pub use boundaries ::create_box ;
pub mod boundaries {
use super ::* ;
2023-08-11 12:46:16 +03:00
pub const HORZ_BOUNDARY : char = '─' ;
pub const VERT_BOUNDARY : char = '│' ;
pub const _TOP_LEFT_CORNER : char = '┌' ;
pub const _TOP_RIGHT_CORNER : char = '┐' ;
pub const _BOTTOM_LEFT_CORNER : char = '└' ;
pub const _BOTTOM_RIGHT_CORNER : char = '┘' ;
pub const LIGHT_VERTICAL_AND_RIGHT : char = '├' ;
pub const _LIGHT_VERTICAL_AND_LEFT : char = '┤' ;
pub const _LIGHT_DOWN_AND_HORIZONTAL : char = '┬' ;
pub const _LIGHT_UP_AND_HORIZONTAL : char = '┴' ;
pub const _DOUBLE_DOWN_AND_RIGHT : char = '╔' ;
pub const _DOUBLE_DOWN_AND_LEFT : char = '╗' ;
pub const _DOUBLE_UP_AND_LEFT : char = '╝' ;
pub const _DOUBLE_UP_AND_RIGHT : char = '╚' ;
2020-02-06 21:51:13 +02:00
fn bin_to_ch ( b : u32 ) -> char {
match b {
0b0001 = > '╶' ,
0b0010 = > '╵' ,
0b0011 = > '└' ,
0b0100 = > '╴' ,
0b0101 = > '─' ,
0b0110 = > '┘' ,
0b0111 = > '┴' ,
0b1000 = > '╷' ,
0b1001 = > '┌' ,
0b1010 = > '│' ,
0b1011 = > '├' ,
0b1100 = > '┐' ,
0b1101 = > '┬' ,
0b1110 = > '┤' ,
0b1111 = > '┼' ,
_ = > unsafe { std ::hint ::unreachable_unchecked ( ) } ,
}
}
fn ch_to_bin ( ch : char ) -> Option < u32 > {
match ch {
'└' = > Some ( 0b0011 ) ,
'─' = > Some ( 0b0101 ) ,
'┘' = > Some ( 0b0110 ) ,
'┴' = > Some ( 0b0111 ) ,
'┌' = > Some ( 0b1001 ) ,
'│' = > Some ( 0b1010 ) ,
'├' = > Some ( 0b1011 ) ,
'┐' = > Some ( 0b1100 ) ,
'┬' = > Some ( 0b1101 ) ,
'┤' = > Some ( 0b1110 ) ,
'┼' = > Some ( 0b1111 ) ,
'╷' = > Some ( 0b1000 ) ,
'╵' = > Some ( 0b0010 ) ,
'╴' = > Some ( 0b0100 ) ,
'╶' = > Some ( 0b0001 ) ,
_ = > None ,
}
}
#[ allow(clippy::never_loop) ]
fn set_and_join_vert ( grid : & mut CellBuffer , idx : Pos ) -> u32 {
let ( x , y ) = idx ;
let mut bin_set = 0b1010 ;
/* Check left side
*
* 1
* -> 2 │ 0
* 3
* /
loop {
if x > 0 {
if let Some ( cell ) = grid . get_mut ( x - 1 , y ) {
if let Some ( adj ) = ch_to_bin ( cell . ch ( ) ) {
if ( adj & 0b0001 ) > 0 {
bin_set | = 0b0100 ;
break ;
} else if adj = = 0b0100 {
cell . set_ch ( bin_to_ch ( 0b0101 ) ) ;
bin_set | = 0b0100 ;
break ;
}
}
}
}
bin_set & = 0b1011 ;
break ;
}
/* Check right side
*
* 1
* 2 │ 0 < -
* 3
* /
loop {
if let Some ( cell ) = grid . get_mut ( x + 1 , y ) {
if let Some ( adj ) = ch_to_bin ( cell . ch ( ) ) {
if ( adj & 0b0100 ) > 0 {
bin_set | = 0b0001 ;
break ;
}
}
}
bin_set & = 0b1110 ;
break ;
}
/* Set upper side
*
* 1 < -
* 2 │ 0
* 3
* /
loop {
if y > 0 {
if let Some ( cell ) = grid . get_mut ( x , y - 1 ) {
if let Some ( adj ) = ch_to_bin ( cell . ch ( ) ) {
cell . set_ch ( bin_to_ch ( adj | 0b1000 ) ) ;
} else {
bin_set & = 0b1101 ;
}
}
}
break ;
}
/* Set bottom side
*
* 1
* 2 │ 0
* 3 < -
* /
loop {
if let Some ( cell ) = grid . get_mut ( x , y + 1 ) {
if let Some ( adj ) = ch_to_bin ( cell . ch ( ) ) {
cell . set_ch ( bin_to_ch ( adj | 0b0010 ) ) ;
} else {
bin_set & = 0b0111 ;
}
}
break ;
}
if bin_set = = 0 {
bin_set = 0b1010 ;
}
bin_set
}
#[ allow(clippy::never_loop) ]
fn set_and_join_horz ( grid : & mut CellBuffer , idx : Pos ) -> u32 {
let ( x , y ) = idx ;
let mut bin_set = 0b0101 ;
/* Check upper side
*
* 1 < -
* 2 ─ 0
* 3
* /
loop {
if y > 0 {
if let Some ( cell ) = grid . get_mut ( x , y - 1 ) {
if let Some ( adj ) = ch_to_bin ( cell . ch ( ) ) {
if ( adj & 0b1000 ) > 0 {
bin_set | = 0b0010 ;
break ;
} else if adj = = 0b0010 {
bin_set | = 0b0010 ;
cell . set_ch ( bin_to_ch ( 0b1010 ) ) ;
break ;
}
}
}
}
bin_set & = 0b1101 ;
break ;
}
/* Check bottom side
*
* 1
* 2 ─ 0
* 3 < -
* /
loop {
if let Some ( cell ) = grid . get_mut ( x , y + 1 ) {
if let Some ( adj ) = ch_to_bin ( cell . ch ( ) ) {
if ( adj & 0b0010 ) > 0 {
bin_set | = 0b1000 ;
break ;
} else if adj = = 0b1000 {
bin_set | = 0b1000 ;
cell . set_ch ( bin_to_ch ( 0b1010 ) ) ;
break ;
}
}
}
bin_set & = 0b0111 ;
break ;
}
/* Set left side
*
* 1
* -> 2 ─ 0
* 3
* /
loop {
if x > 0 {
if let Some ( cell ) = grid . get_mut ( x - 1 , y ) {
if let Some ( adj ) = ch_to_bin ( cell . ch ( ) ) {
cell . set_ch ( bin_to_ch ( adj | 0b0001 ) ) ;
} else {
bin_set & = 0b1011 ;
}
}
}
break ;
}
/* Set right side
*
* 1
* 2 ─ 0 < -
* 3
* /
loop {
if let Some ( cell ) = grid . get_mut ( x + 1 , y ) {
if let Some ( adj ) = ch_to_bin ( cell . ch ( ) ) {
cell . set_ch ( bin_to_ch ( adj | 0b0100 ) ) ;
} else {
bin_set & = 0b1110 ;
}
}
break ;
}
if bin_set = = 0 {
bin_set = 0b0101 ;
}
bin_set
}
2023-08-11 12:46:16 +03:00
pub enum BoxBoundary {
2020-02-06 21:51:13 +02:00
Horizontal ,
Vertical ,
}
2023-08-11 12:46:16 +03:00
pub fn set_and_join_box ( grid : & mut CellBuffer , idx : Pos , ch : BoxBoundary ) {
2020-02-06 21:51:13 +02:00
/* Connected sides:
*
* 1
* 2 c 0
* 3
*
* #3210
* 0b____
* /
if grid . ascii_drawing {
grid [ idx ] . set_ch ( match ch {
BoxBoundary ::Vertical = > '|' ,
BoxBoundary ::Horizontal = > '-' ,
} ) ;
return ;
}
let bin_set = match ch {
BoxBoundary ::Vertical = > set_and_join_vert ( grid , idx ) ,
BoxBoundary ::Horizontal = > set_and_join_horz ( grid , idx ) ,
} ;
grid [ idx ] . set_ch ( bin_to_ch ( bin_set ) ) ;
}
/// Puts boundaries in `area`.
/// Returns the inner area of the created box.
pub fn create_box ( grid : & mut CellBuffer , area : Area ) -> Area {
2023-10-23 13:56:13 +03:00
debug_assert_eq! ( grid . generation ( ) , area . generation ( ) ) ;
let upper_left = area . upper_left ( ) ;
let bottom_right = area . bottom_right ( ) ;
2020-02-06 21:51:13 +02:00
if ! grid . ascii_drawing {
for x in get_x ( upper_left ) .. get_x ( bottom_right ) {
2022-03-22 21:00:21 +02:00
grid [ ( x , get_y ( upper_left ) ) ] . set_ch ( HORZ_BOUNDARY ) ;
grid [ ( x , get_y ( bottom_right ) ) ] . set_ch ( HORZ_BOUNDARY ) ;
2020-02-06 21:51:13 +02:00
}
for y in get_y ( upper_left ) .. get_y ( bottom_right ) {
2022-03-22 21:00:21 +02:00
grid [ ( get_x ( upper_left ) , y ) ] . set_ch ( VERT_BOUNDARY ) ;
grid [ ( get_x ( bottom_right ) , y ) ] . set_ch ( VERT_BOUNDARY ) ;
2020-02-06 21:51:13 +02:00
}
set_and_join_box ( grid , upper_left , BoxBoundary ::Horizontal ) ;
set_and_join_box (
grid ,
set_x ( upper_left , get_x ( bottom_right ) ) ,
BoxBoundary ::Horizontal ,
) ;
set_and_join_box (
grid ,
set_y ( upper_left , get_y ( bottom_right ) ) ,
BoxBoundary ::Vertical ,
) ;
set_and_join_box ( grid , bottom_right , BoxBoundary ::Vertical ) ;
}
2023-10-23 13:56:13 +03:00
area . skip ( 1 , 1 ) . skip_rows_from_end ( 1 ) . skip_cols_from_end ( 1 )
2020-02-06 21:51:13 +02:00
}
}
2020-02-26 12:25:57 +02:00
impl KMP for CellBuffer {
fn kmp_search ( & self , pattern : & str ) -> smallvec ::SmallVec < [ usize ; 256 ] > {
let ( mut w , prev_ind ) =
pattern
. char_indices ( )
. skip ( 1 )
. fold ( ( vec! [ ] , 0 ) , | ( mut acc , prev_ind ) , ( i , _ ) | {
acc . push ( & pattern [ prev_ind .. i ] ) ;
( acc , i )
} ) ;
w . push ( & pattern [ prev_ind .. ] ) ;
let t = Self ::kmp_table ( & w ) ;
let mut j = 0 ; // (the position of the current character in text)
let mut k = 0 ; // (the position of the current character in pattern)
let mut ret = smallvec ::SmallVec ::new ( ) ;
while j < self . buf . len ( ) & & k < w . len ( ) as i32 {
if self . buf [ j ] . ch ( ) = = '\n' {
j + = 1 ;
continue ;
}
if w [ k as usize ] = = self . buf [ j ] . ch ( ) . encode_utf8 ( & mut [ 0 ; 4 ] ) {
j + = 1 ;
k + = 1 ;
if k as usize = = w . len ( ) {
ret . push ( j - ( k as usize ) ) ;
k = t [ k as usize ] ;
}
} else {
k = t [ k as usize ] ;
if k < 0 {
j + = 1 ;
k + = 1 ;
}
}
}
ret
}
}
terminal: add FormatTag, text format tags
FormatTag describes a set of attributes, colors that exist in a
tag_table field of CellBuffer. The field of tag_associations contains
the hash of a tag and the {start,end} index of the cells that have this
attribute. A tag can thus be used many times.
An example of use is
let t = self.pager.insert_tag(FormatTag {
attrs: Attr::ITALICS,
..Default::default()
});
debug!("FormatTag hash = {}", t);
let (width, height) = self.pager.size();
for i in 0..height {
if self.pager.content[(0, i)].ch() == '>' {
self.pager.set_tag(t, (0, i), (width.saturating_sub(1), i));
}
}
This will set reply lines in text as italics.
This feature interface is not used anywhere yet.
2020-06-01 22:55:35 +03:00
#[ derive(Debug, Default, Copy, Hash, Clone, PartialEq, Eq) ]
pub struct FormatTag {
2020-06-04 21:33:27 +03:00
pub fg : Option < Color > ,
pub bg : Option < Color > ,
pub attrs : Option < Attr > ,
terminal: add FormatTag, text format tags
FormatTag describes a set of attributes, colors that exist in a
tag_table field of CellBuffer. The field of tag_associations contains
the hash of a tag and the {start,end} index of the cells that have this
attribute. A tag can thus be used many times.
An example of use is
let t = self.pager.insert_tag(FormatTag {
attrs: Attr::ITALICS,
..Default::default()
});
debug!("FormatTag hash = {}", t);
let (width, height) = self.pager.size();
for i in 0..height {
if self.pager.content[(0, i)].ch() == '>' {
self.pager.set_tag(t, (0, i), (width.saturating_sub(1), i));
}
}
This will set reply lines in text as italics.
This feature interface is not used anywhere yet.
2020-06-01 22:55:35 +03:00
pub priority : u8 ,
}
2020-06-10 19:02:54 +03:00
2023-08-11 13:16:47 +03:00
impl std ::cmp ::Ord for FormatTag {
fn cmp ( & self , other : & Self ) -> std ::cmp ::Ordering {
2020-06-10 19:02:54 +03:00
self . priority . cmp ( & other . priority )
}
}
2023-08-11 13:16:47 +03:00
impl std ::cmp ::PartialOrd for FormatTag {
fn partial_cmp ( & self , other : & Self ) -> Option < std ::cmp ::Ordering > {
2022-08-25 15:17:18 +03:00
Some ( self . cmp ( other ) )
2020-06-10 19:02:54 +03:00
}
}
2020-10-14 12:50:38 +03:00
2022-11-24 16:43:53 +02:00
impl From < ThemeAttribute > for FormatTag {
fn from ( val : ThemeAttribute ) -> Self {
let ThemeAttribute { fg , bg , attrs , .. } = val ;
Self {
fg : Some ( fg ) ,
bg : Some ( bg ) ,
attrs : Some ( attrs ) ,
priority : 0 ,
}
}
}
impl FormatTag {
#[ inline(always) ]
pub fn set_priority ( mut self , new_val : u8 ) -> Self {
self . priority = new_val ;
self
}
}
2020-10-14 12:50:38 +03:00
#[ derive(Debug, Copy, Hash, Clone, PartialEq, Eq) ]
pub enum WidgetWidth {
Unset ,
Hold ( usize ) ,
Set ( usize ) ,
}
2023-06-16 20:20:12 +03:00
#[ cfg(test) ]
mod tests {
2023-10-23 13:56:13 +03:00
use crate ::terminal ::{ Screen , Virtual } ;
2023-06-16 20:20:12 +03:00
2023-10-23 13:56:13 +03:00
//use melib::text_processing::{Reflow, TextProcessing, _ALICE_CHAPTER_1};
2023-06-16 20:20:12 +03:00
#[ test ]
fn test_cellbuffer_search ( ) {
2023-10-23 13:56:13 +03:00
//let lines: Vec<String> =
// _ALICE_CHAPTER_1.split_lines_reflow(Reflow::All, Some(78));
// let mut buf = CellBuffer::new(
// lines.iter().map(String::len).max().unwrap(),
// lines.len(),
// Cell::with_char(' '),
//);
//let width = buf.size().0;
//for (i, l) in lines.iter().enumerate() {
// buf.write_string(
// l,
// Color::Default,
// Color::Default,
// Attr::DEFAULT,
// ((0, i), (width.saturating_sub(1), i)),
// None,
// );
//}
//for ind in buf.kmp_search("Alice") {
// for c in &buf.cellvec()[ind..std::cmp::min(buf.cellvec().len(),
// ind + 25)] { print!("{}", c.ch());
// }
// println!();
//}
}
#[ test ]
fn test_bounds_iter ( ) {
let mut screen = Screen ::< Virtual > ::new ( ) ;
assert! ( screen . resize ( 120 , 20 ) ) ;
let area = screen . area ( ) ;
assert_eq! ( area . width ( ) , 120 ) ;
assert_eq! ( area . height ( ) , 20 ) ;
let mut full_bounds = screen . grid ( ) . bounds_iter ( area ) ;
assert_eq! ( full_bounds . area ( ) , area ) ;
assert_eq! ( full_bounds . width ( ) , area . width ( ) ) ;
assert_eq! ( full_bounds . height ( ) , area . height ( ) ) ;
assert! ( ! full_bounds . is_empty ( ) ) ;
full_bounds . add_x ( 0 ) ;
assert_eq! ( full_bounds . area ( ) , area ) ;
full_bounds . add_x ( 1 ) ;
assert_eq! ( full_bounds . area ( ) . width ( ) , area . width ( ) - 1 ) ;
full_bounds . add_x ( area . width ( ) ) ;
assert_eq! ( full_bounds . width ( ) , 0 ) ;
assert_eq! ( full_bounds . area ( ) . width ( ) , 0 ) ;
let full_bounds = screen . grid ( ) . bounds_iter ( area ) ;
let row_iters = full_bounds . into_iter ( ) . collect ::< Vec < _ > > ( ) ;
assert_eq! ( row_iters . len ( ) , area . height ( ) ) ;
for mid in 0 .. row_iters . len ( ) {
assert_eq! ( mid , row_iters [ mid ] . row_index ( ) ) ;
let ( left , right ) = row_iters . as_slice ( ) . split_at ( mid ) ;
let mid = & right [ 0 ] ;
assert! ( area . contains ( mid . area ( ) ) ) ;
for l in left {
assert! ( area . contains ( l . area ( ) ) ) ;
assert! ( ! mid . area ( ) . contains ( l . area ( ) ) ) ;
}
for r in & right [ 1 .. ] {
assert! ( area . contains ( r . area ( ) ) ) ;
assert! ( ! mid . area ( ) . contains ( r . area ( ) ) ) ;
2023-06-16 20:20:12 +03:00
}
2023-10-23 13:56:13 +03:00
}
let inner_area = area . place_inside ( ( 60 , 10 ) , true , true ) ;
let bounds = screen . grid ( ) . bounds_iter ( inner_area ) ;
let row_iters = bounds . into_iter ( ) . collect ::< Vec < _ > > ( ) ;
assert_eq! ( row_iters . len ( ) , inner_area . height ( ) ) ;
for mut row in row_iters {
let row_index = row . row_index ( ) ;
assert_eq! ( row . area ( ) . width ( ) , 61 ) ;
assert_eq! ( row . next ( ) , Some ( ( 2 , row_index ) ) ) ;
assert_eq! ( row . area ( ) . width ( ) , 60 ) ;
assert_eq! (
& row . collect ::< Vec < ( usize , usize ) > > ( ) ,
& ( 3 .. 63 )
. zip ( std ::iter ::repeat ( row_index ) )
. collect ::< Vec < ( usize , usize ) > > ( )
) ;
2023-06-16 20:20:12 +03:00
}
}
}