From 5ff015d12e870346c1a3f8ef661ad9e4f355b4e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denys=20S=C3=A9guret?= Date: Fri, 12 Apr 2024 09:28:25 +0200 Subject: optionally show lines surrounding a match in preview (#866) New settings: ``` ############################################################### # lines around matching line in filtered preview # # When searching the content of a file, you can have either # only the matching lines displayed, or some of the surrounding # ones too. # lines_before_match_in_preview: 1 lines_after_match_in_preview: 1 ``` Fix #756 --- CHANGELOG.md | 2 + bacon.toml | 1 + resources/default-conf/conf.hjson | 10 + .../default-conf/skins/catppuccin-macchiato.hjson | 2 + .../default-conf/skins/catppuccin-mocha.hjson | 2 + resources/default-conf/skins/dark-blue.hjson | 1 + resources/default-conf/skins/dark-gruvbox.hjson | 1 + resources/default-conf/skins/dark-orange.hjson | 1 + resources/default-conf/skins/solarized-light.hjson | 1 + resources/default-conf/skins/white.hjson | 1 + src/app/app_context.rs | 8 + src/conf/conf.rs | 90 ++++--- src/preview/preview.rs | 16 ++ src/preview/preview_state.rs | 8 + src/skin/style_map.rs | 1 + src/syntactic/syntactic_view.rs | 286 ++++++++++++++------- src/verb/execution_builder.rs | 2 +- website/docs/conf_file.md | 13 + website/docs/skins.md | 2 + 19 files changed, 320 insertions(+), 128 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41d4967..b6b9326 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ ### next +- optionally display lines surrounding a matching one in display, with `lines_before_match_in_preview` and `lines_after_match_in_preview` - Fix #756 +- filtered preview: jump between matches with `:next_match` (default: `tab`) and `:previous_match` (default `shift-tab`) - display setuid, setgid and sticky bits in permission - Fix #863 ### v1.36.1 - 2024-03-11 diff --git a/bacon.toml b/bacon.toml index 7f70310..ee7b106 100644 --- a/bacon.toml +++ b/bacon.toml @@ -43,6 +43,7 @@ command = [ "-A", "clippy::match_like_matches_macro", "-A", "clippy::module_inception", "-A", "clippy::needless_bool", + "-A", "clippy::needless_range_loop", "-A", "clippy::neg_multiply", "-A", "clippy::vec_init_then_push", ] diff --git a/resources/default-conf/conf.hjson b/resources/default-conf/conf.hjson index b5f1b3d..29f8909 100644 --- a/resources/default-conf/conf.hjson +++ b/resources/default-conf/conf.hjson @@ -206,6 +206,16 @@ content_search_max_file_size: 10MB # enable_kitty_keyboard: false +############################################################### +# lines around matching line in filtered preview +# +# When searching the content of a file, you can have either +# only the matching lines displayed, or some of the surrounding +# ones too. +# +lines_before_match_in_preview: 1 +lines_after_match_in_preview: 1 + ############################################################### # Imports # diff --git a/resources/default-conf/skins/catppuccin-macchiato.hjson b/resources/default-conf/skins/catppuccin-macchiato.hjson index 897faec..21a3bf1 100644 --- a/resources/default-conf/skins/catppuccin-macchiato.hjson +++ b/resources/default-conf/skins/catppuccin-macchiato.hjson @@ -59,6 +59,8 @@ skin: { preview: rgb(202, 211, 245) rgb(30, 32, 48) # fg:$overlay0 preview_line_number: rgb(110, 115, 141) none + # fg:$overlay0 + preview_separator: rgb(110, 115, 141) none # ### MATCH # diff --git a/resources/default-conf/skins/catppuccin-mocha.hjson b/resources/default-conf/skins/catppuccin-mocha.hjson index 514ac3a..6ac1b92 100644 --- a/resources/default-conf/skins/catppuccin-mocha.hjson +++ b/resources/default-conf/skins/catppuccin-mocha.hjson @@ -59,6 +59,8 @@ skin: { preview: rgb(205, 214, 244) rgb(24, 24, 37) # fg:$overlay0 preview_line_number: rgb(108, 112, 134) none + # fg:$overlay0 + preview_separator: rgb(108, 112, 134) none # ### MATCH # diff --git a/resources/default-conf/skins/dark-blue.hjson b/resources/default-conf/skins/dark-blue.hjson index 0ceb2b9..7dbc7f4 100644 --- a/resources/default-conf/skins/dark-blue.hjson +++ b/resources/default-conf/skins/dark-blue.hjson @@ -79,6 +79,7 @@ skin: { preview: gray(20) gray(1) / gray(18) gray(2) preview_title: gray(23) gray(2) / gray(21) gray(2) preview_line_number: gray(12) gray(3) + preview_separator: gray(5) None preview_match: None ansi(29) hex_null: gray(8) None hex_ascii_graphic: gray(18) None diff --git a/resources/default-conf/skins/dark-gruvbox.hjson b/resources/default-conf/skins/dark-gruvbox.hjson index 2804729..e62d583 100644 --- a/resources/default-conf/skins/dark-gruvbox.hjson +++ b/resources/default-conf/skins/dark-gruvbox.hjson @@ -66,6 +66,7 @@ skin: { preview_title: rgb(235, 219, 178) rgb(40, 40, 40) / rgb(189, 174, 147) rgb(40, 40, 40) preview: rgb(235, 219, 178) rgb(40, 40, 40) / rgb(235, 219, 178) rgb(40, 40, 40) preview_line_number: rgb(124, 111, 100) None / rgb(124, 111, 100) rgb(40, 40, 40) + preview_separator: rgb(70, 70, 80) None / rgb(60, 60, 60) None preview_match: None ansi(29) Bold hex_null: rgb(189, 174, 147) None hex_ascii_graphic: rgb(213, 196, 161) None diff --git a/resources/default-conf/skins/dark-orange.hjson b/resources/default-conf/skins/dark-orange.hjson index 9fb0eee..8e2f845 100644 --- a/resources/default-conf/skins/dark-orange.hjson +++ b/resources/default-conf/skins/dark-orange.hjson @@ -75,6 +75,7 @@ skin: { help_table_border: ansi(239) None preview: gray(20) gray(1) / gray(18) gray(2) preview_line_number: gray(12) gray(3) + preview_separator: ansi(94) None / gray(3) None preview_match: None ansi(29) hex_null: gray(11) None hex_ascii_graphic: gray(18) None diff --git a/resources/default-conf/skins/solarized-light.hjson b/resources/default-conf/skins/solarized-light.hjson index b9d7907..6e19275 100644 --- a/resources/default-conf/skins/solarized-light.hjson +++ b/resources/default-conf/skins/solarized-light.hjson @@ -102,6 +102,7 @@ skin: { preview_title: "rgb(147, 161, 161) rgb(238, 232, 213)" preview: "rgb(101, 123, 131) rgb(253, 246, 227) / rgb(147, 161, 161) rgb(238, 232, 213)" preview_line_number: "rgb(147, 161, 161) rgb(238, 232, 213)" + preview_separator: "rgb(147, 161, 161) rgb(238, 232, 213)" preview_match: "None ansi(29)" staging_area_title: "gray(22) rgb(253, 246, 227)" good_to_bad_0: ansi(28) diff --git a/resources/default-conf/skins/white.hjson b/resources/default-conf/skins/white.hjson index 1e8d9b9..908e648 100644 --- a/resources/default-conf/skins/white.hjson +++ b/resources/default-conf/skins/white.hjson @@ -62,6 +62,7 @@ skin: { preview_title: gray(3) None / gray(5) None preview: gray(5) gray(23) / gray(7) gray(23) preview_line_number: gray(6) gray(20) + preview_separator: gray(7) None / gray(18) None preview_match: None ansi(29) Underlined hex_null: gray(15) None hex_ascii_graphic: gray(2) None diff --git a/src/app/app_context.rs b/src/app/app_context.rs index 35c1afa..b772c02 100644 --- a/src/app/app_context.rs +++ b/src/app/app_context.rs @@ -121,6 +121,12 @@ pub struct AppContext { pub keyboard_enhanced: bool, pub kitty_graphics_transmission: TransmissionMedium, + + /// Number of lines to display after a match in the preview + pub lines_after_match_in_preview: usize, + + /// Number of lines to display before a match in the preview + pub lines_before_match_in_preview: usize, } impl AppContext { @@ -221,6 +227,8 @@ impl AppContext { keyboard_enhanced: false, kitty_graphics_transmission: config.kitty_graphics_transmission .unwrap_or_default(), + lines_after_match_in_preview: config.lines_after_match_in_preview.unwrap_or(0), + lines_before_match_in_preview: config.lines_before_match_in_preview.unwrap_or(0), }) } /// Return the --cmd argument, coming from the launch arguments (prefered) diff --git a/src/conf/conf.rs b/src/conf/conf.rs index aaeb147..42dd56e 100644 --- a/src/conf/conf.rs +++ b/src/conf/conf.rs @@ -43,88 +43,94 @@ macro_rules! overwrite_map { /// The configuration read from conf.toml or conf.hjson file(s) #[derive(Default, Clone, Debug, Deserialize)] pub struct Conf { - /// the files used to load this configuration - #[serde(skip)] - pub files: Vec, + #[serde(alias="capture-mouse")] + pub capture_mouse: Option, - #[serde(alias="default-flags")] - pub default_flags: Option, // the flags to apply before cli ones + #[serde(alias="cols-order")] + pub cols_order: Option, + + #[serde(alias="content-search-max-file-size", deserialize_with="file_size::deserialize", default)] + pub content_search_max_file_size: Option, #[serde(alias="date-time-format")] pub date_time_format: Option, - #[serde(default)] - pub verbs: Vec, - - pub skin: Option>, - - #[serde(default, alias="special-paths")] - pub special_paths: HashMap, - - #[serde(alias="search-modes")] - pub search_modes: Option>, + #[serde(alias="default-flags")] + pub default_flags: Option, // the flags to apply before cli ones /// Obsolete, kept for compatibility: you should now use capture_mouse #[serde(alias="disable-mouse-capture")] pub disable_mouse_capture: Option, - #[serde(alias="capture-mouse")] - pub capture_mouse: Option, - - #[serde(alias="cols-order")] - pub cols_order: Option, - - #[serde(alias="show-selection-mark")] - pub show_selection_mark: Option, + #[serde(alias="enable-keyboard-enhancements")] + pub enable_kitty_keyboard: Option, #[serde(default, alias="ext-colors")] pub ext_colors: AHashMap, - #[serde(alias="syntax-theme")] - pub syntax_theme: Option, + pub file_sum_threads_count: Option, - #[serde(alias="true-colors")] - pub true_colors: Option, + /// the files used to load this configuration + #[serde(skip)] + pub files: Vec, #[serde(alias="icon-theme")] pub icon_theme: Option, - pub modal: Option, + #[serde(default)] + pub imports: Vec, /// the initial mode (only relevant when modal is true) #[serde(alias="initial-mode")] pub initial_mode: Option, - pub max_panels_count: Option, + #[serde(alias="kitty-graphics-transmission")] + pub kitty_graphics_transmission: Option, - #[serde(alias="quit-on-last-cancel")] - pub quit_on_last_cancel: Option, + #[serde(alias="lines-after-match-in-preview")] + pub lines_after_match_in_preview: Option, - pub file_sum_threads_count: Option, + #[serde(alias="lines-before-match-in-preview")] + pub lines_before_match_in_preview: Option, + + pub max_panels_count: Option, #[serde(alias="max_staged_count")] pub max_staged_count: Option, - #[serde(default)] - pub imports: Vec, + pub modal: Option, + + #[serde(alias="quit-on-last-cancel")] + pub quit_on_last_cancel: Option, + + #[serde(alias="search-modes")] + pub search_modes: Option>, #[serde(alias="show-matching-characters-on-path-searches")] pub show_matching_characters_on_path_searches: Option, - #[serde(alias="content-search-max-file-size", deserialize_with="file_size::deserialize", default)] - pub content_search_max_file_size: Option, + #[serde(alias="show-selection-mark")] + pub show_selection_mark: Option, + + pub skin: Option>, + + #[serde(default, alias="special-paths")] + pub special_paths: HashMap, + + #[serde(alias="syntax-theme")] + pub syntax_theme: Option, #[serde(alias="terminal-title")] pub terminal_title: Option, + #[serde(alias="true-colors")] + pub true_colors: Option, + #[serde(alias="update-work-dir")] pub update_work_dir: Option, - #[serde(alias="enable-keyboard-enhancements")] - pub enable_kitty_keyboard: Option, - - #[serde(alias="kitty-graphics-transmission")] - pub kitty_graphics_transmission: Option, + #[serde(default)] + pub verbs: Vec, // BEWARE: entries added here won't be usable unless also // added in read_file! @@ -213,6 +219,8 @@ impl Conf { overwrite!(self, update_work_dir, conf); overwrite!(self, enable_kitty_keyboard, conf); overwrite!(self, kitty_graphics_transmission, conf); + overwrite!(self, lines_after_match_in_preview, conf); + overwrite!(self, lines_before_match_in_preview, conf); self.verbs.append(&mut conf.verbs); // the following maps are "additive": we can add entries from several // config files and they still make sense diff --git a/src/preview/preview.rs b/src/preview/preview.rs index b2af52c..be19570 100644 --- a/src/preview/preview.rs +++ b/src/preview/preview.rs @@ -256,6 +256,22 @@ impl Preview { _ => {} } } + + pub fn previous_match(&mut self) { + if let Self::Syntactic(sv) = self { + sv.previous_match(); + } else { + self.move_selection(-1, true); + } + } + pub fn next_match(&mut self) { + if let Self::Syntactic(sv) = self { + sv.next_match(); + } else { + self.move_selection(1, true); + } + } + pub fn select_first(&mut self) { match self { Self::Dir(dv) => dv.select_first(), diff --git a/src/preview/preview_state.rs b/src/preview/preview_state.rs index ca27d95..470169a 100644 --- a/src/preview/preview_state.rs +++ b/src/preview/preview_state.rs @@ -393,6 +393,14 @@ impl PanelState for PreviewState { self.mut_preview().select_last(); Ok(CmdResult::Keep) } + Internal::previous_match => { + self.mut_preview().previous_match(); + Ok(CmdResult::Keep) + } + Internal::next_match => { + self.mut_preview().next_match(); + Ok(CmdResult::Keep) + } Internal::preview_image => self.set_mode(PreviewMode::Image, con), Internal::preview_text => self.set_mode(PreviewMode::Text, con), Internal::preview_binary => self.set_mode(PreviewMode::Hex, con), diff --git a/src/skin/style_map.rs b/src/skin/style_map.rs index 20bb2ca..91d14f9 100644 --- a/src/skin/style_map.rs +++ b/src/skin/style_map.rs @@ -214,6 +214,7 @@ StyleMap! { preview: gray(20), gray(1), [] / gray(18), gray(2), [] preview_title: gray(23), gray(2), [] / gray(21), gray(2), [] preview_line_number: gray(12), gray(3), [] + preview_separator: gray(7), None, [] preview_match: None, ansi(29), [] hex_null: gray(8), None, [] hex_ascii_graphic: gray(18), None, [] diff --git a/src/syntactic/syntactic_view.rs b/src/syntactic/syntactic_view.rs index b23b773..12cc168 100644 --- a/src/syntactic/syntactic_view.rs +++ b/src/syntactic/syntactic_view.rs @@ -23,9 +23,12 @@ use { str, }, syntect::highlighting::Style, - termimad::{Area, CropWriter, SPACE_FILLING}, + termimad::{Area, CropWriter, Filling, SPACE_FILLING}, }; +pub static SEPARATOR_FILLING: Lazy = Lazy::new(|| { Filling::from_char('─') }); + + /// Homogeneously colored piece of a line #[derive(Debug)] pub struct Region { @@ -50,6 +53,12 @@ impl Region { } } +#[derive(Debug)] +pub enum DisplayLine { + Content(Line), + Separator, +} + #[derive(Debug)] pub struct Line { pub number: LineNumber, // starting at 1 @@ -62,13 +71,29 @@ pub struct Line { pub struct SyntacticView { pub path: PathBuf, pub pattern: InputPattern, - lines: Vec, + lines: Vec, scroll: usize, page_height: usize, selection_idx: Option, // index in lines of the selection, if any + content_lines_count: usize, // number of lines excluding separators total_lines_count: usize, // including lines not filtered out } +impl DisplayLine { + pub fn line_number(&self) -> Option { + match self { + DisplayLine::Content(line) => Some(line.number), + DisplayLine::Separator => None, + } + } + pub fn is_match(&self) -> bool { + match self { + DisplayLine::Content(line) => line.name_match.is_some(), + DisplayLine::Separator => false, + } + } +} + impl SyntacticView { /// Return a prepared text view with syntax coloring if possible. @@ -88,6 +113,7 @@ impl SyntacticView { scroll: 0, page_height: 0, selection_idx: None, + content_lines_count: 0, total_lines_count: 0, }; if sv.read_lines(dam, con, no_style)? { @@ -120,7 +146,7 @@ impl SyntacticView { } let with_style = !no_style && md.len() < MAX_SIZE_FOR_STYLING; let mut reader = BufReader::new(f); - self.lines.clear(); + let mut content_lines = Vec::new(); let mut line = String::new(); self.total_lines_count = 0; let mut offset = 0; @@ -146,32 +172,66 @@ impl SyntacticView { // We don't remove '\n' or '\r' at this point because some syntax sets // need them for correct detection of comments. See #477 // Those chars are removed on printing - if pattern.is_empty() || pattern.score_of_string(&line).is_some() { - let name_match = pattern.search_string(&line); - let regions = if let Some(highlighter) = highlighter.as_mut() { - highlighter - .highlight(&line, &SYNTAXER.syntax_set) - .map_err(|e| ProgramError::SyntectCrashed { details: e.to_string() })? - .iter() - .map(Region::from_syntect) - .collect() - } else { - Vec::new() - }; - self.lines.push(Line { - regions, - start, - len: line.len(), - name_match, - number, - }); - } + let name_match = pattern.search_string(&line); + let regions = if let Some(highlighter) = highlighter.as_mut() { + highlighter + .highlight(&line, &SYNTAXER.syntax_set) + .map_err(|e| ProgramError::SyntectCrashed { details: e.to_string() })? + .iter() + .map(Region::from_syntect) + .collect() + } else { + Vec::new() + }; + content_lines.push(Line { + regions, + start, + len: line.len(), + name_match, + number, + }); line.clear(); if dam.has_event() { info!("event interrupted preview filtering"); return Ok(false); } } + let mut must_add_separators = false; + if !pattern.is_empty() { + let lines_before = con.lines_before_match_in_preview; + let lines_after = con.lines_after_match_in_preview; + if lines_before + lines_after > 0 { + let mut kept = vec![false; content_lines.len()]; + for (i, line) in content_lines.iter().enumerate() { + if line.name_match.is_some() { + for j in i.saturating_sub(lines_before)..(i + lines_after + 1).min(content_lines.len()) { + kept[j] = true; + } + } + } + for i in 1..kept.len() - 1 { + if !kept[i] && kept[i - 1] && kept[i + 1] { + kept[i] = true; + } + } + content_lines.retain(|line| kept[line.number - 1]); + must_add_separators = true; + } else { + content_lines.retain(|line| line.name_match.is_some()); + } + } + self.lines.clear(); + self.content_lines_count = content_lines.len(); + for line in content_lines { + if must_add_separators { + if let Some(last_number) = self.lines.last().and_then(|l| l.line_number()) { + if line.number > last_number + 1 { + self.lines.push(DisplayLine::Separator); + } + } + } + self.lines.push(DisplayLine::Content(line)); + } Ok(true) } @@ -207,6 +267,10 @@ impl SyntacticView { pub fn get_selected_line(&self) -> Option { self.selection_idx .and_then(|idx| self.lines.get(idx)) + .and_then(|line| match line { + DisplayLine::Content(line) => Some(line), + DisplayLine::Separator => None, + }) .and_then(|line| { File::open(&self.path) .and_then(|file| unsafe { Mmap::map(&file) }) @@ -222,7 +286,7 @@ impl SyntacticView { pub fn get_selected_line_number(&self) -> Option { self.selection_idx - .map(|idx| self.lines[idx].number) + .and_then(|idx| self.lines[idx].line_number()) } pub fn unselect(&mut self) { self.selection_idx = None; @@ -253,7 +317,7 @@ impl SyntacticView { pub fn try_select_line_number(&mut self, number: LineNumber) -> bool { // this could obviously be optimized for (idx, line) in self.lines.iter().enumerate() { - if line.number == number { + if line.line_number() == Some(number) { self.selection_idx = Some(idx); self.ensure_selection_is_visible(); return true; @@ -271,6 +335,29 @@ impl SyntacticView { self.ensure_selection_is_visible(); } + pub fn previous_match(&mut self) { + let s = self.selection_idx.unwrap_or(0); + for d in 1..self.lines.len() { + let idx = (self.lines.len() + s - d) % self.lines.len(); + if self.lines[idx].is_match() { + self.selection_idx = Some(idx); + self.ensure_selection_is_visible(); + return; + } + } + } + pub fn next_match(&mut self) { + let s = self.selection_idx.unwrap_or(0); + for d in 1..self.lines.len() { + let idx = (s + d) % self.lines.len(); + if self.lines[idx].is_match() { + self.selection_idx = Some(idx); + self.ensure_selection_is_visible(); + return; + } + } + } + pub fn try_scroll( &mut self, cmd: ScrollCommand, @@ -299,6 +386,22 @@ impl SyntacticView { self.scroll != old_scroll } + pub fn max_line_number(&self) -> Option { + for line in self.lines.iter().rev() { + if let Some(n) = line.line_number() { + return Some(n); + } + } + None + } + + pub fn get_content_line(&self, idx: usize) -> Option<&Line> { + self.lines.get(idx).and_then(|line| match line { + DisplayLine::Content(line) => Some(line), + DisplayLine::Separator => None, + }) + } + pub fn display( &mut self, w: &mut W, @@ -311,7 +414,10 @@ impl SyntacticView { self.page_height = area.height as usize; self.ensure_selection_is_visible(); } - let max_number_len = self.lines.last().map_or(0, |l|l.number).to_string().len(); + let max_number_len = self.max_line_number() + .unwrap_or(0) + .to_string() + .len(); let show_line_number = area.width > 55 || ( self.pattern.is_some() && area.width > 8 ); let line_count = area.height as usize; let styles = &panel_skin.styles; @@ -336,73 +442,81 @@ impl SyntacticView { let selected = self.selection_idx == Some(line_idx); let bg = if selected { selection_bg } else { normal_bg }; let mut op_mmap: Option = None; - if let Some(line) = self.lines.get(line_idx) { - let mut regions = &line.regions; - let regions_ur; - if regions.is_empty() && line.len > 0 { - if op_mmap.is_none() { - let file = File::open(&self.path)?; - let mmap = unsafe { Mmap::map(&file)? }; - op_mmap = Some(mmap); + match self.lines.get(line_idx) { + Some(DisplayLine::Separator) => { + cw.w.queue(SetBackgroundColor(bg))?; + cw.queue_unstyled_str(" ")?; + cw.fill(&styles.preview_separator, &SEPARATOR_FILLING)?; + } + Some(DisplayLine::Content(line)) => { + let mut regions = &line.regions; + let regions_ur; + if regions.is_empty() && line.len > 0 { + if op_mmap.is_none() { + let file = File::open(&self.path)?; + let mmap = unsafe { Mmap::map(&file)? }; + op_mmap = Some(mmap); + } + if op_mmap.as_ref().unwrap().len() < line.start + line.len { + warn!("file truncated since parsing"); + } else { + // an UTF8 error can only happen if file modified during display + let string = String::from_utf8( + // we copy the memmap slice, as it's not immutable + (op_mmap.unwrap()[line.start..line.start + line.len]).to_vec(), + ) + .unwrap_or_else(|_| "Bad UTF8".to_string()); + regions_ur = vec![Region { + fg: normal_fg, + string, + }]; + regions = ®ions_ur; + } } - if op_mmap.as_ref().unwrap().len() < line.start + line.len { - warn!("file truncated since parsing"); + cw.w.queue(SetBackgroundColor(bg))?; + if show_line_number { + cw.queue_g_string( + &styles.preview_line_number, + format!(" {:w$} ", line.number, w = max_number_len), + )?; } else { - // an UTF8 error can only happen if file modified during display - let string = String::from_utf8( - // we copy the memmap slice, as it's not immutable - (op_mmap.unwrap()[line.start..line.start + line.len]).to_vec(), - ) - .unwrap_or_else(|_| "Bad UTF8".to_string()); - regions_ur = vec![Region { - fg: normal_fg, - string, - }]; - regions = ®ions_ur; + cw.queue_unstyled_str(" ")?; } - } - cw.w.queue(SetBackgroundColor(bg))?; - if show_line_number { - cw.queue_g_string( - &styles.preview_line_number, - format!(" {:w$} ", line.number, w = max_number_len), - )?; - } else { - cw.queue_unstyled_str(" ")?; - } - cw.w.queue(SetBackgroundColor(bg))?; - if con.show_selection_mark { - cw.queue_unstyled_char(if selected { '▶' } else { ' ' })?; - } - if let Some(nm) = &line.name_match { - let mut dec = 0; - let pos = &nm.pos; - let mut pos_idx: usize = 0; - for content in regions { - let s = content.string.trim_end_matches(is_char_end_of_line); - cw.w.queue(SetForegroundColor(content.fg))?; - if pos_idx < pos.len() { - for (cand_idx, cand_char) in s.chars().enumerate() { - if pos_idx < pos.len() && pos[pos_idx] == cand_idx + dec { - cw.w.queue(SetBackgroundColor(match_bg))?; - cw.queue_unstyled_char(cand_char)?; - cw.w.queue(SetBackgroundColor(bg))?; - pos_idx += 1; - } else { - cw.queue_unstyled_char(cand_char)?; + cw.w.queue(SetBackgroundColor(bg))?; + if con.show_selection_mark { + cw.queue_unstyled_char(if selected { '▶' } else { ' ' })?; + } + if let Some(nm) = &line.name_match { + let mut dec = 0; + let pos = &nm.pos; + let mut pos_idx: usize = 0; + for content in regions { + let s = content.string.trim_end_matches(is_char_end_of_line); + cw.w.queue(SetForegroundColor(content.fg))?; + if pos_idx < pos.len() { + for (cand_idx, cand_char) in s.chars().enumerate() { + if pos_idx < pos.len() && pos[pos_idx] == cand_idx + dec { + cw.w.queue(SetBackgroundColor(match_bg))?; + cw.queue_unstyled_char(cand_char)?; + cw.w.queue(SetBackgroundColor(bg))?; + pos_idx += 1; + } else { + cw.queue_unstyled_char(cand_char)?; + } } + dec += s.chars().count(); + } else { + cw.queue_unstyled_str(s)?; } - dec += s.chars().count(); - } else { - cw.queue_unstyled_str(s)?; } - } - } else { - for content in regions { - cw.w.queue(SetForegroundColor(content.fg))?; - cw.queue_unstyled_str(content.string.trim_end_matches(is_char_end_of_line))?; + } else { + for content in regions { + cw.w.queue(SetForegroundColor(content.fg))?; + cw.queue_unstyled_str(content.string.trim_end_matches(is_char_end_of_line))?; + } } } + None => {} } cw.fill( if selected { &styles.selected_line } else { &styles.preview }, @@ -428,7 +542,7 @@ impl SyntacticView { ) -> Result<(), ProgramError> { let width = area.width as usize; let mut s = if self.pattern.is_some() { - format!("{}/{}", self.lines.len(), self.total_lines_count) + format!("{}/{}", self.content_lines_count, self.total_lines_count) } else { format!("{}", self.total_lines_count) }; diff --git a/src/verb/execution_builder.rs b/src/verb/execution_builder.rs index 524936d..54e33ce 100644 --- a/src/verb/execution_builder.rs +++ b/src/verb/execution_builder.rs @@ -170,7 +170,7 @@ impl<'b> ExecutionStringBuilder<'b> { })) } "file-git-relative" => { // file path relative to git repo workdir - let Some(sel) = sel else { return None; }; + let sel = sel?; let path = git2::Repository::discover(self.root).ok() .and_then(|repo| repo.workdir().map(path_to_string)) .and_then(|gitroot| sel.path.strip_prefix(gitroot).ok()) diff --git a/website/docs/conf_file.md b/website/docs/conf_file.md index 45d1ba5..339657c 100644 --- a/website/docs/conf_file.md +++ b/website/docs/conf_file.md @@ -388,3 +388,16 @@ show_matching_characters_on_path_searches = false which gives this: ![not shown](img/subpath-match-not-shown.png) + +## Lines surrounding a match in preview + +If you want to have more than just the matching lines displayed in preview, you may changes those parameters in config: + +```Hjson +lines_before_match_in_preview: 0 +lines_after_match_in_preview: 0 +``` +```TOML +lines_before_match_in_preview = 0 +lines_after_match_in_preview = 0 +``` diff --git a/website/docs/skins.md b/website/docs/skins.md index fa80a56..ae240ce 100644 --- a/website/docs/skins.md +++ b/website/docs/skins.md @@ -77,6 +77,7 @@ skin: { help_table_border: ansi(239) None preview_title: gray(23) None / gray(21) None preview: gray(20) gray(1) / gray(18) gray(2) + preview_separator: ansi(94) None / gray(3) None preview_line_number: gray(12) gray(3) preview_match: None ansi(29) hex_null: gray(11) None @@ -159,6 +160,7 @@ help_table_border = "ansi(239) None" preview_title = "gray(23) None / gray(21) None" preview = "gray(20) gray(1) / gray(18) gray(2)" preview_line_number = "gray(12) gray(3)" +preview_separator: "ansi(94) None / gray(3) None" preview_match = "None ansi(29)" hex_null = "gray(11) None" hex_ascii_graphic = "gray(18) None" -- cgit v1.2.3