Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support relative negative line ranges #3068

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- Syntax highlighting for JavaScript files that start with `#!/usr/bin/env bun` #2913 (@sharunkumar)
- `bat --strip-ansi={never,always,auto}` to remove ANSI escape sequences from bat's input, see #2999 (@eth-p)
- Add or remove individual style components without replacing all styles #2929 (@eth-p)
- Support negative relative line ranges, e.g. `bat -r :-10` / `bat -r='-10:'`, see #3068 (@ajesipow)

## Bugfixes

Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ bytesize = { version = "1.3.0" }
encoding_rs = "0.8.34"
os_str_bytes = { version = "~7.0", optional = true }
run_script = { version = "^0.10.1", optional = true}
itertools = "0.13.0"

[dependencies.git2]
version = "0.18"
Expand Down
12 changes: 10 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,17 +116,25 @@ pub fn get_pager_executable(config_pager: Option<&str>) -> Option<String> {

#[test]
fn default_config_should_include_all_lines() {
use crate::line_range::MaxBufferedLineNumber;
use crate::line_range::RangeCheckResult;

assert_eq!(LineRanges::default().check(17), RangeCheckResult::InRange);
assert_eq!(
LineRanges::default().check(17, MaxBufferedLineNumber::Tentative(17)),
RangeCheckResult::InRange
);
}

#[test]
fn default_config_should_highlight_no_lines() {
use crate::line_range::MaxBufferedLineNumber;
use crate::line_range::RangeCheckResult;

assert_ne!(
Config::default().highlighted_lines.0.check(17),
Config::default()
.highlighted_lines
.0
.check(17, MaxBufferedLineNumber::Tentative(17)),
RangeCheckResult::InRange
);
}
67 changes: 54 additions & 13 deletions src/controller.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::io::{self, BufRead, Write};

use crate::assets::HighlightingAssets;
use crate::config::{Config, VisibleLines};
#[cfg(feature = "git")]
Expand All @@ -10,11 +8,14 @@ use crate::input::{Input, InputReader, OpenedInput};
use crate::lessopen::LessOpenPreprocessor;
#[cfg(feature = "git")]
use crate::line_range::LineRange;
use crate::line_range::{LineRanges, RangeCheckResult};
use crate::line_range::{LineRanges, MaxBufferedLineNumber, RangeCheckResult};
use crate::output::OutputType;
#[cfg(feature = "paging")]
use crate::paging::PagingMode;
use crate::printer::{InteractivePrinter, OutputHandle, Printer, SimplePrinter};
use std::collections::VecDeque;
use std::io::{self, BufRead, Write};
use std::mem;

use clircle::{Clircle, Identifier};

Expand Down Expand Up @@ -241,20 +242,63 @@ impl<'b> Controller<'b> {
reader: &mut InputReader,
line_ranges: &LineRanges,
) -> Result<()> {
let mut line_buffer = Vec::new();
let mut line_number: usize = 1;

let mut current_line_buffer: Vec<u8> = Vec::new();
let mut current_line_number: usize = 1;
// Buffer needs to be 1 greater than the offset to have a look-ahead line for EOF
let buffer_size: usize = line_ranges.largest_offset_from_end() + 1;
// Buffers multiple line data and line number
let mut buffered_lines: VecDeque<(Vec<u8>, usize)> = VecDeque::with_capacity(buffer_size);

let mut reached_eof: bool = false;
let mut first_range: bool = true;
let mut mid_range: bool = false;

let style_snip = self.config.style_components.snip();

while reader.read_line(&mut line_buffer)? {
match line_ranges.check(line_number) {
loop {
if reached_eof && buffered_lines.is_empty() {
// Done processing all lines
break;
}
if !reached_eof {
if reader.read_line(&mut current_line_buffer)? {
// Fill the buffer
buffered_lines
.push_back((mem::take(&mut current_line_buffer), current_line_number));
current_line_number += 1;
} else {
// No more data to read
reached_eof = true;
}
}

if buffered_lines.len() < buffer_size && !reached_eof {
// The buffer needs to be completely filled first
continue;
}

let Some((line, line_nr)) = buffered_lines.pop_front() else {
break;
};

// Determine if the last line number in the buffer is the last line of the file or
// just a line somewhere in the file
let max_buffered_line_number = buffered_lines
.back()
.map(|(_, max_line_number)| {
if reached_eof {
MaxBufferedLineNumber::Final(*max_line_number)
} else {
MaxBufferedLineNumber::Tentative(*max_line_number)
}
})
.unwrap_or(MaxBufferedLineNumber::Final(line_nr));

match line_ranges.check(line_nr, max_buffered_line_number) {
RangeCheckResult::BeforeOrBetweenRanges => {
// Call the printer in case we need to call the syntax highlighter
// for this line. However, set `out_of_range` to `true`.
printer.print_line(true, writer, line_number, &line_buffer)?;
printer.print_line(true, writer, line_nr, &line, max_buffered_line_number)?;
mid_range = false;
}

Expand All @@ -269,15 +313,12 @@ impl<'b> Controller<'b> {
}
}

printer.print_line(false, writer, line_number, &line_buffer)?;
printer.print_line(false, writer, line_nr, &line, max_buffered_line_number)?;
}
RangeCheckResult::AfterLastRange => {
break;
}
}

line_number += 1;
line_buffer.clear();
}
Ok(())
}
Expand Down
Loading