diff --git a/Cargo.lock b/Cargo.lock index c4f5b0c1..5e7d733e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -427,6 +427,27 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if 1.0.0", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "dtoa" version = "0.4.6" @@ -961,6 +982,7 @@ dependencies = [ "async-std", "backtrace", "bincode", + "directories-next", "futures", "insta", "interprocess", @@ -1228,13 +1250,32 @@ version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +[[package]] +name = "redox_syscall" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ec8ca9416c5ea37062b502703cd7fcb207736bc294f6e0cf367ac6fc234570" +dependencies = [ + "bitflags", +] + [[package]] name = "redox_termios" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" dependencies = [ - "redox_syscall", + "redox_syscall 0.1.57", +] + +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom 0.2.0", + "redox_syscall 0.2.4", ] [[package]] @@ -1442,7 +1483,7 @@ checksum = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918" dependencies = [ "cfg-if 0.1.10", "libc", - "redox_syscall", + "redox_syscall 0.1.57", "winapi", ] @@ -1532,7 +1573,7 @@ dependencies = [ "cfg-if 0.1.10", "libc", "rand", - "redox_syscall", + "redox_syscall 0.1.57", "remove_dir_all", "winapi", ] @@ -1554,7 +1595,7 @@ source = "git+https://gitlab.com/TheLostLambda/termion.git#70159e07c59c02dc681db dependencies = [ "libc", "numtoa", - "redox_syscall", + "redox_syscall 0.1.57", "redox_termios", "serde", ] diff --git a/Cargo.toml b/Cargo.toml index 82c11965..779de5be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ edition = "2018" [dependencies] backtrace = "0.3.55" bincode = "1.3.1" +directories-next = "2.0" futures = "0.3.5" libc = "0.2" nix = "0.17.0" @@ -35,6 +36,7 @@ features = ["unstable"] insta = "0.16.1" [build-dependencies] +directories-next = "2.0" structopt = "0.3" [profile.release] diff --git a/GOVERNANCE.md b/GOVERNANCE.md index e02b5a44..3dbc852f 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -25,3 +25,4 @@ Once the organization reaches 10 members, a reasonable and achievable process mu * Brooks Rady * Denis Maximov * Kunal Mohan +* Henil Dedania diff --git a/assets/layouts/default.yaml b/assets/layouts/default.yaml new file mode 100644 index 00000000..e6335691 --- /dev/null +++ b/assets/layouts/default.yaml @@ -0,0 +1,8 @@ +--- +direction: Horizontal +parts: + - direction: Vertical + - direction: Vertical + split_size: + Fixed: 1 + plugin: status-bar \ No newline at end of file diff --git a/src/tests/fixtures/layouts/panes-with-plugins.yaml b/assets/layouts/strider.yaml similarity index 63% rename from src/tests/fixtures/layouts/panes-with-plugins.yaml rename to assets/layouts/strider.yaml index 7fb13b2e..e33f516a 100644 --- a/src/tests/fixtures/layouts/panes-with-plugins.yaml +++ b/assets/layouts/strider.yaml @@ -6,12 +6,9 @@ parts: - direction: Horizontal split_size: Percent: 20 - plugin: strider.wasm + plugin: strider - direction: Horizontal - split_size: - Percent: 80 - split_size: - Percent: 80 - direction: Vertical split_size: - Percent: 20 \ No newline at end of file + Fixed: 1 + plugin: status-bar \ No newline at end of file diff --git a/assets/plugins/status-bar.wasm b/assets/plugins/status-bar.wasm new file mode 100644 index 00000000..0b012080 Binary files /dev/null and b/assets/plugins/status-bar.wasm differ diff --git a/strider.wasm b/assets/plugins/strider.wasm similarity index 77% rename from strider.wasm rename to assets/plugins/strider.wasm index e0062773..77b07b01 100644 Binary files a/strider.wasm and b/assets/plugins/strider.wasm differ diff --git a/build.rs b/build.rs index f0f92e61..c405d27e 100644 --- a/build.rs +++ b/build.rs @@ -1,4 +1,5 @@ -use std::fs; +use directories_next::ProjectDirs; +use std::{fs, path::Path}; use structopt::clap::Shell; include!("src/cli.rs"); @@ -6,8 +7,9 @@ include!("src/cli.rs"); const BIN_NAME: &str = "mosaic"; fn main() { + // Generate Shell Completions let mut clap_app = CliArgs::clap(); - println!("cargo:rerun-if-changed=src/app.rs"); + println!("cargo:rerun-if-changed=src/cli.rs"); let mut out_dir = std::env::var_os("CARGO_MANIFEST_DIR").unwrap(); out_dir.push("/assets/completions"); @@ -19,4 +21,21 @@ fn main() { clap_app.gen_completions(BIN_NAME, Shell::Bash, &out_dir); clap_app.gen_completions(BIN_NAME, Shell::Zsh, &out_dir); clap_app.gen_completions(BIN_NAME, Shell::Fish, &out_dir); + + // Install Default Plugins and Layouts + let assets = vec![ + "plugins/status-bar.wasm", + "plugins/strider.wasm", + "layouts/default.yaml", + "layouts/strider.yaml", + ]; + let project_dirs = ProjectDirs::from("org", "Mosaic Contributors", "Mosaic").unwrap(); + let data_dir = project_dirs.data_dir(); + fs::create_dir_all(data_dir.join("plugins")).unwrap(); + fs::create_dir_all(data_dir.join("layouts")).unwrap(); + for asset in assets { + println!("cargo:rerun-if-changed=assets/{}", asset); + fs::copy(Path::new("assets/").join(asset), data_dir.join(asset)) + .expect("Failed to copy asset files"); + } } diff --git a/src/errors.rs b/src/errors.rs index e53bbc30..cb483bbc 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -166,6 +166,7 @@ pub enum ScreenContext { ClearScroll, CloseFocusedPane, ToggleActiveTerminalFullscreen, + SetSelectable, ClosePane, ApplyLayout, NewTab, @@ -200,6 +201,7 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::ToggleActiveTerminalFullscreen => { ScreenContext::ToggleActiveTerminalFullscreen } + ScreenInstruction::SetSelectable(..) => ScreenContext::SetSelectable, ScreenInstruction::ClosePane(_) => ScreenContext::ClosePane, ScreenInstruction::ApplyLayout(_) => ScreenContext::ApplyLayout, ScreenInstruction::NewTab(_) => ScreenContext::NewTab, @@ -244,6 +246,7 @@ pub enum PluginContext { Load, Draw, Input, + GlobalInput, Unload, Quit, } @@ -254,6 +257,7 @@ impl From<&PluginInstruction> for PluginContext { PluginInstruction::Load(..) => PluginContext::Load, PluginInstruction::Draw(..) => PluginContext::Draw, PluginInstruction::Input(..) => PluginContext::Input, + PluginInstruction::GlobalInput(_) => PluginContext::GlobalInput, PluginInstruction::Unload(_) => PluginContext::Unload, PluginInstruction::Quit => PluginContext::Quit, } @@ -262,6 +266,8 @@ impl From<&PluginInstruction> for PluginContext { #[derive(Debug, Clone, Copy, PartialEq)] pub enum AppContext { + GetState, + SetState, Exit, Error, } @@ -269,6 +275,8 @@ pub enum AppContext { impl From<&AppInstruction> for AppContext { fn from(app_instruction: &AppInstruction) -> Self { match *app_instruction { + AppInstruction::GetState(_) => AppContext::GetState, + AppInstruction::SetState(_) => AppContext::SetState, AppInstruction::Exit => AppContext::Exit, AppInstruction::Error(_) => AppContext::Error, } diff --git a/src/input.rs b/src/input.rs index cc144361..3c5518f8 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,9 +1,8 @@ -/// Module for handling input -use crate::errors::ContextType; -use crate::os_input_output::OsApi; use crate::pty_bus::PtyInstruction; use crate::screen::ScreenInstruction; use crate::CommandIsExecuting; +use crate::{errors::ContextType, wasm_vm::PluginInstruction}; +use crate::{os_input_output::OsApi, update_state, AppState}; use crate::{AppInstruction, SenderWithContext, OPENCALLS}; struct InputHandler { @@ -12,6 +11,7 @@ struct InputHandler { command_is_executing: CommandIsExecuting, send_screen_instructions: SenderWithContext, send_pty_instructions: SenderWithContext, + send_plugin_instructions: SenderWithContext, send_app_instructions: SenderWithContext, } @@ -21,6 +21,7 @@ impl InputHandler { command_is_executing: CommandIsExecuting, send_screen_instructions: SenderWithContext, send_pty_instructions: SenderWithContext, + send_plugin_instructions: SenderWithContext, send_app_instructions: SenderWithContext, ) -> Self { InputHandler { @@ -29,6 +30,7 @@ impl InputHandler { command_is_executing, send_screen_instructions, send_pty_instructions, + send_plugin_instructions, send_app_instructions, } } @@ -38,9 +40,13 @@ impl InputHandler { let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow()); err_ctx.add_call(ContextType::StdinHandler); self.send_pty_instructions.update(err_ctx); + self.send_plugin_instructions.update(err_ctx); self.send_app_instructions.update(err_ctx); self.send_screen_instructions.update(err_ctx); loop { + update_state(&self.send_app_instructions, |_| AppState { + input_mode: self.mode, + }); match self.mode { InputMode::Normal => self.read_normal_mode(), InputMode::Command => self.read_command_mode(false), @@ -59,6 +65,11 @@ impl InputHandler { loop { let stdin_buffer = self.os_input.read_from_stdin(); + #[cfg(not(test))] // Absolutely zero clue why this breaks *all* of the tests + drop( + self.send_plugin_instructions + .send(PluginInstruction::GlobalInput(stdin_buffer.clone())), + ); match stdin_buffer.as_slice() { [7] => { // ctrl-g @@ -88,6 +99,11 @@ impl InputHandler { loop { let stdin_buffer = self.os_input.read_from_stdin(); + #[cfg(not(test))] // Absolutely zero clue why this breaks *all* of the tests + drop( + self.send_plugin_instructions + .send(PluginInstruction::GlobalInput(stdin_buffer.clone())), + ); // uncomment this to print the entered character to a log file (/tmp/mosaic/mosaic-log.txt) for debugging // debug_log_to_file(format!("buffer {:?}", stdin_buffer)); @@ -98,12 +114,10 @@ impl InputHandler { // multiple commands. If we're already in persistent mode, it'll return us to normal mode. match self.mode { InputMode::Command => self.mode = InputMode::CommandPersistent, - InputMode::CommandPersistent => { - self.mode = InputMode::Normal; - return; - } + InputMode::CommandPersistent => self.mode = InputMode::Normal, _ => panic!(), } + return; } [27] => { // Esc @@ -248,7 +262,7 @@ impl InputHandler { self.command_is_executing.wait_until_pane_is_closed(); } //@@@khs26 Write this to the powerbar? - _ => {} + _ => continue, } if self.mode == InputMode::Command { @@ -267,6 +281,9 @@ impl InputHandler { self.send_pty_instructions .send(PtyInstruction::Quit) .unwrap(); + self.send_plugin_instructions + .send(PluginInstruction::Quit) + .unwrap(); self.send_app_instructions .send(AppInstruction::Exit) .unwrap(); @@ -282,7 +299,7 @@ impl InputHandler { /// normal mode /// - Exiting means that we should start the shutdown process for mosaic or the given /// input handler -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone, Copy)] pub enum InputMode { Normal, Command, @@ -290,6 +307,36 @@ pub enum InputMode { Exiting, } +// FIXME: This should be auto-generated from the soon-to-be-added `get_default_keybinds` +pub fn get_help(mode: &InputMode) -> Vec { + let command_help = vec![ + " Split".into(), + " Resize".into(), + "

