use crate::buf::opt::BufferOptions;
use crate::buf::unicode;
use crate::prelude::*;
use ropey::RopeSlice;
use smallvec::SmallVec;
#[derive(Debug, Default, Clone)]
pub struct ColumnIndex {
char2width: SmallVec<[usize; 80]>,
width2char: BTreeMap<usize, usize>,
}
impl ColumnIndex {
pub fn with_capacity(size: usize) -> Self {
Self {
char2width: SmallVec::with_capacity(size),
width2char: BTreeMap::new(),
}
}
pub fn new() -> Self {
Self {
char2width: SmallVec::new(),
width2char: BTreeMap::new(),
}
}
#[cfg(not(test))]
fn _internal_check(&self) {}
#[cfg(test)]
fn _internal_check(&self) {
debug_assert!(self.char2width.len() >= self.width2char.len());
let mut last_width: Option<usize> = None;
for (i, w) in self.char2width.iter().enumerate() {
match last_width {
Some(last_width1) => {
debug_assert!(*w >= last_width1);
}
None => { }
}
last_width = Some(*w);
debug_assert!(self.width2char.contains_key(w));
let c = self.width2char[w];
debug_assert!(i >= c);
}
}
fn _build_cache(
&mut self,
options: &BufferOptions,
buf_line: &RopeSlice,
char_idx_bound: Option<usize>,
width_bound: Option<usize>,
) {
let n = buf_line.len_chars();
let start_idx = self.char2width.len();
let mut prefix: usize = if start_idx == 0 {
0_usize
} else {
self.char2width[start_idx - 1]
};
let mut rope_chars = buf_line.chars_at(start_idx);
for i in start_idx..n {
let c = rope_chars.next().unwrap();
prefix += unicode::char_width(options, c);
self.char2width.push(prefix);
let c = self.char2width.len() - 1;
debug_assert_eq!(i, c);
match self.width2char.get(&prefix) {
Some(c1) => {
if *c1 > c {
self.width2char.insert(prefix, c);
}
}
None => {
self.width2char.insert(prefix, c);
}
}
if let Some(char_idx) = char_idx_bound {
if i > char_idx {
return;
}
}
if let Some(width) = width_bound {
if prefix > width {
return;
}
}
}
}
fn _build_cache_until_char(
&mut self,
options: &BufferOptions,
buf_line: &RopeSlice,
char_idx: usize,
) {
self._build_cache(options, buf_line, Some(char_idx), None);
}
pub fn width_before(
&mut self,
options: &BufferOptions,
buf_line: &RopeSlice,
char_idx: usize,
) -> usize {
self._build_cache_until_char(options, buf_line, char_idx);
self._internal_check();
if char_idx == 0 {
0
} else if self.char2width.is_empty() {
debug_assert_eq!(buf_line.len_chars(), 0);
0
} else {
debug_assert!(!self.char2width.is_empty());
debug_assert!(buf_line.len_chars() > 0);
if char_idx - 1 < self.char2width.len() {
self.char2width[char_idx - 1]
} else {
self.char2width[self.char2width.len() - 1]
}
}
}
pub fn width_until(
&mut self,
options: &BufferOptions,
buf_line: &RopeSlice,
char_idx: usize,
) -> usize {
self._build_cache_until_char(options, buf_line, char_idx);
self._internal_check();
if self.char2width.is_empty() {
debug_assert_eq!(buf_line.len_chars(), 0);
0
} else {
debug_assert!(!self.char2width.is_empty());
debug_assert!(buf_line.len_chars() > 0);
if char_idx < self.char2width.len() {
self.char2width[char_idx]
} else {
self.char2width[self.char2width.len() - 1]
}
}
}
fn _build_cache_until_width(
&mut self,
options: &BufferOptions,
buf_line: &RopeSlice,
width: usize,
) {
self._build_cache(options, buf_line, None, Some(width));
}
pub fn char_before(
&mut self,
options: &BufferOptions,
buf_line: &RopeSlice,
width: usize,
) -> Option<usize> {
self._build_cache_until_width(options, buf_line, width);
self._internal_check();
if width == 0 {
None
} else if self.width2char.is_empty() {
debug_assert_eq!(buf_line.len_chars(), 0);
None
} else {
debug_assert!(!self.width2char.is_empty());
debug_assert!(buf_line.len_chars() > 0);
let (last_width, _last_char_idx) =
self.width2char.last_key_value().unwrap();
if width > *last_width {
return None;
}
for w in (1..width).rev() {
if let Some(c) = self.width2char.get(&w) {
return Some(*c);
}
}
None
}
}
pub fn char_at(
&mut self,
options: &BufferOptions,
buf_line: &RopeSlice,
width: usize,
) -> Option<usize> {
self._build_cache_until_width(options, buf_line, width);
self._internal_check();
if self.width2char.is_empty() {
debug_assert_eq!(buf_line.len_chars(), 0);
None
} else {
debug_assert!(!self.width2char.is_empty());
debug_assert!(buf_line.len_chars() > 0);
if width == 0 {
if *self.char2width.first().unwrap() == 0 {
return Some(0);
} else {
return None;
}
}
let (last_width, _last_char_idx) =
self.width2char.last_key_value().unwrap();
if width > *last_width {
return None;
}
for w in width..=*last_width {
if let Some(c) = self.width2char.get(&w) {
return Some(*c);
}
}
None
}
}
pub fn char_after(
&mut self,
options: &BufferOptions,
buf_line: &RopeSlice,
width: usize,
) -> Option<usize> {
self._build_cache_until_width(options, buf_line, width + 1);
self._internal_check();
let n = buf_line.len_chars();
if self.char2width.is_empty() {
debug_assert_eq!(n, 0);
return None;
}
if width == 0 {
return Some(0);
}
if let Some(char_idx) = self.char_at(options, buf_line, width) {
if char_idx + 1 < n {
return Some(char_idx + 1);
}
}
None
}
pub fn last_char_until(
&mut self,
options: &BufferOptions,
buf_line: &RopeSlice,
width: usize,
) -> Option<usize> {
self._build_cache_until_width(options, buf_line, width);
self._internal_check();
if width == 0 {
if *self.char2width.first().unwrap() == 0 {
return Some(0);
} else {
return None;
}
}
let (last_width, last_char_idx) = self.width2char.last_key_value().unwrap();
if width > *last_width {
return Some(*last_char_idx);
} else if let Some(char_idx) = self.char_at(options, buf_line, width) {
return Some(char_idx);
}
None
}
pub fn truncate_since_char(&mut self, char_idx: usize) {
self._internal_check();
if self.char2width.is_empty() || self.width2char.is_empty() {
debug_assert_eq!(self.char2width.is_empty(), self.width2char.is_empty());
} else if char_idx < self.char2width.len() {
self.char2width.truncate(char_idx.saturating_sub(1));
let end_char = self.char2width.len();
self.width2char.retain(|&_w, &mut c| c < end_char);
}
}
pub fn truncate_since_width(&mut self, width: usize) {
self._internal_check();
if self.char2width.is_empty() || self.width2char.is_empty() {
debug_assert_eq!(self.char2width.is_empty(), self.width2char.is_empty());
} else {
let (last_width, _last_char_idx) =
self.width2char.last_key_value().unwrap();
if width <= *last_width {
for w in (1..=width).rev() {
match self.width2char.get(&w) {
Some(c) => {
self.truncate_since_char(*c);
return;
}
None => { }
}
}
}
}
}
}