Skip to content

Commit

Permalink
[tui] Use "newtype" pattern in EditorBufferMut
Browse files Browse the repository at this point in the history
Extract the inner type as EditorBufferMut.

Provide two new types:
1. When validation is needed after mutations, using Drop.
2. When no validation needs to be performed (no Drop impl).
  • Loading branch information
nazmulidris committed Feb 19, 2025
1 parent c93aad3 commit 9f8b8f9
Show file tree
Hide file tree
Showing 9 changed files with 309 additions and 227 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
"mworld",
"nanos",
"neovim",
"newtype",
"nextest",
"Nodesource",
"notcurses",
Expand Down
66 changes: 35 additions & 31 deletions tui/src/tui/editor/editor_buffer/buffer_selection_support.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ pub fn handle_selection_single_line_caret_movement(

let buffer_mut = buffer.get_mut(width(0) + height(0));

buffer_mut.sel_list.insert(
buffer_mut.inner.sel_list.insert(
row_index,
new_range,
SelectionRange::caret_movement_direction_left_right(previous, current),
Expand Down Expand Up @@ -120,7 +120,7 @@ pub fn handle_selection_single_line_caret_movement(
let new_range = range.shrink_end_by(width(*delta));

let buffer_mut = buffer.get_mut(width(0) + height(0));
buffer_mut.sel_list.insert(
buffer_mut.inner.sel_list.insert(
row_index,
new_range,
SelectionRange::caret_movement_direction_left_right(previous, current),
Expand All @@ -137,7 +137,7 @@ pub fn handle_selection_single_line_caret_movement(
let new_range = range.grow_start_by(width(*delta));

let buffer_mut = buffer.get_mut(width(0) + height(0));
buffer_mut.sel_list.insert(
buffer_mut.inner.sel_list.insert(
row_index,
new_range,
SelectionRange::caret_movement_direction_left_right(previous, current),
Expand All @@ -154,7 +154,7 @@ pub fn handle_selection_single_line_caret_movement(
let new_range = range.grow_end_by(width(*delta));

let buffer_mut = buffer.get_mut(width(0) + height(0));
buffer_mut.sel_list.insert(
buffer_mut.inner.sel_list.insert(
row_index,
new_range,
SelectionRange::caret_movement_direction_left_right(previous, current),
Expand All @@ -172,7 +172,7 @@ pub fn handle_selection_single_line_caret_movement(
let new_range = range.shrink_start_by(width(*delta));

let buffer_mut = buffer.get_mut(width(0) + height(0));
buffer_mut.sel_list.insert(
buffer_mut.inner.sel_list.insert(
row_index,
new_range,
SelectionRange::caret_movement_direction_left_right(previous, current),
Expand All @@ -189,7 +189,7 @@ pub fn handle_selection_single_line_caret_movement(
if let Some(range) = buffer.get_selection_list().get(row_index) {
if range.start_disp_col_idx_scr_adj == range.end_disp_col_idx_scr_adj {
let buffer_mut = buffer.get_mut(width(0) + height(0));
buffer_mut.sel_list.remove(
buffer_mut.inner.sel_list.remove(
row_index,
SelectionRange::caret_movement_direction_left_right(previous, current),
);
Expand Down Expand Up @@ -301,7 +301,7 @@ pub fn handle_selection_multiline_caret_movement_hit_top_or_bottom_of_document(
/* 3: row_index */
d = format!("row_index: {:?}", row_index).green().on_dark_grey(),
/* 4: selection_map */
e = format!("{:?}", buffer_mut.sel_list)
e = format!("{:?}", buffer_mut.inner.sel_list)
.magenta()
.on_dark_grey(),
);
Expand All @@ -314,12 +314,12 @@ pub fn handle_selection_multiline_caret_movement_hit_top_or_bottom_of_document(

match curr.col_index.cmp(&prev.col_index) {
cmp::Ordering::Less => {
match buffer_mut.sel_list.get(row_index) {
match buffer_mut.inner.sel_list.get(row_index) {
// Extend range to left (caret moved up and hit the top).
Some(range) => {
let start = col(0);
let end = range.end_disp_col_idx_scr_adj;
buffer_mut.sel_list.insert(
buffer_mut.inner.sel_list.insert(
row_index,
SelectionRange {
start_disp_col_idx_scr_adj: start,
Expand All @@ -332,7 +332,7 @@ pub fn handle_selection_multiline_caret_movement_hit_top_or_bottom_of_document(
None => {
let start = col(0);
let end = prev.col_index;
buffer_mut.sel_list.insert(
buffer_mut.inner.sel_list.insert(
row_index,
SelectionRange {
start_disp_col_idx_scr_adj: start,
Expand All @@ -344,17 +344,17 @@ pub fn handle_selection_multiline_caret_movement_hit_top_or_bottom_of_document(
}
}
cmp::Ordering::Greater => {
match buffer_mut.sel_list.get(row_index) {
match buffer_mut.inner.sel_list.get(row_index) {
// Extend range to right (caret moved down and hit bottom).
Some(range) => {
if let Some(line_us) = buffer_mut.lines.get(usize(row_index)) {
if let Some(line_us) = buffer_mut.inner.lines.get(usize(row_index)) {
let start = range.start_disp_col_idx_scr_adj;
// For selection, go one col index past the end of the line,
// since selection range is not inclusive of the end index.
let end = caret_scroll_index::col_index_for_width(
line_us.display_width,
);
buffer_mut.sel_list.insert(
buffer_mut.inner.sel_list.insert(
row_index,
SelectionRange {
start_disp_col_idx_scr_adj: start,
Expand All @@ -368,7 +368,7 @@ pub fn handle_selection_multiline_caret_movement_hit_top_or_bottom_of_document(
None => {
let start = prev.col_index;
let end = curr.col_index;
buffer_mut.sel_list.insert(
buffer_mut.inner.sel_list.insert(
row_index,
SelectionRange {
start_disp_col_idx_scr_adj: start,
Expand Down Expand Up @@ -650,12 +650,12 @@ mod multiline_select_helpers {
};

let buffer_mut = buffer.get_mut(width(0) + height(0));
buffer_mut.sel_list.insert(
buffer_mut.inner.sel_list.insert(
first.row_index,
first_row_range,
caret_vertical_movement_direction,
);
buffer_mut.sel_list.insert(
buffer_mut.inner.sel_list.insert(
last.row_index,
last_row_range,
caret_vertical_movement_direction,
Expand All @@ -681,7 +681,7 @@ mod multiline_select_helpers {
let buffer_mut = buffer.get_mut(width(0) + height(0));

// Extend the existing range (in selection map) for the first row to end of line.
if let Some(first_row_range) = buffer_mut.sel_list.get(first.row_index) {
if let Some(first_row_range) = buffer_mut.inner.sel_list.get(first.row_index) {
let start_col = first_row_range.start_disp_col_idx_scr_adj;
// Go one col index past the end of the width, since selection range is not
// inclusive of end index.
Expand All @@ -690,7 +690,7 @@ mod multiline_select_helpers {
start_disp_col_idx_scr_adj: start_col,
end_disp_col_idx_scr_adj: end_col,
};
buffer_mut.sel_list.insert(
buffer_mut.inner.sel_list.insert(
first.row_index,
new_first_row_range,
caret_vertical_movement_direction,
Expand All @@ -703,7 +703,7 @@ mod multiline_select_helpers {
let end_col = last.col_index;
(start_col, end_col).into()
};
buffer_mut.sel_list.insert(
buffer_mut.inner.sel_list.insert(
last.row_index,
last_row_range,
caret_vertical_movement_direction,
Expand All @@ -729,7 +729,7 @@ mod multiline_select_helpers {
let buffer_mut = buffer.get_mut(width(0) + height(0));

// FIRST ROW.
if let Some(first_row_range) = buffer_mut.sel_list.get(first.row_index) {
if let Some(first_row_range) = buffer_mut.inner.sel_list.get(first.row_index) {
// Extend the existing range (in selection map) for the first row to end of line.
let updated_first_row_range = SelectionRange {
start_disp_col_idx_scr_adj: first_row_range.start_disp_col_idx_scr_adj,
Expand All @@ -738,7 +738,7 @@ mod multiline_select_helpers {
// not inclusive of end index.
caret_scroll_index::col_index_for_width(first_line_width),
};
buffer_mut.sel_list.insert(
buffer_mut.inner.sel_list.insert(
first.row_index,
updated_first_row_range,
caret_vertical_movement_direction,
Expand All @@ -756,23 +756,23 @@ mod multiline_select_helpers {
)
.into()
};
buffer_mut.sel_list.insert(
buffer_mut.inner.sel_list.insert(
first.row_index,
new_first_row_range,
caret_vertical_movement_direction,
);
}

// LAST ROW.
if let Some(last_row_range) = buffer_mut.sel_list.get(last.row_index) {
if let Some(last_row_range) = buffer_mut.inner.sel_list.get(last.row_index) {
// Extend the existing range (in selection map) for the last row to start of line.
let start_col = col(0);
let end_col = last_row_range.end_disp_col_idx_scr_adj;
let updated_last_row_range = SelectionRange {
start_disp_col_idx_scr_adj: start_col,
end_disp_col_idx_scr_adj: end_col,
};
buffer_mut.sel_list.insert(
buffer_mut.inner.sel_list.insert(
last.row_index,
updated_last_row_range,
caret_vertical_movement_direction,
Expand All @@ -784,7 +784,7 @@ mod multiline_select_helpers {
let end_col = last.col_index;
(start_col, end_col).into()
};
buffer_mut.sel_list.insert(
buffer_mut.inner.sel_list.insert(
last.row_index,
new_last_row_range,
caret_vertical_movement_direction,
Expand All @@ -808,24 +808,26 @@ mod multiline_select_helpers {
let buffer_mut = buffer.get_mut(width(0) + height(0));

// Drop the existing range (in selection map) for the last row.
if buffer_mut.sel_list.get(last.row_index).is_some() {
if buffer_mut.inner.sel_list.get(last.row_index).is_some() {
buffer_mut
.inner
.sel_list
.remove(last.row_index, caret_vertical_movement_direction);
}

// Change the existing range (in selection map) for the first row.
if let Some(first_row_range) = buffer_mut.sel_list.get(first.row_index) {
if let Some(first_row_range) = buffer_mut.inner.sel_list.get(first.row_index) {
let lhs = first_row_range.start_disp_col_idx_scr_adj;
let rhs = first.col_index;
match lhs.cmp(&rhs) {
cmp::Ordering::Equal => {
buffer_mut
.inner
.sel_list
.remove(first.row_index, caret_vertical_movement_direction);
}
cmp::Ordering::Less | cmp::Ordering::Greater => {
buffer_mut.sel_list.insert(
buffer_mut.inner.sel_list.insert(
first.row_index,
SelectionRange {
start_disp_col_idx_scr_adj: lhs.min(rhs),
Expand Down Expand Up @@ -854,23 +856,25 @@ mod multiline_select_helpers {
let buffer_mut = buffer.get_mut(width(0) + height(0));

// Drop the existing range (in selection map) for the first row.
if buffer_mut.sel_list.get(first.row_index).is_some() {
if buffer_mut.inner.sel_list.get(first.row_index).is_some() {
buffer_mut
.inner
.sel_list
.remove(first.row_index, caret_vertical_movement_direction);
}

// Change the existing range (in selection map) for the last row.
if let Some(last_row_range) = buffer_mut.sel_list.get(last.row_index) {
if let Some(last_row_range) = buffer_mut.inner.sel_list.get(last.row_index) {
let lhs = last.col_index;
let rhs = last_row_range.end_disp_col_idx_scr_adj;
let row_index = last.row_index;
match lhs.cmp(&rhs) {
cmp::Ordering::Equal => buffer_mut
.inner
.sel_list
.remove(row_index, caret_vertical_movement_direction),
cmp::Ordering::Greater | cmp::Ordering::Less => {
buffer_mut.sel_list.insert(
buffer_mut.inner.sel_list.insert(
row_index,
SelectionRange {
start_disp_col_idx_scr_adj: rhs.min(lhs),
Expand Down
56 changes: 21 additions & 35 deletions tui/src/tui/editor/editor_buffer/buffer_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ use r3bl_core::{call_if_true,
CharStorage,
ColWidth,
Dim,
RowHeight,
RowIndex,
ScrOfs,
Size,
Expand All @@ -47,7 +48,7 @@ use smallvec::{smallvec, SmallVec};
use super::SelectionList;
use crate::{caret_locate,
editor_engine::engine_public_api,
validate_buffer_mut::EditorBufferMut,
validate_buffer_mut::{EditorBufferMutNoDrop, EditorBufferMutWithDrop},
EditorEngine,
HasFocus,
RenderArgs,
Expand Down Expand Up @@ -82,9 +83,10 @@ use crate::{caret_locate,
/// All the fields in this struct are private. In order to access them you have to use the
/// accessor associated functions. To mutate them, you have to use the
/// [get_mut](EditorBuffer::get_mut) method, which returns a struct of mutable references
/// to the fields. This struct [EditorBufferMut] implements the [Drop] trait, which allows
/// for validation [crate::validate_buffer_mut::perform_validation_checks_after_mutation]
/// operations to be applied post mutation.
/// to the fields. This struct [crate::EditorBufferMut] implements the [Drop] trait, which
/// allows for validation
/// [crate::validate_buffer_mut::perform_validation_checks_after_mutation] operations to
/// be applied post mutation.
///
/// # Different kinds of caret positions
///
Expand Down Expand Up @@ -367,7 +369,7 @@ pub mod content {
}

/// Get line display with at caret's scroll adjusted row index. Use this when you
/// don't have access to this struct. Eg: in [EditorBufferMut].
/// don't have access to this struct. Eg: in [crate::EditorBufferMut].
pub fn impl_get_line_display_width_at_caret_scr_adj(
caret_raw: CaretRaw,
scr_ofs: ScrOfs,
Expand Down Expand Up @@ -506,8 +508,6 @@ pub mod content {
}

pub mod access_and_mutate {
use r3bl_core::RowHeight;

use super::*;

impl EditorBuffer {
Expand Down Expand Up @@ -615,30 +615,30 @@ pub mod access_and_mutate {
/// This makes it easy to determine what code mutates this struct, since it is
/// necessary to validate things after mutation quite a bit in editor_ops.rs.
///
/// [EditorBufferMut] implements the [Drop] trait, which ensures that any
/// [crate::EditorBufferMut] implements the [Drop] trait, which ensures that any
/// validation changes are applied after making changes to the [EditorBuffer].
pub fn get_mut(&mut self, vp: Dim) -> EditorBufferMut<'_> {
EditorBufferMut {
lines: &mut self.content.lines,
caret_raw: &mut self.content.caret_raw,
scr_ofs: &mut self.content.scr_ofs,
sel_list: &mut self.content.sel_list,
pub fn get_mut(&mut self, vp: Dim) -> EditorBufferMutWithDrop<'_> {
EditorBufferMutWithDrop::new(
&mut self.content.lines,
&mut self.content.caret_raw,
&mut self.content.scr_ofs,
&mut self.content.sel_list,
vp,
}
)
}

/// This is a special case of [EditorBuffer::get_mut] where the [Drop] trait is
/// not used to perform validation checks after mutation. This is useful when you
/// don't want to run validation checks after mutation, which happens when the
/// window is resized using [mod@crate::validate_scroll_on_resize].
pub fn get_mut_no_drop(&mut self, vp: Dim) -> EditorBufferMutNoDrop<'_> {
EditorBufferMutNoDrop {
lines: &mut self.content.lines,
caret_raw: &mut self.content.caret_raw,
scr_ofs: &mut self.content.scr_ofs,
sel_list: &mut self.content.sel_list,
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() }
Expand All @@ -649,20 +649,6 @@ pub mod access_and_mutate {
}
}

pub struct EditorBufferMutNoDrop<'a> {
pub lines: &'a mut VecEditorContentLines,
pub caret_raw: &'a mut CaretRaw,
pub scr_ofs: &'a mut ScrOfs,
pub sel_list: &'a mut SelectionList,
/// - Viewport width is optional because it's only needed for caret validation.
/// And you can get it from [crate::EditorEngine]. You can pass `0` if you don't have
/// it.
/// - Viewport height is optional because it's only needed for caret validation.
/// And you can get it from [crate::EditorEngine]. You can pass `0` if you don't have
/// it.
pub vp: Dim,
}

pub mod history {
use super::*;

Expand Down
Loading

0 comments on commit 9f8b8f9

Please sign in to comment.