mirror of
https://github.com/TECHNOFAB11/tmux-copyrat.git
synced 2025-12-12 08:00:08 +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"
|
setup_pattern_binding "d" "--pattern-name datetime"
|
||||||
# prefix + t + e searches for email addresses (see https://www.regular-expressions.info/email.html)
|
# prefix + t + e searches for email addresses (see https://www.regular-expressions.info/email.html)
|
||||||
setup_pattern_binding "e" "--pattern-name email"
|
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
|
# prefix + t + D searches for docker shas
|
||||||
setup_pattern_binding "D" "--pattern-name docker"
|
setup_pattern_binding "D" "--pattern-name docker"
|
||||||
# prefix + t + c searches for hex colors #aa00f5
|
# prefix + t + c searches for hex colors #aa00f5
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,14 @@ fn main() -> Result<(), error::ParseError> {
|
||||||
let config = ConfigExt::initialize()?;
|
let config = ConfigExt::initialize()?;
|
||||||
|
|
||||||
// Identify active pane and capture its content.
|
// 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
|
let active_pane = panes
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|p| p.is_active)
|
.find(|p| p.is_active)
|
||||||
.expect("Exactly one tmux pane should be active in the current window.");
|
.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<_>>();
|
let lines = buffer.split('\n').collect::<Vec<_>>();
|
||||||
|
|
||||||
// We have to dance a little with Panes, because this process' i/o streams
|
// 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");
|
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]
|
#[test]
|
||||||
fn priority_between_regexes() {
|
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 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 lines = buffer.split('\n').collect::<Vec<_>>();
|
||||||
let use_all_patterns = true;
|
let use_all_patterns = true;
|
||||||
let named_pat = vec![];
|
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()
|
.iter()
|
||||||
.map(|&s| s.to_string())
|
.map(|&s| s.to_string())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
|
||||||
|
|
@ -133,17 +133,16 @@ fn find_raw_spans<'a>(
|
||||||
if *pat_name != "ansi_colors" {
|
if *pat_name != "ansi_colors" {
|
||||||
let text = reg_match.as_str();
|
let text = reg_match.as_str();
|
||||||
|
|
||||||
// In case the pattern has a capturing group, try obtaining
|
// All patterns must have a capturing group: try obtaining
|
||||||
// that text and start offset, else use the entire match.
|
// that text and start offset.
|
||||||
let (subtext, substart) = match reg
|
let capture = reg
|
||||||
.captures_iter(text)
|
.captures_iter(text)
|
||||||
.next()
|
.next()
|
||||||
.expect("This regex is guaranteed to match.")
|
.expect("This regex is guaranteed to match.")
|
||||||
.get(1)
|
.get(1)
|
||||||
{
|
.expect("This regex should have a capture group.");
|
||||||
Some(capture) => (capture.as_str(), capture.start()),
|
|
||||||
None => (text, 0),
|
let (subtext, substart) = (capture.as_str(), capture.start());
|
||||||
};
|
|
||||||
|
|
||||||
raw_spans.push(RawSpan {
|
raw_spans.push(RawSpan {
|
||||||
x: offset + reg_match.start() as i32 + substart as i32,
|
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;
|
use crate::error;
|
||||||
|
|
||||||
pub(super) const EXCLUDE_PATTERNS: [(&str, &str); 1] =
|
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.
|
/// Holds all the regex patterns that are currently supported.
|
||||||
///
|
///
|
||||||
/// The email address was obtained at https://www.regular-expressions.info/email.html.
|
/// The email address was obtained at https://www.regular-expressions.info/email.html.
|
||||||
/// Others were obtained from Ferran Basora.
|
/// Some others were obtained from Ferran Basora, the rest is by me.
|
||||||
pub(super) const PATTERNS: [(&str, &str); 17] = [
|
pub(super) const PATTERNS: [(&str, &str); 20] = [
|
||||||
("markdown-url", r"\[[^]]*\]\(([^)]+)\)"),
|
("markdown-url", r"\[[^]]*\]\(([^)]+)\)"),
|
||||||
(
|
(
|
||||||
"url",
|
"url",
|
||||||
r"((https?://|git@|git://|ssh://|ftp://|file:///)[^ \(\)\[\]\{\}]+)",
|
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-a", r"--- a/([^ ]+)"),
|
||||||
("diff-b", r"\+\+\+ b/([^ ]+)"),
|
("diff-b", r"\+\+\+ b/([^ ]+)"),
|
||||||
("docker", r"sha256:([0-9a-f]{64})"),
|
("docker", r"sha256:([0-9a-f]{64})"),
|
||||||
("path", r"(([.\w\-@~]+)?(/[.\w\-@]+)+)"),
|
("path", r"(([.\w\-@~]+)?(/[.\w\-@]+)+)"),
|
||||||
("hexcolor", r"#[0-9a-fA-F]{6}"),
|
("hexcolor", r"(#[0-9a-fA-F]{6})"),
|
||||||
(
|
(
|
||||||
"uuid",
|
"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",
|
"version",
|
||||||
r"(v?\d{1,4}\.\d{1,4}(\.\d{1,4})?(-(alpha|beta|rc)(\.\d)?)?)[^.0-9s]",
|
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}"),
|
("ipfs", r"(Qm[0-9a-zA-Z]{44})"),
|
||||||
("sha", r"[0-9a-f]{7,40}"),
|
("sha", r"([0-9a-f]{7,40})"),
|
||||||
("ipv4", r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"),
|
("ipv4", r"(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})"),
|
||||||
("ipv6", r"[A-f0-9:]+:+[A-f0-9:]+[%\w\d]+"),
|
("ipv6", r"([A-f0-9:]+:+[A-f0-9:]+[%\w\d]+)"),
|
||||||
("pointer-address", r"0x[0-9a-fA-F]+"),
|
("pointer-address", r"(0x[0-9a-fA-F]+)"),
|
||||||
(
|
(
|
||||||
"datetime",
|
"datetime",
|
||||||
r"(\d{4}-?\d{2}-?\d{2}([ T]\d{2}:\d{2}:\d{2}(\.\d{3,9})?)?)",
|
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).
|
/// 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`
|
// Pane id must be start with '%' followed by a `u32`
|
||||||
let id_str = iter.next().unwrap();
|
let id_str = iter.next().unwrap();
|
||||||
let id = PaneId::from_str(id_str)?;
|
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>()?;
|
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)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct PaneId(String);
|
pub struct PaneId(String);
|
||||||
|
|
||||||
|
|
@ -90,12 +132,12 @@ impl FromStr for PaneId {
|
||||||
type Err = ParseError;
|
type Err = ParseError;
|
||||||
|
|
||||||
/// Parse into PaneId. The `&str` must be start with '%'
|
/// 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> {
|
fn from_str(src: &str) -> Result<Self, Self::Err> {
|
||||||
if !src.starts_with('%') {
|
if !src.starts_with('%') {
|
||||||
return Err(ParseError::ExpectedPaneIdMarker);
|
return Err(ParseError::ExpectedPaneIdMarker);
|
||||||
}
|
}
|
||||||
let id = src[1..].parse::<u32>()?;
|
let id = src[1..].parse::<u16>()?;
|
||||||
let id = format!("%{}", id);
|
let id = format!("%{}", id);
|
||||||
Ok(PaneId(id))
|
Ok(PaneId(id))
|
||||||
}
|
}
|
||||||
|
|
@ -114,7 +156,7 @@ impl fmt::Display for PaneId {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a list of `Pane` from the current tmux session.
|
/// 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![
|
let args = vec![
|
||||||
"list-panes",
|
"list-panes",
|
||||||
"-F",
|
"-F",
|
||||||
|
|
@ -162,51 +204,6 @@ pub fn get_options(prefix: &str) -> Result<HashMap<String, String>, ParseError>
|
||||||
Ok(args)
|
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).
|
/// Ask tmux to swap the current Pane with the target_pane (uses Tmux format).
|
||||||
pub fn swap_pane_with(target_pane: &str) -> Result<(), ParseError> {
|
pub fn swap_pane_with(target_pane: &str) -> Result<(), ParseError> {
|
||||||
// -Z: keep the window zoomed if it was zoomed.
|
// -Z: keep the window zoomed if it was zoomed.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue