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::state::source_rc;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use clap::Parser;
|
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)]
|
#[derive(Parser,Debug)]
|
||||||
struct FernArgs {
|
struct FernArgs {
|
||||||
@@ -98,7 +99,11 @@ fn fern_interactive() {
|
|||||||
let mut readline_err_count: u32 = 0;
|
let mut readline_err_count: u32 = 0;
|
||||||
|
|
||||||
loop { // Main loop
|
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) => {
|
Ok(line) => {
|
||||||
readline_err_count = 0;
|
readline_err_count = 0;
|
||||||
line
|
line
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ pub mod highlight;
|
|||||||
|
|
||||||
use std::path::Path;
|
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
|
/// Initialize the line editor
|
||||||
fn get_prompt() -> ShResult<String> {
|
fn get_prompt() -> ShResult<String> {
|
||||||
@@ -20,8 +20,13 @@ fn get_prompt() -> ShResult<String> {
|
|||||||
Ok(format!("\n{}",expand_prompt(&prompt)?))
|
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 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()
|
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 mode::{CmdReplay, ViInsert, ViMode, ViNormal};
|
||||||
use term::Terminal;
|
use term::Terminal;
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
@@ -16,6 +16,11 @@ pub mod vicmd;
|
|||||||
pub mod mode;
|
pub mod mode;
|
||||||
pub mod register;
|
pub mod register;
|
||||||
|
|
||||||
|
/// Unified interface for different line editing methods
|
||||||
|
pub trait Readline {
|
||||||
|
fn readline(&mut self) -> ShResult<String>;
|
||||||
|
}
|
||||||
|
|
||||||
pub struct FernVi {
|
pub struct FernVi {
|
||||||
term: Terminal,
|
term: Terminal,
|
||||||
line: LineBuf,
|
line: LineBuf,
|
||||||
@@ -25,91 +30,29 @@ pub struct FernVi {
|
|||||||
last_movement: Option<MotionCmd>,
|
last_movement: Option<MotionCmd>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FernVi {
|
impl Readline for FernVi {
|
||||||
pub fn new(prompt: Option<String>) -> Self {
|
fn readline(&mut self) -> ShResult<String> {
|
||||||
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.writeln("This is a line!");
|
||||||
Self {
|
self.term.writeln("This is a line!");
|
||||||
term: Terminal::new(),
|
self.term.writeln("This is a line!");
|
||||||
line,
|
let prompt_thing = "prompt thing -> ";
|
||||||
prompt,
|
self.term.write(prompt_thing);
|
||||||
mode: Box::new(ViInsert::new()),
|
let line = "And another!";
|
||||||
last_action: None,
|
let mut iters: usize = 0;
|
||||||
last_movement: None,
|
let mut newlines_written = 0;
|
||||||
}
|
loop {
|
||||||
}
|
iters += 1;
|
||||||
pub fn calculate_prompt_offset(&self) -> usize {
|
for i in 0..iters {
|
||||||
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 {
|
|
||||||
self.term.writeln(line);
|
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();
|
panic!()
|
||||||
|
*/
|
||||||
let pos = self.term.cursor_pos();
|
self.print_buf(false)?;
|
||||||
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);
|
|
||||||
loop {
|
loop {
|
||||||
let dims = self.term.get_dimensions()?;
|
|
||||||
self.line.update_term_dims(dims.0, dims.1);
|
|
||||||
|
|
||||||
let key = self.term.read_key();
|
let key = self.term.read_key();
|
||||||
let Some(cmd) = self.mode.handle_key(key) else {
|
let Some(cmd) = self.mode.handle_key(key) else {
|
||||||
@@ -121,9 +64,45 @@ impl FernVi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.exec_cmd(cmd.clone())?;
|
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<()> {
|
pub fn exec_cmd(&mut self, cmd: ViCmd) -> ShResult<()> {
|
||||||
if cmd.is_mode_transition() {
|
if cmd.is_mode_transition() {
|
||||||
let count = cmd.verb_count();
|
let count = cmd.verb_count();
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ use std::str::Chars;
|
|||||||
use nix::NixPath;
|
use nix::NixPath;
|
||||||
|
|
||||||
use super::keys::{KeyEvent as E, KeyCode as K, ModKeys as M};
|
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 super::vicmd::{Anchor, Bound, Dest, Direction, Motion, MotionBuilder, MotionCmd, RegisterName, TextObj, To, Verb, VerbBuilder, VerbCmd, ViCmd, Word};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
@@ -72,14 +71,8 @@ impl ViInsert {
|
|||||||
impl ViMode for ViInsert {
|
impl ViMode for ViInsert {
|
||||||
fn handle_key(&mut self, key: E) -> Option<ViCmd> {
|
fn handle_key(&mut self, key: E) -> Option<ViCmd> {
|
||||||
match key {
|
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) => {
|
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.pending_cmd.set_motion(MotionCmd(1,Motion::ForwardChar));
|
||||||
self.register_and_return()
|
self.register_and_return()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,18 @@
|
|||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use super::linebuf::TermCharBuf;
|
|
||||||
|
|
||||||
pub static REGISTERS: Mutex<Registers> = Mutex::new(Registers::new());
|
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();
|
let lock = REGISTERS.lock().unwrap();
|
||||||
lock.get_reg(ch).map(|r| r.buf().clone())
|
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();
|
let mut lock = REGISTERS.lock().unwrap();
|
||||||
if let Some(r) = lock.get_reg_mut(ch) { r.write(buf) }
|
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();
|
let mut lock = REGISTERS.lock().unwrap();
|
||||||
if let Some(r) = lock.get_reg_mut(ch) { r.append(buf) }
|
if let Some(r) = lock.get_reg_mut(ch) { r.append(buf) }
|
||||||
}
|
}
|
||||||
@@ -53,33 +51,33 @@ pub struct Registers {
|
|||||||
impl Registers {
|
impl Registers {
|
||||||
pub const fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
default: Register(TermCharBuf(vec![])),
|
default: Register(String::new()),
|
||||||
a: Register(TermCharBuf(vec![])),
|
a: Register(String::new()),
|
||||||
b: Register(TermCharBuf(vec![])),
|
b: Register(String::new()),
|
||||||
c: Register(TermCharBuf(vec![])),
|
c: Register(String::new()),
|
||||||
d: Register(TermCharBuf(vec![])),
|
d: Register(String::new()),
|
||||||
e: Register(TermCharBuf(vec![])),
|
e: Register(String::new()),
|
||||||
f: Register(TermCharBuf(vec![])),
|
f: Register(String::new()),
|
||||||
g: Register(TermCharBuf(vec![])),
|
g: Register(String::new()),
|
||||||
h: Register(TermCharBuf(vec![])),
|
h: Register(String::new()),
|
||||||
i: Register(TermCharBuf(vec![])),
|
i: Register(String::new()),
|
||||||
j: Register(TermCharBuf(vec![])),
|
j: Register(String::new()),
|
||||||
k: Register(TermCharBuf(vec![])),
|
k: Register(String::new()),
|
||||||
l: Register(TermCharBuf(vec![])),
|
l: Register(String::new()),
|
||||||
m: Register(TermCharBuf(vec![])),
|
m: Register(String::new()),
|
||||||
n: Register(TermCharBuf(vec![])),
|
n: Register(String::new()),
|
||||||
o: Register(TermCharBuf(vec![])),
|
o: Register(String::new()),
|
||||||
p: Register(TermCharBuf(vec![])),
|
p: Register(String::new()),
|
||||||
q: Register(TermCharBuf(vec![])),
|
q: Register(String::new()),
|
||||||
r: Register(TermCharBuf(vec![])),
|
r: Register(String::new()),
|
||||||
s: Register(TermCharBuf(vec![])),
|
s: Register(String::new()),
|
||||||
t: Register(TermCharBuf(vec![])),
|
t: Register(String::new()),
|
||||||
u: Register(TermCharBuf(vec![])),
|
u: Register(String::new()),
|
||||||
v: Register(TermCharBuf(vec![])),
|
v: Register(String::new()),
|
||||||
w: Register(TermCharBuf(vec![])),
|
w: Register(String::new()),
|
||||||
x: Register(TermCharBuf(vec![])),
|
x: Register(String::new()),
|
||||||
y: Register(TermCharBuf(vec![])),
|
y: Register(String::new()),
|
||||||
z: Register(TermCharBuf(vec![])),
|
z: Register(String::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn get_reg(&self, ch: Option<char>) -> Option<&Register> {
|
pub fn get_reg(&self, ch: Option<char>) -> Option<&Register> {
|
||||||
@@ -153,17 +151,16 @@ impl Registers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Default,Debug)]
|
#[derive(Clone,Default,Debug)]
|
||||||
pub struct Register(TermCharBuf);
|
pub struct Register(String);
|
||||||
|
|
||||||
impl Register {
|
impl Register {
|
||||||
pub fn buf(&self) -> &TermCharBuf {
|
pub fn buf(&self) -> &String {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
pub fn write(&mut self, buf: TermCharBuf) {
|
pub fn write(&mut self, buf: String) {
|
||||||
self.0 = buf
|
self.0 = buf
|
||||||
}
|
}
|
||||||
pub fn append(&mut self, mut buf: TermCharBuf) {
|
pub fn append(&mut self, buf: String) {
|
||||||
self.0.0.append(&mut buf.0)
|
self.0.push_str(&buf)
|
||||||
}
|
}
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
self.0.clear()
|
self.0.clear()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::{os::fd::{BorrowedFd, RawFd}, thread::sleep, time::{Duration, Instant}};
|
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::{errno::Errno, fcntl::{fcntl, FcntlArg, OFlag}, libc::{self, STDIN_FILENO}, sys::termios, unistd::{isatty, read, write}};
|
||||||
use nix::libc::{winsize, TIOCGWINSZ};
|
use nix::libc::{winsize, TIOCGWINSZ};
|
||||||
|
use unicode_width::UnicodeWidthChar;
|
||||||
use std::mem::zeroed;
|
use std::mem::zeroed;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
@@ -8,10 +9,20 @@ use crate::libsh::error::ShResult;
|
|||||||
|
|
||||||
use super::keys::{KeyCode, KeyEvent, ModKeys};
|
use super::keys::{KeyCode, KeyEvent, ModKeys};
|
||||||
|
|
||||||
|
#[derive(Default,Debug)]
|
||||||
|
struct WriteMap {
|
||||||
|
lines: usize,
|
||||||
|
cols: usize,
|
||||||
|
offset: usize
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Terminal {
|
pub struct Terminal {
|
||||||
stdin: RawFd,
|
stdin: RawFd,
|
||||||
stdout: RawFd,
|
stdout: RawFd,
|
||||||
|
recording: bool,
|
||||||
|
write_records: WriteMap,
|
||||||
|
cursor_records: WriteMap
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Terminal {
|
impl Terminal {
|
||||||
@@ -20,6 +31,13 @@ impl Terminal {
|
|||||||
Self {
|
Self {
|
||||||
stdin: STDIN_FILENO,
|
stdin: STDIN_FILENO,
|
||||||
stdout: 1,
|
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))
|
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")
|
self.write("\x1b[s")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn restore_cursor_pos(&self) {
|
pub fn restore_cursor_pos(&mut self) {
|
||||||
self.write("\x1b[u")
|
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",))
|
self.write(&format!("\x1b[{row};{col}H",))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +145,7 @@ impl Terminal {
|
|||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) if e == Errno::EAGAIN => {}
|
Err(Errno::EAGAIN) => {}
|
||||||
Err(e) => panic!("nonblocking read failed: {e}")
|
Err(e) => panic!("nonblocking read failed: {e}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,23 +169,126 @@ impl Terminal {
|
|||||||
fcntl(self.stdin, FcntlArg::F_SETFL(new_flags)).unwrap();
|
fcntl(self.stdin, FcntlArg::F_SETFL(new_flags)).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_bytes(&self, buf: &[u8]) {
|
pub fn reset_records(&mut self) {
|
||||||
Self::with_raw_mode(|| {
|
self.write_records = Default::default();
|
||||||
write(unsafe{BorrowedFd::borrow_raw(self.stdout)}, buf).expect("Failed to write to stdout");
|
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());
|
self.write_bytes(s.as_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn writeln(&self, s: &str) {
|
pub fn writeln(&mut self, s: &str) {
|
||||||
self.write(s);
|
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");
|
self.write_bytes(b"\x1b[2J\x1b[H");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,7 +346,7 @@ impl Terminal {
|
|||||||
KeyEvent(KeyCode::Null, ModKeys::empty())
|
KeyEvent(KeyCode::Null, ModKeys::empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cursor_pos(&self) -> (usize, usize) {
|
pub fn cursor_pos(&mut self) -> (usize, usize) {
|
||||||
self.write("\x1b[6n");
|
self.write("\x1b[6n");
|
||||||
let mut buf = [0u8;32];
|
let mut buf = [0u8;32];
|
||||||
let n = self.read_byte(&mut buf);
|
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)]
|
#[derive(Clone,Copy,Debug)]
|
||||||
pub struct RegisterName {
|
pub struct RegisterName {
|
||||||
@@ -30,14 +30,14 @@ impl RegisterName {
|
|||||||
pub fn count(&self) -> usize {
|
pub fn count(&self) -> usize {
|
||||||
self.count
|
self.count
|
||||||
}
|
}
|
||||||
pub fn write_to_register(&self, buf: TermCharBuf) {
|
pub fn write_to_register(&self, buf: String) {
|
||||||
if self.append {
|
if self.append {
|
||||||
append_register(self.name, buf);
|
append_register(self.name, buf);
|
||||||
} else {
|
} else {
|
||||||
write_register(self.name, buf);
|
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)
|
read_register(self.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,7 +153,7 @@ pub enum Verb {
|
|||||||
NormalMode,
|
NormalMode,
|
||||||
VisualMode,
|
VisualMode,
|
||||||
JoinLines,
|
JoinLines,
|
||||||
InsertChar(TermChar),
|
InsertChar(char),
|
||||||
Insert(String),
|
Insert(String),
|
||||||
Breakline(Anchor),
|
Breakline(Anchor),
|
||||||
Indent,
|
Indent,
|
||||||
@@ -237,7 +237,7 @@ pub enum Motion {
|
|||||||
/// forward-word, vi-end-word, vi-next-word
|
/// forward-word, vi-end-word, vi-next-word
|
||||||
ForwardWord(To, Word), // Forward until start/end of word
|
ForwardWord(To, Word), // Forward until start/end of word
|
||||||
/// character-search, character-search-backward, vi-char-search
|
/// character-search, character-search-backward, vi-char-search
|
||||||
CharSearch(Direction,Dest,TermChar),
|
CharSearch(Direction,Dest,char),
|
||||||
/// backward-char
|
/// backward-char
|
||||||
BackwardChar,
|
BackwardChar,
|
||||||
/// forward-char
|
/// forward-char
|
||||||
|
|||||||
@@ -38,8 +38,9 @@ impl Display for FernBellStyle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Default, Clone, Copy, Debug)]
|
||||||
pub enum FernEditMode {
|
pub enum FernEditMode {
|
||||||
|
#[default]
|
||||||
Vi,
|
Vi,
|
||||||
Emacs
|
Emacs
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user