Skip to content

Commit

Permalink
WIP editor_engine.rs and editor_buffer.rs
Browse files Browse the repository at this point in the history
Caret
  - Add TWCommand to show & set caret (GlobalCursor)
  - Add support for LocalPaintedEffect caret
TWCommandQueue
  - add macro to make it easy to join and drop other queues into one
Position
  - add methods to check for bounds when adding rows and cols
Unicode
  - Vec<GraphemeClusterSegment> to / from String
Debug
  - Add GetSize derive macro for various structs
Testing
  - Add tests for editor buffer & engine
  - No such thing as visible for testing in integration tests
Ergonomics
  - Add better macro for debug: call_if_debug_true!
Documentation
  - Better code examples for macros

Here's the design doc for this feature:
#23
  • Loading branch information
nazmulidris committed Sep 3, 2022
1 parent 39f741f commit b2f69c9
Show file tree
Hide file tree
Showing 19 changed files with 680 additions and 115 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -3,3 +3,4 @@ Cargo.lock
**/target
docs/*.bkp
docs/*.dtmp
.idea
34 changes: 34 additions & 0 deletions .vscode/bookmarks.json
@@ -0,0 +1,34 @@
{
"files": [
{
"path": "src/tui/crossterm_helpers/tw_command.rs",
"bookmarks": [
{
"line": 316,
"column": 4,
"label": ""
},
{
"line": 323,
"column": 4,
"label": ""
},
{
"line": 483,
"column": 8,
"label": ""
}
]
},
{
"path": "src/tui/terminal_window/main_event_loop.rs",
"bookmarks": [
{
"line": 234,
"column": 6,
"label": ""
}
]
}
]
}
1 change: 1 addition & 0 deletions .vscode/settings.json
Expand Up @@ -24,6 +24,7 @@
"keyb",
"keyevent",
"Keypress",
"keypresses",
"lazyfield",
"lazymemovalues",
"litint",
Expand Down
38 changes: 38 additions & 0 deletions TODO.todo
@@ -1,3 +1,41 @@
╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮
│ r3bl_rs_utils │
╯ ╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
editor engine (https://github.com/r3bl-org/r3bl_rs_utils/issues/23):
✔ wire up the engine into framework @done(22-08-31 13:56)
✔ fancy debug impl for editor buffer @done(22-08-31 13:57)
✔ insert content: @done(22-09-01 11:46)
✔ type characters & store in buffer @done(22-09-01 11:46)
✔ add tests for editor buffer @done(22-09-01 11:46)
✔ paint caret: @done(22-09-02 09:43)
✔ use cursor show / hide @done(22-09-02 08:19)
✔ use reverse / invert colors to paint the caret (so there can be many) @done(22-09-02 09:42)
✔ bounds check max rows when painting content @done(22-09-02 11:29)
✔ implement render clipping: @done(22-09-02 13:10)
✔ figure out how to incorporate row & col bounds checking to implement clipping @done(22-09-02 13:10)
☐ insert content:
☐ handle new lines
☐ move cursor:
☐ left/right arrow key move in buffer
☐ up/down arrow key move in buffer
☐ delete content:
☐ delete/backspace edit buffer line
☐ delete/backspace lines
☐ scrolling
☐ left/right
☐ up/down
☐ keyboard shortcut to save/load buffer to/from file
☐ highlight and selection:
☐ add support for text selection highlighting
☐ selection, copy, paste
☐ multiple carets:
☐ add support for multiple carets & network service providers to move them

framework:
☐ https://github.com/r3bl-org/r3bl_rs_utils/issues/28
☐ https://github.com/r3bl-org/r3bl_rs_utils/issues/27
☐ https://github.com/r3bl-org/r3bl_rs_utils/issues/24
☐ https://github.com/r3bl-org/r3bl_rs_utils/issues/26

writing:
☐ https://github.com/r3bl-org/r3bl_rs_utils/issues/19
21 changes: 21 additions & 0 deletions core/src/decl_macros.rs
Expand Up @@ -143,6 +143,27 @@ macro_rules! call_if_true {
}};
}

/// Syntactic sugar to run a conditional statement. Here's an example.
/// ```rust
/// const DEBUG: bool = true;
/// call_if_debug_true!(
/// eprintln!(
/// "{} {} {}\r",
/// r3bl_rs_utils::style_error("▶"),
/// r3bl_rs_utils::style_prompt($msg),
/// r3bl_rs_utils::style_dimmed(&format!("{:#?}", $err))
/// )
/// );
/// ```
#[macro_export]
macro_rules! call_if_debug_true {
($block: expr) => {{
if DEBUG {
$block
}
}};
}

/// This is a really simple macro to make it effortless to use the color console
/// logger. It takes a single identifier as an argument, or any number of them.
/// It simply dumps an arrow symbol, followed by the identifier ([stringify]'d)
Expand Down
8 changes: 8 additions & 0 deletions core/src/tui_core/dimens/base_units.rs
Expand Up @@ -25,3 +25,11 @@ macro_rules! convert_to_base_unit {
$self.try_into().unwrap_or($self as UnitType)
};
}

/// Converts a [UnitType] to ([i32], [usize]), etc.
#[macro_export]
macro_rules! convert_from_base_unit {
($self:expr) => {
$self.try_into().unwrap_or($self as usize)
};
}
34 changes: 24 additions & 10 deletions core/src/tui_core/dimens/position.rs
Expand Up @@ -90,24 +90,38 @@ impl From<Position> for (UnitType, UnitType) {
}

impl Position {
/// Add given `col` value to `self`.
pub fn add_col(&mut self, value: usize) -> Self {
let value: UnitType = value as UnitType;
/// Add given `col` count to `self`.
pub fn add_cols(&mut self, num_cols_to_add: usize) -> Self {
let value: UnitType = convert_to_base_unit!(num_cols_to_add);
self.col += value;
*self
}

/// Add given `row` value to `self`.
pub fn add_row(&mut self, value: usize) -> Self {
let value = value as UnitType;
/// Add given `col` count to `self` w/ bounds check for max cols.
pub fn add_cols_with_bounds(&mut self, num_cols_to_add: usize, box_bounds_size: Size) -> Self {
let value: UnitType = convert_to_base_unit!(num_cols_to_add);
let max: UnitType = box_bounds_size.cols;

if (self.col + value) >= max {
self.col = max
} else {
self.col += value;
}

*self
}

/// Add given `row` count to `self`.
pub fn add_rows(&mut self, num_rows_to_add: usize) -> Self {
let value = convert_to_base_unit!(num_rows_to_add);
self.row += value;
*self
}

/// Add given `row` value to `self` w/ bounds check for max rows.
pub fn add_row_with_bounds(&mut self, value: usize, box_bounding_size: Size) -> Self {
let value: UnitType = value as UnitType;
let max: UnitType = box_bounding_size.rows;
/// Add given `row` count to `self` w/ bounds check for max rows.
pub fn add_rows_with_bounds(&mut self, num_rows_to_add: usize, box_bounds_size: Size) -> Self {
let value: UnitType = convert_to_base_unit!(num_rows_to_add);
let max: UnitType = box_bounds_size.rows;

if (self.row + value) >= max {
self.row = max
Expand Down
104 changes: 93 additions & 11 deletions core/src/tui_core/graphemes/unicode_string_ext.rs
Expand Up @@ -16,9 +16,9 @@
*/

