mirror of
https://github.com/TECHNOFAB11/tmux-copyrat.git
synced 2025-12-12 16:10:07 +01:00
Merge pull request 'Cleanup tmux & add pattern for quoted strings' (#3) from regex into master
Reviewed-on: https://gitea.grael.cc/grael/tmux-feat: copyrat/pulls/3
This commit is contained in:
commit
62a5d10746
6 changed files with 114 additions and 74 deletions
|
|
@ -87,6 +87,8 @@ setup_pattern_binding "h" "--pattern-name sha"
|
|||
setup_pattern_binding "d" "--pattern-name datetime"
|
||||
# prefix + t + e searches for email addresses (see https://www.regular-expressions.info/email.html)
|
||||
setup_pattern_binding "e" "--pattern-name email"
|
||||
# prefix + t + q searches for strings inside single|double|backticks
|
||||
setup_pattern_binding "q" "-x quoted-single -x quoted-double -x quoted-tick"
|
||||
# prefix + t + D searches for docker shas
|
||||
setup_pattern_binding "D" "--pattern-name docker"
|
||||
# prefix + t + c searches for hex colors #aa00f5
|
||||
|
|
|
|||
|
|
@ -9,14 +9,14 @@ fn main() -> Result<(), error::ParseError> {
|
|||
let config = ConfigExt::initialize()?;
|
||||
|
||||
// Identify active pane and capture its content.
|
||||
let panes: Vec<tmux::Pane> = tmux::list_panes()?;
|
||||
let panes: Vec<tmux::Pane> = tmux::available_panes()?;
|
||||
|
||||
let active_pane = panes
|
||||
.into_iter()
|
||||
.find(|p| p.is_active)
|
||||
.expect("Exactly one tmux pane should be active in the current window.");
|
||||
|
||||
let buffer = tmux::capture_pane(&active_pane, &config.capture_region)?;
|
||||
let buffer = active_pane.capture(&config.capture_region)?;
|
||||
let lines = buffer.split('\n').collect::<Vec<_>>();
|
||||
|
||||
// We have to dance a little with Panes, because this process' i/o streams
|
||||
|
|
|
|||
|
|
@ -573,13 +573,48 @@ mod tests {
|
|||
assert_eq!(spans.get(0).unwrap().text, "2021-03-04T12:23:34");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn match_quoted_string() {
|
||||
let buffer =
|
||||
r#"Lorem 'first string' and "second string" and `rustc --explain E0223` ipsum."#;
|
||||
let lines = buffer.split('\n').collect::<Vec<_>>();
|
||||
|
||||
let use_all_patterns = false;
|
||||
use crate::textbuf::regexes::parse_pattern_name;
|
||||
let named_pat = vec![
|
||||
parse_pattern_name("quoted-single").unwrap(),
|
||||
parse_pattern_name("quoted-double").unwrap(),
|
||||
parse_pattern_name("quoted-tick").unwrap(),
|
||||
];
|
||||
|
||||
let custom = vec![];
|
||||
let alphabet = Alphabet("abcd".to_string());
|
||||
let reverse = false;
|
||||
let unique_hint = false;
|
||||
let spans = Model::new(
|
||||
&lines,
|
||||
&alphabet,
|
||||
use_all_patterns,
|
||||
&named_pat,
|
||||
&custom,
|
||||
reverse,
|
||||
unique_hint,
|
||||
)
|
||||
.spans;
|
||||
|
||||
assert_eq!(spans.len(), 3);
|
||||
assert_eq!(spans.get(0).unwrap().text, "first string");
|
||||
assert_eq!(spans.get(1).unwrap().text, "second string");
|
||||
assert_eq!(spans.get(2).unwrap().text, "rustc --explain E0223");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn priority_between_regexes() {
|
||||
let buffer = "Lorem [link](http://foo.bar) ipsum CUSTOM-52463 lorem ISSUE-123 lorem\nLorem /var/fd70b569/9999.log 52463 lorem\n Lorem 973113 lorem 123e4567-e89b-12d3-a456-426655440000 lorem 8888 lorem\n https://crates.io/23456/fd70b569 lorem";
|
||||
let lines = buffer.split('\n').collect::<Vec<_>>();
|
||||
let use_all_patterns = true;
|
||||
let named_pat = vec![];
|
||||
let custom: Vec<String> = ["CUSTOM-[0-9]{4,}", "ISSUE-[0-9]{3}"]
|
||||
let custom: Vec<String> = ["(CUSTOM-[0-9]{4,})", "(ISSUE-[0-9]{3})"]
|
||||
.iter()
|
||||
.map(|&s| s.to_string())
|
||||
.collect();
|
||||
|
|
|
|||
|
|
@ -133,17 +133,16 @@ fn find_raw_spans<'a>(
|
|||
if *pat_name != "ansi_colors" {
|
||||
let text = reg_match.as_str();
|
||||
|
||||
// In case the pattern has a capturing group, try obtaining
|
||||
// that text and start offset, else use the entire match.
|
||||
let (subtext, substart) = match reg
|
||||
// All patterns must have a capturing group: try obtaining
|
||||
// that text and start offset.
|
||||
let capture = reg
|
||||
.captures_iter(text)
|
||||
.next()
|
||||
.expect("This regex is guaranteed to match.")
|
||||
.get(1)
|
||||
{
|
||||
Some(capture) => (capture.as_str(), capture.start()),
|
||||
None => (text, 0),
|
||||
};
|
||||
.expect("This regex should have a capture group.");
|
||||
|
||||
let (subtext, substart) = (capture.as_str(), capture.start());
|
||||
|
||||
raw_spans.push(RawSpan {
|
||||
x: offset + reg_match.start() as i32 + substart as i32,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
//! This module defines the regex patterns.
|
||||
//!
|
||||
//! All patterns must have one capture group. The first group is used.
|
||||
|
||||
use crate::error;
|
||||
|
||||
pub(super) const EXCLUDE_PATTERNS: [(&str, &str); 1] =
|
||||
|
|
@ -6,37 +10,40 @@ pub(super) const EXCLUDE_PATTERNS: [(&str, &str); 1] =
|
|||
/// Holds all the regex patterns that are currently supported.
|
||||
///
|
||||
/// The email address was obtained at https://www.regular-expressions.info/email.html.
|
||||
/// Others were obtained from Ferran Basora.
|
||||
pub(super) const PATTERNS: [(&str, &str); 17] = [
|
||||
/// Some others were obtained from Ferran Basora, the rest is by me.
|
||||
pub(super) const PATTERNS: [(&str, &str); 20] = [
|
||||
("markdown-url", r"\[[^]]*\]\(([^)]+)\)"),
|
||||
(
|
||||
"url",
|
||||
r"((https?://|git@|git://|ssh://|ftp://|file:///)[^ \(\)\[\]\{\}]+)",
|
||||
),
|
||||
("email", r"\b[A-z0-9._%+-]+@[A-z0-9.-]+\.[A-z]{2,}\b"),
|
||||
("email", r"\b([A-z0-9._%+-]+@[A-z0-9.-]+\.[A-z]{2,})\b"),
|
||||
("diff-a", r"--- a/([^ ]+)"),
|
||||
("diff-b", r"\+\+\+ b/([^ ]+)"),
|
||||
("docker", r"sha256:([0-9a-f]{64})"),
|
||||
("path", r"(([.\w\-@~]+)?(/[.\w\-@]+)+)"),
|
||||
("hexcolor", r"#[0-9a-fA-F]{6}"),
|
||||
("hexcolor", r"(#[0-9a-fA-F]{6})"),
|
||||
(
|
||||
"uuid",
|
||||
r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}",
|
||||
r"([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})",
|
||||
),
|
||||
(
|
||||
"version",
|
||||
r"(v?\d{1,4}\.\d{1,4}(\.\d{1,4})?(-(alpha|beta|rc)(\.\d)?)?)[^.0-9s]",
|
||||
),
|
||||
("ipfs", r"Qm[0-9a-zA-Z]{44}"),
|
||||
("sha", r"[0-9a-f]{7,40}"),
|
||||
("ipv4", r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"),
|
||||
("ipv6", r"[A-f0-9:]+:+[A-f0-9:]+[%\w\d]+"),
|
||||
("pointer-address", r"0x[0-9a-fA-F]+"),
|
||||
("ipfs", r"(Qm[0-9a-zA-Z]{44})"),
|
||||
("sha", r"([0-9a-f]{7,40})"),
|
||||
("ipv4", r"(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})"),
|
||||
("ipv6", r"([A-f0-9:]+:+[A-f0-9:]+[%\w\d]+)"),
|
||||
("pointer-address", r"(0x[0-9a-fA-F]+)"),
|
||||
(
|
||||
"datetime",
|
||||
r"(\d{4}-?\d{2}-?\d{2}([ T]\d{2}:\d{2}:\d{2}(\.\d{3,9})?)?)",
|
||||
),
|
||||
("digits", r"[0-9]{4,}"),
|
||||
("quoted-single", r#"'([^']+)'"#),
|
||||
("quoted-double", r#""([^"]+)""#),
|
||||
("quoted-tick", r#"`([^`]+)`"#),
|
||||
("digits", r"([0-9]{4,})"),
|
||||
];
|
||||
|
||||
/// Type-safe string Pattern Name (newtype).
|
||||
|
|
|
|||
103
src/tmux.rs
103
src/tmux.rs
|
|
@ -53,11 +53,6 @@ impl FromStr for Pane {
|
|||
// Pane id must be start with '%' followed by a `u32`
|
||||
let id_str = iter.next().unwrap();
|
||||
let id = PaneId::from_str(id_str)?;
|
||||
// if !id_str.starts_with('%') {
|
||||
// return Err(ParseError::ExpectedPaneIdMarker);
|
||||
// }
|
||||
// let id = id_str[1..].parse::<u32>()?;
|
||||
// let id = format!("%{}", id);
|
||||
|
||||
let is_copy_mode = iter.next().unwrap().parse::<bool>()?;
|
||||
|
||||
|
|
@ -83,6 +78,53 @@ impl FromStr for Pane {
|
|||
}
|
||||
}
|
||||
|
||||
impl Pane {
|
||||
/// Returns the entire Pane content as a `String`.
|
||||
///
|
||||
/// The provided `region` specifies if the visible area is captured, or the
|
||||
/// entire history.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// In Tmux, the start line is the line at the top of the pane. The end line
|
||||
/// is the last line at the bottom of the pane.
|
||||
///
|
||||
/// - In normal mode, the index of the start line is always 0. The index of
|
||||
/// the end line is always the pane's height minus one. These do not need to
|
||||
/// be specified when capturing the pane's content.
|
||||
///
|
||||
/// - If navigating history in copy mode, the index of the start line is the
|
||||
/// opposite of the pane's scroll position. For instance a pane of 40 lines,
|
||||
/// scrolled up by 3 lines. It is necessarily in copy mode. Its start line
|
||||
/// index is `-3`. The index of the last line is `(40-1) - 3 = 36`.
|
||||
///
|
||||
pub fn capture(&self, region: &CaptureRegion) -> Result<String, ParseError> {
|
||||
let mut args_str = format!("capture-pane -t {pane_id} -J -p", pane_id = self.id);
|
||||
|
||||
let region_str = match region {
|
||||
CaptureRegion::VisibleArea => {
|
||||
if self.is_copy_mode && self.scroll_position > 0 {
|
||||
format!(
|
||||
" -S {start} -E {end}",
|
||||
start = -self.scroll_position,
|
||||
end = self.height - self.scroll_position - 1
|
||||
)
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
CaptureRegion::EntireHistory => String::from(" -S - -E -"),
|
||||
};
|
||||
|
||||
args_str.push_str(®ion_str);
|
||||
|
||||
let args: Vec<&str> = args_str.split(' ').collect();
|
||||
|
||||
let output = duct::cmd("tmux", &args).read()?;
|
||||
Ok(output)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct PaneId(String);
|
||||
|
||||
|
|
@ -90,12 +132,12 @@ impl FromStr for PaneId {
|
|||
type Err = ParseError;
|
||||
|
||||
/// Parse into PaneId. The `&str` must be start with '%'
|
||||
/// followed by a `u32`.
|
||||
/// followed by a `u16`.
|
||||
fn from_str(src: &str) -> Result<Self, Self::Err> {
|
||||
if !src.starts_with('%') {
|
||||
return Err(ParseError::ExpectedPaneIdMarker);
|
||||
}
|
||||
let id = src[1..].parse::<u32>()?;
|
||||
let id = src[1..].parse::<u16>()?;
|
||||
let id = format!("%{}", id);
|
||||
Ok(PaneId(id))
|
||||
}
|
||||
|
|
@ -114,7 +156,7 @@ impl fmt::Display for PaneId {
|
|||
}
|
||||
|
||||
/// Returns a list of `Pane` from the current tmux session.
|
||||
pub fn list_panes() -> Result<Vec<Pane>, ParseError> {
|
||||
pub fn available_panes() -> Result<Vec<Pane>, ParseError> {
|
||||
let args = vec![
|
||||
"list-panes",
|
||||
"-F",
|
||||
|
|
@ -162,51 +204,6 @@ pub fn get_options(prefix: &str) -> Result<HashMap<String, String>, ParseError>
|
|||
Ok(args)
|
||||
}
|
||||
|
||||
/// Returns the entire Pane content as a `String`.
|
||||
///
|
||||
/// The provided `region` specifies if the visible area is captured, or the
|
||||
/// entire history.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// In Tmux, the start line is the line at the top of the pane. The end line
|
||||
/// is the last line at the bottom of the pane.
|
||||
///
|
||||
/// - In normal mode, the index of the start line is always 0. The index of
|
||||
/// the end line is always the pane's height minus one. These do not need to
|
||||
/// be specified when capturing the pane's content.
|
||||
///
|
||||
/// - If navigating history in copy mode, the index of the start line is the
|
||||
/// opposite of the pane's scroll position. For instance a pane of 40 lines,
|
||||
/// scrolled up by 3 lines. It is necessarily in copy mode. Its start line
|
||||
/// index is `-3`. The index of the last line is `(40-1) - 3 = 36`.
|
||||
///
|
||||
pub fn capture_pane(pane: &Pane, region: &CaptureRegion) -> Result<String, ParseError> {
|
||||
let mut args_str = format!("capture-pane -t {pane_id} -J -p", pane_id = pane.id);
|
||||
|
||||
let region_str = match region {
|
||||
CaptureRegion::VisibleArea => {
|
||||
if pane.is_copy_mode && pane.scroll_position > 0 {
|
||||
format!(
|
||||
" -S {start} -E {end}",
|
||||
start = -pane.scroll_position,
|
||||
end = pane.height - pane.scroll_position - 1
|
||||
)
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
CaptureRegion::EntireHistory => String::from(" -S - -E -"),
|
||||
};
|
||||
|
||||
args_str.push_str(®ion_str);
|
||||
|
||||
let args: Vec<&str> = args_str.split(' ').collect();
|
||||
|
||||
let output = duct::cmd("tmux", &args).read()?;
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// Ask tmux to swap the current Pane with the target_pane (uses Tmux format).
|
||||
pub fn swap_pane_with(target_pane: &str) -> Result<(), ParseError> {
|
||||
// -Z: keep the window zoomed if it was zoomed.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue