~hime/linetest

019483892dd46bb3d4f4184b45021f8932bbdf56 — drbawb 4 years ago 6951c6c master
split app into modules

- ui.rs: main UI event loop
- net.rs: async event loop for network io
- app.rs: core application & shell logic
4 files changed, 125 insertions(+), 98 deletions(-)

M src/app.rs
M src/main.rs
A src/net.rs
A src/ui.rs
M src/app.rs => src/app.rs +39 -42
@@ 1,9 1,9 @@
use crossterm::{
	cursor,
	event::{self, KeyEvent, KeyModifiers},
	style::Print,
	terminal::{self, Clear, ClearType},
	QueueableCommand,
    cursor,
    event::{self, KeyEvent, KeyModifiers},
    style::Print,
    terminal::{self, Clear, ClearType},
    QueueableCommand,
};

use std::cmp;


@@ 139,6 139,7 @@ impl InputBuffer {
            self.span_len(),
            self.buffer.len()
        );

        stdout
            .queue(cursor::MoveTo(0, 1))?
            .queue(Clear(ClearType::CurrentLine))?


@@ 168,52 169,64 @@ pub struct Shell {
    term_rows: u16,

    buffer: InputBuffer,
    core: Core,
    lines: Vec<String>,
    is_running: bool,
}

