Merge pull request #402 from kinode-dao/dr/terminal-refactor

refactor terminal a bit
This commit is contained in:
doria 2024-06-18 00:55:31 +09:00 committed by GitHub
commit 0b94aec95c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 376 additions and 241 deletions

View File

@ -710,6 +710,9 @@ pub async fn kernel(
let mut process_handles: ProcessHandles = HashMap::new();
let mut is_debug: bool = false;
// this flag starts as true, and terminal will alert us if we can
// skip sending prints for every event.
let mut print_full_event_loop: bool = true;
let mut reboot_processes: Vec<(t::ProcessId, StartProcessMetadata, Vec<u8>)> = vec![];
// filter out OnExit::None processes from process_map
@ -870,9 +873,17 @@ pub async fn kernel(
loop {
tokio::select! {
// debug mode toggle: when on, this loop becomes a manual step-through
debug = recv_debug_in_loop.recv() => {
if let Some(t::DebugCommand::Toggle) = debug {
Some(debug_command) = recv_debug_in_loop.recv() => {
match debug_command {
t::DebugCommand::ToggleStepthrough => {
is_debug = !is_debug;
},
t::DebugCommand::Step => {
// can't step here, must be in stepthrough-mode
},
t::DebugCommand::ToggleEventLoop => {
print_full_event_loop = !print_full_event_loop;
}
}
},
// network error message receiver: handle `timeout` and `offline` errors
@ -1042,17 +1053,20 @@ pub async fn kernel(
while is_debug {
let debug = recv_debug_in_loop.recv().await.expect("event loop: debug channel died");
match debug {
t::DebugCommand::Toggle => is_debug = !is_debug,
t::DebugCommand::ToggleStepthrough => is_debug = !is_debug,
t::DebugCommand::Step => break,
t::DebugCommand::ToggleEventLoop => print_full_event_loop = !print_full_event_loop,
}
}
// display every single event when verbose
if print_full_event_loop {
let _ = send_to_terminal.send(
t::Printout {
verbosity: 3,
content: format!("{kernel_message}")
}
).await;
}
if our.name != kernel_message.target.node {
send_to_net.send(kernel_message).await.expect("fatal: net module died");

View File

@ -455,17 +455,8 @@ async fn main() {
// abort all remaining tasks
tasks.shutdown().await;
let stdout = std::io::stdout();
let mut stdout = stdout.lock();
crossterm::execute!(
stdout,
crossterm::event::DisableBracketedPaste,
crossterm::terminal::SetTitle(""),
crossterm::style::SetForegroundColor(crossterm::style::Color::Red),
crossterm::style::Print(format!("\r\n{quit_msg}\r\n")),
crossterm::style::ResetColor,
)
.expect("failed to clean up terminal visual state! your terminal window might be funky now");
// reset all modified aspects of terminal -- clean ourselves up
terminal::utils::cleanup(&quit_msg);
}
async fn set_http_server_port(set_port: Option<&u16>) -> u16 {
@ -755,10 +746,7 @@ async fn serve_register_fe(
}
};
tokio::fs::write(
format!("{}/.keys", home_directory_path),
encoded_keyfile.clone(),
)
tokio::fs::write(format!("{}/.keys", home_directory_path), &encoded_keyfile)
.await
.unwrap();
@ -831,10 +819,7 @@ async fn login_with_password(
.await
.expect("information used to boot does not match information onchain");
tokio::fs::write(
format!("{}/.keys", home_directory_path),
disk_keyfile.clone(),
)
tokio::fs::write(format!("{}/.keys", home_directory_path), &disk_keyfile)
.await
.unwrap();

View File

@ -36,6 +36,7 @@ sol! {
function key(bytes32) external view returns (bytes32);
function nodes(bytes32) external view returns (address, uint96);
function ip(bytes32) external view returns (uint128, uint16, uint16, uint16, uint16);
function routers(bytes32) external view returns (bytes32[]);
}
/// Serve the registration page and receive POSTs and PUTs from it
@ -702,6 +703,7 @@ pub async fn assign_routing(
let namehash = FixedBytes::<32>::from_slice(&keygen::namehash(&our.name));
let ip_call = ipCall { _0: namehash }.abi_encode();
let key_call = keyCall { _0: namehash }.abi_encode();
let router_call = routersCall { _0: namehash }.abi_encode();
let tx_input = TransactionInput::new(Bytes::from(ip_call));
let tx = TransactionRequest::default()
.to(kns_address)
@ -731,6 +733,18 @@ pub async fn assign_routing(
));
}
let router_tx_input = TransactionInput::new(Bytes::from(router_call));
let router_tx = TransactionRequest::default()
.to(kns_address)
.input(router_tx_input);
let Ok(routers) = provider.call(&router_tx).await else {
return Err(anyhow::anyhow!("Failed to fetch node routers from PKI"));
};
let Ok(routers) = <Vec<FixedBytes<32>>>::abi_decode(&routers, false) else {
return Err(anyhow::anyhow!("Failed to decode node routers from PKI"));
};
let node_ip = format!(
"{}.{}.{}.{}",
(ip >> 24) & 0xFF,
@ -739,6 +753,10 @@ pub async fn assign_routing(
ip & 0xFF
);
if !routers.is_empty() {
// indirect node
return Ok(());
}
if node_ip != *"0.0.0.0" && (ws != 0 || tcp != 0) {
// direct node
let mut ports = std::collections::BTreeMap::new();
@ -763,8 +781,6 @@ pub async fn assign_routing(
ports.insert("tcp".to_string(), tcp);
}
our.routing = NodeRouting::Direct { ip: node_ip, ports };
} else {
// indirect node
}
Ok(())
}

View File

@ -1,41 +1,23 @@
use anyhow::Result;
use chrono::{Datelike, Local, Timelike};
use crossterm::{
cursor,
event::{
DisableBracketedPaste, EnableBracketedPaste, Event, EventStream, KeyCode, KeyEvent,
KeyModifiers,
},
event::{Event, EventStream, KeyCode, KeyEvent, KeyModifiers},
execute, style,
style::Print,
terminal::{self, disable_raw_mode, enable_raw_mode, ClearType},
terminal::{self, ClearType},
};
use futures::{future::FutureExt, StreamExt};
use std::fs::{read_to_string, OpenOptions};
use std::io::{stdout, BufWriter, Write};
use lib::types::core::{
Address, DebugCommand, DebugSender, Identity, KernelMessage, Message, MessageSender,
PrintReceiver, PrintSender, Printout, Request, TERMINAL_PROCESS_ID,
};
use std::{
fs::{read_to_string, OpenOptions},
io::{BufWriter, Write},
};
use tokio::signal::unix::{signal, SignalKind};
use lib::types::core::*;
mod utils;
struct RawMode;
impl RawMode {
fn new() -> anyhow::Result<Self> {
enable_raw_mode()?;
Ok(RawMode)
}
}
impl Drop for RawMode {
fn drop(&mut self) {
match disable_raw_mode() {
Ok(_) => {}
Err(e) => {
println!("terminal: failed to disable raw mode: {e:?}\r");
}
}
}
}
pub mod utils;
/*
* terminal driver
@ -50,122 +32,50 @@ pub async fn terminal(
mut print_rx: PrintReceiver,
is_detached: bool,
mut verbose_mode: u8,
) -> Result<()> {
let mut stdout = stdout();
execute!(
stdout,
EnableBracketedPaste,
terminal::SetTitle(format!("{}", our.name))
)?;
) -> anyhow::Result<()> {
let (stdout, _maybe_raw_mode) = utils::startup(&our, version, is_detached)?;
let (mut win_cols, mut win_rows) = terminal::size().unwrap();
// print initial splash screen, large if there's room, small otherwise
if win_cols >= 90 {
execute!(
stdout,
style::SetForegroundColor(style::Color::Magenta),
Print(format!(
r#"
.`
`@@,, ,* 888 d8P d8b 888
`@%@@@, ,~-##` 888 d8P Y8P 888
~@@#@%#@@, ##### 888 d8P 888
~-%######@@@, ##### 888d88K 888 88888b. .d88b. .d88888 .d88b.
-%%#######@#####, 8888888b 888 888 "88b d88""88b d88" 888 d8P Y8b
~^^%##########@ 888 Y88b 888 888 888 888 888 888 888 88888888
>^#########@ 888 Y88b 888 888 888 Y88..88P Y88b 888 Y8b.
`>#######` 888 Y88b 888 888 888 "Y88P" "Y88888 "Y8888
.>######%
/###%^#% {} ({})
/##%@# ` runtime version {}
./######` a general purpose sovereign cloud computer
/.^`.#^#^`
` ,#`#`#,
,/ /` `
.*`
networking public key: {}
"#,
our.name,
if our.is_direct() {
"direct"
} else {
"indirect"
},
version,
our.networking_key,
)),
style::ResetColor
)?;
} else {
execute!(
stdout,
style::SetForegroundColor(style::Color::Magenta),
Print(format!(
r#"
888 d8P d8b 888
888 d8P Y8P 888
888 d8P 888
888d88K 888 88888b. .d88b. .d88888 .d88b.
8888888b 888 888 "88b d88""88b d88" 888 d8P Y8b
888 Y88b 888 888 888 888 888 888 888 88888888
888 Y88b 888 888 888 Y88..88P Y88b 888 Y8b.
888 Y88b 888 888 888 "Y88P" "Y88888 "Y8888
// mutable because we adjust them on window resize events
let (mut win_cols, mut win_rows) =
crossterm::terminal::size().expect("terminal: couldn't fetch size");
{} ({})
version {}
a general purpose sovereign cloud computer
net pubkey: {}
"#,
our.name,
if our.is_direct() {
"direct"
} else {
"indirect"
},
version,
our.networking_key,
)),
style::ResetColor
)?;
}
let _raw_mode = if is_detached {
None
} else {
Some(RawMode::new()?)
};
let mut reader = EventStream::new();
let mut current_line = format!("{} > ", our.name);
let prompt_len: usize = our.name.len() + 3;
let mut cursor_col: u16 = prompt_len.try_into().unwrap();
let mut cursor_col: u16 = prompt_len as u16;
let mut line_col: usize = cursor_col as usize;
let mut in_step_through: bool = false;
let mut search_mode: bool = false;
let mut search_depth: usize = 0;
let mut logging_mode: bool = false;
// the terminal stores the most recent 1000 lines entered by user
// in history. TODO should make history size adjustable.
let history_path = std::fs::canonicalize(&home_directory_path)
.unwrap()
.expect("terminal: could not get path for .terminal_history file")
.join(".terminal_history");
let history = read_to_string(&history_path).unwrap_or_default();
let history_handle = OpenOptions::new()
.append(true)
.create(true)
.open(&history_path)
.unwrap();
.expect("terminal: could not open/create .terminal_history");
let history_writer = BufWriter::new(history_handle);
// TODO make adjustable max history length
let mut command_history = utils::CommandHistory::new(1000, history, history_writer);
// if CTRL+L is used to turn on logging, all prints to terminal
// will also be written with their full timestamp to the .terminal_log file.
// logging mode is always off by default. TODO add a boot flag to change this.
let log_path = std::fs::canonicalize(&home_directory_path)
.unwrap()
.expect("terminal: could not get path for .terminal_log file")
.join(".terminal_log");
let log_handle = OpenOptions::new()
.append(true)
.create(true)
.open(&log_path)
.unwrap();
.expect("terminal: could not open/create .terminal_log");
let mut log_writer = BufWriter::new(log_handle);
// use to trigger cleanup if receive signal to kill process
@ -186,21 +96,32 @@ pub async fn terminal(
let mut sigusr2 =
signal(SignalKind::user_defined2()).expect("terminal: failed to set up SIGUSR2 handler");
loop {
let event = reader.next().fuse();
// if the verbosity boot flag was **not** set to "full event loop", tell kernel
// the kernel will try and print all events by default so that booting with
// verbosity mode 3 guarantees all events from boot are shown.
if verbose_mode != 3 {
let _ = debug_event_loop.send(DebugCommand::ToggleEventLoop).await;
}
let mut reader = EventStream::new();
let mut stdout = stdout.lock();
loop {
tokio::select! {
Some(printout) = print_rx.recv() => {
let now = Local::now();
// always write print to log if in logging mode
if logging_mode {
let _ = writeln!(log_writer, "[{}] {}", now.to_rfc2822(), printout.content);
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;
}
let mut stdout = stdout.lock();
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} ",
@ -208,41 +129,45 @@ pub async fn terminal(
now.hour(),
now.minute(),
)),
)?;
let color = match printout.verbosity {
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,
style::SetForegroundColor(color),
Print(format!("{}\r\n", line)),
style::ResetColor,
)?;
}
// 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(&current_line, prompt_len, win_cols, (line_col, cursor_col))),
cursor::MoveTo(cursor_col, win_rows),
)?;
}
Some(Ok(event)) = event => {
let mut stdout = stdout.lock();
Some(Ok(event)) = reader.next().fuse() => {
match event {
// resize is super annoying because this event trigger often
//
// 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;
},
// handle pasting of text from outside
//
// 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::<String>();
@ -256,7 +181,9 @@ pub async fn terminal(
cursor::MoveTo(cursor_col, win_rows),
)?;
}
//
// CTRL+C, CTRL+D: turn off the node
//
Event::Key(KeyEvent {
code: KeyCode::Char('c'),
modifiers: KeyModifiers::CONTROL,
@ -267,10 +194,18 @@ pub async fn terminal(
modifiers: KeyModifiers::CONTROL,
..
}) => {
execute!(stdout, DisableBracketedPaste, terminal::SetTitle(""))?;
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,
@ -294,26 +229,34 @@ pub async fn terminal(
}
}
).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
// CTRL+S: step through system-level event loop
//
Event::Key(KeyEvent {
code: KeyCode::Char('j'),
modifiers: KeyModifiers::CONTROL,
..
}) => {
let _ = debug_event_loop.send(DebugCommand::ToggleStepthrough).await;
in_step_through = !in_step_through;
let _ = print_tx.send(
Printout {
verbosity: 0,
content: match in_step_through {
true => "debug mode off".into(),
false => "debug mode on: use CTRL+S to step through events".into(),
false => "debug mode off".into(),
true => "debug mode on: use CTRL+S to step through events".into(),
}
}
).await;
let _ = debug_event_loop.send(DebugCommand::Toggle).await;
in_step_through = !in_step_through;
},
//
// CTRL+S: step through system-level event loop (when in step-through mode)
//
Event::Key(KeyEvent {
code: KeyCode::Char('s'),
modifiers: KeyModifiers::CONTROL,
@ -342,7 +285,6 @@ pub async fn terminal(
},
//
// UP / CTRL+P: go up one command in history
// DOWN / CTRL+N: go down one command in history
//
Event::Key(KeyEvent { code: KeyCode::Up, .. }) |
Event::Key(KeyEvent {
@ -357,6 +299,7 @@ pub async fn terminal(
line_col = current_line.len();
},
None => {
// the "no-no" ding
print!("\x07");
},
}
@ -368,6 +311,9 @@ pub async fn terminal(
Print(utils::truncate_rightward(&current_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'),
@ -381,6 +327,7 @@ pub async fn terminal(
line_col = current_line.len();
},
None => {
// the "no-no" ding
print!("\x07");
},
}
@ -401,7 +348,7 @@ pub async fn terminal(
..
}) => {
line_col = prompt_len;
cursor_col = prompt_len.try_into().unwrap();
cursor_col = prompt_len as u16;
execute!(
stdout,
cursor::MoveTo(0, win_rows),
@ -438,32 +385,16 @@ pub async fn terminal(
search_depth += 1;
}
search_mode = true;
let search_query = &current_line[prompt_len..];
if search_query.is_empty() {
continue;
}
if let Some(result) = command_history.search(search_query, search_depth) {
let result_underlined = utils::underline(result, search_query);
execute!(
stdout,
cursor::MoveTo(0, win_rows),
terminal::Clear(ClearType::CurrentLine),
Print(utils::truncate_in_place(
&format!("{} * {}", our.name, result_underlined),
utils::execute_search(
&our,
&mut stdout,
&current_line,
prompt_len,
win_cols,
(line_col, cursor_col))),
cursor::MoveTo(cursor_col, win_rows),
(win_cols, win_rows),
(line_col, cursor_col),
&mut command_history,
search_depth,
)?;
} else {
execute!(
stdout,
cursor::MoveTo(0, win_rows),
terminal::Clear(ClearType::CurrentLine),
Print(utils::truncate_in_place(&current_line, prompt_len, win_cols, (line_col, cursor_col))),
cursor::MoveTo(cursor_col, win_rows),
)?;
}
},
//
// CTRL+G: exit search mode
@ -480,15 +411,22 @@ pub async fn terminal(
stdout,
cursor::MoveTo(0, win_rows),
terminal::Clear(ClearType::CurrentLine),
Print(utils::truncate_in_place(&current_line, prompt_len, win_cols, (line_col, cursor_col))),
Print(utils::truncate_in_place(
&format!("{} > {}", our.name, &current_line[prompt_len..]),
prompt_len,
win_cols,
(line_col, cursor_col))),
cursor::MoveTo(cursor_col, win_rows),
)?;
},
//
// handle keypress events
// 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 {
@ -496,23 +434,18 @@ pub async fn terminal(
}
line_col += 1;
if search_mode {
let search_query = &current_line[prompt_len..];
if let Some(result) = command_history.search(search_query, search_depth) {
let result_underlined = utils::underline(result, search_query);
execute!(
stdout,
cursor::MoveTo(0, win_rows),
terminal::Clear(ClearType::CurrentLine),
Print(utils::truncate_in_place(
&format!("{} * {}", our.name, result_underlined),
utils::execute_search(
&our,
&mut stdout,
&current_line,
prompt_len,
win_cols,
(line_col, cursor_col))),
cursor::MoveTo(cursor_col, win_rows),
(win_cols, win_rows),
(line_col, cursor_col),
&mut command_history,
search_depth,
)?;
continue;
}
}
execute!(
stdout,
cursor::MoveTo(0, win_rows),
@ -521,6 +454,9 @@ pub async fn terminal(
cursor::MoveTo(cursor_col, win_rows),
)?;
},
//
// BACKSPACE or DELETE: delete a single character at cursor
//
KeyCode::Backspace | KeyCode::Delete => {
if line_col == prompt_len {
continue;
@ -531,23 +467,18 @@ pub async fn terminal(
line_col -= 1;
current_line.remove(line_col);
if search_mode {
let search_query = &current_line[prompt_len..];
if let Some(result) = command_history.search(search_query, search_depth) {
let result_underlined = utils::underline(result, search_query);
execute!(
stdout,
cursor::MoveTo(0, win_rows),
terminal::Clear(ClearType::CurrentLine),
Print(utils::truncate_in_place(
&format!("{} * {}", our.name, result_underlined),
utils::execute_search(
&our,
&mut stdout,
&current_line,
prompt_len,
win_cols,
(line_col, cursor_col))),
cursor::MoveTo(cursor_col, win_rows),
(win_cols, win_rows),
(line_col, cursor_col),
&mut command_history,
search_depth,
)?;
continue;
}
}
execute!(
stdout,
cursor::MoveTo(0, win_rows),
@ -556,6 +487,9 @@ pub async fn terminal(
cursor::MoveTo(cursor_col, win_rows),
)?;
},
//
// LEFT: move cursor one spot left
//
KeyCode::Left => {
if cursor_col as usize == prompt_len {
if line_col == prompt_len {
@ -581,6 +515,9 @@ pub async fn terminal(
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
@ -604,6 +541,9 @@ pub async fn terminal(
)?;
}
},
//
// 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 {
@ -612,7 +552,7 @@ pub async fn terminal(
command_history.search(
&current_line[prompt_len..],
search_depth
).unwrap_or(&current_line[prompt_len..]).to_string()
).unwrap_or_default().to_string()
};
let next = format!("{} > ", our.name);
execute!(
@ -627,7 +567,7 @@ pub async fn terminal(
search_depth = 0;
current_line = next;
command_history.add(command.clone());
cursor_col = prompt_len.try_into().unwrap();
cursor_col = prompt_len as u16;
line_col = prompt_len;
event_loop.send(
KernelMessage {
@ -652,10 +592,14 @@ pub async fn terminal(
}
).await.expect("terminal: couldn't execute command!");
},
_ => {},
_ => {
// 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")),
@ -668,6 +612,5 @@ pub async fn terminal(
_ = sigusr2.recv() => return Err(anyhow::anyhow!("exiting due to SIGUSR2")),
}
}
execute!(stdout.lock(), DisableBracketedPaste, terminal::SetTitle(""))?;
Ok(())
}

View File

@ -1,6 +1,137 @@
use std::collections::VecDeque;
use std::fs::File;
use std::io::{BufWriter, Write};
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
use lib::types::core::Identity;
use std::{
collections::VecDeque,
fs::File,
io::{BufWriter, Stdout, Write},
};
pub struct RawMode;
impl RawMode {
fn new() -> std::io::Result<Self> {
enable_raw_mode()?;
Ok(RawMode)
}
}
impl Drop for RawMode {
fn drop(&mut self) {
match disable_raw_mode() {
Ok(_) => {}
Err(e) => {
println!("terminal: failed to disable raw mode: {e:?}\r");
}
}
}
}
pub fn startup(
our: &Identity,
version: &str,
is_detached: bool,
) -> std::io::Result<(Stdout, Option<RawMode>)> {
let mut stdout = std::io::stdout();
crossterm::execute!(
stdout,
crossterm::event::EnableBracketedPaste,
crossterm::terminal::SetTitle(format!("kinode {}", our.name))
)?;
let (win_cols, _) = crossterm::terminal::size().expect("terminal: couldn't fetch size");
// print initial splash screen, large if there's room, small otherwise
if win_cols >= 90 {
crossterm::execute!(
stdout,
crossterm::style::SetForegroundColor(crossterm::style::Color::Magenta),
crossterm::style::Print(format!(
r#"
.`
`@@,, ,* 888 d8P d8b 888
`@%@@@, ,~-##` 888 d8P Y8P 888
~@@#@%#@@, ##### 888 d8P 888
~-%######@@@, ##### 888d88K 888 88888b. .d88b. .d88888 .d88b.
-%%#######@#####, 8888888b 888 888 "88b d88""88b d88" 888 d8P Y8b
~^^%##########@ 888 Y88b 888 888 888 888 888 888 888 88888888
>^#########@ 888 Y88b 888 888 888 Y88..88P Y88b 888 Y8b.
`>#######` 888 Y88b 888 888 888 "Y88P" "Y88888 "Y8888
.>######%
/###%^#% {} ({})
/##%@# ` runtime version {}
./######` a general purpose sovereign cloud computer
/.^`.#^#^`
` ,#`#`#,
,/ /` `
.*`
networking public key: {}
"#,
our.name,
if our.is_direct() {
"direct"
} else {
"indirect"
},
version,
our.networking_key,
)),
crossterm::style::ResetColor
)
.expect("terminal: couldn't print splash");
} else {
crossterm::execute!(
stdout,
crossterm::style::SetForegroundColor(crossterm::style::Color::Magenta),
crossterm::style::Print(format!(
r#"
888 d8P d8b 888
888 d8P Y8P 888
888 d8P 888
888d88K 888 88888b. .d88b. .d88888 .d88b.
8888888b 888 888 "88b d88""88b d88" 888 d8P Y8b
888 Y88b 888 888 888 888 888 888 888 88888888
888 Y88b 888 888 888 Y88..88P Y88b 888 Y8b.
888 Y88b 888 888 888 "Y88P" "Y88888 "Y8888
{} ({})
version {}
a general purpose sovereign cloud computer
net pubkey: {}
"#,
our.name,
if our.is_direct() {
"direct"
} else {
"indirect"
},
version,
our.networking_key,
)),
crossterm::style::ResetColor
)?;
}
Ok((
stdout,
if is_detached {
None
} else {
Some(RawMode::new()?)
},
))
}
pub fn cleanup(quit_msg: &str) {
let stdout = std::io::stdout();
let mut stdout = stdout.lock();
crossterm::execute!(
stdout,
crossterm::event::DisableBracketedPaste,
crossterm::terminal::SetTitle(""),
crossterm::style::SetForegroundColor(crossterm::style::Color::Red),
crossterm::style::Print(format!("\r\n{quit_msg}\r\n")),
crossterm::style::ResetColor,
)
.expect("failed to clean up terminal visual state! your terminal window might be funky now");
}
#[derive(Debug)]
pub struct CommandHistory {
@ -70,6 +201,9 @@ impl CommandHistory {
/// yes this is O(n) to provide desired ordering, can revisit if slow
pub fn search(&mut self, find: &str, depth: usize) -> Option<&str> {
let mut skips = 0;
if find.is_empty() {
return None;
}
// if there is at least one match, and we've skipped past it, return oldest match
let mut last_match: Option<&str> = None;
for line in self.lines.iter() {
@ -86,14 +220,56 @@ impl CommandHistory {
}
}
pub fn underline(s: &str, to_underline: &str) -> String {
pub fn execute_search(
our: &Identity,
stdout: &mut std::io::StdoutLock,
current_line: &str,
prompt_len: usize,
(win_cols, win_rows): (u16, u16),
(line_col, cursor_col): (usize, u16),
command_history: &mut CommandHistory,
search_depth: usize,
) -> Result<(), std::io::Error> {
let search_query = &current_line[prompt_len..];
if let Some(result) = command_history.search(search_query, search_depth) {
let (result_underlined, u_end) = underline(result, search_query);
let search_cursor_col = u_end + prompt_len as u16;
crossterm::execute!(
stdout,
crossterm::cursor::MoveTo(0, win_rows),
crossterm::terminal::Clear(crossterm::terminal::ClearType::CurrentLine),
crossterm::style::Print(truncate_in_place(
&format!("{} * {}", our.name, result_underlined),
prompt_len,
win_cols,
(line_col, search_cursor_col)
)),
crossterm::cursor::MoveTo(search_cursor_col, win_rows),
)
} else {
crossterm::execute!(
stdout,
crossterm::cursor::MoveTo(0, win_rows),
crossterm::terminal::Clear(crossterm::terminal::ClearType::CurrentLine),
crossterm::style::Print(truncate_in_place(
&format!("{} * {}: no results", our.name, &current_line[prompt_len..]),
prompt_len,
win_cols,
(line_col, cursor_col)
)),
crossterm::cursor::MoveTo(cursor_col, win_rows),
)
}
}
pub fn underline(s: &str, to_underline: &str) -> (String, u16) {
// format result string to have query portion underlined
let mut result = s.to_string();
let u_start = s.find(to_underline).unwrap();
let u_end = u_start + to_underline.len();
result.insert_str(u_end, "\x1b[24m");
result.insert_str(u_start, "\x1b[4m");
result
(result, u_end as u16)
}
pub fn truncate_rightward(s: &str, prompt_len: usize, width: u16) -> String {

View File

@ -1180,8 +1180,9 @@ pub type Rsvp = Option<Address>;
#[derive(Debug, Serialize, Deserialize)]
pub enum DebugCommand {
Toggle,
ToggleStepthrough,
Step,
ToggleEventLoop,
}
/// IPC format for requests sent to kernel runtime module