separated drawing to screen and internals
This commit is contained in:
parent
cfc3b64f53
commit
1822cf4237
|
@ -1,3 +1,7 @@
|
|||
# v1.6 - 17 September 2021
|
||||
|
||||
Massive redesign, completely separated drawing to screen and internals
|
||||
|
||||
# v1.5 - 24 August 2021
|
||||
|
||||
Proper display of utf8 character width.
|
||||
|
|
|
@ -27,7 +27,7 @@ it takes less than a minute on my machine!
|
|||
- [ ] Help menu
|
||||
- [ ] Config
|
||||
- [ ] Autosave feature
|
||||
- [ ] Better utf-8 support
|
||||
- [x] Better utf-8 support
|
||||
- [ ] Mouse support
|
||||
- [ ] Copy-paste support with xclip or similar
|
||||
- [ ] Search-and-replace
|
||||
|
|
471
src/editor.rs
471
src/editor.rs
|
@ -1,17 +1,18 @@
|
|||
use crate::Colorscheme;
|
||||
use crate::Config;
|
||||
use crate::Document;
|
||||
use crate::Events;
|
||||
use crate::Row;
|
||||
use crate::SharedInfo;
|
||||
use crate::Statusbar;
|
||||
use crate::Terminal;
|
||||
|
||||
use std::cmp;
|
||||
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::sync::Arc;
|
||||
|
||||
use termion::event::Key;
|
||||
use tokio::select;
|
||||
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Position {
|
||||
|
@ -20,21 +21,24 @@ pub struct Position {
|
|||
}
|
||||
|
||||
pub struct Editor {
|
||||
terminal: Terminal,
|
||||
document: Document,
|
||||
statusbar: Statusbar,
|
||||
// terminal: Terminal,
|
||||
// pub document: Arc<RwLock<Document>>,
|
||||
// pub statusbar: Arc<RwLock<Statusbar>>,
|
||||
// pub cursor_position: Arc<RwLock<Position>>,
|
||||
pub shared: Arc<RwLock<SharedInfo>>,
|
||||
|
||||
cursor_position: Position,
|
||||
offset: Position,
|
||||
inputreceiver: UnboundedReceiver<Key>,
|
||||
eventsender: UnboundedSender<Events>,
|
||||
|
||||
// offset: Position,
|
||||
rememberx: usize,
|
||||
quitting: bool,
|
||||
|
||||
colorscheme: Colorscheme,
|
||||
config: Config,
|
||||
// colorscheme: Colorscheme,
|
||||
// config: Config,
|
||||
}
|
||||
|
||||
impl Editor {
|
||||
pub fn default() -> Self {
|
||||
pub fn default(irt: UnboundedReceiver<Key>, st: UnboundedSender<Events>) -> Self {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let document = if args.len() > 1 {
|
||||
let filename = &args[1];
|
||||
|
@ -45,178 +49,85 @@ impl Editor {
|
|||
Document::default()
|
||||
};
|
||||
|
||||
let terminal = Terminal::default().expect("Failed to initialize terminal");
|
||||
let tsd = terminal.sender.clone();
|
||||
// let terminal = Terminal::default().expect("Failed to initialize terminal");
|
||||
|
||||
let shared = SharedInfo {
|
||||
cpos: Position::default(),
|
||||
stbar: Statusbar::new(st.clone()),
|
||||
doc: document,
|
||||
};
|
||||
|
||||
Self {
|
||||
terminal,
|
||||
document,
|
||||
statusbar: Statusbar::new(tsd),
|
||||
shared: Arc::new(RwLock::new(shared)),
|
||||
inputreceiver: irt,
|
||||
eventsender: st,
|
||||
|
||||
cursor_position: Position::default(),
|
||||
offset: Position::default(),
|
||||
// offset: Position::default(),
|
||||
rememberx: 0,
|
||||
quitting: false,
|
||||
|
||||
colorscheme: Colorscheme::default(),
|
||||
config: Config::default(),
|
||||
// colorscheme: Colorscheme::default(),
|
||||
// config: Config::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) -> Result<(), Box<dyn Error>> {
|
||||
fn refresh_screen(&self) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
self.eventsender.send(Events::Refresh)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn refresh_status(&self) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
self.eventsender.send(Events::RefreshStatus)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn quit(&self) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
self.eventsender.send(Events::Quit)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn scroll(&self) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
self.eventsender.send(Events::Scroll)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
// let ticker = tick(Duration::from_millis(500));
|
||||
|
||||
self.refresh_screen()?;
|
||||
|
||||
loop {
|
||||
select! {
|
||||
event = self.terminal.receiver.recv() => {match event.unwrap() {
|
||||
Events::Quit => {
|
||||
self.terminal.reset_screen();
|
||||
break;
|
||||
},
|
||||
Events::Refresh => {self.refresh_screen()?;}
|
||||
Events::RefreshStatus => {self.refresh_status()?;}
|
||||
}}
|
||||
k = self.terminal.inputreceiver.recv() => {self.process_keypress(k.unwrap()).await?; self.refresh_screen()?;}
|
||||
// recv(ticker) -> _ => {self.refresh_screen();}
|
||||
match self.inputreceiver.recv().await {
|
||||
Some(k) => {
|
||||
self.process_keypress(k).await?;
|
||||
self.refresh_screen()?;
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn calculate_pos(& self) -> Position {
|
||||
async fn process_keypress(&mut self, k: Key) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let sharedarc = self.shared.clone();
|
||||
let mut sharedlock = sharedarc.write().await;
|
||||
let shared = &mut *sharedlock;
|
||||
|
||||
let Position { x, y } = self.cursor_position;
|
||||
let Position { x: ox, y: oy } = self.offset;
|
||||
let mut doc = &mut shared.doc;
|
||||
let cpos = &mut shared.cpos;
|
||||
let stbar = &mut shared.stbar;
|
||||
|
||||
let w = self.terminal.size().width as usize;
|
||||
|
||||
let maxw = self.config.maxwidth;
|
||||
|
||||
let wpad = (w.saturating_sub(maxw)) / 2;
|
||||
let ypad = self.config.vertical_padding;
|
||||
|
||||
let mut linewidth = 0;
|
||||
|
||||
if let Some(r) = self.document.row(y) {
|
||||
linewidth = r.width(ox, x);
|
||||
}
|
||||
|
||||
Position {
|
||||
x: linewidth + wpad,
|
||||
y: (y + ypad).saturating_sub(oy),
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh_screen(&mut self) -> Result<(), Box<dyn Error>> {
|
||||
Terminal::cursor_hide();
|
||||
self.draw_rows();
|
||||
self.draw_info();
|
||||
|
||||
self.terminal.cursor_position(&self.calculate_pos());
|
||||
Terminal::cursor_show();
|
||||
self.terminal.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn refresh_status(&mut self) -> Result<(), Box<dyn Error>> {
|
||||
Terminal::cursor_hide();
|
||||
|
||||
|
||||
|
||||
let s = self.terminal.size();
|
||||
|
||||
let w = s.width as usize;
|
||||
let h = s.height as usize - 2;
|
||||
|
||||
|
||||
self.terminal.cursor_position(&Position { x: 0, y: h });
|
||||
|
||||
self.draw_info();
|
||||
|
||||
self.terminal.cursor_position(&self.calculate_pos());
|
||||
Terminal::cursor_show();
|
||||
self.terminal.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw_row(&self, row: &Row) {
|
||||
let w = self.terminal.size().width as usize;
|
||||
|
||||
let maxw = self.config.maxwidth;
|
||||
|
||||
let pad = (w.saturating_sub(maxw)) / 2;
|
||||
|
||||
let start = self.offset.x;
|
||||
let end = self.offset.x + w - pad * 2;
|
||||
let r = row.render(start, end);
|
||||
let space = &" ".repeat(pad);
|
||||
println!("{}{}\r", space, r)
|
||||
}
|
||||
|
||||
fn draw_rows(&self) {
|
||||
Terminal::cursor_reset();
|
||||
Terminal::set_text_color(self.colorscheme.text);
|
||||
Terminal::set_bg_color(self.colorscheme.background);
|
||||
|
||||
let h = self.terminal.size().height as usize;
|
||||
// let w = self.terminal.size().width as usize;
|
||||
|
||||
let vertical_pad = self.config.vertical_padding.saturating_sub(self.offset.y);
|
||||
|
||||
let trueh = h.saturating_sub(vertical_pad);
|
||||
|
||||
for _ in 0..vertical_pad {
|
||||
Terminal::clear_current_line();
|
||||
println!("\r")
|
||||
}
|
||||
|
||||
for terminal_row in 0..trueh - 2 {
|
||||
Terminal::clear_current_line();
|
||||
if let Some(row) = self.document.row(
|
||||
terminal_row as usize + self.offset.y.saturating_sub(self.config.vertical_padding),
|
||||
) {
|
||||
self.draw_row(row);
|
||||
} else {
|
||||
println!("\r");
|
||||
}
|
||||
}
|
||||
Terminal::reset_text_color();
|
||||
Terminal::reset_bg_color();
|
||||
}
|
||||
|
||||
fn draw_info(&self) {
|
||||
Terminal::set_bg_color(self.colorscheme.status_background);
|
||||
Terminal::set_text_color(self.colorscheme.status_text);
|
||||
|
||||
let width = self.terminal.size().width as usize;
|
||||
|
||||
let status = self.statusbar.status();
|
||||
|
||||
let status2 = self
|
||||
.statusbar
|
||||
.status2(&self.document, &self.cursor_position, width);
|
||||
|
||||
Terminal::clear_current_line();
|
||||
println!("{}\r", status);
|
||||
Terminal::clear_current_line();
|
||||
print!("{}\r", status2);
|
||||
|
||||
Terminal::reset_text_color();
|
||||
Terminal::reset_bg_color();
|
||||
}
|
||||
|
||||
async fn process_keypress(&mut self, k: Key) -> Result<(), Box<dyn Error>> {
|
||||
match k {
|
||||
Key::Ctrl('q') => {
|
||||
if self.document.dirty && !self.quitting {
|
||||
self.statusbar.change_status(
|
||||
if doc.dirty && !self.quitting {
|
||||
stbar.change_status(
|
||||
&"Unsaved changes. Are you sure you want to quit? Press ctrl+q again.",
|
||||
)?;
|
||||
self.quitting = true;
|
||||
} else {
|
||||
self.terminal.quit()?;
|
||||
self.quit()?;
|
||||
}
|
||||
|
||||
return Ok(()); // Returing instantly, so that the self.quitting variable won't be reset to false
|
||||
|
@ -224,23 +135,29 @@ impl Editor {
|
|||
}
|
||||
|
||||
Key::Ctrl('s') => {
|
||||
if self.document.dirty {
|
||||
match self.document.save() {
|
||||
if doc.dirty {
|
||||
match doc.save() {
|
||||
Ok(true) => {
|
||||
self.statusbar
|
||||
.change_status("Document saved successfully.")?;
|
||||
stbar.change_status("Document saved successfully.")?;
|
||||
}
|
||||
Ok(false) => {
|
||||
|
||||
drop(sharedlock); // DROP THE LOCK BEFORE PROMPTING
|
||||
|
||||
let filename = self
|
||||
.prompt(&String::from("Input filename"), &String::from(""))
|
||||
.await?;
|
||||
|
||||
let sharedarc = self.shared.clone();
|
||||
let shared = &mut *(*sharedarc).write().await;
|
||||
let mut doc = &mut shared.doc;
|
||||
let stbar = &mut shared.stbar;
|
||||
|
||||
self.document.filename = filename;
|
||||
if let Ok(true) = self.document.save() {
|
||||
self.statusbar
|
||||
.change_status("Document saved successfully.")?;
|
||||
doc.filename = filename;
|
||||
if let Ok(true) = doc.save() {
|
||||
stbar.change_status("Document saved successfully.")?;
|
||||
} else {
|
||||
self.statusbar.change_status("Operation canceled.")?;
|
||||
stbar.change_status("Operation canceled.")?;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
|
@ -248,60 +165,74 @@ impl Editor {
|
|||
} // TODO edit stuff
|
||||
};
|
||||
} else {
|
||||
self.statusbar.change_status("No need to save, pal!")?;
|
||||
stbar.change_status("No need to save, pal!")?;
|
||||
}
|
||||
}
|
||||
|
||||
Key::Ctrl('o') => {
|
||||
|
||||
drop(sharedlock); // DROP THE LOCK BEFORE PROMPTING
|
||||
|
||||
let filename = self
|
||||
.prompt(&String::from("Input filename"), &String::from(""))
|
||||
.await?;
|
||||
|
||||
let sharedarc = self.shared.clone();
|
||||
let shared = &mut *(*sharedarc).write().await;
|
||||
let mut doc = &mut shared.doc;
|
||||
let cpos = &mut shared.cpos;
|
||||
let stbar = &mut shared.stbar;
|
||||
|
||||
match filename {
|
||||
Some(fl) if !fl.is_empty() => {
|
||||
self.document = Document::open(&fl).unwrap_or_default();
|
||||
self.document.filename = Some(fl);
|
||||
self.statusbar.change_status("Opening file")?;
|
||||
self.cursor_position = Position { x: 0, y: 0 };
|
||||
*doc = Document::open(&fl).unwrap_or_default();
|
||||
doc.filename = Some(fl);
|
||||
stbar.change_status("Opening file")?;
|
||||
*cpos = Position { x: 0, y: 0 };
|
||||
}
|
||||
_ => {
|
||||
self.statusbar.change_status("Operation canceled.")?;
|
||||
stbar.change_status("Operation canceled.")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Key::Char(c) => {
|
||||
self.document.dirty = true;
|
||||
self.document.insert(&self.cursor_position, c);
|
||||
self.move_cursor(Key::Right);
|
||||
doc.dirty = true;
|
||||
doc.insert(&cpos, c);
|
||||
self.move_cursor(Key::Right, doc, cpos).await?;
|
||||
}
|
||||
Key::Delete => {
|
||||
self.document.dirty = true;
|
||||
self.document.delete(&self.cursor_position)
|
||||
doc.dirty = true;
|
||||
doc.delete(&cpos)
|
||||
}
|
||||
Key::Backspace => {
|
||||
if self.cursor_position.x > 0 || self.cursor_position.y > 0 {
|
||||
self.document.dirty = true;
|
||||
self.move_cursor(Key::Left);
|
||||
self.document.delete(&self.cursor_position);
|
||||
if cpos.x > 0 || cpos.y > 0 {
|
||||
doc.dirty = true;
|
||||
self.move_cursor(Key::Left, doc, cpos).await?;
|
||||
doc.delete(&cpos);
|
||||
}
|
||||
}
|
||||
Key::Up | Key::Down | Key::Left | Key::Right => self.move_cursor(k),
|
||||
Key::Up | Key::Down | Key::Left | Key::Right => {
|
||||
self.move_cursor(k, doc, cpos).await?;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
self.quitting = false;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn savefile(&self) {
|
||||
self.document.filecheck();
|
||||
}
|
||||
// fn savefile(&self) {
|
||||
// doc.filecheck();
|
||||
// }
|
||||
|
||||
fn move_cursor(&mut self, key: Key) {
|
||||
let Position { mut y, mut x } = self.cursor_position;
|
||||
async fn move_cursor(&mut self, key: Key, doc: &Document, cpos: &mut Position) -> Result<(), Box<dyn Error + Sync + Send>> {
|
||||
// let doc = (*docarc).read().await;
|
||||
// let mut cpos = (*curarc).write().await;
|
||||
|
||||
let w = self.document.row(y).map_or(0, Row::len);
|
||||
let h = self.document.len();
|
||||
let Position { mut y, mut x } = *cpos;
|
||||
|
||||
let w = doc.row(y).map_or(0, Row::len);
|
||||
let h = doc.len();
|
||||
|
||||
match key {
|
||||
Key::Up => y = y.saturating_sub(1),
|
||||
|
@ -315,7 +246,7 @@ impl Editor {
|
|||
x -= 1;
|
||||
} else if y > 0 {
|
||||
y -= 1;
|
||||
x = self.document.row(y).map_or(0, Row::len);
|
||||
x = doc.row(y).map_or(0, Row::len);
|
||||
}
|
||||
self.rememberx = x;
|
||||
}
|
||||
|
@ -330,7 +261,7 @@ impl Editor {
|
|||
}
|
||||
_ => (),
|
||||
}
|
||||
let neww = self.document.row(y).map_or(0, Row::len);
|
||||
let neww = doc.row(y).map_or(0, Row::len);
|
||||
|
||||
if neww <= self.rememberx {
|
||||
x = neww;
|
||||
|
@ -338,103 +269,91 @@ impl Editor {
|
|||
x = self.rememberx
|
||||
};
|
||||
|
||||
self.cursor_position = Position { x, y };
|
||||
self.scroll();
|
||||
}
|
||||
|
||||
fn scroll(&mut self) {
|
||||
let Position { x, y } = self.cursor_position;
|
||||
let s = self.terminal.size();
|
||||
let w = s.width as usize;
|
||||
let h = s.height as usize - 2;
|
||||
|
||||
// let yscroll = self.config.vertical_scrollspace;
|
||||
// let xscroll = self.config.horizontal_scrollspace;
|
||||
|
||||
let ypad = self.config.vertical_padding;
|
||||
let maxw = self.config.maxwidth;
|
||||
|
||||
let mut offset = &mut self.offset;
|
||||
|
||||
let true_w = cmp::min(w, maxw);
|
||||
|
||||
if y - offset.y.saturating_sub(ypad) + 1 > h - ypad.saturating_sub(offset.y) {
|
||||
offset.y = y.saturating_sub(h - ypad).saturating_add(1);
|
||||
} else if y < offset.y {
|
||||
offset.y = y;
|
||||
}
|
||||
|
||||
if x + 1 > offset.x.saturating_add(true_w) {
|
||||
offset.x = x.saturating_sub(true_w).saturating_add(1);
|
||||
} else if x < offset.x {
|
||||
offset.x = x;
|
||||
}
|
||||
*cpos = Position { x, y };
|
||||
self.scroll()
|
||||
}
|
||||
|
||||
async fn prompt(
|
||||
&mut self,
|
||||
prompt_message: &String,
|
||||
original: &String,
|
||||
) -> Result<Option<String>, Box<dyn Error>> {
|
||||
self.statusbar.prompting = true;
|
||||
self.statusbar.status_stat = prompt_message.clone();
|
||||
) -> Result<Option<String>, Box<dyn Error + Send + Sync>> {
|
||||
let sharedarc = self.shared.clone();
|
||||
let mut sharedlock = sharedarc.write().await;
|
||||
let shared = &mut *sharedlock;
|
||||
|
||||
let stbar = &mut shared.stbar;
|
||||
|
||||
stbar.prompting = true;
|
||||
stbar.status_stat = prompt_message.clone();
|
||||
|
||||
let mut pos = original.len();
|
||||
|
||||
let mut r = Row::from(&original[..]);
|
||||
|
||||
stbar.status2_stat = r.render(0, r.len());
|
||||
|
||||
drop(sharedlock); // DROP BEFORE REFRESHING
|
||||
|
||||
self.refresh_status()?;
|
||||
|
||||
loop {
|
||||
self.statusbar.status2_stat = r.render(0, r.len());
|
||||
self.refresh_status()?;
|
||||
select!(
|
||||
k = self.terminal.inputreceiver.recv() => {
|
||||
match k.unwrap() {
|
||||
k = self.inputreceiver.recv() => {
|
||||
{
|
||||
let mut sharedlock = sharedarc.write().await;
|
||||
let shared = &mut *sharedlock;
|
||||
let stbar = &mut shared.stbar;
|
||||
|
||||
Key::Left => {
|
||||
if pos > 0 {
|
||||
pos = pos - 1
|
||||
match k.unwrap() {
|
||||
|
||||
Key::Left => {
|
||||
if pos > 0 {
|
||||
pos = pos - 1
|
||||
}
|
||||
}
|
||||
|
||||
Key::Right => {
|
||||
if pos < r.len() {
|
||||
pos = pos + 1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Key::Char('\n')=> {
|
||||
stbar.prompting = false;
|
||||
if r.is_empty() {
|
||||
return Ok(None)
|
||||
} else {
|
||||
return Ok(
|
||||
Some(r.render(0, r.len()))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Key::Esc => {
|
||||
stbar.prompting = false;
|
||||
return Ok(None)
|
||||
}
|
||||
|
||||
Key::Backspace => {
|
||||
if pos > 0 {
|
||||
r.delete(pos-1);
|
||||
pos = pos - 1
|
||||
}
|
||||
}
|
||||
|
||||
Key::Char(c) => {
|
||||
r.insert(pos, c);
|
||||
pos = pos + 1
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
stbar.status2_stat = r.render(0, r.len());
|
||||
}
|
||||
|
||||
Key::Right => {
|
||||
if pos < r.len() {
|
||||
pos = pos + 1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Key::Char('\n')=> {
|
||||
self.statusbar.prompting = false;
|
||||
if r.is_empty() {
|
||||
return Ok(None)
|
||||
} else {
|
||||
return Ok(
|
||||
Some(r.render(0, r.len()))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Key::Esc => {
|
||||
self.statusbar.prompting = false;
|
||||
return Ok(None)
|
||||
}
|
||||
|
||||
Key::Backspace => {
|
||||
if pos > 0 {
|
||||
r.delete(pos-1);
|
||||
pos = pos - 1
|
||||
}
|
||||
}
|
||||
|
||||
Key::Char(c) => {
|
||||
r.insert(pos, c);
|
||||
pos = pos + 1
|
||||
}
|
||||
|
||||
_ => {}
|
||||
|
||||
}
|
||||
self.refresh_status()?; // everything is dropped automatically, refresh again after each character
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,4 +3,5 @@ pub enum Events {
|
|||
Refresh,
|
||||
RefreshStatus,
|
||||
Quit,
|
||||
Scroll,
|
||||
}
|
||||
|
|
69
src/main.rs
69
src/main.rs
|
@ -7,22 +7,79 @@ mod events;
|
|||
mod row;
|
||||
mod statusbar;
|
||||
mod terminal;
|
||||
mod helpmenu;
|
||||
mod renderer;
|
||||
|
||||
use renderer::Renderer;
|
||||
use editor::Editor;
|
||||
pub use colorscheme::Colorscheme;
|
||||
pub use config::Config;
|
||||
pub use document::Document;
|
||||
use editor::Editor;
|
||||
pub use editor::Position;
|
||||
pub use events::Events;
|
||||
pub use row::Row;
|
||||
pub use statusbar::Statusbar;
|
||||
pub use terminal::Terminal;
|
||||
|
||||
|
||||
use std::error::Error;
|
||||
use tokio::sync::mpsc::{unbounded_channel};
|
||||
use tokio::select;
|
||||
|
||||
|
||||
pub struct SharedInfo {
|
||||
cpos : Position,
|
||||
doc : Document,
|
||||
stbar : Statusbar
|
||||
}
|
||||
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let mut edit = Editor::default();
|
||||
match edit.run().await {
|
||||
Err(e) => println!("FUUUUUUUUUUCK!!"),
|
||||
Ok(()) => (),
|
||||
}
|
||||
|
||||
|
||||
let (st, rt) = unbounded_channel();
|
||||
let (ist, irt) = unbounded_channel();
|
||||
|
||||
|
||||
let mut edit = Editor::default(irt, st.clone());
|
||||
|
||||
let shared = edit.shared.clone();
|
||||
|
||||
|
||||
let mut render = Renderer::default(st, ist, rt, shared);
|
||||
|
||||
// TODO: ERROR CARCHING INSIDE TOKIO THREADS
|
||||
// tokio::task::JoinHandle<std::result::Result<(), Box<dyn Error + Send + Sync>>>
|
||||
|
||||
|
||||
let t1 : tokio::task::JoinHandle<std::result::Result<(), Box<dyn Error + Send + Sync>>> = tokio::spawn(async move {
|
||||
render.run().await?;
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let t2 : tokio::task::JoinHandle<std::result::Result<(), Box<dyn Error + Send + Sync>>> = tokio::spawn(async move {
|
||||
edit.run().await?;
|
||||
Ok(())
|
||||
});
|
||||
|
||||
|
||||
|
||||
// sleep(Duration::from_secs(10)).await;
|
||||
select!(
|
||||
k = t1 => {
|
||||
match k {
|
||||
Err(e) => println!("Catastrophic error: \n{:?}", e), // TODO: crash handling
|
||||
Ok(_) => return
|
||||
}
|
||||
}
|
||||
|
||||
k = t2 => {
|
||||
match k {
|
||||
Err(e) => println!("Catastrophic error: \n{:?}", e), // TODO: crash handling
|
||||
Ok(_) => return
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,249 @@
|
|||
use crate::Colorscheme;
|
||||
use crate::Config;
|
||||
use crate::Events;
|
||||
use crate::Position;
|
||||
use crate::Row;
|
||||
use crate::SharedInfo;
|
||||
use crate::Terminal;
|
||||
|
||||
use std::cmp;
|
||||
use std::error::Error;
|
||||
use std::sync::Arc;
|
||||
|
||||
use termion::event::Key;
|
||||
|
||||
use tokio::select;
|
||||
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
pub struct Renderer {
|
||||
shared: Arc<RwLock<SharedInfo>>,
|
||||
terminal: Terminal,
|
||||
|
||||
// render_position: Position,
|
||||
offset: Position,
|
||||
colorscheme: Colorscheme,
|
||||
|
||||
pub receiver: UnboundedReceiver<Events>,
|
||||
config: Config,
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
pub fn default(
|
||||
st: UnboundedSender<Events>,
|
||||
ist: UnboundedSender<Key>,
|
||||
rt: UnboundedReceiver<Events>,
|
||||
shared: Arc<RwLock<SharedInfo>>,
|
||||
) -> Self {
|
||||
// let (st, rt) = unbounded_channel();
|
||||
// let (ist, irt) = unbounded_channel();
|
||||
|
||||
let terminal = Terminal::default(st.clone(), ist).expect("Failed to initialize terminal");
|
||||
|
||||
// let editor = Editor::default(irt, st);
|
||||
|
||||
Self {
|
||||
shared,
|
||||
terminal,
|
||||
|
||||
receiver: rt,
|
||||
|
||||
// render_position: Position::default(),
|
||||
offset: Position::default(),
|
||||
colorscheme: Colorscheme::default(),
|
||||
|
||||
config: Config::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
self.refresh_screen().await?;
|
||||
|
||||
loop {
|
||||
select! {
|
||||
event = self.receiver.recv() => {match event.unwrap() {
|
||||
Events::Quit => {
|
||||
self.terminal.reset_screen();
|
||||
break;
|
||||
},
|
||||
Events::Refresh => {self.refresh_screen().await?;}
|
||||
Events::RefreshStatus => {self.refresh_status().await?;}
|
||||
Events::Scroll => {self.scroll().await;}
|
||||
}}
|
||||
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn calculate_pos(&self) -> Position {
|
||||
let sharedarc = self.shared.clone();
|
||||
|
||||
let shared = (*sharedarc).read().await;
|
||||
|
||||
let doc = &shared.doc;
|
||||
let cpos = &shared.cpos;
|
||||
|
||||
let Position { x, y } = *cpos;
|
||||
let Position { x: ox, y: oy } = self.offset;
|
||||
|
||||
let w = self.terminal.size().width as usize;
|
||||
|
||||
let maxw = self.config.maxwidth;
|
||||
|
||||
let wpad = (w.saturating_sub(maxw)) / 2;
|
||||
let ypad = self.config.vertical_padding;
|
||||
|
||||
let mut linewidth = 0;
|
||||
|
||||
if let Some(r) = doc.row(y) {
|
||||
linewidth = r.width(ox, x);
|
||||
}
|
||||
Position {
|
||||
x: linewidth + wpad,
|
||||
y: (y + ypad).saturating_sub(oy),
|
||||
}
|
||||
}
|
||||
|
||||
async fn refresh_screen(&mut self) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
Terminal::cursor_hide();
|
||||
self.draw_rows().await;
|
||||
self.draw_info().await;
|
||||
|
||||
self.terminal.cursor_position(&self.calculate_pos().await);
|
||||
Terminal::cursor_show();
|
||||
self.terminal.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn refresh_status(&mut self) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
Terminal::cursor_hide();
|
||||
|
||||
let s = self.terminal.size();
|
||||
|
||||
// let w = s.width as usize;
|
||||
let h = s.height as usize - 2;
|
||||
|
||||
self.terminal.cursor_position(&Position { x: 0, y: h });
|
||||
|
||||
self.draw_info().await;
|
||||
|
||||
self.terminal.cursor_position(&self.calculate_pos().await);
|
||||
Terminal::cursor_show();
|
||||
self.terminal.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw_row(&self, row: &Row) {
|
||||
let w = self.terminal.size().width as usize;
|
||||
|
||||
let maxw = self.config.maxwidth;
|
||||
|
||||
let pad = (w.saturating_sub(maxw)) / 2;
|
||||
|
||||
let start = self.offset.x;
|
||||
let end = self.offset.x + w - pad * 2;
|
||||
let r = row.render(start, end);
|
||||
let space = &" ".repeat(pad);
|
||||
println!("{}{}\r", space, r)
|
||||
}
|
||||
|
||||
async fn draw_rows(&self) {
|
||||
let sharedarc = self.shared.clone();
|
||||
|
||||
let shared = (*sharedarc).read().await;
|
||||
|
||||
let doc = &shared.doc;
|
||||
|
||||
Terminal::cursor_reset();
|
||||
Terminal::set_text_color(self.colorscheme.text);
|
||||
Terminal::set_bg_color(self.colorscheme.background);
|
||||
|
||||
let h = self.terminal.size().height as usize;
|
||||
// let w = self.terminal.size().width as usize;
|
||||
|
||||
let vertical_pad = self.config.vertical_padding.saturating_sub(self.offset.y);
|
||||
|
||||
let trueh = h.saturating_sub(vertical_pad);
|
||||
|
||||
for _ in 0..vertical_pad {
|
||||
Terminal::clear_current_line();
|
||||
println!("\r")
|
||||
}
|
||||
|
||||
for terminal_row in 0..trueh - 2 {
|
||||
Terminal::clear_current_line();
|
||||
if let Some(row) = doc.row(
|
||||
terminal_row as usize + self.offset.y.saturating_sub(self.config.vertical_padding),
|
||||
) {
|
||||
self.draw_row(row);
|
||||
} else {
|
||||
println!("\r");
|
||||
}
|
||||
}
|
||||
Terminal::reset_text_color();
|
||||
Terminal::reset_bg_color();
|
||||
}
|
||||
|
||||
async fn draw_info(&self) {
|
||||
let sharedarc = self.shared.clone();
|
||||
|
||||
let shared = (*sharedarc).read().await;
|
||||
|
||||
let doc = &shared.doc;
|
||||
let cpos = &shared.cpos;
|
||||
let stbar = &shared.stbar;
|
||||
|
||||
Terminal::set_bg_color(self.colorscheme.status_background);
|
||||
Terminal::set_text_color(self.colorscheme.status_text);
|
||||
|
||||
let width = self.terminal.size().width as usize;
|
||||
|
||||
let status = stbar.status();
|
||||
|
||||
let status2 = stbar.status2(&doc, &cpos, width);
|
||||
|
||||
Terminal::clear_current_line();
|
||||
println!("{}\r", status);
|
||||
Terminal::clear_current_line();
|
||||
print!("{}\r", status2);
|
||||
|
||||
Terminal::reset_text_color();
|
||||
Terminal::reset_bg_color();
|
||||
}
|
||||
|
||||
async fn scroll(&mut self) {
|
||||
let sharedarc = self.shared.clone();
|
||||
|
||||
let shared = (*sharedarc).read().await;
|
||||
|
||||
let cpos = &shared.cpos;
|
||||
|
||||
let Position { x, y } = *cpos;
|
||||
let s = self.terminal.size();
|
||||
let w = s.width as usize;
|
||||
let h = s.height as usize - 2;
|
||||
|
||||
// let yscroll = self.config.vertical_scrollspace;
|
||||
// let xscroll = self.config.horizontal_scrollspace;
|
||||
|
||||
let ypad = self.config.vertical_padding;
|
||||
let maxw = self.config.maxwidth;
|
||||
|
||||
let mut offset = &mut self.offset;
|
||||
|
||||
let true_w = cmp::min(w, maxw);
|
||||
|
||||
if y - offset.y.saturating_sub(ypad) + 1 > h - ypad.saturating_sub(offset.y) {
|
||||
offset.y = y.saturating_sub(h - ypad).saturating_add(1);
|
||||
} else if y < offset.y {
|
||||
offset.y = y;
|
||||
}
|
||||
|
||||
if x + 1 > offset.x.saturating_add(true_w) {
|
||||
offset.x = x.saturating_sub(true_w).saturating_add(1);
|
||||
} else if x < offset.x {
|
||||
offset.x = x;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -64,7 +64,7 @@ impl Statusbar {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn change_status(&mut self, message: &str) -> Result<(), Box<dyn Error>> {
|
||||
pub fn change_status(&mut self, message: &str) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let mut s = self.sharedstatus.write().unwrap();
|
||||
*s = String::from(message);
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ use termion::raw::{IntoRawMode, RawTerminal};
|
|||
use termion::screen::{AlternateScreen, ToMainScreen};
|
||||
|
||||
use tokio::signal::unix::{signal, SignalKind};
|
||||
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
|
||||
use tokio::sync::mpsc::{UnboundedSender};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Size {
|
||||
|
@ -28,23 +28,23 @@ pub struct Terminal {
|
|||
screen: AlternateScreen<RawTerminal<Stdout>>,
|
||||
|
||||
pub sender: UnboundedSender<Events>,
|
||||
pub receiver: UnboundedReceiver<Events>,
|
||||
// pub receiver: UnboundedReceiver<Events>,
|
||||
|
||||
inputsender: UnboundedSender<Key>,
|
||||
pub inputreceiver: UnboundedReceiver<Key>,
|
||||
// pub inputreceiver: UnboundedReceiver<Key>,
|
||||
|
||||
sigthread: Option<tokio::task::JoinHandle<()>>,
|
||||
keythread: Option<std::thread::JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl Terminal {
|
||||
pub fn default() -> Result<Self, Box<dyn Error>> {
|
||||
pub fn default(st: UnboundedSender<Events>, ist: UnboundedSender<Key>) -> Result<Self, Box<dyn Error + Send + Sync>> {
|
||||
let size = termion::terminal_size()?;
|
||||
let out = stdout().into_raw_mode()?;
|
||||
let screen = AlternateScreen::from(out);
|
||||
|
||||
let (st, rt) = unbounded_channel();
|
||||
let (ist, irt) = unbounded_channel();
|
||||
// let (st, rt) = unbounded_channel();
|
||||
// let (ist, irt) = unbounded_channel();
|
||||
|
||||
let mut res = Self {
|
||||
size: Arc::new(RwLock::new(Size {
|
||||
|
@ -53,9 +53,9 @@ impl Terminal {
|
|||
})),
|
||||
screen,
|
||||
sender: st,
|
||||
receiver: rt,
|
||||
// receiver: rt,
|
||||
inputsender: ist,
|
||||
inputreceiver: irt,
|
||||
// inputreceiver: irt,
|
||||
sigthread: None,
|
||||
keythread: None,
|
||||
};
|
||||
|
@ -70,7 +70,7 @@ impl Terminal {
|
|||
println!("{}", ToMainScreen);
|
||||
}
|
||||
|
||||
fn start_update_thread(&mut self) -> Result<(), Box<dyn Error>> {
|
||||
fn start_update_thread(&mut self) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let mut signals = signal(SignalKind::window_change())?; // TODO: Support for other signals
|
||||
|
||||
let arcsize = self.size.clone();
|
||||
|
@ -79,6 +79,7 @@ impl Terminal {
|
|||
|
||||
let resizethread = tokio::spawn(async move {
|
||||
loop {
|
||||
|
||||
match signals.recv().await {
|
||||
Some(_) => {
|
||||
let size = termion::terminal_size().unwrap_or_default();
|
||||
|
@ -98,7 +99,7 @@ impl Terminal {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn start_reading_keys(&mut self) -> Result<(), Box<dyn Error>> {
|
||||
fn start_reading_keys(&mut self) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let mut reader = stdin().keys();
|
||||
|
||||
let sender = self.inputsender.clone();
|
||||
|
@ -133,7 +134,7 @@ impl Terminal {
|
|||
print!("{}", termion::cursor::Goto(x, y))
|
||||
}
|
||||
|
||||
pub fn flush(&mut self) -> Result<(), Box<dyn Error>> {
|
||||
pub fn flush(&mut self) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
self.screen.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue