aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authordenis <denismaximov98@gmail.com>2021-01-22 08:24:56 +0200
committerdenis <denismaximov98@gmail.com>2021-01-22 08:24:56 +0200
commitbd1e3d2d7b25bf3d5f0feb77801c18e70da1c936 (patch)
treeecddbd0f4dacd009ff0868922fbc2f824258648d /src
parentAdd some message types for server-client comms (diff)
parentfeat(plugin): Search in root folder for plugins and layout files (diff)
downloadsl0ppij-bd1e3d2d7b25bf3d5f0feb77801c18e70da1c936.tar.gz
sl0ppij-bd1e3d2d7b25bf3d5f0feb77801c18e70da1c936.tar.xz
sl0ppij-bd1e3d2d7b25bf3d5f0feb77801c18e70da1c936.zip
Merge branch 'main' into server-client
Diffstat (limited to 'src')
-rw-r--r--src/errors.rs8
-rw-r--r--src/input.rs67
-rw-r--r--src/layout.rs191
-rw-r--r--src/main.rs649
-rw-r--r--src/panes/grid.rs820
-rw-r--r--src/panes/mod.rs4
-rw-r--r--src/panes/plugin_pane.rs8
-rw-r--r--src/panes/scroll.rs838
-rw-r--r--src/panes/terminal_pane.rs133
-rw-r--r--src/pty_bus.rs12
-rw-r--r--src/screen.rs11
-rw-r--r--src/tab.rs71
-rw-r--r--src/tests/fixtures/layouts/panes-with-plugins.yaml17
-rw-r--r--src/tests/integration/layouts.rs40
-rw-r--r--src/tests/integration/snapshots/mosaic__tests__integration__compatibility__fish_paste_multiline-2.snap2
-rw-r--r--src/tests/integration/snapshots/mosaic__tests__integration__compatibility__fish_paste_multiline-3.snap2
-rw-r--r--src/tests/tty_inputs.rs646
-rw-r--r--src/utils/consts.rs2
-rw-r--r--src/wasm_vm.rs45
19 files changed, 1883 insertions, 1683 deletions
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<ScreenInstruction>,
send_pty_instructions: SenderWithContext<PtyInstruction>,
+ send_plugin_instructions: SenderWithContext<PluginInstruction>,
send_app_instructions: SenderWithContext<AppInstruction>,
}
@@ -21,6 +21,7 @@ impl InputHandler {
command_is_executing: CommandIsExecuting,
send_screen_instructions: SenderWithContext<ScreenInstruction>,
send_pty_instructions: SenderWithContext<PtyInstruction>,
+ send_plugin_instructions: SenderWithContext<PluginInstruction>,
send_app_instructions: SenderWithContext<AppInstruction>,
) -> 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<String> {
+ let command_help = vec![
+ "<n/b/z> Split".into(),
+ "<j/k/h/l> Resize".into(),
+ "<p> Focus Next".into(),
+ "<x> Close Pane".into(),
+ "<q> Quit".into(),
+ "<PgUp/PgDown> Scroll".into(),
+ "<1> New Tab".into(),
+ "<2/3> Move Tab".into(),
+ "<4> Close Tab".into(),
+ ];
+ match mode {
+ InputMode::Normal => vec!["<Ctrl-g> Command Mode".into()],
+ InputMode::Command => [
+ vec![
+ "<Ctrl-g> Persistent Mode".into(),
+ "<ESC> Normal Mode".into(),
+ ],
+ command_help,
+ ]
+ .concat(),
+ InputMode::CommandPersistent => {
+ [vec!["<ESC/Ctrl-g> 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<ScreenInstruction>,
send_pty_instructions: SenderWithContext<PtyInstruction>,
+ send_plugin_instructions: SenderWithContext<PluginInstruction>,
send_app_instructions: SenderWithContext<AppInstruction>,
) {
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<u8>,
+ sizes: Vec<Option<SplitSize>>,
) -> Vec<PositionAndSize> {
- 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<u8>,
+ sizes: Vec<Option<SplitSize>>,
) -> Vec<PositionAndSize> {
- 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<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");
- }
- }
- })
- .collect();
+ let sizes: Vec<Option<SplitSize>> = 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<T: Clone> SenderWithContext<T> {
}
}
+ 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<AppInstruction>,
+ 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<AppState>),
+ SetState(AppState),
Exit,
Error(String),
}
pub fn start(mut os_input: Box<dyn OsApi>, 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<dyn OsApi>, 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<dyn OsApi>, 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();
}
-
- 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;
- }
+ 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(),
- );
+ }
+ })
+ .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 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::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;
- }
+ 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("wasm".to_string())
- .spawn({
- let mut send_pty_instructions = send_pty_instructions.clone();
- let mut send_screen_instructions = send_screen_instructions.clone();
+ 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 store = Store::default();
+ let store = Store::default();
+ let mut plugin_id = 0;
+ let mut plugin_map = HashMap::new();
- 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()));
- 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();
+ // 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 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 wasi = wasi_env.import_object(&module).unwrap();
- let plugin_env = PluginEnv {
- send_pty_instructions: send_pty_instructions.clone(),
- wasi_env,
- };
+ 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 mosaic = mosaic_imports(&store, &plugin_env);
+ let instance = Instance::new(&module, &mosaic.chain_back(wasi)).unwrap();
- let start = instance.exports.get_function("_start").unwrap();
+ let start = instance.exports.get_function("_start").unwrap();
- // This eventually calls the `.init()` method
- start.call(&[]).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();
+ 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();
- let draw = instance.exports.get_function("draw").unwrap();
+ draw.call(&[Value::I32(rows as i32), Value::I32(cols as i32)])
+ .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();
- buf_tx.send(wasi_stdout(&plugin_env.wasi_env)).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::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));
+ }
+ 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,
}
+
+ drop(send_screen_instructions.send(ScreenInstruction::Render));
}
+ PluginInstruction::Unload(pid) => drop(plugin_map.remove(&pid)),
+ PluginInstruction::Quit => break,
}
- })
- .unwrap(),
- );
+ }
+ })
+ .unwrap();
- // TODO: currently we don't push this into active_threads
+ // 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<dyn OsApi>, 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<dyn OsApi>, 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<dyn OsApi>, 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<dyn OsApi>, 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<Row>) -> Vec<Row> {
+ 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<Row>) -> Vec<Row> {
+ 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<Row>,
+ destination: &mut Vec<Row>,
+ count: usize,
+ max_src_width: Option<usize>,
+ max_dst_width: Option<usize>,
+) {
+ let mut next_lines: Vec<Row> = 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<Row>,
+ destination: &mut Vec<Row>,
+ count: usize,
+ max_src_width: Option<usize>,
+ max_dst_width: Option<usize>,
+) {
+ let mut next_lines: Vec<Row> = 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<Row>,
+ viewport: Vec<Row>,
+ lines_below: Vec<Row>,
+ 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<Row> = 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<Vec<TerminalCharacter>> {
+ let mut lines: Vec<Vec<TerminalCharacter>> = self
+ .viewport
+ .iter()
+ .map(|r| {
+ let mut line: Vec<TerminalCharacter> = 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<TerminalCharacter>,
+ 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<TerminalCharacter>) -> Self {
+ Row {
+ columns,
+ is_canonical: false,
+ }
+ }
+ pub fn from_rows(mut rows: Vec<Row>) -> 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<TerminalCharacter>) {
+ 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<TerminalCharacter>) {
+ self.columns.append(to_append);
+ }
+ pub fn replace_beginning_with(&mut self, mut line_part: Vec<TerminalCharacter>) {
+ 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<Row> {
+ let mut parts: Vec<Row> = vec![];
+ let mut current_part: Vec<TerminalCharacter> = 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<PositionAndSize>,
pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
@@ -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<String> {
// 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<WrappedFragment>,
-}
-
-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<TerminalCharacter> {
- 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<TerminalCharacter>,
- width: usize,
- padding: Option<TerminalCharacter>,
- ) -> Vec<WrappedFragment> {
- 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<TerminalCharacter>,
-}
-
-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<TerminalCharacter>) -> 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<CanonicalLine>,
- cursor_position: CursorPosition,
- total_columns: usize,
- lines_in_view: usize,
- viewport_bottom_offset: Option<usize>,
- scroll_region: Option<(usize, usize)>, // start line, end line (if set, this is the area the will scroll)
- show_cursor: bool,
- alternative_buffer: Option<Vec<CanonicalLine>>,
- alternative_cursor_position: Option<CursorPosition>,
-}
-
-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<Vec<TerminalCharacter>> {
- let mut lines: VecDeque<Vec<TerminalCharacter>> = 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<TerminalCharacter> =
- 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<Winsize> for PositionAndSize {
#[derive(Debug)]
pub struct TerminalPane {
+ pub grid: Grid,
+ pub alternative_grid: Option<Grid>, // 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<PositionAndSize>,
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<u8>) -> Vec<u8> {
// 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<String> {
// 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<Vec<TerminalCharacter>> {
- 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<PaneId>) {
- 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<RawFd>)),
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<AppInstruction>,
}
+// 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<PositionAndSize>;
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<String>;
fn pid(&self) -> PaneId;
fn reduce_height_down(&mut self, count: usize);
@@ -618,10 +621,24 @@ impl Tab {
fn get_panes(&self) -> impl Iterator<Item = (&PaneId, &Box<dyn Pane>)> {
self.panes.iter()
}
+ // FIXME: This is some shameful duplication...
+ fn get_selectable_panes(&self) -> impl Iterator<Item = (&PaneId, &Box<dyn Pane>)> {
+ 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<PaneId>) -> Option<PaneId> {
+ panes
+ .into_iter()
+ .rev()
+ .find(|pid| self.panes.get(pid).unwrap().selectable())
+ }
fn pane_ids_directly_left_of(&self, id: &PaneId) -> Option<Vec<PaneId>> {
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<PaneId> = self.get_panes().map(|(&pid, _)| pid).collect(); // TODO: better, no allocations
+ let terminal_ids: Vec<PaneId> = 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<Vec<PaneId>> {
- 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<Vec<PaneId>> {
- 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<Vec<PaneId>> {
- 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<Vec<PaneId>> {
- if let Some(terminal) = &self.panes.get(&id) {
+ fn panes_below_between_aligning_borders(&self, id: PaneId) -> Option<Vec<PaneId>> {
+ 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<PaneId> {
+ pub fn get_pane_ids(&self) -> Vec<PaneId> {
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/fixtures/layouts/panes-with-plugins.yaml b/src/tests/fixtures/layouts/panes-with-plugins.yaml
deleted file mode 100644
index 7fb13b2e..00000000
--- a/src/tests/fixtures/layouts/panes-with-plugins.yaml
+++ /dev/null
@@ -1,17 +0,0 @@
----
-direction: Horizontal
-parts:
- - direction: Vertical
- parts:
- - direction: Horizontal
- split_size:
- Percent: 20
- plugin: strider.wasm
- - direction: Horizontal
- split_size:
- Percent: 80
- split_size:
- Percent: 80
- - direction: Vertical
- split_size:
- Percent: 20 \ No newline at end of file
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<u32>, PathBuf),
Draw(Sender<String>, u32, usize, usize), // String buffer, plugin id, rows, cols
Input(u32, Vec<u8>), // plugin id, input bytes
+ GlobalInput(Vec<u8>), // input bytes
Unload(u32),
Quit,
}
#[derive(WasmerEnv, Clone)]
pub struct PluginEnv {
+ pub plugin_id: u32,
+ pub send_screen_instructions: SenderWithContext<ScreenInstruction>,
+ pub send_app_instructions: SenderWithContext<AppInstruction>,
pub send_pty_instructions: SenderWithContext<PtyInstruction>, // 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