This commit is contained in:
Gabriel 2025-10-14 10:05:11 +00:00 committed by GitHub
commit f9c4122d72
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 88 additions and 17 deletions

View file

@ -13,8 +13,8 @@ pub struct Input {
pub line: String,
}
fn extract_elements(argstr: &str) -> Result<(&str, &str, &str)> {
let mut parts = argstr.split(deser::terminal::DELIMITER).skip(3);
fn extract_elements(argstr: &str, skip_columns: usize) -> Result<(&str, &str, &str)> {
let mut parts = argstr.split(deser::terminal::DELIMITER).skip(skip_columns);
let tags = parts.next().context("No `tags` element provided.")?;
let comment = parts.next().context("No `comment` element provided.")?;
let snippet = parts.next().context("No `snippet` element provided.")?;
@ -25,7 +25,10 @@ impl Runnable for Input {
fn run(&self) -> Result<()> {
let line = &self.line;
let (tags, comment, snippet) = extract_elements(line)?;
// In multiline mode, only 1 column is used for rendering tags, comment an snippet with zfs.
// In legacy mode, using 3 columns for tags, comment and snippet allow permuting their order with --with-nth of fzf.
let skip_columns = if CONFIG.multiline() { 1 } else { 3 };
let (tags, comment, snippet) = extract_elements(line, skip_columns)?;
println!(
"{comment} {tags} \n{snippet}",

View file

@ -36,7 +36,8 @@ use clap::{crate_version, Parser, Subcommand};
navi --fzf-overrides-var '--no-select-1' # same, but for variable selection
navi --fzf-overrides '--nth 1,2' # only consider the first two columns for search
navi --fzf-overrides '--no-exact' # use looser search algorithm
navi --tag-rules='git,!checkout' # show non-checkout git snippets only")]
navi --tag-rules='git,!checkout' # show non-checkout git snippets only
navi --multiline # show multiline snippets (adds support for newlines in snippet/comment)")]
#[clap(version = crate_version!())]
pub(super) struct ClapConfig {
/// Colon-separated list of paths containing .cheat files
@ -80,6 +81,10 @@ pub(super) struct ClapConfig {
#[arg(long, allow_hyphen_values = true)]
pub fzf_overrides_var: Option<String>,
/// When set, show multiline snippets in the terminal listing (split snippet/comment across lines)
#[arg(long)]
pub multiline: bool,
/// Finder application to use
#[arg(long, ignore_case = true)]
pub finder: Option<FinderChoice>,

View file

@ -169,6 +169,10 @@ impl Config {
self.yaml.style.tag.width_percentage
}
pub fn multiline(&self) -> bool {
self.clap.multiline || self.yaml.style.multiline
}
pub fn comment_width_percentage(&self) -> u16 {
self.yaml.style.comment.width_percentage
}

View file

@ -38,6 +38,7 @@ pub struct Style {
pub tag: ColorWidth,
pub comment: ColorWidth,
pub snippet: ColorWidth,
pub multiline: bool,
}
#[derive(Deserialize, Debug)]
@ -167,6 +168,7 @@ impl Default for Style {
min_width: 45,
},
snippet: Default::default(),
multiline: false,
}
}
}

View file

@ -33,21 +33,66 @@ lazy_static! {
pub fn write(item: &Item) -> String {
let (tag_width_percentage, comment_width_percentage, snippet_width_percentage) = *COLUMN_WIDTHS;
format!(
"{tags_short}{delimiter}{comment_short}{delimiter}{snippet_short}{delimiter}{tags}{delimiter}{comment}{delimiter}{snippet}{delimiter}{file_index}{delimiter}\n",
let snippet = &item.snippet.trim_end_matches(LINE_SEPARATOR);
let printer_item = if CONFIG.multiline() {
let separator_count = max(
snippet.matches(LINE_SEPARATOR).count(),
item.comment.matches(LINE_SEPARATOR).count(),
);
let splitted_comment = item.comment.split(LINE_SEPARATOR).collect::<Vec<&str>>();
let splitted_snippet = snippet.split(LINE_SEPARATOR).collect::<Vec<&str>>();
(0..=separator_count)
.map(|i| {
format!(
"{tags_short}{delimiter}{comment_line_i}{delimiter}{snippet_line_i}",
tags_short = style(limit_str(
if i == 0 { &item.tags } else { "" },
tag_width_percentage
))
.with(CONFIG.tag_color()),
comment_line_i = style(limit_str(
splitted_comment.get(i).unwrap_or(&""),
comment_width_percentage
))
.with(CONFIG.comment_color()),
snippet_line_i = style(limit_str(
splitted_snippet.get(i).unwrap_or(&""),
snippet_width_percentage
))
.with(CONFIG.snippet_color()),
delimiter = " ",
)
})
.collect::<Vec<String>>()
.join("\n")
} else {
format!(
"{tags_short}{delimiter}{comment_short}{delimiter}{snippet_short}",
tags_short = style(limit_str(&item.tags, tag_width_percentage)).with(CONFIG.tag_color()),
comment_short = style(limit_str(&item.comment, comment_width_percentage)).with(CONFIG.comment_color()),
snippet_short = style(limit_str(&fix_newlines(&item.snippet), snippet_width_percentage)).with(CONFIG.snippet_color()),
comment_short = style(limit_str(&fix_newlines(&item.comment), comment_width_percentage))
.with(CONFIG.comment_color()),
snippet_short = style(limit_str(&fix_newlines(&item.snippet), snippet_width_percentage))
.with(CONFIG.snippet_color()),
delimiter = DELIMITER,
)
};
format!(
"{printer_item}{delimiter}{tags}{delimiter}{comment}{delimiter}{snippet}{delimiter}{file_index}{delimiter}\0",
printer_item = printer_item,
tags = item.tags,
comment = item.comment,
delimiter = DELIMITER,
snippet = &item.snippet.trim_end_matches(LINE_SEPARATOR),
snippet = snippet,
file_index = item.file_index.unwrap_or(0),
)
}
pub fn read(raw_snippet: &str, is_single: bool) -> Result<(&str, Item)> {
let mut lines = raw_snippet.split('\n');
let mut lines = raw_snippet.split('\0');
let key = if is_single {
"enter"
} else {
@ -56,11 +101,12 @@ pub fn read(raw_snippet: &str, is_single: bool) -> Result<(&str, Item)> {
.context("Key was promised but not present in `selections`")?
};
let skip_columns = if CONFIG.multiline() { 1 } else { 3 };
let mut parts = lines
.next()
.context("No more parts in `selections`")?
.split(DELIMITER)
.skip(3);
.skip(skip_columns);
let tags = parts.next().unwrap_or("").into();
let comment = parts.next().unwrap_or("").into();

View file

@ -119,7 +119,8 @@ impl FinderChoice {
]);
if !opts.show_all_columns {
command.args(["--with-nth", "1,2,3"]);
let with_nth = if CONFIG.multiline() { "1" } else { "1,2,3" };
command.args(["--read0", "--print0", "--with-nth", with_nth]);
}
if !opts.prevent_select1 {

View file

@ -225,9 +225,10 @@ impl<'a> Parser<'a> {
let write_fn = self.write_fn;
self.writer
return self
.writer
.write_all(write_fn(item).as_bytes())
.context("Failed to write command to finder's stdin")
.context("Failed to write command to finder's stdin");
}
pub fn read_lines(
@ -265,6 +266,7 @@ impl<'a> Parser<'a> {
else if line.starts_with('%') {
should_break = self.write_cmd(&item).is_err();
item.snippet = String::from("");
item.comment = String::from("");
item.tags = without_prefix(&line);
}
// dependency
@ -281,9 +283,17 @@ impl<'a> Parser<'a> {
}
// comment
else if line.starts_with('#') {
should_break = self.write_cmd(&item).is_err();
item.snippet = String::from("");
item.comment = without_prefix(&line);
// if current item has a snippet, write it to the finder and start a new item
if !item.snippet.is_empty() {
should_break = self.write_cmd(&item).is_err();
item.snippet = String::from("");
item.comment = without_prefix(&line);
} else {
if !item.comment.is_empty() {
item.comment.push_str(deser::LINE_SEPARATOR);
}
item.comment.push_str(&without_prefix(&line));
}
}
// variable
else if !variable_cmd.is_empty()