summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDenys Séguret <cano.petrole@gmail.com>2024-04-12 09:28:25 +0200
committerGitHub <noreply@github.com>2024-04-12 09:28:25 +0200
commit5ff015d12e870346c1a3f8ef661ad9e4f355b4e4 (patch)
treea44066f977117ca08ef8a3077d39a77ea2ceb3e6
parent719547fc2d8f7a209dac904da4ea6b2ae935db10 (diff)
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
-rw-r--r--CHANGELOG.md2
-rw-r--r--bacon.toml1
-rw-r--r--resources/default-conf/conf.hjson10
-rw-r--r--resources/default-conf/skins/catppuccin-macchiato.hjson2
-rw-r--r--resources/default-conf/skins/catppuccin-mocha.hjson2
-rw-r--r--resources/default-conf/skins/dark-blue.hjson1
-rw-r--r--resources/default-conf/skins/dark-gruvbox.hjson1
-rw-r--r--resources/default-conf/skins/dark-orange.hjson1
-rw-r--r--resources/default-conf/skins/solarized-light.hjson1
-rw-r--r--resources/default-conf/skins/white.hjson1
-rw-r--r--src/app/app_context.rs8
-rw-r--r--src/conf/conf.rs90
-rw-r--r--src/preview/preview.rs16
-rw-r--r--src/preview/preview_state.rs8
-rw-r--r--src/skin/style_map.rs1
-rw-r--r--src/syntactic/syntactic_view.rs286
-rw-r--r--src/verb/execution_builder.rs2
-rw-r--r--website/docs/conf_file.md13
-rw-r--r--website/docs/skins.md2
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
@@ -207,6 +207,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
#
# While it's possible to have all configuration in one file,
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<PathBuf>,
+ #[serde(alias="capture-mouse")]
+ pub capture_mouse: Option<bool>,
- #[serde(alias="default-flags")]
- pub default_flags: Option<String>, // the flags to apply before cli ones
+ #[serde(alias="cols-order")]
+ pub cols_order: Option<ColsConf>,
+
+ #[serde(alias="content-search-max-file-size", deserialize_with="file_size::deserialize", default)]
+ pub content_search_max_file_size: Option<u64>,
#[serde(alias="date-time-format")]
pub date_time_format: Option<String>,
- #[serde(default)]
- pub verbs: Vec<VerbConf>,
-
- pub skin: Option<AHashMap<String, SkinEntry>>,
-
- #[serde(default, alias="special-paths")]
- pub special_paths: HashMap<GlobConf, SpecialHandlingConf>,
-
- #[serde(alias="search-modes")]
- pub search_modes: Option<FnvHashMap<String, String>>,
+ #[serde(alias="default-flags")]
+ pub default_flags: Option<String>, // 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<bool>,
- #[serde(alias="capture-mouse")]
- pub capture_mouse: Option<bool>,
-
- #[serde(alias="cols-order")]
- pub cols_order: Option<ColsConf>,
-
- #[serde(alias="show-selection-mark")]
- pub show_selection_mark: Option<bool>,
+ #[serde(alias="enable-keyboard-enhancements")]
+ pub enable_kitty_keyboard: Option<bool>,
#[serde(default, alias="ext-colors")]
pub ext_colors: AHashMap<String, String>,
- #[serde(alias="syntax-theme")]
- pub syntax_theme: Option<SyntaxTheme>,
+ pub file_sum_threads_count: Option<usize>,
- #[serde(alias="true-colors")]
- pub true_colors: Option<bool>,
+ /// the files used to load this configuration
+ #[serde(skip)]
+ pub files: Vec<PathBuf>,
#[serde(alias="icon-theme")]
pub icon_theme: Option<String>,
- pub modal: Option<bool>,
+ #[serde(default)]
+ pub imports: Vec<Import>,
/// the initial mode (only relevant when modal is true)
#[serde(alias="initial-mode")]
pub initial_mode: Option<Mode>,
- pub max_panels_count: Option<usize>,
+ #[serde(alias="kitty-graphics-transmission")]
+ pub kitty_graphics_transmission: Option<TransmissionMedium>,
- #[serde(alias="quit-on-last-cancel")]
- pub quit_on_last_cancel: Option<bool>,
+ #[serde(alias="lines-after-match-in-preview")]
+ pub lines_after_match_in_preview: Option<usize>,
- pub file_sum_threads_count: Option<usize>,
+ #[serde(alias="lines-before-match-in-preview")]
+ pub lines_before_match_in_preview: Option<usize>,
+
+ pub max_panels_count: Option<usize>,
#[serde(alias="max_staged_count")]
pub max_staged_count: Option<usize>,
- #[serde(default)]
- pub imports: Vec<Import>,
+ pub modal: Option<bool>,
+
+ #[serde(alias="quit-on-last-cancel")]
+ pub quit_on_last_cancel: Option<bool>,
+
+ #[serde(alias="search-modes")]
+ pub search_modes: Option<FnvHashMap<String, String>>,
#[serde(alias="show-matching-characters-on-path-searches")]
pub show_matching_characters_on_path_searches: Option<bool>,
- #[serde(alias="content-search-max-file-size", deserialize_with="file_size::deserialize", default)]
- pub content_search_max_file_size: Option<u64>,
+ #[serde(alias="show-selection-mark")]
+ pub show_selection_mark: Option<bool>,
+
+ pub skin: Option<AHashMap<String, SkinEntry>>,
+
+ #[serde(default, alias="special-paths")]
+ pub special_paths: HashMap<GlobConf, SpecialHandlingConf>,
+
+ #[serde(alias="syntax-theme")]
+ pub syntax_theme: Option<SyntaxTheme>,
#[serde(alias="terminal-title")]
pub terminal_title: Option<ExecPattern>,
+ #[serde(alias="true-colors")]
+ pub true_colors: Option<bool>,
+
#[serde(alias="update-work-dir")]
pub update_work_dir: Option<bool>,
- #[serde(alias="enable-keyboard-enhancements")]
- pub enable_kitty_keyboard: Option<bool>,
-
- #[serde(alias="kitty-graphics-transmission")]
- pub kitty_graphics_transmission: Option<TransmissionMedium>,
+ #[serde(default)]
+ pub verbs: Vec<VerbConf>,
// 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<Filling> = Lazy::new(|| { Filling::from_char('─') });
+
+
/// Homogeneously colored piece of a line
#[derive(Debug)]
pub struct Region {
@@ -51,6 +54,12 @@ impl Region {
}
#[derive(Debug)]
+pub enum DisplayLine {
+ Content(Line),
+ Separator,
+}
+
+#[derive(Debug)]
pub struct Line {
pub number: LineNumber, // starting at 1
pub start: usize, // offset in the file, in bytes
@@ -62,13 +71,29 @@ pub struct Line {
pub struct SyntacticView {
pub path: PathBuf,
pub pattern: InputPattern,
- lines: Vec<Line>,
+ lines: Vec<DisplayLine>,
scroll: usize,
page_height: usize,
selection_idx: Option<usize>, // 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<LineNumber> {
+ 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<String> {
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<LineNumber> {
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<LineNumber> {
+ 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<Mmap> = 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 = &regions_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 = &regions_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;