class Object

Constants

BLUE
BOLD
BOLD_OFF
CLEAR
CONFIG_FILE
CONFIG_PATH
CURMARK
CURSOR_COLOR
CYAN
GIGA_SIZE

code related to long listing of files

GMARK

—————– CONSTANTS —————– ##

GREEN
IDX
GLOBALS

hints or shortcuts to get to files without moving

KEY_DOWN
KEY_END
KEY_F1
KEY_F10
KEY_F5
KEY_F6
KEY_F7
KEY_F8
KEY_F9
KEY_HOME

I needed to replace the O with a [ for this to work in Vim Home comes as ^[OH whereas on the command line it is correct as ^[[H

KEY_LEFT
KEY_PGDN
KEY_PGUP
KEY_RIGHT
KEY_S_F1
KEY_UP
KILO_SIZE
MAGENTA
MEGA_SIZE
MSCROLL
ON_BLUE
ON_GREEN
ON_RED
ON_YELLOW
PAGER_COMMAND
RED
REVERSE
REVERSE_OFF
SEPARATOR
SPACE
UNDERLINE
VERSION
INSTALLATION

copy into PATH alias c=~/bin/cetus.rb c

VMARK
YELLOW

Public Instance Methods

add_slash(files) click to toggle source
# File bin/cetus, line 2299
def add_slash files
  return files.map do |f|
    File.directory?(f) ? f + '/' : f
  end
end
add_to_selection(file) click to toggle source

add given file/s to selected file list

# File bin/cetus, line 3161
def add_to_selection file
  ff = file
  case file
  when String
    ff = [file]
  end
  @current_dir ||= Dir.pwd
  ff.each do |f|
    # this is wrong if the file is a dir listing or visited files listing.
    # full = File.join(@current_dir, f)
    full = expand_path(f)
    @selected_files.push(full) unless @selected_files.include?(full)
  end
end
ag() click to toggle source
# File bin/cetus, line 2982
def ag
  pattern = readline 'Enter a pattern to search (ag): '
  return if pattern == ''

  @title = "Files found using 'ag -t: ' #{pattern}"

  ## ag options :
  #     -t : all text files
  #     -l : print only file names
  #     -a : print all files, even ignored
  system %(ag -t "#{pattern}" | less)

  pause
  files = `ag -lt "#{pattern}"`.split("\n")
  if files.empty?
    perror "No files found for #{pattern}."
    @title = nil
    return
  end
  @files = files
end
ask_hint(deflt=nil) click to toggle source

prompt user for file shortcut and return file or nil

# File bin/cetus, line 2741
def ask_hint deflt=nil
  f = nil
  key = get_char
  return deflt if key == 'ENTER'

  ix = get_index(key, @vps)
  f = @viewport[ix] if ix
  f
end
bindkey_ext_command() click to toggle source

bind a key to an external command wich can be then be used for files

# File bin/cetus, line 2956
def bindkey_ext_command
  print
  pbold 'Bind a capital letter to an external command'
  print 'Enter a capital letter to bind: '
  key = get_char
  return if key == 'Q'

  if /^[A-Z]$/.match?(key)
    print "Enter an external command to bind to #{key}: "
    com = gets.chomp
    if com != ''
      print 'Enter prompt for command (blank if same as command): '
      pro = gets.chomp
      pro = com if pro == ''
    end
    print 'Pause after output [y/n]: '
    yn = get_char
    @bindings[key] = "command_file #{pro} #{yn} #{com}"
  end
end
bookmark_menu() click to toggle source
# File bin/cetus, line 1737
def bookmark_menu
  h = {
    v: :view_bookmarks,
    c: :create_bookmark,
    r: :remove_bookmark,
    g: :goto_bookmark
  }
  menu 'Bookmark Menu', h
end
calculate_bookmarks_for_dir() click to toggle source

FIXME: need to update when bookmark created or deleted

# File bin/cetus, line 574
def calculate_bookmarks_for_dir

  bm = @bookmarks.select { |k, v| v == @current_dir }.keys.join(',')
  bm = " ('#{bm})" if bm && bm != ''
  @bm = bm
end
calculate_numeric_hotkeys() click to toggle source
# File bin/cetus, line 868
def calculate_numeric_hotkeys
  @hk = @bookmarks.select { |k, _| k.match?(/[0-9]/)  }.keys.sort.join('')
end
change_dir(f) click to toggle source

cd to a dir.

# File bin/cetus, line 1351
def change_dir f
  unless File.directory? f
    perror "#{f} is not a directory, or does not exist."
    return
  end

  # before leaving a dir we save it in the list, as well as the cursor
  # position, so we can restore that position when we return
  @visited_dirs.insert(0, Dir.pwd)
  save_dir_pos

  f = expand_path(f)
  Dir.chdir f
  @current_dir = Dir.pwd # 2019-04-24 - earlier was in post_cd but too late
  read_directory
  post_cd

  redraw_required
end
child_dirs() click to toggle source
# File bin/cetus, line 2285
def child_dirs
  @title = 'Directories in current directory'
  # M is MARK_DIRS option for putting trailing slash after dir
  # @files = `zsh -c 'print -rl -- *(/#{@sorto}#{@hidden}M)'`.split("\n")
  @files = dirs
  message "#{@files.size} directories."
end
clean_selected_files() click to toggle source

remove non-existent files from select list due to move or delete

or rename or whatever
# File bin/cetus, line 2933
def clean_selected_files
  @selected_files.select! { |x| x = expand_path(x); File.exist?(x) }
end
clear_last_line() click to toggle source
# File bin/cetus, line 3831
def clear_last_line
  last_line
  # print a colored line at bottom of screen
  # \e[33;41m  - set color of status_line
  # %*s        - set blank spaces for entire line
  # \e[m       - reset text mode
  # \r         - bring to start of line since callers will print.
  print format("\e[33;4%sm%*s\e[m\r", @status_color || '1', @gcols, ' ')
end
clear_message() click to toggle source
# File bin/cetus, line 3857
def clear_message
  if @keys_to_clear
    @keys_to_clear -= 1
    if @keys_to_clear == 0
      message nil
      @keys_to_clear = nil
    end
  end
end
clear_screen() click to toggle source

copied from fff

# File bin/cetus, line 170
def clear_screen
  # Only clear the scrolling window (dir item list).
  # '\e[%sH':    Move cursor to bottom of scroll area.
  # '\e[9999C':  Move cursor to right edge of the terminal.
  # '\e[1J':     Clear screen to top left corner (from cursor up).
  # '\e[2J':     Clear screen fully (if using tmux) (fixes clear issues).
  # '\e[1;%sr':  Clearing the screen resets the scroll region(?). Re-set it.
  #              Also sets cursor to (0,0).
  # ENV["TMUX:+\e[2J]"],
  printf("\e[%sH\e[9999C\e[1J\e[1;%sr", \
         @glines - 0, # was 2
         @glines) # was grows
end
color_for(f) click to toggle source

determine color for a filename based on extension, then pattern, then filetype

# File bin/cetus, line 1089
def color_for f
  return nil if f == SEPARATOR

  fname = f.start_with?("~/") ? File.expand_path(f) : f

  extension = File.extname(fname)
  color = @ls_color[extension]
  return color if color

  # check file against patterns
  if File.file?(fname)
    @ls_pattern.each_pair do |k, v|
      # if fname.match?(/k/)
      if /#{k}/.match?(fname)
        # @log.debug "#{fname} matched #{k}. color is #{v[1..-2]}"
        return v
        # else
        # @log.debug "#{fname} di not match #{k}. color is #{v[1..-2]}"
      end
    end
  end

  # check filetypes
  if File.exist? fname
    # @log.debug "Filetype:#{File.ftype(fname)}"

    return @ls_ftype[File.ftype(fname)] if @ls_ftype.key? File.ftype(fname)
    return @ls_ftype['ex'] if File.executable?(fname)
  else
    # orphan file, but fff uses mi
    return @ls_ftype['mi'] if File.symlink?(fname)

    @log.warn "FILE WRONG: #{fname}"
    return @ls_ftype['or']
  end

  nil
end
column_next(direction=0) click to toggle source
Tabs to next column in multi-column displays.
Moves column offset so we can reach unindexed columns or entries,
or those with double letters

0 forward and any other back/prev direction is 0 (forward) or '1' (backward)

# File bin/cetus, line 2769
def column_next direction=0
  # right movement or panning cycles back to first column
  # leftward movement stops at first column.
  if direction == 0
    @stact += @grows
    @stact = 0 if @stact >= @vps
    @cursor += @grows
    # 2019-03-18 - zero loses offset. we need to maintain it
    # @cursor = 0 if @cursor >= @vps
    if @cursor - @sta >= @vps
      @cursor -= @grows while @cursor > @sta
      @stact -= @grows while @stact > 0
      @cursor += @grows if @cursor < @sta
      @stact  += @grows if @stact < 0
    end
  else
    @stact -= @grows
    @cursor -= @grows
    @stact = 0 if @stact < 0
    # setting cursor as zero loses the position or offset
    # We are trying to maintain offset
    @cursor += @grows if @cursor < 0
  end
end
columnate(ary, siz) click to toggle source

print in columns ary - array of data (viewport, not view) with hint and mark, possibly long listing siz - how many lines should there be in one column

# File bin/cetus, line 909
def columnate ary, siz
  buff = []
  return buff if ary.nil? || ary.empty?

  # determine width based on number of files to show
  # if less than sz then 1 col and full width
  #
  wid = get_width ary.size, siz
  @temp_wid = wid

  # ix refers to the index in the complete file list, wherease we only show 60 at a time
  ix = 0
  loop do
    ## ctr refers to the index in the column
    #  siz is how many items in one column
    ctr = 0
    while ctr < siz

      f = get_formatted_filename(ix, wid)

      # if already a value in that line, append to it
      if buff[ctr]
        buff[ctr] += f
      else
        buff[ctr] = f
      end

      ctr += 1
      ix += 1
      break if ix >= ary.size
    end
    break if ix >= ary.size
  end
  buff
end
columns_incdec(howmany) click to toggle source

increase or decrease column

# File bin/cetus, line 2948
def columns_incdec howmany
  @gviscols += howmany.to_i
  @gviscols = 1 if @gviscols < 1
  @gviscols = 6 if @gviscols > 6
  @pagesize = @grows * @gviscols
end
command_file(prompt, *command) click to toggle source

generic external command program prompt is the user friendly text of command such as list for ls, or extract for dtrx, page for less pauseyn is whether to pause after command as in file or ls

# File bin/cetus, line 2715
def command_file prompt, *command
  pauseyn = command.shift
  command = command.join ' '
  clear_last_line
  print "[#{prompt}] Choose a file [#{@view[@cursor]}]: "
  file = ask_hint @view[@cursor]
  # print "#{prompt} :: Enter file shortcut: "
  # file = ask_hint
  perror 'Command Cancelled' unless file
  return unless file

  file = expand_path(file)
  if File.exist? file
    file = Shellwords.escape(file)
    pbold "#{command} #{file} (#{pauseyn})"
    system "#{command} #{file}"
    setup_terminal
    pause if pauseyn == 'y'
    refresh
  else
    perror "File #{file} not found"
  end
end
config_read() click to toggle source

read dirs and files and bookmarks from file

# File bin/cetus, line 2075
def config_read
  f = File.expand_path(CONFIG_FILE)
  return unless File.readable? f

  hash = loadYML(f)
  @used_dirs = hash['DIRS']
  @visited_files = hash['FILES']
  @bookmarks = hash['BOOKMARKS']
  @used_dirs.concat get_env_paths
end
config_write() click to toggle source
save dirs and files and bookmarks to a file
  • moved to yml 2019-03-09

# File bin/cetus, line 2101
def config_write
  # Putting it in a format that zfm can also read and write
  f1 = File.expand_path(CONFIG_FILE)
  hash = {}
  hash['DIRS'] = @used_dirs.select { |dir| File.exist? dir }
  hash['FILES'] = @visited_files.select { |file| File.exist? file }
  # NOTE bookmarks is a hash and contains FILE:cursor_pos
  hash['BOOKMARKS'] = @bookmarks # .select {|file| File.exist? file}
  writeYML hash, f1
  @writing = @modified = false
  message "Saved #{f1}"
end
convert_key_to_index(key) click to toggle source

convert pressed key to an index in viewport. Earlier this was simple, but now that we put hints/shortcuts

in rows on the left after panning, we need to account for cycled hints.
# File bin/cetus, line 2418
def convert_key_to_index key
  i = IDX.index(key)
  return nil unless i

  # @log.debug "get_index with #{key}: #{i}. #{@stact}. #{@vps}"
  # TODO: if very high key given, consider going to last file ?
  #  that way one can press zz or ZZ to go to last file.
  # 2019-04-11 - XXX actually this doesnt place the cursor on last file
  #  it opens it, which may not be what we want
  retnil = nil # vps - 1 # nil
  # user has entered a key that is outside of range, return nil
  return retnil if @stact == 0 && i + @stact >= @vps

  # again, key out of range
  # return nil if @stact > 0 && i + @stact >= @vps && i + @stact - @vps >= @stact
  return retnil if @stact > 0 && i + @stact >= @vps && i - @vps >= 0

  # panning case, hints are recycled
  return (i + @stact) - @vps if i + @stact >= @vps

  # regular hint
  return i + @stact # if i

end
copy_file() click to toggle source
# File bin/cetus, line 2506
def copy_file
  rbfiles = current_or_selected_files
  return if rbfiles.nil? || rbfiles.empty?

  count = rbfiles.count
  first = rbfiles.first
  text = "#{count} files"

  # Target must be directory for multiple files.
  # NOTE: target should not be same as source dir but there can be files
  #  from multiple directories
  if count == 1
    if File.exist? File.basename(first)
      default = get_unique_file_name File.basename(first)
      Readline::HISTORY.push default
    else
      default = '.'
    end
    # default : if file exists here, then add suffix
    # if no file here, then directory is default.
  else
    default = if File.exist? File.basename(first)
                ''
              else
                '.'
              end
    # current directory is default if first file does not exist
    # if first file exists here, then no default
  end
  target = readline "Copy to (#{default}): "
  return unless target # C-c

  target = default if target == ''
  target = File.expand_path(target)
  return if target == ''

  if count > 1 && !File.directory?(target)
    perror 'Copy target must be a directory for multiple files.'
    return
  end

  if count == 1 && !File.directory?(target) && File.exist?(target)
    perror "Target #{target} exists."
    return
  end

  # if rbfiles is array, then dest must be a directory.
  rbfiles = rbfiles.first if rbfiles.count == 1

  begin
    FileUtils.cp rbfiles, target
    message "Copied #{text} to #{target}."
  rescue StandardError => exc
    @log.warn exc.to_s
    @log.warn "Target: #{target}, files:#{rbfiles}"
    perror exc.to_s
  end
  refresh
end
create_a_dir() click to toggle source
# File bin/cetus, line 3322
def create_a_dir
  str = readline 'Enter directory name: '
  return if str == ''

  if File.exist? str
    perror "#{str} exists."
    return
  end
  begin
    FileUtils.mkdir str
    @used_dirs.insert(0, str) if File.exist?(str)
    refresh
  rescue StandardError => ex
    perror "Error in newdir: #{ex}"
  end
end
create_a_file() click to toggle source
# File bin/cetus, line 3339
def create_a_file
  str = readline 'Enter file name: '
  return if str.nil? || str == ''

  system %($EDITOR "#{str}")
  setup_terminal
  @visited_files.insert(0, expand_path(str)) if File.exist?(str)
  refresh
end
create_bookmark() click to toggle source

accept a character to save this dir as a bookmark

# File bin/cetus, line 2129
def create_bookmark
  clear_last_line
  print 'Enter A-Z, a-z or 0-9 to create a bookmark: '
  # print "\e[?25h" # unhide cursor
  key = get_char
  # print "\e[?25l" # hide cursor
  if /^[0-9A-Za-z]$/.match?(key)
    set_bookmark key
    @modified = true
    message "Created bookmark #{key} for #{File.basename(Dir.pwd)}."
  else
    perror 'Bookmark must be alpha character or number.'
  end
end
create_dir_with_selection() click to toggle source

maybe do this internally

# File bin/cetus, line 3418
def create_dir_with_selection
  execute_script 'create_dir_with_selection'
end
create_menu() click to toggle source

TODO: create a link

# File bin/cetus, line 1922
def create_menu
  h = { f: :create_a_file,
        d: :create_a_dir,
        s: :create_dir_with_selection,
        b: :create_bookmark }
  _, menu_text = menu 'Create Menu', h
end
create_viewport() click to toggle source

——————- create_viewport —————— #

# File bin/cetus, line 548
def create_viewport
  @view = if @patt
            if @ignore_case
              @files.grep(/#{@patt}/i)
            else
              @files.grep(/#{@patt}/)
            end
          else
            @files
          end

  fl = @view.size
  @sta = 0 if @sta >= fl || @sta < 0
  @cursor = 0 if @cursor >= fl || @cursor < 0

  # NOTE if we make cursor zero, then it can be < sta so in the next line
  #  it will be made equal to sta which we may not want
  @cursor = @sta if @sta > @cursor

  # viewport are the files that are visible, subset of view
  @viewport = @view[@sta, @pagesize]
  @vps = @viewport.size
end
cset(symb) click to toggle source
# File bin/cetus, line 1862
def cset symb
  return if symb.nil? || symb == :''

  if @toggles.key? symb
    toggle_value symb
  elsif @options.key? symb
    rotate_value symb
  else
    @log.warn "CSET: #{symb} does not exist. Please check code."
    raise ArgumentError, "CSET: (#{symb}) does not exist. Please check code."
  end
  rescan_required
  return true
end
current_file() click to toggle source

convenience method to return file under cursor

# File bin/cetus, line 3350
def current_file
  @view[@cursor]
end
current_or_selected_files() click to toggle source
# File bin/cetus, line 3354
def current_or_selected_files
  return @selected_files unless @selected_files.empty?

  return [current_file]
end
cursor_dn() click to toggle source

move cursor down a line

# File bin/cetus, line 3073
def cursor_dn
  @cursor_movement = :down
  @old_cursor = @cursor
  move_to(pos + 1)
end
cursor_scroll_dn() click to toggle source

scroll cursor down

# File bin/cetus, line 3060
def cursor_scroll_dn
  @cursor_movement = :down
  @old_cursor = @cursor
  move_to(pos + MSCROLL)
end
cursor_scroll_up() click to toggle source
# File bin/cetus, line 3066
def cursor_scroll_up
  @cursor_movement = :up
  @old_cursor = @cursor
  move_to(pos - MSCROLL)
end
cursor_up() click to toggle source
# File bin/cetus, line 3079
def cursor_up
  @old_cursor = @cursor
  @cursor_movement = :up
  move_to(pos - 1)
end
date_format(tim) click to toggle source

format date for file given stat

# File bin/cetus, line 898
def date_format tim
  # tim.strftime '%Y/%m/%d %H:%M:%S'
  tim.strftime '%Y/%m/%d %H:%M'
end
debug_vars() click to toggle source
# File bin/cetus, line 1655
def debug_vars
  page_with_tempfile do |file|
    file.puts "DEBUG VARIABLES for #{current_file}:"
    file.puts
    file.puts "sta    #{@sta}"
    file.puts "cursor #{@cursor}"
    file.puts "stact  #{@stact}"
    file.puts "viewport.size  #{@vps}"
    file.puts "pagesize       #{@pagesize}"
    file.puts "view.size      #{@view.size}"
    file.puts "grows          #{@grows}"
    file.puts "File: #{current_file}"
    file.puts
    file.puts "Opener: #{opener_for(current_file)}"
    file.puts
    file.puts `file "#{current_file}"`
    file.puts
    file.puts `stat   "#{current_file}"`
  end
  redraw_required
end
delete_file() click to toggle source
# File bin/cetus, line 2443
def delete_file
  # file_actions :delete
  rbfiles = current_or_selected_files
  return if rbfiles.nil? || rbfiles.empty?

  count = rbfiles.count
  first = rbfiles.first
  text = count == 1 ? File.basename(first) : "#{count} files"
  shfiles = Shellwords.join(rbfiles)

  delcommand = 'rmtrash'
  clear_last_line
  print "#{delcommand} #{text[0..40]} ? [yn?]: "
  key = get_char
  view_selected_files if key == '?'
  return if key != 'y'

  clear_last_line
  print "\r deleting ..."
  system "#{delcommand} #{shfiles}"
  @log.info "trashed #{shfiles}."
  message "Deleted #{text[0..40]}."
  refresh
end
dirs(dir='*') click to toggle source
# File bin/cetus, line 2293
def dirs dir='*'
  files = Dir.glob(dir, File::FNM_DOTMATCH).select { |f| File.directory?(f) } - %w[. ..]
  files = add_slash files
  files
end
dirtree() click to toggle source
# File bin/cetus, line 2305
def dirtree
  @title = 'Child directories recursive'
  # zsh **/ is recursive
  # files1 = `zsh -c 'print -rl -- **/*(/#{@sorto}#{@hidden}M)'`.split("\n")
  @files = Dir['**/']
  message "#{@files.size} files."
end
draw_directory() click to toggle source
# File bin/cetus, line 696
def draw_directory

  # view consists of all files (filtered by pattern if necessary)
  # viewport is only that subset of view that is displayed on screen
  create_viewport
  clear_screen
  print_title

  # break viewport into as many columns as required
  buff = columnate @viewport, @grows

  # starts printing array on line 3
  buff.each { |line| print "\r", line, "\n" }
  print

  status_line

  # place cursor correctly, we will use this to highlight current row
end
edit_current() click to toggle source

regardless of mode, edit the current file using editor

# File bin/cetus, line 1285
def edit_current
  command = ENV['EDITOR'] || ENV['VISUAL'] || 'vim'
  run_on_current command
  @visited_files.insert(0, expand_path(current_file))
end
enhance_file_list() click to toggle source

If there's a short file list, take recently mod and accessed folders and put latest files from there and insert it here. I take both since recent mod can be binaries / object files and gems created by a process, and not actually edited files. Recent accessed gives latest source, but in some cases even this can be misleading since running a program accesses include files.

# File bin/cetus, line 3536
def enhance_file_list
  return unless @enhanced_mode
  @current_dir ||= Dir.pwd

  begin
    actr = @files.size

    # zshglob: M = MARK_DIRS with slash
    # zshglob: N = NULL_GLOB no error if no result, this is causing space to split
    #  file sometimes for single file.

    # if only one entry and its a dir
    # get its children and maybe the recent mod files a few
    # FIXME: simplify condition into one
    if @files.size == 1
      # its a dir, let give the next level at least
      return unless @files.first[-1] == '/'

      d = @files.first
      # zshglob: 'om' = ordered on modification time
      # f1 = `zsh -c 'print -rl -- #{d}*(omM)'`.split("\n")
      f = get_files_by_mtime(d)

      if f && !f.empty?
        @files.concat f
        @files.concat get_important_files(d)
      end
      return
    end
    #
    # check if a ruby project dir, although it could be a backup file too,
    # if so , expand lib and maybe bin, put a couple recent files
    # FIXME: gemspec file will be same as current folder
    if @files.index('Gemfile') || !@files.grep(/\.gemspec/).empty?

      if @files.index('app/')
        insert_into_list('config/', "config/routes.rb")
      end

      # usually the lib dir has only one file and one dir
      # NOTE: avoid lib if rails project
      flg = false
      @files.concat get_important_files(@current_dir)
      if @files.index('lib/') && !@files.index('app/')
        # get first five entries by modification time
        # f1 = `zsh -c 'print -rl -- lib/*(om[1,5]MN)'`.split("\n")
        f = get_files_by_mtime('lib')&.first(5)
        # @log.warn "f1 #{f1} != #{f} in lib" if f1 != f
        if f && !f.empty?
          insert_into_list('lib/', f)
          flg = true
        end

        # look into lib file for that project
        # lib has a dir in it with the gem name
        dd = File.basename(@current_dir)
        if f.index("lib/#{dd}/")
          # f1 = `zsh -c 'print -rl -- lib/#{dd}/*(om[1,5]MN)'`.split("\n")
          f = get_files_by_mtime("lib/#{dd}")&.first(5)
          # @log.warn "2756 f1 #{f1} != #{f} in lib/#{dd}" if f1 != f
          if f && !f.empty?
            insert_into_list("lib/#{dd}/", f)
            flg = true
          end
        end
      end

      # look into bin directory and get first five modified files
      # FIXME: not in the case of rails, only for commandline programs
      if @files.index('bin/') && !@files.index('app/')
        # f1 = `zsh -c 'print -rl -- bin/*(om[1,5]MN)'`.split("\n")
        f = get_files_by_mtime('bin')&.first(5)
        # @log.warn "2768 f1 #{f1} != #{f} in bin/" if f1 != f
        insert_into_list('bin/', f) if f && !f.empty?
        flg = true
      end

      # oft used rails files
      # TODO remove concerns dir
      # FIXME too many files added, try adding recent only
      if @files.index('app/')
        [ "app/controllers", "app/models", "app/views" ].each do |dir|
          f = get_files_by_mtime(dir)&.first(5)
          if f && !f.empty?
            @log.debug "f has #{f.count} files before"
            @log.debug "f has #{f} files before"
            f = get_recent(f)
            @log.debug "f has #{f.count} files after"
            @log.debug "f has #{f} files after"
            insert_into_list("#{dir}/", f) unless f.empty?
          end
        end

        insert_into_list('config/', "config/routes.rb")

        # top 3 dirs in app dir
        f = get_files_by_mtime('app/')&.first(3)
        insert_into_list('app/', f) if f && !f.empty?
        flg = true
      end
      return if flg


    end  # Gemfile

    # 2019-06-01 - added for crystal and other languages
    if @files.index('src/')
      f = get_files_by_mtime('src')&.first(5)
      insert_into_list('src/', f) if f && !f.empty?
    end
    return if @files.size > 15

    # Get most recently accessed directory
    ## NOTE: first check accessed else modified will change accessed
    # 2019-03-28 - adding NULL_GLOB breaks file name on spaces
    # print -n : don't add newline
    # zzmoda = `zsh -c 'print -rn -- *(/oa[1]MN)'`
    # zzmoda = nil if zzmoda == ''
    moda = get_most_recently_accessed_dir
    # @log.warn "Error 2663 #{zzmoda} != #{moda}" if zzmoda != moda
    if moda && moda != ''

      # get most recently accessed file in that directory
      # NOTE: adding NULL_GLOB splits files on spaces
      # FIXME: this zsh one gave a dir instead of file.
      # zzmodf = `zsh -c 'print -rl -- #{moda}*(oa[1]M)'`.chomp
      # zzmodf = nil if zzmodf == ''
      modf = get_most_recently_accessed_file moda
      # @log.warn "Error 2670 (#{zzmodf}) != (#{modf}) gmra in #{moda} #{zzmodf.class}, #{modf.class} : Loc: #{Dir.pwd}" if zzmodf != modf

      raise "2784: #{modf}" if modf && !File.exist?(modf)

      insert_into_list moda, modf if modf && modf != ''

      # get most recently modified file in that directory
      # zzmodm = `zsh -c 'print -rn -- #{moda}*(om[1]M)'`.chomp
      modm = get_most_recently_modified_file moda
      # zzmodm = nil if zzmodm == ''
      # @log.debug "Error 2678 (gmrmf) #{zzmodm} != #{modm} in #{moda}" if zzmodm != modm
      raise "2792: #{modm}" if modm && !File.exist?(modm)

      insert_into_list moda, modm if modm && modm != '' && modm != modf
    end

    ## get most recently modified dir
    # zzmodm = `zsh -c 'print -rn -- *(/om[1]M)'`
    # zzmodm = nil if zzmodm == ''
    modm = get_most_recently_modified_dir
    # @log.debug "Error 2686 rmd #{zzmodm} != #{modm}" if zzmodm != modm

    if modm != moda

      # get most recently accessed file in that directory
      # modmf = `zsh -c 'print -rn -- #{modm}*(oa[1]M)'`
      modmf = get_most_recently_accessed_file modm
      raise "2806: #{modmf}" if modmf && !File.exist?(modmf)

      insert_into_list modm, modmf

      # get most recently modified file in that directory
      # modmf11 = `zsh -c 'print -rn -- #{modm}*(om[1]M)'`
      modmf1 = get_most_recently_modified_file modm
      raise "2812: #{modmf1}" if modmf1 && !File.exist?(modmf1)

      insert_into_list(modm, modmf1) if modmf1 != modmf
    else
      # if both are same then our options get reduced so we need to get something more
      # If you access the latest mod dir, then come back you get only one, since mod and accessed
      # are the same dir, so we need to find the second modified dir
    end
  ensure
    # if any files were added, then add a separator
    bctr = @files.size
    @files.insert actr, SEPARATOR if actr < bctr
  end
end
escape() click to toggle source

clear sort order and refresh listing, used typically if you are in some view such as visited dirs or files

# File bin/cetus, line 1384
def escape
  @sorto = @default_sort_order
  @viewctr = 0
  @title = nil
  @filterstr = 'M'
  @message = nil
  @mode = nil
  visual_block_clear
  refresh
end
execute() click to toggle source

execute a command on selected or current file

# File bin/cetus, line 2978
def execute
  run_command current_or_selected_files
end
execute_script(filename) click to toggle source

this is quite important and should not be left to a script example of calling a script from somewhere directly without selection

# File bin/cetus, line 3405
def execute_script filename
  # script_path = '~/.config/cetus/scripts'
  script_path = File.join(CONFIG_PATH, 'cetus', 'scripts')
  script = File.expand_path(File.join(script_path, filename))
  unless File.exist? script
    perror "Unable to find #{filename}: #{script}"
    return
  end

  scripts script
end
expand_path(file) click to toggle source

Had to create this since ruby crashes on files start with '~'

# File bin/cetus, line 3898
def expand_path file
  return File.expand_path(file) if file.start_with?("~/")
  return file if file[0] == '/'

  return File.join(@current_dir, file)
end
extras() click to toggle source

This is quite badly placed and named. Maybe these should go elsewhere

# File bin/cetus, line 1931
def extras
  h = {
    '1' => :one_column,
    '2' => :multi_column,
    c: :columns,
    s: :scripts,
    g: :generators,
    B: :bindkey_ext_command,
    f: :page_flags,
    R: :remove_from_list,
    v: :vidir,
    r: :config_read,
    w: :config_write
  }
  key, menu_text = menu 'Extras Menu', h
  case menu_text
  when :one_column
    @pagesize = @grows
  when :multi_column
    @pagesize = @grows * @gviscols
  when :columns
    print "How many columns to show: 1-6 [current #{@gviscols}]? "
    key = get_char
    key = key.to_i
    if key > 0 && key < 7
      @gviscols = key.to_i
      @pagesize = @grows * @gviscols
    end
  end
end
ffind() click to toggle source
# File bin/cetus, line 3004
def ffind
  last_line
  # print 'Enter a file name pattern to find: '
  pattern = readline '! find . -iname :'
  return if pattern == ''

  @title = "Files found using 'find' #{pattern}"
  files = `find . -iname "#{pattern}"`.split("\n")
  if files.empty?
    perror 'No files found. Try adding *'
  else
    @files = files
  end
end
file_actions(action=nil) click to toggle source

currently i am only passing the action in from the list there as a key I should be able to pass in new actions that are external commands 2019-03-08 - TODO when a file name changes or moves it must be removed

from selection
# File bin/cetus, line 2798
def file_actions action=nil
  h = {
    d: :delete,
    D: '/bin/rm',
    m: :move,
    c: :copy,
    r: :rename,
    e: :execute,
    v: ENV['EDITOR'] || :vim,
    o: :open_current,
    p: :most
  }

  rbfiles = current_or_selected_files # use with ruby FileUtils
  return if rbfiles.nil? || rbfiles.empty?

  count = rbfiles.count
  first = rbfiles.first

  h.delete(:r) if count > 1

  if count == 1

    # add chdir if dir of file under cursor is not same as current dir
    h[:C] = :chdir if File.dirname(File.expand_path(first)) != Dir.pwd

    h[:s] = :page_stat_for_file
    h[:f] = :file
    if filetype(first) == :zip
      h[:x] = :dtrx
      h[:u] = :unzip
    end
    h[:g] = if File.extname(first) == '.gz'
              :gunzip
            else
              :gzip
            end

  end
  # 2019-06-20 - FIXME next line crashes current_file is nil
  if current_file
    h[:M] = :set_move_target if File.directory?(current_file)
  end
  h[:z] = :zip unless filetype(first) == :zip
  h['/'] = :ffind
  h[:l] = :locate

  # if first file has spaces then add remspace method
  # TODO: put this in scripts
  # take care that directories can have spaces
  h[:W] = :remspace if File.basename(first).index ' '

  text = count == 1 ? File.basename(first) : "#{count} files"
  shfiles = Shellwords.join(rbfiles)

  # --------------------------------------------------------------
  # Use 'text' for display
  # Use 'shfiles' for system actions, these are escaped
  # Use 'rbfiles' for looping and ruby FileUtils, system commands can bork on unescaped names
  # Use 'count' for how many files, in case of single file operation.
  # --------------------------------------------------------------

  # if no action passed, then ask for action
  if action
    menu_text = action
  else
    key, menu_text = menu "File Menu for #{text[0..@gcols-20]}", h
    menu_text = :quit if key == 'q'
  end
  return unless menu_text # pressed some wrong key

  case menu_text.to_sym

  when :quit
    1
  when :delete
    delete_file

  when :move
    move_file

  when :copy
    copy_file

  when :zip
    zip_file

  when :rename
    rename_file

  when :chdir
    change_dir File.dirname(File.expand_path(rbfiles.first)) if count == 1

  when :set_move_target
    set_move_target current_file

  when :most, :less, :vim

    system "#{menu_text} #{shfiles}"
    setup_terminal
    # should we remove from selection ?

  when :remspace
    remove_spaces_from_name

  when :execute
    execute

  when :page_stat_for_file
    1
    # already been executed by menu
    # We could have just put 'stat' in the menu but that doesn't look so nice
  when :locate, :ffind
    1
  else
    return unless menu_text

    clear_last_line
    pause "#{menu_text} #{shfiles} "
    system "#{menu_text} #{shfiles}"
    pause # putting this back 2019-04-13 - file doesn't show anything
    message "Ran #{menu_text}."
    @log.info "#{menu_text} #{shfiles}"
    setup_terminal
    refresh
  end

  return if count == 0

  clean_selected_files
  visual_block_clear # 2019-04-15 - get out of mode after operation over.
end
file_matching?(file, patt) click to toggle source
# File bin/cetus, line 3227
def file_matching? file, patt
  file =~ /#{patt}/
end
file_starting_with(first_char=nil) click to toggle source

————- file matching methods ——————————–#

# File bin/cetus, line 3217
def file_starting_with first_char=nil
  unless first_char
    clear_last_line
    print "\rEnter first char: "
    first_char = get_char
  end
  ix = return_next_match(method(:file_matching?), "^#{first_char}")
  goto_line ix if ix
end
filetype(f) click to toggle source

return filetype of file using `file` external command. NOTE: Should we send back executable as separate type or allow

it to be nil, so it will be paged.
# File bin/cetus, line 3262
def filetype f
  return nil unless f

  f = Shellwords.escape(f)
  s = `file #{f}`
  return :text if s.index 'text'
  return :zip if s.index(/[Zz]ip/)
  return :zip if s.index('archive')
  return :image if s.index 'image'
  return :sqlite if s.index 'SQLite'
  # return :db if s.index 'database'
  return :text if s.index 'data'

  nil
end
filter_files_by_pattern() click to toggle source

take regex from user, to run on files on screen, user can filter file names

# File bin/cetus, line 1533
def filter_files_by_pattern
  @title = 'Search Results: (Press Esc to cancel)'
  @mode = 'SEARCH'
  if @toggles[:instant_search]
    search_as_you_type
  else
    @patt = readline '/'
  end
end
filter_for_current_extension() click to toggle source
# File bin/cetus, line 2022
def filter_for_current_extension
  extn = File.extname(current_file)
  return unless extn

  @files = @files.select { |f| !File.directory?(f) && extn == File.extname(f) }
end
filter_menu() click to toggle source
# File bin/cetus, line 1962
def filter_menu
  h = { d: :dirs, f: :files,
        e: :emptydirs, '0' => :emptyfiles,
        r: :recent_files,
        a: :reduce_list,   # key ??? XXX
        x: :extension }
  _, menu_text = menu 'Filter Menu', h
  files = nil
  case menu_text
  when :dirs
    @filterstr = '/M'
    # zsh /M MARK_DIRS appends trailing '/' to directories
    files = `zsh -c 'print -rl -- *(#{@sorto}/M)'`.split("\n")
    @title = 'Filter: directories only'
  when :files
    @filterstr = '.'
    # zsh '.' for files, '/' for dirs
    files = `zsh -c 'print -rl -- *(#{@sorto}#{@hidden}.)'`.split("\n")
    @title = 'Filter: files only'
  when :emptydirs
    @filterstr = '/D^F'
    # zsh F = full dirs, ^F empty dirs
    files = `zsh -c 'print -rl -- *(#{@sorto}/D^F)'`.split("\n")
    @title = 'Filter: empty directories'
  when :emptyfiles
    @filterstr = '.L0'
    # zsh .L size in bytes
    files = `zsh -c 'print -rl -- *(#{@sorto}#{@hidden}.L0)'`.split("\n")
    @title = 'Filter: empty files'
  when :reduce_list
    files = reduce
  when :extension
    files = filter_for_current_extension
  when :recent_files
    # files = recent_files
    @title = 'Filter: files by mtime'
    files = get_files_by_mtime.first(10)
  else
    return
  end
  if files && files.count > 0
    @files = files
    @stact = 0
    @message = "Filtered on #{menu_text}. Press ESC to return."
    @bm = ' (' + @bindings.key('filter_menu') + ') '
  else
    perror 'Sorry, No files. '
    @title = nil
  end
end
format_long_listing(f) click to toggle source
# File bin/cetus, line 1056
def format_long_listing f
  return unless @long_listing
  return format('%10s  %s  %s', '-', '----------', f) if f == SEPARATOR

  begin
    if File.exist? f
      stat = File.stat(f)
    elsif f[0] == '~'
      stat = File.stat(expand_path(f))
    elsif File.symlink?(f)
      # dead link
      stat = File.lstat(f)
    else
      # remove last character and get stat
      last = f[-1]
      stat = File.stat(f.chop) if last == ' ' || last == '@' || last == '*'
    end
    # TODO select date_func from toggles

    f = format('%10s  %s  %s',
               readable_file_size(stat.size, 1),
               date_format(stat.send(@date_func)),
               f)

  rescue StandardError => e
    @log.warn "WARN::#{e}: FILE:: #{f}"
    f = format('%10s  %s  %s', '?', '??????????', f)
  end

  return f
end
fzfmenu(title, h) click to toggle source

2019-03-08 - 23:46 FIXME returns a String not symbol, so some callers can fail

# File bin/cetus, line 2220
def fzfmenu(title, h)
  return unless h

  pbold title.to_s
  # XXX using keys not values since toggle hash being used
  values = h.keys.join("\n")
  binding = `echo "#{values}" | fzf --prompt="#{title.to_s} :"`
  if binding
    binding = binding.chomp
    send(binding) if respond_to?(binding, true)
  end
  binding
end
generators() click to toggle source

allow user to select a script that generates filenames which

will be displayed for selection or action.
# File bin/cetus, line 3424
def generators
  write_selected_files

  title = 'Select a generator'
  script_path = '~/.config/cetus/generators'
  script_path = File.join(CONFIG_PATH, 'cetus', 'generators')
  binding = `find #{script_path} -type f | fzf --prompt="#{title} :"`.chomp
  return if binding.nil? || binding == ''

  # call generator and accept list of files
  @title = "Files from #{File.basename(binding)}"
  @files = `#{binding} "#{current_file}"`.split("\n")
end
get_char() click to toggle source
get a character from user and return as a string

Adapted from: stackoverflow.com/questions/174933/how-to-get-a-single-character-without-pressing-enter/8274275#8274275 Need to take complex keys and match against a hash.

# File bin/cetus, line 262
def get_char
  system('stty raw -echo 2>/dev/null') # turn raw input on
  c = nil
  # if $stdin.ready?
  c = $stdin.getc
  cn = c.ord
  return 'ENTER' if cn == 10 || cn == 13
  return 'BACKSPACE' if cn == 127
  return 'C-SPACE' if cn == 0
  return 'SPACE' if cn == 32
  # next does not seem to work, you need to bind C-i
  return 'TAB' if cn == 8

  if cn >= 0 && cn < 27
    x = cn + 96
    return "C-#{x.chr}"
  end
  if c == ''
    buff = c.chr
    loop do
      k = nil
      if $stdin.ready?
        k = $stdin.getc
        # puts "got #{k}"
        buff += k.chr
      else
        x = @kh[buff]
        return x if x

        # puts "returning with  #{buff}"
        if buff.size == 2
          ## possibly a meta/alt char
          k = buff[-1]
          return "M-#{k.chr}"
        end
        return buff
      end
    end
  end
  # end
  return c.chr if c
ensure
  # system('stty -raw echo 2>/dev/null') # turn raw input on
  # 2019-03-29 - echo was causing printing of arrow key code on screen
  # if moving fast
  system('stty -raw 2>/dev/null') # turn raw input on
end
get_env_paths() click to toggle source
# File bin/cetus, line 2086
def get_env_paths
  files = []
  %w[GEM_HOME PYTHONHOME].each do |p|
    d = ENV[p]
    files.push d if d
  end
  %w[RUBYLIB RUBYPATH GEM_PATH PYTHONPATH].each do |p|
    d = ENV[p]
    files.concat d.split(':') if d
  end
  files
end
get_files_by_atime(dir='.') click to toggle source
# File bin/cetus, line 3793
def get_files_by_atime dir='.'
  gfb dir, :atime
end
get_files_by_mtime(dir='*') click to toggle source

return a list of entries sorted by mtime. A / is added after to directories

# File bin/cetus, line 3789
def get_files_by_mtime dir='*'
  gfb dir, :mtime
end
get_formatted_filename(ix, wid) click to toggle source

given index (in viewport) get a formatted, displayable rendition of the filename returns a String with hint, marks, filename (optional details) and color codes truncated to correct width.

# File bin/cetus, line 986
def get_formatted_filename ix, wid
  f = @viewport[ix]

  ind = get_shortcut(ix)
  mark = get_mark(f)
  # make visited files bold
  bcolor = BOLD if mark == VMARK
  # Handle separator before enhanced file list.
  # We do lose a shortcut
  ind = cur = mark = '-' if f == SEPARATOR
  prefix = "#{ind}#{mark}#{cur}"

  color = color_for(f)
  color = "#{bcolor}#{color}"

  f = format_long_listing(f) if @long_listing

  # replace unprintable chars with ?
  f = f.gsub(/[^[:print:]]/, '?')

  f = "#{prefix}#{f}"

  unformatted_len = f.length
  if unformatted_len > wid
    # take the excess out from the center on both sides

    f = truncate_formatted_filename(f, unformatted_len, wid)
    f = "#{color}#{f}#{CLEAR}" if color # NOTE clear not added if not color

  elsif unformatted_len < wid

    # f << ' ' * (wid - unformatted_len)
    f = "#{color}#{f}#{CLEAR}" + ' ' * (wid - unformatted_len)
  end

  return f
end
get_important_files(dir) click to toggle source

Get visited files and bookmarks that are inside this directory

at a lower level.

2019-03-23 - not exactly clear what is happening XXX this gets a directory (containing '/' at end)

# File bin/cetus, line 3732
def get_important_files dir
  # checks various lists like visited_files and bookmarks
  # to see if files from this dir or below are in it.
  # More to be used in a dir with few files.
  list = []
  l = dir.size + 1

  # 2019-03-23 - i think we are getting the basename of the file
  #  if it is present in the given directory XXX
  @visited_files.each do |e|
    list << e[l..-1] if e.index(dir) == 0
  end
  list = get_recent(list)

  # bookmarks if it starts with this directory then add it
  # FIXME it puts same directory cetus into the list with full path
  # We need to remove the base until this dir. get relative part
  list1 = @bookmarks.values.select do |e|
    e.index(dir) == 0 && e != dir
  end

  list.concat list1
  list
end
get_index(key, vsz=999) click to toggle source
returns the integer offset in view (file array based on a-y za-zz and Za - Zz

Called when user types a key

should we even ask for a second key if there are not enough rows
What if we want to also trap z with numbers for other purposes
# File bin/cetus, line 2394
def get_index key, vsz=999
  # @log.debug "Etners get_index with #{key}"
  i = convert_key_to_index key
  return i if i

  if vsz > 25
    if key == 'z' || key == 'Z'
      last_line
      print key
      zch = get_char
      print zch
      i = convert_key_to_index("#{key}#{zch}")
      # @log.debug "convert returned #{i} for #{key}#{zch}"
      return i if i
      # i = IDX.index
      # return i + @stact if i
    end
  end
  nil
end
get_mark(file) click to toggle source
# File bin/cetus, line 1034
def get_mark file
  return SPACE if @selected_files.empty? && @visited_files.empty?

  @current_dir ||= Dir.pwd
  # this crashes on filename that starts with a ~. 2019-05-04 -
  fullname = if file.start_with?("~/")
               File.expand_path(file)
             else
               File.join(@current_dir, file)
             end

  return GMARK if selected?(fullname)
  return VMARK if visited? fullname

  # 2019-04-27 - this boldfaces visited files, but should only be done if color
  #  otherwise it will overflow.
  #  This was nice but it messes with width in multiple columns truncate
  # return "#{BOLD}+" if visited? fullname

  SPACE
end
get_most_recently_accessed_dir(dir='.') click to toggle source
# File bin/cetus, line 3757
def get_most_recently_accessed_dir dir='.'
  gmr dir, :directory?, :atime
end
get_most_recently_accessed_file(dir='.') click to toggle source
# File bin/cetus, line 3761
def get_most_recently_accessed_file dir='.'
  gmr dir, :file?, :atime
end
get_most_recently_modified_dir(dir='.') click to toggle source
# File bin/cetus, line 3769
def get_most_recently_modified_dir dir='.'
  file = gmr dir, :directory?, :mtime
end
get_most_recently_modified_file(dir='.') click to toggle source
# File bin/cetus, line 3765
def get_most_recently_modified_file dir='.'
  gmr dir, :file?, :mtime
end
get_recent(array) click to toggle source
# File bin/cetus, line 3718
def get_recent(array)
  array = array.select do |f|
    stat = File.stat(f)
    span = @now - stat.mtime
    spandays = span / 86400
    spandays < 3
  end
  array
end
get_shortcut(index) click to toggle source
return shortcut/hint for an index (offset in file array)

ix is the index of a file in the complete array (view)

# File bin/cetus, line 2369
def get_shortcut index
  # Case where user has panned to the right columns:
  # Earlier, we showed '<' in left columns, if user has panned right.
  # Now we show unused shortcuts after exhausting them.
  # return '<' if index < @stact
  if index < @stact
    index = @vps - @stact + index
    i = IDX[index]
    return i if i

    return '['
  end

  # Normal case (user has not panned columns)
  index -= @stact
  i = IDX[index]
  return i if i

  '->'
end
get_unique_file_name(fname) click to toggle source

generate a unique filename by adding a zero padded number prior to the extension. This is used only during copy operation.

# File bin/cetus, line 2568
def get_unique_file_name fname
  100.times do |i|
    suffix = "%03d" % i
    extn = File.extname(fname)
    base = File.basename(fname, extn)
    # f = fname + '.' + suffix
    f = base + suffix + extn
    return f unless File.exist?(f)
  end

  timestamp = Time.now.strftime('%Y%m%d-%H%M%S')
  return fname + '.' + timestamp
end
get_width(arysz, siz) click to toggle source
# File bin/cetus, line 1024
def get_width arysz, siz
  ars = [@pagesize, arysz].min
  d = 0
  return @gcols - d if ars <= siz

  tmp = (ars * 1.000 / siz).ceil
  wid = @gcols / tmp - d
  wid
end
gfb(dir, func) click to toggle source

get files ordered by mtime or atime, returning latest first dir is dir to get files in, default '.' func can be :atime or :mtime or even :size or :ftype

# File bin/cetus, line 3800
def gfb dir, func
  dir += '/*' if File.directory?(dir)
  dir = dir.gsub('//', '/')

  # sort by time and then reverse so latest first.
  sorted_files = Dir[dir].sort_by { |f|
      if File.exist? f
        File.send(func, f)
      else
        sys_stat(func, f)
      end
  }.reverse

  # add slash to directories
  sorted_files = add_slash sorted_files
  return sorted_files
end
gmr(dir, type, func) click to toggle source

get most recent file or directory, based on atime or mtime dir is name of directory in which to get files, default is '.' type is :file? or :directory? func can be :mtime or :atime or :ctime or :birthtime

# File bin/cetus, line 3777
def gmr dir, type, func
  file = Dir.glob(dir + '/*')
    .select { |f| File.send(type, f) }
    .max_by { |f| File.send(func, f) }
  file = File.basename(file) + '/' if file && type == :directory?
  return file.gsub('//', '/') if file

  nil
end
goto_bookmark(key=nil) click to toggle source

Goes to directory bookmarked with number or char.

# File bin/cetus, line 1513
def goto_bookmark key=nil
  unless key
    clear_last_line
    print 'Enter bookmark char (? to view): '
    key = get_char
  end
  if key == '?'
    view_bookmarks
    return
  end

  d = @bookmarks[key]
  if d
    change_dir d
  else
    perror "#{key} not a bookmark. "
  end
end
goto_dir() click to toggle source

accept dir to goto and change to that ( can be a file too)

# File bin/cetus, line 1433
def goto_dir
  # print "\e[?25h"
  # print_last_line 'Enter path: '
  begin
    # path = gets.chomp
    path = readline 'Enter path to go to: '
    if path.nil? || path == ''
      clear_last_line
      return
    end
    # rescue => ex
  rescue StandardError => ex
    # Nope, already caught interrupt and sent back nil
    perror 'Cancelled cd, press a key'
    return
  ensure
    # print "\e[?25l"
  end
  f = expand_path(path)
  unless File.directory? f
    ## check for env variable
    tmp = ENV[path]
    if tmp.nil? || !File.directory?(tmp)
      ## check for dir in home
      tmp = File.expand_path("~/#{path}")
      f = tmp if File.directory? tmp
    else
      f = tmp
    end
  end

  open_file f
end
goto_end() click to toggle source

goto end / bottom

# File bin/cetus, line 1568
def goto_end
  @cursor = @view.size - 1
  @sta = @view.size - @pagesize
  @old_cursor = nil
  redraw_required
end
goto_home_dir() click to toggle source
# File bin/cetus, line 1508
def goto_home_dir
  change_dir ENV['HOME']
end
goto_line(pos) click to toggle source

position cursor on a specific line which could be on a nother page therefore calculate the correct start offset of the display also.

# File bin/cetus, line 3252
def goto_line pos
  pages = ((pos * 1.00) / @pagesize).ceil
  pages -= 1
  @sta = pages * @pagesize + 1
  @cursor = pos
end
goto_parent_dir() click to toggle source

go to parent dir, and maintain cursor on the dir we came out of

# File bin/cetus, line 1483
def goto_parent_dir

  # if in search mode, LEFT should open current dir like escape
  # Not nice to put this in here !!!
  if @mode == "SEARCH"
    escape
    return
  end

  # When changing to parent, we need to keep cursor on
  #  parent dir, not first
  curr = File.basename(Dir.pwd)

  return if curr == '/'

  change_dir '..'

  return if curr == Dir.pwd

  # get index of child dir in this dir, and set cursor to it.
  index = @files.index(curr + '/')
  pause "WARNING: Could not find #{curr} in this directory." unless index
  @cursor = index if index
end
goto_previous_dir() click to toggle source
# File bin/cetus, line 1371
def goto_previous_dir
  prev_dir = @visited_dirs.first
  return unless prev_dir

  change_dir prev_dir
end
goto_top() click to toggle source
# File bin/cetus, line 1561
def goto_top
  @sta = @cursor = 0
  @old_cursor = nil
  redraw_required
end
group_directories_first() click to toggle source

put directories first, then files

# File bin/cetus, line 1404
def group_directories_first
  return if @group_directories == :none

  files = @files
  dirs = files.select { |f| File.directory?(f) }
  # earlier I had File? which removed links, esp dead ones
  fi   = files.reject { |f| File.directory?(f) }
  @files = if @group_directories == :first
             dirs + fi
           else
             fi + dirs
           end
end
index_of(dir) click to toggle source
# File bin/cetus, line 1378
def index_of dir
  @files.index(dir)
end
insert_into_list(_dir, file) click to toggle source

insert important files to end of @files

# File bin/cetus, line 3714
def insert_into_list _dir, file
  @files.push(*file)
end
last_line() click to toggle source
# File bin/cetus, line 3825
def last_line
  # system "tput cup #{@glines} 0"
  # print "\e[#{@glines};0H"
  tput_cup @glines, 0
end
list_files(dir='*', sorto=@sorto, hidden=@hidden, _filter=@filterstr) click to toggle source

return a list of directory contents sorted as per sort order NOTE: FNM_CASEFOLD does not work with Dir.glob XXX _filter unused.

# File bin/cetus, line 484
def list_files dir='*', sorto=@sorto, hidden=@hidden, _filter=@filterstr
  dir += '/*' if File.directory?(dir)
  dir = dir.gsub('//', '/')

  # decide sort method based on second character
  # first char is o or O (reverse)
  # second char is macLn etc (as in zsh glob)
  so = sorto ? sorto[1] : nil
  func = case so
         when 'm'
           :mtime
         when 'a'
           :atime
         when 'c'
           :ctime
         when 'L'
           :size
         when 'n'
           :path
         when 'x'
           :extname
         end

  # sort by time and then reverse so latest first.
  sorted_files = if hidden == 'D'
                   Dir.glob(dir, File::FNM_DOTMATCH) - %w[. ..]
                 else
                   Dir.glob(dir)
                 end

  # WARN: crashes on a deadlink since no mtime
  if func
    sorted_files = sorted_files.sort_by do |f|
      if File.exist? f
        File.send(func, f)
      else
        sys_stat(func, f)
      end
    end
  end

  sorted_files.sort! { |w1, w2| w1.casecmp(w2) } if func == :path && @ignore_case

  # zsh gives mtime sort with latest first, ruby gives latest last
  sorted_files.reverse! if sorto && sorto[0] == 'O'

  # add slash to directories
  sorted_files = add_slash sorted_files
  # return sorted_files
  @files = sorted_files
  calculate_bookmarks_for_dir # we don't want to override those set by others
end
list_selected_files() click to toggle source

————- end of view_selected_files ——————————–#

# File bin/cetus, line 3453
def list_selected_files
  @title = 'Selected Files'
  @files = @selected_files

  @bm =  @bindings.key('list_selected_files')
  @bm = ' (' + @bm + ')' if @bm
end
loadYML(filename) click to toggle source
# File bin/cetus, line 2116
def loadYML filename
  hash = YAML.safe_load(File.open(filename))
  warn hash.keys.size if @opt_debug
  return hash
end
locate() click to toggle source
# File bin/cetus, line 3019
def locate
  @locate_command ||= /darwin/.match?(RUBY_PLATFORM) ? 'mdfind -name' : 'locate'
  pattern = readline "Enter a file name pattern to #{@locate_command}: "
  return if pattern == ''

  @title = "Files found using: (#{@locate_command} #{pattern})"
  files = `#{@locate_command} #{pattern}`.split("\n")
  files.select! { |x| File.exist?(File.expand_path(x)) }
  if files.empty?
    perror 'No files found.'
    return
  end
  @files = files
  @bm = nil
end
main_menu() click to toggle source

MENU MAIN maybe a list menu - options for date format, size format, age, truncate_from, inode etc

menu(title, h) click to toggle source

Create a menu using title, and hash of key and binding

message(mess) click to toggle source

set message which will be displayed in status line TODO: maybe we should pad it 2019-04-08 -

# File bin/cetus, line 3820
def message mess
  @message = mess
  @keys_to_clear = 2 if mess
end
move_file() click to toggle source
# File bin/cetus, line 2468
def move_file
  rbfiles = current_or_selected_files
  return if rbfiles.nil? || rbfiles.empty?

  count = rbfiles.count
  first = rbfiles.first
  text = count == 1 ? File.basename(first) : "#{count} files"

  # multiple files can only be moved to a directory
  default = @move_target.nil? ? '.' : @move_target
  target = readline "Move #{text[0..40]} to (#{default}): "
  return unless target

  target = default if target == ''
  target = File.expand_path(target)
  return if target == ''

  if count > 1 && !File.directory?(target)
    perror 'Move target must be a directory for multiple files.'
    return
  end

  if count == 1 && !File.directory?(target) && File.exist?(target)
    perror "Target #{target} exists."
    return
  end

  begin
    FileUtils.mv rbfiles, target
    message "Moved #{text} to #{target}."
  rescue StandardError => exc
    @log.warn "move_file: #{exc}."
    @log.warn "MOVE: files: #{rbfiles}, target:#{target}"
    perror exc.to_s
  end
  refresh
end
move_instant() click to toggle source

instantly move to move target. Use during bulk operations so you don't have to enter or goto target dir and come back. XXX try removing this, making things complex.

# File bin/cetus, line 2665
def move_instant
  # FIXME: cannot be in target directory and do auto update
  # should we have a key for specifying move_target ?

  # for the mo, lets not move if nothing selected
  if @selected_files.empty?
    target = readline "Set Move target #{@move_target}:"
    target = Dir.pwd if target == '.'
    target = File.expand_path(target)
    @move_target = target
    message "Move target is #{@move_target}. Select files and press this key."
    return
  end

  # files selected. Use earlier target if there, else ask
  # XXX how do we change it once set
  target = @move_target
  if target.nil? || target == ''
    count = @selected_files.count
    target = readline "Move #{count} files to :"
    return unless target
  end
  target = File.expand_path(target)
  unless File.directory? target
    perror "#{target} not a directory."
    return
  end

  files = @selected_files
  ccount = 0
  files.each do |f|
    FileUtils.mv f, target
    @log.info "2.#{f} moved to #{target}."
    ccount += 1
  rescue StandardError => exc
    @log.warn 'Case 2:'
    @log.warn "Target is #{target}, file was #{f}"
    @log.warn exc.to_s
    perror exc.to_s
  end
  @move_target = target
  clean_selected_files
  message "#{ccount} files moved to #{target}."
  refresh
end
move_to(position) click to toggle source

move cursor to given position/line

# File bin/cetus, line 3091
def move_to position
  orig = @cursor
  place_cursor(CLEAR) if @highlight_row_flag
  @cursor = position
  @cursor = [@cursor, @view.size - 1].min
  @cursor = [@cursor, 0].max

  # try to stop it from landing on separator
  if current_file == SEPARATOR
    @cursor += 1 if @cursor_movement == :down
    @cursor -= 1 if @cursor_movement == :up
    # 2019-06-01 - remove return in case scrolling happens here
    # return
  end

  # 2019-03-18 - adding sta
  # @sta = position - only when page flips and file not visible
  # FIXME not correct, it must stop at end or correctly cycle
  # sta goes to 0 but cursor remains at 70
  # viewport.size may be wrong here, maybe should be pagesize only
  oldsta = @sta
  if @cursor - @sta >= @pagesize
    @sta += @pagesize
    # elsif @sta - @cursor >= @vps
  end
  if @sta > @cursor
    @sta -= @pagesize
    # @sta = @cursor
  end

  @cursor_movement = nil if oldsta != @sta # we need to redraw

  # -------- return here --- only visual mode continues ---------------------#
  return unless @visual_mode

  star = [orig, @cursor].min
  fin = [orig, @cursor].max
  @cursor_movement = nil # visual mode needs to redraw page

  # PWD has to be there in selction
  # FIXME with visited_files
  if selected? File.join(@current_dir, current_file)
    # this depends on the direction
    # @selected_files = @selected_files - @view[star..fin]
    remove_from_selection @view[star..fin]
    ## current row remains in selection always.
    add_to_selection current_file
  else
    # @selected_files.concat @view[star..fin]
    add_to_selection @view[star..fin]
  end
  message "#{@selected_files.count} files selected.   "
end
next_page() click to toggle source

page/scroll down.

# File bin/cetus, line 1544
def next_page
  @sta += @pagesize
  @cursor += @pagesize
  @sta = @cursor if @sta > @cursor
  @stact = 0
  @old_cursor = nil
  redraw_required
end
only_cursor_moved(flag=true) click to toggle source

indicate that only cursor moved, so don't redraw or rescan

# File bin/cetus, line 3893
def only_cursor_moved flag=true
  @cursor_movement = flag
end
only_cursor_moved?() click to toggle source

returns true if only cursor moved and redrawing not required

# File bin/cetus, line 3868
def only_cursor_moved?
  # only movement has happened within this page, don't redraw
  return unless @cursor_movement && @old_cursor

  # if cursor has not moved (first or last item on screen)
  if @old_cursor == @cursor
    place_cursor # otherwise highlight vanishes if keep pressing UP on first row.
    @cursor_movement = false
    return true # next in the loop
  end

  # we may want to print debug info if flag is on
  if @debug_flag
    clear_last_line
    print_debug_info
  else
    status_line
  end

  place_cursor
  @cursor_movement = false
  return true # next in the loop
end
open_current() click to toggle source
# File bin/cetus, line 1291
def open_current
  opener = /darwin/.match?(RUBY_PLATFORM) ? 'open' : 'xdg-open'
  run_on_current opener
  @visited_files.insert(0, expand_path(current_file))
end
open_file(f) click to toggle source

open file or directory

# File bin/cetus, line 1239
def open_file f
  return unless f

  f = File.expand_path(f) if f.start_with?("~/")
  unless File.exist? f
    # this happens if we use (T) in place of (M)
    # it places a space after normal files and @ and * which borks commands
    last = f[-1]
    f = f.chop if last == ' ' || last == '@' || last == '*'
  end

  # could be a bookmark with position attached to it
  f, _nextpos = f.split(':') if f.index(':')
  if File.directory? f
    save_dir_pos
    change_dir f # , nextpos
  elsif File.readable? f
    comm = opener_for f
    # '%%' will be substituted with the filename. See zip
    comm = if comm.index('%%')
             comm.gsub('%%', Shellwords.escape(f))
           else
             comm + " #{Shellwords.escape(f)}"
           end
    clear_screen
    reset_terminal
    system(comm.to_s)
    setup_terminal
    # XXX maybe use absolute_path instead of hardcoding
    f = expand_path(f)
    @visited_files.insert(0, f)
    push_used_dirs @current_dir
  else
    perror "open_file: (#{f}) not found"
    # could check home dir or CDPATH env variable DO
  end
  redraw_required
end
opener_for(f) click to toggle source
# File bin/cetus, line 3278
def opener_for f
  # by default, default command is nil. Changed in toggle_pager_mode
  @default_command ||= '$PAGER'
  # by default mode, is false, changed in toggle_pager_mode
  # Get filetype, and check for command for type, else extn else unknown
  if !@editor_mode
    ft = filetype f
    @log.debug "opener: #{ft} for #{f}"
    comm = PAGER_COMMAND[ft] if ft
    comm ||= PAGER_COMMAND[File.extname(f)]
    comm ||= PAGER_COMMAND[:unknown]
    @log.debug "opener: #{comm}"
  else
    # 2019-04-10 - what does this mean, that in editor_mode, editor
    # opens everything? what of images etc
    # TODO use editor only for text, otherwise use filetype or another hash
    # like editor_command
    comm = @default_command
  end
  comm ||= @default_command
  comm
end
order_menu() click to toggle source

—————– end of flag related functions —————————-#

# File bin/cetus, line 1878
def order_menu
  # zsh o = order, O = reverse order
  # ruby mtime/atime/ctime come reversed so we have to change o to O
  lo = nil
  h = { m: :modified, a: :accessed, M: :oldest,
        s: :largest, S: :smallest, n: :name, N: :rev_name,
        # d: :dirs,
        c: :inode,
        x: :extension,
        z: :clear }
  _, menu_text = menu 'Sort Menu', h
  case menu_text
  when :modified
    lo = 'Om'
  when :accessed
    lo = 'Oa'
  when :inode
    lo = 'Oc'
  when :oldest
    lo = 'om'
  when :largest
    lo = 'OL'
  when :smallest
    lo = 'oL'
  when :name
    lo = 'on'
  when :extension
    lo = 'ox'
  when :rev_name
    lo = 'On'
  when :dirs
    lo = '/'
  when :clear
    lo = ''
  else
    return
  end
  ## This needs to persist and be a part of all listings, put in change_dir.
  @sorto = lo
  message "Sorted on #{menu_text}"
  rescan_required
end
page_current() click to toggle source

regardless of mode, view the current file using pager

# File bin/cetus, line 1279
def page_current
  command = ENV['MANPAGER'] || ENV['PAGER'] || 'less'
  run_on_current command
end
page_flags() click to toggle source

display values of flag and options in pager

# File bin/cetus, line 1619
def page_flags
  # XXX once this is changed to an array, then remove 'keys'
  page_with_tempfile do |file|
    file.puts 'Values of toggles/flags'
    file.puts '-----------------------'
    @toggles.each_pair do |flag, v|
      if instance_variable_defined? "@#{flag}"
        value = instance_variable_get "@#{flag}"
      else
        value = v
      end
      file.puts "#{flag} : #{value}"
    end
    file.puts '-----------------------'
    @options.each_pair do |flag, struc|
      var = struc[:var]
      value = instance_variable_get "@#{var}"
      file.puts "#{flag} : #{value}"
    end
  end
end
page_stat_for_file() click to toggle source
# File bin/cetus, line 1609
def page_stat_for_file
  stat = `stat #{current_file}`
  return unless stat

  page_with_tempfile do |file|
    file.puts stat
  end
end
page_with_tempfile() { |file| ... } click to toggle source
# File bin/cetus, line 1641
def page_with_tempfile
  require 'tempfile'
  file = Tempfile.new('cetus')
  begin
    yield file
    file.flush
    system "$PAGER #{file.path}"
    setup_terminal
  rescue StandardError
    file.close
    file.unlink
  end
end
parse_ls_colors() click to toggle source
# File bin/cetus, line 1128
def parse_ls_colors
  colorvar = ENV['LS_COLORS']
  if colorvar.nil?
    @ls_colors_found = nil
    return
  end
  @ls_colors_found = true
  ls = colorvar.split(':')
  ls.each do |e|
    patt, colr = e.split '='
    colr = "\e[" + colr + 'm'
    if e.start_with? '*.'
      # extension, avoid '*' and use the rest as key
      @ls_color[patt[1..-1]] = colr
      # @log.debug "COLOR: Writing extension (#{patt})."
    elsif e[0] == '*'
      # file pattern, this would be a glob pattern not regex
      # only for files not directories
      patt = patt.gsub('.', '\.')
      patt = patt.sub('+', '\\\+') # if i put a plus it does not go at all
      patt = patt.gsub('-', '\-')
      patt = patt.tr('?', '.')
      patt = patt.gsub('*', '.*')
      patt = "^#{patt}" if patt[0] != '.'
      patt = "#{patt}$" if patt[-1] != '*'
      @ls_pattern[patt] = colr
      # @log.debug "COLOR: Writing file (#{patt})."
    elsif patt.length == 2
      # file type, needs to be mapped to what ruby will return
      # file, directory di, characterSpecial cd, blockSpecial bd, fifo pi, link ln, socket so, or unknown
      # di = directory
      # fi = file
      # ln = symbolic link
      # pi = fifo file
      # so = socket file
      # bd = block (buffered) special file
      # cd = character (unbuffered) special file
      # or = symbolic link pointing to a non-existent file (orphan)
      # mi = non-existent file pointed to by a symbolic link (visible when you type ls -l)
      # ex = file which is executable (ie. has 'x' set in permissions).
      case patt
      when 'di'
        @ls_ftype['directory'] = colr
      when 'cd'
        @ls_ftype['characterSpecial'] = colr
      when 'bd'
        @ls_ftype['blockSpecial'] = colr
      when 'pi'
        @ls_ftype['fifo'] = colr
      when 'ln'
        @ls_ftype['link'] = colr
      when 'so'
        @ls_ftype['socket'] = colr
      else
        @ls_ftype[patt] = colr
      end
      # @log.debug "COLOR: ftype #{patt}"
    end
  end
  # @log.debug "LSP: #{@ls_pattern.values}"
end
partial_reset_terminal() click to toggle source

Call before shelling to editor pager and when exiting

# File bin/cetus, line 200
def partial_reset_terminal
  # Reset the terminal to a useable state (undo all changes).
  # '\e[?7h':  Re-enable line wrapping.
  # '\e[?25h': Unhide the cursor.
  # '\e[2J':   Clear the terminal.
  # '\e[;r':   Set the scroll region to its default value.
  #            Also sets cursor to (0,0).
  # '\e[?1049l: Restore main screen buffer.
  print "\e[?7h\e[?25h\e[;r\e[?1049l"

  # Show user input.
  system 'stty echo'
end
pause(text=' Press a key.') click to toggle source
# File bin/cetus, line 2361
def pause text=' Press a key.'
  last_line
  print text
  get_char
end
pbold(text) click to toggle source
# File bin/cetus, line 2349
def pbold text
  puts "#{BOLD}#{text}#{BOLD_OFF}"
end
perror(text) click to toggle source

This is supposed to print on the status line but prints on next line.FIXME 2019-03-24 - 00:08

# File bin/cetus, line 2355
def perror text
  clear_last_line
  puts "\r#{RED}#{text}. Press a key.#{CLEAR}"
  get_char
end
place_cursor(color=CURSOR_COLOR) click to toggle source

place cursor correctly, we will use this to highlight current row XXX i am not sure how to highlight the bg without rewriting the text. I can get the filename but it has been truncated. I can get the earlier filename but the color has to be determined again. FIXME color of original hint lost if @highlight_row_flag. we can even get the filename, but it has been formatted and could be a long listing. color can be CURSOR_COLOR or CLEAR if CLEAR then we use original colors from get_formatted_filename. Otherwise we need to use CURSOR_COLOR in place of current color

# File bin/cetus, line 726
def place_cursor color=CURSOR_COLOR
  # empty directory
  if @vps == 0
    tput_cup 0, 0
    return
  end

  c = @cursor - @sta

  # NOTE: cursor can be higher that viewport !!!
  # we need to move it to next page TODO 2019-05-21 -
  if c > @vps
    c = 0
  end

  wid = get_width @vps, @grows
  if c < @grows
    rows = c
    cols = 0
  else
    rows = c % @grows
    cols = (c / @grows) * wid
  end

  tput_cup rows, cols
  return unless @highlight_row_flag

  color = nil if color == CLEAR # let it determine its own color
  f = get_formatted_filename(c, wid)

  f = color + f + CLEAR if color

  # f = color + f if color # NOTE: this was causing 2 issues: color bleed onto
  # status line and top line. And the highcolor of some rows would not go away.
  # If REVERSE_OFF is used, then REVERSE_OFF must also be used
  #
  print f
  tput_cup rows, cols # put it back at start
end
pop_dir() click to toggle source

part copied and changed from change_dir since we don't dir going back on top or we'll be stuck in a cycle

# File bin/cetus, line 2049
def pop_dir
  # the first time we pop, we need to put the current on stack
  @visited_dirs.push Dir.pwd unless @visited_dirs.index(Dir.pwd)
  ## XXX make sure thre is something to pop
  d = @visited_dirs.delete_at 0
  ## XXX make sure the dir exists, cuold have been deleted. can be an error or crash otherwise
  @visited_dirs.push d
  Dir.chdir d
  @current_dir = Dir.pwd # 2019-04-24 - earlier was in post_cd but too late
  post_cd
  rescan_required
end
pos() click to toggle source

return cursor position

# File bin/cetus, line 3086
def pos
  @cursor
end
post_cd() click to toggle source

after changing directory

# File bin/cetus, line 2063
def post_cd
  @title = @patt = @message = nil
  @sta = @cursor = @stact = 0
  @visual_block_start = nil
  screen_settings
  calculate_bookmarks_for_dir

  # goto last position cursor was in this dir
  revert_dir_pos
end
prev_page() click to toggle source
# File bin/cetus, line 1553
def prev_page
  @sta -= @pagesize
  @cursor -= @pagesize
  @old_cursor = nil
  # FIXME: check cursor sanity and if not changed then no redraw
  redraw_required
end
print_debug_info(cf=current_file) click to toggle source
print_filename_status_line(cf=current_file) click to toggle source
print_help() click to toggle source
print_on_right(text) click to toggle source

print right aligned XXX does not clear are, if earlier text was longer then that remains. TODO: 2019-04-10 - this should update a variable, and status_line should clear and reprint mode, message, patt and right text

print_title() click to toggle source

——————- print_title —————— #

push_used_dirs(d=Dir.pwd) click to toggle source

create a list of dirs in which some action has happened, for saving

# File bin/cetus, line 2341
def push_used_dirs d=Dir.pwd
  # @used_dirs.index(d) || @used_dirs.push(d)
  return if @used_dirs[0] == d

  @used_dirs.delete(d) if @used_dirs.index(d)
  @used_dirs.insert(0, d)
end
quit_command() click to toggle source
# File bin/cetus, line 2254
def quit_command
  # if we are in some mode, like search results then 'q' should come out.
  if @mode
    escape
    return
  end

  if @modified
    last_line
    puts 'Press y to save bookmarks before quitting ' if @modified
    print 'Press n to quit without saving'
    key = get_char
  else
    @quitting = true
  end
  @quitting = true if key == 'n'
  @quitting = @writing = true if key == 'y'
end
read_directory() click to toggle source
# File bin/cetus, line 466
def read_directory
  @now = Time.now
  rescan_required false

  @filterstr ||= 'M' # XXX can we remove from here
  @current_dir ||= Dir.pwd
  list_files

  group_directories_first
  return unless @enhanced_mode

  enhance_file_list
  @files = @files.uniq
end
readable_file_size(size, precision) click to toggle source

Return the file size with a readable style. NOTE format is a kernel method.

# File bin/cetus, line 889
def readable_file_size size, precision
  if size < KILO_SIZE then format('%d B', size)
  elsif size < MEGA_SIZE then format("%.#{precision}f K", (size / KILO_SIZE))
  elsif size < GIGA_SIZE then format("%.#{precision}f M", (size / MEGA_SIZE))
  else format("%.#{precision}f G", (size / GIGA_SIZE))
  end
end
readline(prompt='>') click to toggle source

wrap readline so C-c can be ignored, but blank is taken as default

# File bin/cetus, line 234
def readline prompt='>'
  clear_last_line
  print "\r"
  # do we need to clear till end of line, see ask_regex commented
  # unhide cursor
  print "\e[?25h"
  system 'stty echo'
  begin
    if prompt.length > 40
      puts prompt
      prompt = '>'
    end
    target = Readline.readline(prompt, true)
  rescue Interrupt
    return nil
  ensure
    # hide cursor
    # NO LONGER HIDING cursor 2019-03-29 -
    # print "\e[?25l"
    system 'stty -echo'
  end
  target.chomp
end
recent_files() click to toggle source

lists recent files in current dir In some cases it shows mostly .git files, we need to prune those

# File bin/cetus, line 2326
def recent_files
  # print -rl -- **/*(Dom[1,10])
  @title = 'Recent files'
  # zsh D DOT_GLOB, show dot files
  # zsh om order on modification time
  @files = `zsh -c 'print -rl -- **/*(Dom[1,15])'`.split("\n").reject { |f| f[0] == '.' }
end
redraw(flag=false) click to toggle source
# File bin/cetus, line 690
def redraw flag=false
  read_directory if flag

  draw_directory
end
redraw_required(flag=true) click to toggle source
# File bin/cetus, line 772
def redraw_required flag=true
  @redraw_required = flag
end
reduce(pattern=nil) click to toggle source
# File bin/cetus, line 2013
def reduce pattern=nil
  unless pattern
    pattern = readline 'Enter a pattern to reduce current list: '
  end
  @title = "Filter: pattern #{pattern}"
  @bm = "(%r)"
  @files = @files.select { |f| f.match?(pattern) }
end
refresh() click to toggle source
refresh listing after some change like option change, or toggle

Should we check selected_files array also for deleted/renamed files

# File bin/cetus, line 1397
def refresh
  @patt = nil
  @title = nil
  rescan_required
end
remove_bookmark() click to toggle source
# File bin/cetus, line 2144
def remove_bookmark
  bmlist = @bookmarks.keys.sort.join('')
  clear_last_line
  print "Enter bookmark to delete: #{bmlist}:"
  key = get_char
  if bmlist.index(key)
    @modified = true
    @bookmarks.delete key
    message "Deleted #{key} "
  else
    perror 'Bookmark does not exist'
  end
end
remove_from_list() click to toggle source

Editing of the User Dir List. remove current entry from used dirs list, since we may not want some entries being there Need to call this from somewhere. Test it out again. Usage. Invoke `1` or `2` and select some files and then call remove

# File bin/cetus, line 3500
def remove_from_list

  # XXX key to invoke this is difficult. make it easier
  selfiles = current_or_selected_files
  sz = selfiles.size
  print "Remove #{sz} files from used list (y)?: "
  key = get_char
  return if key != 'y'

  # arr = @selected_files.map { |path| File.expand_path(path) }
  # @log.debug "BEFORE: Selected files are: #{@selected_files}"
  arr = selfiles.map { |path|
    if path[0] != '/'
      expand_path(path)
    else
      path
    end
  }
  if File.directory? arr.first
    @used_dirs -= arr
    select_from_used_dirs
  else
    @visited_files -= arr
    select_from_visited_files
  end
  unselect_all
  @modified = true
  # redraw_required
end
remove_from_selection(file) click to toggle source
# File bin/cetus, line 3176
def remove_from_selection file
  ff = file
  case file
  when String
    ff = [file]
  end
  @current_dir ||= Dir.pwd
  ff.each do |f|
    # full = File.join(@current_dir, f)
    full = expand_path(f)
    @selected_files.delete full
  end
end
remove_spaces_from_name() click to toggle source

remove spaces and brackets from file name replace space with underscore, removes square and round brackets

# File bin/cetus, line 2618
def remove_spaces_from_name
  execute_script 'remove_brackets'
end
rename_file() click to toggle source
# File bin/cetus, line 2582
def rename_file
  rbfiles = current_or_selected_files
  return if rbfiles.nil? || rbfiles.empty?

  count = rbfiles.count
  first = rbfiles.first
  text = count == 1 ? File.basename(first) : "#{count} files"

  if count > 1
    perror 'Select only one file for rename.'
    return
  end

  Readline::HISTORY.push File.basename(first)
  target = readline "Rename #{text[0..40]} to : "
  return if target == '' || target == '.' || target == '..'

  if File.exist? target
    perror "Target (#{target}) exists."
    return
  end

  begin
    FileUtils.mv first, target
    message "Renamed to #{target}."
    @log.info "Renamed #{first} to #{target}."
  rescue StandardError => exc
    @log.warn exc.to_s
    @log.warn "RENAME: files: #{first}, target:#{target}"
    pause exc.to_s
  end
  refresh
end
rescan?() click to toggle source

should we do a read of the dir

# File bin/cetus, line 681
def rescan?
  @rescan_required
end
rescan_required(flag=true) click to toggle source
# File bin/cetus, line 685
def rescan_required flag=true
  @rescan_required = flag
  redraw_required if flag
end
reset_terminal() click to toggle source

copied from fff Call before shelling to editor pager and when exiting

# File bin/cetus, line 186
def reset_terminal
  # Reset the terminal to a useable state (undo all changes).
  # '\e[?7h':  Re-enable line wrapping.
  # '\e[?25h': Unhide the cursor.
  # '\e[2J':   Clear the terminal.
  # '\e[;r':   Set the scroll region to its default value.
  #            Also sets cursor to (0,0).
  # '\e[?1049l: Restore main screen buffer.
  print "\e[?7h\e[?25h\e[2J\e[;r\e[?1049l"

  # Show user input.
  system 'stty echo'
end
resolve_binding(key) click to toggle source
# File bin/cetus, line 795
def resolve_binding key
  # fetch binding for key
  x = @bindings[key]

  # remove comment string so only binding is left
  x, = x.split(':') if x

  # split into binding and args
  x = x.split if x
  if x
    redraw_required # trying here so default 2019-04-26 -
    binding = x.shift
    args = x
    send(binding, *args) if binding
  else
    # perror "No binding for #{key}"
    @log.debug "No binding for #{key}"
  end
end
resolve_key(key) click to toggle source

return value determines if screen is redrawn or not.

# File bin/cetus, line 777
def resolve_key key
  clear_message

  # hint mode, pick file based on shortcut
  return select_hint(@viewport, key) if key.match?(/^[a-pr-zZ]$/)

  if '0123456789'.include?(key)
    resolve_numeric_key(key)
  elsif key == 'BACKSPACE'
    @patt = @patt[0..-2] if @patt && !@patt.empty?
    @message = @patt = nil if @patt == ''
    @title = nil unless @patt
  else
    resolve_binding key
  end
  true
end
resolve_numeric_key(key) click to toggle source

numbers represent quick bookmarks if bookmark exists, go to else create it.

# File bin/cetus, line 817
def resolve_numeric_key key
  d = @bookmarks[key]
  if d
    change_dir d
    return
  end

  set_bookmark key
  message "Created bookmark #{key}."
end
return_next_match(binding, *args) click to toggle source

generic method to take cursor to next position for a given condition

# File bin/cetus, line 3232
def return_next_match binding, *args
  first = nil
  ix = 0
  @view.each_with_index do |elem, ii|
    next unless binding.call(elem, *args)

    first ||= ii
    if ii > @cursor
      ix = ii
      break
    end
  end
  return first if ix == 0

  ix
end
revert_dir_pos() click to toggle source

revert to the position we were at in this directory

# File bin/cetus, line 3310
def revert_dir_pos
  @sta = 0
  @cursor = 0
  a = @dir_position[Dir.pwd]
  if a
    @sta = a.first
    @cursor = a[1]
    raise "sta is nil for #{Dir.pwd} : #{@dir_position[Dir.pwd]}" unless @sta
    raise 'cursor is nil' unless @cursor
  end
end
rotate_value(symb) click to toggle source

rotates the value of an option that has multiple values

# File bin/cetus, line 1850
def rotate_value symb
  hash = @options[symb]
  curr = hash[:current]
  index = hash[:values].index(curr) || 0
  index += 1
  index = 0 if index >= hash[:values].count
  x = hash[:current] = hash[:values][index]
  var = hash[:var]
  instance_variable_set "@#{var}", x if var
  message "#{symb} is set to #{x}. "
end
run() click to toggle source

main loop which calls all other programs

# File bin/cetus, line 3906
def run
  Signal.trap('EXIT') do
    reset_terminal
    exit
  end
  Signal.trap('WINCH') do
    screen_settings
    redraw
    place_cursor
  end

  setup_terminal
  config_read
  parse_ls_colors
  set_bookmark '0'

  redraw true
  place_cursor

  # do we need this, have they changed after redraw XXX
  @patt = nil
  @sta = 0

  # forever loop that prints dir and takes a key
  loop do
    key = get_char

    unless resolve_key key # key did not map to file name, so don't redraw
      place_cursor
      next
    end

    break if @quitting

    next if only_cursor_moved?

    next unless @redraw_required # no change, or ignored key

    redraw rescan?
    place_cursor

  end
  write_curdir
  puts 'bye'
  config_write if @writing
  @log&.close
end
run_command(f) click to toggle source

run system command on given file/s

Accepts external command from user
After putting readline in place of gets, pressing a C-c has a delayed effect.
It goes into exception block after executing other commands and still
does not do the return !
# File bin/cetus, line 1320
def run_command f
  files = Shellwords.join(f)
  count = f.count
  text = if count > 1
           "#{count} files"
         else
           files[0..40]
         end
  begin
    command = readline "Run a command on #{text}: "
    return if command.empty?

    # command2 = gets().chomp
    command2 = readline 'Second part of command: '
    pause "#{command} #{files} #{command2}"

    reset_terminal
    system "#{command} #{files} #{command2}"
    setup_terminal
  rescue StandardError => ex
    perror "Canceled or failed command, (#{ex}) press a key."
    @log.warn "RUNCOMMAND: #{ex}"
    return
  end

  refresh
  push_used_dirs Dir.pwd
  # should we clear selection also ?
end
run_on_current(command) click to toggle source

run given command on current file

# File bin/cetus, line 1298
def run_on_current command
  f = current_file
  return unless f

  f = expand_path(f)
  return unless File.readable?(f)

  f = Shellwords.escape(f)
  clear_screen
  reset_terminal
  comm = "#{command} #{f}"
  system(comm.to_s)
  push_used_dirs
  setup_terminal
  redraw_required
end
save_dir_pos() click to toggle source

save offset in directory so we can revert to it when we return

# File bin/cetus, line 3302
def save_dir_pos
  # the next line meant that it would not save first directory.
  # return if @sta == 0 && @cursor == 0

  @dir_position[Dir.pwd] = [@sta, @cursor]
end
screen_settings() click to toggle source
check screen size and accordingly adjust some variables

NOTE: tput is ncurses dependent, so use stty

# File bin/cetus, line 2754
def screen_settings
  @glines, @gcols = `stty size`.split.map(&:to_i)
  # @glines = `tput lines`.to_i
  # @gcols = `tput cols`.to_i
  @grows = @glines - 3
  # @pagesize = 60
  # @gviscols = 3
  @pagesize = @grows * @gviscols
end
scripts(binding=nil) click to toggle source

——————- scripts —————— # prompt for scripts to execute, giving file name under cursor

# File bin/cetus, line 3362
def scripts binding=nil
  # some scripts may work with the selected_files and not want to be called
  #  with filenames.
  write_selected_files

  unless binding
    title = 'Select a script'
    # script_path = '~/.config/cetus/scripts'
    script_path = File.join(CONFIG_PATH, 'cetus', 'scripts')
    binding = `find #{script_path} -type f | fzf --prompt="#{title} :"`.chomp
    return if binding.nil? || binding == ''
  end
  unless File.exist? binding
    @log.warn "Unable to find #{binding}"
    return
  end

  # TODO: check if binding is a file and executable
  # xargs only seems to take the first file
  # cf = current_or_selected_files.join('\0')
  # cf = Shellwords.join(current_or_selected_files)
  # This was getting called repeatedly even if script used selected_files
  # current_or_selected_files.each do |file|
  # system %( #{binding} "#{file}" )
  # end

  # 2019-04-08 - to avoid confusion, we pass name of file under cursor
  # script may ignore this and use selected_files

  # reset clears the screen, we don't want that. just unhide cursor and echo keys TODO
  partial_reset_terminal
  @log.info "Calling #{binding}."
  system %( #{binding} "#{current_file}" )

  # system %(echo "#{cf}" | xargs #{binding})
  pause
  setup_terminal
  visual_block_clear
  refresh
end
search_as_you_type() click to toggle source

v2 if search_as_you_type which takes keyboard control This is fairly straightforward and took only a few minutes to get done. return value is not used.

# File bin/cetus, line 831
def search_as_you_type

  @patt = '' + '' # rubocop suggestion to get unfrozen string
  @title = 'Search Results (ENTER to return, ESC to cancel)'
  clear_last_line
  print "\r/"
  loop do
    key = get_char
    if key == 'ENTER'
      @title = 'Search Results (ESCAPE to return)'
      return true
    elsif key == 'ESCAPE'
      @mode = @title = @patt = nil
      status_line
      return false
    elsif key == 'BACKSPACE'
      @patt = @patt[0..-2]
      @message = nil # remove pesky ESCAPE message
    elsif key.match?(/^[a-zA-Z0-9\. _]$/)
      @patt += key if @patt
    else
      resolve_key key
      @mode = @title = nil
      # if directory changes, then patt is nilled causing error above
      return true
    end
    # XXX is rescan required ?
    draw_directory
    place_cursor
  end
end
select_all() click to toggle source

select all entries (files and directories)

# File bin/cetus, line 1425
def select_all
  dir = Dir.pwd
  # check this out with visited_files TODO FIXME
  @selected_files = @view.map { |file| File.join(dir, file) }
  message "#{@selected_files.count} files selected."
end
select_current() click to toggle source
# File bin/cetus, line 2334
def select_current
  ## vp is local there, so i can do @vp[0]
  # open_file @view[@sta] if @view[@sta]
  open_file @view[@cursor] if @view[@cursor]
end
select_from_used_dirs() click to toggle source
# File bin/cetus, line 2029
def select_from_used_dirs
  @title = 'Used Directories'
  home = File.expand_path '~'
  @files = @used_dirs.uniq.map { |path| path.sub(home.to_s, '~') }
  @bm =  @bindings.key('select_from_used_dirs')
  @bm = ' (' + @bm + ')' if @bm
  # redraw_required
end
select_from_visited_files() click to toggle source
# File bin/cetus, line 2038
def select_from_visited_files
  @title = 'Visited Files'
  home = File.expand_path '~'
  @files = @visited_files.uniq.map { |path| path.sub(home.to_s, '~') }
  @bm = @bindings.key('select_from_visited_files')
  @bm = ' (' + @bm + ')' if @bm
  # redraw_required
end
select_hint(view, key) click to toggle source

select file based on key pressed

# File bin/cetus, line 1191
def select_hint view, key
  ix = get_index(key, view.size)
  return nil unless ix

  f = view[ix]
  return nil unless f
  return nil if f == SEPARATOR

  @cursor = @sta + ix

  if @mode == 'SEL'
    toggle_select f
  elsif @mode == 'COM'
    # not being called any longer I think
    run_command f
  else
    open_file f
  end
  true
end
selected?(fullname) click to toggle source

is given file in selected array 2019-04-24 - now takes fullname so path addition does not keep happening in

a loop in draw directory.
# File bin/cetus, line 3156
def selected? fullname
  return @selected_files.index fullname
end
selection_menu() click to toggle source

copy and move here ?

# File bin/cetus, line 1724
def selection_menu
  h = {
    a: :select_all,
    u: :unselect_all,
    s: :toggle_select,
   '*' => 'toggle_multiple_selection',
   'x' => 'toggle_visual_mode',
   'm' => 'toggle_selection_mode',
    v: :view_selected_files
  }
  menu 'Selection Menu', h
end
set_bookmark(key, dir=Dir.pwd) click to toggle source
# File bin/cetus, line 863
def set_bookmark key, dir=Dir.pwd
  @bookmarks[key] = dir
  calculate_numeric_hotkeys
end
set_move_target(cf=current_file) click to toggle source

set the default target for further moves We need to also be able to set it to current dir in which user is. TODO

# File bin/cetus, line 2939
def set_move_target cf=current_file
  ff = expand_path(cf)
  return unless File.directory? ff

  @move_target = ff
  message "Move target set to #{cf}."
end
setup_terminal() click to toggle source

copied from fff call AFTER shelling to most or vim

# File bin/cetus, line 216
def setup_terminal
  # Setup the terminal for the TUI.
  # '\e[?1049h': Use alternative screen buffer. smcup
  # '\e[?7l':    Disable line wrapping.
  # '\e[?25l':   Hide the cursor.
  # '\e[2J':     Clear the screen.
  # '\e[1;Nr':   Limit scrolling to scrolling area.
  #              Also sets cursor to (0,0).
  # printf("\e[?1049h\e[?7l\e[?25l\e[2J\e[1;%sr", @glines)
  # 2019-03-29 - XXX temporarily not hiding cursor to see if we can place it.
  printf("\e[?1049h\e[?7l\e[?25h\e[2J\e[1;%sr", @glines)
  # earlier glines was grows

  # Hide echoing of user input
  system 'stty -echo'
end
status_line() click to toggle source

TODO: clean this up and simplify it NOTE: earlier this was called on every key (up-arow down arrow, now only called when page changes, so we only put directory name) NOTE: called only from draw_directory.

# File bin/cetus, line 623
def status_line
  # prompt
  v_mm = @mode ? "[#{@mode}] " : ''
  cf = current_file
  @message = " | No matches. Press ESCAPE " if @patt && !cf

  clear_last_line

  # Print the filename at the right side of the status line
  # sometimes due to search, there is no file
  if cf
    if @debug_flag
      print_debug_info cf
    else
      # print_on_right "#{Dir.pwd}"
      print_filename_status_line if @filename_status_line
    end
  end
  # move to beginning of line, reset text mode after printing
  # patt and message are together, no gap, why not ? 2019-04-08 -
  if @patt && @patt != ''
    patt = "[/#{@patt}" + ']' # to get unfrozen string
    patt[-1] = '/i]' if @ignore_case
  end
  # bring cursor to start of line
  # add background color
  # print mode
  # print search pattern if any
  # print message if any
  # print "\r#{v_mm}#{patt}#{@message}\e[m"
  print "\r\e[33;4#{@status_color}m#{v_mm}#{patt}#{@message}\e[m"
end
subcommand() click to toggle source

allow user to exit using :q :wq :x Was this supposed to be augmented, or just remain limited like this We should be able to do everything in the menus from here. TODO

# File bin/cetus, line 2161
def subcommand
  # clear_last_line
  # pbold 'Subcommand:'
  begin
    prompt = %(
    [q]  quit                              [w]   config write              [d] delete
    [x]  update config + quit              [r]   config read               [r] rename
    [wq] write config + quit               [e]   edit file under cursor    [m] move
    [P]  copy PWD to clipboard             [o]   open file under cursor    [c] copy
    [p]  copy filename to clipboard        [h]   help                      [t] toggle flags
    )
    # command = readline 'Enter command: q x wq P p w e r h :'
    command = readline prompt
    return if command == ''
  rescue StandardError
    return
  end
  if command == 'q'
    quit_command
  elsif command == 'wq'
    @quitting = true
    @writing = true
  elsif command == 'w'
    config_write
  elsif command == 'r'
    config_read
  elsif command == 'x'
    @quitting = true
    @writing = true if @modified
  elsif command == 'e'
    edit_current
  elsif command == 'o'
    open_current
  elsif command == 'd'
    delete_file
  elsif command == 'c'
    copy_file
  elsif command == 'r'
    rename_file
  elsif command == 'm'
    move_file
  elsif command == 'h' || command == 'help' || command == '?'
    print_help
  elsif command == 'P'
    # or should be put current file in clip ?
    system 'echo $PWD | pbcopy'
    message 'Stored PWD in clipboard (using pbcopy)'
  elsif command == 'p'
    system "echo #{current_file} | pbcopy"
    message "Stored #{current_file} in clipboard (using pbcopy)"
  elsif command == 't' || command == 'toggle'
    toggle_menu
  else
    perror "Don't know about command #{command}. Try :h or :help"
  end
end
sys_stat(func, file) click to toggle source

Deal with deadlinks.

# File bin/cetus, line 538
def sys_stat func, file
  return unless File.symlink? file

  # lstat does not respond to path and extname
  return File.send(func, file) unless File.lstat(file).respond_to? func

  return File.lstat(file).send(func)
end
toggle_columns() click to toggle source
# File bin/cetus, line 1793
def toggle_columns
  @gviscols = if @gviscols == 1
                3
              else
                1
              end
  x = @grows * @gviscols
  @pagesize = @pagesize == x ? @grows : x
  message "Visible columns now set to #{@gviscols}"
  rescan_required

end
toggle_editor_mode() click to toggle source
# File bin/cetus, line 1806
def toggle_editor_mode
  toggle_value :editor_mode
  @default_command = if @editor_mode
                       ENV['EDITOR'] # earlier nil # 2019-03-10 -
                       # it was nil so we could set a default command
                     else
                       ENV['MANPAGER'] || ENV['PAGER']
                     end
  message "Default command is #{@default_command}"
end
toggle_long_listing() click to toggle source
# File bin/cetus, line 1817
def toggle_long_listing
  toggle_value :long_listing
  @long_listing = @toggles[:long_listing]
  if @long_listing
    @saved_gviscols = @gviscols
    @gviscols = 1
    @pagesize = @grows
  else
    @gviscols = @saved_gviscols || 3
    x = @grows * @gviscols
    @pagesize = @pagesize == x ? @grows : x
  end
  if @stact > 0
    @sta = @stact
    @stact = 0 # in case user was panned 2019-03-20 -
  end
  message "Long listing is #{@long_listing}, date_func is #{@date_func}. visible columns is #{@gviscols}."
  # rescan_required
end
toggle_menu() click to toggle source
# File bin/cetus, line 2234
def toggle_menu
  binding = fzfmenu 'Toggles', @toggles.merge(@options)
  return if binding.nil? || binding == ''

  menu_text = binding.to_sym
  return if respond_to?(menu_text, true) # already handled

  # for visual and selection mode
  # check if respond_to? symbol or toggle + symbol then call and return.
  symb = "toggle_#{menu_text}".to_sym
  @log.debug "trying #{symb}."
  if respond_to?(symb, true)
    @log.debug "calling #{symb}."
    send(symb)
    return
  end

  cset menu_text
end
toggle_multiple_selection() click to toggle source

allow single or multiple selection with C-s key

# File bin/cetus, line 1234
def toggle_multiple_selection
  toggle_value :multiple_selection
end
toggle_select(f=current_file) click to toggle source

toggle selection state of file

# File bin/cetus, line 1213
def toggle_select f=current_file
  # if selected? File.join(@current_dir, current_file)
  if selected? expand_path(current_file)
    remove_from_selection f
  else
    @selected_files.clear unless @multiple_selection
    add_to_selection f
  end
  message "#{@selected_files.count} files selected.   "

  # 2019-04-24 - trying to avoid redrawing entire screen.
  # If multiple_selection then current selection is either added or removed,
  #  nothing else changes, so we redraw only if not multi. Also place cursor
  #  i.e. redraw current row if mutliple selection
  if @multiple_selection
    redraw_required false
    place_cursor
  end
end
toggle_selection_mode() click to toggle source

toggle mode to selection or not In selection, pressed hotkey selects a file without opening, one can keep selecting (or deselecting).

# File bin/cetus, line 1471
def toggle_selection_mode
  if @mode == 'SEL'
    unselect_all
    @mode = nil
    message 'Selection mode is single.   '
  else
    @mode = 'SEL'
    message 'Typing a hint selects the file. Typing again will clear   .  '
  end
end
toggle_value(flag) click to toggle source

—————– flag related functions ———————————–# toggles the value of a toggle flag, also setting the variable if defined WARN: be careful of variable being set directly. Replace such vars one by one.

# File bin/cetus, line 1840
def toggle_value flag
  x = @toggles[flag] = !@toggles[flag]
  if instance_variable_defined? "@#{flag}"
    instance_variable_set "@#{flag}", x
    @log.debug "instance_variable_set #{flag}, #{x}"
  end
  message "#{flag} is set to #{x}"
end
toggle_visual_mode() click to toggle source

————- visual mode methods ——————————–#

# File bin/cetus, line 3191
def toggle_visual_mode
  @mode = nil
  # @visual_mode = !@visual_mode
  toggle_value :visual_mode
  return unless @visual_mode

  @mode = 'VIS'
  @visual_block_start = @cursor
  add_to_selection current_file
end
tput_cup(row, col) click to toggle source

place cursor on row and col taking first two rows into account

# File bin/cetus, line 767
def tput_cup row, col
  # we add 3: 2 is for the help and directory line. 1 is since tput is 1 based
  print "\e[#{row + 3};#{col + 1}H"
end
tree() click to toggle source

Get a full recursive listing of what's in this dir - useful for small projects with more structure than files.

# File bin/cetus, line 2316
def tree
  # Caution: use only for small projects, don't use in root.
  @title = 'Full Tree'
  # @files = `zsh -c 'print -rl -- **/*(#{@sorto}#{@hidden}M)'`.split("\n")
  @files = Dir['**/*']
  message "#{@files.size} files."
end
truncate_formatted_filename(f, unformatted_len, wid) click to toggle source

shorten the filename to wid unformatted_len is the length without ANSI formatting wid is the exact width every line should restrict itself to. f is filename with hint and space and possible ANSI codes. WARN: check for hint getting swallowed in case of 5 columns

# File bin/cetus, line 950
def truncate_formatted_filename f, unformatted_len, wid
  excess = unformatted_len - wid

  f = case @truncate_from

      when :right
        # FIXME: 2019-04-23 - do we need the control code at end ??
        f[0..wid - 3] + '$ '

      when :center

        # from central point calculate how much to remove in both directions
        center = unformatted_len / 2
        excess_half = excess / 2
        point = center + excess_half
        point1 = point - excess

        # remove text between point1 and point
        f[0..(point1 - 1)] + '$' + f[point + 2..-1] + ' '

      when :left

        # NOTE: we cannot remove the hint
        # for single hints we need to add extra space
        # there could be escape codes of varying length
        sindex = f.index(' ') || f.index('+')
        # 4 = 2 for literals, 2 to get ahead of sindex+1
        # FIXME crashing here, maybe there was a plus sign in place of space
        f[0..sindex + 0] + '<' + f[sindex + 3 + excess..-1] + ' '
      end
  return f
end
unselect_all() click to toggle source

unselect all files

# File bin/cetus, line 1419
def unselect_all
  @selected_files = []
  @toggles[:visual_mode] = @visual_mode = false
end
vidir() click to toggle source
# File bin/cetus, line 3051
def vidir
  system 'vidir'
  refresh
  setup_terminal
end
view_bookmarks() click to toggle source
# File bin/cetus, line 1677
def view_bookmarks
  clear_last_line
  puts 'Bookmarks: '
  @bookmarks.each_pair { |k, v| puts "#{k.ljust(7)}  =>  #{v}" }
  puts
  print 'Enter bookmark to goto: '
  key = get_char
  goto_bookmark(key)
end
view_menu() click to toggle source

if menu options returns a hash, then treat as another level menu and process

rather than having to create more of these. but these can be called directly too.
# File bin/cetus, line 1709
def view_menu
  h = {
    f: :select_from_visited_files,
    d: :select_from_used_dirs,
    b: :view_bookmarks,
    s: :list_selected_files,
    c: :child_dirs,
    r: :recent_files,
    t: :tree,
    e: :dirtree
  }
  menu 'View Menu', h
end
view_selected_files() click to toggle source

——————- view_selected_files —————— #

# File bin/cetus, line 3440
def view_selected_files
  fname = write_selected_files

  unless fname
    message 'No file selected.    '
    return
  end

  system "$PAGER #{fname}"
  setup_terminal
end
views() click to toggle source
# File bin/cetus, line 2273
def views
  views = %w[/ om oa Om OL oL On on]
  viewlabels = %w[Dirs Newest Accessed Oldest Largest Smallest Reverse Name]
  @sorto = views[@viewctr]
  @title = viewlabels[@viewctr]
  @viewctr += 1
  @viewctr = 0 if @viewctr > views.size

  @files = `zsh -c 'print -rl -- *(#{@sorto}#{@hidden}M)'`.split("\n")
  redraw_required
end
visited?(fullname) click to toggle source

is given file in selected array

# File bin/cetus, line 3147
def visited? fullname
  return @visited_files.index fullname
end
visual_block_clear() click to toggle source

Called from Escape key and scripts and file actions. Clears selection.

# File bin/cetus, line 3203
def visual_block_clear
  if @visual_block_start
    star = [@visual_block_start, @cursor].min
    fin = [@visual_block_start, @cursor].max
    remove_from_selection @view[star..fin]
  end
  @visual_block_start = nil
  @toggles[:visual_mode] = @visual_mode = false
  @mode = nil if @mode == 'VIS'
  # is this the right place to put this ??? 2019-04-16 -
  clean_selected_files
end
writeYML(obj, filename) click to toggle source
# File bin/cetus, line 2122
def writeYML obj, filename
  File.open(filename, 'w') { |f| f.write obj.to_yaml }
  warn "Written to file #{filename}" if @opt_debug
end
write_curdir() click to toggle source

write current dir to a file so we can ccd to it when exiting

# File bin/cetus, line 873
def write_curdir
  f = File.expand_path('~/.fff_d')
  s = Dir.pwd
  File.open(f, 'w') do |f2|
    f2.puts s
  end
  # puts "Written #{s} to #{f}"
end
write_selected_files() click to toggle source

write selected files to a file and return path if no selected files then blank out the file, or else script could use old selection again.

# File bin/cetus, line 3464
def write_selected_files
  require 'pathname'
  # fname = File.join(File.dirname(CONFIG_FILE), 'selected_files')
  # 2019-04-10 - changed to ~/tmp otherwise confusion about location
  fname = File.join('~/tmp/', 'selected_files')
  fname = expand_path(fname)

  # remove file if no selection
  if @selected_files.empty?
    File.unlink(fname) if File.exist?(fname)
    return nil
  end

  # TODO : what if user does not want full path e,g zip
  # TODO: what if unix commands need escaped files ?
  base = Pathname.new Dir.pwd
  File.open(fname, 'w') do |file|
    @selected_files.each do |row|
      # use relative filename. Otherwise things like zip and tar run into issues
      unless @selected_files_fullpath_flag
        p = Pathname.new(row)
        row = p.relative_path_from(base)
      end
      row = Shellwords.escape(row) if @selected_files_escaped_flag
      file.puts row
    end
  end

  return fname
end
z_interface() click to toggle source

takes directories from the z program, if you use autojump you can modify this accordingly

# File bin/cetus, line 3038
def z_interface
  file = File.expand_path('~/.z')
  return unless File.exist? file

  @title = 'Directories from ~/.z'
  @files = `sort -rn -k2 -t '|' ~/.z | cut -f1 -d '|'`.split("\n")
  home = ENV['HOME']
  # shorten file names
  @files.collect! do |f|
    f.sub(/#{home}/, '~')
  end
end
zip_file() click to toggle source
# File bin/cetus, line 2622
def zip_file
  rbfiles = current_or_selected_files
  return if rbfiles.nil? || rbfiles.empty?

  # count = rbfiles.count
  # first = rbfiles.first
  # text = count == 1 ? File.basename(first) : "#{count} files"

  extn = '.tgz'
  default = "archive#{extn}"
  Readline::HISTORY.push default # check for exist before pushing
  target = readline "Archive name (#{default}): "
  return unless target
  return if target == ''

  if target && target.size < 4
    perror 'Use target of more than 4 characters.'
    return
  end
  target += extn if File.extname(target) == ''

  if File.exist? target
    perror "Target (#{target}) exists."
    return
  end

  # convert absolute paths to relative ones in this zip
  # the problem with zip is that we have full paths
  # so the zip file has full paths and extraction sucks
  require 'pathname'
  base = Pathname.new Dir.pwd
  relfiles = rbfiles.map { |f| p = Pathname.new(f); p.relative_path_from(base) }
  zfiles = Shellwords.join relfiles

  system "tar zcvf #{target} #{zfiles}"
  message "Created #{target} with #{relfiles.count} files."
  setup_terminal
  refresh
end