mirror of
https://github.com/TECHNOFAB11/tmux-copyrat.git
synced 2025-12-12 16:10:07 +01:00
feat: capture wrapped lines
This commit is contained in:
parent
45a4083547
commit
193dca67e3
3 changed files with 71 additions and 27 deletions
23
src/model.rs
23
src/model.rs
|
|
@ -1,5 +1,4 @@
|
||||||
use std::collections;
|
use std::collections;
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use sequence_trie::SequenceTrie;
|
use sequence_trie::SequenceTrie;
|
||||||
|
|
@ -227,6 +226,7 @@ impl<'a> Model<'a> {
|
||||||
|
|
||||||
/// Represents matched text, its location on screen, the pattern that created
|
/// Represents matched text, its location on screen, the pattern that created
|
||||||
/// it, and the associated hint.
|
/// it, and the associated hint.
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Match<'a> {
|
pub struct Match<'a> {
|
||||||
pub x: i32,
|
pub x: i32,
|
||||||
pub y: i32,
|
pub y: i32,
|
||||||
|
|
@ -235,17 +235,8 @@ pub struct Match<'a> {
|
||||||
pub hint: String,
|
pub hint: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> fmt::Debug for Match<'a> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"Match {{ x: {}, y: {}, pattern: {}, text: {}, hint: <{}> }}",
|
|
||||||
self.x, self.y, self.pattern, self.text, self.hint,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Internal surrogate for `Match`, before a Hint has been associated.
|
/// Internal surrogate for `Match`, before a Hint has been associated.
|
||||||
|
#[derive(Debug)]
|
||||||
struct RawMatch<'a> {
|
struct RawMatch<'a> {
|
||||||
pub x: i32,
|
pub x: i32,
|
||||||
pub y: i32,
|
pub y: i32,
|
||||||
|
|
@ -253,16 +244,6 @@ struct RawMatch<'a> {
|
||||||
pub text: &'a str,
|
pub text: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> fmt::Debug for RawMatch<'a> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"RawMatch {{ x: {}, y: {}, pattern: {}, text: {} }}",
|
|
||||||
self.x, self.y, self.pattern, self.text,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
||||||
|
|
@ -205,7 +205,7 @@ pub fn get_options(prefix: &str) -> Result<HashMap<String, String>, ParseError>
|
||||||
/// position. To support both cases, the implementation always provides those
|
/// position. To support both cases, the implementation always provides those
|
||||||
/// parameters to tmux.
|
/// parameters to tmux.
|
||||||
pub fn capture_pane(pane: &Pane, region: &CaptureRegion) -> Result<String, ParseError> {
|
pub fn capture_pane(pane: &Pane, region: &CaptureRegion) -> Result<String, ParseError> {
|
||||||
let mut args = format!("capture-pane -t {pane_id} -p", pane_id = pane.id);
|
let mut args = format!("capture-pane -t {pane_id} -J -p", pane_id = pane.id);
|
||||||
|
|
||||||
let region_str = match region {
|
let region_str = match region {
|
||||||
CaptureRegion::VisibleArea => {
|
CaptureRegion::VisibleArea => {
|
||||||
|
|
|
||||||
73
src/ui.rs
73
src/ui.rs
|
|
@ -11,6 +11,8 @@ use crate::{colors, model};
|
||||||
|
|
||||||
pub struct Ui<'a> {
|
pub struct Ui<'a> {
|
||||||
model: &'a mut model::Model<'a>,
|
model: &'a mut model::Model<'a>,
|
||||||
|
term_width: u16,
|
||||||
|
line_offsets: Vec<usize>,
|
||||||
matches: Vec<model::Match<'a>>,
|
matches: Vec<model::Match<'a>>,
|
||||||
lookup_trie: SequenceTrie<char, usize>,
|
lookup_trie: SequenceTrie<char, usize>,
|
||||||
focus_index: usize,
|
focus_index: usize,
|
||||||
|
|
@ -33,8 +35,13 @@ impl<'a> Ui<'a> {
|
||||||
let lookup_trie = model::Model::build_lookup_trie(&matches);
|
let lookup_trie = model::Model::build_lookup_trie(&matches);
|
||||||
let focus_index = if model.reverse { matches.len() - 1 } else { 0 };
|
let focus_index = if model.reverse { matches.len() - 1 } else { 0 };
|
||||||
|
|
||||||
|
let (term_width, _) = termion::terminal_size().expect("Cannot read the terminal size.");
|
||||||
|
let line_offsets = get_line_offsets(&model.lines, term_width);
|
||||||
|
|
||||||
Ui {
|
Ui {
|
||||||
model,
|
model,
|
||||||
|
term_width,
|
||||||
|
line_offsets,
|
||||||
matches,
|
matches,
|
||||||
lookup_trie,
|
lookup_trie,
|
||||||
focus_index,
|
focus_index,
|
||||||
|
|
@ -45,6 +52,25 @@ impl<'a> Ui<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert the `Match` text into the coordinates of the wrapped lines.
|
||||||
|
///
|
||||||
|
/// Compute the new x offset of the text as the remainder of the line width
|
||||||
|
/// (e.g. the match could start at offset 120 in a 80-width terminal, the new
|
||||||
|
/// offset being 40).
|
||||||
|
///
|
||||||
|
/// Compute the new y offset of the text as the initial y offset plus any
|
||||||
|
/// additional offset due to previous split lines. This is obtained thanks to
|
||||||
|
/// the `offset_per_line` member.
|
||||||
|
pub fn map_coords_to_wrapped_space(&self, offset_x: usize, offset_y: usize) -> (usize, usize) {
|
||||||
|
let line_width = self.term_width as usize;
|
||||||
|
|
||||||
|
let new_offset_x = offset_x % line_width;
|
||||||
|
let new_offset_y =
|
||||||
|
self.line_offsets.get(offset_y as usize).unwrap() + offset_x / line_width;
|
||||||
|
|
||||||
|
(new_offset_x, new_offset_y)
|
||||||
|
}
|
||||||
|
|
||||||
/// Move focus onto the previous hint, returning both the index of the
|
/// Move focus onto the previous hint, returning both the index of the
|
||||||
/// previously focused match, and the index of the newly focused one.
|
/// previously focused match, and the index of the newly focused one.
|
||||||
fn prev_focus_index(&mut self) -> (usize, usize) {
|
fn prev_focus_index(&mut self) -> (usize, usize) {
|
||||||
|
|
@ -108,7 +134,12 @@ impl<'a> Ui<'a> {
|
||||||
/// # Notes
|
/// # Notes
|
||||||
/// - All trailing whitespaces are trimmed, empty lines are skipped.
|
/// - All trailing whitespaces are trimmed, empty lines are skipped.
|
||||||
/// - This writes directly on the writer, avoiding extra allocation.
|
/// - This writes directly on the writer, avoiding extra allocation.
|
||||||
fn render_base_text(stdout: &mut dyn io::Write, lines: &Vec<&str>, colors: &UiColors) -> () {
|
fn render_base_text(
|
||||||
|
stdout: &mut dyn io::Write,
|
||||||
|
lines: &Vec<&str>,
|
||||||
|
line_offsets: &Vec<usize>,
|
||||||
|
colors: &UiColors,
|
||||||
|
) -> () {
|
||||||
write!(
|
write!(
|
||||||
stdout,
|
stdout,
|
||||||
"{bg_color}{fg_color}",
|
"{bg_color}{fg_color}",
|
||||||
|
|
@ -117,14 +148,17 @@ impl<'a> Ui<'a> {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
for (index, line) in lines.iter().enumerate() {
|
for (line_index, line) in lines.iter().enumerate() {
|
||||||
let trimmed_line = line.trim_end();
|
let trimmed_line = line.trim_end();
|
||||||
|
|
||||||
if !trimmed_line.is_empty() {
|
if !trimmed_line.is_empty() {
|
||||||
|
let offset_y: usize =
|
||||||
|
*(line_offsets.get(line_index)).expect("Cannot get offset_per_line.");
|
||||||
|
|
||||||
write!(
|
write!(
|
||||||
stdout,
|
stdout,
|
||||||
"{goto}{text}",
|
"{goto}{text}",
|
||||||
goto = cursor::Goto(1, index as u16 + 1),
|
goto = cursor::Goto(1, offset_y as u16 + 1),
|
||||||
text = &trimmed_line,
|
text = &trimmed_line,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -283,6 +317,7 @@ impl<'a> Ui<'a> {
|
||||||
let text = mat.text;
|
let text = mat.text;
|
||||||
|
|
||||||
let (offset_x, offset_y) = self.match_offsets(mat);
|
let (offset_x, offset_y) = self.match_offsets(mat);
|
||||||
|
let (offset_x, offset_y) = self.map_coords_to_wrapped_space(offset_x, offset_y);
|
||||||
|
|
||||||
Ui::render_matched_text(
|
Ui::render_matched_text(
|
||||||
stdout,
|
stdout,
|
||||||
|
|
@ -327,7 +362,12 @@ impl<'a> Ui<'a> {
|
||||||
/// and `hint` are rendered in their proper position.
|
/// and `hint` are rendered in their proper position.
|
||||||
fn full_render(&self, stdout: &mut dyn io::Write) -> () {
|
fn full_render(&self, stdout: &mut dyn io::Write) -> () {
|
||||||
// 1. Trim all lines and render non-empty ones.
|
// 1. Trim all lines and render non-empty ones.
|
||||||
Ui::render_base_text(stdout, &self.model.lines, &self.rendering_colors);
|
Ui::render_base_text(
|
||||||
|
stdout,
|
||||||
|
&self.model.lines,
|
||||||
|
&self.line_offsets,
|
||||||
|
&self.rendering_colors,
|
||||||
|
);
|
||||||
|
|
||||||
for (index, mat) in self.matches.iter().enumerate() {
|
for (index, mat) in self.matches.iter().enumerate() {
|
||||||
let focused = index == self.focus_index;
|
let focused = index == self.focus_index;
|
||||||
|
|
@ -514,6 +554,23 @@ impl<'a> Ui<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compute each line's actual y offset if displayed in a terminal of width
|
||||||
|
/// `term_width`.
|
||||||
|
fn get_line_offsets(lines: &Vec<&str>, term_width: u16) -> Vec<usize> {
|
||||||
|
lines
|
||||||
|
.iter()
|
||||||
|
.scan(0, |offset, &line| {
|
||||||
|
let value = *offset;
|
||||||
|
// amount of extra y space taken by this line
|
||||||
|
let extra = line.trim_end().len() / term_width as usize;
|
||||||
|
|
||||||
|
*offset = *offset + 1 + extra;
|
||||||
|
|
||||||
|
Some(value)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
/// Describes if, during rendering, a hint should aligned to the leading edge of
|
/// Describes if, during rendering, a hint should aligned to the leading edge of
|
||||||
/// the matched text, or to its trailing edge.
|
/// the matched text, or to its trailing edge.
|
||||||
#[derive(Debug, Clap)]
|
#[derive(Debug, Clap)]
|
||||||
|
|
@ -574,6 +631,8 @@ path: /usr/local/bin/git
|
||||||
|
|
||||||
path: /usr/local/bin/cargo";
|
path: /usr/local/bin/cargo";
|
||||||
let lines: Vec<&str> = content.split('\n').collect();
|
let lines: Vec<&str> = content.split('\n').collect();
|
||||||
|
let line_offsets: Vec<usize> = (0..lines.len()).collect();
|
||||||
|
|
||||||
let colors = UiColors {
|
let colors = UiColors {
|
||||||
text_fg: Box::new(color::Black),
|
text_fg: Box::new(color::Black),
|
||||||
text_bg: Box::new(color::White),
|
text_bg: Box::new(color::White),
|
||||||
|
|
@ -586,7 +645,7 @@ path: /usr/local/bin/cargo";
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut writer = vec![];
|
let mut writer = vec![];
|
||||||
Ui::render_base_text(&mut writer, &lines, &colors);
|
Ui::render_base_text(&mut writer, &lines, &line_offsets, &colors);
|
||||||
|
|
||||||
let goto1 = cursor::Goto(1, 1);
|
let goto1 = cursor::Goto(1, 1);
|
||||||
let goto2 = cursor::Goto(1, 2);
|
let goto2 = cursor::Goto(1, 2);
|
||||||
|
|
@ -815,6 +874,8 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - ";
|
||||||
let custom_regexes = vec![];
|
let custom_regexes = vec![];
|
||||||
let alphabet = alphabets::Alphabet("abcd".to_string());
|
let alphabet = alphabets::Alphabet("abcd".to_string());
|
||||||
let mut model = model::Model::new(content, &alphabet, &named_pat, &custom_regexes, false);
|
let mut model = model::Model::new(content, &alphabet, &named_pat, &custom_regexes, false);
|
||||||
|
let term_width: u16 = 80;
|
||||||
|
let line_offsets = get_line_offsets(&model.lines, term_width);
|
||||||
let rendering_colors = UiColors {
|
let rendering_colors = UiColors {
|
||||||
text_fg: Box::new(color::Black),
|
text_fg: Box::new(color::Black),
|
||||||
text_bg: Box::new(color::White),
|
text_bg: Box::new(color::White),
|
||||||
|
|
@ -830,6 +891,8 @@ Barcelona https://en.wikipedia.org/wiki/Barcelona - ";
|
||||||
// create a Ui without any match
|
// create a Ui without any match
|
||||||
let ui = Ui {
|
let ui = Ui {
|
||||||
model: &mut model,
|
model: &mut model,
|
||||||
|
term_width,
|
||||||
|
line_offsets,
|
||||||
matches: vec![], // no matches
|
matches: vec![], // no matches
|
||||||
lookup_trie: SequenceTrie::new(),
|
lookup_trie: SequenceTrie::new(),
|
||||||
focus_index: 0,
|
focus_index: 0,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue