Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add multiline input support to prompt #343

Draft
wants to merge 6 commits into
base: trunk
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 41 additions & 30 deletions src/api/prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ use alloc::vec::Vec;
use core::cmp;
use vte::{Params, Parser, Perform};

const MAX_WIDTH: usize = 80;

pub struct Prompt {
pub completion: Completion,
pub history: History,
offset: usize, // Offset line by the length of the prompt string
offset: usize, // Offset input by the length of the prompt string
cursor: usize,
line: Vec<char>, // UTF-32
input: Vec<char>, // UTF-32
}

impl Prompt {
Expand All @@ -20,15 +22,15 @@ impl Prompt {
history: History::new(),
offset: 0,
cursor: 0,
line: Vec::with_capacity(80),
input: Vec::with_capacity(MAX_WIDTH),
}
}

pub fn input(&mut self, prompt: &str) -> Option<String> {
print!("{}", prompt);
self.offset = offset_from_prompt(prompt);
self.cursor = self.offset;
self.line = Vec::with_capacity(80);
self.input = Vec::with_capacity(MAX_WIDTH);
let mut parser = Parser::new();
while let Some(c) = io::stdin().read_char() {
match c {
Expand All @@ -44,7 +46,7 @@ impl Prompt {
self.update_completion();
self.update_history();
println!();
return Some(self.line.iter().collect());
return Some(self.input.iter().collect());
},
c => {
for b in c.to_string().as_bytes() {
Expand All @@ -59,7 +61,7 @@ impl Prompt {

fn update_history(&mut self) {
if let Some(i) = self.history.pos {
self.line = self.history.entries[i].chars().collect();
self.input = self.history.entries[i].chars().collect();
self.history.pos = None;
}
}
Expand All @@ -68,7 +70,7 @@ impl Prompt {
if let Some(i) = self.completion.pos {
let complete = self.completion.entries[i].chars();
self.cursor += complete.clone().count();
self.line.extend(complete);
self.input.extend(complete);
self.completion.pos = None;
self.completion.entries = Vec::new();
}
Expand All @@ -91,8 +93,8 @@ impl Prompt {
}
},
None => {
let line: String = self.line.iter().collect();
self.completion.entries = (self.completion.completer)(&line);
let input: String = self.input.iter().collect();
self.completion.entries = (self.completion.completer)(&input);
if !self.completion.entries.is_empty() {
(0, 0)
} else {
Expand Down Expand Up @@ -123,8 +125,8 @@ impl Prompt {
}
},
None => {
let line: String = self.line.iter().collect();
self.completion.entries = (self.completion.completer)(&line);
let input: String = self.input.iter().collect();
self.completion.entries = (self.completion.completer)(&input);
if !self.completion.entries.is_empty() {
(0, 0)
} else {
Expand All @@ -146,13 +148,13 @@ impl Prompt {
}
let (bs, i) = match self.history.pos {
Some(i) => (self.history.entries[i].chars().count(), cmp::max(i, 1) - 1),
None => (self.line.len(), n - 1),
None => (self.input.len(), n - 1),
};
let line = &self.history.entries[i];
let input = &self.history.entries[i];
let blank = ' '.to_string().repeat((self.offset + bs) - self.cursor);
let erase = '\x08'.to_string().repeat(bs);
print!("{}{}{}", blank, erase, line);
self.cursor = self.offset + line.chars().count();
print!("{}{}{}", blank, erase, input);
self.cursor = self.offset + input.chars().count();
self.history.pos = Some(i);
}

Expand All @@ -166,22 +168,26 @@ impl Prompt {
Some(i) => (self.history.entries[i].chars().count(), i + 1),
None => return,
};
let (pos, line) = if i < n {
let (pos, input) = if i < n {
(Some(i), self.history.entries[i].clone())
} else {
(None, self.line.iter().collect())
(None, self.input.iter().collect())
};
let erase = '\x08'.to_string().repeat(bs);
print!("{}{}", erase, line);
self.cursor = self.offset + line.chars().count();
print!("{}{}", erase, input);
self.cursor = self.offset + input.chars().count();
self.history.pos = pos;
}

fn handle_forward_key(&mut self) {
self.update_completion();
self.update_history();
if self.cursor < self.offset + self.line.len() {
print!("\x1b[1C");
if self.cursor < self.offset + self.input.len() {
if self.cursor % MAX_WIDTH == MAX_WIDTH - 1 { // Move cursor to begining of next line
print!("\x1b[1E");
} else {
print!("\x1b[1C");
}
self.cursor += 1;
}
}
Expand All @@ -190,18 +196,23 @@ impl Prompt {
self.update_completion();
self.update_history();
if self.cursor > self.offset {
print!("\x1b[1D");
if self.cursor % MAX_WIDTH == 0 { // Move cursor to end of previous line
print!("\x1b[1F");
print!("\x1b[{}G", MAX_WIDTH);
} else {
print!("\x1b[1D");
}
self.cursor -= 1;
}
}

fn handle_delete_key(&mut self) {
self.update_completion();
self.update_history();
if self.cursor < self.offset + self.line.len() {
if self.cursor < self.offset + self.input.len() {
let i = self.cursor - self.offset;
self.line.remove(i);
let s = &self.line[i..]; // UTF-32
self.input.remove(i);
let s = &self.input[i..]; // UTF-32
let n = s.len() + 1;
let s: String = s.iter().collect(); // UTF-8
print!("{} \x1b[{}D", s, n);
Expand All @@ -213,8 +224,8 @@ impl Prompt {
self.update_history();
if self.cursor > self.offset {
let i = self.cursor - self.offset - 1;
self.line.remove(i);
let s = &self.line[i..]; // UTF-32
self.input.remove(i);
let s = &self.input[i..]; // UTF-32
let n = s.len() + 1;
let s: String = s.iter().collect(); // UTF-8
print!("\x08{} \x1b[{}D", s, n);
Expand All @@ -227,8 +238,8 @@ impl Prompt {
self.update_history();
if console::is_printable(c) {
let i = self.cursor - self.offset;
self.line.insert(i, c);
let s = &self.line[i..]; // UTF-32
self.input.insert(i, c);
let s = &self.input[i..]; // UTF-32
let n = s.len();
let s: String = s.iter().collect(); // UTF-8
print!("{} \x1b[{}D", s, n);
Expand Down Expand Up @@ -279,7 +290,7 @@ pub struct Completion {
pos: Option<usize>,
}

fn empty_completer(_line: &str) -> Vec<String> {
fn empty_completer(_input: &str) -> Vec<String> {
Vec::new()
}

Expand Down
119 changes: 48 additions & 71 deletions src/sys/vga.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::api::vga::color;
use crate::sys;

use bit_field::BitField;
use core::cmp;
use core::fmt;
use core::fmt::Write;
use lazy_static::lazy_static;
Expand Down Expand Up @@ -286,74 +287,62 @@ impl Perform for Writer {
}

fn csi_dispatch(&mut self, params: &Params, _: &[u8], _: bool, c: char) {
let n = params.iter().next().map_or(1, |param| param[0] as usize);

match c {
'm' => {
let mut fg = FG;
let mut bg = BG;
for param in params.iter() {
match param[0] {
0 => {
fg = FG;
bg = BG;
},
30..=37 | 90..=97 => {
fg = color::from_ansi(param[0] as u8);
},
40..=47 | 100..=107 => {
bg = color::from_ansi((param[0] as u8) - 10);
},
_ => {},
}
match n {
0 => {
fg = FG;
bg = BG;
},
30..=37 | 90..=97 => {
fg = color::from_ansi(n as u8);
},
40..=47 | 100..=107 => {
bg = color::from_ansi((n as u8) - 10);
},
_ => {},
}
self.set_color(fg, bg);
},
'A' => { // Cursor Up
let mut n = 1;
for param in params.iter() {
n = param[0] as usize;
}
// TODO: Don't go past edge
self.writer[1] -= n;
self.cursor[1] -= n;
self.writer[1] = cmp::max(self.writer[1] - n, 0);
self.cursor[1] = cmp::max(self.cursor[1] - n, 0);
},
'B' => { // Cursor Down
let mut n = 1;
for param in params.iter() {
n = param[0] as usize;
}
// TODO: Don't go past edge
self.writer[1] += n;
self.cursor[1] += n;
self.writer[1] = cmp::min(self.writer[1] + n, BUFFER_HEIGHT - 1);
self.cursor[1] = cmp::min(self.cursor[1] + n, BUFFER_HEIGHT - 1);
},
'C' => { // Cursor Forward
let mut n = 1;
for param in params.iter() {
n = param[0] as usize;
}
// TODO: Don't go past edge
self.writer[0] += n;
self.cursor[0] += n;
self.writer[0] = cmp::min(self.writer[0] + n, BUFFER_WIDTH - 1);
self.cursor[0] = cmp::min(self.cursor[0] + n, BUFFER_WIDTH - 1);
},
'D' => { // Cursor Backward
let mut n = 1;
for param in params.iter() {
n = param[0] as usize;
}
// TODO: Don't go past edge
self.writer[0] -= n;
self.cursor[0] -= n;
self.writer[0] = cmp::max(self.writer[0] - n, 0);
self.cursor[0] = cmp::max(self.cursor[0] - n, 0);
},
'E' => { // Cursor Next Line
self.writer[0] = 0; // TODO: What should we do at the last line?
self.cursor[0] = 0;
self.writer[1] = cmp::min(self.writer[1] + n, BUFFER_HEIGHT - 1);
self.cursor[1] = cmp::min(self.cursor[1] + n, BUFFER_HEIGHT - 1);
},
'F' => { // Cursor Previous Line
self.writer[0] = 0;
self.cursor[0] = 0;
self.writer[1] = cmp::max(self.writer[1] - n, 0);
self.cursor[1] = cmp::max(self.cursor[1] - n, 0);
},
'G' => { // Cursor Horizontal Absolute
let (_, y) = self.cursor_position();
let mut x = 1;
for param in params.iter() {
x = param[0] as usize; // 1-indexed value
let x = n - 1; // 1-indexed value
let y = self.cursor_position().1;
if x < BUFFER_WIDTH {
self.set_writer_position(x, y);
self.set_cursor_position(x, y);
}
if x > BUFFER_WIDTH {
return;
}
self.set_writer_position(x - 1, y);
self.set_cursor_position(x - 1, y);
},
'H' => { // Move cursor
let mut x = 1;
Expand All @@ -372,10 +361,6 @@ impl Perform for Writer {
self.set_cursor_position(x - 1, y - 1);
},
'J' => { // Erase in Display
let mut n = 0;
for param in params.iter() {
n = param[0] as usize;
}
match n {
// TODO: 0 and 1, from cursor to begining or to end of screen
2 => self.clear_screen(),
Expand All @@ -386,10 +371,6 @@ impl Perform for Writer {
},
'K' => { // Erase in Line
let (x, y) = self.cursor_position();
let mut n = 0;
for param in params.iter() {
n = param[0] as usize;
}
match n {
0 => self.clear_row_after(x, y),
1 => return, // TODO: self.clear_row_before(x, y),
Expand All @@ -400,21 +381,17 @@ impl Perform for Writer {
self.set_cursor_position(x, y);
},
'h' => { // Enable
for param in params.iter() {
match param[0] {
12 => self.enable_echo(),
25 => self.enable_cursor(),
_ => return,
}
match n {
12 => self.enable_echo(),
25 => self.enable_cursor(),
_ => return,
}
},
'l' => { // Disable
for param in params.iter() {
match param[0] {
12 => self.disable_echo(),
25 => self.disable_cursor(),
_ => return,
}
match n {
12 => self.disable_echo(),
25 => self.disable_cursor(),
_ => return,
}
},
_ => {},
Expand Down