use crate::prelude::*;
#[allow(unused_imports)]
use crate::{arc_impl, lock};
pub use crate::buf::cidx::ColumnIndex;
pub use crate::buf::opt::*;
use ahash::RandomState;
use compact_str::CompactString;
use lru::LruCache;
use paste::paste;
use path_absolutize::Absolutize;
use ropey::{Rope, RopeBuilder};
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::fs::Metadata;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::sync::atomic::{AtomicI32, Ordering};
use std::time::Instant;
use tracing::trace;
pub mod cidx;
pub mod opt;
pub mod unicode;
pub type BufferId = i32;
pub fn next_buffer_id() -> BufferId {
static VALUE: AtomicI32 = AtomicI32::new(1);
VALUE.fetch_add(1, Ordering::Relaxed)
}
#[derive(Debug)]
pub struct Buffer {
id: BufferId,
rope: Rope,
cached_lines_width: Rc<RefCell<LruCache<usize, ColumnIndex, RandomState>>>,
options: BufferLocalOptions,
filename: Option<PathBuf>,
absolute_filename: Option<PathBuf>,
metadata: Option<Metadata>,
last_sync_time: Option<Instant>,
}
arc_impl!(Buffer);
#[inline]
fn get_cached_size(canvas_height: u16) -> std::num::NonZeroUsize {
std::num::NonZeroUsize::new(canvas_height as usize * 2 + 3).unwrap()
}
impl Buffer {
pub fn _new(
canvas_height: u16,
rope: Rope,
options: BufferLocalOptions,
filename: Option<PathBuf>,
absolute_filename: Option<PathBuf>,
metadata: Option<Metadata>,
last_sync_time: Option<Instant>,
) -> Self {
let cache_size = get_cached_size(canvas_height);
Self {
id: next_buffer_id(),
rope,
cached_lines_width: Rc::new(RefCell::new(LruCache::with_hasher(
cache_size,
RandomState::new(),
))),
options,
filename,
absolute_filename,
metadata,
last_sync_time,
}
}
#[cfg(debug_assertions)]
pub fn _new_empty(canvas_height: u16, options: BufferLocalOptions) -> Self {
let cache_size = get_cached_size(canvas_height);
Self {
id: next_buffer_id(),
rope: Rope::new(),
cached_lines_width: Rc::new(RefCell::new(LruCache::with_hasher(
cache_size,
RandomState::new(),
))),
options,
filename: None,
absolute_filename: None,
metadata: None,
last_sync_time: None,
}
}
pub fn id(&self) -> BufferId {
self.id
}
pub fn filename(&self) -> &Option<PathBuf> {
&self.filename
}
pub fn set_filename(&mut self, filename: Option<PathBuf>) {
self.filename = filename;
}
pub fn absolute_filename(&self) -> &Option<PathBuf> {
&self.absolute_filename
}
pub fn set_absolute_filename(&mut self, absolute_filename: Option<PathBuf>) {
self.absolute_filename = absolute_filename;
}
pub fn metadata(&self) -> &Option<Metadata> {
&self.metadata
}
pub fn set_metadata(&mut self, metadata: Option<Metadata>) {
self.metadata = metadata;
}
pub fn last_sync_time(&self) -> &Option<Instant> {
&self.last_sync_time
}
pub fn set_last_sync_time(&mut self, last_sync_time: Option<Instant>) {
self.last_sync_time = last_sync_time;
}
}
impl Buffer {
pub fn char_width(&self, c: char) -> usize {
unicode::char_width(&self.options, c)
}
pub fn char_symbol(&self, c: char) -> (CompactString, usize) {
unicode::char_symbol(&self.options, c)
}
}
impl Buffer {
pub fn get_rope(&self) -> &Rope {
&self.rope
}
pub fn get_rope_mut(&mut self) -> &mut Rope {
&mut self.rope
}
pub fn clone_line(
&self,
line_idx: usize,
start_char_idx: usize,
max_chars: usize,
) -> Option<String> {
match self.rope.get_line(line_idx) {
Some(bufline) => match bufline.get_chars_at(start_char_idx) {
Some(chars_iter) => {
let mut builder = String::with_capacity(max_chars);
for (i, c) in chars_iter.enumerate() {
if i >= max_chars {
return Some(builder);
}
builder.push(c);
}
Some(builder)
}
None => None,
},
None => None,
}
}
pub fn last_char_on_line(&self, line_idx: usize) -> Option<usize> {
match self.rope.get_line(line_idx) {
Some(line) => {
let line_len_chars = line.len_chars();
if line_len_chars > 0 {
Some(line_len_chars - 1)
} else {
None
}
}
None => None,
}
}
pub fn last_char_on_line_no_empty_eol(&self, line_idx: usize) -> Option<usize> {
match self.rope.get_line(line_idx) {
Some(line) => match self.last_char_on_line(line_idx) {
Some(last_char) => {
if self.char_width(line.char(last_char)) == 0 {
Some(last_char.saturating_sub(1))
} else {
Some(last_char)
}
}
None => None,
},
None => None,
}
}
pub fn is_empty_eol(&self, line_idx: usize, char_idx: usize) -> bool {
match self.rope.get_line(line_idx) {
Some(line) => {
if char_idx == line.len_chars().saturating_sub(1) {
match line.get_char(char_idx) {
Some(c) => self.char_width(c) == 0,
None => false,
}
} else {
false
}
}
None => false,
}
}
}
impl Buffer {
pub fn options(&self) -> &BufferLocalOptions {
&self.options
}
pub fn set_options(&mut self, options: &BufferLocalOptions) {
self.options = *options;
}
}
impl Buffer {
pub fn width_before(&self, line_idx: usize, char_idx: usize) -> usize {
let rope_line = self.rope.line(line_idx);
self
.cached_lines_width
.borrow_mut()
.get_or_insert_mut(line_idx, || -> ColumnIndex {
ColumnIndex::with_capacity(rope_line.len_chars())
})
.width_before(&self.options, &rope_line, char_idx)
}
pub fn width_until(&self, line_idx: usize, char_idx: usize) -> usize {
let rope_line = self.rope.line(line_idx);
self
.cached_lines_width
.borrow_mut()
.get_or_insert_mut(line_idx, || -> ColumnIndex {
ColumnIndex::with_capacity(rope_line.len_chars())
})
.width_until(&self.options, &rope_line, char_idx)
}
pub fn char_before(&self, line_idx: usize, width: usize) -> Option<usize> {
let rope_line = self.rope.line(line_idx);
self
.cached_lines_width
.borrow_mut()
.get_or_insert_mut(line_idx, || -> ColumnIndex {
ColumnIndex::with_capacity(rope_line.len_chars())
})
.char_before(&self.options, &rope_line, width)
}
pub fn char_at(&self, line_idx: usize, width: usize) -> Option<usize> {
let rope_line = self.rope.line(line_idx);
self
.cached_lines_width
.borrow_mut()
.get_or_insert_mut(line_idx, || -> ColumnIndex {
ColumnIndex::with_capacity(rope_line.len_chars())
})
.char_at(&self.options, &rope_line, width)
}
pub fn char_after(&self, line_idx: usize, width: usize) -> Option<usize> {
let rope_line = self.rope.line(line_idx);
self
.cached_lines_width
.borrow_mut()
.get_or_insert_mut(line_idx, || -> ColumnIndex {
ColumnIndex::with_capacity(rope_line.len_chars())
})
.char_after(&self.options, &rope_line, width)
}
pub fn last_char_until(&self, line_idx: usize, width: usize) -> Option<usize> {
let rope_line = self.rope.line(line_idx);
self
.cached_lines_width
.borrow_mut()
.get_or_insert_mut(line_idx, || -> ColumnIndex {
ColumnIndex::with_capacity(rope_line.len_chars())
})
.last_char_until(&self.options, &rope_line, width)
}
pub fn truncate_cached_line_since_char(&self, line_idx: usize, char_idx: usize) {
self
.cached_lines_width
.borrow_mut()
.get_or_insert_mut(line_idx, || -> ColumnIndex {
let rope_line = self.rope.line(line_idx);
ColumnIndex::with_capacity(rope_line.len_chars())
})
.truncate_since_char(char_idx)
}
pub fn truncate_cached_line_since_width(&self, line_idx: usize, width: usize) {
self
.cached_lines_width
.borrow_mut()
.get_or_insert_mut(line_idx, || -> ColumnIndex {
let rope_line = self.rope.line(line_idx);
ColumnIndex::with_capacity(rope_line.len_chars())
})
.truncate_since_width(width)
}
pub fn remove_cached_line(&self, line_idx: usize) {
self.cached_lines_width.borrow_mut().pop(&line_idx);
}
pub fn retain_cached_lines<F>(&self, f: F)
where
F: Fn(/* line_idx */ &usize, /* column_idx */ &ColumnIndex) -> bool,
{
let mut cached_width = self.cached_lines_width.borrow_mut();
let to_be_removed_lines: Vec<usize> = cached_width
.iter()
.filter(|(line_idx, column_idx)| !f(line_idx, column_idx))
.map(|(line_idx, _)| *line_idx)
.collect();
for line_idx in to_be_removed_lines.iter() {
cached_width.pop(line_idx);
}
}
pub fn clear_cached_lines(&self) {
self.cached_lines_width.borrow_mut().clear()
}
pub fn resize_cached_lines(&self, canvas_height: u16) {
let new_cache_size = get_cached_size(canvas_height);
let mut cached_width = self.cached_lines_width.borrow_mut();
if new_cache_size > cached_width.cap() {
cached_width.resize(new_cache_size);
}
}
}
#[derive(Debug, Clone)]
pub struct BuffersManager {
buffers: BTreeMap<BufferId, BufferArc>,
buffers_by_path: HashMap<Option<PathBuf>, BufferArc>,
global_local_options: BufferLocalOptions,
}
arc_impl!(BuffersManager);
pub type BuffersManagerKeys<'a> = std::collections::btree_map::Keys<'a, BufferId, BufferArc>;
pub type BuffersManagerValues<'a> = std::collections::btree_map::Values<'a, BufferId, BufferArc>;
pub type BuffersManagerIter<'a> = std::collections::btree_map::Iter<'a, BufferId, BufferArc>;
impl BuffersManager {
pub fn new() -> Self {
BuffersManager {
buffers: BTreeMap::new(),
buffers_by_path: HashMap::new(),
global_local_options: BufferLocalOptionsBuilder::default().build().unwrap(),
}
}
pub fn new_file_buffer(&mut self, canvas_height: u16, filename: &Path) -> IoResult<BufferId> {
let abs_filename = match filename.absolutize() {
Ok(abs_filename) => abs_filename.to_path_buf(),
Err(e) => {
trace!("Failed to absolutize filepath {:?}:{:?}", filename, e);
return Err(e);
}
};
debug_assert!(
!self
.buffers_by_path
.contains_key(&Some(abs_filename.clone()))
);
let existed = match std::fs::exists(abs_filename.clone()) {
Ok(existed) => existed,
Err(e) => {
trace!("Failed to detect file {:?}:{:?}", filename, e);
return Err(e);
}
};
let buf = if existed {
match self.edit_file(canvas_height, filename, &abs_filename) {
Ok(buf) => buf,
Err(e) => {
return Err(e);
}
}
} else {
Buffer::_new(
canvas_height,
Rope::new(),
*self.global_local_options(),
Some(filename.to_path_buf()),
Some(abs_filename.clone()),
None,
None,
)
};
let buf_id = buf.id();
let buf = Buffer::to_arc(buf);
self.buffers.insert(buf_id, buf.clone());
self.buffers_by_path.insert(Some(abs_filename), buf);
Ok(buf_id)
}
pub fn new_empty_buffer(&mut self, canvas_height: u16) -> BufferId {
debug_assert!(!self.buffers_by_path.contains_key(&None));
let buf = Buffer::_new(
canvas_height,
Rope::new(),
*self.global_local_options(),
None,
None,
None,
None,
);
let buf_id = buf.id();
let buf = Buffer::to_arc(buf);
self.buffers.insert(buf_id, buf.clone());
self.buffers_by_path.insert(None, buf);
buf_id
}
#[cfg(debug_assertions)]
pub fn _add_buffer(&mut self, buf: BufferArc) -> BufferId {
let (buf_id, abs_filepath) = {
let buf = lock!(buf);
(buf.id(), buf.absolute_filename().clone())
};
self.buffers.insert(buf_id, buf.clone());
if abs_filepath.is_none() {
assert!(!self.buffers_by_path.contains_key(&None));
}
self.buffers_by_path.insert(abs_filepath, buf);
buf_id
}
}
impl BuffersManager {
fn to_rope(&self, buf: &[u8], bufsize: usize) -> Rope {
let bufstr = self.to_str(buf, bufsize);
let mut block = RopeBuilder::new();
block.append(&bufstr.to_owned());
block.finish()
}
fn to_str(&self, buf: &[u8], bufsize: usize) -> String {
let fencoding = self.global_local_options().file_encoding();
match fencoding {
FileEncodingOption::Utf8 => String::from_utf8_lossy(&buf[0..bufsize]).into_owned(),
}
}
fn edit_file(
&self,
canvas_height: u16,
filename: &Path,
absolute_filename: &Path,
) -> IoResult<Buffer> {
match std::fs::File::open(filename) {
Ok(fp) => {
let metadata = match fp.metadata() {
Ok(metadata) => metadata,
Err(e) => {
trace!("Failed to fetch metadata from file {:?}:{:?}", filename, e);
return Err(e);
}
};
let mut buf: Vec<u8> = Vec::new();
let mut reader = std::io::BufReader::new(fp);
let bytes = match reader.read_to_end(&mut buf) {
Ok(bytes) => bytes,
Err(e) => {
trace!("Failed to read file {:?}:{:?}", filename, e);
return Err(e);
}
};
trace!(
"Read {} bytes (buf: {}) from file {:?}",
bytes,
buf.len(),
filename
);
debug_assert!(bytes == buf.len());
Ok(Buffer::_new(
canvas_height,
self.to_rope(&buf, buf.len()),
*self.global_local_options(),
Some(filename.to_path_buf()),
Some(absolute_filename.to_path_buf()),
Some(metadata),
Some(Instant::now()),
))
}
Err(e) => {
trace!("Failed to open file {:?}:{:?}", filename, e);
Err(e)
}
}
}
}
impl BuffersManager {
pub fn is_empty(&self) -> bool {
self.buffers.is_empty()
}
pub fn len(&self) -> usize {
self.buffers.len()
}
pub fn remove(&mut self, id: &BufferId) -> Option<BufferArc> {
self.buffers.remove(id)
}
pub fn get(&self, id: &BufferId) -> Option<&BufferArc> {
self.buffers.get(id)
}
pub fn contains_key(&self, id: &BufferId) -> bool {
self.buffers.contains_key(id)
}
pub fn keys(&self) -> BuffersManagerKeys {
self.buffers.keys()
}
pub fn values(&self) -> BuffersManagerValues {
self.buffers.values()
}
pub fn iter(&self) -> BuffersManagerIter {
self.buffers.iter()
}
pub fn first_key_value(&self) -> Option<(&BufferId, &BufferArc)> {
self.buffers.first_key_value()
}
pub fn last_key_value(&self) -> Option<(&BufferId, &BufferArc)> {
self.buffers.last_key_value()
}
}
impl Default for BuffersManager {
fn default() -> Self {
BuffersManager::new()
}
}
impl BuffersManager {
pub fn global_local_options(&self) -> &BufferLocalOptions {
&self.global_local_options
}
pub fn global_local_options_mut(&mut self) -> &mut BufferLocalOptions {
&mut self.global_local_options
}
pub fn set_global_local_options(&mut self, options: &BufferLocalOptions) {
self.global_local_options = *options;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn next_buffer_id1() {
assert!(next_buffer_id() > 0);
}
}