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

Print Range of Lines #159

Closed
tskinn opened this issue May 29, 2018 · 8 comments
Closed

Print Range of Lines #159

tskinn opened this issue May 29, 2018 · 8 comments
Assignees
Labels
feature-request New feature or request
Milestone

Comments

@tskinn
Copy link
Contributor

tskinn commented May 29, 2018

This might be outside of the scope of bat since one of the projects goals is to be a drop in for cat, but at the same time bat is compared to source-highlight which has the --line-range flag.

The use case I have in mind is using bat as the preview command provided to fzf. (https://github.com/junegunn/fzf#preview-window)

Also along these same lines it would be nice to have the option to highlight a particular line differently than the rest of the syntax highlighting.

If these features are in scope, I wouldn't mind working them myself.

@sharkdp sharkdp added the feature-request New feature or request label May 30, 2018
@sharkdp
Copy link
Owner

sharkdp commented May 30, 2018

Thank you for your feedback!

since one of the projects goals is to be a drop in for cat

I'd like bat to be a potential drop-in replacement for cat, but that doesn't mean that we cannot add additional features and command-line options (we already did). I will, however, be reluctant if the scope of the program is extended too much (see, for example, #55).

Concerning --line-range, I think this could be a useful addition. In particular, since we will need something really similar for #23 (where the line-ranges would be determined by the Git hunks).

However there are a few things I would like to discuss:

  • Which exact use case do you have in mind? Could it also be solved by piping into head and tail (or do you want to specify multiple ranges)?
  • How would the command-line interface look like? Would multiple ranges be specified via --line-range 3-10 --line-range 30-100? Do we allow for partial ranges (30- or 30-end)? single lines?
  • How does this work if multiple file inputs are specified?

The use case I have in mind is using bat as the preview command provided to fzf. (junegunn/fzf#preview-window)

I did the same thing yesterday 😄. I'd be curious to see why you would like to have a --line-range option for this.

Also along these same lines it would be nice to have the option to highlight a particular line differently than the rest of the syntax highlighting.

Could you please open a new ticket to discuss this?

@tskinn
Copy link
Contributor Author

tskinn commented May 30, 2018

Sorry. I didn't explain my use case in sufficient detail.

I would use bat as a replacement of or component in the preview.sh file. So I would pipe the output of ripgrep to fzf and use a script incorporating bat to print the result of ripgrep with some now prettified context.

example using skim

So in the above image the right hand panel would print the output of bat. That is why I also brought up the idea of highlighting a single line differently as well.

Which exact use case do you have in mind? Could it also be solved by piping into head and tail (or do you want to specify multiple ranges)?

You make a good point. I do still need to look into a combination of other tools. If I find something sufficient for the task I will update here.

How would the command-line interface look like? Would multiple ranges be specified via --line-range 3-10 --line-range 30-100? Do we allow for partial ranges (30- or 30-end)? single lines?

In my specific use case I wouldn't need to specify multiple ranges but since the git hunks feature will be doing something so similar it might not hurt to include multiple ranges as an option. As to what it might look like, my first thoughts would be to have one flag and multiple ranges separated by commas.
--line-range 3-10,30-100

or

Have a range separated by a colon.
--line-range 3:10,30:100
Although I think I lean more towards the former.

For partial ranges I like the idea of saying --line-range 30- or --line-range -30. I don't know if I would use this feature it but if it existed I think that the single dash would be better than 30-end or beg-30.

And I don't really see a use for single lines at the moment but who am I to judge :)
If it did exist I would want to just specify a single number as the range. --line-range 30

How does this work if multiple file inputs are specified?

This is a little muddy but my first thoughts are that the same range(s) would be displayed from every file if possible.

@sharkdp
Copy link
Owner

sharkdp commented May 30, 2018

Ok, sounds good! If you would like to work on this, that would very much appreciated.

I would use bat as a replacement of or component in the preview.sh file. So I would pipe the output of ripgrep to fzf and use a script incorporating bat to print the result of ripgrep with some now prettified context.

That sounds pretty awesome.. I have never thought of that. I'd be interested to see the results, once this works.

@sharkdp
Copy link
Owner

sharkdp commented Jun 12, 2018

Closed via #162. @tskinn I'd be interested to see your fzf scripts 😄

@sharkdp sharkdp closed this as completed Jun 12, 2018
@sharkdp sharkdp added this to the v0.5.0 milestone Aug 19, 2018
@sharkdp
Copy link
Owner

sharkdp commented Aug 19, 2018

Released in bat 0.5.

@juanMarinero
Copy link

juanMarinero commented Jan 4, 2021

Hi! --line-range enables bat to replace default head and tail.
My tail_bat function might be very improvable, thanks for critics in advance!
[original removed]

Edited:

alias cat='batcat'
alias head='head_bat'
alias tail='tail_bat'

function head_bat() {

  # E.g.
  # file01="$HOME/.vimrc"
  # head_bat $file01     # with default -n
  # head_bat -n 20 $file01
  # head_bat -A $file01  # with default -n
  # head_bat -n 20 -A $file01 # -A show tabs, spaces, ...
  # head_bat -A -n 20 $file01 # WRONG!! '-n' must be 1st flag (if any)
  # head_bat $file01 $HOME/.bashrc # RIGHT multiple files

  local lines2show=10
  while test $# -gt 0; do
    # printf "%-15s --> %s\n" "\$1" "$1"
    case "$1" in
    -n)
      # '-n' must be 1st flag (if any)
      shift
      local lines2show=$1
      shift
      ;;
    *)
      # call 'bat' passing:
      # - '--line-range''s value
      # - and rest of current fcn arguments ($@) unaltered: other 'bat' flags, and final filename/s
      bat --line-range :$lines2show $@ # E.g.: $ bat --line-range :10 [filename/s]
      return 0                         # break all (switch, while and current fcn)
      shift
      ;;
    esac
  done
}

function tail_bat() {

  # E.g.
  # file01="$HOME/.vimrc"
  # tail_bat $file01     # with default -lines
  # tail_bat -lines 20 $file01
  # tail_bat -A $file01  # with default -lines
  # tail_bat -lines 20 -A $file01 # -A show tabs, spaces, ...
  # tail_bat -A -lines 20 $file01 # RIGHT '-lines' can be 1st flag (if any)
  # tail_bat $file01 $HOME/.bashrc
  # tail_bat -lines 20 $file01 $HOME/.bashrc
  # tail_bat -lines 20 -A $file01 $HOME/.bashrc
  # tail_bat -A -lines 20 $file01 $HOME/.bashrc # RIGHT! '-lines' can be 1st flag (if any)

  local arr_orig=("${*}") #copy all params
  declare -a arr_files
  declare -a arr_flags
  count=0
  for i in ${arr_orig[@]}; do
    if test -f "$i"; then
      # file exists
      arr_files+=($i)
    else
      arr_flags+=($i)
    fi
    ((count++))
  done

  local lines2show=10
  local arr_flags_tmp=("${arr_flags[@]}") #copy all params (shift will unset cells)
  index=0
  for k in ${arr_flags_tmp[@]}; do
    # printf "%-15s --> %s\n" "\$1" "$1"
    case "$k" in
    -lines)
      # '-n' must be 1st flag (if any)
      unset arr_flags_tmp[$index]
      arr_flags_tmp=("${arr_flags_tmp[@]}")
      local lines2show=${arr_flags_tmp[$index]}
      unset arr_flags_tmp[$index]
      arr_flags_tmp=("${arr_flags_tmp[@]}")
      ;;
    *)
      ((index++))
      ;;
    esac
  done
  for i in ${arr_files[@]}; do
    local file01=${i}
    local lines_total=$(wc -l <$file01)
    # either lines2show is default or explicit
    local var_range_start=$(($lines_total - $lines2show + 1))
    local var_range_start=$(($var_range_start < 1 ? 1 : $var_range_start))
    # call 'bat' passing:
    # - calculated '--line-range'
    # - and rest of current fcn arguments ($arr_flags_tmp) unaltered: other 'bat' flags, and filename
    bat --line-range $var_range_start: $arr_flags_tmp $file01 # E.g.: $ bat --line-range 990: [filename]
  done
}