Focus Next".into(), + " Close Pane".into(), + " Quit".into(), + " Scroll".into(), + "<1> New Tab".into(), + "<2/3> Move Tab".into(), + "<4> Close Tab".into(), + ]; + match mode { + InputMode::Normal => vec![" Command Mode".into()], + InputMode::Command => [ + vec![ + " Persistent Mode".into(), + " Normal Mode".into(), + ], + command_help, + ] + .concat(), + InputMode::CommandPersistent => { + [vec![" Normal Mode".into()], command_help].concat() + } + InputMode::Exiting => vec!["Bye from Mosaic!".into()], + } +} + /// Entry point to the module that instantiates a new InputHandler and calls its /// reading loop pub fn input_loop( @@ -297,6 +344,7 @@ pub fn input_loop( command_is_executing: CommandIsExecuting, send_screen_instructions: SenderWithContext, send_pty_instructions: SenderWithContext, + send_plugin_instructions: SenderWithContext, send_app_instructions: SenderWithContext, ) { let _handler = InputHandler::new( @@ -304,6 +352,7 @@ pub fn input_loop( command_is_executing, send_screen_instructions, send_pty_instructions, + send_plugin_instructions, send_app_instructions, ) .get_input(); diff --git a/src/layout.rs b/src/layout.rs index c82fc404..d09e7618 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -1,62 +1,135 @@ +use crate::utils::consts::MOSAIC_ROOT_LAYOUT_DIR; +use directories_next::ProjectDirs; use serde::{Deserialize, Serialize}; -use std::{fs::File, io::prelude::*, path::PathBuf}; +use std::path::{Path, PathBuf}; +use std::{fs::File, io::prelude::*}; use crate::panes::PositionAndSize; fn split_space_to_parts_vertically( space_to_split: &PositionAndSize, - percentages: Vec, + sizes: Vec>, ) -> Vec { - let mut split_parts = vec![]; + let mut split_parts = Vec::new(); let mut current_x_position = space_to_split.x; - let width = space_to_split.columns - (percentages.len() - 1); // minus space for gaps - for percentage in percentages.iter() { - let columns = (width as f32 * (*percentage as f32 / 100.0)) as usize; // TODO: round properly + let mut current_width = 0; + let max_width = space_to_split.columns - (sizes.len() - 1); // minus space for gaps + + let mut parts_to_grow = Vec::new(); + + // First fit in the parameterized sizes + for size in sizes { + let columns = match size { + Some(SplitSize::Percent(percent)) => { + (max_width as f32 * (percent as f32 / 100.0)) as usize + } // TODO: round properly + Some(SplitSize::Fixed(size)) => size as usize, + None => { + parts_to_grow.push(current_x_position); + 1 // This is grown later on + } + }; split_parts.push(PositionAndSize { x: current_x_position, y: space_to_split.y, columns, rows: space_to_split.rows, }); + current_width += columns; current_x_position += columns + 1; // 1 for gap } - let total_width = split_parts - .iter() - .fold(0, |total_width, part| total_width + part.columns); - if total_width < width { - // we have some extra space left, let's add it to the last part - let last_part_index = split_parts.len() - 1; - let mut last_part = split_parts.get_mut(last_part_index).unwrap(); - last_part.columns += width - total_width; + + if current_width > max_width { + panic!("Layout contained too many columns to fit onto the screen!"); + } + + let mut last_flexible_index = split_parts.len() - 1; + if let Some(new_columns) = (max_width - current_width).checked_div(parts_to_grow.len()) { + current_width = 0; + current_x_position = 0; + for (idx, part) in split_parts.iter_mut().enumerate() { + part.x = current_x_position; + if parts_to_grow.contains(&part.x) { + part.columns = new_columns; + last_flexible_index = idx; + } + current_width += part.columns; + current_x_position += part.columns + 1; // 1 for gap + } + } + + if current_width < max_width { + // we have some extra space left, let's add it to the last flexible part + let extra = max_width - current_width; + let mut last_part = split_parts.get_mut(last_flexible_index).unwrap(); + last_part.columns += extra; + for part in (&mut split_parts[last_flexible_index + 1..]).iter_mut() { + part.x += extra; + } } split_parts } fn split_space_to_parts_horizontally( space_to_split: &PositionAndSize, - percentages: Vec, + sizes: Vec>, ) -> Vec { - let mut split_parts = vec![]; + let mut split_parts = Vec::new(); let mut current_y_position = space_to_split.y; - let height = space_to_split.rows - (percentages.len() - 1); // minus space for gaps - for percentage in percentages.iter() { - let rows = (height as f32 * (*percentage as f32 / 100.0)) as usize; // TODO: round properly + let mut current_height = 0; + let max_height = space_to_split.rows - (sizes.len() - 1); // minus space for gaps + + let mut parts_to_grow = Vec::new(); + + for size in sizes { + let rows = match size { + Some(SplitSize::Percent(percent)) => { + (max_height as f32 * (percent as f32 / 100.0)) as usize + } // TODO: round properly + Some(SplitSize::Fixed(size)) => size as usize, + None => { + parts_to_grow.push(current_y_position); + 1 // This is grown later on + } + }; split_parts.push(PositionAndSize { x: space_to_split.x, y: current_y_position, columns: space_to_split.columns, rows, }); + current_height += rows; current_y_position += rows + 1; // 1 for gap } - let total_height = split_parts - .iter() - .fold(0, |total_height, part| total_height + part.rows); - if total_height < height { - // we have some extra space left, let's add it to the last part - let last_part_index = split_parts.len() - 1; - let mut last_part = split_parts.get_mut(last_part_index).unwrap(); - last_part.rows += height - total_height; + + if current_height > max_height { + panic!("Layout contained too many rows to fit onto the screen!"); + } + + let mut last_flexible_index = split_parts.len() - 1; + if let Some(new_rows) = (max_height - current_height).checked_div(parts_to_grow.len()) { + current_height = 0; + current_y_position = 0; + + for (idx, part) in split_parts.iter_mut().enumerate() { + part.y = current_y_position; + if parts_to_grow.contains(&part.y) { + part.rows = new_rows; + last_flexible_index = idx; + } + current_height += part.rows; + current_y_position += part.rows + 1; // 1 for gap + } + } + + if current_height < max_height { + // we have some extra space left, let's add it to the last flexible part + let extra = max_height - current_height; + let mut last_part = split_parts.get_mut(last_flexible_index).unwrap(); + last_part.rows += extra; + for part in (&mut split_parts[last_flexible_index + 1..]).iter_mut() { + part.y += extra; + } } split_parts } @@ -66,24 +139,11 @@ fn split_space( layout: &Layout, ) -> Vec<(Layout, PositionAndSize)> { let mut pane_positions = Vec::new(); - let percentages: Vec = layout - .parts - .iter() - .map(|part| { - let split_size = part.split_size.as_ref(); - match split_size { - Some(SplitSize::Percent(percent)) => *percent, - None => { - // TODO: if there is no split size, it should get the remaining "free space" - panic!("Please enter the percentage of the screen part"); - } - } - }) - .collect(); + let sizes: Vec> = layout.parts.iter().map(|part| part.split_size).collect(); let split_parts = match layout.direction { - Direction::Vertical => split_space_to_parts_vertically(space_to_split, percentages), - Direction::Horizontal => split_space_to_parts_horizontally(space_to_split, percentages), + Direction::Vertical => split_space_to_parts_vertically(space_to_split, sizes), + Direction::Horizontal => split_space_to_parts_horizontally(space_to_split, sizes), }; for (i, part) in layout.parts.iter().enumerate() { let part_position_and_size = split_parts.get(i).unwrap(); @@ -97,43 +157,16 @@ fn split_space( pane_positions } -fn validate_layout_percentage_total(layout: &Layout) -> bool { - let total_percentages: u8 = layout - .parts - .iter() - .map(|part| { - let split_size = part.split_size.as_ref(); - match split_size { - Some(SplitSize::Percent(percent)) => *percent, - None => { - // TODO: if there is no split size, it should get the remaining "free space" - panic!("Please enter the percentage of the screen part"); - } - } - }) - .sum(); - if total_percentages != 100 { - return false; - } - - for part in layout.parts.iter() { - if !part.parts.is_empty() { - return validate_layout_percentage_total(part); - } - } - - true -} - #[derive(Debug, Serialize, Deserialize, Clone)] pub enum Direction { Horizontal, Vertical, } -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, Copy)] pub enum SplitSize { Percent(u8), // 1 to 100 + Fixed(u16), // An absolute number of columns or rows } #[derive(Debug, Serialize, Deserialize, Clone)] @@ -149,7 +182,13 @@ pub struct Layout { impl Layout { pub fn new(layout_path: PathBuf) -> Self { + let project_dirs = ProjectDirs::from("org", "Mosaic Contributors", "Mosaic").unwrap(); + let layout_dir = project_dirs.data_dir().join("layouts/"); + let root_layout_dir = Path::new(MOSAIC_ROOT_LAYOUT_DIR); let mut layout_file = File::open(&layout_path) + .or_else(|_| File::open(&layout_path.with_extension("yaml"))) + .or_else(|_| File::open(&layout_dir.join(&layout_path).with_extension("yaml"))) + .or_else(|_| File::open(root_layout_dir.join(&layout_path).with_extension("yaml"))) .unwrap_or_else(|_| panic!("cannot find layout {}", &layout_path.display())); let mut layout = String::new(); @@ -158,17 +197,9 @@ impl Layout { .unwrap_or_else(|_| panic!("could not read layout {}", &layout_path.display())); let layout: Layout = serde_yaml::from_str(&layout) .unwrap_or_else(|_| panic!("could not parse layout {}", &layout_path.display())); - layout.validate(); - layout } - pub fn validate(&self) { - if !validate_layout_percentage_total(&self) { - panic!("The total percent for each part should equal 100."); - } - } - pub fn total_terminal_panes(&self) -> usize { let mut total_panes = 0; total_panes += self.parts.len(); diff --git a/src/main.rs b/src/main.rs index 64bc2816..3757dfc4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,14 +17,16 @@ mod utils; mod wasm_vm; -use std::cell::RefCell; -use std::collections::HashMap; use std::io::Write; use std::os::unix::net::UnixStream; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::sync::mpsc::{channel, sync_channel, Receiver, SendError, Sender, SyncSender}; use std::thread; +use std::{cell::RefCell, sync::mpsc::TrySendError}; +use std::{collections::HashMap, fs}; +use directories_next::ProjectDirs; +use input::InputMode; use panes::PaneId; use serde::{Deserialize, Serialize}; use structopt::StructOpt; @@ -43,6 +45,7 @@ use crate::layout::Layout; use crate::os_input_output::{get_os_input, OsApi}; use crate::pty_bus::{PtyBus, PtyInstruction, VteEvent}; use crate::screen::{Screen, ScreenInstruction}; +use crate::utils::consts::MOSAIC_ROOT_PLUGIN_DIR; use crate::utils::{ consts::{MOSAIC_IPC_PIPE, MOSAIC_TMP_DIR, MOSAIC_TMP_LOG_DIR}, logging::*, @@ -86,6 +89,14 @@ impl SenderWithContext { } } + pub fn try_send(&self, event: T) -> Result<(), TrySendError<(T, ErrorContext)>> { + if let SenderType::SyncSender(ref s) = self.sender { + s.try_send((event, self.err_ctx)) + } else { + panic!("try_send can only be called on SyncSenders!") + } + } + pub fn update(&mut self, new_ctx: ErrorContext) { self.err_ctx = new_ctx; } @@ -126,14 +137,44 @@ pub fn main() { } } +// FIXME: It would be good to add some more things to this over time +#[derive(Debug, Clone)] +pub struct AppState { + pub input_mode: InputMode, +} + +impl Default for AppState { + fn default() -> Self { + Self { + input_mode: InputMode::Normal, + } + } +} + +// FIXME: Make this a method on the big `Communication` struct, so that app_tx can be extracted +// from self instead of being explicitly passed here +pub fn update_state( + app_tx: &SenderWithContext, + update_fn: impl FnOnce(AppState) -> AppState, +) { + let (state_tx, state_rx) = channel(); + + drop(app_tx.send(AppInstruction::GetState(state_tx))); + let state = state_rx.recv().unwrap(); + + drop(app_tx.send(AppInstruction::SetState(update_fn(state)))) +} + #[derive(Clone)] pub enum AppInstruction { + GetState(Sender), + SetState(AppState), Exit, Error(String), } pub fn start(mut os_input: Box, opts: CliArgs) { - let mut active_threads = vec![]; + let mut app_state = AppState::default(); let command_is_executing = CommandIsExecuting::new(); @@ -169,7 +210,12 @@ pub fn start(mut os_input: Box, opts: CliArgs) { os_input.clone(), opts.debug, ); - let maybe_layout = opts.layout.map(Layout::new); + // Don't use default layouts in tests, but do everywhere else + #[cfg(not(test))] + let default_layout = Some(PathBuf::from("default")); + #[cfg(test)] + let default_layout = None; + let maybe_layout = opts.layout.or(default_layout).map(Layout::new); #[cfg(not(test))] std::panic::set_hook({ @@ -180,313 +226,338 @@ pub fn start(mut os_input: Box, opts: CliArgs) { }) }); - active_threads.push( - thread::Builder::new() - .name("pty".to_string()) - .spawn({ - let mut command_is_executing = command_is_executing.clone(); - move || { - if let Some(layout) = maybe_layout { - pty_bus.spawn_terminals_for_layout(layout); - } else { - let pid = pty_bus.spawn_terminal(None); + let pty_thread = thread::Builder::new() + .name("pty".to_string()) + .spawn({ + let mut command_is_executing = command_is_executing.clone(); + send_pty_instructions.send(PtyInstruction::NewTab).unwrap(); + move || loop { + let (event, mut err_ctx) = pty_bus + .receive_pty_instructions + .recv() + .expect("failed to receive event on channel"); + err_ctx.add_call(ContextType::Pty(PtyContext::from(&event))); + pty_bus.send_screen_instructions.update(err_ctx); + match event { + PtyInstruction::SpawnTerminal(file_to_open) => { + let pid = pty_bus.spawn_terminal(file_to_open); pty_bus .send_screen_instructions - .send(ScreenInstruction::NewTab(pid)) + .send(ScreenInstruction::NewPane(PaneId::Terminal(pid))) .unwrap(); } + PtyInstruction::SpawnTerminalVertically(file_to_open) => { + let pid = pty_bus.spawn_terminal(file_to_open); + pty_bus + .send_screen_instructions + .send(ScreenInstruction::VerticalSplit(PaneId::Terminal(pid))) + .unwrap(); + } + PtyInstruction::SpawnTerminalHorizontally(file_to_open) => { + let pid = pty_bus.spawn_terminal(file_to_open); + pty_bus + .send_screen_instructions + .send(ScreenInstruction::HorizontalSplit(PaneId::Terminal(pid))) + .unwrap(); + } + PtyInstruction::NewTab => { + if let Some(layout) = maybe_layout.clone() { + pty_bus.spawn_terminals_for_layout(layout); + } else { + let pid = pty_bus.spawn_terminal(None); + pty_bus + .send_screen_instructions + .send(ScreenInstruction::NewTab(pid)) + .unwrap(); + } + } + PtyInstruction::ClosePane(id) => { + pty_bus.close_pane(id); + command_is_executing.done_closing_pane(); + } + PtyInstruction::CloseTab(ids) => { + pty_bus.close_tab(ids); + command_is_executing.done_closing_pane(); + } + PtyInstruction::Quit => { + break; + } + } + } + }) + .unwrap(); - loop { - let (event, mut err_ctx) = pty_bus - .receive_pty_instructions - .recv() - .expect("failed to receive event on channel"); - err_ctx.add_call(ContextType::Pty(PtyContext::from(&event))); - pty_bus.send_screen_instructions.update(err_ctx); - match event { - PtyInstruction::SpawnTerminal(file_to_open) => { - let pid = pty_bus.spawn_terminal(file_to_open); - pty_bus - .send_screen_instructions - .send(ScreenInstruction::NewPane(PaneId::Terminal(pid))) - .unwrap(); - } - PtyInstruction::SpawnTerminalVertically(file_to_open) => { - let pid = pty_bus.spawn_terminal(file_to_open); - pty_bus - .send_screen_instructions - .send(ScreenInstruction::VerticalSplit(PaneId::Terminal(pid))) - .unwrap(); - } - PtyInstruction::SpawnTerminalHorizontally(file_to_open) => { - let pid = pty_bus.spawn_terminal(file_to_open); - pty_bus - .send_screen_instructions - .send(ScreenInstruction::HorizontalSplit(PaneId::Terminal(pid))) - .unwrap(); - } - PtyInstruction::NewTab => { - let pid = pty_bus.spawn_terminal(None); - pty_bus - .send_screen_instructions - .send(ScreenInstruction::NewTab(pid)) - .unwrap(); - } - PtyInstruction::ClosePane(id) => { - pty_bus.close_pane(id); - command_is_executing.done_closing_pane(); - } - PtyInstruction::CloseTab(ids) => { - pty_bus.close_tab(ids); - command_is_executing.done_closing_pane(); - } - PtyInstruction::Quit => { - break; - } + let screen_thread = thread::Builder::new() + .name("screen".to_string()) + .spawn({ + let mut command_is_executing = command_is_executing.clone(); + let os_input = os_input.clone(); + let send_pty_instructions = send_pty_instructions.clone(); + let send_plugin_instructions = send_plugin_instructions.clone(); + let send_app_instructions = send_app_instructions.clone(); + let max_panes = opts.max_panes; + + move || { + let mut screen = Screen::new( + receive_screen_instructions, + send_pty_instructions, + send_plugin_instructions, + send_app_instructions, + &full_screen_ws, + os_input, + max_panes, + ); + loop { + let (event, mut err_ctx) = screen + .receiver + .recv() + .expect("failed to receive event on channel"); + err_ctx.add_call(ContextType::Screen(ScreenContext::from(&event))); + screen.send_app_instructions.update(err_ctx); + screen.send_pty_instructions.update(err_ctx); + match event { + ScreenInstruction::Pty(pid, vte_event) => { + screen + .get_active_tab_mut() + .unwrap() + .handle_pty_event(pid, vte_event); + } + ScreenInstruction::Render => { + screen.render(); + } + ScreenInstruction::NewPane(pid) => { + screen.get_active_tab_mut().unwrap().new_pane(pid); + command_is_executing.done_opening_new_pane(); + } + ScreenInstruction::HorizontalSplit(pid) => { + screen.get_active_tab_mut().unwrap().horizontal_split(pid); + command_is_executing.done_opening_new_pane(); + } + ScreenInstruction::VerticalSplit(pid) => { + screen.get_active_tab_mut().unwrap().vertical_split(pid); + command_is_executing.done_opening_new_pane(); + } + ScreenInstruction::WriteCharacter(bytes) => { + screen + .get_active_tab_mut() + .unwrap() + .write_to_active_terminal(bytes); + } + ScreenInstruction::ResizeLeft => { + screen.get_active_tab_mut().unwrap().resize_left(); + } + ScreenInstruction::ResizeRight => { + screen.get_active_tab_mut().unwrap().resize_right(); + } + ScreenInstruction::ResizeDown => { + screen.get_active_tab_mut().unwrap().resize_down(); + } + ScreenInstruction::ResizeUp => { + screen.get_active_tab_mut().unwrap().resize_up(); + } + ScreenInstruction::MoveFocus => { + screen.get_active_tab_mut().unwrap().move_focus(); + } + ScreenInstruction::MoveFocusLeft => { + screen.get_active_tab_mut().unwrap().move_focus_left(); + } + ScreenInstruction::MoveFocusDown => { + screen.get_active_tab_mut().unwrap().move_focus_down(); + } + ScreenInstruction::MoveFocusRight => { + screen.get_active_tab_mut().unwrap().move_focus_right(); + } + ScreenInstruction::MoveFocusUp => { + screen.get_active_tab_mut().unwrap().move_focus_up(); + } + ScreenInstruction::ScrollUp => { + screen + .get_active_tab_mut() + .unwrap() + .scroll_active_terminal_up(); + } + ScreenInstruction::ScrollDown => { + screen + .get_active_tab_mut() + .unwrap() + .scroll_active_terminal_down(); + } + ScreenInstruction::ClearScroll => { + screen + .get_active_tab_mut() + .unwrap() + .clear_active_terminal_scroll(); + } + ScreenInstruction::CloseFocusedPane => { + screen.get_active_tab_mut().unwrap().close_focused_pane(); + screen.render(); + } + ScreenInstruction::SetSelectable(id, selectable) => { + screen + .get_active_tab_mut() + .unwrap() + .set_pane_selectable(id, selectable); + // FIXME: Is this needed? + screen.render(); + } + ScreenInstruction::ClosePane(id) => { + screen.get_active_tab_mut().unwrap().close_pane(id); + screen.render(); + } + ScreenInstruction::ToggleActiveTerminalFullscreen => { + screen + .get_active_tab_mut() + .unwrap() + .toggle_active_pane_fullscreen(); + } + ScreenInstruction::NewTab(pane_id) => { + screen.new_tab(pane_id); + command_is_executing.done_opening_new_pane(); + } + ScreenInstruction::SwitchTabNext => screen.switch_tab_next(), + ScreenInstruction::SwitchTabPrev => screen.switch_tab_prev(), + ScreenInstruction::CloseTab => screen.close_tab(), + ScreenInstruction::ApplyLayout((layout, new_pane_pids)) => { + screen.apply_layout(layout, new_pane_pids); + command_is_executing.done_opening_new_pane(); + } + ScreenInstruction::Quit => { + break; } } } - }) - .unwrap(), - ); + } + }) + .unwrap(); - active_threads.push( - thread::Builder::new() - .name("screen".to_string()) - .spawn({ - let mut command_is_executing = command_is_executing.clone(); - let os_input = os_input.clone(); - let send_pty_instructions = send_pty_instructions.clone(); - let send_plugin_instructions = send_plugin_instructions.clone(); - let send_app_instructions = send_app_instructions.clone(); - let max_panes = opts.max_panes; + let wasm_thread = thread::Builder::new() + .name("wasm".to_string()) + .spawn({ + let mut send_pty_instructions = send_pty_instructions.clone(); + let mut send_screen_instructions = send_screen_instructions.clone(); + let mut send_app_instructions = send_app_instructions.clone(); - move || { - let mut screen = Screen::new( - receive_screen_instructions, - send_pty_instructions, - send_plugin_instructions, - send_app_instructions, - &full_screen_ws, - os_input, - max_panes, - ); - loop { - let (event, mut err_ctx) = screen - .receiver - .recv() - .expect("failed to receive event on channel"); - err_ctx.add_call(ContextType::Screen(ScreenContext::from(&event))); - screen.send_app_instructions.update(err_ctx); - screen.send_pty_instructions.update(err_ctx); - match event { - ScreenInstruction::Pty(pid, vte_event) => { - screen - .get_active_tab_mut() - .unwrap() - .handle_pty_event(pid, vte_event); - } - ScreenInstruction::Render => { - screen.render(); - } - ScreenInstruction::NewPane(pid) => { - screen.get_active_tab_mut().unwrap().new_pane(pid); - command_is_executing.done_opening_new_pane(); - } - ScreenInstruction::HorizontalSplit(pid) => { - screen.get_active_tab_mut().unwrap().horizontal_split(pid); - command_is_executing.done_opening_new_pane(); - } - ScreenInstruction::VerticalSplit(pid) => { - screen.get_active_tab_mut().unwrap().vertical_split(pid); - command_is_executing.done_opening_new_pane(); - } - ScreenInstruction::WriteCharacter(bytes) => { - screen - .get_active_tab_mut() - .unwrap() - .write_to_active_terminal(bytes); - } - ScreenInstruction::ResizeLeft => { - screen.get_active_tab_mut().unwrap().resize_left(); - } - ScreenInstruction::ResizeRight => { - screen.get_active_tab_mut().unwrap().resize_right(); - } - ScreenInstruction::ResizeDown => { - screen.get_active_tab_mut().unwrap().resize_down(); - } - ScreenInstruction::ResizeUp => { - screen.get_active_tab_mut().unwrap().resize_up(); - } - ScreenInstruction::MoveFocus => { - screen.get_active_tab_mut().unwrap().move_focus(); - } - ScreenInstruction::MoveFocusLeft => { - screen.get_active_tab_mut().unwrap().move_focus_left(); - } - ScreenInstruction::MoveFocusDown => { - screen.get_active_tab_mut().unwrap().move_focus_down(); - } - ScreenInstruction::MoveFocusRight => { - screen.get_active_tab_mut().unwrap().move_focus_right(); - } - ScreenInstruction::MoveFocusUp => { - screen.get_active_tab_mut().unwrap().move_focus_up(); - } - ScreenInstruction::ScrollUp => { - screen - .get_active_tab_mut() - .unwrap() - .scroll_active_terminal_up(); - } - ScreenInstruction::ScrollDown => { - screen - .get_active_tab_mut() - .unwrap() - .scroll_active_terminal_down(); - } - ScreenInstruction::ClearScroll => { - screen - .get_active_tab_mut() - .unwrap() - .clear_active_terminal_scroll(); - } - ScreenInstruction::CloseFocusedPane => { - screen.get_active_tab_mut().unwrap().close_focused_pane(); - screen.render(); - } - ScreenInstruction::ClosePane(id) => { - screen.get_active_tab_mut().unwrap().close_pane(id); - screen.render(); - } - ScreenInstruction::ToggleActiveTerminalFullscreen => { - screen - .get_active_tab_mut() - .unwrap() - .toggle_active_pane_fullscreen(); - } - ScreenInstruction::NewTab(pane_id) => { - screen.new_tab(pane_id); - command_is_executing.done_opening_new_pane(); - } - ScreenInstruction::SwitchTabNext => screen.switch_tab_next(), - ScreenInstruction::SwitchTabPrev => screen.switch_tab_prev(), - ScreenInstruction::CloseTab => screen.close_tab(), - ScreenInstruction::ApplyLayout((layout, new_pane_pids)) => { - screen.apply_layout(layout, new_pane_pids) - } - ScreenInstruction::Quit => { - break; + let store = Store::default(); + let mut plugin_id = 0; + let mut plugin_map = HashMap::new(); + + move || loop { + let (event, mut err_ctx) = receive_plugin_instructions + .recv() + .expect("failed to receive event on channel"); + err_ctx.add_call(ContextType::Plugin(PluginContext::from(&event))); + send_screen_instructions.update(err_ctx); + send_pty_instructions.update(err_ctx); + send_app_instructions.update(err_ctx); + match event { + PluginInstruction::Load(pid_tx, path) => { + let project_dirs = + ProjectDirs::from("org", "Mosaic Contributors", "Mosaic").unwrap(); + let plugin_dir = project_dirs.data_dir().join("plugins/"); + let root_plugin_dir = Path::new(MOSAIC_ROOT_PLUGIN_DIR); + let wasm_bytes = fs::read(&path) + .or_else(|_| fs::read(&path.with_extension("wasm"))) + .or_else(|_| fs::read(&plugin_dir.join(&path).with_extension("wasm"))) + .or_else(|_| { + fs::read(&root_plugin_dir.join(&path).with_extension("wasm")) + }) + .unwrap_or_else(|_| panic!("cannot find plugin {}", &path.display())); + + // FIXME: Cache this compiled module on disk. I could use `(de)serialize_to_file()` for that + let module = Module::new(&store, &wasm_bytes).unwrap(); + + let output = Pipe::new(); + let input = Pipe::new(); + let mut wasi_env = WasiState::new("mosaic") + .env("CLICOLOR_FORCE", "1") + .preopen(|p| { + p.directory(".") // FIXME: Change this to a more meaningful dir + .alias(".") + .read(true) + .write(true) + .create(true) + }) + .unwrap() + .stdin(Box::new(input)) + .stdout(Box::new(output)) + .finalize() + .unwrap(); + + let wasi = wasi_env.import_object(&module).unwrap(); + + let plugin_env = PluginEnv { + plugin_id, + send_pty_instructions: send_pty_instructions.clone(), + send_screen_instructions: send_screen_instructions.clone(), + send_app_instructions: send_app_instructions.clone(), + wasi_env, + }; + + let mosaic = mosaic_imports(&store, &plugin_env); + let instance = Instance::new(&module, &mosaic.chain_back(wasi)).unwrap(); + + let start = instance.exports.get_function("_start").unwrap(); + + // This eventually calls the `.init()` method + start.call(&[]).unwrap(); + + plugin_map.insert(plugin_id, (instance, plugin_env)); + pid_tx.send(plugin_id).unwrap(); + plugin_id += 1; + } + PluginInstruction::Draw(buf_tx, pid, rows, cols) => { + let (instance, plugin_env) = plugin_map.get(&pid).unwrap(); + + let draw = instance.exports.get_function("draw").unwrap(); + + draw.call(&[Value::I32(rows as i32), Value::I32(cols as i32)]) + .unwrap(); + + buf_tx.send(wasi_stdout(&plugin_env.wasi_env)).unwrap(); + } + // FIXME: Deduplicate this with the callback below! + PluginInstruction::Input(pid, input_bytes) => { + let (instance, plugin_env) = plugin_map.get(&pid).unwrap(); + + let handle_key = instance.exports.get_function("handle_key").unwrap(); + for key in input_bytes.keys() { + if let Ok(key) = key { + wasi_write_string( + &plugin_env.wasi_env, + &serde_json::to_string(&key).unwrap(), + ); + handle_key.call(&[]).unwrap(); } } + + drop(send_screen_instructions.send(ScreenInstruction::Render)); } - } - }) - .unwrap(), - ); - - active_threads.push( - thread::Builder::new() - .name("wasm".to_string()) - .spawn({ - let mut send_pty_instructions = send_pty_instructions.clone(); - let mut send_screen_instructions = send_screen_instructions.clone(); - - move || { - let store = Store::default(); - - let mut plugin_id = 0; - let mut plugin_map = HashMap::new(); - - loop { - let (event, mut err_ctx) = receive_plugin_instructions - .recv() - .expect("failed to receive event on channel"); - err_ctx.add_call(ContextType::Plugin(PluginContext::from(&event))); - send_screen_instructions.update(err_ctx); - send_pty_instructions.update(err_ctx); - match event { - PluginInstruction::Load(pid_tx, path) => { - // FIXME: Cache this compiled module on disk. I could use `(de)serialize_to_file()` for that - let module = Module::from_file(&store, &path).unwrap(); - - let output = Pipe::new(); - let input = Pipe::new(); - let mut wasi_env = WasiState::new("mosaic") - .env("CLICOLOR_FORCE", "1") - .preopen(|p| { - p.directory(".") // FIXME: Change this to a more meaningful dir - .alias(".") - .read(true) - .write(true) - .create(true) - }) - .unwrap() - .stdin(Box::new(input)) - .stdout(Box::new(output)) - .finalize() - .unwrap(); - - let wasi = wasi_env.import_object(&module).unwrap(); - - let plugin_env = PluginEnv { - send_pty_instructions: send_pty_instructions.clone(), - wasi_env, - }; - - let mosaic = mosaic_imports(&store, &plugin_env); - let instance = - Instance::new(&module, &mosaic.chain_back(wasi)).unwrap(); - - let start = instance.exports.get_function("_start").unwrap(); - - // This eventually calls the `.init()` method - start.call(&[]).unwrap(); - - plugin_map.insert(plugin_id, (instance, plugin_env)); - pid_tx.send(plugin_id).unwrap(); - plugin_id += 1; - } - PluginInstruction::Draw(buf_tx, pid, rows, cols) => { - let (instance, plugin_env) = plugin_map.get(&pid).unwrap(); - - let draw = instance.exports.get_function("draw").unwrap(); - - draw.call(&[Value::I32(rows as i32), Value::I32(cols as i32)]) - .unwrap(); - - buf_tx.send(wasi_stdout(&plugin_env.wasi_env)).unwrap(); - } - PluginInstruction::Input(pid, input_bytes) => { - let (instance, plugin_env) = plugin_map.get(&pid).unwrap(); - - let handle_key = - instance.exports.get_function("handle_key").unwrap(); - for key in input_bytes.keys() { - if let Ok(key) = key { - wasi_write_string( - &plugin_env.wasi_env, - &serde_json::to_string(&key).unwrap(), - ); - handle_key.call(&[]).unwrap(); - } + PluginInstruction::GlobalInput(input_bytes) => { + // FIXME: Set up an event subscription system, and timed callbacks + for (instance, plugin_env) in plugin_map.values() { + let handler = + instance.exports.get_function("handle_global_key").unwrap(); + for key in input_bytes.keys() { + if let Ok(key) = key { + wasi_write_string( + &plugin_env.wasi_env, + &serde_json::to_string(&key).unwrap(), + ); + handler.call(&[]).unwrap(); } - - send_screen_instructions - .send(ScreenInstruction::Render) - .unwrap(); } - PluginInstruction::Unload(pid) => drop(plugin_map.remove(&pid)), - PluginInstruction::Quit => break, } - } - } - }) - .unwrap(), - ); - // TODO: currently we don't push this into active_threads + drop(send_screen_instructions.send(ScreenInstruction::Render)); + } + PluginInstruction::Unload(pid) => drop(plugin_map.remove(&pid)), + PluginInstruction::Quit => break, + } + } + }) + .unwrap(); + + // TODO: currently we don't wait for this to quit // because otherwise the app will hang. Need to fix this so it both // listens to the ipc-bus and is able to quit cleanly #[cfg(not(test))] @@ -552,6 +623,7 @@ pub fn start(mut os_input: Box, opts: CliArgs) { .spawn({ let send_screen_instructions = send_screen_instructions.clone(); let send_pty_instructions = send_pty_instructions.clone(); + let send_plugin_instructions = send_plugin_instructions.clone(); let os_input = os_input.clone(); move || { input_loop( @@ -559,6 +631,7 @@ pub fn start(mut os_input: Box, opts: CliArgs) { command_is_executing, send_screen_instructions, send_pty_instructions, + send_plugin_instructions, send_app_instructions, ) } @@ -574,18 +647,21 @@ pub fn start(mut os_input: Box, opts: CliArgs) { send_screen_instructions.update(err_ctx); send_pty_instructions.update(err_ctx); match app_instruction { + AppInstruction::GetState(state_tx) => drop(state_tx.send(app_state.clone())), + AppInstruction::SetState(state) => app_state = state, AppInstruction::Exit => { let _ = send_screen_instructions.send(ScreenInstruction::Quit); let _ = send_pty_instructions.send(PtyInstruction::Quit); - let _ = send_plugin_instructions.send(PluginInstruction::Quit); break; } AppInstruction::Error(backtrace) => { let _ = send_screen_instructions.send(ScreenInstruction::Quit); + let _ = screen_thread.join(); let _ = send_pty_instructions.send(PtyInstruction::Quit); - + let _ = pty_thread.join(); let _ = send_plugin_instructions.send(PluginInstruction::Quit); + let _ = wasm_thread.join(); os_input.unset_raw_mode(0); let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1); let error = format!("{}\n{}", goto_start_of_last_line, backtrace); @@ -593,17 +669,18 @@ pub fn start(mut os_input: Box, opts: CliArgs) { .get_stdout_writer() .write(error.as_bytes()) .unwrap(); - for thread_handler in active_threads { - let _ = thread_handler.join(); - } std::process::exit(1); } } } - for thread_handler in active_threads { - thread_handler.join().unwrap(); - } + let _ = send_screen_instructions.send(ScreenInstruction::Quit); + screen_thread.join().unwrap(); + let _ = send_pty_instructions.send(PtyInstruction::Quit); + pty_thread.join().unwrap(); + let _ = send_plugin_instructions.send(PluginInstruction::Quit); + wasm_thread.join().unwrap(); + // cleanup(); let reset_style = "\u{1b}[m"; let show_cursor = "\u{1b}[?25h"; diff --git a/src/panes/grid.rs b/src/panes/grid.rs new file mode 100644 index 00000000..90e471e5 --- /dev/null +++ b/src/panes/grid.rs @@ -0,0 +1,820 @@ +use std::{ + cmp::Ordering, + fmt::{self, Debug, Formatter}, +}; + +use crate::panes::terminal_character::{ + CharacterStyles, TerminalCharacter, EMPTY_TERMINAL_CHARACTER, +}; + +fn get_top_non_canonical_rows(rows: &mut Vec) -> Vec { + let mut index_of_last_non_canonical_row = None; + for (i, row) in rows.iter().enumerate() { + if row.is_canonical { + break; + } else { + index_of_last_non_canonical_row = Some(i); + } + } + match index_of_last_non_canonical_row { + Some(index_of_last_non_canonical_row) => { + rows.drain(..=index_of_last_non_canonical_row).collect() + } + None => vec![], + } +} + +fn get_bottom_canonical_row_and_wraps(rows: &mut Vec) -> Vec { + let mut index_of_last_non_canonical_row = None; + for (i, row) in rows.iter().enumerate().rev() { + if row.is_canonical { + index_of_last_non_canonical_row = Some(i); + break; + } else { + index_of_last_non_canonical_row = Some(i); + } + } + match index_of_last_non_canonical_row { + Some(index_of_last_non_canonical_row) => { + rows.drain(index_of_last_non_canonical_row..).collect() + } + None => vec![], + } +} + +fn transfer_rows_down( + source: &mut Vec, + destination: &mut Vec, + count: usize, + max_src_width: Option, + max_dst_width: Option, +) { + let mut next_lines: Vec = vec![]; + let mut lines_added_to_destination: isize = 0; + loop { + if lines_added_to_destination as usize == count { + break; + } + if next_lines.is_empty() { + match source.pop() { + Some(next_line) => { + let mut top_non_canonical_rows_in_dst = get_top_non_canonical_rows(destination); + lines_added_to_destination -= top_non_canonical_rows_in_dst.len() as isize; + next_lines.push(next_line); + next_lines.append(&mut top_non_canonical_rows_in_dst); + next_lines = match max_dst_width { + Some(max_row_width) => { + Row::from_rows(next_lines).split_to_rows_of_length(max_row_width) + } + None => vec![Row::from_rows(next_lines)], + }; + } + None => break, // no more rows + } + } + destination.insert(0, next_lines.pop().unwrap()); + lines_added_to_destination += 1; + } + if !next_lines.is_empty() { + match max_src_width { + Some(max_row_width) => { + let mut excess_rows = + Row::from_rows(next_lines).split_to_rows_of_length(max_row_width); + source.append(&mut excess_rows); + } + None => { + let excess_row = Row::from_rows(next_lines); + source.push(excess_row); + } + } + } +} + +fn transfer_rows_up( + source: &mut Vec, + destination: &mut Vec, + count: usize, + max_src_width: Option, + max_dst_width: Option, +) { + let mut next_lines: Vec = vec![]; + for _ in 0..count { + if next_lines.is_empty() { + if !source.is_empty() { + let next_line = source.remove(0); + if !next_line.is_canonical { + let mut bottom_canonical_row_and_wraps_in_dst = + get_bottom_canonical_row_and_wraps(destination); + next_lines.append(&mut bottom_canonical_row_and_wraps_in_dst); + } + next_lines.push(next_line); + next_lines = match max_dst_width { + Some(max_row_width) => { + Row::from_rows(next_lines).split_to_rows_of_length(max_row_width) + } + None => vec![Row::from_rows(next_lines)], + }; + } else { + break; // no more rows + } + } + destination.push(next_lines.remove(0)); + } + if !next_lines.is_empty() { + match max_src_width { + Some(max_row_width) => { + let excess_rows = Row::from_rows(next_lines).split_to_rows_of_length(max_row_width); + for row in excess_rows { + source.insert(0, row); + } + } + None => { + let excess_row = Row::from_rows(next_lines); + source.insert(0, excess_row); + } + } + } +} + +#[derive(Clone)] +pub struct Grid { + lines_above: Vec, + viewport: Vec, + lines_below: Vec, + cursor: Cursor, + scroll_region: Option<(usize, usize)>, + width: usize, + height: usize, +} + +impl Debug for Grid { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + for (i, row) in self.viewport.iter().enumerate() { + if row.is_canonical { + writeln!(f, "{:?} (C): {:?}", i, row)?; + } else { + writeln!(f, "{:?} (W): {:?}", i, row)?; + } + } + Ok(()) + } +} + +impl Grid { + pub fn new(rows: usize, columns: usize) -> Self { + Grid { + lines_above: vec![], + viewport: vec![], + lines_below: vec![], + cursor: Cursor::new(0, 0), + scroll_region: None, + width: columns, + height: rows, + } + } + fn cursor_canonical_line_index(&self) -> usize { + let mut cursor_canonical_line_index = 0; + let mut canonical_lines_traversed = 0; + for (i, line) in self.viewport.iter().enumerate() { + if line.is_canonical { + cursor_canonical_line_index = canonical_lines_traversed; + canonical_lines_traversed += 1; + } + if i == self.cursor.y { + break; + } + } + cursor_canonical_line_index + } + // TODO: merge these two funtions + fn cursor_index_in_canonical_line(&self) -> usize { + let mut cursor_canonical_line_index = 0; + let mut cursor_index_in_canonical_line = 0; + for (i, line) in self.viewport.iter().enumerate() { + if line.is_canonical { + cursor_canonical_line_index = i; + } + if i == self.cursor.y { + let line_wrap_position_in_line = self.cursor.y - cursor_canonical_line_index; + cursor_index_in_canonical_line = line_wrap_position_in_line + self.cursor.x; + break; + } + } + cursor_index_in_canonical_line + } + fn canonical_line_y_coordinates(&self, canonical_line_index: usize) -> usize { + let mut canonical_lines_traversed = 0; + let mut y_coordinates = 0; + for (i, line) in self.viewport.iter().enumerate() { + if line.is_canonical { + canonical_lines_traversed += 1; + if canonical_lines_traversed == canonical_line_index + 1 { + y_coordinates = i; + break; + } + } + } + y_coordinates + } + pub fn scroll_up_one_line(&mut self) { + if !self.lines_above.is_empty() && self.viewport.len() == self.height { + let line_to_push_down = self.viewport.pop().unwrap(); + self.lines_below.insert(0, line_to_push_down); + let line_to_insert_at_viewport_top = self.lines_above.pop().unwrap(); + self.viewport.insert(0, line_to_insert_at_viewport_top); + } + } + pub fn scroll_down_one_line(&mut self) { + if !self.lines_below.is_empty() && self.viewport.len() == self.height { + let mut line_to_push_up = self.viewport.remove(0); + if line_to_push_up.is_canonical { + self.lines_above.push(line_to_push_up); + } else { + let mut last_line_above = self.lines_above.pop().unwrap(); + last_line_above.append(&mut line_to_push_up.columns); + self.lines_above.push(last_line_above); + } + let line_to_insert_at_viewport_bottom = self.lines_below.remove(0); + self.viewport.push(line_to_insert_at_viewport_bottom); + } + } + pub fn change_size(&mut self, new_rows: usize, new_columns: usize) { + if new_columns != self.width { + let mut cursor_canonical_line_index = self.cursor_canonical_line_index(); + let cursor_index_in_canonical_line = self.cursor_index_in_canonical_line(); + let mut viewport_canonical_lines = vec![]; + for mut row in self.viewport.drain(..) { + if !row.is_canonical + && viewport_canonical_lines.is_empty() + && !self.lines_above.is_empty() + { + let mut first_line_above = self.lines_above.pop().unwrap(); + first_line_above.append(&mut row.columns); + viewport_canonical_lines.push(first_line_above); + cursor_canonical_line_index += 1; + } else if row.is_canonical { + viewport_canonical_lines.push(row); + } else { + match viewport_canonical_lines.last_mut() { + Some(last_line) => { + last_line.append(&mut row.columns); + } + None => { + // the state is corrupted somehow + // this is a bug and I'm not yet sure why it happens + // usually it fixes itself and is a result of some race + // TODO: investigate why this happens and solve it + return; + } + } + } + } + let mut new_viewport_rows = vec![]; + for mut canonical_line in viewport_canonical_lines { + let mut canonical_line_parts: Vec = vec![]; + if canonical_line.columns.is_empty() { + canonical_line_parts.push(Row::new().canonical()); + } + while !canonical_line.columns.is_empty() { + let next_wrap = if canonical_line.len() > new_columns { + canonical_line.columns.drain(..new_columns) + } else { + canonical_line.columns.drain(..) + }; + let row = Row::from_columns(next_wrap.collect()); + // if there are no more parts, this row is canonical as long as it originally + // was canonical (it might not have been for example if it's the first row in + // the viewport, and the actual canonical row is above it in the scrollback) + let row = if canonical_line_parts.is_empty() && canonical_line.is_canonical { + row.canonical() + } else { + row + }; + canonical_line_parts.push(row); + } + new_viewport_rows.append(&mut canonical_line_parts); + } + self.viewport = new_viewport_rows; + + let mut new_cursor_y = self.canonical_line_y_coordinates(cursor_canonical_line_index); + let new_cursor_x = (cursor_index_in_canonical_line / new_columns) + + (cursor_index_in_canonical_line % new_columns); + let current_viewport_row_count = self.viewport.len(); + match current_viewport_row_count.cmp(&self.height) { + Ordering::Less => { + let row_count_to_transfer = self.height - current_viewport_row_count; + transfer_rows_down( + &mut self.lines_above, + &mut self.viewport, + row_count_to_transfer, + None, + Some(new_columns), + ); + let rows_pulled = self.viewport.len() - current_viewport_row_count; + new_cursor_y += rows_pulled; + } + Ordering::Greater => { + let row_count_to_transfer = current_viewport_row_count - self.height; + if row_count_to_transfer > new_cursor_y { + new_cursor_y = 0; + } else { + new_cursor_y -= row_count_to_transfer; + } + transfer_rows_up( + &mut self.viewport, + &mut self.lines_above, + row_count_to_transfer, + Some(new_columns), + None, + ); + } + Ordering::Equal => {} + } + self.cursor.y = new_cursor_y; + self.cursor.x = new_cursor_x; + } + if new_rows != self.height { + let current_viewport_row_count = self.viewport.len(); + match current_viewport_row_count.cmp(&new_rows) { + Ordering::Less => { + let row_count_to_transfer = new_rows - current_viewport_row_count; + transfer_rows_down( + &mut self.lines_above, + &mut self.viewport, + row_count_to_transfer, + None, + Some(new_columns), + ); + let rows_pulled = self.viewport.len() - current_viewport_row_count; + self.cursor.y += rows_pulled; + } + Ordering::Greater => { + let row_count_to_transfer = current_viewport_row_count - new_rows; + if row_count_to_transfer > self.cursor.y { + self.cursor.y = 0; + } else { + self.cursor.y -= row_count_to_transfer; + } + transfer_rows_up( + &mut self.viewport, + &mut self.lines_above, + row_count_to_transfer, + Some(new_columns), + None, + ); + } + Ordering::Equal => {} + } + } + self.height = new_rows; + self.width = new_columns; + if self.scroll_region.is_some() { + self.set_scroll_region_to_viewport_size(); + } + } + pub fn as_character_lines(&self) -> Vec> { + let mut lines: Vec> = self + .viewport + .iter() + .map(|r| { + let mut line: Vec = r.columns.iter().copied().collect(); + // pad line + line.resize(self.width, EMPTY_TERMINAL_CHARACTER); + line + }) + .collect(); + let empty_row = vec![EMPTY_TERMINAL_CHARACTER; self.width]; + for _ in lines.len()..self.height { + lines.push(empty_row.clone()); + } + lines + } + pub fn cursor_coordinates(&self) -> Option<(usize, usize)> { + if self.cursor.is_hidden { + None + } else { + Some((self.cursor.x, self.cursor.y)) + } + } + pub fn move_viewport_up(&mut self, count: usize) { + for _ in 0..count { + self.scroll_up_one_line(); + } + } + pub fn move_viewport_down(&mut self, count: usize) { + for _ in 0..count { + self.scroll_down_one_line(); + } + } + pub fn reset_viewport(&mut self) { + let row_count_below = self.lines_below.len(); + for _ in 0..row_count_below { + self.scroll_down_one_line(); + } + } + pub fn rotate_scroll_region_up(&mut self, _count: usize) { + // TBD + } + pub fn rotate_scroll_region_down(&mut self, _count: usize) { + // TBD + } + pub fn add_canonical_line(&mut self) { + if let Some((scroll_region_top, scroll_region_bottom)) = self.scroll_region { + if self.cursor.y == scroll_region_bottom { + // end of scroll region + // when we have a scroll region set and we're at its bottom + // we need to delete its first line, thus shifting all lines in it upwards + // then we add an empty line at its end which will be filled by the application + // controlling the scroll region (presumably filled by whatever comes next in the + // scroll buffer, but that's not something we control) + self.viewport.remove(scroll_region_top); + self.viewport + .insert(scroll_region_bottom, Row::new().canonical()); + return; + } + } + if self.viewport.len() <= self.cursor.y + 1 { + let new_row = Row::new().canonical(); + self.viewport.push(new_row); + } + if self.cursor.y == self.height - 1 { + let row_count_to_transfer = 1; + transfer_rows_up( + &mut self.viewport, + &mut self.lines_above, + row_count_to_transfer, + Some(self.width), + None, + ); + } else { + self.cursor.y += 1; + } + } + pub fn move_cursor_to_beginning_of_line(&mut self) { + self.cursor.x = 0; + } + pub fn move_cursor_backwards(&mut self, count: usize) { + if self.cursor.x > count { + self.cursor.x -= count; + } else { + self.cursor.x = 0; + } + } + pub fn insert_character_at_cursor_position(&mut self, terminal_character: TerminalCharacter) { + match self.viewport.get_mut(self.cursor.y) { + Some(row) => row.add_character_at(terminal_character, self.cursor.x), + None => { + // pad lines until cursor if they do not exist + for _ in self.viewport.len()..self.cursor.y { + self.viewport.push(Row::new().canonical()); + } + self.viewport + .push(Row::new().with_character(terminal_character).canonical()); + } + } + } + pub fn add_character(&mut self, terminal_character: TerminalCharacter) { + // TODO: try to separate adding characters from moving the cursors in this function + if self.cursor.x < self.width { + self.insert_character_at_cursor_position(terminal_character); + } else { + // line wrap + self.cursor.x = 0; + if self.cursor.y == self.height - 1 { + let row_count_to_transfer = 1; + transfer_rows_up( + &mut self.viewport, + &mut self.lines_above, + row_count_to_transfer, + Some(self.width), + None, + ); + let wrapped_row = Row::new(); + self.viewport.push(wrapped_row); + } else { + self.cursor.y += 1; + if self.viewport.len() <= self.cursor.y { + let line_wrapped_row = Row::new(); + self.viewport.push(line_wrapped_row); + } + } + self.insert_character_at_cursor_position(terminal_character); + } + self.move_cursor_forward_until_edge(1); + } + pub fn move_cursor_forward_until_edge(&mut self, count: usize) { + let count_to_move = std::cmp::min(count, self.width - (self.cursor.x)); + self.cursor.x += count_to_move; + } + pub fn replace_characters_in_line_after_cursor(&mut self, replace_with: TerminalCharacter) { + self.viewport + .get_mut(self.cursor.y) + .unwrap() + .truncate(self.cursor.x); + if self.cursor.x < self.width - 1 { + let mut characters_to_append = vec![replace_with; self.width - self.cursor.x]; + self.viewport + .get_mut(self.cursor.y) + .unwrap() + .append(&mut characters_to_append); + } + } + pub fn replace_characters_in_line_before_cursor(&mut self, replace_with: TerminalCharacter) { + let line_part = vec![replace_with; self.cursor.x]; + let row = self.viewport.get_mut(self.cursor.y).unwrap(); + row.replace_beginning_with(line_part); + } + pub fn clear_all_after_cursor(&mut self, replace_with: TerminalCharacter) { + let cursor_row = self.viewport.get_mut(self.cursor.y).unwrap(); + cursor_row.truncate(self.cursor.x); + let mut replace_with_columns_in_cursor_row = vec![replace_with; self.width - self.cursor.x]; + cursor_row.append(&mut replace_with_columns_in_cursor_row); + + let replace_with_columns = vec![replace_with; self.width]; + self.replace_characters_in_line_after_cursor(replace_with); + for row in self.viewport.iter_mut().skip(self.cursor.y + 1) { + row.replace_columns(replace_with_columns.clone()); + } + } + pub fn clear_cursor_line(&mut self) { + self.viewport.get_mut(self.cursor.y).unwrap().truncate(0); + } + pub fn clear_all(&mut self, replace_with: TerminalCharacter) { + let replace_with_columns = vec![replace_with; self.width]; + self.replace_characters_in_line_after_cursor(replace_with); + for row in self.viewport.iter_mut() { + row.replace_columns(replace_with_columns.clone()); + } + } + fn pad_current_line_until(&mut self, position: usize) { + let current_row = self.viewport.get_mut(self.cursor.y).unwrap(); + for _ in current_row.len()..position { + current_row.push(EMPTY_TERMINAL_CHARACTER); + } + } + fn pad_lines_until(&mut self, position: usize) { + for _ in self.viewport.len()..position { + self.viewport.push(Row::new().canonical()); + } + } + pub fn move_cursor_to(&mut self, x: usize, y: usize) { + self.cursor.x = x; + self.cursor.y = y; + self.pad_lines_until(self.cursor.y + 1); + self.pad_current_line_until(self.cursor.x); + } + pub fn move_cursor_up(&mut self, count: usize) { + self.cursor.y = if self.cursor.y < count { + 0 + } else { + self.cursor.y - count + }; + } + pub fn move_cursor_up_with_scrolling(&mut self, count: usize) { + let (scroll_region_top, scroll_region_bottom) = + self.scroll_region.unwrap_or((0, self.height - 1)); + for _ in 0..count { + let current_line_index = self.cursor.y; + if current_line_index == scroll_region_top { + // if we're at the top line, we create a new line and remove the last line that + // would otherwise overflow + self.viewport.remove(scroll_region_bottom); + self.viewport.insert(current_line_index, Row::new()); // TODO: .canonical() ? + } else if current_line_index > scroll_region_top + && current_line_index <= scroll_region_bottom + { + self.move_cursor_up(count); + } + } + } + pub fn move_cursor_down(&mut self, count: usize) { + let lines_to_add = if self.cursor.y + count > self.height - 1 { + (self.cursor.y + count) - (self.height - 1) + } else { + 0 + }; + self.cursor.y = if self.cursor.y + count > self.height - 1 { + self.height - 1 + } else { + self.cursor.y + count + }; + for _ in 0..lines_to_add { + self.add_canonical_line(); + } + self.pad_lines_until(self.cursor.y); + } + pub fn move_cursor_back(&mut self, count: usize) { + if self.cursor.x < count { + self.cursor.x = 0; + } else { + self.cursor.x -= count; + } + } + pub fn hide_cursor(&mut self) { + self.cursor.is_hidden = true; + } + pub fn show_cursor(&mut self) { + self.cursor.is_hidden = false; + } + pub fn set_scroll_region(&mut self, top_line_index: usize, bottom_line_index: usize) { + self.scroll_region = Some((top_line_index, bottom_line_index)); + } + pub fn clear_scroll_region(&mut self) { + self.scroll_region = None; + } + pub fn set_scroll_region_to_viewport_size(&mut self) { + self.scroll_region = Some((0, self.height - 1)); + } + pub fn delete_lines_in_scroll_region(&mut self, count: usize) { + if let Some((scroll_region_top, scroll_region_bottom)) = self.scroll_region { + let current_line_index = self.cursor.y; + if current_line_index >= scroll_region_top && current_line_index <= scroll_region_bottom + { + // when deleting lines inside the scroll region, we must make sure it stays the + // same size (and that other lines below it aren't shifted inside it) + // so we delete the current line(s) and add an empty line at the end of the scroll + // region + for _ in 0..count { + self.viewport.remove(current_line_index); + self.viewport + .insert(scroll_region_bottom, Row::new().canonical()); + } + } + } + } + pub fn add_empty_lines_in_scroll_region(&mut self, count: usize) { + if let Some((scroll_region_top, scroll_region_bottom)) = self.scroll_region { + let current_line_index = self.cursor.y; + if current_line_index >= scroll_region_top && current_line_index <= scroll_region_bottom + { + // when adding empty lines inside the scroll region, we must make sure it stays the + // same size and that lines don't "leak" outside of it + // so we add an empty line where the cursor currently is, and delete the last line + // of the scroll region + for _ in 0..count { + self.viewport.remove(scroll_region_bottom); + self.viewport + .insert(current_line_index, Row::new().canonical()); + } + } + } + } + pub fn move_cursor_to_column(&mut self, column: usize) { + self.cursor.x = column; + self.pad_current_line_until(self.cursor.x); + } + pub fn move_cursor_to_line(&mut self, line: usize) { + self.cursor.y = line; + self.pad_lines_until(self.cursor.y + 1); + self.pad_current_line_until(self.cursor.x); + } + pub fn replace_with_empty_chars(&mut self, count: usize, empty_char_style: CharacterStyles) { + let mut empty_character = EMPTY_TERMINAL_CHARACTER; + empty_character.styles = empty_char_style; + let pad_until = std::cmp::min(self.width, self.cursor.x + count); + self.pad_current_line_until(pad_until); + let current_row = self.viewport.get_mut(self.cursor.y).unwrap(); + for i in 0..count { + current_row.replace_character_at(empty_character, self.cursor.x + i); + } + } + pub fn erase_characters(&mut self, count: usize, empty_char_style: CharacterStyles) { + let mut empty_character = EMPTY_TERMINAL_CHARACTER; + empty_character.styles = empty_char_style; + let current_row = self.viewport.get_mut(self.cursor.y).unwrap(); + for _ in 0..count { + current_row.delete_character(self.cursor.x); + } + let mut empty_space_to_append = vec![empty_character; count]; + self.viewport + .get_mut(self.cursor.y) + .unwrap() + .append(&mut empty_space_to_append); + } +} + +#[derive(Clone)] +pub struct Row { + pub columns: Vec, + pub is_canonical: bool, +} + +impl Debug for Row { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + for character in &self.columns { + write!(f, "{:?}", character)?; + } + Ok(()) + } +} + +impl Row { + pub fn new() -> Self { + Row { + columns: vec![], + is_canonical: false, + } + } + pub fn from_columns(columns: Vec) -> Self { + Row { + columns, + is_canonical: false, + } + } + pub fn from_rows(mut rows: Vec) -> Self { + if rows.is_empty() { + Row::new() + } else { + let mut first_row = rows.remove(0); + for row in rows.iter_mut() { + first_row.append(&mut row.columns); + } + first_row + } + } + pub fn with_character(mut self, terminal_character: TerminalCharacter) -> Self { + self.columns.push(terminal_character); + self + } + pub fn canonical(mut self) -> Self { + self.is_canonical = true; + self + } + pub fn add_character_at(&mut self, terminal_character: TerminalCharacter, x: usize) { + match self.columns.len().cmp(&x) { + Ordering::Equal => self.columns.push(terminal_character), + Ordering::Less => { + self.columns.resize(x, EMPTY_TERMINAL_CHARACTER); + self.columns.push(terminal_character); + } + Ordering::Greater => { + // this is much more performant than remove/insert + self.columns.push(terminal_character); + self.columns.swap_remove(x); + } + } + } + pub fn replace_character_at(&mut self, terminal_character: TerminalCharacter, x: usize) { + // this is much more performant than remove/insert + self.columns.push(terminal_character); + self.columns.swap_remove(x); + } + pub fn replace_columns(&mut self, columns: Vec) { + self.columns = columns; + } + pub fn push(&mut self, terminal_character: TerminalCharacter) { + self.columns.push(terminal_character); + } + pub fn truncate(&mut self, x: usize) { + self.columns.truncate(x); + } + pub fn append(&mut self, to_append: &mut Vec) { + self.columns.append(to_append); + } + pub fn replace_beginning_with(&mut self, mut line_part: Vec) { + drop(self.columns.drain(0..line_part.len())); + line_part.append(&mut self.columns); + self.columns = line_part; + } + pub fn len(&self) -> usize { + self.columns.len() + } + pub fn delete_character(&mut self, x: usize) { + self.columns.remove(x); + } + pub fn split_to_rows_of_length(&mut self, max_row_length: usize) -> Vec { + let mut parts: Vec = vec![]; + let mut current_part: Vec = vec![]; + for character in self.columns.drain(..) { + if current_part.len() == max_row_length { + parts.push(Row::from_columns(current_part)); + current_part = vec![]; + } + current_part.push(character); + } + if !current_part.is_empty() { + parts.push(Row::from_columns(current_part)) + }; + if !parts.is_empty() && self.is_canonical { + parts.get_mut(0).unwrap().is_canonical = true; + } + parts + } +} + +#[derive(Clone, Debug)] +pub struct Cursor { + x: usize, + y: usize, + is_hidden: bool, +} + +impl Cursor { + pub fn new(x: usize, y: usize) -> Self { + Cursor { + x, + y, + is_hidden: false, + } + } +} diff --git a/src/panes/mod.rs b/src/panes/mod.rs index 7d478886..4e7d6e80 100644 --- a/src/panes/mod.rs +++ b/src/panes/mod.rs @@ -1,9 +1,9 @@ +mod grid; mod plugin_pane; -mod scroll; mod terminal_character; mod terminal_pane; +pub use grid::*; pub use plugin_pane::*; -pub use scroll::*; pub use terminal_character::*; pub use terminal_pane::*; diff --git a/src/panes/plugin_pane.rs b/src/panes/plugin_pane.rs index 1573d577..a93ff8c0 100644 --- a/src/panes/plugin_pane.rs +++ b/src/panes/plugin_pane.rs @@ -9,6 +9,7 @@ use crate::panes::{PaneId, PositionAndSize}; pub struct PluginPane { pub pid: u32, pub should_render: bool, + pub selectable: bool, pub position_and_size: PositionAndSize, pub position_and_size_override: Option, pub send_plugin_instructions: SenderWithContext, @@ -23,6 +24,7 @@ impl PluginPane { Self { pid, should_render: true, + selectable: true, position_and_size, position_and_size_override: None, send_plugin_instructions, @@ -92,6 +94,12 @@ impl Pane for PluginPane { fn set_should_render(&mut self, should_render: bool) { self.should_render = should_render; } + fn selectable(&self) -> bool { + self.selectable + } + fn set_selectable(&mut self, selectable: bool) { + self.selectable = selectable; + } fn render(&mut self) -> Option { // if self.should_render { if true { diff --git a/src/panes/scroll.rs b/src/panes/scroll.rs deleted file mode 100644 index 98d1b6f4..00000000 --- a/src/panes/scroll.rs +++ /dev/null @@ -1,838 +0,0 @@ -use std::collections::VecDeque; -use std::{ - cmp::max, - fmt::{self, Debug, Formatter}, -}; - -use crate::panes::terminal_character::{ - CharacterStyles, TerminalCharacter, EMPTY_TERMINAL_CHARACTER, -}; - -/* - * Scroll - * - * holds the terminal buffer and controls the viewport (which part of it we see) - * its functions include line-wrapping and tracking the location of the cursor - * - */ - -/* - * CanonicalLine vs. WrappedFragment - * - * If the terminal had infinite width and we did not need to line wrap, the CanonicalLine would - * be our only unit of line separation. - * Each CanonicalLine has one or more WrappedFragments, which are re-calculated when the terminal is - * resized, or when characters are added to the line - * - */ - -#[derive(Clone)] -pub struct CanonicalLine { - pub wrapped_fragments: Vec, -} - -impl CanonicalLine { - pub fn new() -> Self { - CanonicalLine { - wrapped_fragments: vec![WrappedFragment::new()], - } - } - pub fn add_new_wrap(&mut self, terminal_character: TerminalCharacter) { - let mut new_fragment = WrappedFragment::new(); - new_fragment.add_character(terminal_character, 0); - self.wrapped_fragments.push(new_fragment); - } - pub fn change_width(&mut self, new_width: usize) { - let characters = self.flattened_characters(); - let wrapped_fragments = - CanonicalLine::fill_fragments_up_to_width(characters, new_width, None); - self.wrapped_fragments = wrapped_fragments; - } - pub fn clear_after(&mut self, fragment_index: usize, column_index: usize) { - let fragment_to_clear = self - .wrapped_fragments - .get_mut(fragment_index) - .expect("fragment out of bounds"); - fragment_to_clear.clear_after_and_including(column_index); - self.wrapped_fragments.truncate(fragment_index + 1); - } - pub fn replace_with_empty_chars( - &mut self, - fragment_index: usize, - from_col: usize, - count: usize, - style_of_empty_space: CharacterStyles, - ) { - let mut characters_replaced = 0; - let mut column_position_in_fragment = from_col; - let mut current_fragment = fragment_index; - let mut empty_space_character = EMPTY_TERMINAL_CHARACTER; - empty_space_character.styles = style_of_empty_space; - loop { - if characters_replaced == count { - break; - } - match self.wrapped_fragments.get_mut(current_fragment) { - Some(fragment_to_clear) => { - let fragment_characters_count = fragment_to_clear.characters.len(); - if fragment_characters_count >= column_position_in_fragment { - fragment_to_clear - .add_character(empty_space_character, column_position_in_fragment); - column_position_in_fragment += 1; - characters_replaced += 1; - } else { - current_fragment += 1; - column_position_in_fragment = 0; - } - } - None => { - // end of line, nothing more to clear - break; - } - } - } - } - pub fn erase_chars( - &mut self, - fragment_index: usize, - from_col: usize, - count: usize, - style_of_empty_space: CharacterStyles, - ) { - let mut empty_character = EMPTY_TERMINAL_CHARACTER; - empty_character.styles = style_of_empty_space; - let current_width = self.wrapped_fragments.get(0).unwrap().characters.len(); - let mut characters = self.flattened_characters(); - let absolute_position_of_character = fragment_index * current_width + from_col; - for _ in 0..count { - characters.remove(absolute_position_of_character); - } - let wrapped_fragments = CanonicalLine::fill_fragments_up_to_width( - characters, - current_width, - Some(empty_character), - ); - self.wrapped_fragments = wrapped_fragments; - } - pub fn replace_with_empty_chars_after_cursor( - &mut self, - fragment_index: usize, - from_col: usize, - total_columns: usize, - style_of_empty_space: CharacterStyles, - ) { - let mut empty_char_character = EMPTY_TERMINAL_CHARACTER; - empty_char_character.styles = style_of_empty_space; - let current_fragment = self.wrapped_fragments.get_mut(fragment_index).unwrap(); - let fragment_characters_count = current_fragment.characters.len(); - - for i in from_col..fragment_characters_count { - current_fragment.add_character(empty_char_character, i); - } - - for i in fragment_characters_count..total_columns { - current_fragment.add_character(empty_char_character, i); - } - - self.wrapped_fragments.truncate(fragment_index + 1); - } - pub fn replace_with_empty_chars_before_cursor( - &mut self, - fragment_index: usize, - until_col: usize, - style_of_empty_space: CharacterStyles, - ) { - let mut empty_char_character = EMPTY_TERMINAL_CHARACTER; - empty_char_character.styles = style_of_empty_space; - - if fragment_index > 0 { - for i in 0..(fragment_index - 1) { - let fragment = self.wrapped_fragments.get_mut(i).unwrap(); - for i in 0..fragment.characters.len() { - fragment.add_character(empty_char_character, i); - } - } - } - let current_fragment = self.wrapped_fragments.get_mut(fragment_index).unwrap(); - for i in 0..until_col { - current_fragment.add_character(empty_char_character, i); - } - } - fn flattened_characters(&self) -> Vec { - self.wrapped_fragments - .iter() - .fold( - Vec::with_capacity(self.wrapped_fragments.len()), - |mut characters, wrapped_fragment| { - characters.push(wrapped_fragment.characters.iter().copied()); - characters - }, - ) - .into_iter() - .flatten() - .collect() - } - fn fill_fragments_up_to_width( - mut characters: Vec, - width: usize, - padding: Option, - ) -> Vec { - let mut wrapped_fragments = vec![]; - while !characters.is_empty() { - if characters.len() > width { - wrapped_fragments.push(WrappedFragment::from_vec( - characters.drain(..width).collect(), - )); - } else { - let mut last_fragment = WrappedFragment::from_vec(characters.drain(..).collect()); - let last_fragment_len = last_fragment.characters.len(); - if let Some(empty_char_character) = padding { - if last_fragment_len < width { - for _ in last_fragment_len..width { - last_fragment.characters.push(empty_char_character); - } - } - } - wrapped_fragments.push(last_fragment); - } - } - if wrapped_fragments.is_empty() { - wrapped_fragments.push(WrappedFragment::new()); - } - wrapped_fragments - } -} - -impl Debug for CanonicalLine { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - for wrapped_fragment in &self.wrapped_fragments { - writeln!(f, "{:?}", wrapped_fragment)?; - } - Ok(()) - } -} - -#[derive(Clone)] -pub struct WrappedFragment { - pub characters: Vec, -} - -impl WrappedFragment { - pub fn new() -> Self { - WrappedFragment { characters: vec![] } - } - pub fn add_character( - &mut self, - terminal_character: TerminalCharacter, - position_in_line: usize, - ) { - if position_in_line == self.characters.len() { - self.characters.push(terminal_character); - } else { - // this is much more performant than remove/insert - self.characters.push(terminal_character); - self.characters.swap_remove(position_in_line); - } - } - pub fn from_vec(characters: Vec) -> Self { - WrappedFragment { characters } - } - pub fn clear_after_and_including(&mut self, character_index: usize) { - self.characters.truncate(character_index); - } -} - -impl Debug for WrappedFragment { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - for character in &self.characters { - write!(f, "{:?}", character)?; - } - Ok(()) - } -} - -#[derive(Debug, Clone, Copy)] -pub struct CursorPosition { - line_index: (usize, usize), // (canonical line index, fragment index in line) - column_index: usize, // 0 is the first character from the pane edge -} - -impl CursorPosition { - pub fn new() -> Self { - CursorPosition { - line_index: (0, 0), - column_index: 0, - } - } - pub fn move_forward(&mut self, count: usize) { - // TODO: panic if out of bounds? - self.column_index += count; - } - pub fn move_backwards(&mut self, count: usize) { - self.column_index = u32::overflowing_sub(self.column_index as u32, count as u32).0 as usize; - } - pub fn move_to_next_linewrap(&mut self) { - self.line_index.1 += 1; - } - pub fn move_to_next_canonical_line(&mut self) { - self.line_index.0 += 1; - } - pub fn _move_to_prev_canonical_line(&mut self) { - self.line_index.0 -= 1; - } - pub fn move_to_beginning_of_linewrap(&mut self) { - self.column_index = 0; - } - pub fn move_to_beginning_of_canonical_line(&mut self) { - self.column_index = 0; - self.line_index.1 = 0; - } - pub fn move_down_by_canonical_lines(&mut self, count: usize) { - // this method does not verify that we will not overflow - let current_canonical_line_position = self.line_index.0; - self.line_index = (current_canonical_line_position + count, 0); - } - pub fn move_to_canonical_line(&mut self, index: usize) { - self.line_index = (index, 0); - } - pub fn move_to_column(&mut self, col: usize) { - self.column_index = col; - } - pub fn reset(&mut self) { - self.column_index = 0; - self.line_index = (0, 0); - } -} - -pub struct Scroll { - canonical_lines: Vec, - cursor_position: CursorPosition, - total_columns: usize, - lines_in_view: usize, - viewport_bottom_offset: Option, - scroll_region: Option<(usize, usize)>, // start line, end line (if set, this is the area the will scroll) - show_cursor: bool, - alternative_buffer: Option>, - alternative_cursor_position: Option, -} - -impl Scroll { - pub fn new(total_columns: usize, lines_in_view: usize) -> Self { - let mut canonical_lines = vec![]; - canonical_lines.push(CanonicalLine::new()); - let cursor_position = CursorPosition::new(); - Scroll { - canonical_lines: vec![CanonicalLine::new()], // The rest will be created by newlines explicitly - total_columns, - lines_in_view, - cursor_position, - viewport_bottom_offset: None, - scroll_region: None, - show_cursor: true, - alternative_buffer: None, - alternative_cursor_position: None, - } - } - pub fn as_character_lines(&self) -> Vec> { - let mut lines: VecDeque> = VecDeque::new(); // TODO: with capacity lines_from_end? - let canonical_lines = self.canonical_lines.iter().rev(); - let mut lines_to_skip = self.viewport_bottom_offset.unwrap_or(0); - 'gather_lines: for current_canonical_line in canonical_lines { - for wrapped_fragment in current_canonical_line.wrapped_fragments.iter().rev() { - let mut line: Vec = - wrapped_fragment.characters.iter().copied().collect(); - if lines_to_skip > 0 { - lines_to_skip -= 1; - } else { - // pad line if needed - line.resize(self.total_columns, EMPTY_TERMINAL_CHARACTER); - lines.push_front(line); - } - if lines.len() == self.lines_in_view { - break 'gather_lines; - } - } - } - if lines.len() < self.lines_in_view { - // pad lines in case we don't have enough scrollback to fill the view - let empty_line = vec![EMPTY_TERMINAL_CHARACTER; self.total_columns]; - - for _ in lines.len()..self.lines_in_view { - // pad lines in case we didn't have enough - lines.push_back(empty_line.clone()); - } - } - Vec::from(lines) - } - pub fn add_character(&mut self, terminal_character: TerminalCharacter) { - let (canonical_line_position, wrapped_fragment_index_in_line) = - self.cursor_position.line_index; - let cursor_position_in_line = self.cursor_position.column_index; - let current_line = self - .canonical_lines - .get_mut(canonical_line_position) - .expect("cursor out of bounds"); - let current_wrapped_fragment = current_line - .wrapped_fragments - .get_mut(wrapped_fragment_index_in_line) - .expect("cursor out of bounds"); - - if cursor_position_in_line < self.total_columns { - current_wrapped_fragment.add_character(terminal_character, cursor_position_in_line); - self.cursor_position.move_forward(1); - } else { - current_line.add_new_wrap(terminal_character); - self.cursor_position.move_to_next_linewrap(); - self.cursor_position.move_to_beginning_of_linewrap(); - self.cursor_position.move_forward(1); - } - } - pub fn show_cursor(&mut self) { - self.show_cursor = true; - } - pub fn hide_cursor(&mut self) { - self.show_cursor = false; - } - pub fn add_canonical_line(&mut self) { - let current_canonical_line_index = self.cursor_position.line_index.0; - if let Some((scroll_region_top, scroll_region_bottom)) = - self.scroll_region_absolute_indices() - { - if current_canonical_line_index == scroll_region_bottom { - // end of scroll region - // when we have a scroll region set and we're at its bottom - // we need to delete its first line, thus shifting all lines in it upwards - // then we add an empty line at its end which will be filled by the application - // controlling the scroll region (presumably filled by whatever comes next in the - // scroll buffer, but that's not something we control) - self.canonical_lines.remove(scroll_region_top); - self.canonical_lines - .insert(scroll_region_bottom, CanonicalLine::new()); - return; - } - } - - use std::cmp::Ordering; - match current_canonical_line_index.cmp(&(self.canonical_lines.len() - 1)) { - Ordering::Equal => { - self.canonical_lines.push(CanonicalLine::new()); - self.cursor_position.move_to_next_canonical_line(); - self.cursor_position.move_to_beginning_of_canonical_line(); - } - Ordering::Less => { - self.cursor_position.move_to_next_canonical_line(); - self.cursor_position.move_to_beginning_of_canonical_line(); - } - _ => panic!("cursor out of bounds, cannot add_canonical_line"), - } - } - pub fn cursor_coordinates_on_screen(&self) -> Option<(usize, usize)> { - // (x, y) - if !self.show_cursor { - return None; - } - let (canonical_line_cursor_position, line_wrap_cursor_position) = - self.cursor_position.line_index; - let x = self.cursor_position.column_index; - let mut y = 0; - let indices_and_canonical_lines = self.canonical_lines.iter().enumerate().rev(); - for (current_index, current_line) in indices_and_canonical_lines { - if current_index == canonical_line_cursor_position { - y += current_line.wrapped_fragments.len() - line_wrap_cursor_position; - break; - } else { - y += current_line.wrapped_fragments.len(); - } - } - let total_lines = self - .canonical_lines - .iter() - .fold(0, |total_lines, current_line| { - total_lines + current_line.wrapped_fragments.len() - }); // TODO: is this performant enough? should it be cached or kept track of? - let y = if total_lines < self.lines_in_view { - total_lines - y - } else if y > self.lines_in_view { - self.lines_in_view - } else { - self.lines_in_view - y - }; - Some((x, y)) - } - pub fn move_cursor_forward(&mut self, count: usize) { - let (current_canonical_line_index, current_line_wrap_position) = - self.cursor_position.line_index; - let current_cursor_column_position = self.cursor_position.column_index; - let current_canonical_line = self - .canonical_lines - .get_mut(current_canonical_line_index) - .expect("cursor out of bounds"); - let current_fragment = current_canonical_line - .wrapped_fragments - .get_mut(current_line_wrap_position) - .expect("cursor out of bounds"); - let move_count = if current_cursor_column_position + count > self.total_columns { - // move to last column in the current line wrap - self.total_columns - current_cursor_column_position - } else { - count - }; - - current_fragment.characters.resize( - max( - current_fragment.characters.len(), - current_cursor_column_position + move_count, - ), - EMPTY_TERMINAL_CHARACTER, - ); - - self.cursor_position.move_forward(move_count); - } - pub fn move_cursor_back(&mut self, count: usize) { - let current_cursor_column_position = self.cursor_position.column_index; - if current_cursor_column_position < count { - self.cursor_position.move_to_beginning_of_linewrap(); - } else { - self.cursor_position.move_backwards(count); - } - } - pub fn move_cursor_to_beginning_of_linewrap(&mut self) { - self.cursor_position.move_to_beginning_of_linewrap(); - } - pub fn _move_cursor_to_beginning_of_canonical_line(&mut self) { - self.cursor_position.move_to_beginning_of_canonical_line(); - } - pub fn move_cursor_backwards(&mut self, count: usize) { - self.cursor_position.move_backwards(count); - } - pub fn move_cursor_up(&mut self, count: usize) { - for _ in 0..count { - let (current_canonical_line_index, current_line_wrap_index) = - self.cursor_position.line_index; - if current_line_wrap_index > 0 { - // go up one line-wrap - self.cursor_position.line_index.1 -= 1; - } else if current_canonical_line_index > 0 { - // go up to previous canonical line - self.cursor_position.line_index.0 -= 1; - let current_canonical_line = self - .canonical_lines - .get(self.cursor_position.line_index.0) - .unwrap(); - let wraps_in_current_line = current_canonical_line.wrapped_fragments.len(); - // make sure to only go up to the last wrap of the previous line - self.cursor_position.line_index.1 = wraps_in_current_line - 1; - } - } - } - pub fn move_cursor_down(&mut self, count: usize) { - let current_canonical_line = self.cursor_position.line_index.0; - let max_count = (self.canonical_lines.len() - 1) - current_canonical_line; - let count = std::cmp::min(count, max_count); - self.cursor_position.move_down_by_canonical_lines(count); - } - pub fn change_size(&mut self, columns: usize, lines: usize) { - for canonical_line in self.canonical_lines.iter_mut() { - canonical_line.change_width(columns); - } - let cursor_line = self - .canonical_lines - .get(self.cursor_position.line_index.0) - .expect("cursor out of bounds"); - if cursor_line.wrapped_fragments.len() <= self.cursor_position.line_index.1 { - self.cursor_position.line_index.1 = cursor_line.wrapped_fragments.len() - 1; - } - self.lines_in_view = lines; - self.total_columns = columns; - } - pub fn clear_canonical_line_right_of_cursor(&mut self, style_of_empty_space: CharacterStyles) { - let (current_canonical_line_index, current_line_wrap_position) = - self.cursor_position.line_index; - let current_cursor_column_position = self.cursor_position.column_index; - let current_canonical_line = self - .canonical_lines - .get_mut(current_canonical_line_index) - .expect("cursor out of bounds"); - current_canonical_line.replace_with_empty_chars_after_cursor( - current_line_wrap_position, - current_cursor_column_position, - self.total_columns, - style_of_empty_space, - ); - } - pub fn clear_canonical_line_left_of_cursor(&mut self, style_of_empty_space: CharacterStyles) { - let (current_canonical_line_index, current_line_wrap_position) = - self.cursor_position.line_index; - let current_cursor_column_position = self.cursor_position.column_index; - let current_canonical_line = self - .canonical_lines - .get_mut(current_canonical_line_index) - .expect("cursor out of bounds"); - current_canonical_line.replace_with_empty_chars_before_cursor( - current_line_wrap_position, - current_cursor_column_position, - style_of_empty_space, - ); - } - pub fn clear_all_after_cursor(&mut self) { - let (current_canonical_line_index, current_line_wrap_position) = - self.cursor_position.line_index; - let current_cursor_column_position = self.cursor_position.column_index; - let current_canonical_line = self - .canonical_lines - .get_mut(current_canonical_line_index) - .expect("cursor out of bounds"); - current_canonical_line - .clear_after(current_line_wrap_position, current_cursor_column_position); - self.canonical_lines - .truncate(current_canonical_line_index + 1); - } - pub fn replace_with_empty_chars( - &mut self, - count: usize, - style_of_empty_space: CharacterStyles, - ) { - let (current_canonical_line_index, current_line_wrap_position) = - self.cursor_position.line_index; - let current_cursor_column_position = self.cursor_position.column_index; - let current_canonical_line = self - .canonical_lines - .get_mut(current_canonical_line_index) - .expect("cursor out of bounds"); - current_canonical_line.replace_with_empty_chars( - current_line_wrap_position, - current_cursor_column_position, - count, - style_of_empty_space, - ); - } - pub fn erase_characters(&mut self, count: usize, style_of_empty_space: CharacterStyles) { - let (current_canonical_line_index, current_line_wrap_position) = - self.cursor_position.line_index; - let current_cursor_column_position = self.cursor_position.column_index; - let current_canonical_line = self - .canonical_lines - .get_mut(current_canonical_line_index) - .expect("cursor out of bounds"); - current_canonical_line.erase_chars( - current_line_wrap_position, - current_cursor_column_position, - count, - style_of_empty_space, - ); - } - pub fn clear_all(&mut self) { - self.canonical_lines.clear(); - self.canonical_lines.push(CanonicalLine::new()); - self.cursor_position.reset(); - } - pub fn move_cursor_to(&mut self, line: usize, col: usize) { - let line_on_screen = if self.canonical_lines.len() > self.lines_in_view { - line + (self.canonical_lines.len() - self.lines_in_view) - } else { - line - }; - if self.canonical_lines.len() > line_on_screen { - self.cursor_position.move_to_canonical_line(line_on_screen); - } else { - for _ in self.canonical_lines.len()..=line_on_screen { - self.canonical_lines.push(CanonicalLine::new()); - } - self.cursor_position.move_to_canonical_line(line_on_screen); - } - let (current_canonical_line_index, current_line_wrap_position) = - self.cursor_position.line_index; - let current_canonical_line = self - .canonical_lines - .get_mut(current_canonical_line_index) - .expect("cursor out of bounds"); - let current_fragment = current_canonical_line - .wrapped_fragments - .get_mut(current_line_wrap_position) - .expect("cursor out of bounds"); - - current_fragment.characters.resize( - max(current_fragment.characters.len(), col), - EMPTY_TERMINAL_CHARACTER, - ); - self.cursor_position.move_to_column(col); - } - pub fn move_cursor_to_column(&mut self, col: usize) { - let current_canonical_line = self.cursor_position.line_index.0; - self.move_cursor_to(current_canonical_line, col); - } - pub fn move_cursor_to_line(&mut self, line: usize) { - let current_column = self.cursor_position.column_index; - self.move_cursor_to(line, current_column); - } - pub fn move_current_buffer_to_alternative_buffer(&mut self) { - self.alternative_buffer = Some(self.canonical_lines.drain(..).collect()); - self.alternative_cursor_position = Some(self.cursor_position); - self.clear_all(); - } - pub fn override_current_buffer_with_alternative_buffer(&mut self) { - if let Some(alternative_buffer) = self.alternative_buffer.as_mut() { - self.canonical_lines = alternative_buffer.drain(..).collect(); - } - if let Some(alternative_cursor_position) = self.alternative_cursor_position { - self.cursor_position = alternative_cursor_position; - } - self.alternative_buffer = None; - self.alternative_cursor_position = None; - } - pub fn set_scroll_region(&mut self, top_line: usize, bottom_line: usize) { - self.scroll_region = Some((top_line, bottom_line)); - } - pub fn clear_scroll_region(&mut self) { - self.scroll_region = None; - } - fn scroll_region_absolute_indices(&mut self) -> Option<(usize, usize)> { - self.scroll_region?; - if self.canonical_lines.len() > self.lines_in_view { - let absolute_top = self.canonical_lines.len() - 1 - self.lines_in_view; - let absolute_bottom = self.canonical_lines.len() - 1; - Some((absolute_top, absolute_bottom)) - } else { - Some((self.scroll_region.unwrap().0, self.scroll_region.unwrap().1)) - } - } - fn scroll_region_absolute_indices_or_screen_edges(&mut self) -> (usize, usize) { - match self.scroll_region { - Some(_scroll_region) => self.scroll_region_absolute_indices().unwrap(), - None => { - // indices of screen top and bottom edge - // TODO: what if we don't have enough lines? - // let absolute_top = self.canonical_lines.len() - 1 - self.lines_in_view; - let absolute_top = self.canonical_lines.len() - self.lines_in_view; - let absolute_bottom = self.canonical_lines.len() - 1; - (absolute_top, absolute_bottom) - } - } - } - pub fn delete_lines_in_scroll_region(&mut self, count: usize) { - if let Some((scroll_region_top, scroll_region_bottom)) = - self.scroll_region_absolute_indices() - { - let current_canonical_line_index = self.cursor_position.line_index.0; - if current_canonical_line_index >= scroll_region_top - && current_canonical_line_index <= scroll_region_bottom - { - // when deleting lines inside the scroll region, we must make sure it stays the - // same size (and that other lines below it aren't shifted inside it) - // so we delete the current line(s) and add an empty line at the end of the scroll - // region - for _ in 0..count { - self.canonical_lines.remove(current_canonical_line_index); - self.canonical_lines - .insert(scroll_region_bottom, CanonicalLine::new()); - } - } - } - } - pub fn add_empty_lines_in_scroll_region(&mut self, count: usize) { - if let Some((scroll_region_top, scroll_region_bottom)) = - self.scroll_region_absolute_indices() - { - let current_canonical_line_index = self.cursor_position.line_index.0; - if current_canonical_line_index >= scroll_region_top - && current_canonical_line_index <= scroll_region_bottom - { - // when adding empty lines inside the scroll region, we must make sure it stays the - // same size and that lines don't "leak" outside of it - // so we add an empty line where the cursor currently is, and delete the last line - // of the scroll region - for _ in 0..count { - self.canonical_lines.remove(scroll_region_bottom); - self.canonical_lines - .insert(current_canonical_line_index, CanonicalLine::new()); - } - } - } - } - pub fn move_cursor_up_in_scroll_region(&mut self, count: usize) { - let (scroll_region_top, scroll_region_bottom) = - self.scroll_region_absolute_indices_or_screen_edges(); - for _ in 0..count { - let current_canonical_line_index = self.cursor_position.line_index.0; - if current_canonical_line_index == scroll_region_top { - // if we're at the top line, we create a new line and remove the last line that - // would otherwise overflow - self.canonical_lines.remove(scroll_region_bottom); - self.canonical_lines - .insert(current_canonical_line_index, CanonicalLine::new()); - } else if current_canonical_line_index > scroll_region_top - && current_canonical_line_index <= scroll_region_bottom - { - self.move_cursor_up(count); - } - } - } - /// [scroll_up](https://github.com/alacritty/alacritty/blob/ec42b42ce601808070462111c0c28edb0e89babb/alacritty_terminal/src/grid/mod.rs#L261) - /// This function takes the first line of the scroll region and moves it to the bottom (count times) - pub fn rotate_scroll_region_up(&mut self, count: usize) { - if let Some((_, scroll_region_bottom)) = self.scroll_region_absolute_indices() { - if self.show_cursor { - let scroll_region_bottom_index = scroll_region_bottom - 1; - self.cursor_position - .move_to_canonical_line(scroll_region_bottom_index); - - let new_empty_lines = vec![CanonicalLine::new(); count]; - self.canonical_lines.splice( - scroll_region_bottom_index..scroll_region_bottom_index + 1, - new_empty_lines, - ); - - self.cursor_position - .move_to_canonical_line(scroll_region_bottom_index + count); - } - } - } - /// [scroll_down](https://github.com/alacritty/alacritty/blob/ec42b42ce601808070462111c0c28edb0e89babb/alacritty_terminal/src/grid/mod.rs#L221) - /// This function takes the last line of the scroll region and moves it to the top (count times) - pub fn rotate_scroll_region_down(&mut self, count: usize) { - if let Some((scroll_region_top, _)) = self.scroll_region_absolute_indices() { - if self.show_cursor { - let scroll_region_top_index = scroll_region_top - 1; - self.cursor_position - .move_to_canonical_line(scroll_region_top_index); - - let new_empty_lines = vec![CanonicalLine::new(); count]; - self.canonical_lines.splice( - scroll_region_top_index..scroll_region_top_index, - new_empty_lines, - ); - - self.cursor_position - .move_to_canonical_line(scroll_region_top_index + count); - } - } - } - pub fn move_viewport_up(&mut self, count: usize) { - if let Some(current_offset) = self.viewport_bottom_offset.as_mut() { - *current_offset += count; - } else { - self.viewport_bottom_offset = Some(count); - } - } - pub fn move_viewport_down(&mut self, count: usize) { - if let Some(current_offset) = self.viewport_bottom_offset.as_mut() { - if *current_offset > count { - *current_offset -= count; - } else { - self.viewport_bottom_offset = None; - } - } - } - pub fn reset_viewport(&mut self) { - self.viewport_bottom_offset = None; - } -} - -impl Debug for Scroll { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - for line in &self.canonical_lines { - writeln!(f, "{:?}", line)?; - } - Ok(()) - } -} diff --git a/src/panes/terminal_pane.rs b/src/panes/terminal_pane.rs index 0d04c681..466f5b65 100644 --- a/src/panes/terminal_pane.rs +++ b/src/panes/terminal_pane.rs @@ -4,9 +4,12 @@ use crate::tab::Pane; use ::nix::pty::Winsize; use ::std::os::unix::io::RawFd; use ::vte::Perform; +use std::fmt::Debug; -use crate::panes::terminal_character::{CharacterStyles, TerminalCharacter}; -use crate::panes::Scroll; +use crate::panes::grid::Grid; +use crate::panes::terminal_character::{ + CharacterStyles, TerminalCharacter, EMPTY_TERMINAL_CHARACTER, +}; use crate::utils::logging::debug_log_to_file; use crate::VteEvent; @@ -35,9 +38,11 @@ impl From for PositionAndSize { #[derive(Debug)] pub struct TerminalPane { + pub grid: Grid, + pub alternative_grid: Option, // for 1049h/l instructions which tell us to switch between these two pub pid: RawFd, - pub scroll: Scroll, pub should_render: bool, + pub selectable: bool, pub position_and_size: PositionAndSize, pub position_and_size_override: Option, pub cursor_key_mode: bool, // DECCKM - when set, cursor keys should send ANSI direction codes (eg. "OD") instead of the arrow keys (eg. "") @@ -111,7 +116,7 @@ impl Pane for TerminalPane { } fn cursor_coordinates(&self) -> Option<(usize, usize)> { // (x, y) - self.scroll.cursor_coordinates_on_screen() + self.grid.cursor_coordinates() } fn adjust_input_to_terminal(&self, input_bytes: Vec) -> Vec { // there are some cases in which the terminal state means that input sent to it @@ -165,6 +170,12 @@ impl Pane for TerminalPane { fn set_should_render(&mut self, should_render: bool) { self.should_render = should_render; } + fn selectable(&self) -> bool { + self.selectable + } + fn set_selectable(&mut self, selectable: bool) { + self.selectable = selectable; + } fn render(&mut self) -> Option { // if self.should_render { if true { @@ -252,28 +263,30 @@ impl Pane for TerminalPane { self.mark_for_rerender(); } fn scroll_up(&mut self, count: usize) { - self.scroll.move_viewport_up(count); + self.grid.move_viewport_up(count); self.mark_for_rerender(); } fn scroll_down(&mut self, count: usize) { - self.scroll.move_viewport_down(count); + self.grid.move_viewport_down(count); self.mark_for_rerender(); } fn clear_scroll(&mut self) { - self.scroll.reset_viewport(); + self.grid.reset_viewport(); self.mark_for_rerender(); } } impl TerminalPane { pub fn new(pid: RawFd, position_and_size: PositionAndSize) -> TerminalPane { - let scroll = Scroll::new(position_and_size.columns, position_and_size.rows); + let grid = Grid::new(position_and_size.rows, position_and_size.columns); let pending_styles = CharacterStyles::new(); TerminalPane { pid, - scroll, + grid, + alternative_grid: None, should_render: true, + selectable: true, pending_styles, position_and_size, position_and_size_override: None, @@ -310,35 +323,38 @@ impl TerminalPane { fn reflow_lines(&mut self) { let rows = self.get_rows(); let columns = self.get_columns(); - self.scroll.change_size(columns, rows); + self.grid.change_size(rows, columns); + if let Some(alternative_grid) = self.alternative_grid.as_mut() { + alternative_grid.change_size(rows, columns); + } } pub fn read_buffer_as_lines(&self) -> Vec> { - self.scroll.as_character_lines() + self.grid.as_character_lines() } #[cfg(test)] pub fn cursor_coordinates(&self) -> Option<(usize, usize)> { // (x, y) - self.scroll.cursor_coordinates_on_screen() + self.grid.cursor_coordinates() } pub fn rotate_scroll_region_up(&mut self, count: usize) { - self.scroll.rotate_scroll_region_up(count); + self.grid.rotate_scroll_region_up(count); self.mark_for_rerender(); } pub fn rotate_scroll_region_down(&mut self, count: usize) { - self.scroll.rotate_scroll_region_down(count); + self.grid.rotate_scroll_region_down(count); self.mark_for_rerender(); } fn add_newline(&mut self) { - self.scroll.add_canonical_line(); + self.grid.add_canonical_line(); // self.reset_all_ansi_codes(); // TODO: find out if we should be resetting here or not self.mark_for_rerender(); } fn move_to_beginning_of_line(&mut self) { - self.scroll.move_cursor_to_beginning_of_linewrap(); + self.grid.move_cursor_to_beginning_of_line(); } fn move_cursor_backwards(&mut self, count: usize) { - self.scroll.move_cursor_backwards(count); + self.grid.move_cursor_backwards(count); } fn _reset_all_ansi_codes(&mut self) { self.pending_styles.clear(); @@ -353,7 +369,7 @@ impl vte::Perform for TerminalPane { character: c, styles: self.pending_styles, }; - self.scroll.add_character(terminal_character); + self.grid.add_character(terminal_character); } fn execute(&mut self, byte: u8) { @@ -369,7 +385,7 @@ impl vte::Perform for TerminalPane { styles: self.pending_styles, }; // TODO: handle better with line wrapping - self.scroll.add_character(terminal_tab_character); + self.grid.add_character(terminal_tab_character); } 10 => { // 0a, newline @@ -409,23 +425,30 @@ impl vte::Perform for TerminalPane { } else { params[0] as usize }; - self.scroll.move_cursor_forward(move_by); + self.grid.move_cursor_forward_until_edge(move_by); } else if c == 'K' { // clear line (0 => right, 1 => left, 2 => all) if params[0] == 0 { - self.scroll - .clear_canonical_line_right_of_cursor(self.pending_styles); + let mut char_to_replace = EMPTY_TERMINAL_CHARACTER; + char_to_replace.styles = self.pending_styles; + self.grid + .replace_characters_in_line_after_cursor(char_to_replace); } else if params[0] == 1 { - self.scroll - .clear_canonical_line_left_of_cursor(self.pending_styles); + let mut char_to_replace = EMPTY_TERMINAL_CHARACTER; + char_to_replace.styles = self.pending_styles; + self.grid + .replace_characters_in_line_before_cursor(char_to_replace); + } else if params[0] == 2 { + self.grid.clear_cursor_line(); } - // TODO: implement 2 } else if c == 'J' { // clear all (0 => below, 1 => above, 2 => all, 3 => saved) + let mut char_to_replace = EMPTY_TERMINAL_CHARACTER; + char_to_replace.styles = self.pending_styles; if params[0] == 0 { - self.scroll.clear_all_after_cursor(); + self.grid.clear_all_after_cursor(char_to_replace); } else if params[0] == 2 { - self.scroll.clear_all(); + self.grid.clear_all(char_to_replace); } // TODO: implement 1 } else if c == 'H' { @@ -444,22 +467,22 @@ impl vte::Perform for TerminalPane { } else { (params[0] as usize - 1, params[1] as usize - 1) }; - self.scroll.move_cursor_to(row, col); + self.grid.move_cursor_to(col, row); } else if c == 'A' { // move cursor up until edge of screen let move_up_count = if params[0] == 0 { 1 } else { params[0] }; - self.scroll.move_cursor_up(move_up_count as usize); + self.grid.move_cursor_up(move_up_count as usize); } else if c == 'B' { // move cursor down until edge of screen let move_down_count = if params[0] == 0 { 1 } else { params[0] }; - self.scroll.move_cursor_down(move_down_count as usize); + self.grid.move_cursor_down(move_down_count as usize); } else if c == 'D' { let move_back_count = if params[0] == 0 { 1 } else { params[0] as usize }; - self.scroll.move_cursor_back(move_back_count); + self.grid.move_cursor_back(move_back_count); } else if c == 'l' { let first_intermediate_is_questionmark = match _intermediates.get(0) { Some(b'?') => true, @@ -469,11 +492,14 @@ impl vte::Perform for TerminalPane { if first_intermediate_is_questionmark { match params.get(0) { Some(&1049) => { - self.scroll - .override_current_buffer_with_alternative_buffer(); + if let Some(alternative_grid) = self.alternative_grid.as_mut() { + std::mem::swap(&mut self.grid, alternative_grid); + } + self.alternative_grid = None; + self.mark_for_rerender(); } Some(&25) => { - self.scroll.hide_cursor(); + self.grid.hide_cursor(); self.mark_for_rerender(); } Some(&1) => { @@ -491,11 +517,21 @@ impl vte::Perform for TerminalPane { if first_intermediate_is_questionmark { match params.get(0) { Some(&25) => { - self.scroll.show_cursor(); + self.grid.show_cursor(); self.mark_for_rerender(); } Some(&1049) => { - self.scroll.move_current_buffer_to_alternative_buffer(); + let columns = self + .position_and_size_override + .map(|x| x.columns) + .unwrap_or(self.position_and_size.columns); + let rows = self + .position_and_size_override + .map(|x| x.rows) + .unwrap_or(self.position_and_size.rows); + let current_grid = + std::mem::replace(&mut self.grid, Grid::new(rows, columns)); + self.alternative_grid = Some(current_grid); } Some(&1) => { self.cursor_key_mode = true; @@ -508,11 +544,11 @@ impl vte::Perform for TerminalPane { // minus 1 because these are 1 indexed let top_line_index = params[0] as usize - 1; let bottom_line_index = params[1] as usize - 1; - self.scroll + self.grid .set_scroll_region(top_line_index, bottom_line_index); - self.scroll.show_cursor(); + self.grid.show_cursor(); } else { - self.scroll.clear_scroll_region(); + self.grid.clear_scroll_region(); } } else if c == 't' { // TBD - title? @@ -527,7 +563,7 @@ impl vte::Perform for TerminalPane { } else { params[0] as usize }; - self.scroll + self.grid .delete_lines_in_scroll_region(line_count_to_delete); } else if c == 'L' { // insert blank lines if inside scroll region @@ -536,7 +572,7 @@ impl vte::Perform for TerminalPane { } else { params[0] as usize }; - self.scroll + self.grid .add_empty_lines_in_scroll_region(line_count_to_add); } else if c == 'q' { // ignore for now to run on mac @@ -544,10 +580,9 @@ impl vte::Perform for TerminalPane { let column = if params[0] == 0 { 0 } else { - // params[0] as usize params[0] as usize - 1 }; - self.scroll.move_cursor_to_column(column); + self.grid.move_cursor_to_column(column); } else if c == 'd' { // goto line let line = if params[0] == 0 { @@ -556,7 +591,7 @@ impl vte::Perform for TerminalPane { // minus 1 because this is 1 indexed params[0] as usize - 1 }; - self.scroll.move_cursor_to_line(line); + self.grid.move_cursor_to_line(line); } else if c == 'P' { // erase characters let count = if params[0] == 0 { @@ -564,7 +599,7 @@ impl vte::Perform for TerminalPane { } else { params[0] as usize }; - self.scroll.erase_characters(count, self.pending_styles); + self.grid.erase_characters(count, self.pending_styles); } else if c == 'X' { // erase characters and replace with empty characters of current style let count = if params[0] == 0 { @@ -572,7 +607,7 @@ impl vte::Perform for TerminalPane { } else { params[0] as usize }; - self.scroll + self.grid .replace_with_empty_chars(count, self.pending_styles); } else if c == 'T' { /* @@ -594,8 +629,8 @@ impl vte::Perform for TerminalPane { } else { params[0] as usize }; - self.scroll.delete_lines_in_scroll_region(count); - self.scroll.add_empty_lines_in_scroll_region(count); + self.grid.delete_lines_in_scroll_region(count); + self.grid.add_empty_lines_in_scroll_region(count); } else { let _ = debug_log_to_file(format!("Unhandled csi: {}->{:?}", c, params)); } @@ -603,7 +638,7 @@ impl vte::Perform for TerminalPane { fn esc_dispatch(&mut self, intermediates: &[u8], _ignore: bool, byte: u8) { if let (b'M', None) = (byte, intermediates.get(0)) { - self.scroll.move_cursor_up_in_scroll_region(1); + self.grid.move_cursor_up_with_scrolling(1); } } } diff --git a/src/pty_bus.rs b/src/pty_bus.rs index e635b176..ba5a608b 100644 --- a/src/pty_bus.rs +++ b/src/pty_bus.rs @@ -299,13 +299,15 @@ impl PtyBus { let child_pid = self.id_to_child_pid.get(&id).unwrap(); self.os_input.kill(*child_pid).unwrap(); } - PaneId::Plugin(pid) => self - .send_plugin_instructions - .send(PluginInstruction::Unload(pid)) - .unwrap(), + PaneId::Plugin(pid) => drop( + self.send_plugin_instructions + .send(PluginInstruction::Unload(pid)), + ), } } pub fn close_tab(&mut self, ids: Vec) { - ids.iter().for_each(|&id| self.close_pane(id)); + ids.iter().for_each(|&id| { + self.close_pane(id); + }); } } diff --git a/src/screen.rs b/src/screen.rs index 365d1d7a..7fb19b6c 100644 --- a/src/screen.rs +++ b/src/screen.rs @@ -42,6 +42,7 @@ pub enum ScreenInstruction { ClearScroll, CloseFocusedPane, ToggleActiveTerminalFullscreen, + SetSelectable(PaneId, bool), ClosePane(PaneId), ApplyLayout((Layout, Vec)), NewTab(RawFd), @@ -138,14 +139,16 @@ impl Screen { if self.tabs.len() > 1 { self.switch_tab_prev(); } - let mut active_tab = self.tabs.remove(&active_tab_index).unwrap(); + let active_tab = self.tabs.remove(&active_tab_index).unwrap(); let pane_ids = active_tab.get_pane_ids(); self.send_pty_instructions .send(PtyInstruction::CloseTab(pane_ids)) .unwrap(); if self.tabs.is_empty() { self.active_tab_index = None; - self.render(); + self.send_app_instructions + .send(AppInstruction::Exit) + .unwrap(); } } pub fn render(&mut self) { @@ -155,10 +158,6 @@ impl Screen { } else { self.close_tab(); } - } else { - self.send_app_instructions - .send(AppInstruction::Exit) - .unwrap(); }; } diff --git a/src/tab.rs b/src/tab.rs index 7a56508c..6c65958f 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -66,6 +66,7 @@ pub struct Tab { pub send_app_instructions: SenderWithContext, } +// FIXME: Use a struct that has a pane_type enum, to reduce all of the duplication pub trait Pane { fn x(&self) -> usize; fn y(&self) -> usize; @@ -81,6 +82,8 @@ pub trait Pane { fn position_and_size_override(&self) -> Option; fn should_render(&self) -> bool; fn set_should_render(&mut self, should_render: bool); + fn selectable(&self) -> bool; + fn set_selectable(&mut self, selectable: bool); fn render(&mut self) -> Option; fn pid(&self) -> PaneId; fn reduce_height_down(&mut self, count: usize); @@ -618,10 +621,24 @@ impl Tab { fn get_panes(&self) -> impl Iterator)> { self.panes.iter() } + // FIXME: This is some shameful duplication... + fn get_selectable_panes(&self) -> impl Iterator)> { + self.panes.iter().filter(|(_, p)| p.selectable()) + } fn has_panes(&self) -> bool { let mut all_terminals = self.get_panes(); all_terminals.next().is_some() } + fn has_selectable_panes(&self) -> bool { + let mut all_terminals = self.get_selectable_panes(); + all_terminals.next().is_some() + } + fn next_active_pane(&self, panes: Vec) -> Option { + panes + .into_iter() + .rev() + .find(|pid| self.panes.get(pid).unwrap().selectable()) + } fn pane_ids_directly_left_of(&self, id: &PaneId) -> Option> { let mut ids = vec![]; let terminal_to_check = self.panes.get(id).unwrap(); @@ -1421,14 +1438,14 @@ impl Tab { } } pub fn move_focus(&mut self) { - if !self.has_panes() { + if !self.has_selectable_panes() { return; } if self.fullscreen_is_active { return; } let active_terminal_id = self.get_active_pane_id().unwrap(); - let terminal_ids: Vec = self.get_panes().map(|(&pid, _)| pid).collect(); // TODO: better, no allocations + let terminal_ids: Vec = self.get_selectable_panes().map(|(&pid, _)| pid).collect(); // TODO: better, no allocations let first_terminal = terminal_ids.get(0).unwrap(); let active_terminal_id_position = terminal_ids .iter() @@ -1442,7 +1459,7 @@ impl Tab { self.render(); } pub fn move_focus_left(&mut self) { - if !self.has_panes() { + if !self.has_selectable_panes() { return; } if self.fullscreen_is_active { @@ -1450,7 +1467,7 @@ impl Tab { } let active_terminal = self.get_active_pane(); if let Some(active) = active_terminal { - let terminals = self.get_panes(); + let terminals = self.get_selectable_panes(); let next_index = terminals .enumerate() .filter(|(_, (_, c))| { @@ -1472,7 +1489,7 @@ impl Tab { self.render(); } pub fn move_focus_down(&mut self) { - if !self.has_panes() { + if !self.has_selectable_panes() { return; } if self.fullscreen_is_active { @@ -1480,7 +1497,7 @@ impl Tab { } let active_terminal = self.get_active_pane(); if let Some(active) = active_terminal { - let terminals = self.get_panes(); + let terminals = self.get_selectable_panes(); let next_index = terminals .enumerate() .filter(|(_, (_, c))| { @@ -1502,7 +1519,7 @@ impl Tab { self.render(); } pub fn move_focus_up(&mut self) { - if !self.has_panes() { + if !self.has_selectable_panes() { return; } if self.fullscreen_is_active { @@ -1510,7 +1527,7 @@ impl Tab { } let active_terminal = self.get_active_pane(); if let Some(active) = active_terminal { - let terminals = self.get_panes(); + let terminals = self.get_selectable_panes(); let next_index = terminals .enumerate() .filter(|(_, (_, c))| { @@ -1532,7 +1549,7 @@ impl Tab { self.render(); } pub fn move_focus_right(&mut self) { - if !self.has_panes() { + if !self.has_selectable_panes() { return; } if self.fullscreen_is_active { @@ -1540,7 +1557,7 @@ impl Tab { } let active_terminal = self.get_active_pane(); if let Some(active) = active_terminal { - let terminals = self.get_panes(); + let terminals = self.get_selectable_panes(); let next_index = terminals .enumerate() .filter(|(_, (_, c))| { @@ -1578,7 +1595,7 @@ impl Tab { }) } fn panes_to_the_left_between_aligning_borders(&self, id: PaneId) -> Option> { - if let Some(terminal) = &self.panes.get(&id) { + if let Some(terminal) = self.panes.get(&id) { let upper_close_border = terminal.y(); let lower_close_border = terminal.y() + terminal.rows() + 1; @@ -1601,7 +1618,7 @@ impl Tab { None } fn panes_to_the_right_between_aligning_borders(&self, id: PaneId) -> Option> { - if let Some(terminal) = &self.panes.get(&id) { + if let Some(terminal) = self.panes.get(&id) { let upper_close_border = terminal.y(); let lower_close_border = terminal.y() + terminal.rows() + 1; @@ -1625,7 +1642,7 @@ impl Tab { None } fn panes_above_between_aligning_borders(&self, id: PaneId) -> Option> { - if let Some(terminal) = &self.panes.get(&id) { + if let Some(terminal) = self.panes.get(&id) { let left_close_border = terminal.x(); let right_close_border = terminal.x() + terminal.columns() + 1; @@ -1647,8 +1664,8 @@ impl Tab { } None } - fn terminals_below_between_aligning_borders(&self, id: PaneId) -> Option> { - if let Some(terminal) = &self.panes.get(&id) { + fn panes_below_between_aligning_borders(&self, id: PaneId) -> Option> { + if let Some(terminal) = self.panes.get(&id) { let left_close_border = terminal.x(); let right_close_border = terminal.x() + terminal.columns() + 1; @@ -1681,9 +1698,17 @@ impl Tab { } } } - pub fn get_pane_ids(&mut self) -> Vec { + pub fn get_pane_ids(&self) -> Vec { self.get_panes().map(|(&pid, _)| pid).collect() } + pub fn set_pane_selectable(&mut self, id: PaneId, selectable: bool) { + if let Some(pane) = self.panes.get_mut(&id) { + pane.set_selectable(selectable); + if self.get_active_pane_id() == Some(id) && !selectable { + self.active_terminal = self.next_active_pane(self.get_pane_ids()) + } + } + } pub fn close_pane(&mut self, id: PaneId) { if self.panes.get(&id).is_some() { self.close_pane_without_rerender(id); @@ -1699,7 +1724,7 @@ impl Tab { // 1 for the border } if self.active_terminal == Some(id) { - self.active_terminal = Some(*terminals.last().unwrap()); + self.active_terminal = self.next_active_pane(terminals); } } else if let Some(terminals) = self.panes_to_the_right_between_aligning_borders(id) { for terminal_id in terminals.iter() { @@ -1707,7 +1732,7 @@ impl Tab { // 1 for the border } if self.active_terminal == Some(id) { - self.active_terminal = Some(*terminals.last().unwrap()); + self.active_terminal = self.next_active_pane(terminals); } } else if let Some(terminals) = self.panes_above_between_aligning_borders(id) { for terminal_id in terminals.iter() { @@ -1715,21 +1740,21 @@ impl Tab { // 1 for the border } if self.active_terminal == Some(id) { - self.active_terminal = Some(*terminals.last().unwrap()); + self.active_terminal = self.next_active_pane(terminals); } - } else if let Some(terminals) = self.terminals_below_between_aligning_borders(id) { + } else if let Some(terminals) = self.panes_below_between_aligning_borders(id) { for terminal_id in terminals.iter() { self.increase_pane_height_up(&terminal_id, terminal_to_close_height + 1); // 1 for the border } if self.active_terminal == Some(id) { - self.active_terminal = Some(*terminals.last().unwrap()); + self.active_terminal = self.next_active_pane(terminals); } } else { } self.panes.remove(&id); - if !self.has_panes() { - self.active_terminal = None; + if self.active_terminal.is_none() { + self.active_terminal = self.next_active_pane(self.get_pane_ids()); } } } diff --git a/src/tests/integration/layouts.rs b/src/tests/integration/layouts.rs index 35ff7188..2086ac72 100644 --- a/src/tests/integration/layouts.rs +++ b/src/tests/integration/layouts.rs @@ -50,43 +50,3 @@ pub fn accepts_basic_layout() { assert_snapshot!(next_to_last_snapshot); assert_snapshot!(last_snapshot); } - -#[test] -#[should_panic(expected = "The total percent for each part should equal 100.")] -pub fn should_throw_for_more_than_100_percent_total() { - let fake_win_size = PositionAndSize { - columns: 121, - rows: 20, - x: 0, - y: 0, - }; - let mut fake_input_output = get_fake_os_input(&fake_win_size); - fake_input_output.add_terminal_input(&[&QUIT]); - - let mut opts = CliArgs::default(); - opts.layout = Some(PathBuf::from( - "src/tests/fixtures/layouts/parts-total-more-than-100-percent.yaml", - )); - - start(Box::new(fake_input_output.clone()), opts); -} - -#[test] -#[should_panic(expected = "The total percent for each part should equal 100.")] -pub fn should_throw_for_less_than_100_percent_total() { - let fake_win_size = PositionAndSize { - columns: 121, - rows: 20, - x: 0, - y: 0, - }; - let mut fake_input_output = get_fake_os_input(&fake_win_size); - fake_input_output.add_terminal_input(&[&QUIT]); - - let mut opts = CliArgs::default(); - opts.layout = Some(PathBuf::from( - "src/tests/fixtures/layouts/parts-total-less-than-100-percent.yaml", - )); - - start(Box::new(fake_input_output.clone()), opts); -} diff --git a/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__fish_paste_multiline-2.snap b/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__fish_paste_multiline-2.snap index d1c18eb2..60dc4f7d 100644 --- a/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__fish_paste_multiline-2.snap +++ b/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__fish_paste_multiline-2.snap @@ -21,7 +21,6 @@ df: /run/user/1000/doc: Operation not permitted awk '{printf "\\\\t%s\\\\t%4s / %4s %s\\\\n\n", $6, $3, $2, $5}' | \ sed -e 's/^\(.*\([8][5-9]\|[9][0-9]\)%.*\)$/\\\\e[0;31m\1\\\\e[0m/' -e 's/^\(.*\([7][5-9]\|[8][0-4]\)%.*\ )$/\\\\e[0;33m\1\\\\e[0m/' | \ -) paste -sd ''\ )█ @@ -30,3 +29,4 @@ df: /run/user/1000/doc: Operation not permitted + diff --git a/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__fish_paste_multiline-3.snap b/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__fish_paste_multiline-3.snap index a1cb439e..0e4b997f 100644 --- a/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__fish_paste_multiline-3.snap +++ b/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__fish_paste_multiline-3.snap @@ -20,7 +20,6 @@ df: /run/user/1000/doc: Operation not permitted awk '{printf "\\\\t%s\\\\t%4s / %4s %s\\\\n\n", $6, $3, $2, $5}' | \ sed -e 's/^\(.*\([8][5-9]\|[9][0-9]\)%.*\)$/\\\\e[0;31m\1\\\\e[0m/' -e 's/^\(.*\([7][5-9]\|[8][0-4]\)%.*\ )$/\\\\e[0;33m\1\\\\e[0m/' | \ -) paste -sd ''\ ) @@ -29,4 +28,5 @@ df: /run/user/1000/doc: Operation not permitted + Bye from Mosaic!█ diff --git a/src/tests/tty_inputs.rs b/src/tests/tty_inputs.rs index 387724cc..2f57cf9f 100644 --- a/src/tests/tty_inputs.rs +++ b/src/tests/tty_inputs.rs @@ -1,389 +1,389 @@ pub const COL_121: [&str; 20] = [ - "line1-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n", - "line2-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n", - "line3-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n", - "line4-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n", - "line5-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n", - "line6-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n", - "line7-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n", - "line8-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n", - "line9-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n", - "line10-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n", - "line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n", - "line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n", - "line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n", - "line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n", - "line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n", - "line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n", - "line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n", - "line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n", - "line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n", + "line1-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n", + "line2-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n", + "line3-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n", + "line4-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n", + "line5-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n", + "line6-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n", + "line7-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n", + "line8-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n", + "line9-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n", + "line10-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n", + "line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n", + "line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n", + "line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n", + "line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n", + "line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n", + "line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n", + "line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n", + "line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n", + "line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n", "prompt $ ", ]; pub const COL_10: [&str; 20] = [ - "line1-bbbb\n", - "line2-bbbb\n", - "line3-bbbb\n", - "line4-bbbb\n", - "line5-bbbb\n", - "line6-bbbb\n", - "line7-bbbb\n", - "line8-bbbb\n", - "line9-bbbb\n", - "line10-bbb\n", - "line11-bbb\n", - "line12-bbb\n", - "line13-bbb\n", - "line14-bbb\n", - "line15-bbb\n", - "line16-bbb\n", - "line17-bbb\n", - "line18-bbb\n", - "line19-bbb\n", + "line1-bbbb\r\n", + "line2-bbbb\r\n", + "line3-bbbb\r\n", + "line4-bbbb\r\n", + "line5-bbbb\r\n", + "line6-bbbb\r\n", + "line7-bbbb\r\n", + "line8-bbbb\r\n", + "line9-bbbb\r\n", + "line10-bbb\r\n", + "line11-bbb\r\n", + "line12-bbb\r\n", + "line13-bbb\r\n", + "line14-bbb\r\n", + "line15-bbb\r\n", + "line16-bbb\r\n", + "line17-bbb\r\n", + "line18-bbb\r\n", + "line19-bbb\r\n", "prompt $ ", ]; pub const COL_14: [&str; 20] = [ - "line1-bbbbbbbb\n", - "line2-bbbbbbbb\n", - "line3-bbbbbbbb\n", - "line4-bbbbbbbb\n", - "line5-bbbbbbbb\n", - "line6-bbbbbbbb\n", - "line7-bbbbbbbb\n", - "line8-bbbbbbbb\n", - "line9-bbbbbbbb\n", - "line10-bbbbbbb\n", - "line11-bbbbbbb\n", - "line12-bbbbbbb\n", - "line13-bbbbbbb\n", - "line14-bbbbbbb\n", - "line15-bbbbbbb\n", - "line16-bbbbbbb\n", - "line17-bbbbbbb\n", - "line18-bbbbbbb\n", - "line19-bbbbbbb\n", + "line1-bbbbbbbb\r\n", + "line2-bbbbbbbb\r\n", + "line3-bbbbbbbb\r\n", + "line4-bbbbbbbb\r\n", + "line5-bbbbbbbb\r\n", + "line6-bbbbbbbb\r\n", + "line7-bbbbbbbb\r\n", + "line8-bbbbbbbb\r\n", + "line9-bbbbbbbb\r\n", + "line10-bbbbbbb\r\n", + "line11-bbbbbbb\r\n", + "line12-bbbbbbb\r\n", + "line13-bbbbbbb\r\n", + "line14-bbbbbbb\r\n", + "line15-bbbbbbb\r\n", + "line16-bbbbbbb\r\n", + "line17-bbbbbbb\r\n", + "line18-bbbbbbb\r\n", + "line19-bbbbbbb\r\n", "prompt $ ", ]; pub const COL_15: [&str; 20] = [ - "line1-bbbbbbbbb\n", - "line2-bbbbbbbbb\n", - "line3-bbbbbbbbb\n", - "line4-bbbbbbbbb\n", - "line5-bbbbbbbbb\n", - "line6-bbbbbbbbb\n", - "line7-bbbbbbbbb\n", - "line8-bbbbbbbbb\n", - "line9-bbbbbbbbb\n", - "line10-bbbbbbbb\n", - "line11-bbbbbbbb\n", - "line12-bbbbbbbb\n", - "line13-bbbbbbbb\n", - "line14-bbbbbbbb\n", - "line15-bbbbbbbb\n", - "line16-bbbbbbbb\n", - "line17-bbbbbbbb\n", - "line18-bbbbbbbb\n", - "line19-bbbbbbbb\n", + "line1-bbbbbbbbb\r\n", + "line2-bbbbbbbbb\r\n", + "line3-bbbbbbbbb\r\n", + "line4-bbbbbbbbb\r\n", + "line5-bbbbbbbbb\r\n", + "line6-bbbbbbbbb\r\n", + "line7-bbbbbbbbb\r\n", + "line8-bbbbbbbbb\r\n", + "line9-bbbbbbbbb\r\n", + "line10-bbbbbbbb\r\n", + "line11-bbbbbbbb\r\n", + "line12-bbbbbbbb\r\n", + "line13-bbbbbbbb\r\n", + "line14-bbbbbbbb\r\n", + "line15-bbbbbbbb\r\n", + "line16-bbbbbbbb\r\n", + "line17-bbbbbbbb\r\n", + "line18-bbbbbbbb\r\n", + "line19-bbbbbbbb\r\n", "prompt $ ", ]; pub const COL_19: [&str; 20] = [ - "line1-bbbbbbbbbbbbb\n", - "line2-bbbbbbbbbbbbb\n", - "line3-bbbbbbbbbbbbb\n", - "line4-bbbbbbbbbbbbb\n", - "line5-bbbbbbbbbbbbb\n", - "line6-bbbbbbbbbbbbb\n", - "line7-bbbbbbbbbbbbb\n", - "line8-bbbbbbbbbbbbb\n", - "line9-bbbbbbbbbbbbb\n", - "line10-bbbbbbbbbbbb\n", - "line11-bbbbbbbbbbbb\n", - "line12-bbbbbbbbbbbb\n", - "line13-bbbbbbbbbbbb\n", - "line14-bbbbbbbbbbbb\n", - "line15-bbbbbbbbbbbb\n", - "line16-bbbbbbbbbbbb\n", - "line17-bbbbbbbbbbbb\n", - "line18-bbbbbbbbbbbb\n", - "line19-bbbbbbbbbbbb\n", + "line1-bbbbbbbbbbbbb\r\n", + "line2-bbbbbbbbbbbbb\r\n", + "line3-bbbbbbbbbbbbb\r\n", + "line4-bbbbbbbbbbbbb\r\n", + "line5-bbbbbbbbbbbbb\r\n", + "line6-bbbbbbbbbbbbb\r\n", + "line7-bbbbbbbbbbbbb\r\n", + "line8-bbbbbbbbbbbbb\r\n", + "line9-bbbbbbbbbbbbb\r\n", + "line10-bbbbbbbbbbbb\r\n", + "line11-bbbbbbbbbbbb\r\n", + "line12-bbbbbbbbbbbb\r\n", + "line13-bbbbbbbbbbbb\r\n", + "line14-bbbbbbbbbbbb\r\n", + "line15-bbbbbbbbbbbb\r\n", + "line16-bbbbbbbbbbbb\r\n", + "line17-bbbbbbbbbbbb\r\n", + "line18-bbbbbbbbbbbb\r\n", + "line19-bbbbbbbbbbbb\r\n", "prompt $ ", ]; pub const COL_20: [&str; 20] = [ - "line1-bbbbbbbbbbbbbb\n", - "line2-bbbbbbbbbbbbbb\n", - "line3-bbbbbbbbbbbbbb\n", - "line4-bbbbbbbbbbbbbb\n", - "line5-bbbbbbbbbbbbbb\n", - "line6-bbbbbbbbbbbbbb\n", - "line7-bbbbbbbbbbbbbb\n", - "line8-bbbbbbbbbbbbbb\n", - "line9-bbbbbbbbbbbbbb\n", - "line10-bbbbbbbbbbbbb\n", - "line11-bbbbbbbbbbbbb\n", - "line12-bbbbbbbbbbbbb\n", - "line13-bbbbbbbbbbbbb\n", - "line14-bbbbbbbbbbbbb\n", - "line15-bbbbbbbbbbbbb\n", - "line16-bbbbbbbbbbbbb\n", - "line17-bbbbbbbbbbbbb\n", - "line18-bbbbbbbbbbbbb\n", - "line19-bbbbbbbbbbbbb\n", + "line1-bbbbbbbbbbbbbb\r\n", + "line2-bbbbbbbbbbbbbb\r\n", + "line3-bbbbbbbbbbbbbb\r\n", + "line4-bbbbbbbbbbbbbb\r\n", + "line5-bbbbbbbbbbbbbb\r\n", + "line6-bbbbbbbbbbbbbb\r\n", + "line7-bbbbbbbbbbbbbb\r\n", + "line8-bbbbbbbbbbbbbb\r\n", + "line9-bbbbbbbbbbbbbb\r\n", + "line10-bbbbbbbbbbbbb\r\n", + "line11-bbbbbbbbbbbbb\r\n", + "line12-bbbbbbbbbbbbb\r\n", + "line13-bbbbbbbbbbbbb\r\n", + "line14-bbbbbbbbbbbbb\r\n", + "line15-bbbbbbbbbbbbb\r\n", + "line16-bbbbbbbbbbbbb\r\n", + "line17-bbbbbbbbbbbbb\r\n", + "line18-bbbbbbbbbbbbb\r\n", + "line19-bbbbbbbbbbbbb\r\n", "prompt $ ", ]; pub const COL_24: [&str; 20] = [ - "line1-bbbbbbbbbbbbbbbbbb\n", - "line2-bbbbbbbbbbbbbbbbbb\n", - "line3-bbbbbbbbbbbbbbbbbb\n", - "line4-bbbbbbbbbbbbbbbbbb\n", - "line5-bbbbbbbbbbbbbbbbbb\n", - "line6-bbbbbbbbbbbbbbbbbb\n", - "line7-bbbbbbbbbbbbbbbbbb\n", - "line8-bbbbbbbbbbbbbbbbbb\n", - "line9-bbbbbbbbbbbbbbbbbb\n", - "line10-bbbbbbbbbbbbbbbbb\n", - "line11-bbbbbbbbbbbbbbbbb\n", - "line12-bbbbbbbbbbbbbbbbb\n", - "line13-bbbbbbbbbbbbbbbbb\n", - "line14-bbbbbbbbbbbbbbbbb\n", - "line15-bbbbbbbbbbbbbbbbb\n", - "line16-bbbbbbbbbbbbbbbbb\n", - "line17-bbbbbbbbbbbbbbbbb\n", - "line18-bbbbbbbbbbbbbbbbb\n", - "line19-bbbbbbbbbbbbbbbbb\n", + "line1-bbbbbbbbbbbbbbbbbb\r\n", + "line2-bbbbbbbbbbbbbbbbbb\r\n", + "line3-bbbbbbbbbbbbbbbbbb\r\n", + "line4-bbbbbbbbbbbbbbbbbb\r\n", + "line5-bbbbbbbbbbbbbbbbbb\r\n", + "line6-bbbbbbbbbbbbbbbbbb\r\n", + "line7-bbbbbbbbbbbbbbbbbb\r\n", + "line8-bbbbbbbbbbbbbbbbbb\r\n", + "line9-bbbbbbbbbbbbbbbbbb\r\n", + "line10-bbbbbbbbbbbbbbbbb\r\n", + "line11-bbbbbbbbbbbbbbbbb\r\n", + "line12-bbbbbbbbbbbbbbbbb\r\n", + "line13-bbbbbbbbbbbbbbbbb\r\n", + "line14-bbbbbbbbbbbbbbbbb\r\n", + "line15-bbbbbbbbbbbbbbbbb\r\n", + "line16-bbbbbbbbbbbbbbbbb\r\n", + "line17-bbbbbbbbbbbbbbbbb\r\n", + "line18-bbbbbbbbbbbbbbbbb\r\n", + "line19-bbbbbbbbbbbbbbbbb\r\n", "prompt $ ", ]; pub const COL_29: [&str; 20] = [ - "line1-bbbbbbbbbbbbbbbbbbbbbbb\n", - "line2-bbbbbbbbbbbbbbbbbbbbbbb\n", - "line3-bbbbbbbbbbbbbbbbbbbbbbb\n", - "line4-bbbbbbbbbbbbbbbbbbbbbbb\n", - "line5-bbbbbbbbbbbbbbbbbbbbbbb\n", - "line6-bbbbbbbbbbbbbbbbbbbbbbb\n", - "line7-bbbbbbbbbbbbbbbbbbbbbbb\n", - "line8-bbbbbbbbbbbbbbbbbbbbbbb\n", - "line9-bbbbbbbbbbbbbbbbbbbbbbb\n", - "line10-bbbbbbbbbbbbbbbbbbbbbb\n", - "line11-bbbbbbbbbbbbbbbbbbbbbb\n", - "line12-bbbbbbbbbbbbbbbbbbbbbb\n", - "line13-bbbbbbbbbbbbbbbbbbbbbb\n", - "line14-bbbbbbbbbbbbbbbbbbbbbb\n", - "line15-bbbbbbbbbbbbbbbbbbbbbb\n", - "line16-bbbbbbbbbbbbbbbbbbbbbb\n", - "line17-bbbbbbbbbbbbbbbbbbbbbb\n", - "line18-bbbbbbbbbbbbbbbbbbbbbb\n", - "line19-bbbbbbbbbbbbbbbbbbbbbb\n", + "line1-bbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line2-bbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line3-bbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line4-bbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line5-bbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line6-bbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line7-bbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line8-bbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line9-bbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line10-bbbbbbbbbbbbbbbbbbbbbb\r\n", + "line11-bbbbbbbbbbbbbbbbbbbbbb\r\n", + "line12-bbbbbbbbbbbbbbbbbbbbbb\r\n", + "line13-bbbbbbbbbbbbbbbbbbbbbb\r\n", + "line14-bbbbbbbbbbbbbbbbbbbbbb\r\n", + "line15-bbbbbbbbbbbbbbbbbbbbbb\r\n", + "line16-bbbbbbbbbbbbbbbbbbbbbb\r\n", + "line17-bbbbbbbbbbbbbbbbbbbbbb\r\n", + "line18-bbbbbbbbbbbbbbbbbbbbbb\r\n", + "line19-bbbbbbbbbbbbbbbbbbbbbb\r\n", "prompt $ ", ]; pub const COL_30: [&str; 20] = [ - "line1-bbbbbbbbbbbbbbbbbbbbbbbb\n", - "line2-bbbbbbbbbbbbbbbbbbbbbbbb\n", - "line3-bbbbbbbbbbbbbbbbbbbbbbbb\n", - "line4-bbbbbbbbbbbbbbbbbbbbbbbb\n", - "line5-bbbbbbbbbbbbbbbbbbbbbbbb\n", - "line6-bbbbbbbbbbbbbbbbbbbbbbbb\n", - "line7-bbbbbbbbbbbbbbbbbbbbbbbb\n", - "line8-bbbbbbbbbbbbbbbbbbbbbbbb\n", - "line9-bbbbbbbbbbbbbbbbbbbbbbbb\n", - "line10-bbbbbbbbbbbbbbbbbbbbbbb\n", - "line11-bbbbbbbbbbbbbbbbbbbbbbb\n", - "line12-bbbbbbbbbbbbbbbbbbbbbbb\n", - "line13-bbbbbbbbbbbbbbbbbbbbbbb\n", - "line14-bbbbbbbbbbbbbbbbbbbbbbb\n", - "line15-bbbbbbbbbbbbbbbbbbbbbbb\n", - "line16-bbbbbbbbbbbbbbbbbbbbbbb\n", - "line17-bbbbbbbbbbbbbbbbbbbbbbb\n", - "line18-bbbbbbbbbbbbbbbbbbbbbbb\n", - "line19-bbbbbbbbbbbbbbbbbbbbbbb\n", + "line1-bbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line2-bbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line3-bbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line4-bbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line5-bbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line6-bbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line7-bbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line8-bbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line9-bbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line10-bbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line11-bbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line12-bbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line13-bbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line14-bbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line15-bbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line16-bbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line17-bbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line18-bbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line19-bbbbbbbbbbbbbbbbbbbbbbb\r\n", "prompt $ ", ]; pub const COL_34: [&str; 20] = [ - "line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line10-bbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line11-bbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line12-bbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line13-bbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line14-bbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line15-bbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line16-bbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line17-bbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line18-bbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line19-bbbbbbbbbbbbbbbbbbbbbbbbbbb\n", + "line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line10-bbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line11-bbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line12-bbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line13-bbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line14-bbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line15-bbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line16-bbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line17-bbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line18-bbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line19-bbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", "prompt $ ", ]; pub const COL_40: [&str; 20] = [ - "line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", + "line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", "prompt $ ", ]; pub const COL_39: [&str; 20] = [ - "line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", + "line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", "prompt $ ", ]; pub const COL_50: [&str; 20] = [ - "line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", + "line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", "prompt $ ", ]; pub const COL_60: [&str; 20] = [ - "line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", + "line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", "prompt $ ", ]; pub const COL_70: [&str; 20] = [ - "line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", + "line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", "prompt $ ", ]; pub const COL_90: [&str; 20] = [ - "line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", + "line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", "prompt $ ", ]; pub const COL_96: [&str; 20] = [ - "line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", - "line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", + "line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", + "line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n", "prompt $ ", ]; diff --git a/src/utils/consts.rs b/src/utils/consts.rs index 5aa6aab4..7a0c9ddf 100644 --- a/src/utils/consts.rs +++ b/src/utils/consts.rs @@ -2,3 +2,5 @@ pub const MOSAIC_TMP_DIR: &str = "/tmp/mosaic"; pub const MOSAIC_TMP_LOG_DIR: &str = "/tmp/mosaic/mosaic-log"; pub const MOSAIC_TMP_LOG_FILE: &str = "/tmp/mosaic/mosaic-log/log.txt"; pub const MOSAIC_IPC_PIPE: &str = "/tmp/mosaic/ipc"; +pub const MOSAIC_ROOT_PLUGIN_DIR: &str = "/usr/share/mosaic/plugins"; +pub const MOSAIC_ROOT_LAYOUT_DIR: &str = "/usr/share/mosaic/layouts"; diff --git a/src/wasm_vm.rs b/src/wasm_vm.rs index 51bd9375..97a1a587 100644 --- a/src/wasm_vm.rs +++ b/src/wasm_vm.rs @@ -1,20 +1,30 @@ -use std::{path::PathBuf, sync::mpsc::Sender}; +use std::{ + path::PathBuf, + sync::mpsc::{channel, Sender}, +}; use wasmer::{imports, Function, ImportObject, Store, WasmerEnv}; use wasmer_wasi::WasiEnv; -use crate::{pty_bus::PtyInstruction, SenderWithContext}; +use crate::{ + input::get_help, panes::PaneId, pty_bus::PtyInstruction, screen::ScreenInstruction, + AppInstruction, SenderWithContext, +}; #[derive(Clone, Debug)] pub enum PluginInstruction { Load(Sender, PathBuf), Draw(Sender, u32, usize, usize), // String buffer, plugin id, rows, cols Input(u32, Vec), // plugin id, input bytes + GlobalInput(Vec), // input bytes Unload(u32), Quit, } #[derive(WasmerEnv, Clone)] pub struct PluginEnv { + pub plugin_id: u32, + pub send_screen_instructions: SenderWithContext, + pub send_app_instructions: SenderWithContext, pub send_pty_instructions: SenderWithContext, // FIXME: This should be a big bundle of all of the channels pub wasi_env: WasiEnv, } @@ -24,7 +34,9 @@ pub struct PluginEnv { pub fn mosaic_imports(store: &Store, plugin_env: &PluginEnv) -> ImportObject { imports! { "mosaic" => { - "host_open_file" => Function::new_native_with_env(store, plugin_env.clone(), host_open_file) + "host_open_file" => Function::new_native_with_env(store, plugin_env.clone(), host_open_file), + "host_set_selectable" => Function::new_native_with_env(store, plugin_env.clone(), host_set_selectable), + "host_get_help" => Function::new_native_with_env(store, plugin_env.clone(), host_get_help), } } } @@ -38,6 +50,33 @@ fn host_open_file(plugin_env: &PluginEnv) { .unwrap(); } +// FIXME: Think about these naming conventions – should everything be prefixed by 'host'? +fn host_set_selectable(plugin_env: &PluginEnv, selectable: i32) { + let selectable = selectable != 0; + plugin_env + .send_screen_instructions + .send(ScreenInstruction::SetSelectable( + PaneId::Plugin(plugin_env.plugin_id), + selectable, + )) + .unwrap() +} + +fn host_get_help(plugin_env: &PluginEnv) { + let (state_tx, state_rx) = channel(); + // FIXME: If I changed the application so that threads were sent the termination + // signal and joined one at a time, there would be an order to shutdown, so I + // could get rid of this .is_ok() check and the .try_send() + if plugin_env + .send_app_instructions + .try_send(AppInstruction::GetState(state_tx)) + .is_ok() + { + let help = get_help(&state_rx.recv().unwrap().input_mode); + wasi_write_string(&plugin_env.wasi_env, &serde_json::to_string(&help).unwrap()); + } +} + // Helper Functions --------------------------------------------------------------------------------------------------- // FIXME: Unwrap city