mirror of
https://github.com/uqbar-dao/nectar.git
synced 2025-01-03 06:11:01 +03:00
Merge pull request #402 from kinode-dao/dr/terminal-refactor
refactor terminal a bit
This commit is contained in:
commit
0b94aec95c
@ -710,6 +710,9 @@ pub async fn kernel(
|
|||||||
let mut process_handles: ProcessHandles = HashMap::new();
|
let mut process_handles: ProcessHandles = HashMap::new();
|
||||||
|
|
||||||
let mut is_debug: bool = false;
|
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![];
|
let mut reboot_processes: Vec<(t::ProcessId, StartProcessMetadata, Vec<u8>)> = vec![];
|
||||||
|
|
||||||
// filter out OnExit::None processes from process_map
|
// filter out OnExit::None processes from process_map
|
||||||
@ -870,9 +873,17 @@ pub async fn kernel(
|
|||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
// debug mode toggle: when on, this loop becomes a manual step-through
|
// debug mode toggle: when on, this loop becomes a manual step-through
|
||||||
debug = recv_debug_in_loop.recv() => {
|
Some(debug_command) = recv_debug_in_loop.recv() => {
|
||||||
if let Some(t::DebugCommand::Toggle) = debug {
|
match debug_command {
|
||||||
is_debug = !is_debug;
|
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
|
// network error message receiver: handle `timeout` and `offline` errors
|
||||||
@ -1042,17 +1053,20 @@ pub async fn kernel(
|
|||||||
while is_debug {
|
while is_debug {
|
||||||
let debug = recv_debug_in_loop.recv().await.expect("event loop: debug channel died");
|
let debug = recv_debug_in_loop.recv().await.expect("event loop: debug channel died");
|
||||||
match debug {
|
match debug {
|
||||||
t::DebugCommand::Toggle => is_debug = !is_debug,
|
t::DebugCommand::ToggleStepthrough => is_debug = !is_debug,
|
||||||
t::DebugCommand::Step => break,
|
t::DebugCommand::Step => break,
|
||||||
|
t::DebugCommand::ToggleEventLoop => print_full_event_loop = !print_full_event_loop,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// display every single event when verbose
|
// display every single event when verbose
|
||||||
let _ = send_to_terminal.send(
|
if print_full_event_loop {
|
||||||
|
let _ = send_to_terminal.send(
|
||||||
t::Printout {
|
t::Printout {
|
||||||
verbosity: 3,
|
verbosity: 3,
|
||||||
content: format!("{kernel_message}")
|
content: format!("{kernel_message}")
|
||||||
}
|
}
|
||||||
).await;
|
).await;
|
||||||
|
}
|
||||||
|
|
||||||
if our.name != kernel_message.target.node {
|
if our.name != kernel_message.target.node {
|
||||||
send_to_net.send(kernel_message).await.expect("fatal: net module died");
|
send_to_net.send(kernel_message).await.expect("fatal: net module died");
|
||||||
|
@ -455,17 +455,8 @@ async fn main() {
|
|||||||
|
|
||||||
// abort all remaining tasks
|
// abort all remaining tasks
|
||||||
tasks.shutdown().await;
|
tasks.shutdown().await;
|
||||||
let stdout = std::io::stdout();
|
// reset all modified aspects of terminal -- clean ourselves up
|
||||||
let mut stdout = stdout.lock();
|
terminal::utils::cleanup(&quit_msg);
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_http_server_port(set_port: Option<&u16>) -> u16 {
|
async fn set_http_server_port(set_port: Option<&u16>) -> u16 {
|
||||||
@ -755,12 +746,9 @@ async fn serve_register_fe(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
tokio::fs::write(
|
tokio::fs::write(format!("{}/.keys", home_directory_path), &encoded_keyfile)
|
||||||
format!("{}/.keys", home_directory_path),
|
.await
|
||||||
encoded_keyfile.clone(),
|
.unwrap();
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let _ = kill_tx.send(true);
|
let _ = kill_tx.send(true);
|
||||||
|
|
||||||
@ -831,12 +819,9 @@ async fn login_with_password(
|
|||||||
.await
|
.await
|
||||||
.expect("information used to boot does not match information onchain");
|
.expect("information used to boot does not match information onchain");
|
||||||
|
|
||||||
tokio::fs::write(
|
tokio::fs::write(format!("{}/.keys", home_directory_path), &disk_keyfile)
|
||||||
format!("{}/.keys", home_directory_path),
|
.await
|
||||||
disk_keyfile.clone(),
|
.unwrap();
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
(our, disk_keyfile, k)
|
(our, disk_keyfile, k)
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@ sol! {
|
|||||||
function key(bytes32) external view returns (bytes32);
|
function key(bytes32) external view returns (bytes32);
|
||||||
function nodes(bytes32) external view returns (address, uint96);
|
function nodes(bytes32) external view returns (address, uint96);
|
||||||
function ip(bytes32) external view returns (uint128, uint16, uint16, uint16, uint16);
|
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
|
/// 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 namehash = FixedBytes::<32>::from_slice(&keygen::namehash(&our.name));
|
||||||
let ip_call = ipCall { _0: namehash }.abi_encode();
|
let ip_call = ipCall { _0: namehash }.abi_encode();
|
||||||
let key_call = keyCall { _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_input = TransactionInput::new(Bytes::from(ip_call));
|
||||||
let tx = TransactionRequest::default()
|
let tx = TransactionRequest::default()
|
||||||
.to(kns_address)
|
.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!(
|
let node_ip = format!(
|
||||||
"{}.{}.{}.{}",
|
"{}.{}.{}.{}",
|
||||||
(ip >> 24) & 0xFF,
|
(ip >> 24) & 0xFF,
|
||||||
@ -739,6 +753,10 @@ pub async fn assign_routing(
|
|||||||
ip & 0xFF
|
ip & 0xFF
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if !routers.is_empty() {
|
||||||
|
// indirect node
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
if node_ip != *"0.0.0.0" && (ws != 0 || tcp != 0) {
|
if node_ip != *"0.0.0.0" && (ws != 0 || tcp != 0) {
|
||||||
// direct node
|
// direct node
|
||||||
let mut ports = std::collections::BTreeMap::new();
|
let mut ports = std::collections::BTreeMap::new();
|
||||||
@ -763,8 +781,6 @@ pub async fn assign_routing(
|
|||||||
ports.insert("tcp".to_string(), tcp);
|
ports.insert("tcp".to_string(), tcp);
|
||||||
}
|
}
|
||||||
our.routing = NodeRouting::Direct { ip: node_ip, ports };
|
our.routing = NodeRouting::Direct { ip: node_ip, ports };
|
||||||
} else {
|
|
||||||
// indirect node
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,41 +1,23 @@
|
|||||||
use anyhow::Result;
|
|
||||||
use chrono::{Datelike, Local, Timelike};
|
use chrono::{Datelike, Local, Timelike};
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
cursor,
|
cursor,
|
||||||
event::{
|
event::{Event, EventStream, KeyCode, KeyEvent, KeyModifiers},
|
||||||
DisableBracketedPaste, EnableBracketedPaste, Event, EventStream, KeyCode, KeyEvent,
|
|
||||||
KeyModifiers,
|
|
||||||
},
|
|
||||||
execute, style,
|
execute, style,
|
||||||
style::Print,
|
style::Print,
|
||||||
terminal::{self, disable_raw_mode, enable_raw_mode, ClearType},
|
terminal::{self, ClearType},
|
||||||
};
|
};
|
||||||
use futures::{future::FutureExt, StreamExt};
|
use futures::{future::FutureExt, StreamExt};
|
||||||
use std::fs::{read_to_string, OpenOptions};
|
use lib::types::core::{
|
||||||
use std::io::{stdout, BufWriter, Write};
|
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 tokio::signal::unix::{signal, SignalKind};
|
||||||
|
|
||||||
use lib::types::core::*;
|
pub mod utils;
|
||||||
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* terminal driver
|
* terminal driver
|
||||||
@ -50,122 +32,50 @@ pub async fn terminal(
|
|||||||
mut print_rx: PrintReceiver,
|
mut print_rx: PrintReceiver,
|
||||||
is_detached: bool,
|
is_detached: bool,
|
||||||
mut verbose_mode: u8,
|
mut verbose_mode: u8,
|
||||||
) -> Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let mut stdout = stdout();
|
let (stdout, _maybe_raw_mode) = utils::startup(&our, version, is_detached)?;
|
||||||
execute!(
|
|
||||||
stdout,
|
|
||||||
EnableBracketedPaste,
|
|
||||||
terminal::SetTitle(format!("{}", our.name))
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let (mut win_cols, mut win_rows) = terminal::size().unwrap();
|
// mutable because we adjust them on window resize events
|
||||||
// print initial splash screen, large if there's room, small otherwise
|
let (mut win_cols, mut win_rows) =
|
||||||
if win_cols >= 90 {
|
crossterm::terminal::size().expect("terminal: couldn't fetch size");
|
||||||
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
|
|
||||||
|
|
||||||
{} ({})
|
|
||||||
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 mut current_line = format!("{} > ", our.name);
|
||||||
let prompt_len: usize = our.name.len() + 3;
|
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 line_col: usize = cursor_col as usize;
|
||||||
|
|
||||||
let mut in_step_through: bool = false;
|
let mut in_step_through: bool = false;
|
||||||
|
|
||||||
let mut search_mode: bool = false;
|
let mut search_mode: bool = false;
|
||||||
let mut search_depth: usize = 0;
|
let mut search_depth: usize = 0;
|
||||||
|
|
||||||
let mut logging_mode: bool = false;
|
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)
|
let history_path = std::fs::canonicalize(&home_directory_path)
|
||||||
.unwrap()
|
.expect("terminal: could not get path for .terminal_history file")
|
||||||
.join(".terminal_history");
|
.join(".terminal_history");
|
||||||
let history = read_to_string(&history_path).unwrap_or_default();
|
let history = read_to_string(&history_path).unwrap_or_default();
|
||||||
let history_handle = OpenOptions::new()
|
let history_handle = OpenOptions::new()
|
||||||
.append(true)
|
.append(true)
|
||||||
.create(true)
|
.create(true)
|
||||||
.open(&history_path)
|
.open(&history_path)
|
||||||
.unwrap();
|
.expect("terminal: could not open/create .terminal_history");
|
||||||
let history_writer = BufWriter::new(history_handle);
|
let history_writer = BufWriter::new(history_handle);
|
||||||
// TODO make adjustable max history length
|
|
||||||
let mut command_history = utils::CommandHistory::new(1000, history, history_writer);
|
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)
|
let log_path = std::fs::canonicalize(&home_directory_path)
|
||||||
.unwrap()
|
.expect("terminal: could not get path for .terminal_log file")
|
||||||
.join(".terminal_log");
|
.join(".terminal_log");
|
||||||
let log_handle = OpenOptions::new()
|
let log_handle = OpenOptions::new()
|
||||||
.append(true)
|
.append(true)
|
||||||
.create(true)
|
.create(true)
|
||||||
.open(&log_path)
|
.open(&log_path)
|
||||||
.unwrap();
|
.expect("terminal: could not open/create .terminal_log");
|
||||||
let mut log_writer = BufWriter::new(log_handle);
|
let mut log_writer = BufWriter::new(log_handle);
|
||||||
|
|
||||||
// use to trigger cleanup if receive signal to kill process
|
// use to trigger cleanup if receive signal to kill process
|
||||||
@ -186,21 +96,32 @@ pub async fn terminal(
|
|||||||
let mut sigusr2 =
|
let mut sigusr2 =
|
||||||
signal(SignalKind::user_defined2()).expect("terminal: failed to set up SIGUSR2 handler");
|
signal(SignalKind::user_defined2()).expect("terminal: failed to set up SIGUSR2 handler");
|
||||||
|
|
||||||
loop {
|
// if the verbosity boot flag was **not** set to "full event loop", tell kernel
|
||||||
let event = reader.next().fuse();
|
// 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! {
|
tokio::select! {
|
||||||
Some(printout) = print_rx.recv() => {
|
Some(printout) = print_rx.recv() => {
|
||||||
let now = Local::now();
|
let now = Local::now();
|
||||||
|
// always write print to log if in logging mode
|
||||||
if 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 {
|
if printout.verbosity > verbose_mode {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let mut stdout = stdout.lock();
|
|
||||||
execute!(
|
execute!(
|
||||||
stdout,
|
stdout,
|
||||||
|
// print goes immediately above the dedicated input line at bottom
|
||||||
cursor::MoveTo(0, win_rows - 1),
|
cursor::MoveTo(0, win_rows - 1),
|
||||||
terminal::Clear(ClearType::CurrentLine),
|
terminal::Clear(ClearType::CurrentLine),
|
||||||
Print(format!("{} {:02}:{:02} ",
|
Print(format!("{} {:02}:{:02} ",
|
||||||
@ -208,41 +129,45 @@ pub async fn terminal(
|
|||||||
now.hour(),
|
now.hour(),
|
||||||
now.minute(),
|
now.minute(),
|
||||||
)),
|
)),
|
||||||
)?;
|
style::SetForegroundColor(match printout.verbosity {
|
||||||
let color = match printout.verbosity {
|
|
||||||
0 => style::Color::Reset,
|
0 => style::Color::Reset,
|
||||||
1 => style::Color::Green,
|
1 => style::Color::Green,
|
||||||
2 => style::Color::Magenta,
|
2 => style::Color::Magenta,
|
||||||
_ => style::Color::Red,
|
_ => style::Color::Red,
|
||||||
};
|
}),
|
||||||
|
)?;
|
||||||
for line in printout.content.lines() {
|
for line in printout.content.lines() {
|
||||||
execute!(
|
execute!(
|
||||||
stdout,
|
stdout,
|
||||||
style::SetForegroundColor(color),
|
|
||||||
Print(format!("{}\r\n", line)),
|
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!(
|
execute!(
|
||||||
stdout,
|
stdout,
|
||||||
|
style::ResetColor,
|
||||||
cursor::MoveTo(0, win_rows),
|
cursor::MoveTo(0, win_rows),
|
||||||
Print(utils::truncate_in_place(¤t_line, prompt_len, win_cols, (line_col, cursor_col))),
|
Print(utils::truncate_in_place(¤t_line, prompt_len, win_cols, (line_col, cursor_col))),
|
||||||
cursor::MoveTo(cursor_col, win_rows),
|
cursor::MoveTo(cursor_col, win_rows),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
Some(Ok(event)) = event => {
|
Some(Ok(event)) = reader.next().fuse() => {
|
||||||
let mut stdout = stdout.lock();
|
|
||||||
match event {
|
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
|
// comes "too late" to stop terminal from messing with the
|
||||||
// already-printed lines. TODO figure out the right way
|
// already-printed lines. TODO figure out the right way
|
||||||
// to compensate for this cross-platform and do this in a
|
// to compensate for this cross-platform and do this in a
|
||||||
// generally stable way.
|
// generally stable way.
|
||||||
|
//
|
||||||
Event::Resize(width, height) => {
|
Event::Resize(width, height) => {
|
||||||
win_cols = width;
|
win_cols = width;
|
||||||
win_rows = height;
|
win_rows = height;
|
||||||
},
|
},
|
||||||
// handle pasting of text from outside
|
//
|
||||||
|
// PASTE: handle pasting of text from outside
|
||||||
|
//
|
||||||
Event::Paste(pasted) => {
|
Event::Paste(pasted) => {
|
||||||
// strip out control characters and newlines
|
// strip out control characters and newlines
|
||||||
let pasted = pasted.chars().filter(|c| !c.is_control() && !c.is_ascii_control()).collect::<String>();
|
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),
|
cursor::MoveTo(cursor_col, win_rows),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
//
|
||||||
// CTRL+C, CTRL+D: turn off the node
|
// CTRL+C, CTRL+D: turn off the node
|
||||||
|
//
|
||||||
Event::Key(KeyEvent {
|
Event::Key(KeyEvent {
|
||||||
code: KeyCode::Char('c'),
|
code: KeyCode::Char('c'),
|
||||||
modifiers: KeyModifiers::CONTROL,
|
modifiers: KeyModifiers::CONTROL,
|
||||||
@ -267,10 +194,18 @@ pub async fn terminal(
|
|||||||
modifiers: KeyModifiers::CONTROL,
|
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;
|
break;
|
||||||
},
|
},
|
||||||
|
//
|
||||||
// CTRL+V: toggle through verbosity modes
|
// CTRL+V: toggle through verbosity modes
|
||||||
|
//
|
||||||
Event::Key(KeyEvent {
|
Event::Key(KeyEvent {
|
||||||
code: KeyCode::Char('v'),
|
code: KeyCode::Char('v'),
|
||||||
modifiers: KeyModifiers::CONTROL,
|
modifiers: KeyModifiers::CONTROL,
|
||||||
@ -294,26 +229,34 @@ pub async fn terminal(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
).await;
|
).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+J: toggle debug mode -- makes system-level event loop step-through
|
||||||
// CTRL+S: step through system-level event loop
|
//
|
||||||
Event::Key(KeyEvent {
|
Event::Key(KeyEvent {
|
||||||
code: KeyCode::Char('j'),
|
code: KeyCode::Char('j'),
|
||||||
modifiers: KeyModifiers::CONTROL,
|
modifiers: KeyModifiers::CONTROL,
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
|
let _ = debug_event_loop.send(DebugCommand::ToggleStepthrough).await;
|
||||||
|
in_step_through = !in_step_through;
|
||||||
let _ = print_tx.send(
|
let _ = print_tx.send(
|
||||||
Printout {
|
Printout {
|
||||||
verbosity: 0,
|
verbosity: 0,
|
||||||
content: match in_step_through {
|
content: match in_step_through {
|
||||||
true => "debug mode off".into(),
|
false => "debug mode off".into(),
|
||||||
false => "debug mode on: use CTRL+S to step through events".into(),
|
true => "debug mode on: use CTRL+S to step through events".into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
).await;
|
).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 {
|
Event::Key(KeyEvent {
|
||||||
code: KeyCode::Char('s'),
|
code: KeyCode::Char('s'),
|
||||||
modifiers: KeyModifiers::CONTROL,
|
modifiers: KeyModifiers::CONTROL,
|
||||||
@ -342,7 +285,6 @@ pub async fn terminal(
|
|||||||
},
|
},
|
||||||
//
|
//
|
||||||
// UP / CTRL+P: go up one command in history
|
// 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 { code: KeyCode::Up, .. }) |
|
||||||
Event::Key(KeyEvent {
|
Event::Key(KeyEvent {
|
||||||
@ -357,6 +299,7 @@ pub async fn terminal(
|
|||||||
line_col = current_line.len();
|
line_col = current_line.len();
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
|
// the "no-no" ding
|
||||||
print!("\x07");
|
print!("\x07");
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -368,6 +311,9 @@ pub async fn terminal(
|
|||||||
Print(utils::truncate_rightward(¤t_line, prompt_len, win_cols)),
|
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::Down, .. }) |
|
||||||
Event::Key(KeyEvent {
|
Event::Key(KeyEvent {
|
||||||
code: KeyCode::Char('n'),
|
code: KeyCode::Char('n'),
|
||||||
@ -381,6 +327,7 @@ pub async fn terminal(
|
|||||||
line_col = current_line.len();
|
line_col = current_line.len();
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
|
// the "no-no" ding
|
||||||
print!("\x07");
|
print!("\x07");
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -401,7 +348,7 @@ pub async fn terminal(
|
|||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
line_col = prompt_len;
|
line_col = prompt_len;
|
||||||
cursor_col = prompt_len.try_into().unwrap();
|
cursor_col = prompt_len as u16;
|
||||||
execute!(
|
execute!(
|
||||||
stdout,
|
stdout,
|
||||||
cursor::MoveTo(0, win_rows),
|
cursor::MoveTo(0, win_rows),
|
||||||
@ -438,32 +385,16 @@ pub async fn terminal(
|
|||||||
search_depth += 1;
|
search_depth += 1;
|
||||||
}
|
}
|
||||||
search_mode = true;
|
search_mode = true;
|
||||||
let search_query = ¤t_line[prompt_len..];
|
utils::execute_search(
|
||||||
if search_query.is_empty() {
|
&our,
|
||||||
continue;
|
&mut stdout,
|
||||||
}
|
¤t_line,
|
||||||
if let Some(result) = command_history.search(search_query, search_depth) {
|
prompt_len,
|
||||||
let result_underlined = utils::underline(result, search_query);
|
(win_cols, win_rows),
|
||||||
execute!(
|
(line_col, cursor_col),
|
||||||
stdout,
|
&mut command_history,
|
||||||
cursor::MoveTo(0, win_rows),
|
search_depth,
|
||||||
terminal::Clear(ClearType::CurrentLine),
|
)?;
|
||||||
Print(utils::truncate_in_place(
|
|
||||||
&format!("{} * {}", our.name, result_underlined),
|
|
||||||
prompt_len,
|
|
||||||
win_cols,
|
|
||||||
(line_col, cursor_col))),
|
|
||||||
cursor::MoveTo(cursor_col, win_rows),
|
|
||||||
)?;
|
|
||||||
} else {
|
|
||||||
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),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
//
|
//
|
||||||
// CTRL+G: exit search mode
|
// CTRL+G: exit search mode
|
||||||
@ -480,15 +411,22 @@ pub async fn terminal(
|
|||||||
stdout,
|
stdout,
|
||||||
cursor::MoveTo(0, win_rows),
|
cursor::MoveTo(0, win_rows),
|
||||||
terminal::Clear(ClearType::CurrentLine),
|
terminal::Clear(ClearType::CurrentLine),
|
||||||
Print(utils::truncate_in_place(¤t_line, prompt_len, win_cols, (line_col, cursor_col))),
|
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),
|
cursor::MoveTo(cursor_col, win_rows),
|
||||||
)?;
|
)?;
|
||||||
},
|
},
|
||||||
//
|
//
|
||||||
// handle keypress events
|
// KEY: handle keypress events
|
||||||
//
|
//
|
||||||
Event::Key(k) => {
|
Event::Key(k) => {
|
||||||
match k.code {
|
match k.code {
|
||||||
|
//
|
||||||
|
// CHAR: write a single character
|
||||||
|
//
|
||||||
KeyCode::Char(c) => {
|
KeyCode::Char(c) => {
|
||||||
current_line.insert(line_col, c);
|
current_line.insert(line_col, c);
|
||||||
if cursor_col < win_cols {
|
if cursor_col < win_cols {
|
||||||
@ -496,22 +434,17 @@ pub async fn terminal(
|
|||||||
}
|
}
|
||||||
line_col += 1;
|
line_col += 1;
|
||||||
if search_mode {
|
if search_mode {
|
||||||
let search_query = ¤t_line[prompt_len..];
|
utils::execute_search(
|
||||||
if let Some(result) = command_history.search(search_query, search_depth) {
|
&our,
|
||||||
let result_underlined = utils::underline(result, search_query);
|
&mut stdout,
|
||||||
execute!(
|
¤t_line,
|
||||||
stdout,
|
prompt_len,
|
||||||
cursor::MoveTo(0, win_rows),
|
(win_cols, win_rows),
|
||||||
terminal::Clear(ClearType::CurrentLine),
|
(line_col, cursor_col),
|
||||||
Print(utils::truncate_in_place(
|
&mut command_history,
|
||||||
&format!("{} * {}", our.name, result_underlined),
|
search_depth,
|
||||||
prompt_len,
|
)?;
|
||||||
win_cols,
|
continue;
|
||||||
(line_col, cursor_col))),
|
|
||||||
cursor::MoveTo(cursor_col, win_rows),
|
|
||||||
)?;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
execute!(
|
execute!(
|
||||||
stdout,
|
stdout,
|
||||||
@ -521,6 +454,9 @@ pub async fn terminal(
|
|||||||
cursor::MoveTo(cursor_col, win_rows),
|
cursor::MoveTo(cursor_col, win_rows),
|
||||||
)?;
|
)?;
|
||||||
},
|
},
|
||||||
|
//
|
||||||
|
// BACKSPACE or DELETE: delete a single character at cursor
|
||||||
|
//
|
||||||
KeyCode::Backspace | KeyCode::Delete => {
|
KeyCode::Backspace | KeyCode::Delete => {
|
||||||
if line_col == prompt_len {
|
if line_col == prompt_len {
|
||||||
continue;
|
continue;
|
||||||
@ -531,22 +467,17 @@ pub async fn terminal(
|
|||||||
line_col -= 1;
|
line_col -= 1;
|
||||||
current_line.remove(line_col);
|
current_line.remove(line_col);
|
||||||
if search_mode {
|
if search_mode {
|
||||||
let search_query = ¤t_line[prompt_len..];
|
utils::execute_search(
|
||||||
if let Some(result) = command_history.search(search_query, search_depth) {
|
&our,
|
||||||
let result_underlined = utils::underline(result, search_query);
|
&mut stdout,
|
||||||
execute!(
|
¤t_line,
|
||||||
stdout,
|
prompt_len,
|
||||||
cursor::MoveTo(0, win_rows),
|
(win_cols, win_rows),
|
||||||
terminal::Clear(ClearType::CurrentLine),
|
(line_col, cursor_col),
|
||||||
Print(utils::truncate_in_place(
|
&mut command_history,
|
||||||
&format!("{} * {}", our.name, result_underlined),
|
search_depth,
|
||||||
prompt_len,
|
)?;
|
||||||
win_cols,
|
continue;
|
||||||
(line_col, cursor_col))),
|
|
||||||
cursor::MoveTo(cursor_col, win_rows),
|
|
||||||
)?;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
execute!(
|
execute!(
|
||||||
stdout,
|
stdout,
|
||||||
@ -556,6 +487,9 @@ pub async fn terminal(
|
|||||||
cursor::MoveTo(cursor_col, win_rows),
|
cursor::MoveTo(cursor_col, win_rows),
|
||||||
)?;
|
)?;
|
||||||
},
|
},
|
||||||
|
//
|
||||||
|
// LEFT: move cursor one spot left
|
||||||
|
//
|
||||||
KeyCode::Left => {
|
KeyCode::Left => {
|
||||||
if cursor_col as usize == prompt_len {
|
if cursor_col as usize == prompt_len {
|
||||||
if line_col == prompt_len {
|
if line_col == prompt_len {
|
||||||
@ -581,6 +515,9 @@ pub async fn terminal(
|
|||||||
line_col -= 1;
|
line_col -= 1;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
//
|
||||||
|
// RIGHT: move cursor one spot right
|
||||||
|
//
|
||||||
KeyCode::Right => {
|
KeyCode::Right => {
|
||||||
if line_col == current_line.len() {
|
if line_col == current_line.len() {
|
||||||
// at the very end of the current typed line
|
// 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 => {
|
KeyCode::Enter => {
|
||||||
// if we were in search mode, pull command from that
|
// if we were in search mode, pull command from that
|
||||||
let command = if !search_mode {
|
let command = if !search_mode {
|
||||||
@ -612,7 +552,7 @@ pub async fn terminal(
|
|||||||
command_history.search(
|
command_history.search(
|
||||||
¤t_line[prompt_len..],
|
¤t_line[prompt_len..],
|
||||||
search_depth
|
search_depth
|
||||||
).unwrap_or(¤t_line[prompt_len..]).to_string()
|
).unwrap_or_default().to_string()
|
||||||
};
|
};
|
||||||
let next = format!("{} > ", our.name);
|
let next = format!("{} > ", our.name);
|
||||||
execute!(
|
execute!(
|
||||||
@ -627,7 +567,7 @@ pub async fn terminal(
|
|||||||
search_depth = 0;
|
search_depth = 0;
|
||||||
current_line = next;
|
current_line = next;
|
||||||
command_history.add(command.clone());
|
command_history.add(command.clone());
|
||||||
cursor_col = prompt_len.try_into().unwrap();
|
cursor_col = prompt_len as u16;
|
||||||
line_col = prompt_len;
|
line_col = prompt_len;
|
||||||
event_loop.send(
|
event_loop.send(
|
||||||
KernelMessage {
|
KernelMessage {
|
||||||
@ -652,10 +592,14 @@ pub async fn terminal(
|
|||||||
}
|
}
|
||||||
).await.expect("terminal: couldn't execute command!");
|
).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")),
|
_ = 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")),
|
_ = sigusr2.recv() => return Err(anyhow::anyhow!("exiting due to SIGUSR2")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
execute!(stdout.lock(), DisableBracketedPaste, terminal::SetTitle(""))?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,137 @@
|
|||||||
use std::collections::VecDeque;
|
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||||
use std::fs::File;
|
use lib::types::core::Identity;
|
||||||
use std::io::{BufWriter, Write};
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct CommandHistory {
|
pub struct CommandHistory {
|
||||||
@ -70,6 +201,9 @@ impl CommandHistory {
|
|||||||
/// yes this is O(n) to provide desired ordering, can revisit if slow
|
/// yes this is O(n) to provide desired ordering, can revisit if slow
|
||||||
pub fn search(&mut self, find: &str, depth: usize) -> Option<&str> {
|
pub fn search(&mut self, find: &str, depth: usize) -> Option<&str> {
|
||||||
let mut skips = 0;
|
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
|
// if there is at least one match, and we've skipped past it, return oldest match
|
||||||
let mut last_match: Option<&str> = None;
|
let mut last_match: Option<&str> = None;
|
||||||
for line in self.lines.iter() {
|
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 = ¤t_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, ¤t_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
|
// format result string to have query portion underlined
|
||||||
let mut result = s.to_string();
|
let mut result = s.to_string();
|
||||||
let u_start = s.find(to_underline).unwrap();
|
let u_start = s.find(to_underline).unwrap();
|
||||||
let u_end = u_start + to_underline.len();
|
let u_end = u_start + to_underline.len();
|
||||||
result.insert_str(u_end, "\x1b[24m");
|
result.insert_str(u_end, "\x1b[24m");
|
||||||
result.insert_str(u_start, "\x1b[4m");
|
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 {
|
pub fn truncate_rightward(s: &str, prompt_len: usize, width: u16) -> String {
|
||||||
|
@ -1180,8 +1180,9 @@ pub type Rsvp = Option<Address>;
|
|||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub enum DebugCommand {
|
pub enum DebugCommand {
|
||||||
Toggle,
|
ToggleStepthrough,
|
||||||
Step,
|
Step,
|
||||||
|
ToggleEventLoop,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// IPC format for requests sent to kernel runtime module
|
/// IPC format for requests sent to kernel runtime module
|
||||||
|
Loading…
Reference in New Issue
Block a user