@infused-kim
Copy link

infused-kim commented Feb 15, 2024

@juanMarinero thank you for sharing your tail_bat implementation. I have made a few tiny improvements: handle -n flag in addition to --lines and call original tail when using it to follow a file (-f flag).

#!/usr/bin/env bash

# A tail replacement script that uses bat to output the content
#
# Source: https://github.com/sharkdp/bat/issues/159#issuecomment-754166034

function tail_bat() {

  # E.g.
  # file01="$HOME/.vimrc"
  # tail_bat $file01     # with default -lines
  # tail_bat -lines 20 $file01
  # tail_bat -A $file01  # with default -lines
  # tail_bat -lines 20 -A $file01 # -A show tabs, spaces, ...
  # tail_bat -A -lines 20 $file01 # RIGHT '-lines' can be 1st flag (if any)
  # tail_bat $file01 $HOME/.bashrc
  # tail_bat -lines 20 $file01 $HOME/.bashrc
  # tail_bat -lines 20 -A $file01 $HOME/.bashrc
  # tail_bat -A -lines 20 $file01 $HOME/.bashrc # RIGHT! '-lines' can be 1st flag (if any)

  local arr_orig=("${*}") #copy all params
  declare -a arr_files
  declare -a arr_flags
  count=0
  for i in ${arr_orig[@]}; do
    if test -f "$i"; then
      # file exists
      arr_files+=($i)
    else
      arr_flags+=($i)
    fi
    ((count++))
  done

  local lines2show=10
  local arr_flags_tmp=("${arr_flags[@]}") #copy all params (shift will unset cells)
  index=0
  for k in ${arr_flags_tmp[@]}; do
    # printf "%-15s --> %s\n" "\$1" "$1"
    case "$k" in

    --lines) ;&

    -n)
      # '-n' must be 1st flag (if any)
      unset arr_flags_tmp[$index]
      arr_flags_tmp=("${arr_flags_tmp[@]}")
      local lines2show=${arr_flags_tmp[$index]}
      unset arr_flags_tmp[$index]
      arr_flags_tmp=("${arr_flags_tmp[@]}")
      ;;

    -F) ;&

    -f)
      /usr/bin/tail "${@}"
      exit $?
      ;;

    *)
      ((index++))
      ;;

    esac
  done

  for i in ${arr_files[@]}; do
    local file01=${i}
    local lines_total=$(wc -l <$file01)
    # either lines2show is default or explicit
    local var_range_start=$(($lines_total - $lines2show + 1))
    local var_range_start=$(($var_range_start < 1 ? 1 : $var_range_start))
    # call 'bat' passing:
    # - calculated '--line-range'
    # - and rest of current fcn arguments ($arr_flags_tmp) unaltered: other 'bat' flags, and filename
    bat --line-range $var_range_start: $arr_flags_tmp $file01 # E.g.: $ bat --line-range 990: [filename]
  done

}

