use std::fmt::{Debug, Formatter, Result};
use smallvec::smallvec;
use super::{history::EditorHistory, render_cache::RenderCache, sizing, SelectionList};
use crate::{caret_locate,
format_as_kilobytes_with_commas,
glyphs,
height,
inline_string,
row,
validate_buffer_mut::{EditorBufferMutNoDrop, EditorBufferMutWithDrop},
width,
with_mut,
CaretRaw,
CaretScrAdj,
ColWidth,
GCString,
GCStringExt,
InlineString,
RowHeight,
RowIndex,
ScrOfs,
SegString,
Size,
TinyInlineString,
DEBUG_TUI_COPY_PASTE,
DEBUG_TUI_MOD,
DEFAULT_SYN_HI_FILE_EXT};
#[derive(Clone, PartialEq, Default)]
pub struct EditorBuffer {
pub content: EditorContent,
pub history: EditorHistory,
pub render_cache: RenderCache,
}
#[derive(Clone, PartialEq, Default)]
pub struct EditorContent {
pub lines: sizing::VecEditorContentLines,
pub caret_raw: CaretRaw,
pub scr_ofs: ScrOfs,
pub maybe_file_extension: Option<TinyInlineString>,
pub maybe_file_path: Option<InlineString>,
pub sel_list: SelectionList,
}
mod construct {
use super::*;
impl EditorBuffer {
pub fn new_empty(
maybe_file_extension: Option<&str>,
maybe_file_path: Option<&str>,
) -> Self {
let it = Self {
content: EditorContent {
lines: { smallvec!["".grapheme_string()] },
maybe_file_extension: maybe_file_extension.map(|it| it.into()),
maybe_file_path: maybe_file_path.map(|it| it.into()),
..Default::default()
},
..Default::default()
};
DEBUG_TUI_MOD.then(|| {
tracing::info!(
message = %inline_string!("Construct EditorBuffer {ch}", ch = glyphs::CONSTRUCT_GLYPH),
file_extension = ?maybe_file_extension,
file_path = ?maybe_file_path
);
});
it
}
}
}
pub mod versions {
use super::*;
impl EditorBuffer {
pub fn add(&mut self) {
self.render_cache.clear();
let content_copy = self.content.clone();
self.history.add(content_copy);
DEBUG_TUI_COPY_PASTE.then(|| {
tracing::debug!(
message = "🍎🍎🍎 add_content_to_undo_stack buffer",
buffer = ?self
);
});
}
pub fn undo(&mut self) {
self.render_cache.clear();
if let Some(content) = self.history.undo() {
self.content = content;
}
DEBUG_TUI_COPY_PASTE.then(|| {
tracing::debug!(
message = "🍎🍎🍎 undo buffer",
buffer = ?self
);
});
}
pub fn redo(&mut self) {
self.render_cache.clear();
if let Some(content) = self.history.redo() {
self.content = content;
}
DEBUG_TUI_COPY_PASTE.then(|| {
tracing::debug!(message = "🍎🍎🍎 redo buffer",
buffer = ?self
);
});
}
}
}
pub mod content_display_width {
use super::*;
impl EditorBuffer {
pub fn get_max_row_index(&self) -> RowIndex {
height(self.get_lines().len()).convert_to_row_index()
}
pub fn get_line_display_width_at_caret_scr_adj(&self) -> ColWidth {
Self::impl_get_line_display_width_at_caret_scr_adj(
self.get_caret_raw(),
self.get_scr_ofs(),
self.get_lines(),
)
}
pub fn impl_get_line_display_width_at_caret_scr_adj(
caret_raw: CaretRaw,
scr_ofs: ScrOfs,
lines: &sizing::VecEditorContentLines,
) -> ColWidth {
let caret_scr_adj = caret_raw + scr_ofs;
let row_index = caret_scr_adj.row_index;
let maybe_line_gcs = lines.get(row_index.as_usize());
if let Some(line_gcs) = maybe_line_gcs {
line_gcs.display_width
} else {
width(0)
}
}
pub fn get_line_display_width_at_row_index(
&self,
row_index: RowIndex,
) -> ColWidth {
Self::impl_get_line_display_width_at_row_index(row_index, self.get_lines())
}
pub fn impl_get_line_display_width_at_row_index(
row_index: RowIndex,
lines: &sizing::VecEditorContentLines,
) -> ColWidth {
let maybe_line_gcs = lines.get(row_index.as_usize());
if let Some(line_gcs) = maybe_line_gcs {
line_gcs.display_width
} else {
width(0)
}
}
}
}
pub mod content_near_caret {
use super::*;
impl EditorBuffer {
pub fn line_at_caret_is_empty(&self) -> bool {
self.get_line_display_width_at_caret_scr_adj() == width(0)
}
pub fn line_at_caret_scr_adj(&self) -> Option<&GCString> {
if self.is_empty() {
return None;
}
let row_index_scr_adj = self.get_caret_scr_adj().row_index;
let line = self.get_lines().get(row_index_scr_adj.as_usize())?;
Some(line)
}
pub fn string_at_end_of_line_at_caret_scr_adj(&self) -> Option<SegString> {
if self.is_empty() {
return None;
}
let line = self.line_at_caret_scr_adj()?;
if let caret_locate::CaretColLocationInLine::AtEnd =
caret_locate::locate_col(self)
{
let maybe_last_seg_string = line.get_string_at_end();
return maybe_last_seg_string;
}
None
}
pub fn string_to_right_of_caret(&self) -> Option<SegString> {
if self.is_empty() {
return None;
}
let line = self.line_at_caret_scr_adj()?;
match caret_locate::locate_col(self) {
caret_locate::CaretColLocationInLine::AtEnd => line.get_string_at_end(),
_ => line.get_string_at_right_of(self.get_caret_scr_adj().col_index),
}
}
pub fn string_to_left_of_caret(&self) -> Option<SegString> {
if self.is_empty() {
return None;
}
let line = self.line_at_caret_scr_adj()?;
match caret_locate::locate_col(self) {
caret_locate::CaretColLocationInLine::AtEnd => line.get_string_at_end(),
_ => line.get_string_at_left_of(self.get_caret_scr_adj().col_index),
}
}
pub fn prev_line_above_caret(&self) -> Option<&GCString> {
if self.is_empty() {
return None;
}
let row_index_scr_adj = self.get_caret_scr_adj().row_index;
if row_index_scr_adj == row(0) {
return None;
}
let line = self
.get_lines()
.get((row_index_scr_adj - row(1)).as_usize())?;
Some(line)
}
pub fn string_at_caret(&self) -> Option<SegString> {
if self.is_empty() {
return None;
}
let line = self.line_at_caret_scr_adj()?;
let caret_str_adj_col_index = self.get_caret_scr_adj().col_index;
let seg_string = line.get_string_at(caret_str_adj_col_index)?;
Some(seg_string)
}
pub fn next_line_below_caret_to_string(&self) -> Option<&GCString> {
if self.is_empty() {
return None;
}
let caret_scr_adj_row_index = self.get_caret_scr_adj().row_index;
let next_line_row_index = caret_scr_adj_row_index + row(1);
let line = self.get_lines().get(next_line_row_index.as_usize())?;
Some(line)
}
}
}
pub mod access_and_mutate {
use super::*;
impl EditorBuffer {
pub fn is_file_extension_default(&self) -> bool {
match self.content.maybe_file_extension {
Some(ref ext) => ext == DEFAULT_SYN_HI_FILE_EXT,
None => false,
}
}
pub fn has_file_extension(&self) -> bool {
self.content.maybe_file_extension.is_some()
}
pub fn get_maybe_file_extension(&self) -> Option<&str> {
match self.content.maybe_file_extension {
Some(ref s) => Some(s.as_str()),
None => None,
}
}
pub fn is_empty(&self) -> bool { self.content.lines.is_empty() }
pub fn line_at_row_index(&self, row_index: RowIndex) -> Option<&GCString> {
self.content.lines.get(row_index.as_usize())
}
pub fn len(&self) -> RowHeight { height(self.content.lines.len()) }
pub fn get_lines(&self) -> &sizing::VecEditorContentLines { &self.content.lines }
pub fn get_as_string_with_comma_instead_of_newlines(&self) -> InlineString {
self.get_as_string_with_separator(", ")
}
pub fn get_as_string_with_newlines(&self) -> InlineString {
self.get_as_string_with_separator("\n")
}
pub fn get_as_string_with_separator(&self, separator: &str) -> InlineString {
with_mut!(
InlineString::new(),
as acc,
run {
let lines = &self.content.lines;
for (index, line) in lines.iter().enumerate() {
if index > 0 {
acc.push_str(separator);
}
acc.push_str(&line.string);
}
}
)
}
pub fn set_lines<'a>(&mut self, arg_lines: impl IntoIterator<Item = &'a str>) {
self.content.lines.clear();
for line in arg_lines {
self.content.lines.push(line.grapheme_string());
}
self.content.caret_raw = CaretRaw::default();
self.content.scr_ofs = ScrOfs::default();
self.render_cache.clear();
self.history.clear();
}
pub fn get_caret_raw(&self) -> CaretRaw { self.content.caret_raw }
pub fn get_caret_scr_adj(&self) -> CaretScrAdj {
self.content.caret_raw + self.content.scr_ofs
}
pub fn get_scr_ofs(&self) -> ScrOfs { self.content.scr_ofs }
pub fn get_mut(&mut self, vp: Size) -> EditorBufferMutWithDrop<'_> {
EditorBufferMutWithDrop::new(
&mut self.content.lines,
&mut self.content.caret_raw,
&mut self.content.scr_ofs,
&mut self.content.sel_list,
vp,
)
}
pub fn get_mut_no_drop(&mut self, vp: Size) -> EditorBufferMutNoDrop<'_> {
EditorBufferMutNoDrop::new(
&mut self.content.lines,
&mut self.content.caret_raw,
&mut self.content.scr_ofs,
&mut self.content.sel_list,
vp,
)
}
pub fn has_selection(&self) -> bool { !self.content.sel_list.is_empty() }
pub fn clear_selection(&mut self) { self.content.sel_list.clear(); }
pub fn get_selection_list(&self) -> &SelectionList { &self.content.sel_list }
}
}
mod debug_format {
use super::*;
impl Debug for EditorBuffer {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write! {
f,
"EditorBuffer [
- content: {content:?}
- history: {history:?}
]",
content = self.content,
history = self.history,
}
}
}
impl Debug for EditorContent {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
use crate::GetMemSize as _;
let mem_size = self.get_mem_size();
let mem_size_fmt = format_as_kilobytes_with_commas(mem_size);
write! {
f,
"EditorContent [
- lines: {lines}, size: {size}
- selection_map: {map}
- ext: {ext:?}, path:{path:?}, caret: {caret:?}, scroll_offset: {scroll:?}
]",
lines = self.lines.len(),
size = mem_size_fmt,
ext = self.maybe_file_extension,
caret = self.caret_raw,
map = self.sel_list.to_formatted_string(),
scroll = self.scr_ofs,
path = self.maybe_file_path,
}
}
}
}