use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};

use crate::{convert_to_base_unit, Size, UnitType};
use crate::{convert_to_base_unit, CommonResult, Size, UnitType};

/// A grapheme cluster is a user-perceived character. Rust uses `UTF-8` to
/// represent text in `String`. So each character takes up 8 bits or one byte.
Expand Down Expand Up @@ -203,15 +203,6 @@ impl UnicodeStringExt for String {
}
}

#[derive(Debug, Clone)]
pub struct UnicodeString {
pub string: String,
pub vec_segment: Vec<GraphemeClusterSegment>,
pub byte_size: usize,
pub grapheme_cluster_segment_count: usize,
pub display_width: UnitType,
}

#[derive(Debug, Clone)]
pub struct GraphemeClusterSegment {
/// The actual grapheme cluster `&str`. Eg: "H", "📦", "🙏🏽".
Expand All @@ -228,7 +219,51 @@ pub struct GraphemeClusterSegment {
pub display_col_offset: UnitType,
}

#[derive(Debug, Clone)]
pub struct UnicodeString {
pub string: String,
pub vec_segment: Vec<GraphemeClusterSegment>,
pub byte_size: usize,
pub grapheme_cluster_segment_count: usize,
pub display_width: UnitType,
}

/// Convert [char] to [GraphemeClusterSegment].
impl From<char> for GraphemeClusterSegment {
fn from(character: char) -> Self {
let my_string: String = character.into();
my_string.unicode_string().vec_segment[0].clone()
}
}

/// Convert [&str] to [GraphemeClusterSegment].
impl From<&str> for GraphemeClusterSegment {
fn from(chunk: &str) -> Self {
let my_string: String = chunk.to_string();
my_string.unicode_string().vec_segment[0].clone()
}
}

/// Convert [Vec<GraphemeClusterSegment>] to [String].
fn to_string(vec_grapheme_cluster_segment: Vec<GraphemeClusterSegment>) -> String {
let mut my_string = String::new();
for grapheme_cluster_segment in vec_grapheme_cluster_segment {
my_string.push_str(&grapheme_cluster_segment.string);
}
my_string
}

impl UnicodeString {
pub fn char_display_width(character: char) -> usize {
let display_width: usize = UnicodeWidthChar::width(character).unwrap_or(0);
display_width
}

pub fn str_display_width(string: &str) -> usize {
let display_width: usize = UnicodeWidthStr::width(string);
display_width
}

pub fn truncate_to_fit_size(&self, size: Size) -> &str {
let display_cols: UnitType = size.cols;
self.truncate_to_fit_display_cols(display_cols)
Expand All @@ -249,10 +284,12 @@ impl UnicodeString {
&self.string[..string_end_byte_index]
}

/// `local_index` is the index of the grapheme cluster in the `vec_segment`.
pub fn at_logical_index(&self, logical_index: usize) -> Option<&GraphemeClusterSegment> {
self.vec_segment.get(logical_index)
}

/// `display_col` is the col index in the terminal where this grapheme cluster can be displayed.
pub fn at_display_col(&self, display_col: UnitType) -> Option<&GraphemeClusterSegment> {
self.vec_segment.iter().find(|&grapheme_cluster_segment| {
let segment_display_col_start: UnitType = grapheme_cluster_segment.display_col_offset;
Expand All @@ -262,17 +299,62 @@ impl UnicodeString {
})
}

/// Convert a `display_col` to a `logical_index`.
/// - `local_index` is the index of the grapheme cluster in the `vec_segment`.
/// - `display_col` is the col index in the terminal where this grapheme cluster can be displayed.
pub fn logical_index_at_display_col(&self, display_col: UnitType) -> Option<usize> {
self
.at_display_col(display_col)
.map(|segment| segment.logical_index)
}

/// Convert a `logical_index` to a `display_col`.
/// - `local_index` is the index of the grapheme cluster in the `vec_segment`.
/// - `display_col` is the col index in the terminal where this grapheme cluster can be displayed.
pub fn display_col_at_logical_index(&self, logical_index: usize) -> Option<UnitType> {
self
.at_logical_index(logical_index)
.map(|segment| segment.display_col_offset)
}

/// Returns a new ([String], [UnitType]) tuple and does not modify
/// [self.string](UnicodeString::string).
pub fn insert_char_at_display_col(
&self, display_col: UnitType, chunk: &str,
) -> CommonResult<(String, UnitType)> {
let maybe_logical_index = self.logical_index_at_display_col(display_col);
match maybe_logical_index {
// Insert somewhere inside bounds of self.string.
Some(logical_index) => {
// Convert the character into a grapheme cluster.
let character_g_c_s: GraphemeClusterSegment = chunk.into();
let character_display_width: UnitType = character_g_c_s.unicode_width;

// Insert this grapheme cluster to self.vec_segment.
let mut vec_segment_clone = self.vec_segment.clone();
vec_segment_clone.insert(logical_index, character_g_c_s);

// Generate a new string from self.vec_segment and return it and the unicode width of the
// character.
let new_string = to_string(vec_segment_clone);

// In the caller - update the caret position based on the unicode width of the character.
Ok((new_string, character_display_width))
}
// Add to end of self.string.
None => {
// Push character to the end of the cloned string.
let mut new_string = self.string.clone();
new_string.push_str(chunk);

// Get the unicode width of the character.
let character_display_width = UnicodeString::str_display_width(chunk);

// In the caller - update the caret position based on the unicode width of the character.
Ok((new_string, convert_to_base_unit!(character_display_width)))
}
}
}
}

pub fn try_strip_ansi(text: &str) -> Option<String> {
Expand Down
2 changes: 1 addition & 1 deletion core/src/tui_core/lolcat/cat.rs
Expand Up @@ -20,9 +20,9 @@ use std::{fmt::Display,
thread::sleep,
time::Duration};

use get_size::GetSize;
use rand::{thread_rng, Rng};
use serde::*;
use get_size::GetSize;

use crate::*;

Expand Down
2 changes: 1 addition & 1 deletion core/src/tui_core/lolcat/control.rs
Expand Up @@ -18,9 +18,9 @@
use std::fmt::Display;

use atty::Stream;
use get_size::GetSize;
use rand::random;
use serde::*;
use get_size::GetSize;

/// A struct to contain info we need to print with every character.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, GetSize)]
Expand Down
16 changes: 16 additions & 0 deletions macro/src/lib.rs
Expand Up @@ -37,6 +37,22 @@ pub fn derive_macro_builder(input: TokenStream) -> TokenStream {
builder::derive_proc_macro_impl(input)
}

/// Example.
///
/// ```
/// style! {
/// id: "my_style", /* Optional. */
/// attrib: [dim, bold] /* Optional. */
/// padding: 10, /* Optional. */
/// color_fg: TWColor::Blue, /* Optional. */
/// color_bg: TWColor::Red, /* Optional. */
/// }
/// ```
///
/// `color_fg` and `color_bg` can take any of the following:
/// 1. Color enum value.
/// 2. Rgb value.
/// 3. Variable holding either of the above.qq
#[proc_macro]
pub fn style(input: TokenStream) -> TokenStream { make_style::fn_proc_macro_impl(input) }

Expand Down
2 changes: 1 addition & 1 deletion macro/src/make_style/codegen.rs
Expand Up @@ -63,7 +63,7 @@ pub(crate) fn code_gen(
};

quote! {
r3bl_rs_utils::Style {
Style {
id: #id.to_string(),
bold: #has_attrib_bold,
dim: #has_attrib_dim,
Expand Down

0 comments on commit b2f69c9

Please sign in to comment.