tail_bat "$@"

@infused-kim
Copy link

I've made a few improvements since my last version:

head_bat

Improvements:

  • Handle piped input for better compatibility when you alias head to to head_bat
#!/usr/bin/env bash

# A head replacement script that uses bat to output the content
#
# Source: https://github.com/sharkdp/bat/issues/159#issuecomment-754166034

function head_bat() {

  # E.g.
  # file01="$HOME/.vimrc"
  # head_bat $file01     # with default -n
  # head_bat -n 20 $file01
  # head_bat -A $file01  # with default -n
  # head_bat -n 20 -A $file01 # -A show tabs, spaces, ...
  # head_bat -A -n 20 $file01 # WRONG!! '-n' must be 1st flag (if any)
  # head_bat $file01 $HOME/.bashrc # RIGHT multiple files
  #
  # Pipe examples:
  # command cat $file01 | head_bat     # with default -n
  # command cat $file01 | head_bat -n 20
  # command cat $file01 | head_bat -A  # with default -n

  local lines2show=10
  local bat_args=()

  while test $# -gt 0; do
    case "$1" in
    -n)
      # '-n' must be 1st flag (if any)
      shift
      local lines2show=$1
      shift
      ;;
    *)
      bat_args+=("$1")
      shift
      ;;
    esac
  done

  # Check if we're receiving piped input
  if [ ! -t 0 ]; then
    # Handle piped input with any additional bat arguments
    command cat - | command bat --line-range ":$lines2show" "${bat_args[@]}"
  else
    # Handle file arguments
    # call 'bat' passing:
    # - '--line-range''s value
    # - and rest of current fcn arguments stored in bat_args
    command bat --line-range ":$lines2show" "${bat_args[@]}"  # E.g.: $ bat --line-range :10 [filename/s]
  fi
}

