prompt and buffer drawing appears functional
This commit is contained in:
@@ -24,7 +24,8 @@ use crate::signal::sig_setup;
|
||||
use crate::state::source_rc;
|
||||
use crate::prelude::*;
|
||||
use clap::Parser;
|
||||
use state::{read_vars, write_vars};
|
||||
use shopt::FernEditMode;
|
||||
use state::{read_shopts, read_vars, write_shopts, write_vars};
|
||||
|
||||
#[derive(Parser,Debug)]
|
||||
struct FernArgs {
|
||||
@@ -98,7 +99,11 @@ fn fern_interactive() {
|
||||
let mut readline_err_count: u32 = 0;
|
||||
|
||||
loop { // Main loop
|
||||
let input = match prompt::read_line() {
|
||||
let edit_mode = write_shopts(|opt| opt.query("prompt.edit_mode"))
|
||||
.unwrap()
|
||||
.map(|mode| mode.parse::<FernEditMode>().unwrap_or_default())
|
||||
.unwrap();
|
||||
let input = match prompt::read_line(edit_mode) {
|
||||
Ok(line) => {
|
||||
readline_err_count = 0;
|
||||
line
|
||||
|
||||
@@ -3,9 +3,9 @@ pub mod highlight;
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use readline::FernVi;
|
||||
use readline::{FernVi, Readline};
|
||||
|
||||
use crate::{expand::expand_prompt, libsh::error::ShResult, prelude::*, state::read_shopts};
|
||||
use crate::{expand::expand_prompt, libsh::error::ShResult, prelude::*, shopt::FernEditMode, state::read_shopts};
|
||||
|
||||
/// Initialize the line editor
|
||||
fn get_prompt() -> ShResult<String> {
|
||||
@@ -20,8 +20,13 @@ fn get_prompt() -> ShResult<String> {
|
||||
Ok(format!("\n{}",expand_prompt(&prompt)?))
|
||||
}
|
||||
|
||||
pub fn read_line() -> ShResult<String> {
|
||||
pub fn read_line(edit_mode: FernEditMode) -> ShResult<String> {
|
||||
dbg!("hi");
|
||||
let prompt = get_prompt()?;
|
||||
let mut reader = FernVi::new(Some(prompt));
|
||||
let mut reader: Box<dyn Readline> = match edit_mode {
|
||||
FernEditMode::Vi => Box::new(FernVi::new(Some(prompt))),
|
||||
FernEditMode::Emacs => todo!()
|
||||
};
|
||||
dbg!("there");
|
||||
reader.readline()
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
use std::{collections::HashMap, sync::Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
use linebuf::{strip_ansi_codes_and_escapes, LineBuf, TermCharBuf};
|
||||
use linebuf::{strip_ansi_codes_and_escapes, LineBuf};
|
||||
use mode::{CmdReplay, ViInsert, ViMode, ViNormal};
|
||||
use term::Terminal;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
@@ -16,6 +16,11 @@ pub mod vicmd;
|
||||
pub mod mode;
|
||||
pub mod register;
|
||||
|
||||
/// Unified interface for different line editing methods
|
||||
pub trait Readline {
|
||||
fn readline(&mut self) -> ShResult<String>;
|
||||
}
|
||||
|
||||
pub struct FernVi {
|
||||
term: Terminal,
|
||||
line: LineBuf,
|
||||
@@ -25,91 +30,29 @@ pub struct FernVi {
|
||||
last_movement: Option<MotionCmd>,
|
||||
}
|
||||
|
||||
impl FernVi {
|
||||
pub fn new(prompt: Option<String>) -> Self {
|
||||
let prompt = prompt.unwrap_or("$ ".styled(Style::Green | Style::Bold));
|
||||
let line = LineBuf::new().with_initial("The quick brown fox jumps over the lazy dog");//\nThe quick brown fox jumps over the lazy dog\nThe quick brown fox jumps over the lazy dog\n");
|
||||
Self {
|
||||
term: Terminal::new(),
|
||||
line,
|
||||
prompt,
|
||||
mode: Box::new(ViInsert::new()),
|
||||
last_action: None,
|
||||
last_movement: None,
|
||||
}
|
||||
}
|
||||
pub fn calculate_prompt_offset(&self) -> usize {
|
||||
if self.prompt.ends_with('\n') {
|
||||
return 0
|
||||
}
|
||||
strip_ansi_codes_and_escapes(self.prompt.lines().last().unwrap_or_default()).width()
|
||||
}
|
||||
pub fn clear_line(&self) {
|
||||
let prompt_lines = self.prompt.lines().count();
|
||||
let last_line_len = strip_ansi_codes_and_escapes(self.prompt.lines().last().unwrap_or_default()).width();
|
||||
let buf_lines = if self.prompt.ends_with('\n') {
|
||||
self.line.count_lines(last_line_len)
|
||||
} else {
|
||||
// The prompt does not end with a newline, so one of the buffer's lines overlaps with it
|
||||
self.line.count_lines(last_line_len).saturating_sub(1)
|
||||
};
|
||||
let total = prompt_lines + buf_lines;
|
||||
self.term.write_bytes(b"\r\n");
|
||||
self.term.write_bytes(format!("\r\x1b[{total}B").as_bytes());
|
||||
for _ in 0..total {
|
||||
self.term.write_bytes(b"\r\x1b[2K\x1b[1A");
|
||||
}
|
||||
self.term.write_bytes(b"\r\x1b[2K");
|
||||
}
|
||||
pub fn print_buf(&self, refresh: bool) {
|
||||
if refresh {
|
||||
self.clear_line()
|
||||
}
|
||||
let mut prompt_lines = self.prompt.lines().peekable();
|
||||
let mut last_line_len = 0;
|
||||
let lines = self.line.split_lines();
|
||||
while let Some(line) = prompt_lines.next() {
|
||||
if prompt_lines.peek().is_none() {
|
||||
last_line_len = strip_ansi_codes_and_escapes(line).width();
|
||||
self.term.write(line);
|
||||
} else {
|
||||
impl Readline for FernVi {
|
||||
fn readline(&mut self) -> ShResult<String> {
|
||||
/*
|
||||
self.term.writeln("This is a line!");
|
||||
self.term.writeln("This is a line!");
|
||||
self.term.writeln("This is a line!");
|
||||
let prompt_thing = "prompt thing -> ";
|
||||
self.term.write(prompt_thing);
|
||||
let line = "And another!";
|
||||
let mut iters: usize = 0;
|
||||
let mut newlines_written = 0;
|
||||
loop {
|
||||
iters += 1;
|
||||
for i in 0..iters {
|
||||
self.term.writeln(line);
|
||||
}
|
||||
std::thread::sleep(Duration::from_secs(1));
|
||||
self.clear_lines(iters,prompt_thing.len() + 1);
|
||||
}
|
||||
let mut lines_iter = lines.into_iter().peekable();
|
||||
|
||||
let pos = self.term.cursor_pos();
|
||||
while let Some(line) = lines_iter.next() {
|
||||
if lines_iter.peek().is_some() {
|
||||
self.term.writeln(&line);
|
||||
} else {
|
||||
self.term.write(&line);
|
||||
}
|
||||
}
|
||||
self.term.move_cursor_to(pos);
|
||||
|
||||
let (x, y) = self.line.cursor_display_coords(Some(last_line_len));
|
||||
|
||||
if y > 0 {
|
||||
self.term.write(&format!("\r\x1b[{}B", y));
|
||||
}
|
||||
|
||||
|
||||
let cursor_x = if y == 0 { x + last_line_len } else { x };
|
||||
|
||||
if cursor_x > 0 {
|
||||
self.term.write(&format!("\r\x1b[{}C", cursor_x));
|
||||
}
|
||||
self.term.write(&self.mode.cursor_style());
|
||||
}
|
||||
pub fn readline(&mut self) -> ShResult<String> {
|
||||
self.line.set_first_line_offset(self.calculate_prompt_offset());
|
||||
let dims = self.term.get_dimensions()?;
|
||||
self.line.update_term_dims(dims.0, dims.1);
|
||||
self.print_buf(false);
|
||||
panic!()
|
||||
*/
|
||||
self.print_buf(false)?;
|
||||
loop {
|
||||
let dims = self.term.get_dimensions()?;
|
||||
self.line.update_term_dims(dims.0, dims.1);
|
||||
|
||||
let key = self.term.read_key();
|
||||
let Some(cmd) = self.mode.handle_key(key) else {
|
||||
@@ -121,9 +64,45 @@ impl FernVi {
|
||||
}
|
||||
|
||||
self.exec_cmd(cmd.clone())?;
|
||||
self.print_buf(true);
|
||||
self.print_buf(true)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FernVi {
|
||||
pub fn new(prompt: Option<String>) -> Self {
|
||||
let prompt = prompt.unwrap_or("$ ".styled(Style::Green | Style::Bold));
|
||||
let line = LineBuf::new().with_initial("The quick brown fox jumps over the lazy dog");//\nThe quick brown fox jumps over the lazy dog\nThe quick brown fox jumps over the lazy dog\n");
|
||||
let term = Terminal::new();
|
||||
Self {
|
||||
term,
|
||||
line,
|
||||
prompt,
|
||||
mode: Box::new(ViInsert::new()),
|
||||
last_action: None,
|
||||
last_movement: None,
|
||||
}
|
||||
}
|
||||
pub fn print_buf(&mut self, refresh: bool) -> ShResult<()> {
|
||||
let (_,width) = self.term.get_dimensions()?;
|
||||
if refresh {
|
||||
self.term.unwrite()?;
|
||||
}
|
||||
let offset = self.calculate_prompt_offset();
|
||||
let mut line_buf = self.prompt.clone();
|
||||
line_buf.push_str(self.line.as_str());
|
||||
|
||||
self.term.recorded_write(&line_buf, offset)?;
|
||||
self.term.position_cursor(self.line.cursor_display_coords(offset,width))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub fn calculate_prompt_offset(&self) -> usize {
|
||||
if self.prompt.ends_with('\n') {
|
||||
return 0
|
||||
}
|
||||
strip_ansi_codes_and_escapes(self.prompt.lines().last().unwrap_or_default()).width() + 1 // 1 indexed
|
||||
}
|
||||
pub fn exec_cmd(&mut self, cmd: ViCmd) -> ShResult<()> {
|
||||
if cmd.is_mode_transition() {
|
||||
let count = cmd.verb_count();
|
||||
|
||||
@@ -4,7 +4,6 @@ use std::str::Chars;
|
||||
use nix::NixPath;
|
||||
|
||||
use super::keys::{KeyEvent as E, KeyCode as K, ModKeys as M};
|
||||
use super::linebuf::TermChar;
|
||||
use super::vicmd::{Anchor, Bound, Dest, Direction, Motion, MotionBuilder, MotionCmd, RegisterName, TextObj, To, Verb, VerbBuilder, VerbCmd, ViCmd, Word};
|
||||
use crate::prelude::*;
|
||||
|
||||
@@ -72,14 +71,8 @@ impl ViInsert {
|
||||
impl ViMode for ViInsert {
|
||||
fn handle_key(&mut self, key: E) -> Option<ViCmd> {
|
||||
match key {
|
||||
E(K::Grapheme(ch), M::NONE) => {
|
||||
let ch = TermChar::from(ch);
|
||||
self.pending_cmd.set_verb(VerbCmd(1,Verb::InsertChar(ch)));
|
||||
self.pending_cmd.set_motion(MotionCmd(1,Motion::ForwardChar));
|
||||
self.register_and_return()
|
||||
}
|
||||
E(K::Char(ch), M::NONE) => {
|
||||
self.pending_cmd.set_verb(VerbCmd(1,Verb::InsertChar(TermChar::from(ch))));
|
||||
self.pending_cmd.set_verb(VerbCmd(1,Verb::InsertChar(ch)));
|
||||
self.pending_cmd.set_motion(MotionCmd(1,Motion::ForwardChar));
|
||||
self.register_and_return()
|
||||
}
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
use std::sync::Mutex;
|
||||
|
||||
use super::linebuf::TermCharBuf;
|
||||
|
||||
pub static REGISTERS: Mutex<Registers> = Mutex::new(Registers::new());
|
||||
|
||||
pub fn read_register(ch: Option<char>) -> Option<TermCharBuf> {
|
||||
pub fn read_register(ch: Option<char>) -> Option<String> {
|
||||
let lock = REGISTERS.lock().unwrap();
|
||||
lock.get_reg(ch).map(|r| r.buf().clone())
|
||||
}
|
||||
|
||||
pub fn write_register(ch: Option<char>, buf: TermCharBuf) {
|
||||
pub fn write_register(ch: Option<char>, buf: String) {
|
||||
let mut lock = REGISTERS.lock().unwrap();
|
||||
if let Some(r) = lock.get_reg_mut(ch) { r.write(buf) }
|
||||
}
|
||||
|
||||
pub fn append_register(ch: Option<char>, buf: TermCharBuf) {
|
||||
pub fn append_register(ch: Option<char>, buf: String) {
|
||||
let mut lock = REGISTERS.lock().unwrap();
|
||||
if let Some(r) = lock.get_reg_mut(ch) { r.append(buf) }
|
||||
}
|
||||
@@ -53,33 +51,33 @@ pub struct Registers {
|
||||
impl Registers {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
default: Register(TermCharBuf(vec![])),
|
||||
a: Register(TermCharBuf(vec![])),
|
||||
b: Register(TermCharBuf(vec![])),
|
||||
c: Register(TermCharBuf(vec![])),
|
||||
d: Register(TermCharBuf(vec![])),
|
||||
e: Register(TermCharBuf(vec![])),
|
||||
f: Register(TermCharBuf(vec![])),
|
||||
g: Register(TermCharBuf(vec![])),
|
||||
h: Register(TermCharBuf(vec![])),
|
||||
i: Register(TermCharBuf(vec![])),
|
||||
j: Register(TermCharBuf(vec![])),
|
||||
k: Register(TermCharBuf(vec![])),
|
||||
l: Register(TermCharBuf(vec![])),
|
||||
m: Register(TermCharBuf(vec![])),
|
||||
n: Register(TermCharBuf(vec![])),
|
||||
o: Register(TermCharBuf(vec![])),
|
||||
p: Register(TermCharBuf(vec![])),
|
||||
q: Register(TermCharBuf(vec![])),
|
||||
r: Register(TermCharBuf(vec![])),
|
||||
s: Register(TermCharBuf(vec![])),
|
||||
t: Register(TermCharBuf(vec![])),
|
||||
u: Register(TermCharBuf(vec![])),
|
||||
v: Register(TermCharBuf(vec![])),
|
||||
w: Register(TermCharBuf(vec![])),
|
||||
x: Register(TermCharBuf(vec![])),
|
||||
y: Register(TermCharBuf(vec![])),
|
||||
z: Register(TermCharBuf(vec![])),
|
||||
default: Register(String::new()),
|
||||
a: Register(String::new()),
|
||||
b: Register(String::new()),
|
||||
c: Register(String::new()),
|
||||
d: Register(String::new()),
|
||||
e: Register(String::new()),
|
||||
f: Register(String::new()),
|
||||
g: Register(String::new()),
|
||||
h: Register(String::new()),
|
||||
i: Register(String::new()),
|
||||
j: Register(String::new()),
|
||||
k: Register(String::new()),
|
||||
l: Register(String::new()),
|
||||
m: Register(String::new()),
|
||||
n: Register(String::new()),
|
||||
o: Register(String::new()),
|
||||
p: Register(String::new()),
|
||||
q: Register(String::new()),
|
||||
r: Register(String::new()),
|
||||
s: Register(String::new()),
|
||||
t: Register(String::new()),
|
||||
u: Register(String::new()),
|
||||
v: Register(String::new()),
|
||||
w: Register(String::new()),
|
||||
x: Register(String::new()),
|
||||
y: Register(String::new()),
|
||||
z: Register(String::new()),
|
||||
}
|
||||
}
|
||||
pub fn get_reg(&self, ch: Option<char>) -> Option<&Register> {
|
||||
@@ -153,17 +151,16 @@ impl Registers {
|
||||
}
|
||||
|
||||
#[derive(Clone,Default,Debug)]
|
||||
pub struct Register(TermCharBuf);
|
||||
|
||||
pub struct Register(String);
|
||||
impl Register {
|
||||
pub fn buf(&self) -> &TermCharBuf {
|
||||
pub fn buf(&self) -> &String {
|
||||
&self.0
|
||||
}
|
||||
pub fn write(&mut self, buf: TermCharBuf) {
|
||||
pub fn write(&mut self, buf: String) {
|
||||
self.0 = buf
|
||||
}
|
||||
pub fn append(&mut self, mut buf: TermCharBuf) {
|
||||
self.0.0.append(&mut buf.0)
|
||||
pub fn append(&mut self, buf: String) {
|
||||
self.0.push_str(&buf)
|
||||
}
|
||||
pub fn clear(&mut self) {
|
||||
self.0.clear()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::{os::fd::{BorrowedFd, RawFd}, thread::sleep, time::{Duration, Instant}};
|
||||
use nix::{errno::Errno, fcntl::{fcntl, FcntlArg, OFlag}, libc::{self, STDIN_FILENO}, sys::termios, unistd::{isatty, read, write}};
|
||||
use nix::libc::{winsize, TIOCGWINSZ};
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
use std::mem::zeroed;
|
||||
use std::io;
|
||||
|
||||
@@ -8,10 +9,20 @@ use crate::libsh::error::ShResult;
|
||||
|
||||
use super::keys::{KeyCode, KeyEvent, ModKeys};
|
||||
|
||||
#[derive(Default,Debug)]
|
||||
struct WriteMap {
|
||||
lines: usize,
|
||||
cols: usize,
|
||||
offset: usize
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Terminal {
|
||||
stdin: RawFd,
|
||||
stdout: RawFd,
|
||||
recording: bool,
|
||||
write_records: WriteMap,
|
||||
cursor_records: WriteMap
|
||||
}
|
||||
|
||||
impl Terminal {
|
||||
@@ -20,6 +31,13 @@ impl Terminal {
|
||||
Self {
|
||||
stdin: STDIN_FILENO,
|
||||
stdout: 1,
|
||||
recording: false,
|
||||
// Records for buffer writes
|
||||
// Used to find the start of the buffer
|
||||
write_records: WriteMap::default(),
|
||||
// Records for cursor movements after writes
|
||||
// Used to find the end of the buffer
|
||||
cursor_records: WriteMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,15 +71,24 @@ impl Terminal {
|
||||
Ok((ws.ws_row as usize, ws.ws_col as usize))
|
||||
}
|
||||
|
||||
pub fn save_cursor_pos(&self) {
|
||||
pub fn start_recording(&mut self, offset: usize) {
|
||||
self.recording = true;
|
||||
self.write_records.offset = offset;
|
||||
}
|
||||
|
||||
pub fn stop_recording(&mut self) {
|
||||
self.recording = false;
|
||||
}
|
||||
|
||||
pub fn save_cursor_pos(&mut self) {
|
||||
self.write("\x1b[s")
|
||||
}
|
||||
|
||||
pub fn restore_cursor_pos(&self) {
|
||||
pub fn restore_cursor_pos(&mut self) {
|
||||
self.write("\x1b[u")
|
||||
}
|
||||
|
||||
pub fn move_cursor_to(&self, (row,col): (usize,usize)) {
|
||||
pub fn move_cursor_to(&mut self, (row,col): (usize,usize)) {
|
||||
self.write(&format!("\x1b[{row};{col}H",))
|
||||
}
|
||||
|
||||
@@ -118,7 +145,7 @@ impl Terminal {
|
||||
return n
|
||||
}
|
||||
Ok(_) => {}
|
||||
Err(e) if e == Errno::EAGAIN => {}
|
||||
Err(Errno::EAGAIN) => {}
|
||||
Err(e) => panic!("nonblocking read failed: {e}")
|
||||
}
|
||||
|
||||
@@ -142,23 +169,126 @@ impl Terminal {
|
||||
fcntl(self.stdin, FcntlArg::F_SETFL(new_flags)).unwrap();
|
||||
}
|
||||
|
||||
pub fn write_bytes(&self, buf: &[u8]) {
|
||||
Self::with_raw_mode(|| {
|
||||
pub fn reset_records(&mut self) {
|
||||
self.write_records = Default::default();
|
||||
self.cursor_records = Default::default();
|
||||
}
|
||||
|
||||
pub fn recorded_write(&mut self, buf: &str, offset: usize) -> ShResult<()> {
|
||||
self.start_recording(offset);
|
||||
self.write(buf);
|
||||
self.stop_recording();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn unwrite(&mut self) -> ShResult<()> {
|
||||
self.unposition_cursor()?;
|
||||
let WriteMap { lines, cols, offset } = self.write_records;
|
||||
for _ in 0..lines {
|
||||
self.write("\x1b[2K\x1b[A")
|
||||
}
|
||||
let col = offset;
|
||||
self.write(&format!("\x1b[{col}G\x1b[0K"));
|
||||
self.reset_records();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn position_cursor(&mut self, (lines,col): (usize,usize)) -> ShResult<()> {
|
||||
dbg!(self.cursor_pos());
|
||||
self.cursor_records.lines = lines;
|
||||
self.cursor_records.cols = col;
|
||||
self.cursor_records.offset = self.cursor_pos().1;
|
||||
|
||||
for _ in 0..lines {
|
||||
self.write("\x1b[A")
|
||||
}
|
||||
|
||||
self.write(&format!("\x1b[{col}G"));
|
||||
|
||||
dbg!("done moving");
|
||||
dbg!(self.cursor_pos());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn unposition_cursor(&mut self) ->ShResult<()> {
|
||||
dbg!(self.cursor_pos());
|
||||
let WriteMap { lines, cols, offset } = self.cursor_records;
|
||||
|
||||
for _ in 0..lines {
|
||||
self.write("\x1b[B")
|
||||
}
|
||||
|
||||
self.write(&format!("\x1b[{offset}G"));
|
||||
|
||||
dbg!("done moving back");
|
||||
dbg!(self.cursor_pos());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_bytes(&mut self, buf: &[u8]) {
|
||||
if self.recording {
|
||||
let (_, width) = self.get_dimensions().unwrap();
|
||||
let mut bytes = buf.iter().map(|&b| b as char).peekable();
|
||||
while let Some(ch) = bytes.next() {
|
||||
match ch {
|
||||
'\n' => {
|
||||
self.write_records.lines += 1;
|
||||
self.write_records.cols = 0;
|
||||
}
|
||||
'\r' => {
|
||||
self.write_records.cols = 0;
|
||||
}
|
||||
// Consume escape sequences
|
||||
'\x1b' if bytes.peek() == Some(&'[') => {
|
||||
bytes.next();
|
||||
while let Some(&ch) = bytes.peek() {
|
||||
if ch.is_ascii_alphabetic() {
|
||||
bytes.next();
|
||||
break
|
||||
} else {
|
||||
bytes.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
'\t' => {
|
||||
let tab_size = 8;
|
||||
let next_tab = tab_size - (self.write_records.cols % tab_size);
|
||||
self.write_records.cols += next_tab;
|
||||
if self.write_records.cols >= width {
|
||||
self.write_records.lines += 1;
|
||||
self.write_records.cols = 0;
|
||||
}
|
||||
}
|
||||
_ if ch.is_control() => {
|
||||
// ignore control characters for visual width
|
||||
}
|
||||
_ => {
|
||||
let ch_width = ch.width().unwrap_or(0);
|
||||
if self.write_records.cols + ch_width > width {
|
||||
self.write_records.lines += 1;
|
||||
self.write_records.cols = 0;
|
||||
}
|
||||
self.write_records.cols += ch_width;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
write(unsafe { BorrowedFd::borrow_raw(self.stdout) }, buf).expect("Failed to write to stdout");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
pub fn write(&self, s: &str) {
|
||||
pub fn write(&mut self, s: &str) {
|
||||
self.write_bytes(s.as_bytes());
|
||||
}
|
||||
|
||||
pub fn writeln(&self, s: &str) {
|
||||
pub fn writeln(&mut self, s: &str) {
|
||||
self.write(s);
|
||||
self.write_bytes(b"\r\n");
|
||||
self.write_bytes(b"\n");
|
||||
}
|
||||
|
||||
pub fn clear(&self) {
|
||||
pub fn clear(&mut self) {
|
||||
self.write_bytes(b"\x1b[2J\x1b[H");
|
||||
}
|
||||
|
||||
@@ -216,7 +346,7 @@ impl Terminal {
|
||||
KeyEvent(KeyCode::Null, ModKeys::empty())
|
||||
}
|
||||
|
||||
pub fn cursor_pos(&self) -> (usize, usize) {
|
||||
pub fn cursor_pos(&mut self) -> (usize, usize) {
|
||||
self.write("\x1b[6n");
|
||||
let mut buf = [0u8;32];
|
||||
let n = self.read_byte(&mut buf);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::{linebuf::{TermChar, TermCharBuf}, register::{append_register, read_register, write_register}};
|
||||
use super::register::{append_register, read_register, write_register};
|
||||
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
pub struct RegisterName {
|
||||
@@ -30,14 +30,14 @@ impl RegisterName {
|
||||
pub fn count(&self) -> usize {
|
||||
self.count
|
||||
}
|
||||
pub fn write_to_register(&self, buf: TermCharBuf) {
|
||||
pub fn write_to_register(&self, buf: String) {
|
||||
if self.append {
|
||||
append_register(self.name, buf);
|
||||
} else {
|
||||
write_register(self.name, buf);
|
||||
}
|
||||
}
|
||||
pub fn read_from_register(&self) -> Option<TermCharBuf> {
|
||||
pub fn read_from_register(&self) -> Option<String> {
|
||||
read_register(self.name)
|
||||
}
|
||||
}
|
||||
@@ -153,7 +153,7 @@ pub enum Verb {
|
||||
NormalMode,
|
||||
VisualMode,
|
||||
JoinLines,
|
||||
InsertChar(TermChar),
|
||||
InsertChar(char),
|
||||
Insert(String),
|
||||
Breakline(Anchor),
|
||||
Indent,
|
||||
@@ -237,7 +237,7 @@ pub enum Motion {
|
||||
/// forward-word, vi-end-word, vi-next-word
|
||||
ForwardWord(To, Word), // Forward until start/end of word
|
||||
/// character-search, character-search-backward, vi-char-search
|
||||
CharSearch(Direction,Dest,TermChar),
|
||||
CharSearch(Direction,Dest,char),
|
||||
/// backward-char
|
||||
BackwardChar,
|
||||
/// forward-char
|
||||
|
||||
@@ -38,8 +38,9 @@ impl Display for FernBellStyle {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Default, Clone, Copy, Debug)]
|
||||
pub enum FernEditMode {
|
||||
#[default]
|
||||
Vi,
|
||||
Emacs
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user