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.
async
Manos Pitsidianakis 2020-06-01 22:55:35 +03:00
parent 8c1c628c2c
commit ef0f269fbf
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
1 changed files with 81 additions and 1 deletions

View File

@ -29,6 +29,8 @@ use crate::state::Context;
use melib::text_processing::wcwidth;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use smallvec::SmallVec;
use std::collections::{HashMap, HashSet};
use std::convert::From;
use std::fmt;
use std::ops::{Deref, DerefMut, Index, IndexMut};
@ -63,6 +65,8 @@ pub struct CellBuffer {
pub ascii_drawing: bool,
/// If printing to this buffer and we run out of space, expand it.
growable: bool,
tag_table: HashMap<u64, FormatTag>,
tag_associations: SmallVec<[(u64, (usize, usize)); 128]>,
}
impl fmt::Debug for CellBuffer {
@ -97,6 +101,8 @@ impl CellBuffer {
buf: vec![cell; cols * rows],
growable: false,
ascii_drawing: false,
tag_table: Default::default(),
tag_associations: SmallVec::new(),
}
}
@ -107,6 +113,8 @@ impl CellBuffer {
buf: vec![cell; cols * rows],
growable: false,
ascii_drawing: context.settings.terminal.ascii_drawing,
tag_table: Default::default(),
tag_associations: SmallVec::new(),
}
}
@ -371,6 +379,50 @@ impl CellBuffer {
RowIterator { row, col: 0..0 }
}
}
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)));
}
}
}
impl Deref for CellBuffer {
@ -662,7 +714,7 @@ impl Default for Cell {
/// // Basic colors are also 8-bit colors (but not vice-versa).
/// assert_eq!(red.as_byte(), fancy.as_byte())
/// ```
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[derive(Hash, Debug, Copy, Clone, PartialEq, Eq)]
pub enum Color {
Black,
Red,
@ -1647,9 +1699,27 @@ pub fn copy_area(grid_dest: &mut CellBuffer, grid_src: &CellBuffer, dest: Area,
return upper_left!(dest);
}
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: HashSet<u64> = HashSet::default();
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)) {
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(tag_associations[tag_offset].1);
} else {
stack.remove(&tag_associations[tag_offset].1);
}
tag_offset += 1;
}
grid_dest[(x, y)] = grid_src[(src_x, src_y)];
for t in &stack {
grid_dest[(x, y)].attrs |= grid_src.tag_table()[&t].attrs;
}
if src_x >= get_x(bottom_right!(src)) {
break 'for_x;
}
@ -2204,6 +2274,8 @@ pub mod ansi {
cols: max_cols,
growable: false,
ascii_drawing: false,
tag_table: Default::default(),
tag_associations: smallvec::SmallVec::new(),
})
}
}
@ -2717,3 +2789,11 @@ fn test_cellbuffer_search() {
println!("");
}
}
#[derive(Debug, Default, Copy, Hash, Clone, PartialEq, Eq)]
pub struct FormatTag {
pub fg: Color,
pub bg: Color,
pub attrs: Attr,
pub priority: u8,
}