head_bat "$@"

tail_bat

Improvements:

  • Handle piped input for better compatibility when you alias tail to tail_bat
  • When following using -f, call the original tail command through grc to colorize the output (if grc is installed)
#!/usr/bin/env bash

# A tail replacement script that uses bat to output the content
#
# Source: https://github.com/sharkdp/bat/issues/159#issuecomment-754166034

function tail_bat() {

  # E.g.
  # file01="$HOME/.vimrc"
  # tail_bat $file01     # with default -lines
  # tail_bat -n 20 $file01
  # tail_bat -A $file01  # with default -lines
  # tail_bat -n 20 -A $file01 # -A show tabs, spaces, ...
  # tail_bat $file01 $HOME/.bashrc
  # tail_bat -n 20 $file01 $HOME/.bashrc
  # tail_bat -n 20 -A $file01 $HOME/.
  #
  # Follow mode:
  # tail_bat -f $file01
  #
  # Pipe examples:
  # command cat $file01 | tail_bat     # with default -lines
  # command cat $file01 | tail_bat -n 20
  # command cat $file01 | tail_bat -A  # with default -lines

  local lines2show=10
  local arr_flags=()
  local arr_files=()
  local is_following=false
  local original_args=("${@}")

  # Process all arguments
  while [[ $# -gt 0 ]]; do
    case "$1" in
    --lines | -n)
      shift
      lines2show="$1"
      shift
      ;;
    -F | -f)
      is_following=true
      shift
      ;;
    *)
      if test -f "$1"; then
        arr_files+=("$1")
      else
        arr_flags+=("$1")
      fi
      shift
      ;;
    esac
  done

  # Handle follow mode with grc if available
  if [[ "$is_following" = true ]]; then
    if command -v grc >/dev/null 2>&1; then
      command grc tail "${original_args[@]}"
    else
      command tail "${original_args[@]}"
    fi
    exit $?
  fi

  # Function to process a file and display with bat
  function bat_tail_file() {
    local file="$1"
    local lines_total=$(wc -l < "$file")
    local var_range_start=$(($lines_total - $lines2show + 1))
    local var_range_start=$(($var_range_start < 1 ? 1 : $var_range_start))
    command bat --line-range $var_range_start: "${arr_flags[@]}" "$file"
  }

  # Check if we're receiving piped input
  if [ ! -t 0 ]; then
    # Store input in temp file to count lines
    local tmpfile=$(mktemp)
    command cat - > "$tmpfile"
    bat_tail_file "$tmpfile"
    rm "$tmpfile"
    return
  fi

  # Process files
  for file in "${arr_files[@]}"; do
    bat_tail_file "$file"
  done
}

tail_bat "$@"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature-request New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants