implemented command system
This commit is contained in:
@@ -3,7 +3,7 @@ use std::{arch::asm, os::fd::BorrowedFd};
|
|||||||
use nix::{libc::STDIN_FILENO, sys::termios::{self, Termios}, unistd::read};
|
use nix::{libc::STDIN_FILENO, sys::termios::{self, Termios}, unistd::read};
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
use crate::{libsh::error::ShResult, prelude::*};
|
use crate::{libsh::{error::ShResult, sys::sh_quit}, prelude::*};
|
||||||
|
|
||||||
#[derive(Clone,Copy,Debug)]
|
#[derive(Clone,Copy,Debug)]
|
||||||
pub enum Key {
|
pub enum Key {
|
||||||
@@ -19,6 +19,59 @@ pub enum Key {
|
|||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
|
pub enum EditAction {
|
||||||
|
Return,
|
||||||
|
Exit(i32),
|
||||||
|
ClearTerm,
|
||||||
|
ClearLine,
|
||||||
|
Signal(i32),
|
||||||
|
MoveCursorStart,
|
||||||
|
MoveCursorEnd,
|
||||||
|
MoveCursorLeft, // Ctrl + B
|
||||||
|
MoveCursorRight, // Ctrl + F
|
||||||
|
DelWordBack,
|
||||||
|
DelFromCursor,
|
||||||
|
Backspace, // The Ctrl+H version
|
||||||
|
RedrawScreen,
|
||||||
|
HistNext,
|
||||||
|
HistPrev,
|
||||||
|
InsMode(InsAction),
|
||||||
|
NormMode(NormAction),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
|
pub enum InsAction {
|
||||||
|
InsChar(char),
|
||||||
|
Backspace, // The backspace version
|
||||||
|
Delete,
|
||||||
|
Esc,
|
||||||
|
MoveLeft, // Left Arrow
|
||||||
|
MoveRight, // Right Arrow
|
||||||
|
MoveUp,
|
||||||
|
MoveDown
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
|
pub enum NormAction {
|
||||||
|
Count(usize),
|
||||||
|
Motion(Motion),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
|
pub enum Motion {
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EditAction {
|
||||||
|
pub fn is_return(&self) -> bool {
|
||||||
|
if let Self::Return = self {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Terminal {
|
pub struct Terminal {
|
||||||
stdin: RawFd,
|
stdin: RawFd,
|
||||||
@@ -123,7 +176,7 @@ pub struct FernReader {
|
|||||||
pub term: Terminal,
|
pub term: Terminal,
|
||||||
pub prompt: String,
|
pub prompt: String,
|
||||||
pub line: LineBuf,
|
pub line: LineBuf,
|
||||||
pub editor: EditMode
|
pub edit_mode: EditMode
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FernReader {
|
impl FernReader {
|
||||||
@@ -132,49 +185,176 @@ impl FernReader {
|
|||||||
term: Terminal::new(),
|
term: Terminal::new(),
|
||||||
prompt,
|
prompt,
|
||||||
line: Default::default(),
|
line: Default::default(),
|
||||||
editor: Default::default()
|
edit_mode: Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn pack_line(&self) -> String {
|
fn pack_line(&mut self) -> String {
|
||||||
self.line
|
self.line
|
||||||
.buffer
|
.buffer
|
||||||
.iter()
|
.iter()
|
||||||
.collect::<String>()
|
.collect::<String>()
|
||||||
}
|
}
|
||||||
pub fn readline(&mut self) -> ShResult<String> {
|
pub fn readline(&mut self) -> ShResult<String> {
|
||||||
self.display_line(false);
|
self.display_line(/*refresh: */ false);
|
||||||
loop {
|
loop {
|
||||||
|
let cmds = self.get_cmds();
|
||||||
|
for cmd in &cmds {
|
||||||
|
if cmd.is_return() {
|
||||||
|
self.term.write_bytes(b"\r\n");
|
||||||
|
return Ok(self.pack_line())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.process_cmds(cmds)?;
|
||||||
|
self.display_line(/* refresh: */ true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn process_cmds(&mut self, cmds: Vec<EditAction>) -> ShResult<()> {
|
||||||
|
for cmd in cmds {
|
||||||
|
match cmd {
|
||||||
|
EditAction::Exit(code) => {
|
||||||
|
self.term.write_bytes(b"\r\n");
|
||||||
|
sh_quit(code)
|
||||||
|
}
|
||||||
|
EditAction::ClearTerm => todo!(),
|
||||||
|
EditAction::ClearLine => todo!(),
|
||||||
|
EditAction::Signal(sig) => todo!(),
|
||||||
|
EditAction::MoveCursorStart => self.line.move_cursor_start(),
|
||||||
|
EditAction::MoveCursorEnd => self.line.move_cursor_end(),
|
||||||
|
EditAction::MoveCursorLeft => self.line.move_cursor_left(),
|
||||||
|
EditAction::MoveCursorRight => self.line.move_cursor_right(),
|
||||||
|
EditAction::DelWordBack => todo!(),
|
||||||
|
EditAction::DelFromCursor => self.line.del_from_cursor(),
|
||||||
|
EditAction::Backspace => todo!(),
|
||||||
|
EditAction::RedrawScreen => todo!(),
|
||||||
|
EditAction::HistNext => todo!(),
|
||||||
|
EditAction::HistPrev => todo!(),
|
||||||
|
EditAction::InsMode(ins_action) => self.process_ins_cmd(ins_action)?,
|
||||||
|
EditAction::NormMode(norm_action) => self.process_norm_cmd(norm_action)?,
|
||||||
|
EditAction::Return => unreachable!(), // handled earlier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn process_ins_cmd(&mut self, cmd: InsAction) -> ShResult<()> {
|
||||||
|
match cmd {
|
||||||
|
InsAction::InsChar(ch) => self.line.insert_at_cursor(ch),
|
||||||
|
InsAction::Backspace => self.line.backspace_at_cursor(),
|
||||||
|
InsAction::Delete => todo!(),
|
||||||
|
InsAction::Esc => todo!(),
|
||||||
|
InsAction::MoveLeft => self.line.move_cursor_left(),
|
||||||
|
InsAction::MoveRight => self.line.move_cursor_right(),
|
||||||
|
InsAction::MoveUp => todo!(),
|
||||||
|
InsAction::MoveDown => todo!(),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn process_norm_cmd(&mut self, cmd: NormAction) -> ShResult<()> {
|
||||||
|
match cmd {
|
||||||
|
NormAction::Count(num) => todo!(),
|
||||||
|
NormAction::Motion(motion) => todo!(),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn get_cmds(&mut self) -> Vec<EditAction> {
|
||||||
|
match self.edit_mode {
|
||||||
|
EditMode::Normal => todo!(),
|
||||||
|
EditMode::Insert => {
|
||||||
let key = self.read_key().unwrap();
|
let key = self.read_key().unwrap();
|
||||||
self.process_key(key);
|
self.process_key(key)
|
||||||
self.display_line(true);
|
|
||||||
if let Key::Enter = key {
|
|
||||||
self.term.write_bytes(b"\r");
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(self.pack_line())
|
|
||||||
}
|
}
|
||||||
pub fn process_key(&mut self, key: Key) {
|
pub fn process_key(&mut self, key: Key) -> Vec<EditAction> {
|
||||||
|
match self.edit_mode {
|
||||||
|
EditMode::Normal => todo!(),
|
||||||
|
EditMode::Insert => self.process_key_insert_mode(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn process_key_insert_mode(&mut self, key: Key) -> Vec<EditAction> {
|
||||||
match key {
|
match key {
|
||||||
Key::Char(ch) => {
|
Key::Char(ch) => {
|
||||||
self.line.insert_at_cursor(ch);
|
vec![EditAction::InsMode(InsAction::InsChar(ch))]
|
||||||
}
|
}
|
||||||
Key::Enter => {
|
Key::Enter => {
|
||||||
self.line.insert_at_cursor('\n');
|
vec![EditAction::Return]
|
||||||
}
|
}
|
||||||
Key::Backspace => self.line.backspace_at_cursor(),
|
Key::Backspace => {
|
||||||
Key::Esc => todo!(),
|
vec![EditAction::InsMode(InsAction::Backspace)]
|
||||||
Key::Up => todo!(),
|
}
|
||||||
Key::Down => todo!(),
|
Key::Esc => {
|
||||||
Key::Left => self.line.move_cursor_left(),
|
vec![EditAction::InsMode(InsAction::Esc)]
|
||||||
Key::Right => self.line.move_cursor_right(),
|
}
|
||||||
Key::Ctrl(ctrl) => todo!(),
|
Key::Up => {
|
||||||
Key::Unknown => todo!(),
|
vec![EditAction::InsMode(InsAction::MoveUp)]
|
||||||
|
}
|
||||||
|
Key::Down => {
|
||||||
|
vec![EditAction::InsMode(InsAction::MoveDown)]
|
||||||
|
}
|
||||||
|
Key::Left => {
|
||||||
|
vec![EditAction::InsMode(InsAction::MoveLeft)]
|
||||||
|
}
|
||||||
|
Key::Right => {
|
||||||
|
vec![EditAction::InsMode(InsAction::MoveRight)]
|
||||||
|
}
|
||||||
|
Key::Ctrl(ctrl) => self.process_ctrl(ctrl),
|
||||||
|
Key::Unknown => unimplemented!("Unknown key received: {key:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn process_ctrl(&mut self, ctrl: char) -> Vec<EditAction> {
|
||||||
|
match ctrl {
|
||||||
|
'D' => {
|
||||||
|
if self.line.buffer.is_empty() {
|
||||||
|
vec![EditAction::Exit(0)]
|
||||||
|
} else {
|
||||||
|
vec![EditAction::Return]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'C' => {
|
||||||
|
vec![EditAction::ClearLine]
|
||||||
|
}
|
||||||
|
'Z' => {
|
||||||
|
vec![EditAction::Signal(20)] // SIGTSTP
|
||||||
|
}
|
||||||
|
'A' => {
|
||||||
|
vec![EditAction::MoveCursorStart]
|
||||||
|
}
|
||||||
|
'E' => {
|
||||||
|
vec![EditAction::MoveCursorEnd]
|
||||||
|
}
|
||||||
|
'B' => {
|
||||||
|
vec![EditAction::MoveCursorLeft]
|
||||||
|
}
|
||||||
|
'F' => {
|
||||||
|
vec![EditAction::MoveCursorRight]
|
||||||
|
}
|
||||||
|
'U' => {
|
||||||
|
vec![EditAction::ClearLine]
|
||||||
|
}
|
||||||
|
'W' => {
|
||||||
|
vec![EditAction::DelWordBack]
|
||||||
|
}
|
||||||
|
'K' => {
|
||||||
|
vec![EditAction::DelFromCursor]
|
||||||
|
}
|
||||||
|
'H' => {
|
||||||
|
vec![EditAction::Backspace]
|
||||||
|
}
|
||||||
|
'L' => {
|
||||||
|
vec![EditAction::RedrawScreen]
|
||||||
|
}
|
||||||
|
'N' => {
|
||||||
|
vec![EditAction::HistNext]
|
||||||
|
}
|
||||||
|
'P' => {
|
||||||
|
vec![EditAction::HistPrev]
|
||||||
|
}
|
||||||
|
_ => unimplemented!("Unhandled control character: {ctrl}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn clear_line(&self) {
|
fn clear_line(&self) {
|
||||||
let prompt_lines = self.prompt.lines().count();
|
let prompt_lines = self.prompt.lines().count();
|
||||||
let buf_lines = self.line.count_lines().saturating_sub(1); // One of the buffer's lines will overlap with the prompt
|
let buf_lines = self.line.count_lines().saturating_sub(1); // One of the buffer's lines will overlap with the prompt. probably.
|
||||||
let total = prompt_lines + buf_lines;
|
let total = prompt_lines + buf_lines;
|
||||||
self.term.write_bytes(b"\r\n");
|
self.term.write_bytes(b"\r\n");
|
||||||
for _ in 0..total {
|
for _ in 0..total {
|
||||||
@@ -182,7 +362,7 @@ impl FernReader {
|
|||||||
}
|
}
|
||||||
self.term.write_bytes(b"\r\x1b[2K");
|
self.term.write_bytes(b"\r\x1b[2K");
|
||||||
}
|
}
|
||||||
fn display_line(&self, refresh: bool) {
|
fn display_line(&mut self, refresh: bool) {
|
||||||
if refresh {
|
if refresh {
|
||||||
self.clear_line();
|
self.clear_line();
|
||||||
}
|
}
|
||||||
@@ -196,7 +376,8 @@ impl FernReader {
|
|||||||
self.term.writeln(line);
|
self.term.writeln(line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.term.write(&self.pack_line());
|
let line = self.pack_line();
|
||||||
|
self.term.write(&line);
|
||||||
|
|
||||||
let cursor_offset = self.line.cursor + last_line_len;
|
let cursor_offset = self.line.cursor + last_line_len;
|
||||||
self.term.write_bytes(format!("\r\x1b[{}C", cursor_offset).as_bytes());
|
self.term.write_bytes(format!("\r\x1b[{}C", cursor_offset).as_bytes());
|
||||||
@@ -267,9 +448,21 @@ impl LineBuf {
|
|||||||
pub fn move_cursor_left(&mut self) {
|
pub fn move_cursor_left(&mut self) {
|
||||||
self.cursor = self.cursor.saturating_sub(1);
|
self.cursor = self.cursor.saturating_sub(1);
|
||||||
}
|
}
|
||||||
|
pub fn move_cursor_start(&mut self) {
|
||||||
|
self.cursor = 0;
|
||||||
|
}
|
||||||
|
pub fn move_cursor_end(&mut self) {
|
||||||
|
self.cursor = self.buffer.len();
|
||||||
|
}
|
||||||
pub fn move_cursor_right(&mut self) {
|
pub fn move_cursor_right(&mut self) {
|
||||||
|
if self.cursor == self.buffer.len() {
|
||||||
|
return
|
||||||
|
}
|
||||||
self.cursor = self.cursor.saturating_add(1);
|
self.cursor = self.cursor.saturating_add(1);
|
||||||
}
|
}
|
||||||
|
pub fn del_from_cursor(&mut self) {
|
||||||
|
self.buffer.truncate(self.cursor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn strip_ansi_codes(s: &str) -> String {
|
pub fn strip_ansi_codes(s: &str) -> String {
|
||||||
|
|||||||
Reference in New Issue
Block a user