support unicode characters that are longer than one byte
This commit is contained in:
parent
c6090b3cc1
commit
3f2d3491ed
1 changed files with 64 additions and 17 deletions
|
@ -14,6 +14,7 @@ use termion::{
|
||||||
|
|
||||||
const TAB_SIZE: usize = 4;
|
const TAB_SIZE: usize = 4;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Editor {
|
pub struct Editor {
|
||||||
text: String,
|
text: String,
|
||||||
lines: Vec<Line>,
|
lines: Vec<Line>,
|
||||||
|
@ -21,9 +22,11 @@ pub struct Editor {
|
||||||
quit: bool,
|
quit: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
struct Cursor {
|
struct Cursor {
|
||||||
line: usize,
|
line: usize,
|
||||||
column: usize,
|
column: usize,
|
||||||
|
// target_column: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
type Line = Range<usize>;
|
type Line = Range<usize>;
|
||||||
|
@ -75,6 +78,9 @@ impl Editor {
|
||||||
Key::Right => self.move_right(),
|
Key::Right => self.move_right(),
|
||||||
Key::Up => self.move_up(),
|
Key::Up => self.move_up(),
|
||||||
Key::Down => self.move_down(),
|
Key::Down => self.move_down(),
|
||||||
|
Key::F(1) => {
|
||||||
|
dbg!(&self);
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,7 +89,7 @@ impl Editor {
|
||||||
|
|
||||||
fn move_left(&mut self) {
|
fn move_left(&mut self) {
|
||||||
if self.cursor.column > 0 {
|
if self.cursor.column > 0 {
|
||||||
self.cursor.column -= 1;
|
self.cursor.column = self.prev_char_index() - self.current_line().start;
|
||||||
} else if self.cursor.line > 0 {
|
} else if self.cursor.line > 0 {
|
||||||
self.cursor.line -= 1;
|
self.cursor.line -= 1;
|
||||||
self.cursor.column = self.current_line().len();
|
self.cursor.column = self.current_line().len();
|
||||||
|
@ -92,7 +98,7 @@ impl Editor {
|
||||||
|
|
||||||
fn move_right(&mut self) {
|
fn move_right(&mut self) {
|
||||||
if self.cursor.column < self.current_line().len() {
|
if self.cursor.column < self.current_line().len() {
|
||||||
self.cursor.column += 1;
|
self.cursor.column = self.next_char_index() - self.current_line().start;
|
||||||
} else if self.cursor.line < self.lines.len() - 1 {
|
} else if self.cursor.line < self.lines.len() - 1 {
|
||||||
self.cursor.line += 1;
|
self.cursor.line += 1;
|
||||||
self.cursor.column = 0;
|
self.cursor.column = 0;
|
||||||
|
@ -101,33 +107,53 @@ impl Editor {
|
||||||
|
|
||||||
fn move_up(&mut self) {
|
fn move_up(&mut self) {
|
||||||
if self.cursor.line > 0 {
|
if self.cursor.line > 0 {
|
||||||
|
let physical_column = self.text
|
||||||
|
[self.current_line().start..(self.current_line().start + self.cursor.column)]
|
||||||
|
.chars()
|
||||||
|
.count();
|
||||||
self.cursor.line -= 1;
|
self.cursor.line -= 1;
|
||||||
self.cursor.column = self.cursor.column.min(self.current_line().len());
|
self.cursor.column = physical_column.min(self.current_line().len());
|
||||||
|
self.ensure_char_boundary();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn move_down(&mut self) {
|
fn move_down(&mut self) {
|
||||||
if self.cursor.line < self.lines.len() - 1 {
|
if self.cursor.line < self.lines.len() - 1 {
|
||||||
|
let physical_column = self.text
|
||||||
|
[self.current_line().start..(self.current_line().start + self.cursor.column)]
|
||||||
|
.chars()
|
||||||
|
.count();
|
||||||
self.cursor.line += 1;
|
self.cursor.line += 1;
|
||||||
self.cursor.column = self.cursor.column.min(self.current_line().len());
|
self.cursor.column = physical_column.min(self.current_line().len());
|
||||||
|
self.ensure_char_boundary();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn current_line(&self) -> Line {
|
/// Moves cursor left until it is on a character (in case it was in the middle of a multi-byte character)
|
||||||
self.lines.get(self.cursor.line).unwrap_or(&(0..0)).clone()
|
fn ensure_char_boundary(&mut self) {
|
||||||
|
while !self
|
||||||
|
.text
|
||||||
|
.is_char_boundary(self.current_line().start + self.cursor.column)
|
||||||
|
{
|
||||||
|
self.cursor.column -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_line(&self) -> &Line {
|
||||||
|
self.lines.get(self.cursor.line).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_lines(&mut self) {
|
fn find_lines(&mut self) {
|
||||||
self.lines.clear();
|
self.lines.clear();
|
||||||
let mut this_line = 0..0;
|
let mut this_line = 0..0;
|
||||||
for (index, char) in self.text.chars().enumerate() {
|
for (index, char) in self.text.char_indices() {
|
||||||
if char == '\n' {
|
if char == '\n' {
|
||||||
this_line.end = index;
|
this_line.end = index;
|
||||||
self.lines.push(this_line.clone());
|
self.lines.push(this_line.clone());
|
||||||
this_line.start = index + 1;
|
this_line.start = index + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this_line.end = self.text.chars().count();
|
this_line.end = self.text.len();
|
||||||
self.lines.push(this_line);
|
self.lines.push(this_line);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,7 +169,7 @@ impl Editor {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
print!(
|
print!(
|
||||||
"{}{}, {}",
|
"{}({}, {})",
|
||||||
cursor::Goto(1, terminal_size().unwrap().1),
|
cursor::Goto(1, terminal_size().unwrap().1),
|
||||||
self.cursor.line,
|
self.cursor.line,
|
||||||
self.cursor.column
|
self.cursor.column
|
||||||
|
@ -161,34 +187,55 @@ impl Editor {
|
||||||
|
|
||||||
fn insert_char(&mut self, ch: char) {
|
fn insert_char(&mut self, ch: char) {
|
||||||
// eprintln!("inserting {ch} at {}", self.index());
|
// eprintln!("inserting {ch} at {}", self.index());
|
||||||
self.text.insert(self.index(), ch);
|
self.text.insert(self.char_index(), ch);
|
||||||
self.find_lines();
|
self.find_lines();
|
||||||
self.move_right();
|
self.move_right();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn backspace(&mut self) {
|
fn backspace(&mut self) {
|
||||||
if self.index() > 0 {
|
if self.char_index() > 0 {
|
||||||
self.text.remove(self.index() - 1);
|
|
||||||
self.move_left();
|
self.move_left();
|
||||||
|
self.text.remove(self.char_index());
|
||||||
self.find_lines();
|
self.find_lines();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete(&mut self) {
|
fn delete(&mut self) {
|
||||||
if self.index() < self.text.len() {
|
if self.char_index() < self.text.len() {
|
||||||
self.text.remove(self.index());
|
self.text.remove(self.char_index());
|
||||||
self.find_lines();
|
self.find_lines();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn index(&self) -> usize {
|
/// Byte position of current character. May be text.len if cursor is at the end of the file
|
||||||
|
fn char_index(&self) -> usize {
|
||||||
self.current_line().start + self.cursor.column
|
self.current_line().start + self.cursor.column
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Byte position of next character.
|
||||||
|
/// Returns text.len if cursor is on the last character
|
||||||
|
fn next_char_index(&self) -> usize {
|
||||||
|
self.text[self.char_index()..]
|
||||||
|
.char_indices()
|
||||||
|
.nth(1)
|
||||||
|
.map_or(self.text.len(), |(byte, _char)| byte + self.char_index())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Byte position of preceding character.
|
||||||
|
/// Panics if cursor is at index 0
|
||||||
|
fn prev_char_index(&self) -> usize {
|
||||||
|
self.text[..self.char_index()]
|
||||||
|
.char_indices()
|
||||||
|
.last()
|
||||||
|
.map(|(byte, _char)| byte)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
fn physical_column(&self) -> usize {
|
fn physical_column(&self) -> usize {
|
||||||
let start = self.current_line().start;
|
let start = self.current_line().start;
|
||||||
let end = self.current_line().start + self.cursor.column;
|
let end = self.char_index();
|
||||||
|
let preceding_chars = self.text[start..end].chars().count();
|
||||||
let preceding_tabs = self.text[start..end].chars().filter(|&c| c == '\t').count();
|
let preceding_tabs = self.text[start..end].chars().filter(|&c| c == '\t').count();
|
||||||
self.cursor.column + preceding_tabs * (TAB_SIZE - 1)
|
preceding_chars + preceding_tabs * (TAB_SIZE - 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue