1426 lines
42 KiB
Rust
1426 lines
42 KiB
Rust
use std::{collections::HashMap, pin::pin, time::Duration};
|
|
|
|
use super::*;
|
|
use crate::{
|
|
ai::blocklist::agent_view::AgentViewState,
|
|
terminal::model::{
|
|
ansi::{Attr, Handler},
|
|
cell::Flags,
|
|
header_grid::PromptEndPoint,
|
|
session::SessionInfo,
|
|
test_utils::{create_test_block_with_grids, TestBlockBuilder},
|
|
},
|
|
test_util::mock_blockgrid,
|
|
};
|
|
use float_cmp::assert_approx_eq;
|
|
use futures_lite::stream::StreamExt;
|
|
|
|
impl float_cmp::ApproxEq for BlockSection {
|
|
type Margin = float_cmp::F64Margin;
|
|
|
|
fn approx_eq<M: Into<Self::Margin>>(self, other: Self, margin: M) -> bool {
|
|
// Use a minimum epsilon value of 3 * the smallest value that can be
|
|
// represented in an f64. This allows for a very small (but non-zero)
|
|
// amount of _accumulated_ floating-point error.
|
|
let mut margin: Self::Margin = margin.into();
|
|
if margin.epsilon < f64::EPSILON * 3. {
|
|
margin = margin.epsilon(f64::EPSILON * 3.);
|
|
}
|
|
|
|
match (self, other) {
|
|
(BlockSection::PromptAndCommandGrid(r1), BlockSection::PromptAndCommandGrid(r2)) => {
|
|
r1.approx_eq(r2, margin)
|
|
}
|
|
(BlockSection::OutputGrid(r1), BlockSection::OutputGrid(r2)) => {
|
|
r1.approx_eq(r2, margin)
|
|
}
|
|
(a, b) => std::mem::discriminant(&a) == std::mem::discriminant(&b),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
pub fn test_find() {
|
|
let mut block = TestBlockBuilder::new().build();
|
|
|
|
block.precmd(PrecmdValue::default());
|
|
block.start();
|
|
assert_lines_approx_eq!(block.height(&AgentViewState::Inactive), 3.);
|
|
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(Lines::zero()),
|
|
BlockSection::PaddingTop
|
|
);
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(0.5.into_lines()),
|
|
BlockSection::PromptAndCommandGrid(Lines::zero())
|
|
);
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(1.0.into_lines()),
|
|
BlockSection::PromptAndCommandGrid(Lines::zero())
|
|
);
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(1.5.into_lines()),
|
|
BlockSection::PromptAndCommandGrid(0.5.into_lines())
|
|
);
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(2.0.into_lines()),
|
|
BlockSection::PaddingBottom
|
|
);
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(3.5.into_lines()),
|
|
BlockSection::NotContained
|
|
);
|
|
|
|
// Add a few lines to the command grid.
|
|
block.header_grid.command_grid_linefeed();
|
|
block.header_grid.command_grid_linefeed();
|
|
block.header_grid.command_grid_linefeed();
|
|
|
|
assert_lines_approx_eq!(block.height(&AgentViewState::Inactive), 6.);
|
|
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(Lines::zero()),
|
|
BlockSection::PaddingTop
|
|
);
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(0.5.into_lines()),
|
|
BlockSection::PromptAndCommandGrid(Lines::zero())
|
|
);
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(1.0.into_lines()),
|
|
BlockSection::PromptAndCommandGrid(Lines::zero())
|
|
);
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(1.5.into_lines()),
|
|
BlockSection::PromptAndCommandGrid(0.5.into_lines())
|
|
);
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(2.01.into_lines()),
|
|
BlockSection::PromptAndCommandGrid(1.01.into_lines())
|
|
);
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(2.5.into_lines()),
|
|
BlockSection::PromptAndCommandGrid(1.5.into_lines())
|
|
);
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(3.5.into_lines()),
|
|
BlockSection::PromptAndCommandGrid(2.5.into_lines())
|
|
);
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(4.5.into_lines()),
|
|
BlockSection::PromptAndCommandGrid(3.5.into_lines())
|
|
);
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(5.0.into_lines()),
|
|
BlockSection::PaddingBottom
|
|
);
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(8.0.into_lines()),
|
|
BlockSection::NotContained
|
|
);
|
|
|
|
// Add lines to the output grid. The command grid will now shrink to be 3 lines and the
|
|
// output grid will be 2 lines.
|
|
block.header_grid.finish_command_grid();
|
|
block.output_grid.start();
|
|
block.output_grid.linefeed();
|
|
block.output_grid.linefeed();
|
|
block
|
|
.output_grid
|
|
.on_finish_byte_processing(&ansi::ProcessorInput::new(&[]));
|
|
|
|
assert_eq!(block.header_grid.prompt_and_command_number_of_rows(), 3);
|
|
assert_eq!(block.output_grid.len(), 3);
|
|
assert_lines_approx_eq!(block.height(&AgentViewState::Inactive), 8.5);
|
|
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(Lines::zero()),
|
|
BlockSection::PaddingTop
|
|
);
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(0.5.into_lines()),
|
|
BlockSection::PromptAndCommandGrid(Lines::zero())
|
|
);
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(0.8.into_lines()),
|
|
BlockSection::PromptAndCommandGrid(Lines::zero())
|
|
);
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(1.0.into_lines()),
|
|
BlockSection::PromptAndCommandGrid(Lines::zero())
|
|
);
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(1.5.into_lines()),
|
|
BlockSection::PromptAndCommandGrid(0.5.into_lines())
|
|
);
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(2.01.into_lines()),
|
|
BlockSection::PromptAndCommandGrid(1.01.into_lines())
|
|
);
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(2.5.into_lines()),
|
|
BlockSection::PromptAndCommandGrid(1.5.into_lines())
|
|
);
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(3.5.into_lines()),
|
|
BlockSection::PromptAndCommandGrid(2.5.into_lines())
|
|
);
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(4.0.into_lines()),
|
|
BlockSection::PaddingMiddle
|
|
);
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(4.5.into_lines()),
|
|
BlockSection::OutputGrid(Lines::zero())
|
|
);
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(5.5.into_lines()),
|
|
BlockSection::OutputGrid(1.into_lines())
|
|
);
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(6.0.into_lines()),
|
|
BlockSection::OutputGrid(1.5.into_lines())
|
|
);
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(6.5.into_lines()),
|
|
BlockSection::OutputGrid(2.into_lines())
|
|
);
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(7.5.into_lines()),
|
|
BlockSection::PaddingBottom
|
|
);
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(8.5.into_lines()),
|
|
BlockSection::NotContained
|
|
);
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(10.5.into_lines()),
|
|
BlockSection::NotContained
|
|
);
|
|
|
|
let mut block = TestBlockBuilder::new().build();
|
|
|
|
block.precmd(PrecmdValue::default());
|
|
block.start();
|
|
|
|
block.header_grid.command_grid_linefeed();
|
|
block.header_grid.command_grid_linefeed();
|
|
block.header_grid.finish_command_grid();
|
|
|
|
block.output_grid.start();
|
|
block.output_grid.linefeed();
|
|
block.output_grid.linefeed();
|
|
block
|
|
.output_grid
|
|
.on_finish_byte_processing(&ansi::ProcessorInput::new(&[]));
|
|
|
|
assert_eq!(block.header_grid.prompt_and_command_number_of_rows(), 2);
|
|
assert_eq!(block.output_grid.len(), 3);
|
|
assert_lines_approx_eq!(block.height(&AgentViewState::Inactive), 7.5);
|
|
|
|
assert_approx_eq!(
|
|
BlockSection,
|
|
block.find(3.5.into_lines()),
|
|
BlockSection::OutputGrid(Lines::zero())
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
pub fn test_long_running_block_bottom_padding() {
|
|
galaxyui::r#async::block_on(async {
|
|
let mut block = TestBlockBuilder::new().build();
|
|
|
|
block.precmd(Default::default());
|
|
block.start();
|
|
for c in "command".chars() {
|
|
block.input(c);
|
|
}
|
|
block.preexec(Default::default());
|
|
for c in "output".chars() {
|
|
block.input(c);
|
|
}
|
|
|
|
// Initially, the padding is still there because we aren't yet in long-running mode
|
|
assert!(!block.is_active_and_long_running());
|
|
assert!(block.padding_bottom() > LONG_RUNNING_BOTTOM_PADDING_LINES.into_lines());
|
|
|
|
// After the long running duration, the block should switch
|
|
let duration = LONG_RUNNING_COMMAND_DURATION_MS + 1;
|
|
galaxyui::r#async::Timer::after(Duration::from_millis(duration)).await;
|
|
|
|
assert!(block.is_active_and_long_running());
|
|
assert!(block.padding_bottom() == LONG_RUNNING_BOTTOM_PADDING_LINES.into_lines());
|
|
});
|
|
}
|
|
|
|
// Tests that the command grid has a non-zero height even if `preexec` is never called.
|
|
#[test]
|
|
pub fn test_precmd_no_preexec() {
|
|
let mut block = TestBlockBuilder::new().build();
|
|
block.start();
|
|
block.precmd(PrecmdValue::default());
|
|
|
|
for c in "command".chars() {
|
|
block.input(c);
|
|
}
|
|
block.carriage_return();
|
|
block.linefeed();
|
|
|
|
for c in "error".chars() {
|
|
block.input(c);
|
|
}
|
|
block.finish(0);
|
|
|
|
assert_eq!("command\nerror", block.contents_to_string());
|
|
}
|
|
|
|
// Tests that the command grid text is all bolded.
|
|
#[test]
|
|
pub fn test_command_grid_bold() {
|
|
let mut block = TestBlockBuilder::new().build();
|
|
block.start();
|
|
block.precmd(PrecmdValue::default());
|
|
|
|
// We should have the BOLD flag enabled for commands, once we've started the command grid.
|
|
assert!(block
|
|
.header_grid
|
|
.command_cursor_flags()
|
|
.contains(Flags::BOLD));
|
|
|
|
for c in "command".chars() {
|
|
block.input(c);
|
|
}
|
|
|
|
block.finish(0);
|
|
|
|
assert_eq!("command", block.contents_to_string());
|
|
}
|
|
|
|
// Tests that the command grid text is still bolded after RESET attribute.
|
|
#[test]
|
|
pub fn test_command_grid_bold_after_reset() {
|
|
let mut block = TestBlockBuilder::new().build();
|
|
block.start();
|
|
block.precmd(PrecmdValue::default());
|
|
|
|
// We should have the BOLD flag enabled for commands, once we've started the command grid.
|
|
assert!(block
|
|
.header_grid
|
|
.command_cursor_flags()
|
|
.contains(Flags::BOLD));
|
|
|
|
for c in "command".chars() {
|
|
block.input(c);
|
|
}
|
|
// Even after a Reset, we should still re-enable the BOLD flag.
|
|
block.terminal_attribute(Attr::Reset);
|
|
assert!(block
|
|
.header_grid
|
|
.command_cursor_flags()
|
|
.contains(Flags::BOLD));
|
|
|
|
block.finish(0);
|
|
|
|
assert_eq!("command", block.contents_to_string());
|
|
}
|
|
|
|
// Tests that the command grid has non-zero height even if the shell does not echo a linefeed
|
|
// after the user inputs an empty command (this happens in recent Bash versions)
|
|
#[test]
|
|
pub fn test_empty_command() {
|
|
let mut block = TestBlockBuilder::new().build();
|
|
block.start();
|
|
block.precmd(PrecmdValue::default());
|
|
|
|
block.finish(0);
|
|
|
|
assert!(!block.is_command_empty());
|
|
}
|
|
|
|
#[test]
|
|
pub fn test_failed_block() {
|
|
let mut block = TestBlockBuilder::new().build();
|
|
|
|
block.precmd(PrecmdValue::default());
|
|
block.preexec(Default::default());
|
|
|
|
block.finish(1 /* exit_code */);
|
|
assert!(block.has_failed());
|
|
|
|
let mut block = TestBlockBuilder::new().build();
|
|
block.precmd(PrecmdValue::default());
|
|
block.finish(1 /* exit_code */);
|
|
|
|
// The block should not be marked as failed since execution never started.
|
|
assert!(!block.has_failed());
|
|
}
|
|
|
|
#[test]
|
|
fn test_non_error_exit_codes() {
|
|
let mut block = TestBlockBuilder::new().build();
|
|
|
|
block.precmd(Default::default());
|
|
block.preexec(Default::default());
|
|
|
|
block.finish(130 /* exit_code */);
|
|
// The block should not be marked as failed since it was interrupted by user action (SIGINT)
|
|
assert!(!block.has_failed());
|
|
|
|
let mut block = TestBlockBuilder::new().build();
|
|
|
|
block.precmd(Default::default());
|
|
block.preexec(Default::default());
|
|
|
|
block.finish(141 /* exit_code */);
|
|
// The block should not be marked as failed since it was interrupted by a pipe closing (SIGPIPE)
|
|
assert!(!block.has_failed());
|
|
}
|
|
|
|
#[test]
|
|
pub fn test_block_height_non_bootstrapped_block() {
|
|
let mut block = TestBlockBuilder::new().build();
|
|
|
|
// Add a few lines to the grid.
|
|
block.linefeed();
|
|
block.linefeed();
|
|
block.on_finish_byte_processing(&ansi::ProcessorInput::new(&[]));
|
|
|
|
// The block is empty since it was never started.
|
|
assert!(block.is_empty(&AgentViewState::Inactive));
|
|
|
|
block.start();
|
|
|
|
// The block should be non-empty even though it wasn't boostrapped.
|
|
assert_lines_approx_eq!(block.height(&AgentViewState::Inactive), 5.);
|
|
}
|
|
|
|
#[test]
|
|
fn test_background_block() {
|
|
let mut block = TestBlockBuilder::new().build();
|
|
block.set_honor_ps1(true);
|
|
|
|
block.start_background(None);
|
|
assert_eq!(block.state(), BlockState::Background);
|
|
|
|
for c in "background output".chars() {
|
|
block.input(c);
|
|
}
|
|
block.carriage_return();
|
|
block.linefeed();
|
|
|
|
block.finish(0);
|
|
|
|
assert_eq!(
|
|
block.output_grid.contents_to_string(true, None),
|
|
"background output\r\n"
|
|
);
|
|
|
|
// Background blocks have the usual top and bottom padding, but no
|
|
// between-grid padding because there's only one grid.
|
|
assert_lines_approx_eq!(block.output_grid_displayed_height(), 3);
|
|
assert_lines_approx_eq!(block.height(&AgentViewState::Inactive), 4.2);
|
|
}
|
|
|
|
#[test]
|
|
pub fn test_block_duration_formatting() {
|
|
// .01 seconds
|
|
let d1 = chrono::Duration::milliseconds(10);
|
|
assert_eq!(Block::format_duration(d1), " (0.01s)");
|
|
|
|
// .123 seconds
|
|
let d2 = chrono::Duration::milliseconds(123);
|
|
assert_eq!(Block::format_duration(d2), " (0.123s)");
|
|
|
|
// 3.143 seconds
|
|
let d3 = chrono::Duration::milliseconds(3143);
|
|
assert_eq!(Block::format_duration(d3), " (3.143s)");
|
|
|
|
// 10.44 seconds
|
|
let d4 = chrono::Duration::milliseconds(10440);
|
|
assert_eq!(Block::format_duration(d4), " (10.44s)");
|
|
|
|
// 25m 23.423 seconds
|
|
let d5 = chrono::Duration::milliseconds(1523423);
|
|
assert_eq!(Block::format_duration(d5), " (25m 23.42s)");
|
|
|
|
// 3h 25m 8.133 seconds
|
|
let d6 = chrono::Duration::milliseconds(12308133);
|
|
assert_eq!(Block::format_duration(d6), " (3h 25m 8s)");
|
|
|
|
// 22h 51m 48.902 seconds
|
|
let d7 = chrono::Duration::milliseconds(82308902);
|
|
assert_eq!(Block::format_duration(d7), " (22h 51m 49s)");
|
|
|
|
let d8 = chrono::Duration::milliseconds(3604114);
|
|
assert_eq!(Block::format_duration(d8), " (1h 4s)");
|
|
}
|
|
|
|
#[test]
|
|
pub fn test_block_emits_block_completed_event_for_in_band_command() {
|
|
let (events_tx, events_rx) = async_channel::unbounded();
|
|
|
|
let event_proxy = ChannelEventListener::builder_for_test()
|
|
.with_terminal_events_tx(events_tx)
|
|
.build();
|
|
let mut block = TestBlockBuilder::new()
|
|
.with_event_proxy(event_proxy)
|
|
.build();
|
|
|
|
block.start_for_in_band_command();
|
|
block.precmd(Default::default());
|
|
block.preexec(PreexecValue {
|
|
command: "warp_run_generator_command 1234 foo".to_owned(),
|
|
});
|
|
block.finish(0);
|
|
|
|
events_rx.close();
|
|
let block_completed_event =
|
|
galaxyui::r#async::block_on(pin!(events_rx).find(|event| match event {
|
|
Event::BlockCompleted(event) => matches!(event.block_type, BlockType::InBandCommand),
|
|
_ => false,
|
|
}));
|
|
assert!(block_completed_event.is_some());
|
|
}
|
|
|
|
/// Tests the multiline lprompt and multiline command case for selecting text across grids.
|
|
#[test]
|
|
fn test_selection_bounds_all_grids_multiline_lprompt_command() {
|
|
let block_index = BlockIndex::zero();
|
|
// Grid contents:
|
|
// -----
|
|
// lprompt1
|
|
// lprompt2%cmd1 rprompt
|
|
// cmd2
|
|
// cmd3
|
|
// output1
|
|
// output2
|
|
// -----
|
|
|
|
let mut prompt_and_command_grid = mock_blockgrid("lprompt1\r\nlprompt2%cmd1\r\ncmd2\r\ncmd3");
|
|
prompt_and_command_grid.finish();
|
|
let mut rprompt_grid = mock_blockgrid("rprompt");
|
|
rprompt_grid.finish();
|
|
let mut output_grid = mock_blockgrid("output1\r\noutput2\r\n");
|
|
output_grid.finish();
|
|
|
|
let mut block = create_test_block_with_grids(
|
|
block_index,
|
|
prompt_and_command_grid,
|
|
rprompt_grid,
|
|
output_grid,
|
|
true, /* honor_ps1 */
|
|
);
|
|
block.set_raw_prompt_end_point(Some(PromptEndPoint::PromptEnd {
|
|
point: Point::new(1, 8),
|
|
has_extra_trailing_newline: false,
|
|
}));
|
|
|
|
// Test cross-over across all sections (lprompt to output).
|
|
|
|
let all_grids = block.bounds_to_string(
|
|
BlockGridPoint::PromptAndCommand(Point::new(0, 0)),
|
|
BlockGridPoint::Output(Point::new(2, 0)),
|
|
);
|
|
assert_eq!(
|
|
all_grids,
|
|
"lprompt1\nlprompt2%cmd1\nrprompt\ncmd2\ncmd3\noutput1\noutput2"
|
|
);
|
|
|
|
// Test individual sections.
|
|
|
|
let lprompt_only = block.bounds_to_string(
|
|
BlockGridPoint::PromptAndCommand(Point::new(0, 2)),
|
|
BlockGridPoint::PromptAndCommand(Point::new(1, 3)),
|
|
);
|
|
assert_eq!(lprompt_only, "rompt1\nlpro");
|
|
|
|
let rprompt_only = block.bounds_to_string(
|
|
BlockGridPoint::Rprompt(Point::new(0, 1)),
|
|
BlockGridPoint::Rprompt(Point::new(0, 4)),
|
|
);
|
|
assert_eq!(rprompt_only, "prom");
|
|
|
|
let bottom_command_only = block.bounds_to_string(
|
|
BlockGridPoint::PromptAndCommand(Point::new(2, 1)),
|
|
BlockGridPoint::PromptAndCommand(Point::new(3, 2)),
|
|
);
|
|
assert_eq!(bottom_command_only, "md2\ncmd");
|
|
|
|
let output_only = block.bounds_to_string(
|
|
BlockGridPoint::Output(Point::new(0, 3)),
|
|
BlockGridPoint::Output(Point::new(0, 6)),
|
|
);
|
|
assert_eq!(output_only, "put1");
|
|
|
|
// Test cross-overs across sections.
|
|
|
|
let lprompt_to_rprompt = block.bounds_to_string(
|
|
BlockGridPoint::PromptAndCommand(Point::new(0, 2)),
|
|
BlockGridPoint::PromptAndCommand(Point::new(1, 2)),
|
|
);
|
|
assert_eq!(lprompt_to_rprompt, "rompt1\nlpr");
|
|
|
|
let lprompt_to_bottom_command = block.bounds_to_string(
|
|
BlockGridPoint::PromptAndCommand(Point::new(0, 2)),
|
|
BlockGridPoint::PromptAndCommand(Point::new(3, 2)),
|
|
);
|
|
assert_eq!(
|
|
lprompt_to_bottom_command,
|
|
"rompt1\nlprompt2%cmd1\nrprompt\ncmd2\ncmd"
|
|
);
|
|
|
|
let rprompt_to_bottom_command = block.bounds_to_string(
|
|
BlockGridPoint::Rprompt(Point::new(0, 0)),
|
|
BlockGridPoint::PromptAndCommand(Point::new(3, 2)),
|
|
);
|
|
assert_eq!(rprompt_to_bottom_command, "rprompt\ncmd2\ncmd");
|
|
|
|
let rprompt_to_output = block.bounds_to_string(
|
|
BlockGridPoint::Rprompt(Point::new(0, 0)),
|
|
BlockGridPoint::Output(Point::new(0, 3)),
|
|
);
|
|
assert_eq!(rprompt_to_output, "rprompt\ncmd2\ncmd3\noutp");
|
|
|
|
let bottom_command_to_output = block.bounds_to_string(
|
|
BlockGridPoint::PromptAndCommand(Point::new(2, 1)),
|
|
BlockGridPoint::Output(Point::new(1, 1)),
|
|
);
|
|
assert_eq!(bottom_command_to_output, "md2\ncmd3\noutput1\nou");
|
|
|
|
// Test a few "impossible cases" and ensure they return an empty string.
|
|
let impossible_output_to_rprompt = block.bounds_to_string(
|
|
BlockGridPoint::Output(Point::new(1, 0)),
|
|
BlockGridPoint::Rprompt(Point::new(1, 1)),
|
|
);
|
|
assert_eq!(impossible_output_to_rprompt, "");
|
|
|
|
let impossible_output_to_lprompt = block.bounds_to_string(
|
|
BlockGridPoint::Output(Point::new(1, 0)),
|
|
BlockGridPoint::PromptAndCommand(Point::new(0, 1)),
|
|
);
|
|
assert_eq!(impossible_output_to_lprompt, "");
|
|
}
|
|
|
|
/// Tests the single line lprompt and command case for text selection across grids.
|
|
#[test]
|
|
fn test_selection_bounds_all_grids_single_line_lprompt_command() {
|
|
let block_index = BlockIndex::zero();
|
|
// Grid contents:
|
|
// -----
|
|
// lprompt1%cmd1 rprompt
|
|
// output1
|
|
// output2
|
|
// -----
|
|
|
|
let mut prompt_and_command_grid = mock_blockgrid("lprompt%cmd1");
|
|
prompt_and_command_grid.finish();
|
|
let mut rprompt_grid = mock_blockgrid("rprompt");
|
|
rprompt_grid.finish();
|
|
let mut output_grid = mock_blockgrid("output1\r\noutput2\r\n");
|
|
output_grid.finish();
|
|
|
|
let mut block = create_test_block_with_grids(
|
|
block_index,
|
|
prompt_and_command_grid,
|
|
rprompt_grid,
|
|
output_grid,
|
|
true, /* honor_ps1 */
|
|
);
|
|
block.set_raw_prompt_end_point(Some(PromptEndPoint::PromptEnd {
|
|
point: Point::new(0, 7),
|
|
has_extra_trailing_newline: false,
|
|
}));
|
|
|
|
// Test cross-over across all sections (lprompt to output).
|
|
let all_grids = block.bounds_to_string(
|
|
BlockGridPoint::PromptAndCommand(Point::new(0, 0)),
|
|
BlockGridPoint::Output(Point::new(2, 0)),
|
|
);
|
|
assert_eq!(all_grids, "lprompt%cmd1\nrprompt\noutput1\noutput2");
|
|
}
|
|
|
|
/// Tests the single line lprompt, with trailing newline, and command case for text selection across grids.
|
|
#[test]
|
|
fn test_selection_bounds_all_grids_single_line_lprompt_trailing_newline_command() {
|
|
let block_index = BlockIndex::zero();
|
|
// Grid contents (lprompt has trailing newline):
|
|
// -----
|
|
// lprompt1%
|
|
// cmd1 rprompt
|
|
// output1
|
|
// output2
|
|
// -----
|
|
|
|
let mut prompt_and_command_grid = mock_blockgrid("lprompt%\r\ncmd1");
|
|
prompt_and_command_grid.finish();
|
|
let mut rprompt_grid = mock_blockgrid("rprompt");
|
|
rprompt_grid.finish();
|
|
let mut output_grid = mock_blockgrid("output1\r\noutput2\r\n");
|
|
output_grid.finish();
|
|
|
|
let mut block = create_test_block_with_grids(
|
|
block_index,
|
|
prompt_and_command_grid.clone(),
|
|
rprompt_grid,
|
|
output_grid,
|
|
true, /* honor_ps1 */
|
|
);
|
|
// We are including the \n in the lprompt, not the command!
|
|
block.set_raw_prompt_end_point(Some(PromptEndPoint::PromptEnd {
|
|
point: Point::new(0, 7),
|
|
has_extra_trailing_newline: true,
|
|
}));
|
|
|
|
// Test cross-over across all sections (lprompt to output).
|
|
let all_grids = block.bounds_to_string(
|
|
BlockGridPoint::PromptAndCommand(Point::new(0, 0)),
|
|
BlockGridPoint::Output(Point::new(2, 0)),
|
|
);
|
|
assert_eq!(all_grids, "lprompt%\ncmd1\nrprompt\noutput1\noutput2");
|
|
}
|
|
|
|
// Tests the single line lprompt and command, with starting newline, case for text selection across grids.
|
|
#[test]
|
|
fn test_selection_bounds_all_grids_single_line_lprompt_command_starting_newline() {
|
|
let block_index = BlockIndex::zero();
|
|
// Grid contents (command has starting newline):
|
|
// -----
|
|
// lprompt1% rprompt
|
|
// cmd1
|
|
// output1
|
|
// output2
|
|
// -----
|
|
|
|
let mut prompt_and_command_grid = mock_blockgrid("lprompt%\r\ncmd1");
|
|
prompt_and_command_grid.finish();
|
|
let mut rprompt_grid = mock_blockgrid("rprompt");
|
|
rprompt_grid.finish();
|
|
let mut output_grid = mock_blockgrid("output1\r\noutput2\r\n");
|
|
output_grid.finish();
|
|
|
|
let mut block = create_test_block_with_grids(
|
|
block_index,
|
|
prompt_and_command_grid.clone(),
|
|
rprompt_grid,
|
|
output_grid,
|
|
true, /* honor_ps1 */
|
|
);
|
|
// We are including the \n in the command, not the lprompt!
|
|
block.set_raw_prompt_end_point(Some(PromptEndPoint::PromptEnd {
|
|
point: Point::new(0, 7),
|
|
has_extra_trailing_newline: false,
|
|
}));
|
|
|
|
// Test cross-over across all sections (lprompt to output).
|
|
let all_grids = block.bounds_to_string(
|
|
BlockGridPoint::PromptAndCommand(Point::new(0, 0)),
|
|
BlockGridPoint::Output(Point::new(2, 0)),
|
|
);
|
|
assert_eq!(all_grids, "lprompt%\nrprompt\ncmd1\noutput1\noutput2");
|
|
}
|
|
|
|
/// Tests the multiline lprompt case, with no command content (where prompt end point is NOT defined).
|
|
#[test]
|
|
fn test_selection_bounds_all_grids_multiline_lprompt_no_command() {
|
|
let block_index = BlockIndex::zero();
|
|
// Grid contents:
|
|
// -----
|
|
// lprompt1
|
|
// lprompt2% rprompt
|
|
// output1
|
|
// output2
|
|
// -----
|
|
// Note: practically, this case is generally not possible, since a command needs to be run for output, but this is used for testing purposes and it
|
|
// could happen in a case similar to background output block (though, practically, those blocks don't have prompts).
|
|
|
|
let mut prompt_and_command_grid = mock_blockgrid("lprompt1\r\nlprompt2%");
|
|
prompt_and_command_grid.finish();
|
|
let mut rprompt_grid = mock_blockgrid("rprompt");
|
|
rprompt_grid.finish();
|
|
let mut output_grid = mock_blockgrid("output1\r\noutput2\r\n");
|
|
output_grid.finish();
|
|
|
|
let block = create_test_block_with_grids(
|
|
block_index,
|
|
prompt_and_command_grid,
|
|
rprompt_grid,
|
|
output_grid,
|
|
true, /* honor_ps1 */
|
|
);
|
|
|
|
// Test cross-over across all sections (lprompt to output).
|
|
|
|
let all_grids = block.bounds_to_string(
|
|
BlockGridPoint::PromptAndCommand(Point::new(0, 0)),
|
|
BlockGridPoint::Output(Point::new(2, 0)),
|
|
);
|
|
assert_eq!(all_grids, "lprompt1\nlprompt2%\nrprompt\noutput1\noutput2");
|
|
|
|
// Test individual sections.
|
|
|
|
let lprompt_only = block.bounds_to_string(
|
|
BlockGridPoint::PromptAndCommand(Point::new(0, 2)),
|
|
BlockGridPoint::PromptAndCommand(Point::new(1, 3)),
|
|
);
|
|
assert_eq!(lprompt_only, "rompt1\nlpro");
|
|
|
|
let rprompt_only = block.bounds_to_string(
|
|
BlockGridPoint::Rprompt(Point::new(0, 1)),
|
|
BlockGridPoint::Rprompt(Point::new(0, 4)),
|
|
);
|
|
assert_eq!(rprompt_only, "prom");
|
|
|
|
let output_only = block.bounds_to_string(
|
|
BlockGridPoint::Output(Point::new(0, 3)),
|
|
BlockGridPoint::Output(Point::new(0, 6)),
|
|
);
|
|
assert_eq!(output_only, "put1");
|
|
|
|
// Test cross-overs across sections.
|
|
|
|
let lprompt_to_rprompt = block.bounds_to_string(
|
|
BlockGridPoint::PromptAndCommand(Point::new(0, 2)),
|
|
BlockGridPoint::PromptAndCommand(Point::new(1, 2)),
|
|
);
|
|
assert_eq!(lprompt_to_rprompt, "rompt1\nlpr");
|
|
|
|
let rprompt_to_output = block.bounds_to_string(
|
|
BlockGridPoint::Rprompt(Point::new(0, 0)),
|
|
BlockGridPoint::Output(Point::new(0, 3)),
|
|
);
|
|
assert_eq!(rprompt_to_output, "rprompt\noutp");
|
|
|
|
// Test a few "impossible cases" and ensure they return an empty string.
|
|
let impossible_output_to_rprompt = block.bounds_to_string(
|
|
BlockGridPoint::Output(Point::new(1, 0)),
|
|
BlockGridPoint::Rprompt(Point::new(1, 1)),
|
|
);
|
|
assert_eq!(impossible_output_to_rprompt, "");
|
|
|
|
let impossible_output_to_lprompt = block.bounds_to_string(
|
|
BlockGridPoint::Output(Point::new(1, 0)),
|
|
BlockGridPoint::PromptAndCommand(Point::new(0, 1)),
|
|
);
|
|
assert_eq!(impossible_output_to_lprompt, "");
|
|
}
|
|
|
|
/// Tests cloning "typeahead" content from background output grid into the combined grid.
|
|
/// Specifically, this tests the user behavior of typeahead when pressing ENTER after typing a command.
|
|
#[test]
|
|
fn test_clone_command_from_blockgrid() {
|
|
let block_index = BlockIndex::zero();
|
|
// Grid contents (copying INTO this Grid):
|
|
// -----
|
|
// lprompt1
|
|
// lprompt2%
|
|
// -----
|
|
|
|
// Grid to copy contents FROM:
|
|
// -----
|
|
// clone1
|
|
// clone2
|
|
// clone3
|
|
// -----
|
|
|
|
// Final expected grid (internals):
|
|
// -----
|
|
// lprompt1
|
|
// lprompt2% (soft-wrap)
|
|
// clone1 (hard-wrap)
|
|
// clone2 (hard-wrap)
|
|
// clone3 (hard-wrap)
|
|
// -----
|
|
|
|
let prompt_and_command_grid = mock_blockgrid("lprompt1\r\nlprompt2%");
|
|
let mut rprompt_grid = mock_blockgrid("");
|
|
rprompt_grid.finish();
|
|
let mut output_grid = mock_blockgrid("");
|
|
output_grid.finish();
|
|
|
|
let mut block = create_test_block_with_grids(
|
|
block_index,
|
|
prompt_and_command_grid,
|
|
rprompt_grid,
|
|
output_grid,
|
|
true, /* honor_ps1 */
|
|
);
|
|
|
|
let mut grid_to_clone = mock_blockgrid("clone1\r\nclone2\r\nclone3");
|
|
grid_to_clone.finish();
|
|
|
|
block
|
|
.header_grid
|
|
.prompt_and_command_grid_mut()
|
|
.set_mode(ansi::Mode::LineFeedNewLine);
|
|
|
|
block.copy_command_grid(&grid_to_clone);
|
|
|
|
let new_content = block.prompt_and_command_grid().to_string(
|
|
false,
|
|
None,
|
|
RespectObfuscatedSecrets::Yes,
|
|
false,
|
|
RespectDisplayedOutput::Yes,
|
|
);
|
|
assert_eq!(new_content, "lprompt1\nlprompt2%clone1\nclone2\nclone3");
|
|
}
|
|
|
|
#[test]
|
|
fn test_clone_command_from_blockgrid_long() {
|
|
let block_index = BlockIndex::zero();
|
|
// Grid contents (copying INTO this Grid):
|
|
// -----
|
|
// lprompt1
|
|
// lprompt2%
|
|
// -----
|
|
|
|
// Grid to copy contents FROM:
|
|
// -----
|
|
// clone1_long
|
|
// clone2_long
|
|
// clone3_long
|
|
// -----
|
|
|
|
// Final expected grid (internals):
|
|
// -----
|
|
// lprompt1
|
|
// lprompt2% (soft-wrap)
|
|
// clone1_lo (soft-wrap)
|
|
// ng (hard-wrap)
|
|
// clone2_lo (soft-wrap)
|
|
// ng (hard-wrap)
|
|
// clone3_lo (soft-wrap)
|
|
// ng
|
|
// -----
|
|
|
|
let prompt_and_command_grid = mock_blockgrid("lprompt1\r\nlprompt2%");
|
|
let mut rprompt_grid = mock_blockgrid("");
|
|
rprompt_grid.finish();
|
|
let mut output_grid = mock_blockgrid("");
|
|
output_grid.finish();
|
|
|
|
let mut block = create_test_block_with_grids(
|
|
block_index,
|
|
prompt_and_command_grid,
|
|
rprompt_grid,
|
|
output_grid,
|
|
true, /* honor_ps1 */
|
|
);
|
|
|
|
let mut grid_to_clone = mock_blockgrid("clone1_long\r\nclone2_long\r\nclone3_long");
|
|
grid_to_clone.finish();
|
|
|
|
block
|
|
.header_grid
|
|
.prompt_and_command_grid_mut()
|
|
.set_mode(ansi::Mode::LineFeedNewLine);
|
|
|
|
block.copy_command_grid(&grid_to_clone);
|
|
|
|
let new_content = block.prompt_and_command_grid().to_string(
|
|
false,
|
|
None,
|
|
RespectObfuscatedSecrets::Yes,
|
|
false,
|
|
RespectDisplayedOutput::Yes,
|
|
);
|
|
assert_eq!(
|
|
new_content,
|
|
"lprompt1\nlprompt2%clone1_long\nclone2_long\nclone3_long"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_clone_command_from_blockgrid_from_empty() {
|
|
let block_index = BlockIndex::zero();
|
|
// Grid contents (copying INTO this Grid):
|
|
// -----
|
|
// lprompt1
|
|
// lprompt2%
|
|
// -----
|
|
|
|
// Grid to copy contents FROM:
|
|
// -----
|
|
// -----
|
|
|
|
// Final expected grid (internals):
|
|
// -----
|
|
// lprompt1 (hard-wrap)
|
|
// lprompt2%
|
|
// -----
|
|
|
|
let prompt_and_command_grid = mock_blockgrid("lprompt1\r\nlprompt2%");
|
|
let mut rprompt_grid = mock_blockgrid("");
|
|
rprompt_grid.finish();
|
|
let mut output_grid = mock_blockgrid("");
|
|
output_grid.finish();
|
|
|
|
let mut block = create_test_block_with_grids(
|
|
block_index,
|
|
prompt_and_command_grid,
|
|
rprompt_grid,
|
|
output_grid,
|
|
true, /* honor_ps1 */
|
|
);
|
|
|
|
let mut grid_to_clone = mock_blockgrid("");
|
|
grid_to_clone.finish();
|
|
|
|
block
|
|
.header_grid
|
|
.prompt_and_command_grid_mut()
|
|
.set_mode(ansi::Mode::LineFeedNewLine);
|
|
|
|
block.copy_command_grid(&grid_to_clone);
|
|
|
|
let new_content = block.prompt_and_command_grid().to_string(
|
|
false,
|
|
None,
|
|
RespectObfuscatedSecrets::Yes,
|
|
false,
|
|
RespectDisplayedOutput::Yes,
|
|
);
|
|
assert_eq!(new_content, "lprompt1\nlprompt2%");
|
|
}
|
|
|
|
#[test]
|
|
fn test_clone_command_from_blockgrid_to_empty() {
|
|
let block_index = BlockIndex::zero();
|
|
// Grid contents (copying INTO this Grid):
|
|
// -----
|
|
// -----
|
|
|
|
// Grid to copy contents FROM:
|
|
// -----
|
|
// clone1
|
|
// clone2
|
|
// clone3
|
|
// -----
|
|
|
|
// Final expected grid (internals):
|
|
// -----
|
|
// clone1 (hard-wrap)
|
|
// clone2 (hard-wrap)
|
|
// clone3
|
|
// -----
|
|
|
|
let prompt_and_command_grid = mock_blockgrid("");
|
|
let mut rprompt_grid = mock_blockgrid("");
|
|
rprompt_grid.finish();
|
|
let mut output_grid = mock_blockgrid("");
|
|
output_grid.finish();
|
|
|
|
let mut block = create_test_block_with_grids(
|
|
block_index,
|
|
prompt_and_command_grid,
|
|
rprompt_grid,
|
|
output_grid,
|
|
true, /* honor_ps1 */
|
|
);
|
|
|
|
let mut grid_to_clone = mock_blockgrid("clone1\r\nclone2\r\nclone3");
|
|
grid_to_clone.finish();
|
|
|
|
block
|
|
.header_grid
|
|
.prompt_and_command_grid_mut()
|
|
.set_mode(ansi::Mode::LineFeedNewLine);
|
|
|
|
block.copy_command_grid(&grid_to_clone);
|
|
|
|
let new_content = block.prompt_and_command_grid().to_string(
|
|
false,
|
|
None,
|
|
RespectObfuscatedSecrets::Yes,
|
|
false,
|
|
RespectDisplayedOutput::Yes,
|
|
);
|
|
assert_eq!(new_content, "clone1\nclone2\nclone3");
|
|
}
|
|
|
|
/// Tests is_command_empty for an empty command with the combined grid.
|
|
#[test]
|
|
fn test_command_is_empty_combined_grid() {
|
|
let block_index = BlockIndex::zero();
|
|
// Grid contents:
|
|
// -----
|
|
// -----
|
|
|
|
let prompt_and_command_grid = mock_blockgrid("");
|
|
let mut rprompt_grid = mock_blockgrid("");
|
|
rprompt_grid.finish();
|
|
let mut output_grid = mock_blockgrid("");
|
|
output_grid.finish();
|
|
|
|
let mut block = create_test_block_with_grids(
|
|
block_index,
|
|
prompt_and_command_grid,
|
|
rprompt_grid,
|
|
output_grid,
|
|
true, /* honor_ps1 */
|
|
);
|
|
block.start();
|
|
block.finish_command_grid();
|
|
|
|
assert!(
|
|
block.is_command_empty(),
|
|
"Command should be considered empty!"
|
|
);
|
|
}
|
|
|
|
/// Tests is_command_empty for an non-empty command with the combined grid.
|
|
#[test]
|
|
fn test_command_is_not_empty_combined_grid() {
|
|
let block_index = BlockIndex::zero();
|
|
// Grid contents:
|
|
// -----
|
|
// command1 (linefeed at the end from finishing the block)
|
|
// -----
|
|
|
|
let prompt_and_command_grid = mock_blockgrid("command1");
|
|
let mut rprompt_grid = mock_blockgrid("");
|
|
rprompt_grid.finish();
|
|
let mut output_grid = mock_blockgrid("");
|
|
output_grid.finish();
|
|
|
|
let mut block = create_test_block_with_grids(
|
|
block_index,
|
|
prompt_and_command_grid,
|
|
rprompt_grid,
|
|
output_grid,
|
|
true, /* honor_ps1 */
|
|
);
|
|
block.start();
|
|
block.finish(0);
|
|
|
|
assert!(
|
|
!block.is_command_empty(),
|
|
"Command should not be considered empty!"
|
|
);
|
|
}
|
|
|
|
/// Regression test (CORE-1947): checks Warp prompt case for is_command_empty. Specifically,
|
|
/// even if the combined grid's content _exactly_ matches the prompt grid's content (which is used
|
|
/// for PS1 preview), we should NOT consider the command to be empty. The underlying cursor check should
|
|
/// be against (0, 0) in the combined grid, for the Warp prompt case, rather than checking against the
|
|
/// prompt grid (which we do in the PS1 active case).
|
|
#[test]
|
|
fn test_command_is_empty_warp_prompt() {
|
|
let block_index = BlockIndex::zero();
|
|
// Combined grid contents:
|
|
// -----
|
|
// warp_prompt
|
|
// abcde
|
|
// -----
|
|
|
|
// Prompt grid contents:
|
|
// -----
|
|
// abcde
|
|
// -----
|
|
|
|
let prompt_and_command_grid = mock_blockgrid("abcde");
|
|
let mut rprompt_grid = mock_blockgrid("");
|
|
rprompt_grid.finish();
|
|
let mut output_grid = mock_blockgrid("");
|
|
output_grid.finish();
|
|
// Note that the prompt_grid's finished cursor will be at (0, 4) with the content below.
|
|
// This will be the exact same as the combined grid's cursor at (0, 4), which will
|
|
// check the regression case (we should NOT consider the command to be empty!).
|
|
let mut prompt_grid = mock_blockgrid("abcde");
|
|
prompt_grid.finish();
|
|
|
|
// Note that we are indicating Warp prompt, not PS1 here!
|
|
let mut block = create_test_block_with_grids(
|
|
block_index,
|
|
prompt_and_command_grid,
|
|
rprompt_grid,
|
|
output_grid,
|
|
false, /* honor_ps1 */
|
|
);
|
|
block.set_prompt_grid(prompt_grid);
|
|
block.start();
|
|
block.finish_command_grid();
|
|
|
|
assert!(
|
|
!block.is_command_empty(),
|
|
"Command should not be considered empty!"
|
|
);
|
|
}
|
|
|
|
/// Tests is_command_empty behavior with PS1 prompt _and_ command with combined grid.
|
|
#[test]
|
|
fn test_command_is_empty_combined_grid_prompt_and_command() {
|
|
let block_index = BlockIndex::zero();
|
|
// Combined grid contents:
|
|
// -----
|
|
// prompt1command1
|
|
// -----
|
|
|
|
// Prompt grid contents:
|
|
// -----
|
|
// prompt1
|
|
// -----
|
|
|
|
let prompt_and_command_grid = mock_blockgrid("prompt1command1");
|
|
let mut rprompt_grid = mock_blockgrid("");
|
|
rprompt_grid.finish();
|
|
let mut output_grid = mock_blockgrid("");
|
|
output_grid.finish();
|
|
let mut prompt_grid = mock_blockgrid("prompt1");
|
|
prompt_grid.finish();
|
|
|
|
let mut block = create_test_block_with_grids(
|
|
block_index,
|
|
prompt_and_command_grid,
|
|
rprompt_grid,
|
|
output_grid,
|
|
true, /* honor_ps1 */
|
|
);
|
|
block.set_prompt_grid(prompt_grid);
|
|
block.start();
|
|
block.finish(0);
|
|
|
|
assert!(
|
|
!block.is_command_empty(),
|
|
"Command should not be considered empty!"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_top_level_command() {
|
|
let mut sessions = Sessions::new_for_test();
|
|
sessions.register_session_for_test(SessionInfo::new_for_test().with_id(0));
|
|
|
|
let mut block = TestBlockBuilder::new()
|
|
.with_size_info(SizeInfo::new_without_font_metrics(1, 20))
|
|
.build();
|
|
block.start();
|
|
block.precmd(PrecmdValue {
|
|
session_id: Some(0),
|
|
..Default::default()
|
|
});
|
|
for c in "git status".chars() {
|
|
block.input(c);
|
|
}
|
|
|
|
assert_eq!(block.top_level_command(&sessions), Some("git".to_string()));
|
|
|
|
let mut block = TestBlockBuilder::new()
|
|
.with_size_info(SizeInfo::new_without_font_metrics(1, 20))
|
|
.build();
|
|
block.start();
|
|
block.precmd(PrecmdValue {
|
|
session_id: Some(0),
|
|
..Default::default()
|
|
});
|
|
for c in "PAGER=0 git status".chars() {
|
|
block.input(c);
|
|
}
|
|
|
|
assert_eq!(block.top_level_command(&sessions), Some("git".to_string()));
|
|
}
|
|
|
|
#[test]
|
|
fn test_top_level_command_with_aliases() {
|
|
let mut sessions = Sessions::new_for_test();
|
|
sessions.register_session_for_test(SessionInfo::new_for_test().with_id(0).with_aliases(
|
|
HashMap::from_iter([
|
|
("git".into(), String::from("git2")),
|
|
("gs".into(), String::from("git status")),
|
|
("gl".into(), String::from("PAGER=0 git log")),
|
|
]),
|
|
));
|
|
|
|
let mut block = TestBlockBuilder::new()
|
|
.with_size_info(SizeInfo::new_without_font_metrics(1, 20))
|
|
.build();
|
|
block.start();
|
|
block.precmd(PrecmdValue {
|
|
session_id: Some(0),
|
|
..Default::default()
|
|
});
|
|
for c in "git status".chars() {
|
|
block.input(c);
|
|
}
|
|
assert_eq!(block.top_level_command(&sessions), Some("git2".to_string()));
|
|
|
|
let mut block = TestBlockBuilder::new()
|
|
.with_size_info(SizeInfo::new_without_font_metrics(1, 20))
|
|
.build();
|
|
block.start();
|
|
block.precmd(PrecmdValue {
|
|
session_id: Some(0),
|
|
..Default::default()
|
|
});
|
|
for c in "gs --ignored".chars() {
|
|
block.input(c);
|
|
}
|
|
assert_eq!(block.top_level_command(&sessions), Some("git".to_string()));
|
|
|
|
let mut block = TestBlockBuilder::new()
|
|
.with_size_info(SizeInfo::new_without_font_metrics(1, 20))
|
|
.build();
|
|
block.start();
|
|
block.precmd(PrecmdValue {
|
|
session_id: Some(0),
|
|
..Default::default()
|
|
});
|
|
for c in "PAGER= gl --flag".chars() {
|
|
block.input(c);
|
|
}
|
|
assert_eq!(block.top_level_command(&sessions), Some("git".to_string()));
|
|
}
|
|
|
|
#[test]
|
|
fn test_mark_end_of_prompt_with_some_rows_in_flat_storage() {
|
|
use galaxy_terminal::model::grid::Dimensions as _;
|
|
|
|
let mut block = TestBlockBuilder::new()
|
|
// Set the number of visible rows to 1, so that the first row of the
|
|
// prompt ends up in flat storage.
|
|
.with_size_info(SizeInfo::new_without_font_metrics(1, 40))
|
|
.with_honor_ps1(true)
|
|
.build();
|
|
|
|
block.precmd(PrecmdValue {
|
|
ps1: Some(hex::encode("prompt1\r\n")),
|
|
honor_ps1: Some(true),
|
|
..Default::default()
|
|
});
|
|
|
|
block.start();
|
|
block.finish(0);
|
|
|
|
// Resize the grid so that everything ends up in grid storage.
|
|
let rows = block
|
|
.header_grid
|
|
.prompt_and_command_grid()
|
|
.grid_handler()
|
|
.total_rows();
|
|
block.resize(SizeInfo::new_without_font_metrics(rows, 40));
|
|
|
|
// Assert that the end-of-prompt marker is set where we expect it to be.
|
|
// This ensures that we properly set it within flat storage when initially
|
|
// setting the end-of-prompt marker during precmd().
|
|
let prompt_end_point = block.header_grid.prompt_end_point();
|
|
let Some(PromptEndPoint::PromptEnd { point, .. }) = prompt_end_point else {
|
|
panic!("Expected to have a well-defined prompt end point; got: {prompt_end_point:?}");
|
|
};
|
|
assert_eq!(point, Point::new(0, 39));
|
|
assert!(block.header_grid.prompt_and_command_grid().grid_storage()[point].is_end_of_prompt());
|
|
}
|
|
|
|
#[test]
|
|
fn test_restored_block_was_local() {
|
|
// Test default value is None
|
|
let mut block = TestBlockBuilder::new()
|
|
.with_bootstrap_stage(BootstrapStage::RestoreBlocks)
|
|
.build();
|
|
assert_eq!(block.restored_block_was_local(), None);
|
|
|
|
// Test setting to true
|
|
block.set_restored_block_was_local(true);
|
|
assert_eq!(block.restored_block_was_local(), Some(true));
|
|
|
|
// Test changing value
|
|
block.set_restored_block_was_local(false);
|
|
assert_eq!(block.restored_block_was_local(), Some(false));
|
|
|
|
// Test with another new block (default is still None)
|
|
let block = TestBlockBuilder::new()
|
|
.with_bootstrap_stage(BootstrapStage::RestoreBlocks)
|
|
.build();
|
|
assert_eq!(block.restored_block_was_local(), None);
|
|
}
|
|
|
|
#[test]
|
|
fn test_deserialize_legacy_agent_view_visibility_agent_variant() {
|
|
let origin_conversation_id = AIConversationId::new();
|
|
let json = format!("{{\"Agent\":{{\"conversation_id\":\"{origin_conversation_id}\"}}}}");
|
|
|
|
let visibility: SerializedAgentViewVisibility = serde_json::from_str(&json).unwrap();
|
|
match visibility {
|
|
SerializedAgentViewVisibility::Agent {
|
|
origin_conversation_id: parsed_origin_conversation_id,
|
|
pending_other_conversation_ids,
|
|
other_conversation_ids,
|
|
} => {
|
|
assert_eq!(parsed_origin_conversation_id, origin_conversation_id);
|
|
assert!(pending_other_conversation_ids.is_empty());
|
|
assert!(other_conversation_ids.is_empty());
|
|
}
|
|
_ => panic!("Expected agent visibility"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_calculate_optimal_row_counts_wide_terminal() {
|
|
// Terminal width >= 150 should return default values
|
|
let (top, bottom) = calculate_optimal_row_counts(150, 100, 200);
|
|
assert_eq!(top, 100);
|
|
assert_eq!(bottom, 200);
|
|
|
|
let (top, bottom) = calculate_optimal_row_counts(200, 50, 100);
|
|
assert_eq!(top, 50);
|
|
assert_eq!(bottom, 100);
|
|
}
|
|
|
|
#[test]
|
|
fn test_calculate_optimal_row_counts_narrow_terminal() {
|
|
// Terminal width < 150 should optimize based on target total cells
|
|
let (top, bottom) = calculate_optimal_row_counts(75, 100, 200);
|
|
|
|
// Target total cells = (100 + 200) * 150 = 45,000
|
|
// Total rows for width 75 = 45,000 / 75 = 600
|
|
// Top rows = (600 * 100) / 300 = 200
|
|
// Bottom rows = 600 - 200 = 400
|
|
assert_eq!(top, 200);
|
|
assert_eq!(bottom, 400);
|
|
}
|
|
|
|
#[test]
|
|
fn test_calculate_optimal_row_counts_very_narrow_terminal() {
|
|
// Test edge case with very narrow terminal
|
|
let (top, bottom) = calculate_optimal_row_counts(30, 100, 200);
|
|
|
|
// Target total cells = (100 + 200) * 150 = 45,000
|
|
// Total rows for width 30 = 45,000 / 30 = 1,500
|
|
// Top rows = (1,500 * 100) / 300 = 500
|
|
// Bottom rows = 1,500 - 500 = 1,000
|
|
assert_eq!(top, 500);
|
|
assert_eq!(bottom, 1000);
|
|
}
|