impl Shell {
	pub fn new() -> anyhow::Result<Self> {
    pub fn new() -> anyhow::Result<Self> {
        let (cols, rows) = terminal::size()?;

		Ok(Self {
        Ok(Self {
            term_cols: cols,
            term_rows: rows,
            
            buffer: InputBuffer::new(),
            core: Core::new(),
            lines: vec![],
            is_running: true,
		})
        })
    }

	pub fn present(&mut self) -> anyhow::Result<()> {
		let mut stdout = io::stdout();
    pub fn present(&mut self) -> anyhow::Result<()> {
        let mut stdout = io::stdout();
        stdout.queue(cursor::Hide)?;
        
        // save the input cursor position
        stdout
            //.queue(cursor::SavePosition)?
            .queue(cursor::Hide)?;

        // draw title line
        stdout
            .queue(cursor::MoveTo(0, 0))?
            .queue(Clear(ClearType::CurrentLine))?
            .queue(Print(self.status_line()))?;

		// redraw current buffer lines except for prompt
		for i in 2..(self.term_rows - 1) {
			stdout
				.queue(cursor::MoveTo(0, i))?
				.queue(Clear(ClearType::CurrentLine))?;
        }
        
        self.buffer.render(&mut stdout, 0, self.term_rows, self.term_cols)?;
        stdout.queue(cursor::SavePosition)?;

        // redraw current buffer lines except for prompt
        let mut last_n_lines = self.lines
            .iter()
            .rev()                             // sort from newest to oldest
            .take(self.term_rows as usize - 3) // take last n lines, minus room for header & footer
            .rev();                            // reverse again to sort them back into order they were received

        for i in 2..(self.term_rows - 1) {
            stdout
                .queue(cursor::MoveTo(0, i))?
                .queue(Clear(ClearType::CurrentLine))?;

            if let Some(line) = last_n_lines.next() {
                stdout.queue(Print(&line))?;
            }
        }

        // restore cursor position
        stdout
            //.queue(cursor::RestorePosition)?
            .queue(cursor::RestorePosition)?
            .queue(cursor::Show)?;
		Ok(stdout.flush()?)

        Ok(stdout.flush()?)
    }

    pub fn event_message(&mut self, line: String) {
        self.lines.push(line);
    }
    
    pub fn event_keydown(&mut self, event: event::KeyEvent) {


@@ 261,23 274,7 @@ impl Shell {

    pub fn is_running(&self) -> bool { self.is_running }



    fn status_line(&self) -> String {
        format!("linetest - rows [{}] cols [{}]", self.term_rows, self.term_cols)
    }
}

pub struct Core {

}

impl Core {
    pub fn new() -> Self {
        Self {

        }
    }


}
\ No newline at end of file

M src/main.rs => src/main.rs +12 -56
@@ 1,67 1,23 @@
use crossterm::{
	cursor,
	event::{self, Event},
	terminal::{EnterAlternateScreen, LeaveAlternateScreen},
	ExecutableCommand,
};

use std::io;
use std::time::Duration;

use std::thread;

mod app;

mod net;
mod ui;

fn main() -> anyhow::Result<()> {
	// take over user's terminal
	crossterm::terminal::enable_raw_mode()?;
	io::stdout()
		.execute(EnterAlternateScreen)?
		.execute(cursor::Hide)?;

	// block on UI thread
	ui_thread()?;

	
	// return to user's previous terminal
	io::stdout()
		.execute(LeaveAlternateScreen)?
		.execute(cursor::Show)?;

	crossterm::terminal::disable_raw_mode()?;
	
	Ok(())
}

fn ui_thread() -> anyhow::Result<()> {
	
	let mut shell = app::Shell::new()?;
	let poll_hz = Duration::from_millis(1000 / 120);
	// create channels just to keep task thread alive
	let (chan_tx, chan_rx) = async_channel::bounded(1024);

	

	while shell.is_running() {
		shell.present()?;
		if !event::poll(poll_hz)? { continue }

		match event::read()? {
			Event::Key(key_evt) => {
				shell.event_keydown(key_evt);
			},

			Event::Resize(cols, rows) => { 
				shell.event_resize(cols, rows);
			},

			_ => {},
		}
	// run shell on its own dedicated thread
	let ui_handle = thread::spawn(|| { ui::start_task(chan_rx) });

	// TODO: spawn up multiple async workers
	smol::run(net::start_task(chan_tx))?;

	// probably having a bad day if we get here ...
	if let Err(msg) = ui_handle.join() {
		panic!("err: {:?}", msg);
	}





	Ok(())
}
\ No newline at end of file

A src/net.rs => src/net.rs +15 -0
@@ 0,0 1,15 @@
use async_channel::{bounded, Sender, Receiver};
use std::time::Duration;

pub async fn start_task(shell_tx: Sender<String>) -> anyhow::Result<()> {
    let mut counter = 0;

    loop {
        smol::Timer::after(Duration::from_secs(1)).await;
        shell_tx.send(format!("async task count #{}", counter)).await?;
        counter += 1;
    }


	Ok(())
}
\ No newline at end of file

A src/ui.rs => src/ui.rs +59 -0
@@ 0,0 1,59 @@
use crate::app;

use async_channel::{Receiver, TryRecvError};
use crossterm::{
	cursor,
	event::{self, Event},
	terminal::{EnterAlternateScreen, LeaveAlternateScreen},
	ExecutableCommand,
};

use std::io;
use std::time::Duration;

pub fn start_task(rx: Receiver<String>) -> anyhow::Result<()> {
	let mut shell = app::Shell::new()?;
	let poll_hz = Duration::from_millis(1000 / 120);

	// take over user's terminal
	crossterm::terminal::enable_raw_mode()?;

	io::stdout()
		.execute(EnterAlternateScreen)?
		.execute(cursor::Hide)?;
	
	// loop over crossterm events every 120Hz
	while shell.is_running() {
        match rx.try_recv() {
            Ok(line) => shell.event_message(line),
            Err(TryRecvError::Empty) => {},
            Err(TryRecvError::Closed) => panic!("channel closed?"),
        }
        
        shell.present()?;
		if !event::poll(poll_hz)? { continue }

		match event::read()? {
			Event::Key(key_evt) => {
				shell.event_keydown(key_evt);
			},

			Event::Resize(cols, rows) => { 
				shell.event_resize(cols, rows);
			},

			_ => {},
		}


	}

	// return to user's previous terminal
	io::stdout()
		.execute(LeaveAlternateScreen)?
		.execute(cursor::Show)?;

	crossterm::terminal::disable_raw_mode()?;

	Ok(())
}
\ No newline at end of file