#[cfg(test)]
use super::CARD_LAYOUT_MIN_WIDTH;
use super::visual_lines::{TranscriptVisualLine, visual_line_rows, visual_lines};
use crate::{
output::ActivityId,
tui::{state::MissionControlState, transcript},
};
fn copy_position_for_visual_cell(
line: &TranscriptVisualLine,
row: usize,
column: usize,
wrap_width: u16,
) -> Option<crate::tui::input::selection::TextPosition> {
let column_offset = usize::from(line.visual_copy_column_offset);
let aligned_copy_text = if column_offset == 0 {
line.copy_text.clone()
} else {
format!("{}{}", " ".repeat(column_offset), line.copy_text)
};
let position =
transcript::text_position_for_visual_cell(&aligned_copy_text, row, column, wrap_width)?;
Some(crate::tui::input::selection::TextPosition::new(
position
.byte
.saturating_sub(column_offset)
.min(line.copy_text.len()),
row,
column,
))
}
pub(crate) fn text_position_for_visual_cell(
state: &MissionControlState,
visual_row: usize,
column: usize,
wrap_width: u16,
) -> Option<crate::tui::input::selection::TextPosition> {
let lines = visual_lines(state, wrap_width);
let mut current_row = 0usize;
let mut byte_base = 0usize;
let mut copy_line_seen = false;
for line in &lines {
if line.copyable && !line.copy_continuation && copy_line_seen {
byte_base = byte_base.saturating_add(1);
}
if line.copyable {
copy_line_seen = true;
}
let rows = visual_line_rows(line, wrap_width);
let next_row = current_row.saturating_add(rows);
if visual_row < next_row {
if !line.copyable {
return None;
}
let row_in_line = visual_row.saturating_sub(current_row);
let position = copy_position_for_visual_cell(line, row_in_line, column, wrap_width)?;
return Some(crate::tui::input::selection::TextPosition::new(
byte_base.saturating_add(position.byte),
visual_row,
column,
));
}
current_row = next_row;
if line.copyable {
byte_base = byte_base.saturating_add(line.copy_text.len());
}
}
None
}
pub(crate) fn activity_button_for_visual_cell(
state: &MissionControlState,
visual_row: usize,
column: usize,
wrap_width: u16,
) -> Option<ActivityId> {
let lines = visual_lines(state, wrap_width);
let mut current_row = 0usize;
for line in &lines {
let rows = visual_line_rows(line, wrap_width);
let next_row = current_row.saturating_add(rows);
if visual_row < next_row {
let button = line.activity_button.as_ref()?;
let row_in_line = visual_row.saturating_sub(current_row);
return (row_in_line == 0
&& column >= usize::from(button.start_column)
&& column < usize::from(button.end_column))
.then(|| button.activity_id.clone());
}
current_row = next_row;
}
None
}
#[cfg(test)]
pub(crate) fn visual_text(state: &MissionControlState) -> String {
visual_text_for_width(state, CARD_LAYOUT_MIN_WIDTH)
}
pub(crate) fn visual_text_for_width(state: &MissionControlState, wrap_width: u16) -> String {
let mut text = String::new();
let mut copy_line_seen = false;
for line in visual_lines(state, wrap_width)
.into_iter()
.filter(|line| line.copyable)
{
if !line.copy_continuation {
if copy_line_seen {
text.push('\n');
}
copy_line_seen = true;
}
text.push_str(&line.copy_text);
}
text
}