use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
#[derive( Debug, Clone )]
pub struct TextBuffer
{
pub lines: Vec< String >,
pub cursor_line: usize,
pub cursor_col: usize,
}
impl TextBuffer
{
pub fn new() -> Self
{
Self
{
lines: vec![ String::new() ],
cursor_line: 0,
cursor_col: 0,
}
}
pub fn with_text( text: &str ) -> Self
{
let lines: Vec< String > = text.lines().map( |s| s.to_string() ).collect();
let lines = if lines.is_empty()
{
vec![ String::new() ]
}
else
{
lines
};
Self
{
lines,
cursor_line: 0,
cursor_col: 0,
}
}
pub fn text( &self ) -> String
{
self.lines.join( "\n" )
}
pub fn line_count( &self ) -> usize
{
self.lines.len()
}
pub fn char_count( &self ) -> usize
{
self.text().chars().count()
}
pub fn cursor_position( &self ) -> ( usize, usize )
{
( self.cursor_line, self.cursor_col )
}
pub fn current_line( &self ) -> &str
{
&self.lines[ self.cursor_line ]
}
#[ allow( dead_code ) ]
pub fn line( &self, index: usize ) -> Option< &str >
{
self.lines.get( index ).map( |s| s.as_str() )
}
pub fn lines( &self ) -> &[ String ]
{
&self.lines
}
pub fn insert_char( &mut self, ch: char )
{
if ch == '\n'
{
self.insert_newline();
}
else
{
let byte_pos = self.grapheme_to_byte_index( &self.lines[ self.cursor_line ], self.cursor_col );
let line = &mut self.lines[ self.cursor_line ];
line.insert( byte_pos, ch );
self.cursor_col += 1;
}
}
pub fn insert_newline( &mut self )
{
let line = &self.lines[ self.cursor_line ];
let byte_pos = self.grapheme_to_byte_index( line, self.cursor_col );
let rest = line[ byte_pos.. ].to_string();
self.lines[ self.cursor_line ].truncate( byte_pos );
self.cursor_line += 1;
self.lines.insert( self.cursor_line, rest );
self.cursor_col = 0;
}
pub fn delete_char_before( &mut self )
{
if self.cursor_col > 0
{
let byte_pos = self.grapheme_to_byte_index( &self.lines[ self.cursor_line ], self.cursor_col );
let prev_byte_pos = self.grapheme_to_byte_index( &self.lines[ self.cursor_line ], self.cursor_col - 1 );
let line = &mut self.lines[ self.cursor_line ];
line.drain( prev_byte_pos..byte_pos );
self.cursor_col -= 1;
}
else if self.cursor_line > 0
{
let current_line = self.lines.remove( self.cursor_line );
self.cursor_line -= 1;
let prev_line = &self.lines[ self.cursor_line ];
self.cursor_col = prev_line.graphemes( true ).count();
self.lines[ self.cursor_line ].push_str( ¤t_line );
}
}
pub fn delete_char_at( &mut self )
{
let line_len = self.lines[ self.cursor_line ].graphemes( true ).count();
if self.cursor_col < line_len
{
let byte_pos = self.grapheme_to_byte_index( &self.lines[ self.cursor_line ], self.cursor_col );
let next_byte_pos = self.grapheme_to_byte_index( &self.lines[ self.cursor_line ], self.cursor_col + 1 );
let line = &mut self.lines[ self.cursor_line ];
line.drain( byte_pos..next_byte_pos );
}
else if self.cursor_line < self.lines.len() - 1
{
let next_line = self.lines.remove( self.cursor_line + 1 );
self.lines[ self.cursor_line ].push_str( &next_line );
}
}
pub fn move_left( &mut self )
{
if self.cursor_col > 0
{
self.cursor_col -= 1;
}
else if self.cursor_line > 0
{
self.cursor_line -= 1;
let line = &self.lines[ self.cursor_line ];
self.cursor_col = line.graphemes( true ).count();
}
}
pub fn move_right( &mut self )
{
let line = &self.lines[ self.cursor_line ];
let line_len = line.graphemes( true ).count();
if self.cursor_col < line_len
{
self.cursor_col += 1;
}
else if self.cursor_line < self.lines.len() - 1
{
self.cursor_line += 1;
self.cursor_col = 0;
}
}
pub fn move_up( &mut self )
{
if self.cursor_line > 0
{
self.cursor_line -= 1;
self.clamp_cursor_to_line();
}
}
pub fn move_down( &mut self )
{
if self.cursor_line < self.lines.len() - 1
{
self.cursor_line += 1;
self.clamp_cursor_to_line();
}
}
pub fn move_to_line_start( &mut self )
{
self.cursor_col = 0;
}
pub fn move_to_line_end( &mut self )
{
let line = &self.lines[ self.cursor_line ];
self.cursor_col = line.graphemes( true ).count();
}
pub fn move_to_text_start( &mut self )
{
self.cursor_line = 0;
self.cursor_col = 0;
}
pub fn move_to_text_end( &mut self )
{
self.cursor_line = self.lines.len() - 1;
self.move_to_line_end();
}
fn clamp_cursor_to_line( &mut self )
{
let line = &self.lines[ self.cursor_line ];
let line_len = line.graphemes( true ).count();
if self.cursor_col > line_len
{
self.cursor_col = line_len;
}
}
fn grapheme_to_byte_index( &self, s: &str, grapheme_index: usize ) -> usize
{
s.graphemes( true )
.take( grapheme_index )
.map( |g| g.len() )
.sum()
}
#[ allow( dead_code ) ]
pub fn line_display_width( &self, line_index: usize ) -> usize
{
self.lines.get( line_index )
.map( |line| UnicodeWidthStr::width( line.as_str() ) )
.unwrap_or( 0 )
}
#[ allow( dead_code ) ]
pub fn current_line_display_width( &self ) -> usize
{
self.line_display_width( self.cursor_line )
}
}
impl Default for TextBuffer
{
fn default() -> Self
{
Self::new()
}
}