mirror of
https://github.com/dandavison/delta.git
synced 2024-11-23 09:14:43 +03:00
Allow multiple hyperlinks per line
Previously only the last commit was linked. Do not link numbers (technically also commits), and stop after finding 12 commits on a line.
This commit is contained in:
parent
4ea8f9ab60
commit
959471392d
@ -2,7 +2,7 @@ use std::borrow::Cow;
|
||||
use std::path::Path;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use regex::{Captures, Regex};
|
||||
use regex::{Match, Matches, Regex};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::features::OptionValueFunction;
|
||||
@ -31,26 +31,62 @@ pub fn remote_from_config(cfg: &Option<&GitConfig>) -> Option<GitRemoteRepo> {
|
||||
cfg.and_then(GitConfig::get_remote_url)
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
// note: pure numbers are filtered out later again
|
||||
static ref COMMIT_HASH_REGEX: Regex = Regex::new(r"\b[0-9a-f]{8,40}\b").unwrap();
|
||||
}
|
||||
|
||||
pub fn format_commit_line_with_osc8_commit_hyperlink<'a>(
|
||||
line: &'a str,
|
||||
config: &Config,
|
||||
) -> Cow<'a, str> {
|
||||
if let Some(commit_link_format) = &config.hyperlinks_commit_link_format {
|
||||
COMMIT_LINE_REGEX.replace(line, |captures: &Captures| {
|
||||
let prefix = captures.get(1).map(|m| m.as_str()).unwrap_or("");
|
||||
let commit = captures.get(2).map(|m| m.as_str()).unwrap();
|
||||
let suffix = captures.get(3).map(|m| m.as_str()).unwrap_or("");
|
||||
let formatted_commit =
|
||||
format_osc8_hyperlink(&commit_link_format.replace("{commit}", commit), commit);
|
||||
format!("{prefix}{formatted_commit}{suffix}")
|
||||
})
|
||||
} else if let Some(repo) = remote_from_config(&config.git_config()) {
|
||||
COMMIT_LINE_REGEX.replace(line, |captures: &Captures| {
|
||||
format_commit_line_captures_with_osc8_commit_hyperlink(captures, &repo)
|
||||
})
|
||||
} else {
|
||||
Cow::from(line)
|
||||
// Given matches in a line, m = matches[0] and pos = 0: store line[pos..m.start()] first, then
|
||||
// store the T(line[m.start()..m.end()]) match transformation, then set pos = m.end().
|
||||
// Repeat for matches[1..]. Finally, store line[pos..].
|
||||
struct HyperlinkCommits<T>(T)
|
||||
where
|
||||
T: Fn(&str) -> String;
|
||||
impl<T: for<'b> Fn(&'b str) -> String> HyperlinkCommits<T> {
|
||||
fn _m(&self, result: &mut String, line: &str, m: &Match, prev_pos: usize) -> usize {
|
||||
result.push_str(&line[prev_pos..m.start()]);
|
||||
let commit = &line[m.start()..m.end()];
|
||||
// Do not link numbers, require at least one non-decimal:
|
||||
if commit.contains(|c| matches!(c, 'a'..='f')) {
|
||||
result.push_str(&format_osc8_hyperlink(&self.0(commit), commit));
|
||||
} else {
|
||||
result.push_str(commit);
|
||||
}
|
||||
m.end()
|
||||
}
|
||||
fn with_input(&self, line: &str, m0: &Match, matches123: &mut Matches) -> String {
|
||||
let mut result = String::new();
|
||||
let mut pos = self._m(&mut result, line, m0, 0);
|
||||
// limit number of matches per line, an exhaustive `find_iter` is O(len(line) * len(regex)^2)
|
||||
for m in matches123.take(12) {
|
||||
pos = self._m(&mut result, line, &m, pos);
|
||||
}
|
||||
result.push_str(&line[pos..]);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(commit_link_format) = &config.hyperlinks_commit_link_format {
|
||||
let mut matches = COMMIT_HASH_REGEX.find_iter(line);
|
||||
if let Some(first_match) = matches.next() {
|
||||
let result =
|
||||
HyperlinkCommits(|commit_hash| commit_link_format.replace("{commit}", commit_hash))
|
||||
.with_input(line, &first_match, &mut matches);
|
||||
return Cow::from(result);
|
||||
}
|
||||
} else if let Some(repo) = remote_from_config(&config.git_config()) {
|
||||
let mut matches = COMMIT_HASH_REGEX.find_iter(line);
|
||||
if let Some(first_match) = matches.next() {
|
||||
let result = HyperlinkCommits(|commit_hash| repo.format_commit_url(commit_hash))
|
||||
.with_input(line, &first_match, &mut matches);
|
||||
return Cow::from(result);
|
||||
}
|
||||
}
|
||||
Cow::from(line)
|
||||
}
|
||||
|
||||
/// Create a file hyperlink, displaying `text`.
|
||||
@ -89,38 +125,95 @@ fn format_osc8_hyperlink(url: &str, text: &str) -> String {
|
||||
)
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref COMMIT_LINE_REGEX: Regex = Regex::new("(.* )?([0-9a-f]{8,40})(.*)").unwrap();
|
||||
}
|
||||
|
||||
fn format_commit_line_captures_with_osc8_commit_hyperlink(
|
||||
captures: &Captures,
|
||||
repo: &GitRemoteRepo,
|
||||
) -> String {
|
||||
let commit = captures.get(2).unwrap().as_str();
|
||||
format!(
|
||||
"{prefix}{osc}8;;{url}{st}{commit}{osc}8;;{st}{suffix}",
|
||||
url = repo.format_commit_url(commit),
|
||||
commit = commit,
|
||||
prefix = captures.get(1).map(|m| m.as_str()).unwrap_or(""),
|
||||
suffix = captures.get(3).unwrap().as_str(),
|
||||
osc = "\x1b]",
|
||||
st = "\x1b\\"
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use std::iter::FromIterator;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::*;
|
||||
|
||||
use crate::{
|
||||
tests::integration_test_utils::{self, DeltaTest},
|
||||
tests::integration_test_utils::{self, make_config_from_args, DeltaTest},
|
||||
utils,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_formatted_hyperlinks() {
|
||||
let config = make_config_from_args(&["--hyperlinks-commit-link-format", "HERE:{commit}"]);
|
||||
|
||||
let line = "001234abcdf";
|
||||
let result = format_commit_line_with_osc8_commit_hyperlink(line, &config);
|
||||
assert_eq!(
|
||||
result,
|
||||
"\u{1b}]8;;HERE:001234abcdf\u{1b}\\001234abcdf\u{1b}]8;;\u{1b}\\",
|
||||
);
|
||||
|
||||
let line = "a2272718f0b398e48652ace17fca85c1962b3fc22"; // length: 41 > 40
|
||||
let result = format_commit_line_with_osc8_commit_hyperlink(line, &config);
|
||||
assert_eq!(result, "a2272718f0b398e48652ace17fca85c1962b3fc22",);
|
||||
|
||||
let line = "a2272718f0+b398e48652ace17f,ca85c1962b3fc2";
|
||||
let result = format_commit_line_with_osc8_commit_hyperlink(line, &config);
|
||||
assert_eq!(result, "\u{1b}]8;;HERE:a2272718f0\u{1b}\\a2272718f0\u{1b}]8;;\u{1b}\\+\u{1b}]8;;\
|
||||
HERE:b398e48652ace17f\u{1b}\\b398e48652ace17f\u{1b}]8;;\u{1b}\\,\u{1b}]8;;HERE:ca85c1962b3fc2\
|
||||
\u{1b}\\ca85c1962b3fc2\u{1b}]8;;\u{1b}\\");
|
||||
|
||||
let line = "This 01234abcdf Hash";
|
||||
let result = format_commit_line_with_osc8_commit_hyperlink(line, &config);
|
||||
assert_eq!(
|
||||
result,
|
||||
"This \u{1b}]8;;HERE:01234abcdf\u{1b}\\01234abcdf\u{1b}]8;;\u{1b}\\ Hash",
|
||||
);
|
||||
|
||||
let line =
|
||||
"Another 01234abcdf hash but also this one: dc623b084ad2dd14fe5d90189cacad5d49bfbfd3!";
|
||||
let result = format_commit_line_with_osc8_commit_hyperlink(line, &config);
|
||||
assert_eq!(
|
||||
result,
|
||||
"Another \u{1b}]8;;HERE:01234abcdf\u{1b}\\01234abcdf\u{1b}]8;;\u{1b}\\ hash but \
|
||||
also this one: \u{1b}]8;;HERE:dc623b084ad2dd14fe5d90189cacad5d49bfbfd3\u{1b}\
|
||||
\\dc623b084ad2dd14fe5d90189cacad5d49bfbfd3\u{1b}]8;;\u{1b}\\!"
|
||||
);
|
||||
|
||||
let line = "01234abcdf 03043baf30 12abcdef0 12345678";
|
||||
let result = format_commit_line_with_osc8_commit_hyperlink(line, &config);
|
||||
assert_eq!(
|
||||
result,
|
||||
"\u{1b}]8;;HERE:01234abcdf\u{1b}\\01234abcdf\u{1b}]8;;\u{1b}\\ \u{1b}]8;;\
|
||||
HERE:03043baf30\u{1b}\\03043baf30\u{1b}]8;;\u{1b}\\ \u{1b}]8;;HERE:12abcdef0\u{1b}\\\
|
||||
12abcdef0\u{1b}]8;;\u{1b}\\ 12345678"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hyperlinks_to_repo() {
|
||||
let mut config = make_config_from_args(&["--hyperlinks"]);
|
||||
config.git_config = GitConfig::for_testing();
|
||||
|
||||
let line = "This a589ff9debaefdd delta commit";
|
||||
let result = format_commit_line_with_osc8_commit_hyperlink(line, &config);
|
||||
assert_eq!(
|
||||
result,
|
||||
"This \u{1b}]8;;https://github.com/dandavison/delta/commit/a589ff9debaefdd\u{1b}\
|
||||
\\a589ff9debaefdd\u{1b}]8;;\u{1b}\\ delta commit",
|
||||
);
|
||||
|
||||
let line =
|
||||
"Another a589ff9debaefdd hash but also this one: c5696757c0827349a87daa95415656!";
|
||||
let result = format_commit_line_with_osc8_commit_hyperlink(line, &config);
|
||||
assert_eq!(
|
||||
result,
|
||||
"Another \u{1b}]8;;https://github.com/dandavison/delta/commit/a589ff9debaefdd\
|
||||
\u{1b}\\a589ff9debaefdd\u{1b}]8;;\u{1b}\\ hash but also this one: \u{1b}]8;;\
|
||||
https://github.com/dandavison/delta/commit/c5696757c0827349a87daa95415656\u{1b}\
|
||||
\\c5696757c0827349a87daa95415656\u{1b}]8;;\
|
||||
\u{1b}\\!"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_paths_and_hyperlinks_user_in_repo_root_dir() {
|
||||
// Expectations are uninfluenced by git's --relative and delta's relative_paths options.
|
||||
|
Loading…
Reference in New Issue
Block a user