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/>.
* /
2018-07-18 10:42:52 +03:00
/*!
2019-03-14 12:19:25 +02: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
2020-11-23 00:35:27 +02:00
use super ::{ position ::* , Color } ;
2019-10-06 11:28:12 +03:00
use crate ::state ::Context ;
2020-02-04 17:26:25 +02:00
use melib ::text_processing ::wcwidth ;
2019-06-18 21:13:58 +03:00
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 ;
2020-06-10 19:02:54 +03:00
use std ::collections ::HashMap ;
2018-07-23 22:21:41 +03:00
use std ::convert ::From ;
2018-07-24 11:34:44 +03:00
use std ::fmt ;
2018-07-27 21:37:56 +03:00
use std ::ops ::{ Deref , DerefMut , Index , IndexMut } ;
2018-07-10 11:45:30 +03:00
2019-11-24 20:42:26 +02:00
/// 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
///
/// 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.
///
/// A `CellBuffer` is a two-dimensional array of `Cell`s, each pair of indices correspond to a
/// single point on the underlying terminal.
///
/// 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 {
cols : usize ,
rows : usize ,
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 ,
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 ] > ,
2018-07-10 11:45:30 +03:00
}
2018-08-21 19:51:48 +03:00
impl fmt ::Debug for CellBuffer {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
2021-01-05 13:50:23 +02:00
f . debug_struct ( " CellBuffer " )
. 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 )
. field ( " growable " , & self . growable )
. field ( " tag_table " , & self . tag_table )
. field ( " tag_associations " , & self . tag_associations )
. finish ( )
2018-08-21 19:51:48 +03:00
}
}
2018-07-10 11:45:30 +03:00
impl CellBuffer {
2020-12-01 01:02:48 +02:00
pub const MAX_SIZE : usize = 300_000 ;
2018-08-23 14:39:54 +03:00
pub fn area ( & self ) -> Area {
2018-08-23 15:36:52 +03:00
(
( 0 , 0 ) ,
( self . cols . saturating_sub ( 1 ) , self . rows . saturating_sub ( 1 ) ) ,
)
2018-08-23 14:39:54 +03:00
}
2018-08-21 19:51:48 +03:00
pub fn set_cols ( & mut self , new_cols : usize ) {
self . cols = new_cols ;
}
2018-07-10 11:45:30 +03:00
/// Constructs a new `CellBuffer` with the given number of columns and rows, using the given
/// `cell` as a blank.
2020-11-23 00:33:20 +02:00
pub fn new ( cols : usize , rows : usize , default_cell : Cell ) -> CellBuffer {
2018-07-10 11:45:30 +03:00
CellBuffer {
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 ,
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 ( ) ,
2018-07-10 11:45:30 +03:00
}
}
2020-11-27 23:05:35 +02:00
pub fn new_with_context (
cols : usize ,
rows : usize ,
default_cell : Option < Cell > ,
context : & Context ,
) -> CellBuffer {
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
} ) ;
2019-10-06 11:28:12 +03:00
CellBuffer {
cols ,
rows ,
2020-11-27 23:05:35 +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 : context . settings . terminal . ascii_drawing ,
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 ( ) ,
2019-10-06 11:28:12 +03:00
}
}
pub fn set_ascii_drawing ( & mut self , new_val : bool ) {
self . ascii_drawing = new_val ;
}
2019-11-06 15:09:15 +02:00
pub fn set_growable ( & mut self , new_val : bool ) {
self . growable = new_val ;
}
2018-07-10 11:45:30 +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 ]
pub fn resize ( & mut self , newcols : usize , newrows : usize , blank : Cell ) -> bool {
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 {
return ! ( newlen > = Self ::MAX_SIZE ) ;
2019-11-22 14:17:09 +02:00
}
2018-07-10 11:45:30 +03:00
let mut newbuf : Vec < Cell > = Vec ::with_capacity ( newlen ) ;
for y in 0 .. newrows {
for x in 0 .. newcols {
let cell = self . get ( x , y ) . unwrap_or ( & blank ) ;
newbuf . push ( * cell ) ;
}
}
self . buf = newbuf ;
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.
pub fn clear ( & mut self , blank : Cell ) {
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
}
}
/// Returns a reference to the `Cell` at the given coordinates, or `None` if the index is out of
/// bounds.
///
/// # Examples
///
2020-02-04 15:52:12 +02:00
/// ```no_run
2019-11-24 20:42:26 +02:00
///
/// let mut term = Terminal::new().unwrap();
///
/// let a_cell = term.get(5, 5);
/// ```
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 ,
}
}
/// Returns a mutable reference to the `Cell` at the given coordinates, or `None` if the index
/// is out of bounds.
///
/// # Examples
///
2020-02-04 15:52:12 +02:00
/// ```no_run
2019-11-24 20:42:26 +02:00
///
/// let mut term = Terminal::new().unwrap();
///
/// let a_mut_cell = term.get_mut(5, 5);
/// ```
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 ) {
//debug!(
// "scroll_up scroll_region {:?}, top: {} offset {}",
// scroll_region, top, offset
//);
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 ) {
//debug!(
// "scroll_down scroll_region {:?}, top: {} offset {}",
// scroll_region, top, offset
//);
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 {
BoundsIterator {
rows : std ::cmp ::min ( self . rows . saturating_sub ( 1 ) , get_y ( upper_left! ( area ) ) )
2019-12-06 12:33:19 +02:00
.. ( std ::cmp ::min ( self . rows , get_y ( bottom_right! ( area ) ) + 1 ) ) ,
2019-12-01 17:04:55 +02:00
cols : (
std ::cmp ::min ( self . cols . saturating_sub ( 1 ) , get_x ( upper_left! ( area ) ) ) ,
2019-12-06 12:33:19 +02:00
std ::cmp ::min ( self . cols , get_x ( bottom_right! ( area ) ) + 1 ) ,
2019-12-01 17:04:55 +02:00
) ,
}
}
2019-12-01 17:11:13 +02:00
/// See `RowIterator` documentation.
2019-12-12 11:04:14 +02:00
pub fn row_iter ( & self , bounds : std ::ops ::Range < usize > , row : usize ) -> RowIterator {
2019-12-01 17:04:55 +02:00
if row < self . rows {
RowIterator {
row ,
2019-12-12 11:04:14 +02:00
col : std ::cmp ::min ( self . cols . saturating_sub ( 1 ) , bounds . start )
.. ( std ::cmp ::min ( self . cols , bounds . end ) ) ,
2019-12-01 17:04:55 +02:00
}
} else {
RowIterator { row , col : 0 .. 0 }
}
}
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 {
use std ::collections ::hash_map ::DefaultHasher ;
use std ::hash ::{ Hash , Hasher } ;
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 )
. unwrap_or ( self . buf . len ( ) . saturating_sub ( 1 ) ) ;
let end = self
. pos_to_index ( end . 0 , end . 1 )
. unwrap_or ( self . buf . len ( ) . saturating_sub ( 1 ) ) ;
if start ! = end {
self . tag_associations . push ( ( tag , ( start , end ) ) ) ;
}
}
2018-07-10 11:45:30 +03:00
}
impl Deref for CellBuffer {
type Target = [ Cell ] ;
2018-08-07 15:01:15 +03:00
fn deref ( & self ) -> & [ Cell ] {
2018-07-10 11:45:30 +03:00
& self . buf
}
}
impl DerefMut for CellBuffer {
2018-08-07 15:01:15 +03:00
fn deref_mut ( & mut self ) -> & mut [ Cell ] {
2018-07-10 11:45:30 +03:00
& mut self . buf
}
}
impl Index < Pos > for CellBuffer {
type Output = Cell ;
2018-08-07 15:01:15 +03:00
fn index ( & self , index : Pos ) -> & Cell {
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 " )
}
}
impl Default for CellBuffer {
/// Constructs a new `CellBuffer` with a size of `(0, 0)`, using the default `Cell` as a blank.
fn default ( ) -> CellBuffer {
CellBuffer ::new ( 0 , 0 , Cell ::default ( ) )
}
}
2018-07-24 11:34:44 +03:00
impl fmt ::Display for CellBuffer {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
'_ 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
2020-02-04 15:52:12 +02: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
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
/// ```
pub fn new ( ch : char , fg : Color , bg : Color , attrs : Attr ) -> Cell {
2019-08-17 12:22:54 +03:00
Cell {
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
}
/// Creates a new `Cell` with the given `char` and default style.
///
/// # Examples
///
2020-02-04 15:52:12 +02:00
/// ```no_run
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
/// ```
pub fn with_char ( ch : char ) -> Cell {
2020-04-05 12:04:25 +03:00
Cell ::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
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(), ' ');
/// ```
pub fn with_style ( fg : Color , bg : Color , attr : Attr ) -> Cell {
Cell ::new ( ' ' , fg , bg , attr )
}
/// Returns the `Cell`'s character.
///
/// # Examples
///
2020-02-04 15:52:12 +02:00
/// ```no_run
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
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');
/// ```
pub fn set_ch ( & mut self , newch : char ) -> & mut Cell {
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
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
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);
/// ```
pub fn set_fg ( & mut self , newfg : Color ) -> & mut Cell {
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
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
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);
/// ```
pub fn set_bg ( & mut self , newbg : Color ) -> & mut Cell {
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
}
pub fn set_attrs ( & mut self , newattrs : Attr ) -> & mut Cell {
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
2019-12-01 17:11:13 +02: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
2020-01-27 17:07:29 +02:00
pub fn set_empty ( & mut self , new_val : bool ) -> & mut Cell {
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
2019-12-01 17:11:13 +02: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.
2020-01-27 17:07:29 +02:00
pub fn set_keep_fg ( & mut self , new_val : bool ) -> & mut Cell {
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
}
2019-12-01 17:11:13 +02: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.
2020-01-27 17:07:29 +02:00
pub fn set_keep_bg ( & mut self , new_val : bool ) -> & mut Cell {
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
/// 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.
pub fn set_keep_attrs ( & mut self , new_val : bool ) -> & mut Cell {
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
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);
/// ```
fn default ( ) -> Cell {
2020-04-05 12:04:25 +03:00
Cell ::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
/// // Default attribute.
/// let def = Attr::DEFAULT;
///
/// // Base attribute.
/// let base = Attr::BOLD;
///
/// // Combination.
/// let comb = Attr::UNDERLINE | Attr::REVERSE;
/// ```
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 {
Attr ::DEFAULT
2020-01-27 20:17:46 +02:00
}
}
2020-04-05 12:04:25 +03:00
impl fmt ::Display for Attr {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
match * self {
Attr ::DEFAULT = > write! ( f , " Default " ) ,
Attr ::BOLD = > write! ( f , " Bold " ) ,
Attr ::DIM = > write! ( f , " Dim " ) ,
Attr ::ITALICS = > write! ( f , " Italics " ) ,
Attr ::UNDERLINE = > write! ( f , " Underline " ) ,
Attr ::BLINK = > write! ( f , " Blink " ) ,
Attr ::REVERSE = > write! ( f , " Reverse " ) ,
Attr ::HIDDEN = > write! ( f , " Hidden " ) ,
combination = > {
let mut ctr = 0 ;
if combination . intersects ( Attr ::BOLD ) {
ctr + = 1 ;
Attr ::BOLD . fmt ( f ) ? ;
}
if combination . intersects ( Attr ::DIM ) {
if ctr > 0 {
write! ( f , " | " ) ? ;
}
ctr + = 1 ;
Attr ::DIM . fmt ( f ) ? ;
}
if combination . intersects ( Attr ::ITALICS ) {
if ctr > 0 {
write! ( f , " | " ) ? ;
}
ctr + = 1 ;
Attr ::ITALICS . fmt ( f ) ? ;
}
if combination . intersects ( Attr ::UNDERLINE ) {
if ctr > 0 {
write! ( f , " | " ) ? ;
}
ctr + = 1 ;
Attr ::UNDERLINE . fmt ( f ) ? ;
}
if combination . intersects ( Attr ::BLINK ) {
if ctr > 0 {
write! ( f , " | " ) ? ;
}
ctr + = 1 ;
Attr ::BLINK . fmt ( f ) ? ;
}
if combination . intersects ( Attr ::REVERSE ) {
if ctr > 0 {
write! ( f , " | " ) ? ;
}
ctr + = 1 ;
Attr ::REVERSE . fmt ( f ) ? ;
}
if combination . intersects ( Attr ::HIDDEN ) {
if ctr > 0 {
write! ( f , " | " ) ? ;
}
Attr ::HIDDEN . fmt ( f ) ? ;
}
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 ) {
2020-04-05 12:04:25 +03:00
Attr ::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 ( ) {
" Default " = > Ok ( Attr ::DEFAULT ) ,
" Dim " = > Ok ( Attr ::DIM ) ,
" Bold " = > Ok ( Attr ::BOLD ) ,
" Italics " = > Ok ( Attr ::ITALICS ) ,
" Underline " = > Ok ( Attr ::UNDERLINE ) ,
" Blink " = > Ok ( Attr ::BLINK ) ,
" Reverse " = > Ok ( Attr ::REVERSE ) ,
" Hidden " = > Ok ( Attr ::HIDDEN ) ,
combination if combination . contains ( " | " ) = > {
let mut ret = Attr ::DEFAULT ;
for c in combination . trim ( ) . split ( " | " ) {
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
}
}
2020-01-27 17:07:29 +02:00
pub fn write ( self , prev : Attr , stdout : & mut crate ::StateStdout ) -> std ::io ::Result < ( ) > {
use std ::io ::Write ;
2020-04-05 12:04:25 +03:00
match ( self . intersects ( Attr ::BOLD ) , prev . intersects ( Attr ::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 (
| _ | match ( self . intersects ( Attr ::DIM ) , prev . intersects ( Attr ::DIM ) ) {
( true , true ) | ( false , false ) = > Ok ( ( ) ) ,
( false , true ) = > write! ( stdout , " \x1B [22m " ) ,
( true , false ) = > write! ( stdout , " \x1B [2m " ) ,
} ,
)
. and_then ( | _ | {
match (
self . intersects ( Attr ::ITALICS ) ,
prev . intersects ( Attr ::ITALICS ) ,
) {
( 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 (
self . intersects ( Attr ::UNDERLINE ) ,
prev . intersects ( Attr ::UNDERLINE ) ,
) {
( true , true ) | ( false , false ) = > Ok ( ( ) ) ,
( false , true ) = > write! ( stdout , " \x1B [24m " ) ,
( true , false ) = > write! ( stdout , " \x1B [4m " ) ,
}
} )
. and_then (
| _ | match ( self . intersects ( Attr ::BLINK ) , prev . intersects ( Attr ::BLINK ) ) {
( true , true ) | ( false , false ) = > Ok ( ( ) ) ,
( false , true ) = > write! ( stdout , " \x1B [25m " ) ,
( true , false ) = > write! ( stdout , " \x1B [5m " ) ,
} ,
)
. and_then ( | _ | {
match (
self . intersects ( Attr ::REVERSE ) ,
prev . intersects ( Attr ::REVERSE ) ,
) {
( true , true ) | ( false , false ) = > Ok ( ( ) ) ,
( false , true ) = > write! ( stdout , " \x1B [27m " ) ,
( true , false ) = > write! ( stdout , " \x1B [7m " ) ,
}
} )
. and_then ( | _ | {
match ( self . intersects ( Attr ::HIDDEN ) , prev . intersects ( Attr ::HIDDEN ) ) {
( 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
} )
}
2020-01-24 09:19:57 +02:00
}
2018-08-25 02:23:40 +03:00
pub fn copy_area_with_break (
grid_dest : & mut CellBuffer ,
grid_src : & CellBuffer ,
dest : Area ,
src : Area ,
) -> Pos {
if ! is_valid_area! ( dest ) | | ! is_valid_area! ( src ) {
2019-05-01 19:20:33 +03:00
debug! (
2018-08-25 02:23:40 +03:00
" BUG: Invalid areas in copy_area: \n src: {:?} \n dest: {:?} " ,
src , dest
) ;
return upper_left! ( dest ) ;
}
2019-03-14 12:19:25 +02:00
2019-02-18 23:14:06 +02:00
if grid_src . is_empty ( ) | | grid_dest . is_empty ( ) {
return upper_left! ( dest ) ;
}
2018-08-25 02:23:40 +03:00
let mut ret = bottom_right! ( dest ) ;
let mut src_x = get_x ( upper_left! ( src ) ) ;
let mut src_y = get_y ( upper_left! ( src ) ) ;
' y_ : for y in get_y ( upper_left! ( dest ) ) ..= get_y ( bottom_right! ( dest ) ) {
' x_ : for x in get_x ( upper_left! ( dest ) ) ..= get_x ( bottom_right! ( dest ) ) {
if grid_src [ ( src_x , src_y ) ] . ch ( ) = = '\n' {
src_y + = 1 ;
src_x = 0 ;
if src_y > = get_y ( bottom_right! ( src ) ) {
ret . 1 = y ;
break 'y_ ;
}
continue 'y_ ;
}
grid_dest [ ( x , y ) ] = grid_src [ ( src_x , src_y ) ] ;
src_x + = 1 ;
if src_x > = get_x ( bottom_right! ( src ) ) {
src_y + = 1 ;
src_x = 0 ;
if src_y > = get_y ( bottom_right! ( src ) ) {
//clear_area(grid_dest, ((get_x(upper_left!(dest)), y), bottom_right!(dest)));
ret . 1 = y ;
break 'y_ ;
}
break 'x_ ;
}
}
}
ret
}
/// Copy a source `Area` to a destination.
pub fn copy_area ( grid_dest : & mut CellBuffer , grid_src : & CellBuffer , dest : Area , src : Area ) -> Pos {
if ! is_valid_area! ( dest ) | | ! is_valid_area! ( src ) {
2019-05-01 19:20:33 +03:00
debug! (
2018-08-25 02:23:40 +03:00
" BUG: Invalid areas in copy_area: \n src: {:?} \n dest: {:?} " ,
src , dest
) ;
return upper_left! ( dest ) ;
}
2019-02-18 23:14:06 +02:00
if grid_src . is_empty ( ) | | grid_dest . is_empty ( ) {
return upper_left! ( dest ) ;
}
2018-08-25 02:23:40 +03:00
let mut ret = bottom_right! ( dest ) ;
let mut src_x = get_x ( upper_left! ( src ) ) ;
let mut src_y = get_y ( upper_left! ( src ) ) ;
let ( cols , rows ) = grid_src . size ( ) ;
if src_x > = cols | | src_y > = rows {
2019-05-01 19:20:33 +03:00
debug! ( " BUG: src area outside of grid_src in copy_area " , ) ;
2018-08-25 02:23:40 +03:00
return upper_left! ( dest ) ;
}
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 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 ) ;
2020-06-10 19:02:54 +03:00
let mut stack : std ::collections ::BTreeSet < & FormatTag > = std ::collections ::BTreeSet ::default ( ) ;
2018-08-25 02:23:40 +03:00
for y in get_y ( upper_left! ( dest ) ) ..= get_y ( bottom_right! ( dest ) ) {
' for_x : for x in get_x ( upper_left! ( dest ) ) ..= get_x ( bottom_right! ( dest ) ) {
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 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 {
2020-06-10 19:02:54 +03:00
stack . insert ( & grid_src . tag_table ( ) [ & tag_associations [ tag_offset ] . 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
} else {
2020-06-10 19:02:54 +03:00
stack . remove ( & grid_src . tag_table ( ) [ & tag_associations [ tag_offset ] . 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
}
tag_offset + = 1 ;
}
2018-08-25 02:23:40 +03:00
grid_dest [ ( x , y ) ] = grid_src [ ( src_x , src_y ) ] ;
2020-06-10 19:02:54 +03:00
for t in & stack {
if let Some ( fg ) = t . fg {
2020-06-07 18:30:30 +03:00
grid_dest [ ( x , y ) ] . set_fg ( fg ) . set_keep_fg ( true ) ;
2020-06-04 21:33:27 +03:00
}
2020-06-10 19:02:54 +03:00
if let Some ( bg ) = t . bg {
2020-06-07 18:30:30 +03:00
grid_dest [ ( x , y ) ] . set_bg ( bg ) . set_keep_bg ( true ) ;
2020-06-04 21:33:27 +03:00
}
2020-06-10 19:02:54 +03:00
if let Some ( attrs ) = t . attrs {
2020-06-04 21:33:27 +03:00
grid_dest [ ( x , y ) ] . attrs | = attrs ;
2020-06-07 18:30:30 +03:00
grid_dest [ ( x , y ) ] . set_keep_attrs ( true ) ;
2020-06-04 21:33:27 +03: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
}
2018-08-25 02:23:40 +03:00
if src_x > = get_x ( bottom_right! ( src ) ) {
break 'for_x ;
}
src_x + = 1 ;
}
src_x = get_x ( upper_left! ( src ) ) ;
2018-10-14 19:49:16 +03:00
src_y + = 1 ;
if src_y > get_y ( bottom_right! ( src ) ) {
2020-02-08 13:40:47 +02:00
for row in
grid_dest . bounds_iter ( ( ( get_x ( upper_left! ( dest ) ) , y + 1 ) , bottom_right! ( dest ) ) )
{
for c in row {
grid_dest [ c ] . set_ch ( ' ' ) ;
}
}
2018-08-25 02:23:40 +03:00
ret . 1 = y ;
break ;
}
}
ret
}
/// Change foreground and background colors in an `Area`
pub fn change_colors ( grid : & mut CellBuffer , area : Area , fg_color : Color , bg_color : Color ) {
2020-01-27 17:07:29 +02:00
if cfg! ( feature = " debug-tracing " ) {
let bounds = grid . size ( ) ;
let upper_left = upper_left! ( area ) ;
let bottom_right = bottom_right! ( area ) ;
let ( x , y ) = upper_left ;
if y > ( get_y ( bottom_right ) )
| | x > get_x ( bottom_right )
| | y > = get_y ( bounds )
| | x > = get_x ( bounds )
{
debug! ( " BUG: Invalid area in change_colors: \n area: {:?} " , area ) ;
return ;
}
if ! is_valid_area! ( area ) {
debug! ( " BUG: Invalid area in change_colors: \n area: {:?} " , area ) ;
return ;
}
2018-08-25 02:23:40 +03:00
}
2020-01-27 17:07:29 +02:00
for row in grid . bounds_iter ( area ) {
for c in row {
grid [ c ] . set_fg ( fg_color ) . set_bg ( bg_color ) ;
2018-08-25 02:23:40 +03:00
}
}
}
2019-04-06 00:46:16 +03:00
macro_rules ! inspect_bounds {
( $grid :ident , $area :ident , $x : ident , $y : ident , $line_break :ident ) = > {
let bounds = $grid . size ( ) ;
let ( upper_left , bottom_right ) = $area ;
2020-08-02 00:49:59 +03:00
if $x > ( get_x ( bottom_right ) ) | | $x > = get_x ( bounds ) {
2019-11-11 17:59:36 +02:00
if $grid . growable {
2020-12-01 01:02:48 +02:00
if ! $grid . resize (
2020-08-02 16:40:50 +03:00
std ::cmp ::max ( $x + 1 , $grid . cols ) ,
$grid . rows ,
2020-11-23 00:33:20 +02:00
$grid . default_cell ,
2020-12-01 01:02:48 +02:00
) {
break ;
} ;
2019-11-11 17:59:36 +02:00
} else {
$x = get_x ( upper_left ) ;
$y + = 1 ;
2020-12-01 01:02:48 +02:00
if let Some ( _x ) = $line_break {
$x = _x ;
2019-11-18 13:06:30 +02:00
} else {
2020-12-01 01:02:48 +02:00
break ;
2019-11-06 15:09:15 +02:00
}
2019-04-06 00:46:16 +03:00
}
2019-11-11 17:59:36 +02:00
}
2020-08-02 00:49:59 +03:00
if $y > ( get_y ( bottom_right ) ) | | $y > = get_y ( bounds ) {
2019-11-11 17:59:36 +02:00
if $grid . growable {
2020-12-01 01:02:48 +02:00
if ! $grid . resize (
2020-08-02 16:40:50 +03:00
$grid . cols ,
std ::cmp ::max ( $y + 1 , $grid . rows ) ,
2020-11-23 00:33:20 +02:00
$grid . default_cell ,
2020-12-01 01:02:48 +02:00
) {
break ;
} ;
2019-11-11 17:59:36 +02:00
} else {
return ( $x , $y - 1 ) ;
2019-04-06 00:46:16 +03:00
}
}
} ;
}
2018-08-25 02:23:40 +03:00
/// Write an `&str` to a `CellBuffer` in a specified `Area` with the passed colors.
pub fn write_string_to_grid (
s : & str ,
grid : & mut CellBuffer ,
fg_color : Color ,
bg_color : Color ,
2019-08-18 15:44:40 +03:00
attrs : Attr ,
2018-08-25 02:23:40 +03:00
area : Area ,
2019-11-18 13:06:30 +02:00
// The left-most x coordinate.
line_break : Option < usize > ,
2018-08-25 02:23:40 +03:00
) -> Pos {
2020-12-01 01:02:48 +02:00
let mut bounds = grid . size ( ) ;
2018-08-25 02:23:40 +03:00
let upper_left = upper_left! ( area ) ;
let bottom_right = bottom_right! ( area ) ;
let ( mut x , mut y ) = upper_left ;
2019-03-14 12:00:41 +02:00
if y = = get_y ( bounds ) | | x = = get_x ( bounds ) {
2019-11-06 15:09:15 +02:00
if grid . growable {
2020-12-01 01:02:48 +02:00
if ! grid . resize (
std ::cmp ::max ( grid . cols , x + 2 ) ,
std ::cmp ::max ( grid . rows , y + 2 ) ,
2020-11-23 00:33:20 +02:00
grid . default_cell ,
2020-12-01 01:02:48 +02:00
) {
return ( x , y ) ;
}
bounds = grid . size ( ) ;
2019-11-06 15:09:15 +02:00
} else {
return ( x , y ) ;
}
2019-03-14 12:00:41 +02:00
}
2018-08-25 02:23:40 +03:00
if y > ( get_y ( bottom_right ) )
| | x > get_x ( bottom_right )
2019-03-14 12:00:41 +02:00
| | y > get_y ( bounds )
| | x > get_x ( bounds )
2018-08-25 02:23:40 +03:00
{
2019-11-06 15:09:15 +02:00
if grid . growable {
2020-12-01 01:02:48 +02:00
if ! grid . resize (
std ::cmp ::max ( grid . cols , x + 2 ) ,
std ::cmp ::max ( grid . rows , y + 2 ) ,
2020-11-23 00:33:20 +02:00
grid . default_cell ,
2020-12-01 01:02:48 +02:00
) {
return ( x , y ) ;
}
2019-11-06 15:09:15 +02:00
} else {
debug! ( " Invalid area with string {} and area {:?} " , s , area ) ;
return ( x , y ) ;
}
2018-08-25 02:23:40 +03:00
}
2019-04-06 00:46:16 +03:00
for c in s . chars ( ) {
2020-03-12 09:47:39 +02:00
inspect_bounds! ( grid , area , x , y , line_break ) ;
2019-04-04 16:37:17 +03:00
if c = = '\r' {
continue ;
}
2020-02-28 09:17:30 +02:00
if c = = '\n' {
y + = 1 ;
2020-12-01 01:02:48 +02:00
if let Some ( _x ) = line_break {
x = _x ;
2020-02-28 09:17:30 +02:00
inspect_bounds! ( grid , area , x , y , line_break ) ;
continue ;
2020-12-01 01:02:48 +02:00
} else {
break ;
2020-02-28 09:17:30 +02:00
}
}
2019-04-06 00:46:16 +03:00
if c = = '\t' {
grid [ ( x , y ) ] . set_ch ( ' ' ) ;
x + = 1 ;
inspect_bounds! ( grid , area , x , y , line_break ) ;
grid [ ( x , y ) ] . set_ch ( ' ' ) ;
} else {
grid [ ( x , y ) ] . set_ch ( c ) ;
}
2020-01-27 17:07:29 +02:00
grid [ ( x , y ) ]
. set_fg ( fg_color )
. set_bg ( bg_color )
. set_attrs ( attrs ) ;
2019-08-17 12:22:54 +03:00
match wcwidth ( u32 ::from ( c ) ) {
Some ( 0 ) | None = > {
/* Skip drawing zero width characters */
grid [ ( 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 ;
inspect_bounds! ( grid , area , x , y , line_break ) ;
2019-12-08 10:57:36 +02:00
grid [ ( x , y ) ] = Cell ::default ( ) ;
2020-01-27 17:07:29 +02:00
grid [ ( x , y ) ]
. set_fg ( fg_color )
. set_bg ( bg_color )
. set_attrs ( attrs )
. set_empty ( true ) ;
2019-08-17 12:22:54 +03:00
}
_ = > { }
}
2018-08-25 02:23:40 +03:00
x + = 1 ;
}
( x , y )
}
/// Completely clear an `Area` with an empty char and the terminal's default colors.
2020-02-08 13:40:47 +02:00
pub fn clear_area ( grid : & mut CellBuffer , area : Area , attributes : crate ::conf ::ThemeAttribute ) {
2018-08-25 02:23:40 +03:00
if ! is_valid_area! ( area ) {
return ;
}
2019-12-06 12:33:19 +02:00
for row in grid . bounds_iter ( area ) {
for c in row {
grid [ c ] = Cell ::default ( ) ;
2020-02-08 13:40:47 +02:00
grid [ c ]
. set_fg ( attributes . fg )
. set_bg ( attributes . bg )
. set_attrs ( attributes . attrs ) ;
2018-08-25 02:23:40 +03:00
}
}
}
2019-10-03 01:03:20 +03:00
2019-10-06 11:30:35 +03:00
pub mod ansi {
2019-12-01 17:11:13 +02:00
//! Create a `CellBuffer` from a string slice containing ANSI escape codes.
2020-05-29 20:21:08 +03:00
use super ::{ Attr , Cell , CellBuffer , Color } ;
2019-12-01 17:11:13 +02:00
/// Create a `CellBuffer` from a string slice containing ANSI escape codes.
2019-10-06 11:30:35 +03:00
pub fn ansi_to_cellbuffer ( s : & str ) -> Option < CellBuffer > {
2020-05-31 22:37:06 +03:00
let mut bufs : Vec < Vec < Cell > > = Vec ::with_capacity ( 2048 ) ;
let mut row : Vec < Cell > = Vec ::with_capacity ( 2048 ) ;
2019-10-06 11:30:35 +03:00
enum State {
Start ,
Csi ,
SetFg ,
SetBg ,
}
use State ::* ;
let mut rows = 0 ;
2020-05-31 22:37:06 +03:00
let mut max_cols = 0 ;
2019-10-06 11:30:35 +03:00
let mut current_fg = Color ::Default ;
let mut current_bg = Color ::Default ;
2020-05-29 20:21:08 +03:00
let mut current_attrs = Attr ::DEFAULT ;
2019-10-06 11:30:35 +03:00
let mut cur_cell ;
let mut state : State ;
for l in s . lines ( ) {
cur_cell = Cell ::default ( ) ;
state = State ::Start ;
let mut chars = l . chars ( ) . peekable ( ) ;
2020-05-31 22:37:06 +03:00
if rows > 0 {
max_cols = std ::cmp ::max ( row . len ( ) , max_cols ) ;
bufs . push ( row ) ;
row = Vec ::with_capacity ( 2048 ) ;
}
2019-10-06 11:30:35 +03:00
rows + = 1 ;
' line_loop : loop {
let c = chars . next ( ) ;
if c . is_none ( ) {
break 'line_loop ;
}
match ( & state , c . unwrap ( ) ) {
( Start , '\x1b' ) = > {
if chars . next ( ) ! = Some ( '[' ) {
return None ;
}
state = Csi ;
}
( Start , c ) = > {
cur_cell . set_ch ( c ) ;
cur_cell . set_fg ( current_fg ) ;
cur_cell . set_bg ( current_bg ) ;
2020-05-29 20:21:08 +03:00
cur_cell . set_attrs ( current_attrs ) ;
2020-05-31 22:37:06 +03:00
row . push ( cur_cell ) ;
2019-10-06 11:30:35 +03:00
cur_cell = Cell ::default ( ) ;
}
( Csi , 'm' ) = > {
/* Reset styles */
current_fg = Color ::Default ;
current_bg = Color ::Default ;
2020-05-29 20:21:08 +03:00
current_attrs = Attr ::DEFAULT ;
2019-10-06 11:30:35 +03:00
state = Start ;
}
2020-05-31 22:37:06 +03:00
( Csi , '0' ) if chars . peek ( ) = = Some ( & '0' ) = > {
current_attrs = Attr ::DEFAULT ;
chars . next ( ) ;
let next = chars . next ( ) ;
if next = = Some ( 'm' ) {
state = Start ;
} else if next ! = Some ( ';' ) {
return None ;
}
}
( Csi , c @ '0' ..= '8' ) if chars . peek ( ) = = Some ( & 'm' ) = > {
chars . next ( ) ;
state = Start ;
match c {
'0' = > {
2020-05-29 20:21:08 +03:00
//Reset all attributes
current_fg = Color ::Default ;
current_bg = Color ::Default ;
current_attrs = Attr ::DEFAULT ;
}
2020-05-31 22:37:06 +03:00
'1' = > {
2020-05-29 20:21:08 +03:00
current_attrs . set ( Attr ::BOLD , true ) ;
}
2020-05-31 22:37:06 +03:00
'2' = > {
2020-05-29 20:21:08 +03:00
current_attrs . set ( Attr ::DIM , true ) ;
}
2020-05-31 22:37:06 +03:00
'3' = > {
2020-05-29 20:21:08 +03:00
current_attrs . set ( Attr ::ITALICS , true ) ;
}
2020-05-31 22:37:06 +03:00
'4' = > {
2020-05-29 20:21:08 +03:00
current_attrs . set ( Attr ::UNDERLINE , true ) ;
}
2020-05-31 22:37:06 +03:00
'5' = > {
2020-05-29 20:21:08 +03:00
current_attrs . set ( Attr ::BLINK , true ) ;
}
2020-05-31 22:37:06 +03:00
'7' = > {
2020-05-29 20:21:08 +03:00
current_attrs . set ( Attr ::REVERSE , true ) ;
}
2020-05-31 22:37:06 +03:00
'8' = > {
2020-05-29 20:21:08 +03:00
current_attrs . set ( Attr ::HIDDEN , true ) ;
}
_ = > return None ,
}
}
2020-05-31 22:37:06 +03:00
( Csi , '0' ) = > {
continue ;
}
2020-05-29 20:21:08 +03:00
( Csi , '2' ) = > {
match ( chars . next ( ) , chars . next ( ) ) {
( Some ( '2' ) , Some ( 'm' ) ) = > {
current_attrs . set ( Attr ::BOLD , false ) ;
current_attrs . set ( Attr ::DIM , false ) ;
}
( Some ( '3' ) , Some ( 'm' ) ) = > {
current_attrs . set ( Attr ::ITALICS , false ) ;
}
( Some ( '4' ) , Some ( 'm' ) ) = > {
current_attrs . set ( Attr ::UNDERLINE , false ) ;
}
( Some ( '5' ) , Some ( 'm' ) ) = > {
current_attrs . set ( Attr ::BLINK , false ) ;
}
( Some ( '7' ) , Some ( 'm' ) ) = > {
current_attrs . set ( Attr ::REVERSE , false ) ;
}
( Some ( '8' ) , Some ( 'm' ) ) = > {
current_attrs . set ( Attr ::HIDDEN , false ) ;
}
( Some ( '9' ) , Some ( 'm' ) ) = > { /* Not crossed out */ }
_ = > return None ,
2019-10-06 11:30:35 +03:00
}
}
( Csi , '3' ) = > {
match chars . next ( ) {
Some ( '8' ) = > {
/* Set foreground color */
if chars . next ( ) = = Some ( ';' ) {
state = SetFg ;
/* Next arguments are 5;n or 2;r;g;b */
continue ;
}
2020-05-31 22:37:06 +03:00
chars . next ( ) ;
2019-10-06 11:30:35 +03:00
return None ;
}
2020-05-31 22:37:06 +03:00
Some ( '9' ) = > {
current_fg = Color ::Default ;
/* default foreground color */
let next = chars . next ( ) ;
if next = = Some ( 'm' ) {
state = Start ;
} else if next ! = Some ( ';' ) {
return None ;
}
continue ;
}
2019-10-06 11:30:35 +03:00
Some ( c ) if c > = '0' & & c < '8' = > {
current_fg = Color ::from_byte ( c as u8 - 0x30 ) ;
if chars . next ( ) ! = Some ( 'm' ) {
return None ;
}
state = Start ;
}
_ = > return None ,
}
}
( Csi , '4' ) = > {
match chars . next ( ) {
Some ( '8' ) = > {
/* Set background color */
if chars . next ( ) = = Some ( ';' ) {
state = SetBg ;
/* Next arguments are 5;n or 2;r;g;b */
continue ;
}
return None ;
}
2020-05-31 22:37:06 +03:00
Some ( '9' ) = > {
/* default background color */
current_bg = Color ::Default ;
let next = chars . next ( ) ;
if next = = Some ( 'm' ) {
state = Start ;
} else if next ! = Some ( ';' ) {
return None ;
}
continue ;
}
2019-10-06 11:30:35 +03:00
Some ( c ) if c > = '0' & & c < '8' = > {
current_bg = Color ::from_byte ( c as u8 - 0x30 ) ;
if chars . next ( ) ! = Some ( 'm' ) {
return None ;
}
state = Start ;
}
_ = > return None ,
}
}
2020-05-31 22:37:06 +03:00
( Csi , '9' ) = > {
match chars . next ( ) {
Some ( '0' ) = > current_fg = Color ::Black ,
Some ( '1' ) = > current_fg = Color ::Red ,
Some ( '2' ) = > current_fg = Color ::Green ,
Some ( '3' ) = > current_fg = Color ::Yellow ,
Some ( '4' ) = > current_fg = Color ::Blue ,
Some ( '5' ) = > current_fg = Color ::Magenta ,
Some ( '6' ) = > current_fg = Color ::Cyan ,
Some ( '7' ) = > current_fg = Color ::White ,
2020-06-12 01:42:06 +03:00
_ = > { }
2020-05-31 22:37:06 +03:00
}
let next = chars . next ( ) ;
if next ! = Some ( 'm' ) {
//debug!(next);
}
state = Start ;
}
( Csi , '1' ) if chars . peek ( ) = = Some ( & '0' ) = > {
chars . next ( ) ;
match chars . next ( ) {
Some ( '0' ) = > current_bg = Color ::Black ,
Some ( '1' ) = > current_bg = Color ::Red ,
Some ( '2' ) = > current_bg = Color ::Green ,
Some ( '3' ) = > current_bg = Color ::Yellow ,
Some ( '4' ) = > current_bg = Color ::Blue ,
Some ( '5' ) = > current_bg = Color ::Magenta ,
Some ( '6' ) = > current_bg = Color ::Cyan ,
Some ( '7' ) = > current_bg = Color ::White ,
2020-06-12 01:42:06 +03:00
_ = > { }
2020-05-31 22:37:06 +03:00
}
let next = chars . next ( ) ;
if next ! = Some ( 'm' ) {
//debug!(next);
}
state = Start ;
}
2019-10-06 11:30:35 +03:00
( SetFg , '5' ) = > {
if chars . next ( ) ! = Some ( ';' ) {
return None ;
}
let mut accum = 0 ;
while chars . peek ( ) . is_some ( ) & & chars . peek ( ) ! = Some ( & 'm' ) {
let c = chars . next ( ) . unwrap ( ) ;
accum * = 10 ;
accum + = c as u8 - 0x30 ;
}
if chars . next ( ) ! = Some ( 'm' ) {
return None ;
}
current_fg = Color ::from_byte ( accum ) ;
state = Start ;
}
( SetFg , '2' ) = > {
if chars . next ( ) ! = Some ( ';' ) {
return None ;
}
let mut rgb_color = Color ::Rgb ( 0 , 0 , 0 ) ;
if let Color ::Rgb ( ref mut r , ref mut g , ref mut b ) = rgb_color {
' rgb_fg : for val in & mut [ r , g , b ] {
let mut accum = 0 ;
while chars . peek ( ) . is_some ( )
& & chars . peek ( ) ! = Some ( & ';' )
& & chars . peek ( ) ! = Some ( & 'm' )
{
let c = chars . next ( ) . unwrap ( ) ;
accum * = 10 ;
accum + = c as u8 - 0x30 ;
}
* * val = accum ;
match chars . peek ( ) {
Some ( & 'm' ) = > {
break 'rgb_fg ;
}
Some ( & ';' ) = > {
chars . next ( ) ;
}
_ = > return None ,
}
}
}
if chars . next ( ) ! = Some ( 'm' ) {
return None ;
}
current_fg = rgb_color ;
state = Start ;
}
( SetBg , '5' ) = > {
if chars . next ( ) ! = Some ( ';' ) {
return None ;
}
let mut accum = 0 ;
while chars . peek ( ) . is_some ( ) & & chars . peek ( ) ! = Some ( & 'm' ) {
let c = chars . next ( ) . unwrap ( ) ;
accum * = 10 ;
accum + = c as u8 - 0x30 ;
}
if chars . next ( ) ! = Some ( 'm' ) {
return None ;
}
current_bg = Color ::from_byte ( accum ) ;
state = Start ;
}
( SetBg , '2' ) = > {
if chars . next ( ) ! = Some ( ';' ) {
return None ;
}
let mut rgb_color = Color ::Rgb ( 0 , 0 , 0 ) ;
if let Color ::Rgb ( ref mut r , ref mut g , ref mut b ) = rgb_color {
' rgb_bg : for val in & mut [ r , g , b ] {
let mut accum = 0 ;
while chars . peek ( ) . is_some ( )
& & chars . peek ( ) ! = Some ( & ';' )
& & chars . peek ( ) ! = Some ( & 'm' )
{
let c = chars . next ( ) . unwrap ( ) ;
accum * = 10 ;
accum + = c as u8 - 0x30 ;
}
* * val = accum ;
match chars . peek ( ) {
Some ( & 'm' ) = > {
break 'rgb_bg ;
}
Some ( & ';' ) = > {
chars . next ( ) ;
}
_ = > return None ,
}
}
}
if chars . next ( ) ! = Some ( 'm' ) {
return None ;
}
current_bg = rgb_color ;
state = Start ;
}
_ = > unreachable! ( ) ,
}
}
}
2020-05-31 22:37:06 +03:00
max_cols = std ::cmp ::max ( row . len ( ) , max_cols ) ;
bufs . push ( row ) ;
let mut buf : Vec < Cell > = Vec ::with_capacity ( max_cols * bufs . len ( ) ) ;
for l in bufs {
let row_len = l . len ( ) ;
buf . extend ( l . into_iter ( ) ) ;
if row_len < max_cols {
for _ in row_len .. max_cols {
buf . push ( Cell ::default ( ) ) ;
}
}
}
if buf . len ( ) ! = rows * max_cols {
debug! (
" BUG: rows: {} cols: {} = {}, but buf.len() = {} " ,
rows ,
max_cols ,
rows * max_cols ,
buf . len ( )
) ;
2019-10-06 11:30:35 +03:00
}
Some ( CellBuffer {
buf ,
rows ,
2020-05-31 22:37:06 +03:00
cols : max_cols ,
2020-11-27 23:05:35 +02:00
default_cell : Cell ::default ( ) ,
2019-11-06 15:09:15 +02:00
growable : false ,
2019-10-06 11:30:35 +03:00
ascii_drawing : false ,
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 ::SmallVec ::new ( ) ,
2019-10-06 11:30:35 +03:00
} )
}
}
2019-12-01 17:04:55 +02:00
/// Use `RowIterator` to iterate the cells of a row without the need to do any bounds checking;
/// the iterator will simply return `None` when it reaches the end of the row.
/// `RowIterator` can be created via the `CellBuffer::row_iter` method and can be returned by
/// `BoundsIterator` which iterates each row.
2020-02-04 15:52:12 +02:00
/// ```no_run
2019-12-01 17:04:55 +02:00
/// for c in grid.row_iter(
2019-12-12 11:04:14 +02:00
/// x..(x + 11),
2019-12-01 17:04:55 +02:00
/// 0,
/// ) {
/// grid[c].set_ch('w');
/// }
/// ```
pub struct RowIterator {
row : usize ,
col : std ::ops ::Range < usize > ,
}
2019-12-01 17:11:13 +02:00
/// `BoundsIterator` iterates each row returning a `RowIterator`.
2020-02-04 15:52:12 +02:00
/// ```no_run
2019-12-01 17:11:13 +02:00
/// /* Visit each `Cell` in `area`. */
/// for c in grid.bounds_iter(area) {
/// grid[c].set_ch('w');
/// }
/// ```
2019-12-01 17:04:55 +02:00
pub struct BoundsIterator {
rows : std ::ops ::Range < usize > ,
cols : ( usize , usize ) ,
}
impl Iterator for BoundsIterator {
type Item = RowIterator ;
fn next ( & mut self ) -> Option < Self ::Item > {
if let Some ( next_row ) = self . rows . next ( ) {
Some ( RowIterator {
row : next_row ,
2019-12-06 12:33:19 +02:00
col : self . cols . 0 .. self . cols . 1 ,
2019-12-01 17:04:55 +02:00
} )
} else {
None
}
}
}
impl Iterator for RowIterator {
type Item = ( usize , usize ) ;
fn next ( & mut self ) -> Option < Self ::Item > {
if let Some ( next_col ) = self . col . next ( ) {
Some ( ( next_col , self . row ) )
} else {
None
}
}
}
impl RowIterator {
pub fn forward_col ( mut self , new_val : usize ) -> Self {
if self . col . start > new_val {
self
} else if self . col . end < = new_val {
self . col . start = self . col . end ;
self
} else {
self . col . start = new_val ;
self
}
}
}
2020-02-06 21:51:13 +02:00
pub use boundaries ::create_box ;
pub mod boundaries {
use super ::* ;
pub ( crate ) const HORZ_BOUNDARY : char = '─' ;
pub ( crate ) const VERT_BOUNDARY : char = '│' ;
pub ( crate ) const _TOP_LEFT_CORNER : char = '┌' ;
pub ( crate ) const _TOP_RIGHT_CORNER : char = '┐' ;
pub ( crate ) const _BOTTOM_LEFT_CORNER : char = '└' ;
pub ( crate ) const _BOTTOM_RIGHT_CORNER : char = '┘' ;
pub ( crate ) const LIGHT_VERTICAL_AND_RIGHT : char = '├' ;
pub ( crate ) const _LIGHT_VERTICAL_AND_LEFT : char = '┤' ;
2020-06-23 12:48:32 +03:00
pub ( crate ) const _LIGHT_DOWN_AND_HORIZONTAL : char = '┬' ;
pub ( crate ) const _LIGHT_UP_AND_HORIZONTAL : char = '┴' ;
2020-02-06 21:51:13 +02:00
pub ( crate ) const _DOUBLE_DOWN_AND_RIGHT : char = '╔' ;
pub ( crate ) const _DOUBLE_DOWN_AND_LEFT : char = '╗' ;
pub ( crate ) const _DOUBLE_UP_AND_LEFT : char = '╝' ;
pub ( crate ) const _DOUBLE_UP_AND_RIGHT : char = '╚' ;
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 ) ) ;
cell . set_fg ( Color ::Byte ( 240 ) ) ;
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 ) ) ;
cell . set_fg ( Color ::Byte ( 240 ) ) ;
} 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 ) ) ;
cell . set_fg ( Color ::Byte ( 240 ) ) ;
} 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 ) ) ;
cell . set_fg ( Color ::Byte ( 240 ) ) ;
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 ) ) ;
cell . set_fg ( Color ::Byte ( 240 ) ) ;
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 ) ) ;
cell . set_fg ( Color ::Byte ( 240 ) ) ;
} 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 ) ) ;
cell . set_fg ( Color ::Byte ( 240 ) ) ;
} else {
bin_set & = 0b1110 ;
}
}
break ;
}
if bin_set = = 0 {
bin_set = 0b0101 ;
}
bin_set
}
pub ( crate ) enum BoxBoundary {
Horizontal ,
Vertical ,
}
pub ( crate ) fn set_and_join_box ( grid : & mut CellBuffer , idx : Pos , ch : BoxBoundary ) {
/* Connected sides:
*
* 1
* 2 c 0
* 3
*
* #3210
* 0b____
* /
if grid . ascii_drawing {
grid [ idx ] . set_ch ( match ch {
BoxBoundary ::Vertical = > '|' ,
BoxBoundary ::Horizontal = > '-' ,
} ) ;
grid [ idx ] . set_fg ( Color ::Byte ( 240 ) ) ;
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 ) ) ;
grid [ idx ] . set_fg ( Color ::Byte ( 240 ) ) ;
}
/// Puts boundaries in `area`.
/// Returns the inner area of the created box.
pub fn create_box ( grid : & mut CellBuffer , area : Area ) -> Area {
if ! is_valid_area! ( area ) {
return ( ( 0 , 0 ) , ( 0 , 0 ) ) ;
}
let upper_left = upper_left! ( area ) ;
let bottom_right = bottom_right! ( area ) ;
if ! grid . ascii_drawing {
for x in get_x ( upper_left ) .. get_x ( bottom_right ) {
2020-02-22 23:18:56 +02:00
grid [ ( x , get_y ( upper_left ) ) ]
. set_ch ( HORZ_BOUNDARY )
. set_fg ( Color ::Byte ( 240 ) ) ;
grid [ ( x , get_y ( bottom_right ) ) ]
. set_ch ( HORZ_BOUNDARY )
. set_fg ( Color ::Byte ( 240 ) ) ;
2020-02-06 21:51:13 +02:00
}
for y in get_y ( upper_left ) .. get_y ( bottom_right ) {
2020-02-22 23:18:56 +02:00
grid [ ( get_x ( upper_left ) , y ) ]
. set_ch ( VERT_BOUNDARY )
. set_fg ( Color ::Byte ( 240 ) ) ;
grid [ ( get_x ( bottom_right ) , y ) ]
. set_ch ( VERT_BOUNDARY )
. set_fg ( Color ::Byte ( 240 ) ) ;
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 ) ;
}
(
(
std ::cmp ::min (
get_x ( upper_left ) + 2 ,
std ::cmp ::min ( get_x ( upper_left ) + 1 , get_x ( bottom_right ) ) ,
) ,
std ::cmp ::min (
get_y ( upper_left ) + 2 ,
std ::cmp ::min ( get_y ( upper_left ) + 1 , get_y ( bottom_right ) ) ,
) ,
) ,
(
std ::cmp ::max (
get_x ( bottom_right ) . saturating_sub ( 2 ) ,
std ::cmp ::max ( get_x ( bottom_right ) . saturating_sub ( 1 ) , get_x ( upper_left ) ) ,
) ,
std ::cmp ::max (
get_y ( bottom_right ) . saturating_sub ( 2 ) ,
std ::cmp ::max ( get_y ( bottom_right ) . saturating_sub ( 1 ) , get_y ( upper_left ) ) ,
) ,
) ,
)
}
}
2020-02-26 12:25:57 +02:00
use melib ::text_processing ::search ::KMP ;
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
}
}
#[ test ]
fn test_cellbuffer_search ( ) {
use melib ::text_processing ::{ Reflow , TextProcessing , _ALICE_CHAPTER_1 } ;
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 ( ) {
write_string_to_grid (
l ,
& mut buf ,
Color ::Default ,
Color ::Default ,
2020-04-05 12:04:25 +03:00
Attr ::DEFAULT ,
2020-02-26 12:25:57 +02:00
( ( 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 ( ) ) ;
}
2020-07-05 15:28:55 +03:00
println! ( ) ;
2020-02-26 12:25:57 +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
#[ 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
impl core ::cmp ::Ord for FormatTag {
fn cmp ( & self , other : & Self ) -> core ::cmp ::Ordering {
self . priority . cmp ( & other . priority )
}
}
impl core ::cmp ::PartialOrd for FormatTag {
fn partial_cmp ( & self , other : & Self ) -> Option < core ::cmp ::Ordering > {
Some ( self . cmp ( & other ) )
}
}
2020-10-14 12:50:38 +03:00
#[ derive(Debug, Copy, Hash, Clone, PartialEq, Eq) ]
pub enum WidgetWidth {
Unset ,
Hold ( usize ) ,
Set ( usize ) ,
}