From 284d5a99f8f50f0097b4480a654d3dbceaf7ac35 Mon Sep 17 00:00:00 2001 From: dr-frmr Date: Thu, 18 Jul 2024 21:01:01 +0200 Subject: [PATCH] fix: terminal detached is actually detached and works on both sim and non-sim --- kinode/src/main.rs | 16 +- kinode/src/terminal/mod.rs | 1070 ++++++++++++++++++---------------- kinode/src/terminal/utils.rs | 4 + 3 files changed, 578 insertions(+), 512 deletions(-) diff --git a/kinode/src/main.rs b/kinode/src/main.rs index 8b42f8a0..fd9742b8 100644 --- a/kinode/src/main.rs +++ b/kinode/src/main.rs @@ -77,14 +77,12 @@ async fn main() { let rpc = matches.get_one::("rpc"); let password = matches.get_one::("password"); - // if we are in sim-mode, detached determines whether terminal is interactive - #[cfg(not(feature = "simulation-mode"))] - let is_detached = false; + // detached determines whether terminal is interactive + let is_detached = *matches.get_one::("detached").unwrap(); #[cfg(feature = "simulation-mode")] - let (fake_node_name, is_detached, fakechain_port) = ( + let (fake_node_name, fakechain_port) = ( matches.get_one::("fake-node-name"), - *matches.get_one::("detached").unwrap(), matches.get_one::("fakechain-port").cloned(), ); @@ -654,6 +652,10 @@ fn build_command() -> Command { .default_value("true") .value_parser(value_parser!(bool)), ) + .arg( + arg!(--detached "Run in detached mode (don't accept input)") + .action(clap::ArgAction::SetTrue), + ) .arg(arg!(--rpc "Add a WebSockets RPC URL at boot")) .arg(arg!(--password "Node password (in double quotes)")); @@ -663,10 +665,6 @@ fn build_command() -> Command { .arg( arg!(--"fakechain-port" "Port to bind to for fakechain") .value_parser(value_parser!(u16)), - ) - .arg( - arg!(--detached "Run in detached mode (don't accept input)") - .action(clap::ArgAction::SetTrue), ); app } diff --git a/kinode/src/terminal/mod.rs b/kinode/src/terminal/mod.rs index fc37ae62..4e322e09 100644 --- a/kinode/src/terminal/mod.rs +++ b/kinode/src/terminal/mod.rs @@ -103,530 +103,594 @@ pub async fn terminal( let _ = debug_event_loop.send(DebugCommand::ToggleEventLoop).await; } - let mut reader = EventStream::new(); - - loop { - tokio::select! { - Some(printout) = print_rx.recv() => { - // lock here so that runtime can still use println! without freezing.. - // can lock before loop later if we want to reduce overhead - let mut stdout = stdout.lock(); - let now = Local::now(); - // always write print to log if in logging mode - if logging_mode { - writeln!(log_writer, "[{}] {}", now.to_rfc2822(), printout.content)?; - } - // skip writing print to terminal if it's of a greater - // verbosity level than our current mode - if printout.verbosity > verbose_mode { - continue; - } - execute!( - stdout, - // print goes immediately above the dedicated input line at bottom - cursor::MoveTo(0, win_rows - 1), - terminal::Clear(ClearType::CurrentLine), - Print(format!("{} {:02}:{:02} ", - now.weekday(), - now.hour(), - now.minute(), - )), - style::SetForegroundColor(match printout.verbosity { - 0 => style::Color::Reset, - 1 => style::Color::Green, - 2 => style::Color::Magenta, - _ => style::Color::Red, - }), - )?; - for line in printout.content.lines() { + // only create event stream if not in detached mode + // TODO do this without the code repetition.. + if !is_detached { + let mut reader = EventStream::new(); + loop { + tokio::select! { + Some(printout) = print_rx.recv() => { + // lock here so that runtime can still use println! without freezing.. + // can lock before loop later if we want to reduce overhead + let mut stdout = stdout.lock(); + let now = Local::now(); + // always write print to log if in logging mode + if logging_mode { + writeln!(log_writer, "[{}] {}", now.to_rfc2822(), printout.content)?; + } + // skip writing print to terminal if it's of a greater + // verbosity level than our current mode + if printout.verbosity > verbose_mode { + continue; + } execute!( stdout, - Print(format!("{}\r\n", line)), + // print goes immediately above the dedicated input line at bottom + cursor::MoveTo(0, win_rows - 1), + terminal::Clear(ClearType::CurrentLine), + Print(format!("{} {:02}:{:02} ", + now.weekday(), + now.hour(), + now.minute(), + )), + style::SetForegroundColor(match printout.verbosity { + 0 => style::Color::Reset, + 1 => style::Color::Green, + 2 => style::Color::Magenta, + _ => style::Color::Red, + }), )?; - } - // reset color and re-display the current input line - // re-place cursor where user had it at input line - execute!( - stdout, - style::ResetColor, - cursor::MoveTo(0, win_rows), - Print(utils::truncate_in_place(¤t_line, prompt_len, win_cols, (line_col, cursor_col))), - cursor::MoveTo(cursor_col, win_rows), - )?; - } - Some(Ok(event)) = reader.next().fuse() => { - // lock here so that runtime can still use println! without freezing.. - // can lock before loop later if we want to reduce overhead - let mut stdout = stdout.lock(); - match event { - // - // RESIZE: resize is super annoying because this event trigger often - // comes "too late" to stop terminal from messing with the - // already-printed lines. TODO figure out the right way - // to compensate for this cross-platform and do this in a - // generally stable way. - // - Event::Resize(width, height) => { - win_cols = width; - win_rows = height; - }, - // - // PASTE: handle pasting of text from outside - // - Event::Paste(pasted) => { - // strip out control characters and newlines - let pasted = pasted.chars().filter(|c| !c.is_control() && !c.is_ascii_control()).collect::(); - current_line.insert_str(line_col, &pasted); - line_col = line_col + pasted.len(); - cursor_col = std::cmp::min(line_col.try_into().unwrap_or(win_cols), win_cols); + for line in printout.content.lines() { execute!( stdout, - cursor::MoveTo(0, win_rows), - Print(utils::truncate_in_place(¤t_line, prompt_len, win_cols, (line_col, cursor_col))), - cursor::MoveTo(cursor_col, win_rows), + Print(format!("{}\r\n", line)), )?; } - // - // CTRL+C, CTRL+D: turn off the node - // - Event::Key(KeyEvent { - code: KeyCode::Char('c'), - modifiers: KeyModifiers::CONTROL, - .. - }) | - Event::Key(KeyEvent { - code: KeyCode::Char('d'), - modifiers: KeyModifiers::CONTROL, - .. - }) => { - execute!( - stdout, - // print goes immediately above the dedicated input line at bottom - cursor::MoveTo(0, win_rows - 1), - terminal::Clear(ClearType::CurrentLine), - Print("exit code received"), - )?; - break; - }, - // - // CTRL+V: toggle through verbosity modes - // - Event::Key(KeyEvent { - code: KeyCode::Char('v'), - modifiers: KeyModifiers::CONTROL, - .. - }) => { - // go from low to high, then reset to 0 - match verbose_mode { - 0 => verbose_mode = 1, - 1 => verbose_mode = 2, - 2 => verbose_mode = 3, - _ => verbose_mode = 0, + // reset color and re-display the current input line + // re-place cursor where user had it at input line + execute!( + stdout, + style::ResetColor, + cursor::MoveTo(0, win_rows), + Print(utils::truncate_in_place(¤t_line, prompt_len, win_cols, (line_col, cursor_col))), + cursor::MoveTo(cursor_col, win_rows), + )?; + } + Some(Ok(event)) = reader.next().fuse() => { + // lock here so that runtime can still use println! without freezing.. + // can lock before loop later if we want to reduce overhead + let mut stdout = stdout.lock(); + match event { + // + // RESIZE: resize is super annoying because this event trigger often + // comes "too late" to stop terminal from messing with the + // already-printed lines. TODO figure out the right way + // to compensate for this cross-platform and do this in a + // generally stable way. + // + Event::Resize(width, height) => { + win_cols = width; + win_rows = height; + }, + // + // PASTE: handle pasting of text from outside + // + Event::Paste(pasted) => { + // strip out control characters and newlines + let pasted = pasted.chars().filter(|c| !c.is_control() && !c.is_ascii_control()).collect::(); + current_line.insert_str(line_col, &pasted); + line_col = line_col + pasted.len(); + cursor_col = std::cmp::min(line_col.try_into().unwrap_or(win_cols), win_cols); + execute!( + stdout, + cursor::MoveTo(0, win_rows), + Print(utils::truncate_in_place(¤t_line, prompt_len, win_cols, (line_col, cursor_col))), + cursor::MoveTo(cursor_col, win_rows), + )?; } - Printout::new(0, format!("verbose mode: {}", match verbose_mode { - 0 => "off", - 1 => "debug", - 2 => "super-debug", - _ => "full event loop", - })).send(&print_tx).await; - if verbose_mode == 3 { - let _ = debug_event_loop.send(DebugCommand::ToggleEventLoop).await; - } - }, - // - // CTRL+J: toggle debug mode -- makes system-level event loop step-through - // - Event::Key(KeyEvent { - code: KeyCode::Char('j'), - modifiers: KeyModifiers::CONTROL, - .. - }) => { - let _ = debug_event_loop.send(DebugCommand::ToggleStepthrough).await; - in_step_through = !in_step_through; - Printout::new(0, format!("debug mode {}", match in_step_through { - false => "off", - true => "on: use CTRL+S to step through events", - })) + // + // CTRL+C, CTRL+D: turn off the node + // + Event::Key(KeyEvent { + code: KeyCode::Char('c'), + modifiers: KeyModifiers::CONTROL, + .. + }) | + Event::Key(KeyEvent { + code: KeyCode::Char('d'), + modifiers: KeyModifiers::CONTROL, + .. + }) => { + execute!( + stdout, + // print goes immediately above the dedicated input line at bottom + cursor::MoveTo(0, win_rows - 1), + terminal::Clear(ClearType::CurrentLine), + Print("exit code received"), + )?; + break; + }, + // + // CTRL+V: toggle through verbosity modes + // + Event::Key(KeyEvent { + code: KeyCode::Char('v'), + modifiers: KeyModifiers::CONTROL, + .. + }) => { + // go from low to high, then reset to 0 + match verbose_mode { + 0 => verbose_mode = 1, + 1 => verbose_mode = 2, + 2 => verbose_mode = 3, + _ => verbose_mode = 0, + } + Printout::new(0, format!("verbose mode: {}", match verbose_mode { + 0 => "off", + 1 => "debug", + 2 => "super-debug", + _ => "full event loop", + })).send(&print_tx).await; + if verbose_mode == 3 { + let _ = debug_event_loop.send(DebugCommand::ToggleEventLoop).await; + } + }, + // + // CTRL+J: toggle debug mode -- makes system-level event loop step-through + // + Event::Key(KeyEvent { + code: KeyCode::Char('j'), + modifiers: KeyModifiers::CONTROL, + .. + }) => { + let _ = debug_event_loop.send(DebugCommand::ToggleStepthrough).await; + in_step_through = !in_step_through; + Printout::new(0, format!("debug mode {}", match in_step_through { + false => "off", + true => "on: use CTRL+S to step through events", + })) + .send(&print_tx) + .await; + + }, + // + // CTRL+S: step through system-level event loop (when in step-through mode) + // + Event::Key(KeyEvent { + code: KeyCode::Char('s'), + modifiers: KeyModifiers::CONTROL, + .. + }) => { + let _ = debug_event_loop.send(DebugCommand::Step).await; + }, + // + // CTRL+L: toggle logging mode + // + Event::Key(KeyEvent { + code: KeyCode::Char('l'), + modifiers: KeyModifiers::CONTROL, + .. + }) => { + logging_mode = !logging_mode; + Printout::new( + 0, + format!("logging mode: {}", if logging_mode { "on" } else { "off" }) + ) .send(&print_tx) .await; - - }, - // - // CTRL+S: step through system-level event loop (when in step-through mode) - // - Event::Key(KeyEvent { - code: KeyCode::Char('s'), - modifiers: KeyModifiers::CONTROL, - .. - }) => { - let _ = debug_event_loop.send(DebugCommand::Step).await; - }, - // - // CTRL+L: toggle logging mode - // - Event::Key(KeyEvent { - code: KeyCode::Char('l'), - modifiers: KeyModifiers::CONTROL, - .. - }) => { - logging_mode = !logging_mode; - Printout::new( - 0, - format!("logging mode: {}", if logging_mode { "on" } else { "off" }) - ) - .send(&print_tx) - .await; - }, - // - // UP / CTRL+P: go up one command in history - // - Event::Key(KeyEvent { code: KeyCode::Up, .. }) | - Event::Key(KeyEvent { - code: KeyCode::Char('p'), - modifiers: KeyModifiers::CONTROL, - .. - }) => { - // go up one command in history - match command_history.get_prev(¤t_line[prompt_len..]) { - Some(line) => { - current_line = format!("{} > {}", our.name, line); - line_col = current_line.len(); - }, - None => { - // the "no-no" ding - print!("\x07"); - }, - } - cursor_col = std::cmp::min(current_line.len() as u16, win_cols); - execute!( - stdout, - cursor::MoveTo(0, win_rows), - terminal::Clear(ClearType::CurrentLine), - Print(utils::truncate_rightward(¤t_line, prompt_len, win_cols)), - )?; - }, - // - // DOWN / CTRL+N: go down one command in history - // - Event::Key(KeyEvent { code: KeyCode::Down, .. }) | - Event::Key(KeyEvent { - code: KeyCode::Char('n'), - modifiers: KeyModifiers::CONTROL, - .. - }) => { - // go down one command in history - match command_history.get_next() { - Some(line) => { - current_line = format!("{} > {}", our.name, line); - line_col = current_line.len(); - }, - None => { - // the "no-no" ding - print!("\x07"); - }, - } - cursor_col = std::cmp::min(current_line.len() as u16, win_cols); - execute!( - stdout, - cursor::MoveTo(0, win_rows), - terminal::Clear(ClearType::CurrentLine), - Print(utils::truncate_rightward(¤t_line, prompt_len, win_cols)), - )?; - }, - // - // CTRL+A: jump to beginning of line - // - Event::Key(KeyEvent { - code: KeyCode::Char('a'), - modifiers: KeyModifiers::CONTROL, - .. - }) => { - line_col = prompt_len; - cursor_col = prompt_len as u16; - execute!( - stdout, - cursor::MoveTo(0, win_rows), - Print(utils::truncate_from_left(¤t_line, prompt_len, win_cols, line_col)), - cursor::MoveTo(cursor_col, win_rows), - )?; - }, - // - // CTRL+E: jump to end of line - // - Event::Key(KeyEvent { - code: KeyCode::Char('e'), - modifiers: KeyModifiers::CONTROL, - .. - }) => { - line_col = current_line.len(); - cursor_col = std::cmp::min(line_col.try_into().unwrap_or(win_cols), win_cols); - execute!( - stdout, - cursor::MoveTo(0, win_rows), - Print(utils::truncate_from_right(¤t_line, prompt_len, win_cols, line_col)), - )?; - }, - // - // CTRL+R: enter search mode - // if already in search mode, increase search depth - // - Event::Key(KeyEvent { - code: KeyCode::Char('r'), - modifiers: KeyModifiers::CONTROL, - .. - }) => { - if search_mode { - search_depth += 1; - } - search_mode = true; - utils::execute_search( - &our, - &mut stdout, - ¤t_line, - prompt_len, - (win_cols, win_rows), - (line_col, cursor_col), - &mut command_history, - search_depth, - )?; - }, - // - // CTRL+G: exit search mode - // - Event::Key(KeyEvent { - code: KeyCode::Char('g'), - modifiers: KeyModifiers::CONTROL, - .. - }) => { - // just show true current line as usual - search_mode = false; - search_depth = 0; - execute!( - stdout, - cursor::MoveTo(0, win_rows), - terminal::Clear(ClearType::CurrentLine), - Print(utils::truncate_in_place( - &format!("{} > {}", our.name, ¤t_line[prompt_len..]), - prompt_len, - win_cols, - (line_col, cursor_col))), - cursor::MoveTo(cursor_col, win_rows), - )?; - }, - // - // KEY: handle keypress events - // - Event::Key(k) => { - match k.code { - // - // CHAR: write a single character - // - KeyCode::Char(c) => { - current_line.insert(line_col, c); - if cursor_col < win_cols { - cursor_col += 1; - } - line_col += 1; - if search_mode { - utils::execute_search( - &our, - &mut stdout, - ¤t_line, - prompt_len, - (win_cols, win_rows), - (line_col, cursor_col), - &mut command_history, - search_depth, - )?; - continue; - } - execute!( - stdout, - cursor::MoveTo(0, win_rows), - terminal::Clear(ClearType::CurrentLine), - Print(utils::truncate_in_place(¤t_line, prompt_len, win_cols, (line_col, cursor_col))), - cursor::MoveTo(cursor_col, win_rows), - )?; - }, - // - // BACKSPACE: delete a single character at cursor - // - KeyCode::Backspace => { - if line_col == prompt_len { - continue; - } - if cursor_col as usize == line_col { - cursor_col -= 1; - } - line_col -= 1; - current_line.remove(line_col); - if search_mode { - utils::execute_search( - &our, - &mut stdout, - ¤t_line, - prompt_len, - (win_cols, win_rows), - (line_col, cursor_col), - &mut command_history, - search_depth, - )?; - continue; - } - execute!( - stdout, - cursor::MoveTo(0, win_rows), - terminal::Clear(ClearType::CurrentLine), - Print(utils::truncate_in_place(¤t_line, prompt_len, win_cols, (line_col, cursor_col))), - cursor::MoveTo(cursor_col, win_rows), - )?; - }, - // - // DELETE: delete a single character at right of cursor - // - KeyCode::Delete => { - if line_col == current_line.len() { - continue; - } - current_line.remove(line_col); - if search_mode { - utils::execute_search( - &our, - &mut stdout, - ¤t_line, - prompt_len, - (win_cols, win_rows), - (line_col, cursor_col), - &mut command_history, - search_depth, - )?; - continue; - } - execute!( - stdout, - cursor::MoveTo(0, win_rows), - terminal::Clear(ClearType::CurrentLine), - Print(utils::truncate_in_place(¤t_line, prompt_len, win_cols, (line_col, cursor_col))), - cursor::MoveTo(cursor_col, win_rows), - )?; + }, + // + // UP / CTRL+P: go up one command in history + // + Event::Key(KeyEvent { code: KeyCode::Up, .. }) | + Event::Key(KeyEvent { + code: KeyCode::Char('p'), + modifiers: KeyModifiers::CONTROL, + .. + }) => { + // go up one command in history + match command_history.get_prev(¤t_line[prompt_len..]) { + Some(line) => { + current_line = format!("{} > {}", our.name, line); + line_col = current_line.len(); + }, + None => { + // the "no-no" ding + print!("\x07"); + }, } - // - // LEFT: move cursor one spot left - // - KeyCode::Left => { - if cursor_col as usize == prompt_len { - if line_col == prompt_len { - // at the very beginning of the current typed line - continue; - } else { - // virtual scroll leftward through line - line_col -= 1; - execute!( - stdout, - cursor::MoveTo(0, win_rows), - Print(utils::truncate_from_left(¤t_line, prompt_len, win_cols, line_col)), - cursor::MoveTo(cursor_col, win_rows), - )?; + cursor_col = std::cmp::min(current_line.len() as u16, win_cols); + execute!( + stdout, + cursor::MoveTo(0, win_rows), + terminal::Clear(ClearType::CurrentLine), + Print(utils::truncate_rightward(¤t_line, prompt_len, win_cols)), + )?; + }, + // + // DOWN / CTRL+N: go down one command in history + // + Event::Key(KeyEvent { code: KeyCode::Down, .. }) | + Event::Key(KeyEvent { + code: KeyCode::Char('n'), + modifiers: KeyModifiers::CONTROL, + .. + }) => { + // go down one command in history + match command_history.get_next() { + Some(line) => { + current_line = format!("{} > {}", our.name, line); + line_col = current_line.len(); + }, + None => { + // the "no-no" ding + print!("\x07"); + }, + } + cursor_col = std::cmp::min(current_line.len() as u16, win_cols); + execute!( + stdout, + cursor::MoveTo(0, win_rows), + terminal::Clear(ClearType::CurrentLine), + Print(utils::truncate_rightward(¤t_line, prompt_len, win_cols)), + )?; + }, + // + // CTRL+A: jump to beginning of line + // + Event::Key(KeyEvent { + code: KeyCode::Char('a'), + modifiers: KeyModifiers::CONTROL, + .. + }) => { + line_col = prompt_len; + cursor_col = prompt_len as u16; + execute!( + stdout, + cursor::MoveTo(0, win_rows), + Print(utils::truncate_from_left(¤t_line, prompt_len, win_cols, line_col)), + cursor::MoveTo(cursor_col, win_rows), + )?; + }, + // + // CTRL+E: jump to end of line + // + Event::Key(KeyEvent { + code: KeyCode::Char('e'), + modifiers: KeyModifiers::CONTROL, + .. + }) => { + line_col = current_line.len(); + cursor_col = std::cmp::min(line_col.try_into().unwrap_or(win_cols), win_cols); + execute!( + stdout, + cursor::MoveTo(0, win_rows), + Print(utils::truncate_from_right(¤t_line, prompt_len, win_cols, line_col)), + )?; + }, + // + // CTRL+R: enter search mode + // if already in search mode, increase search depth + // + Event::Key(KeyEvent { + code: KeyCode::Char('r'), + modifiers: KeyModifiers::CONTROL, + .. + }) => { + if search_mode { + search_depth += 1; + } + search_mode = true; + utils::execute_search( + &our, + &mut stdout, + ¤t_line, + prompt_len, + (win_cols, win_rows), + (line_col, cursor_col), + &mut command_history, + search_depth, + )?; + }, + // + // CTRL+G: exit search mode + // + Event::Key(KeyEvent { + code: KeyCode::Char('g'), + modifiers: KeyModifiers::CONTROL, + .. + }) => { + // just show true current line as usual + search_mode = false; + search_depth = 0; + execute!( + stdout, + cursor::MoveTo(0, win_rows), + terminal::Clear(ClearType::CurrentLine), + Print(utils::truncate_in_place( + &format!("{} > {}", our.name, ¤t_line[prompt_len..]), + prompt_len, + win_cols, + (line_col, cursor_col))), + cursor::MoveTo(cursor_col, win_rows), + )?; + }, + // + // KEY: handle keypress events + // + Event::Key(k) => { + match k.code { + // + // CHAR: write a single character + // + KeyCode::Char(c) => { + current_line.insert(line_col, c); + if cursor_col < win_cols { + cursor_col += 1; } - } else { - // simply move cursor and line position left - execute!( - stdout, - cursor::MoveLeft(1), - )?; - cursor_col -= 1; - line_col -= 1; - } - }, - // - // RIGHT: move cursor one spot right - // - KeyCode::Right => { - if line_col == current_line.len() { - // at the very end of the current typed line - continue; - } - if cursor_col < (win_cols - 1) { - // simply move cursor and line position right - execute!( - stdout, - cursor::MoveRight(1), - )?; - cursor_col += 1; - line_col += 1; - } else { - // virtual scroll rightward through line line_col += 1; + if search_mode { + utils::execute_search( + &our, + &mut stdout, + ¤t_line, + prompt_len, + (win_cols, win_rows), + (line_col, cursor_col), + &mut command_history, + search_depth, + )?; + continue; + } execute!( stdout, cursor::MoveTo(0, win_rows), - Print(utils::truncate_from_right(¤t_line, prompt_len, win_cols, line_col)), + terminal::Clear(ClearType::CurrentLine), + Print(utils::truncate_in_place(¤t_line, prompt_len, win_cols, (line_col, cursor_col))), + cursor::MoveTo(cursor_col, win_rows), + )?; + }, + // + // BACKSPACE: delete a single character at cursor + // + KeyCode::Backspace => { + if line_col == prompt_len { + continue; + } + if cursor_col as usize == line_col { + cursor_col -= 1; + } + line_col -= 1; + current_line.remove(line_col); + if search_mode { + utils::execute_search( + &our, + &mut stdout, + ¤t_line, + prompt_len, + (win_cols, win_rows), + (line_col, cursor_col), + &mut command_history, + search_depth, + )?; + continue; + } + execute!( + stdout, + cursor::MoveTo(0, win_rows), + terminal::Clear(ClearType::CurrentLine), + Print(utils::truncate_in_place(¤t_line, prompt_len, win_cols, (line_col, cursor_col))), + cursor::MoveTo(cursor_col, win_rows), + )?; + }, + // + // DELETE: delete a single character at right of cursor + // + KeyCode::Delete => { + if line_col == current_line.len() { + continue; + } + current_line.remove(line_col); + if search_mode { + utils::execute_search( + &our, + &mut stdout, + ¤t_line, + prompt_len, + (win_cols, win_rows), + (line_col, cursor_col), + &mut command_history, + search_depth, + )?; + continue; + } + execute!( + stdout, + cursor::MoveTo(0, win_rows), + terminal::Clear(ClearType::CurrentLine), + Print(utils::truncate_in_place(¤t_line, prompt_len, win_cols, (line_col, cursor_col))), + cursor::MoveTo(cursor_col, win_rows), )?; } - }, - // - // ENTER: send current input to terminal process, clearing input line - // - KeyCode::Enter => { - // if we were in search mode, pull command from that - let command = if !search_mode { - current_line[prompt_len..].to_string() + // + // LEFT: move cursor one spot left + // + KeyCode::Left => { + if cursor_col as usize == prompt_len { + if line_col == prompt_len { + // at the very beginning of the current typed line + continue; + } else { + // virtual scroll leftward through line + line_col -= 1; + execute!( + stdout, + cursor::MoveTo(0, win_rows), + Print(utils::truncate_from_left(¤t_line, prompt_len, win_cols, line_col)), + cursor::MoveTo(cursor_col, win_rows), + )?; + } } else { - command_history.search( - ¤t_line[prompt_len..], - search_depth - ).unwrap_or_default().to_string() - }; - let next = format!("{} > ", our.name); - execute!( - stdout, - cursor::MoveTo(0, win_rows), - terminal::Clear(ClearType::CurrentLine), - Print(&format!("{} > {}", our.name, command)), - Print("\r\n"), - Print(&next), - )?; - search_mode = false; - search_depth = 0; - current_line = next; - command_history.add(command.clone()); - cursor_col = prompt_len as u16; - line_col = prompt_len; - KernelMessage::builder() - .id(rand::random()) - .source((our.name.as_str(), TERMINAL_PROCESS_ID.clone())) - .target((our.name.as_str(), TERMINAL_PROCESS_ID.clone())) - .message(Message::Request(Request { - inherit: false, - expects_response: None, - body: command.into_bytes(), - metadata: None, - capabilities: vec![], - })) - .build() - .unwrap() - .send(&event_loop) - .await; - }, - _ => { - // some keycode we don't care about, yet - }, - } - }, - _ => { - // some terminal event we don't care about, yet - }, + // simply move cursor and line position left + execute!( + stdout, + cursor::MoveLeft(1), + )?; + cursor_col -= 1; + line_col -= 1; + } + }, + // + // RIGHT: move cursor one spot right + // + KeyCode::Right => { + if line_col == current_line.len() { + // at the very end of the current typed line + continue; + } + if cursor_col < (win_cols - 1) { + // simply move cursor and line position right + execute!( + stdout, + cursor::MoveRight(1), + )?; + cursor_col += 1; + line_col += 1; + } else { + // virtual scroll rightward through line + line_col += 1; + execute!( + stdout, + cursor::MoveTo(0, win_rows), + Print(utils::truncate_from_right(¤t_line, prompt_len, win_cols, line_col)), + )?; + } + }, + // + // ENTER: send current input to terminal process, clearing input line + // + KeyCode::Enter => { + // if we were in search mode, pull command from that + let command = if !search_mode { + current_line[prompt_len..].to_string() + } else { + command_history.search( + ¤t_line[prompt_len..], + search_depth + ).unwrap_or_default().to_string() + }; + let next = format!("{} > ", our.name); + execute!( + stdout, + cursor::MoveTo(0, win_rows), + terminal::Clear(ClearType::CurrentLine), + Print(&format!("{} > {}", our.name, command)), + Print("\r\n"), + Print(&next), + )?; + search_mode = false; + search_depth = 0; + current_line = next; + command_history.add(command.clone()); + cursor_col = prompt_len as u16; + line_col = prompt_len; + KernelMessage::builder() + .id(rand::random()) + .source((our.name.as_str(), TERMINAL_PROCESS_ID.clone())) + .target((our.name.as_str(), TERMINAL_PROCESS_ID.clone())) + .message(Message::Request(Request { + inherit: false, + expects_response: None, + body: command.into_bytes(), + metadata: None, + capabilities: vec![], + })) + .build() + .unwrap() + .send(&event_loop) + .await; + }, + _ => { + // some keycode we don't care about, yet + }, + } + }, + _ => { + // some terminal event we don't care about, yet + }, + } } + _ = sigalrm.recv() => return Err(anyhow::anyhow!("exiting due to SIGALRM")), + _ = sighup.recv() => return Err(anyhow::anyhow!("exiting due to SIGHUP")), + _ = sigint.recv() => return Err(anyhow::anyhow!("exiting due to SIGINT")), + _ = sigpipe.recv() => continue, // IGNORE SIGPIPE! + _ = sigquit.recv() => return Err(anyhow::anyhow!("exiting due to SIGQUIT")), + _ = sigterm.recv() => return Err(anyhow::anyhow!("exiting due to SIGTERM")), + _ = sigusr1.recv() => return Err(anyhow::anyhow!("exiting due to SIGUSR1")), + _ = sigusr2.recv() => return Err(anyhow::anyhow!("exiting due to SIGUSR2")), } - _ = sigalrm.recv() => return Err(anyhow::anyhow!("exiting due to SIGALRM")), - _ = sighup.recv() => return Err(anyhow::anyhow!("exiting due to SIGHUP")), - _ = sigint.recv() => return Err(anyhow::anyhow!("exiting due to SIGINT")), - _ = sigpipe.recv() => continue, // IGNORE SIGPIPE! - _ = sigquit.recv() => return Err(anyhow::anyhow!("exiting due to SIGQUIT")), - _ = sigterm.recv() => return Err(anyhow::anyhow!("exiting due to SIGTERM")), - _ = sigusr1.recv() => return Err(anyhow::anyhow!("exiting due to SIGUSR1")), - _ = sigusr2.recv() => return Err(anyhow::anyhow!("exiting due to SIGUSR2")), } - } + } else { + loop { + tokio::select! { + Some(printout) = print_rx.recv() => { + // lock here so that runtime can still use println! without freezing.. + // can lock before loop later if we want to reduce overhead + let mut stdout = stdout.lock(); + let now = Local::now(); + // always write print to log if in logging mode + if logging_mode { + writeln!(log_writer, "[{}] {}", now.to_rfc2822(), printout.content)?; + } + // skip writing print to terminal if it's of a greater + // verbosity level than our current mode + if printout.verbosity > verbose_mode { + continue; + } + execute!( + stdout, + // print goes immediately above the dedicated input line at bottom + cursor::MoveTo(0, win_rows - 1), + terminal::Clear(ClearType::CurrentLine), + Print(format!("{} {:02}:{:02} ", + now.weekday(), + now.hour(), + now.minute(), + )), + style::SetForegroundColor(match printout.verbosity { + 0 => style::Color::Reset, + 1 => style::Color::Green, + 2 => style::Color::Magenta, + _ => style::Color::Red, + }), + )?; + for line in printout.content.lines() { + execute!( + stdout, + Print(format!("{}\r\n", line)), + )?; + } + // reset color and re-display the current input line + // re-place cursor where user had it at input line + execute!( + stdout, + style::ResetColor, + cursor::MoveTo(0, win_rows), + Print(utils::truncate_in_place(¤t_line, prompt_len, win_cols, (line_col, cursor_col))), + cursor::MoveTo(cursor_col, win_rows), + )?; + } + _ = sigalrm.recv() => return Err(anyhow::anyhow!("exiting due to SIGALRM")), + _ = sighup.recv() => return Err(anyhow::anyhow!("exiting due to SIGHUP")), + _ = sigint.recv() => return Err(anyhow::anyhow!("exiting due to SIGINT")), + _ = sigpipe.recv() => continue, // IGNORE SIGPIPE! + _ = sigquit.recv() => return Err(anyhow::anyhow!("exiting due to SIGQUIT")), + _ = sigterm.recv() => return Err(anyhow::anyhow!("exiting due to SIGTERM")), + _ = sigusr1.recv() => return Err(anyhow::anyhow!("exiting due to SIGUSR1")), + _ = sigusr2.recv() => return Err(anyhow::anyhow!("exiting due to SIGUSR2")), + } + } + }; + Ok(()) } diff --git a/kinode/src/terminal/utils.rs b/kinode/src/terminal/utils.rs index 825d5896..baf30cb9 100644 --- a/kinode/src/terminal/utils.rs +++ b/kinode/src/terminal/utils.rs @@ -63,6 +63,7 @@ pub fn startup( ,/ /` ` .*` networking public key: {} + {} "#, our.name, if our.is_direct() { @@ -72,6 +73,7 @@ pub fn startup( }, version, our.networking_key, + if is_detached { "(detached)" } else { "" } )), crossterm::style::ResetColor ) @@ -95,6 +97,7 @@ pub fn startup( version {} a general purpose sovereign cloud computer net pubkey: {} + {} "#, our.name, if our.is_direct() { @@ -104,6 +107,7 @@ pub fn startup( }, version, our.networking_key, + if is_detached { "(detached)" } else { "" } )), crossterm::style::ResetColor )?;