use std::collections::VecDeque;
use std::{fmt, io};
use glaredb_error::Result;
use super::code_point_string::CodePointString;
use super::highlighter::HighlightState;
use super::{debug, vt100};
const MIN_COLS: usize = 10;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum KeyEvent {
Backspace,
ShiftEnter, Enter,
Left,
Right,
Up,
Down,
Home,
End,
Tab,
BackTab,
Delete,
Insert,
CtrlC,
Char(char),
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UserInput<'a> {
pub s: &'a str,
pub is_dot_command: bool,
}
impl<'a> UserInput<'a> {
pub fn new(input: &'a str) -> Self {
let trimmed = input.trim();
UserInput {
s: trimmed,
is_dot_command: trimmed.starts_with('.'),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Signal<'a> {
KeepEditing,
InputCompleted(UserInput<'a>),
Exit,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TermSize {
pub cols: usize,
}
#[derive(Debug, Default)]
pub struct History {
queries: VecDeque<String>,
cursor: usize,
}
impl History {
const MAX_LEN: usize = 100;
fn push_query(&mut self, query: String) {
while self.queries.len() >= Self::MAX_LEN {
self.queries.pop_front();
}
self.queries.push_back(query);
self.cursor = self.queries.len();
}
}
#[derive(Debug)]
pub struct LineEditor<W: io::Write> {
prompt: &'static str,
continuation: &'static str,
buffer: TextBuffer,
writer: W,
size: TermSize,
text_pos: usize,
rendered_cursor_row: usize,
max_lines: usize,
did_ctrl_c: bool,
highlighter: HighlightState,
history: History,
}
impl<W> LineEditor<W>
where
W: io::Write,
{
pub fn new(
writer: W,
prompt: &'static str,
continuation: &'static str,
size: TermSize,
) -> Self {
debug::log(|| "init");
LineEditor {
prompt,
continuation,
buffer: TextBuffer::new(),
writer,
size,
text_pos: 0,
rendered_cursor_row: 0,
max_lines: 1, did_ctrl_c: false,
highlighter: HighlightState::new(),
history: History::default(),
}
}
pub fn set_size(&mut self, size: TermSize) {
self.size = TermSize {
cols: usize::max(size.cols, MIN_COLS),
}
}
pub fn get_size(&self) -> TermSize {
self.size
}
pub fn writer_mut(&mut self) -> &mut W {
&mut self.writer
}
pub fn consume_key(&mut self, key: KeyEvent) -> Result<Signal> {
if matches!(key, KeyEvent::CtrlC) {
if self.did_ctrl_c {
write!(self.writer, "{}", vt100::CRLF)?;
self.writer.flush()?;
return Ok(Signal::Exit);
}
} else {
self.did_ctrl_c = false;
}
match key {
KeyEvent::Char(c) => self.edit_insert_char(c, true)?,
KeyEvent::Backspace => self.edit_backspace()?,
KeyEvent::Enter => {
if self.is_complete() {
self.edit_move_to_end()?;
write!(self.writer, "{}", vt100::CRLF)?;
self.writer.flush()?;
let input = UserInput::new(self.buffer.as_ref());
if !input.is_dot_command {
self.history.push_query(input.s.to_string());
}
return Ok(Signal::InputCompleted(input));
}
self.edit_enter()?;
}
KeyEvent::Left => self.edit_move_left()?,
KeyEvent::Right => self.edit_move_right()?,
KeyEvent::Up => self.edit_move_up()?,
KeyEvent::Down => self.edit_move_down()?,
KeyEvent::End => self.edit_move_to_end()?,
KeyEvent::CtrlC => {
self.did_ctrl_c = true;
self.edit_move_to_end()?;
write!(self.writer, "{}", vt100::CRLF)?;
self.edit_start()?
}
_ => (),
}
Ok(Signal::KeepEditing)
}
pub fn consume_text(&mut self, text: &str) -> Result<()> {
for ch in text.chars() {
self.edit_insert_char(ch, false)?;
}
self.refresh()
}
fn history_next_forward(&mut self) -> Result<()> {
if self.history.cursor < self.history.queries.len() {
self.buffer.clear();
self.text_pos = 0;
self.rendered_cursor_row = 0;
self.history.cursor += 1;
let query = if self.history.cursor == self.history.queries.len() {
""
} else {
&self.history.queries[self.history.cursor]
};
for ch in query.chars() {
self.buffer.insert_char(self.text_pos, ch);
self.text_pos += 1;
}
self.refresh()?;
}
Ok(())
}
fn history_next_back(&mut self) -> Result<()> {
if self.history.cursor > 0 {
self.buffer.clear();
self.text_pos = 0;
self.rendered_cursor_row = 0;
self.history.cursor -= 1;
let query = &self.history.queries[self.history.cursor];
for ch in query.chars() {
self.buffer.insert_char(self.text_pos, ch);
self.text_pos += 1;
}
self.refresh()?;
}
Ok(())
}
pub fn edit_start(&mut self) -> Result<()> {
self.buffer.clear();
self.text_pos = 0;
self.rendered_cursor_row = 0;
self.max_lines = 1;
write!(
self.writer,
"{}{}{}",
vt100::MODE_BOLD,
self.prompt,
vt100::MODES_OFF
)?;
self.writer.flush()?;
Ok(())
}
fn edit_move_to_end(&mut self) -> Result<()> {
if self.text_pos == self.buffer.current.len() {
return Ok(());
}
self.text_pos = self.buffer.current.len();
self.refresh()
}
fn edit_enter(&mut self) -> Result<()> {
self.edit_insert_char('\n', true)
}
fn edit_move_up(&mut self) -> Result<()> {
let line = self.buffer.current_line(self.text_pos);
if line == 0 {
return self.history_next_back();
}
let pos_rel = self.text_pos - self.buffer.spans[line].start;
let prev = self.buffer.spans[line - 1];
self.text_pos = usize::min(prev.start + prev.len - 1, prev.start + pos_rel);
self.refresh()?;
Ok(())
}
fn edit_move_down(&mut self) -> Result<()> {
let line = self.buffer.current_line(self.text_pos);
if line == self.buffer.spans.len() - 1 {
return self.history_next_forward();
}
let pos_rel = self.text_pos - self.buffer.spans[line].start;
let next = self.buffer.spans[line + 1];
self.text_pos = usize::min(next.start + next.len - 1, next.start + pos_rel);
self.refresh()?;
Ok(())
}
fn edit_move_left(&mut self) -> Result<()> {
if self.text_pos > 0 {
self.text_pos -= 1;
self.refresh()?;
}
Ok(())
}
fn edit_move_right(&mut self) -> Result<()> {
if self.text_pos < self.buffer.current.len() {
self.text_pos += 1;
self.refresh()?;
}
Ok(())
}
fn edit_insert_char(&mut self, ch: char, refresh: bool) -> Result<()> {
self.buffer.insert_char(self.text_pos, ch);
self.text_pos += 1;
if refresh {
self.refresh()?;
}
Ok(())
}
fn edit_backspace(&mut self) -> Result<()> {
if self.text_pos == 0 {
return Ok(());
}
self.buffer.remove_char(self.text_pos);
self.text_pos -= 1;
self.refresh()?;
Ok(())
}
fn refresh(&mut self) -> Result<()> {
debug::log(|| {
format!(
"max_lines: {}, rendered_cursor_row: {}",
self.max_lines, self.rendered_cursor_row
)
});
let row_diff = self.max_lines - self.rendered_cursor_row - 1;
debug::log(|| format!("row_diff: {row_diff}"));
if row_diff > 0 {
vt100::cursor_down(&mut self.writer, row_diff)?;
}
for _ in 0..self.max_lines - 1 {
write!(
self.writer,
"{}{}",
vt100::CR,
vt100::CLEAR_LINE_CURSOR_RIGHT
)?;
vt100::cursor_up(&mut self.writer, 1)?;
}
write!(
self.writer,
"{}{}",
vt100::CR,
vt100::CLEAR_LINE_CURSOR_RIGHT
)?;
let buffer = &self.buffer;
self.highlighter.tokenize(buffer.current.as_str());
let lines = buffer.lines();
let (pos_line, pos_col) = buffer.current_line_and_column(self.text_pos);
debug::log(|| format!("position: line: {pos_line}, col: {pos_col}"));
let mut visual_line_count = 0;
let mut vis_line = pos_line;
let mut vis_col = pos_col;
if pos_line == 0 {
vis_col += self.prompt.len();
} else {
vis_col += self.continuation.len();
}
for (line_idx, hard_line) in lines.enumerate() {
let prompt_width = if line_idx == 0 {
write!(
self.writer,
"{}{}{}",
vt100::MODE_BOLD,
self.prompt,
vt100::MODES_OFF
)?;
self.prompt.len()
} else {
write!(
self.writer,
"{}{}{}",
vt100::CRLF,
vt100::COLOR_FG_BRIGHT_BLACK,
self.continuation
)?;
self.continuation.len()
};
let content_width = self.size.cols - prompt_width;
let split = LineSplitter::new(hard_line, content_width);
for (soft_line_idx, soft_line) in split.enumerate() {
if soft_line_idx > 0 {
write!(
self.writer,
"{}{}{}",
vt100::CRLF,
vt100::COLOR_FG_BRIGHT_BLACK,
self.continuation
)?;
if pos_line > line_idx {
vis_line += 1;
}
if pos_line == line_idx && vis_col > content_width {
vis_col -= content_width;
vis_line += 1;
}
}
let h_line = self.highlighter.next_chunk_highlight(soft_line);
for h_str in h_line {
h_str.write_vt100_trim_nl(&mut self.writer)?;
}
visual_line_count += 1;
}
}
debug::log(|| format!("visual_line_count: {visual_line_count}"));
self.highlighter.clear_highlight(&mut self.writer)?;
self.max_lines = usize::max(visual_line_count, self.max_lines);
self.rendered_cursor_row = vis_line;
debug::log(|| format!("vis_line: {vis_line}, vis_col: {vis_col}"));
let row_diff = visual_line_count - vis_line - 1;
if row_diff > 0 {
vt100::cursor_up(&mut self.writer, row_diff)?;
}
write!(self.writer, "{}", vt100::CR)?;
vt100::cursor_right(&mut self.writer, vis_col)?;
self.writer.flush()?;
Ok(())
}
fn is_complete(&self) -> bool {
let trimmed = self.buffer.as_ref().trim();
trimmed.ends_with(';') || trimmed.starts_with('.')
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct LineSpan {
start: usize,
len: usize,
}
impl LineSpan {
const TEXT_EMPTY: LineSpan = LineSpan { start: 0, len: 0 };
fn split(&self, at_relative: usize) -> (LineSpan, LineSpan) {
let left = LineSpan {
start: self.start,
len: at_relative,
};
let right = LineSpan {
start: self.start + at_relative,
len: self.len - at_relative,
};
(left, right)
}
}
#[derive(Debug)]
struct TextBuffer {
spans: Vec<LineSpan>,
current: CodePointString,
}
impl TextBuffer {
fn new() -> Self {
TextBuffer {
spans: vec![LineSpan::TEXT_EMPTY],
current: CodePointString::new(),
}
}
fn clear(&mut self) {
self.spans.clear();
self.spans.push(LineSpan::TEXT_EMPTY);
self.current.clear();
}
fn current_line(&self, pos: usize) -> usize {
self.spans
.iter()
.position(|s| {
let lower = s.start;
let upper = s.start + s.len;
pos >= lower && pos < upper
})
.unwrap_or_else(|| self.spans.len() - 1)
}
fn current_line_and_column(&self, pos: usize) -> (usize, usize) {
let line = self.current_line(pos);
let col = pos - self.spans[line].start;
(line, col)
}
fn insert_char(&mut self, pos: usize, ch: char) {
if pos == self.current.len() {
self.current.push_char(ch);
self.spans.last_mut().unwrap().len += 1;
if ch == '\n' {
self.spans.push(LineSpan {
start: pos + 1,
len: 0,
});
}
} else {
self.current.insert_char(pos, ch);
let line_idx = self.current_line(pos);
let line = &mut self.spans[line_idx];
line.len += 1;
if line_idx != self.spans.len() - 1 {
let after = &mut self.spans[line_idx + 1..];
for line in after {
line.start += 1;
}
}
if ch == '\n' {
let line = &self.spans[line_idx];
let relative = pos - line.start + 1; let (line, next) = line.split(relative);
self.spans[line_idx] = line;
self.spans.insert(line_idx + 1, next);
}
}
}
fn remove_char(&mut self, pos: usize) {
if pos == 0 {
return;
}
let ch = match self.current.remove_char(pos - 1) {
Some(ch) => ch,
None => return,
};
let mut line_idx = self.current_line(pos);
let line = self.spans[line_idx];
if pos == line.start {
debug_assert_eq!(ch, '\n');
debug_assert!(self.spans.len() >= 2);
let prev = &mut self.spans[line_idx - 1];
prev.len += line.len;
prev.len -= 1;
self.spans.remove(line_idx);
line_idx -= 1;
if line_idx != self.spans.len() - 1 {
let after = &mut self.spans[line_idx + 1..];
for line in after {
line.start -= 1;
}
}
} else {
let line = &mut self.spans[line_idx];
line.len -= 1;
if line_idx != self.spans.len() - 1 {
let after = &mut self.spans[line_idx + 1..];
for line in after {
line.start -= 1;
}
}
}
}
fn lines(&self) -> LineIter {
LineIter {
buffer: self,
curr: 0,
}
}
fn line_str(&self, line_idx: usize) -> Option<&str> {
if line_idx >= self.spans.len() {
return None;
}
let span = self.spans[line_idx];
self.current
.slice_chars(span.start..(span.start + span.len))
}
}
impl AsRef<str> for TextBuffer {
fn as_ref(&self) -> &str {
self.current.as_str()
}
}
impl fmt::Display for TextBuffer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.current.as_str())
}
}
#[derive(Debug)]
struct LineIter<'a> {
buffer: &'a TextBuffer,
curr: usize,
}
impl<'a> Iterator for LineIter<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
let line = self.buffer.line_str(self.curr)?;
self.curr += 1;
Some(line)
}
}
#[derive(Debug)]
struct LineSplitter<'a> {
finished: bool,
width: usize,
rem: &'a str,
}
impl<'a> LineSplitter<'a> {
fn new(line: &'a str, width: usize) -> Self {
LineSplitter {
finished: false,
width,
rem: line,
}
}
}
impl<'a> Iterator for LineSplitter<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
if self.finished {
return None;
}
if self.rem.len() <= self.width {
let s = self.rem;
self.rem = "";
self.finished = true;
return Some(s);
}
let (line, rem) = self.rem.split_at(self.width);
self.rem = rem;
Some(line)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn insert_str(buf: &mut TextBuffer, mut pos: usize, s: &str) -> usize {
for ch in s.chars() {
buf.insert_char(pos, ch);
pos += 1;
}
pos
}
#[test]
fn text_buffer_append_single_line() {
let mut buf = TextBuffer::new();
insert_str(&mut buf, 0, "select 1;");
assert_eq!(Some("select 1;"), buf.line_str(0));
assert_eq!(None, buf.line_str(1));
assert_eq!(0, buf.current_line(0));
assert_eq!(0, buf.current_line(3));
assert_eq!(0, buf.current_line(8));
}
#[test]
fn text_buffer_insert_single_line() {
let mut buf = TextBuffer::new();
insert_str(&mut buf, 0, "select 1;");
buf.insert_char(7, '6');
assert_eq!(Some("select 61;"), buf.line_str(0));
}
#[test]
fn text_buffer_insert_newline() {
let mut buf = TextBuffer::new();
insert_str(&mut buf, 0, "select 1, 2,\n3;");
buf.insert_char(10, '\n');
println!("LINES: {:?}", buf.lines().collect::<Vec<_>>());
assert_eq!(Some("select 1, \n"), buf.line_str(0));
assert_eq!(Some("2,\n"), buf.line_str(1));
assert_eq!(Some("3;"), buf.line_str(2));
}
#[test]
fn text_buffer_append_multiple_lines() {
let mut buf = TextBuffer::new();
let pos = insert_str(&mut buf, 0, "select");
_ = insert_str(&mut buf, pos, "\n1;");
assert_eq!(Some("select\n"), buf.line_str(0));
assert_eq!(Some("1;"), buf.line_str(1));
assert_eq!(None, buf.line_str(2));
assert_eq!((0, 0), buf.current_line_and_column(0));
assert_eq!((0, 5), buf.current_line_and_column(5));
assert_eq!((0, 6), buf.current_line_and_column(6)); assert_eq!((1, 0), buf.current_line_and_column(7));
assert_eq!((1, 1), buf.current_line_and_column(8));
assert_eq!((1, 2), buf.current_line_and_column(9));
}
#[test]
fn text_buffer_delete_from_end_of_line() {
let mut buf = TextBuffer::new();
let pos = insert_str(&mut buf, 0, "select 1;");
assert_eq!(9, pos);
buf.remove_char(9);
buf.remove_char(8);
buf.remove_char(7);
assert_eq!(Some("select"), buf.line_str(0));
}
#[test]
fn text_buffer_delete_from_line() {
let mut buf = TextBuffer::new();
let pos = insert_str(&mut buf, 0, "select 61;");
assert_eq!(10, pos);
buf.remove_char(8);
assert_eq!(Some("select 1;"), buf.line_str(0));
}
#[test]
fn text_buffer_delete_through_newline() {
let mut buf = TextBuffer::new();
let pos = insert_str(&mut buf, 0, "select");
let pos = insert_str(&mut buf, pos, "\n1;");
assert_eq!(9, pos);
buf.remove_char(9);
buf.remove_char(8);
buf.remove_char(7);
buf.remove_char(6);
assert_eq!(Some("selec"), buf.line_str(0));
assert_eq!(None, buf.line_str(1));
}
#[test]
fn text_buffer_delete_through_newline_multiple_lines() {
let mut buf = TextBuffer::new();
let _ = insert_str(&mut buf, 0, "select 1,\n2,\n3;");
buf.remove_char(12); buf.remove_char(11); buf.remove_char(10);
println!("LINES: {:?}", buf.lines().collect::<Vec<_>>());
assert_eq!(Some("select 1,\n"), buf.line_str(0));
assert_eq!(Some("3;"), buf.line_str(1));
assert_eq!(None, buf.line_str(2));
}
#[test]
fn text_buffer_delete_newline_move_line_up() {
let mut buf = TextBuffer::new();
let _ = insert_str(&mut buf, 0, "select 1,\n2,\n3;");
buf.remove_char(10);
println!("LINES: {:?}", buf.lines().collect::<Vec<_>>());
println!("BUF: {:?}", buf.current);
assert_eq!(Some("select 1,2,\n"), buf.line_str(0));
assert_eq!(Some("3;"), buf.line_str(1));
assert_eq!(None, buf.line_str(2));
}
#[test]
fn text_buffer_position_multiple_empty_lines() {
let mut buf = TextBuffer::new();
let _ = insert_str(&mut buf, 0, "select 1,\n\n\n\n2;");
println!("SPANS: {:?}", buf.spans);
assert_eq!((0, 8), buf.current_line_and_column(8)); assert_eq!((0, 9), buf.current_line_and_column(9)); assert_eq!((1, 0), buf.current_line_and_column(10));
assert_eq!((2, 0), buf.current_line_and_column(11));
assert_eq!((3, 0), buf.current_line_and_column(12));
assert_eq!((4, 0), buf.current_line_and_column(13)); assert_eq!((4, 1), buf.current_line_and_column(14)); }
#[test]
fn text_buffer_position_utf8() {
let mut buf = TextBuffer::new();
let _ = insert_str(&mut buf, 0, "select '◊',\n2;");
println!("SPANS: {:?}", buf.spans);
assert_eq!((0, 7), buf.current_line_and_column(7)); assert_eq!((0, 8), buf.current_line_and_column(8)); assert_eq!((0, 9), buf.current_line_and_column(9)); assert_eq!((0, 10), buf.current_line_and_column(10)); assert_eq!((0, 11), buf.current_line_and_column(11)); }
#[test]
fn line_splitter_fits_in_single_line() {
let s = "mario luigi";
let split = LineSplitter::new(s, 80);
let got: Vec<_> = split.collect();
let expected = vec!["mario luigi"];
assert_eq!(expected, got);
}
#[test]
fn line_splitter_split_into_multiple_lines() {
let s = "mario luigi";
let split = LineSplitter::new(s, 3);
let got: Vec<_> = split.collect();
let expected = vec!["mar", "io ", "lui", "gi"];
assert_eq!(expected, got);
}
#[test]
fn line_split_emit_empty_string() {
let split = LineSplitter::new("", 80);
let got: Vec<_> = split.collect();
assert_eq!(vec![""], got);
}
}