cetus 0.1.29 → 0.1.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (6) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +16 -0
  3. data/README.md +2 -0
  4. data/bin/cetus +714 -269
  5. data/cetus.gemspec +4 -3
  6. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a6bfd0c8210eacca8888f7c6f22e5682feff8c82f3a90d799849d8bb153fd5e9
4
- data.tar.gz: 3ebbdd0708cd4cc6f75a3a77a2161f443a68d703f2c769ca870ffd37242f839e
3
+ metadata.gz: 97704d648638971cc77d3f7f9b22e631893f7f176013da0acd95babf9dd69db1
4
+ data.tar.gz: fc1759b09b662da0f31a93593d1b9eee8859503e122a84927873830bec342603
5
5
  SHA512:
6
- metadata.gz: f0425f755732c290eaf51aa0903a6785fef35f54d72b83f2fd281280e5c00064706cad9dd2d598419a1495d9625792a0b3e15dcb7e0edb1fc8c5f07d177fcc2a
7
- data.tar.gz: 5d9c5f24e835a522adccfc91156469ff148b66013c49ac1c83fc5d41fd00da212500eb2786765593a0585baf4210c9a75f614914b34e1d2faa9ffca5ac7f2613
6
+ metadata.gz: d9975620b45a6b2a426af50e3c6d6afe48738d69a04d090aaf71090c8e647f1fe15fd17e3780eecd3dd7b4f749904432397f71aaaf66c140d67b2035432c75a6
7
+ data.tar.gz: af1b12d3aa62b1f8e5a828d62bbedee51b222c71b6aab9840f44799322499c838a2ff168ca3f14ba5e09255a49246d6daebc0eba1234fe0562e74c0f7e5629cf
data/.gitignore CHANGED
@@ -16,3 +16,19 @@ tmp
16
16
  .yardoc
17
17
  _yardoc
18
18
  doc/
19
+ Gemfile.lock
20
+ README.NOW
21
+ a test directory/
22
+ archived.txt
23
+ bug
24
+ column.rb
25
+ gemenv.txt
26
+ index.html
27
+ log.txt
28
+ oldlib/
29
+ tada.txt
30
+ todo
31
+ todo.notes
32
+ fix.txt
33
+ deadlink
34
+ livelink
data/README.md CHANGED
@@ -6,6 +6,8 @@ lightning-fast file navigator
6
6
  Tested with ruby 2.5
7
7
 
8
8
  Latest changes:
9
+ 2019-03-26 - Reading up 'LS_COLORS' and coloring filenames. v0.1.30
10
+ 2019-03-24 - Major refactoring and cleanup. v 0.1.29
9
11
  2019-03-04 - q is quit key, not Q
10
12
  - show directories first, then files.
11
13
  - trying not to pollute main screen/terminal with listings (i.e. use alt screen)
data/bin/cetus CHANGED
@@ -6,10 +6,12 @@
6
6
  # Author: rkumar http://github.com/rkumar/cetus/
7
7
  # Date: 2013-02-17 - 17:48
8
8
  # License: GPL
9
- # Last update: 2019-03-24 11:58
9
+ # Last update: 2019-04-06 10:15
10
10
  # --------------------------------------------------------------------------- #
11
11
  # cetus.rb Copyright (C) 2012-2019 rahul kumar
12
12
  # == CHANGELOG
13
+ # 2019-03-24 - adding colors per line, but columnate will have to change
14
+ # since size calc will include color codes. Same for truncate
13
15
  # 2019-02-20 - added smcup and rmcup so alt-screen is used. works a bit
14
16
  # 2019-03-04 - change clear to go to 0,0 and clear down to reduce pollution
15
17
  # 2019-03-04 - changed quit to q (earlier Q)
@@ -17,7 +19,6 @@
17
19
  # 2019-03-10 - changing selected_files to have fullpath
18
20
  # 2019-03-22 - refactoring the code, esp run()
19
21
  # == TODO
20
- # FIXME how to check that redraw not called twice depending on where
21
22
  # a method is called from. a menu or direct from key. e.g help
22
23
  # To dirs add GEMHOME RUBYLIB PYTHONILB or PYTHONPATH,
23
24
  # Make rubygem aware : gemspec or Gemfile the expand lib and bin
@@ -29,21 +30,25 @@ require 'io/wait'
29
30
  require 'shellwords'
30
31
  # https://docs.ruby-lang.org/en/2.6.0/FileUtils.html
31
32
  require 'fileutils'
33
+ require 'logger'
34
+ # @log = Logger.new(File.expand_path '~/tmp/log.txt')
35
+ @log = Logger.new('log.txt')
32
36
 
33
37
  ## INSTALLATION
34
38
  # copy into PATH
35
39
  # alias c=~/bin/cetus.rb
36
40
  # c
37
41
 
38
- VERSION = '0.1.29.0'.freeze
39
- CONFIG_FILE = '~/.config/cetus/conf.yml'.freeze
42
+ VERSION = '0.1.32.0'.freeze
43
+ CONFIG_PATH = ENV['XDG_CONFIG_HOME'] || File.join(ENV['HOME'], '.config')
44
+ CONFIG_FILE = "#{CONFIG_PATH}/cetus/conf.yml".freeze
40
45
 
41
- $bindings = {}
46
+ # $bindings = {}
42
47
  $bindings = {
43
48
  '`' => 'main_menu',
44
49
  '=' => 'toggle_menu',
45
- 'M-y' => 'selection_menu',
46
- 'M-s' => 'sort_menu',
50
+ 'M-s' => 'selection_menu',
51
+ 'M-o' => 'sort_menu',
47
52
  'ENTER' => 'select_current',
48
53
  'C-p' => 'page_current',
49
54
  'C-e' => 'edit_current',
@@ -51,7 +56,7 @@ $bindings = {
51
56
  'C-s' => 'toggle_select',
52
57
  'C-r' => 'reduce',
53
58
  'C-g' => 'debug_vars',
54
- '@' => 'selection_mode_toggle',
59
+ '@' => 'toggle_selection_mode',
55
60
  'M-a' => 'select_all',
56
61
  'M-A' => 'unselect_all',
57
62
  '!' => 'execute',
@@ -193,7 +198,9 @@ def setup_terminal
193
198
  # '\e[2J': Clear the screen.
194
199
  # '\e[1;Nr': Limit scrolling to scrolling area.
195
200
  # Also sets cursor to (0,0).
196
- printf("\e[?1049h\e[?7l\e[?25l\e[2J\e[1;%sr", $glines)
201
+ # printf("\e[?1049h\e[?7l\e[?25l\e[2J\e[1;%sr", $glines)
202
+ # 2019-03-29 - XXX temporarily not hiding cursor to see if we can place it.
203
+ printf("\e[?1049h\e[?7l\e[?25h\e[2J\e[1;%sr", $glines)
197
204
  # earlier glines was grows
198
205
 
199
206
  # Hide echoing of user input
@@ -202,15 +209,20 @@ end
202
209
 
203
210
  # wrap readline so C-c can be ignored, but blank is taken as default
204
211
  def readline prompt='>'
212
+ last_line
213
+ # do we need to clear till end of line, see enter_regex commented
205
214
  # unhide cursor
206
215
  print "\e[?25h"
216
+ system 'stty echo'
207
217
  begin
208
218
  target = Readline.readline(prompt, true)
209
219
  rescue Interrupt
210
220
  return nil
211
221
  ensure
212
222
  # hide cursor
213
- print "\e[?25l"
223
+ # NO LONGER HIDING cursor 2019-03-29 -
224
+ # print "\e[?25l"
225
+ system 'stty -echo'
214
226
  end
215
227
  target.chomp
216
228
  end
@@ -261,7 +273,10 @@ def get_char
261
273
  # end
262
274
  return c.chr if c
263
275
  ensure
264
- system('stty -raw echo 2>/dev/null') # turn raw input on
276
+ # system('stty -raw echo 2>/dev/null') # turn raw input on
277
+ # 2019-03-29 - echo was causing printing of arrow key code on screen
278
+ # if moving fast
279
+ system('stty -raw 2>/dev/null') # turn raw input on
265
280
  end
266
281
 
267
282
  ## GLOBALS
@@ -292,6 +307,8 @@ $pager_command = {
292
307
  unknown: 'open'
293
308
  }
294
309
  $dir_position = {}
310
+ $movement = $old_cursor = nil # cursor movement has happened only, don't repaint
311
+ $selection_mode = 1 # single select
295
312
 
296
313
  ## ----------------- CONSTANTS ----------------- ##
297
314
  GMARK = '*'.freeze
@@ -306,11 +323,43 @@ ON_RED = "\e[41m".freeze
306
323
  GREEN = "\e[32m".freeze
307
324
  YELLOW = "\e[33m".freeze
308
325
  BLUE = "\e[1;34m".freeze
326
+ MAGENTA = "\e[35m".freeze
327
+ CYAN = "\e[36m".freeze
309
328
 
310
329
  ON_BLUE = "\e[44m".freeze
311
330
  REVERSE = "\e[7m".freeze
312
331
  UNDERLINE = "\e[4m".freeze
313
- CURSOR_COLOR = ON_BLUE
332
+ CURSOR_COLOR = REVERSE
333
+
334
+ # NOTE: that osx uses LSCOLORS which only has colors for filetypes not
335
+ # extensions and patterns which LS_COLORS has.
336
+ # LS_COLORS contains 2 character filetype colors. ex executable mi broken link
337
+ # extension based colros starting with '*.'
338
+ # file pattern starting with '*' and a character that is not .
339
+ # File.ftype(path) returns
340
+ # file, directory di, characterSpecial cd, blockSpecial bd, fifo pi, link ln, socket so, or unknown
341
+
342
+ # This hash contains color codes for extensions. It is updated from
343
+ # LS_COLORS.
344
+ $ls_color = {
345
+ '.rb' => RED,
346
+ '.tgz' => MAGENTA,
347
+ '.zip' => MAGENTA,
348
+ '.torrent' => GREEN,
349
+ '.srt' => GREEN,
350
+ '.sh' => CYAN
351
+ }
352
+ # This hash contains colors for file patterns, updated from LS_COLORS
353
+ $ls_pattern = {}
354
+ # This hash contains colors for file types, updated from LS_COLORS
355
+ # Default values in absence of LS_COLORS
356
+ $ls_ftype = {
357
+ 'directory' => BLUE,
358
+ 'link' => "\e[01;36m",
359
+ 'mi' => "\e[01;31;7m",
360
+ 'or' => "\e[40;31;01m",
361
+ 'ex' => "\e[01;32m"
362
+ }
314
363
  ## --------------------------------------------- ##
315
364
 
316
365
  $patt = nil
@@ -322,7 +371,8 @@ $visited_files = []
322
371
  $visited_dirs = []
323
372
  ## dirs where some work has been done, for saving and restoring
324
373
  $used_dirs = []
325
- $default_sort_order = 'om'
374
+ # zsh o = order m = modified time
375
+ $default_sort_order = 'Om'
326
376
  $sorto = $default_sort_order
327
377
  $viewctr = 0
328
378
  $history = []
@@ -337,16 +387,85 @@ $status_color = 4 # status line, can be 2 3 4 5 6
337
387
  # ------------------- read_directory ------------------ #
338
388
  def read_directory
339
389
  rescan_required false
340
- # $files = `zsh -c 'print -rl -- *(#{$sorto}#{$hidden}M)'`.split("\n")
390
+
391
+ # http://zsh.sourceforge.net/Doc/Release/Shell-Builtin-Commands.html
392
+ # zsh print -rl
393
+ # -l Print the arguments separated by newlines instead of spaces.
394
+ # -r Ignore the escape conventions of echo.
395
+ # zshglob M = MARK_DIRS
396
+
341
397
  $filterstr ||= 'M'
342
- $files = `zsh -c 'print -rl -- *(#{$sorto}#{$hidden}#{$filterstr})'`.split("\n")
398
+ # $files = `zsh -c 'print -rl -- *(#{$sorto}#{$hidden}#{$filterstr})'`.split("\n")
399
+ $files = list_files
343
400
  $files = sort_file_list $files
401
+ return unless $enhanced_mode
402
+
344
403
  enhance_file_list
345
- ## added by RK 2014-03-31 - 00:29 since too many duplicates
346
- $files = $files.uniq if $enhanced_mode
404
+ $files = $files.uniq
405
+ end
406
+
407
+ # return a list of directory contents sorted as per sort order
408
+ # NOTE: FNM_CASEFOLD does not work with Dir.glob
409
+ def list_files dir='*', sorto=$sorto, hidden=$hidden, filter=$filterstr
410
+
411
+ dir += '/*' if File.directory?(dir)
412
+ dir = dir.gsub('//', '/')
413
+
414
+ # decide sort method based on second character
415
+ # first char is o or O (reverse)
416
+ # second char is macLn etc (as in zsh glob)
417
+ so = sorto ? sorto[1] : nil
418
+ func = case so
419
+ when 'm'
420
+ :mtime
421
+ when 'a'
422
+ :atime
423
+ when 'c'
424
+ :ctime
425
+ when 'L'
426
+ :size
427
+ when 'n'
428
+ :path
429
+ end
430
+
431
+ # sort by time and then reverse so latest first.
432
+ sorted_files = if hidden == 'D'
433
+ Dir.glob(dir, File::FNM_DOTMATCH)
434
+ else
435
+ Dir.glob(dir)
436
+ end
437
+
438
+ # WARN: crashes on a dead link since no mtime
439
+ # HACK: use first files return value for bad link
440
+ if func
441
+ sorted_files = sorted_files.sort_by do |f|
442
+ if File.exist? f
443
+ File.send(func, f)
444
+ else
445
+ sys_stat(f, func)
446
+ end
447
+ end
448
+ end
449
+
450
+ sorted_files.sort! { |w1, w2| w1.casecmp(w2) } if func == :path && $ignorecase
451
+
452
+ # zsh gives mtime sort with latest first, ruby gives latest last
453
+ sorted_files.reverse! if sorto[0] == 'O'
454
+
455
+ # add slash to directories
456
+ sorted_files = add_slash sorted_files
457
+ return sorted_files
347
458
  end
348
459
  # ------------- end of read_directory --------------------------------#
349
460
 
461
+ # this is just a tempo method to deal with dead links.
462
+ # I should be doing a system stat and parsing that
463
+ def sys_stat file, func
464
+ return unless File.symlink? file
465
+
466
+ return File.lstat(file).send(func)
467
+ end
468
+
350
469
  # ------------------- create_viewport ------------------ #
351
470
  def create_viewport
352
471
  $view = if $patt
@@ -382,21 +501,27 @@ def print_title
382
501
  $title ||= Dir.pwd.sub(ENV['HOME'], '~')
383
502
  fin = $sta + $viewport.size
384
503
  fl = $view.size
385
- t = "#{$title} #{$sta + 1} to #{fin} of #{fl} #{$sorto} F:#{$filterstr}"
504
+ if fl.zero?
505
+ t = "#{$title} No files."
506
+ else
507
+ t = "#{$title} #{$sta + 1} to #{fin} of #{fl} #{$sorto} F:#{$filterstr}"
508
+ end
386
509
 
387
510
  # don't exceed columns while printing
388
511
  t = t[t.size - $gcols..-1] if t.size >= $gcols
389
512
 
390
513
  print "#{BOLD}#{t}#{CLEAR}\n"
514
+ print "EMPTY" if fl.zero?
391
515
  end
392
516
  # ------------- end of print_title --------------------------------#
393
517
 
394
- # TODO clean this up and simplify it
518
+ # TODO: clean this up and simplify it
519
+ # NOTE: earlier this was called on every key (up-arow down arrow, now only
520
+ # called when page changes, so we only put directory name)
395
521
  def status_line
396
522
  # prompt
397
523
  v_mm = $mode ? "[#{$mode}] " : ''
398
524
  cf = current_file
399
- $message = "No file highlighted (#{cf})" if cf and !$highlighted_a_column
400
525
  $message = ' | No matches. Press ESCAPE' if $patt && !cf
401
526
 
402
527
  clear_status_line
@@ -404,23 +529,22 @@ def status_line
404
529
  # Print the filename at the right side of the status_line
405
530
  # sometimes due to search, there is no file
406
531
  if cf
407
- # cfl = cf.size
408
- # print right aligned TODO REFACTOR THIS
409
- # padding = @debug_flag ? 15 : 3
410
- # system "tput cup #{$glines} #{$gcols - cfl - padding}"
411
- # print "#{REVERSE}#{cf}#{CLEAR}"
412
532
  if @debug_flag
413
- print_on_right "#{$sta},#{$cursor},#{$stact},#{$viewport.size},#{$grows} | #{cf}"
533
+ # XXX this will not work on file basis FIXME
534
+ print_debug_info cf
414
535
  else
415
- print_on_right "#{cf}"
536
+ print_on_right "#{Dir.pwd}"
416
537
  end
417
538
  end
418
- # print "\r#{v_mm}#{$patt} #{GREEN}#{$message}#{CLEAR} >"
419
539
  # move to beginning of line, reset text mode after printing
420
540
  print "\r#{v_mm}#{$patt}#{$message}\e[m"
421
541
 
422
542
  end
423
543
 
544
+ def print_debug_info cf=current_file()
545
+ print_on_right "= #{$sta},#{$cursor},#{$stact},#{$viewport.size},#{$grows} | #{cf}"
546
+ end
547
+
424
548
  # should we do a read of the dir
425
549
  def rescan?
426
550
  $rescan_required
@@ -449,16 +573,73 @@ def draw_directory
449
573
  print_title
450
574
 
451
575
  # add hint and format the line
452
- buff = _format $viewport
576
+ buff = format_array $viewport
453
577
 
454
- # break into as many columns as required
578
+ # break viewport into as many columns as required
455
579
  # This is where directories get their blue color
456
580
  buff = columnate buff, $grows
457
581
 
582
+ # starts printing array on line 3
458
583
  buff.each { |line| print line, "\n" }
459
584
  print
460
585
 
461
586
  status_line
587
+
588
+ # place cursor correctly, we will use this to highlight current row
589
+ end
590
+
591
+ # place cursor correctly, we will use this to highlight current row
592
+ # XXX i am not sure how to highlight the bg without rewriting the text.
593
+ # I can get the filename but it has been truncated.
594
+ # I can get the earlier filename but the color has to be determined again.
595
+ # FIXME flinks after paging
596
+ # FIXME color of original hint lost
597
+ def place_cursor
598
+ # clear_cursor
599
+ # hint = get_shortcut($cursor)
600
+ c = $cursor - $sta
601
+ if c < $grows
602
+ # system "tput cup #{c + 2} 0"
603
+ # print "\e[#{c + 3};0H"
604
+ tput_cup c, 0
605
+ # print "#{CURSOR_COLOR}#{hint} >#{CLEAR}"
606
+ return
607
+ end
608
+ wid = get_width $viewport.size, $grows
609
+ rows = c % $grows
610
+ cols = (c / $grows) * wid
611
+ # system "tput cup #{rows + 2} #{cols}"
612
+ # print "\e[#{rows + 3};#{cols + 1}H"
613
+ tput_cup rows, cols
614
+ # print "#{CURSOR_COLOR}#{hint} >#{CLEAR}"
615
+ end
616
+
617
+ # clear the earlier position when we move.
618
+ # Currently not using until I can implement chgat here.
619
+ def clear_cursor
620
+ return unless $old_cursor
621
+
622
+ hint = get_shortcut($old_cursor)
623
+ if $old_cursor < $grows
624
+ # FIXME: faster way of getting here ? see fff
625
+ # system "tput cup #{$old_cursor + 2} 0"
626
+ tput_cup $old_cursor, 0
627
+ # print "#{CURSOR_COLOR}#{hint} >#{CLEAR}"
628
+ print "#{hint} "
629
+ return
630
+ end
631
+ wid = get_width $viewport.size, $grows
632
+ rows = $old_cursor % $grows
633
+ cols = ($old_cursor / $grows) * wid
634
+ # system "tput cup #{rows + 2} #{cols}"
635
+ tput_cup rows, cols
636
+ # print "#{CURSOR_COLOR}#{hint} >#{CLEAR}"
637
+ print "#{hint} "
638
+ end
639
+
640
+ def tput_cup row, col
641
+ # we add 3: 2 is for the help and directory line. 1 is since tput is 1 based
642
+ print "\e[#{row + 3};#{col + 1}H"
462
643
  end
463
644
 
464
645
  def redraw_required(flag=true) $redraw_required = flag; end
@@ -490,12 +671,10 @@ def resolve_binding key
490
671
  args = x
491
672
  send(binding, *args) if binding
492
673
  else
493
- perror "No binding for #{key}"
674
+ # perror "No binding for #{key}"
494
675
  end
495
676
  end
496
677
 
497
-
498
-
499
678
  ## write current dir to a file so we can ccd to it when exiting
500
679
  def write_curdir
501
680
  f = File.expand_path('~/.fff_d')
@@ -529,79 +708,43 @@ end
529
708
  ##
530
709
  #
531
710
  # print in columns
532
- # ary - array of data
533
- # sz - lines in one column
711
+ # ary - array of data (viewport, not view)
712
+ # siz - how many lines should there be in one column
534
713
  #
535
- def columnate(ary, sz)
714
+ def columnate ary, siz
536
715
  buff = []
537
716
  return buff if ary.nil? || ary.empty?
538
717
 
539
- # 2019-03-19 - just to make sure highlighted column is visible
540
- $highlighted_a_column = false
541
-
542
718
  # determine width based on number of files to show
543
719
  # if less than sz then 1 col and full width
544
720
  #
545
- wid = 30
546
- ars = ary.size
547
- ars = [$pagesize, ary.size].min
548
- d = 0
549
- if ars <= sz
550
- wid = $gcols - d
551
- else
552
- tmp = (ars * 1.000 / sz).ceil
553
- wid = $gcols / tmp - d
554
- end
555
- # elsif ars < sz * 2
556
- # wid = $gcols/2 - d
557
- # elsif ars < sz * 3
558
- # wid = $gcols/3 - d
559
- # else
560
- # wid = $gcols/$gviscols - d
561
- # end
721
+ wid = get_width ary.size, siz
562
722
 
563
723
  # ix refers to the index in the complete file list, wherease we only show 60 at a time
564
724
  ix = 0
565
725
  loop do
566
726
  ## ctr refers to the index in the column
727
+ # siz is how many items in one column
567
728
  ctr = 0
568
- while ctr < sz
729
+ while ctr < siz
569
730
 
570
731
  f = ary[ix]
571
732
  # f is not just filename but marker and hint
572
- # Check last char to see if directory
573
- linecolor = nil
574
- linecolor = BLUE if f[-1] == '/'
575
733
 
576
734
  # check to see if we need to truncate
577
- # TODO 2019-03-18 - truncate from middle of string.
578
- fsz = f.size
579
- if fsz > wid
580
- fromstart = wid - 8
581
- # f = f[0, wid - 2] + '$ '
582
- # 2019-03-18 - trying to truncate from center
583
- f = f[0, fromstart] + '$' + f[-6..-1] + ' '
584
- ## we do the coloring after trunc so ANSI escpe seq does not get get
585
- if ix + $sta == $cursor
586
- f = "#{CURSOR_COLOR}#{f}#{CLEAR}"
587
- $highlighted_a_column = true
588
- elsif linecolor
589
- f = "#{linecolor}#{f}#{CLEAR}"
590
- end
735
+ # 2019-03-18 - truncate from middle of string.
736
+ # 2019-03-24 - truncate and size to take care of color codes
737
+ # fsz = f.size
738
+ unformatted_len, _ = filename_len(f)
739
+ if unformatted_len > wid
740
+ # take the excess out from the center on both sides
741
+ f = truncate_formatted_filename(f, unformatted_len, wid)
742
+
591
743
 
592
744
  else
593
745
 
594
- ## we do the coloring before padding so the entire line does not get
595
- # padded, only file name
596
- # f = "#{CURSOR_COLOR}#{f}#{CLEAR}" if ix + $sta == $cursor
597
- if ix + $sta == $cursor
598
- f = "#{CURSOR_COLOR}#{f}#{CLEAR}"
599
- $highlighted_a_column = true
600
- else
601
- f = "#{linecolor}#{f}#{CLEAR}" if linecolor
602
- end
603
746
  # f = f.ljust(wid)
604
- f << ' ' * (wid - fsz)
747
+ f << ' ' * (wid - unformatted_len)
605
748
 
606
749
  end
607
750
 
@@ -621,15 +764,43 @@ def columnate(ary, sz)
621
764
  buff
622
765
  end
623
766
 
767
+ # shorten the filename to wid
768
+ # unformatted_len is the length without ANSI formatting
769
+ def truncate_formatted_filename f, unformatted_len, wid
770
+ excess = unformatted_len - wid
771
+ center = unformatted_len / 2
772
+ excess_half = excess / 2
773
+ point = center + excess_half
774
+ point1 = point - excess
775
+ # the space comes after the ANSI formatting
776
+ f = f[0..(point1 - 1)] + '$' + f[point + 2 .. -1] + ' '
777
+ f
778
+ end
779
+
780
+ def get_width arysz, siz
781
+ ars = [$pagesize, arysz].min
782
+ d = 0
783
+ return $gcols - d if ars <= siz
784
+
785
+ tmp = (ars * 1.000 / siz).ceil
786
+ wid = $gcols / tmp - d
787
+ wid
788
+ end
789
+
790
+ # calculate length of filename without ANSI color codes
791
+ def filename_len name
792
+ l = name.length
793
+ return l if name[0] != "\e"
794
+
795
+ # we have an escape code at the start of the name (and end, too)
796
+ index = name.index('m')
797
+ eindex = name.rindex("\e")
798
+ reall = eindex - index - 1
799
+ return reall, l
800
+ end
801
+
624
802
  ## formats the data with number, mark and details
625
- # 2019-03-09 - at some time this got renamed to `format` which is a kernel
626
- # method and this overshadowed that method.
627
- # NOTE: this does not do any coloring since the codes may get chopped by
628
- # columnate method
629
- # TODO 2019-03-23 - 23:30 we can do coloring here. columnate must check for color
630
- # code at start of line, and check size accordingly. If it needs to truncate, then
631
- # first take off the ANSI codes, truncate and put back.
632
- def _format(ary)
803
+ def format_array(ary)
633
804
  # buff = Array.new
634
805
  buff = Array.new(ary.size)
635
806
  return buff if ary.nil? || ary.empty?
@@ -648,37 +819,45 @@ def _format(ary)
648
819
  mark = SPACE
649
820
  mark = '+' if visited? f
650
821
  cur = SPACE
651
- cur = CURMARK if ix + $sta == $cursor
822
+ # cur = CURMARK if ix + $sta == $cursor # 2019-03-29 - removed reduced calls
652
823
  # NOTE seems like f and ary[ix] are the same
653
824
  mark = GMARK if selected?(ary[ix])
654
825
 
826
+ # take care of shortcuts in visited files and dirs
827
+ fullname = f[0] == '~' ? File.expand_path(f) : f
828
+ color = color_for fullname
829
+
655
830
  if $long_listing
656
831
  begin
657
832
  if File.exist? f
658
833
  stat = File.stat(f)
659
834
  else
660
- # remove last character and get stat
661
- last = f[-1]
662
- stat = File.stat(f.chop) if last == ' ' || last == '@' || last == '*'
835
+ # dead link
836
+ if File.symlink?(f)
837
+ stat = File.lstat(f)
838
+ else
839
+ # remove last character and get stat
840
+ last = f[-1]
841
+ stat = File.stat(f.chop) if last == ' ' || last == '@' || last == '*'
842
+ end
663
843
  end
664
844
  # this is for saved directories etc which are shortened
665
845
  stat ||= File.stat(File.expand_path(f))
666
846
  f = format('%10s %s %s', readable_file_size(stat.size, 1),
667
847
  date_format(stat.mtime), f)
668
848
  rescue StandardError => e
849
+ @log.warn "WARN::#{e}: FILE:: #{f}"
669
850
  f = format('%10s %s %s', '?', '??????????', f)
670
851
  end
671
852
  end
672
853
 
673
- s = "#{ind}#{mark}#{cur}#{f}"
854
+ # 2019-03-31 - replace unprintable chars with ?
855
+ f = f.gsub(/[^[:print:]]/, '?')
674
856
 
675
- # NOTE: I cannot color the current line since _format does the chopping
676
- # so not only does the next lines alignment get skewed,
677
- # but also if the line is truncated
678
- # then the color overflows.
679
- # if ix + $sta == $cursor
680
- # s = "#{RED}#{s}#{CLEAR}"
681
- # end
857
+ s = "#{ind}#{mark}#{cur}#{f}"
858
+ if color
859
+ s = "#{color}#{s}#{CLEAR}"
860
+ end
682
861
 
683
862
  buff[ctr] = s
684
863
 
@@ -688,6 +867,104 @@ def _format(ary)
688
867
  buff
689
868
  end
690
869
 
870
+ # determine color for a filename based on extension, then pattern, then filetype
871
+ def color_for fname
872
+ extension = File.extname(fname)
873
+ color = $ls_color[extension]
874
+ return color if color
875
+
876
+ # check file against patterns
877
+ if File.file?(fname)
878
+ $ls_pattern.each_pair do |k, v|
879
+ # if fname.match?(/k/)
880
+ if fname =~ /#{k}/
881
+ # @log.debug "#{fname} matched #{k}. color is #{v[1..-2]}"
882
+ return v
883
+ # else
884
+ # @log.debug "#{fname} di not match #{k}. color is #{v[1..-2]}"
885
+ end
886
+ end
887
+ end
888
+
889
+ # check filetypes
890
+ if File.exist? fname
891
+ # @log.debug "Filetype:#{File.ftype(fname)}"
892
+
893
+ return $ls_ftype[File.ftype(fname)] if $ls_ftype.key? File.ftype(fname)
894
+ return $ls_ftype['ex'] if File.executable?(fname)
895
+ else
896
+ # orphan file, but fff uses mi
897
+ return $ls_ftype['mi'] if File.symlink?(fname)
898
+
899
+ @log.warn "FILE WRONG: #{fname}"
900
+ return $ls_ftype['or']
901
+ end
902
+
903
+ nil
904
+ end
905
+
906
+ def parse_ls_colors
907
+
908
+ colorvar = ENV['LS_COLORS']
909
+ if colorvar.nil?
910
+ $ls_colors_found = nil
911
+ return
912
+ end
913
+ $ls_colors_found = true
914
+ ls = colorvar.split(':')
915
+ ls.each do |e|
916
+ patt, colr = e.split '='
917
+ colr = "\e[" + colr + 'm'
918
+ if e.start_with? '*.'
919
+ # extension, avoid '*' and use the rest as key
920
+ $ls_color[patt[1..-1]] = colr
921
+ # @log.debug "COLOR: Writing extension (#{patt})."
922
+ elsif e[0] == '*'
923
+ # file pattern, this would be a glob pattern not regex
924
+ # only for files not directories
925
+ patt = patt.gsub('.', '\.')
926
+ patt = patt.sub('+', '\\\+') # if i put a plus it does not go at all
927
+ patt = patt.gsub('-', '\-')
928
+ patt = patt.gsub('?', '.')
929
+ patt = patt.gsub('*', '.*')
930
+ patt = "^#{patt}" if patt[0] != '.'
931
+ patt = "#{patt}$" if patt[-1] != '*'
932
+ $ls_pattern[patt] = colr
933
+ # @log.debug "COLOR: Writing file (#{patt})."
934
+ elsif patt.length == 2
935
+ # file type, needs to be mapped to what ruby will return
936
+ # file, directory di, characterSpecial cd, blockSpecial bd, fifo pi, link ln, socket so, or unknown
937
+ # di = directory
938
+ # fi = file
939
+ # ln = symbolic link
940
+ # pi = fifo file
941
+ # so = socket file
942
+ # bd = block (buffered) special file
943
+ # cd = character (unbuffered) special file
944
+ # or = symbolic link pointing to a non-existent file (orphan)
945
+ # mi = non-existent file pointed to by a symbolic link (visible when you type ls -l)
946
+ # ex = file which is executable (ie. has 'x' set in permissions).
947
+ case patt
948
+ when 'di'
949
+ $ls_ftype['directory'] = colr
950
+ when 'cd'
951
+ $ls_ftype['characterSpecial'] = colr
952
+ when 'bd'
953
+ $ls_ftype['blockSpecial'] = colr
954
+ when 'pi'
955
+ $ls_ftype['fifo'] = colr
956
+ when 'ln'
957
+ $ls_ftype['link'] = colr
958
+ when 'so'
959
+ $ls_ftype['socket'] = colr
960
+ else
961
+ $ls_ftype[patt] = colr
962
+ end
963
+ # @log.debug "COLOR: ftype #{patt}"
964
+ end
965
+ end
966
+ end
967
+
691
968
  ## select file based on key pressed
692
969
  def select_hint view, key
693
970
  # a to y is direct
@@ -711,14 +988,14 @@ def select_hint view, key
711
988
  end
712
989
 
713
990
  ## toggle selection state of file
714
- def toggle_select(f=current_file)
715
- # dir = Dir.pwd
716
- # file = File.join(dir, f)
991
+ def toggle_select f=current_file
717
992
  if selected? f
718
993
  remove_from_selection f
719
994
  else
995
+ $selected_files.clear if $selection_mode == 1
720
996
  add_to_selection f
721
997
  end
998
+ message "#{$selected_files.count} files selected. "
722
999
  # XXX is it possible to just redraw this line
723
1000
  redraw_required
724
1001
  end
@@ -734,13 +1011,12 @@ def open_file(f)
734
1011
  last = f[-1]
735
1012
  f = f.chop if last == ' ' || last == '@' || last == '*'
736
1013
  end
737
- nextpos = nil
738
1014
 
739
1015
  # could be a bookmark with position attached to it
740
- f, nextpos = f.split(':') if f.index(':')
1016
+ f, _nextpos = f.split(':') if f.index(':')
741
1017
  if File.directory? f
742
1018
  save_dir_pos
743
- change_dir f, nextpos
1019
+ change_dir f #, nextpos
744
1020
  elsif File.readable? f
745
1021
  # TODO: looks complex pls simplify !! XXX
746
1022
  $default_command ||= '$PAGER'
@@ -842,9 +1118,7 @@ def run_command(f)
842
1118
  end
843
1119
 
844
1120
  ## cd to a dir.
845
- # Also, position cursor on child directory that we changed from if second param
846
- # is true.
847
- def change_dir f, position_cursor=false
1121
+ def change_dir f
848
1122
  unless File.directory? f
849
1123
  perror "#{f} is not a directory, or does not exist."
850
1124
  return
@@ -852,8 +1126,7 @@ def change_dir f, position_cursor=false
852
1126
 
853
1127
  # before leaving a dir we save it in the list, as well as the cursor
854
1128
  # position, so we can restore that position when we return
855
- dir_from = Dir.pwd
856
- $visited_dirs.insert(0, dir_from)
1129
+ $visited_dirs.insert(0, Dir.pwd)
857
1130
  save_dir_pos
858
1131
 
859
1132
  f = File.expand_path(f)
@@ -861,14 +1134,6 @@ def change_dir f, position_cursor=false
861
1134
  read_directory
862
1135
  post_cd
863
1136
 
864
- # position the cursor on the previous directory
865
- # This happens when we go to parent directory
866
- if position_cursor
867
- # get the position of the previous directory
868
- pos = index_of(File.basename(dir_from) + '/')
869
- $cursor = pos if pos
870
- end
871
-
872
1137
  redraw_required
873
1138
  end
874
1139
 
@@ -903,10 +1168,11 @@ def refresh
903
1168
  end
904
1169
 
905
1170
  # put directories first, then files
906
- def sort_file_list(_files)
907
- _dirs = $files.select { |f| File.directory?(f) }
908
- _files = $files.select { |f| File.file?(f) }
909
- _dirs + _files
1171
+ def sort_file_list files
1172
+ dirs = files.select { |f| File.directory?(f) }
1173
+ # earlier I had File? which removed links, esp dead ones
1174
+ fi = files.select { |f| !File.directory?(f) }
1175
+ dirs + fi
910
1176
  end
911
1177
 
912
1178
  ## unselect all files
@@ -924,10 +1190,10 @@ end
924
1190
  ## accept dir to goto and change to that ( can be a file too)
925
1191
  def goto_dir
926
1192
  # print "\e[?25h"
927
- print_last_line 'Enter path: '
1193
+ # print_last_line 'Enter path: '
928
1194
  begin
929
1195
  # path = gets.chomp
930
- path = readline
1196
+ path = readline 'Enter path to go to: '
931
1197
  if path.nil? || path == ''
932
1198
  clear_status_line
933
1199
  return
@@ -960,44 +1226,35 @@ end
960
1226
  # In selection, pressed hotkey selects a file without opening, one can keep selecting
961
1227
  # (or deselecting).
962
1228
  #
963
- def selection_mode_toggle
1229
+ def toggle_selection_mode
964
1230
  if $mode == 'SEL'
1231
+ $selection_mode = 1
965
1232
  unselect_all
966
1233
  $mode = nil
1234
+ message 'Selection mode is single. '
967
1235
  else
968
- # $selection_mode = !$selection_mode
1236
+ $selection_mode = 2
969
1237
  $mode = 'SEL'
1238
+ message 'Typing a hint selects the file. Toggling again will clear selection. '
970
1239
  end
971
1240
  end
972
1241
 
973
- ## toggle command mode
974
- # 2019-03-15 - TODO FIXME preferable when we go into command mode
975
- # run a command on current or selected files. But then is it a mode, do we remain
976
- # there, or come out immediately ?
977
- # 2019-03-17 - have removed this altogether. not required
978
- def command_mode
979
- if $mode == 'COM'
980
- $mode = nil
981
- return
982
- end
983
- $mode = 'COM'
984
- end
985
-
986
1242
  # go to parent dir, and maintain cursor on the dir we came out of
987
1243
  def goto_parent_dir
988
- # 2019-03-20 - when changing to parent, we need to keep cursor on
1244
+ # When changing to parent, we need to keep cursor on
989
1245
  # parent dir, not first
990
- # curr = File.basename(Dir.pwd)
1246
+ curr = File.basename(Dir.pwd)
1247
+
1248
+ return if curr == '/'
1249
+
1250
+ change_dir '..'
991
1251
 
992
- change_dir '..', true
1252
+ return if curr == Dir.pwd
993
1253
 
994
- # TODO 2019-03-24 - we can revert to old thing and remove second arg from cd
995
- # FIXME 2019-03-23 - too late to change the cursor here since change_dir
996
- # now does a redraw !!! XXX
997
1254
  # get index of child dir in this dir, and set cursor to it.
998
- # index = $files.index(curr + '/')
999
- # pause "WARNING: Could not find #{curr} in this directory." unless index
1000
- # $cursor = index if index
1255
+ index = $files.index(curr + '/')
1256
+ pause "WARNING: Could not find #{curr} in this directory." unless index
1257
+ $cursor = index if index
1001
1258
  end
1002
1259
 
1003
1260
  def goto_home_dir
@@ -1022,6 +1279,7 @@ end
1022
1279
  # If lower case character given, then go to first file starting with char.
1023
1280
  def goto_bookmark(key = nil)
1024
1281
  unless key
1282
+ last_line
1025
1283
  print 'Enter bookmark char: '
1026
1284
  key = get_char
1027
1285
  end
@@ -1036,7 +1294,7 @@ def goto_bookmark(key = nil)
1036
1294
  nextpos = d[ix + 1..-1]
1037
1295
  d = d[0, ix]
1038
1296
  end
1039
- change_dir d, nextpos
1297
+ change_dir d #, nextpos
1040
1298
  else
1041
1299
  perror "#{key} not a bookmark"
1042
1300
  end
@@ -1050,7 +1308,7 @@ end
1050
1308
  def enter_regex
1051
1309
  # print 'Enter (regex) pattern: '
1052
1310
  # move to beginning of line, and clear till EOL
1053
- print "\r\e[K"
1311
+ # print "\r\e[K"
1054
1312
  $patt = readline '/'
1055
1313
  end
1056
1314
 
@@ -1059,23 +1317,16 @@ def next_page
1059
1317
  $sta += $pagesize
1060
1318
  $cursor += $pagesize
1061
1319
  $sta = $cursor if $sta > $cursor
1062
- # FIXME this is sometimes correct, but in short cases, cursor no longer refers
1063
- # to a file. also after cycling, it no longer has a file.
1064
- # FIXME: 2019-03-20 - if cursor is panned to 3rd column and then we page,
1065
- # then after a while no hints show up since stact is high.
1066
1320
  $stact = 0
1321
+ $old_cursor = nil
1067
1322
  redraw_required
1068
- # next just does not work on the last page when it should
1069
- # perhaps viewport has not yet been adjusted, that's why
1070
- # $stact = 0 if $stact >= $viewport.size
1071
- # setting to zero is not the best solution since hints are reset but this is better
1072
- # than trying to maintain the same stact since the columns reduce in the end.
1073
- # I tried checking that stact < viewport.size but nothing happened
1074
1323
  end
1075
1324
 
1076
1325
  def prev_page
1077
1326
  $sta -= $pagesize
1078
1327
  $cursor -= $pagesize
1328
+ $old_cursor = nil
1329
+ # FIXME: check cursor sanity and if not changed then no redraw
1079
1330
  redraw_required
1080
1331
  end
1081
1332
 
@@ -1130,9 +1381,8 @@ def page_with_tempfile
1130
1381
  end
1131
1382
 
1132
1383
  def debug_vars
1133
- @debug_flag = true
1134
1384
  page_with_tempfile do |file|
1135
- file.puts 'DEBUG VARIABLES ARE:'
1385
+ file.puts "DEBUG VARIABLES for #{current_file}:"
1136
1386
  file.puts
1137
1387
  file.puts "sta #{$sta}"
1138
1388
  file.puts "cursor #{$cursor}"
@@ -1142,6 +1392,10 @@ def debug_vars
1142
1392
  file.puts "view.size #{$view.size}"
1143
1393
  file.puts "grows #{$grows}"
1144
1394
  file.puts "file #{current_file}"
1395
+ file.puts
1396
+ file.puts `file "#{current_file}"`
1397
+ file.puts
1398
+ file.puts %x[stat "#{current_file}"]
1145
1399
  end
1146
1400
  redraw_required
1147
1401
  end
@@ -1156,7 +1410,7 @@ def view_bookmarks
1156
1410
  goto_bookmark(key) if key =~ /^[0-9A-Z]$/
1157
1411
  end
1158
1412
 
1159
- # MENU MAIN -- keep consistent with zfm
1413
+ # MENU MAIN
1160
1414
  def main_menu
1161
1415
  h = {
1162
1416
  :a => :ag,
@@ -1171,11 +1425,11 @@ def main_menu
1171
1425
  '2' => :select_from_used_dirs,
1172
1426
  :t => :dirtree,
1173
1427
  '4' => :tree,
1174
- :s => :sort_menu,
1428
+ :o => :sort_menu,
1175
1429
  :F => :filter_menu,
1176
1430
  :c => :command_menu,
1177
1431
  :b => :bookmark_menu,
1178
- :y => :selection_menu,
1432
+ :s => :selection_menu,
1179
1433
  :B => :bindkey_ext_command,
1180
1434
  :M => :create_a_dir,
1181
1435
  '%' => :create_a_file,
@@ -1190,7 +1444,7 @@ def selection_menu
1190
1444
  :a => :select_all,
1191
1445
  :u => :unselect_all,
1192
1446
  :s => :toggle_select,
1193
- '@' => 'selection_mode_toggle',
1447
+ '@' => 'toggle_selection_mode',
1194
1448
  'x' => 'visual_mode_toggle',
1195
1449
  :v => :view_selected_files
1196
1450
  }
@@ -1210,6 +1464,7 @@ end
1210
1464
  def menu title, h
1211
1465
  return unless h
1212
1466
 
1467
+ last_line # 2019-03-30 - required since cursor is not longer at bottom
1213
1468
  pbold title.to_s
1214
1469
  # h.each_pair { |k, v| puts " #{k}: #{v}" }
1215
1470
  # 2019-03-09 - trying out using `column` to print in cols
@@ -1231,15 +1486,25 @@ def menu title, h
1231
1486
  end
1232
1487
 
1233
1488
  def toggle_menu
1234
- h = { :h => :toggle_hidden, :c => :toggle_case, :l => :toggle_long_list, '1' => :toggle_columns,
1235
- :p => :toggle_pager_mode, :e => :toggle_enhanced_list }
1489
+ h = { :h => :toggle_hidden,
1490
+ :c => :toggle_case,
1491
+ :l => :toggle_long_list,
1492
+ '1' => :toggle_columns,
1493
+ :p => :toggle_pager_mode,
1494
+ :d => :toggle_debug_flag,
1495
+ :m => :toggle_selection_mode,
1496
+ :e => :toggle_enhanced_list }
1497
+
1236
1498
  _, menu_text = menu 'Toggle Menu', h
1237
1499
  return unless menu_text
1238
1500
 
1239
1501
  case menu_text
1240
1502
  when :toggle_hidden
1503
+ # NOTE: now that I am shifting queries to ruby not sure this will continue
1504
+ # FNM_DOTMATCH
1505
+ # working everywhere
1506
+ # zsh D - dot files should show
1241
1507
  $hidden = $hidden ? nil : 'D'
1242
- # pause "Show hidden files is now #{!$hidden.nil?}. Press a key."
1243
1508
  message "Show hidden is now #{!$hidden.nil?}"
1244
1509
  rescan_required
1245
1510
  when :toggle_case
@@ -1249,8 +1514,6 @@ def toggle_menu
1249
1514
  message "Ignore Case is now #{$ignorecase}"
1250
1515
  rescan_required
1251
1516
  when :toggle_columns
1252
- # FIXME: 2019-03-20 - if 3 then 1
1253
- # adjust stact and sta, if moving from panned position
1254
1517
  if $gviscols == 1
1255
1518
  $gviscols = 3
1256
1519
  else
@@ -1287,27 +1550,34 @@ def toggle_menu
1287
1550
  $pagesize = $pagesize == x ? $grows : x
1288
1551
  end
1289
1552
  if $stact > 0
1290
- # FIXME what if we had paged down as well and sta was > 0
1291
1553
  $sta = $stact
1292
1554
  $stact = 0 # in case user was panned 2019-03-20 -
1293
1555
  end
1294
1556
  message "Long listing is #{$long_listing}, vis cols is #{$gviscols}"
1295
1557
  rescan_required
1558
+ when :toggle_debug_flag
1559
+ @debug_flag = !@debug_flag
1560
+ message "Debug flag is #{@debug_flag}"
1296
1561
  end
1297
1562
  end
1298
1563
 
1299
1564
  def sort_menu
1565
+ # zsh o = order, O = reverse order
1566
+ # ruby mtime/atime/ctime come reversed so we have to change o to O
1300
1567
  lo = nil
1301
1568
  h = { m: :modified, a: :accessed, M: :oldest,
1302
- l: :largest, s: :smallest, n: :name, r: :rev_name, d: :dirs, c: :clear }
1569
+ l: :largest, s: :smallest, n: :name, r: :rev_name, d: :dirs, c: :inode,
1570
+ z: :clear }
1303
1571
  _, menu_text = menu 'Sort Menu', h
1304
1572
  case menu_text
1305
1573
  when :modified
1306
- lo = 'om'
1574
+ lo = 'Om'
1307
1575
  when :accessed
1308
- lo = 'oa'
1576
+ lo = 'Oa'
1577
+ when :inode
1578
+ lo = 'Oc'
1309
1579
  when :oldest
1310
- lo = 'Om'
1580
+ lo = 'om'
1311
1581
  when :largest
1312
1582
  lo = 'OL'
1313
1583
  when :smallest
@@ -1325,9 +1595,6 @@ def sort_menu
1325
1595
  $sorto = lo
1326
1596
  message "Sorted on #{menu_text}"
1327
1597
  rescan_required
1328
- # $files = `zsh -c 'print -rl -- *(#{lo}#{$hidden}M)'`.split("\n") if lo
1329
- # $title = nil
1330
- # $files =$(eval "print -rl -- ${pattern}(${MFM_LISTORDER}$filterstr)")
1331
1598
  end
1332
1599
 
1333
1600
  # thse need to be placed in correct position, some do nothing
@@ -1359,11 +1626,12 @@ def command_menu
1359
1626
  when :locate
1360
1627
  locate
1361
1628
  when :today
1362
- # FIXME use filterstring
1629
+ # zshglob: M = MARK_DIRS with slash
1630
+ # zshglob: 'm0' 'm' = modified time, '0' = 0 days ago
1363
1631
  $files = `zsh -c 'print -rl -- *(#{$hidden}Mm0)'`.split("\n")
1364
1632
  $title = "Today's files"
1365
1633
  when :default_command
1366
- puts " THis no longer works"
1634
+ puts ' This no longer works'
1367
1635
  puts 'Selecting a file usually invokes $EDITOR'
1368
1636
  puts 'What command do you want to use repeatedly on selected files: '
1369
1637
  $default_command = gets.chomp
@@ -1408,18 +1676,22 @@ def filter_menu
1408
1676
  case menu_text
1409
1677
  when :dirs
1410
1678
  $filterstr = '/M'
1679
+ # zsh /M MARK_DIRS appends trailing '/' to directories
1411
1680
  files = `zsh -c 'print -rl -- *(#{$sorto}/M)'`.split("\n")
1412
1681
  $title = 'Filter: directories only'
1413
1682
  when :files
1414
1683
  $filterstr = '.'
1684
+ # zsh '.' for files, '/' for dirs
1415
1685
  files = `zsh -c 'print -rl -- *(#{$sorto}#{$hidden}.)'`.split("\n")
1416
1686
  $title = 'Filter: files only'
1417
1687
  when :emptydirs
1418
1688
  $filterstr = '/D^F'
1689
+ # zsh F = full dirs, ^F empty dirs
1419
1690
  files = `zsh -c 'print -rl -- *(#{$sorto}/D^F)'`.split("\n")
1420
1691
  $title = 'Filter: empty directories'
1421
1692
  when :emptyfiles
1422
1693
  $filterstr = '.L0'
1694
+ # zsh .L size in bytes
1423
1695
  files = `zsh -c 'print -rl -- *(#{$sorto}#{$hidden}.L0)'`.split("\n")
1424
1696
  $title = 'Filter: empty files'
1425
1697
  when :reduce_list
@@ -1427,17 +1699,20 @@ def filter_menu
1427
1699
  when :extension
1428
1700
  files = filter_for_current_extension
1429
1701
  end
1430
- if files
1702
+ if files && files.count > 0
1431
1703
  $files = files
1432
1704
  $stact = 0
1433
- $message = "Filtered on #{menu_text}"
1705
+ $message = "Filtered on #{menu_text}. Press ESC to return."
1434
1706
  # redraw
1707
+ else
1708
+ perror 'Sorry, No files. '
1709
+ $title = nil
1435
1710
  end
1436
1711
  end
1437
1712
 
1438
1713
  def reduce pattern=nil
1439
1714
  unless pattern
1440
- print 'Enter a pattern to reduce current list: '
1715
+ last_line 'Enter a pattern to reduce current list: '
1441
1716
  pattern = gets.chomp
1442
1717
  end
1443
1718
  $title = "Filter: pattern #{pattern}"
@@ -1481,9 +1756,6 @@ def pop_dir
1481
1756
  ## XXX make sure the dir exists, cuold have been deleted. can be an error or crash otherwise
1482
1757
  $visited_dirs.push d
1483
1758
  Dir.chdir d
1484
- # $filterstr ||= 'M'
1485
- # $files = `zsh -c 'print -rl -- *(#{$sorto}#{$hidden}#{$filterstr})'`.split("\n")
1486
- # $files = sort_file_list $files
1487
1759
  post_cd
1488
1760
  rescan_required
1489
1761
  end
@@ -1493,11 +1765,10 @@ def post_cd
1493
1765
  $title = $patt = $message = nil
1494
1766
  $sta = $cursor = $stact = 0
1495
1767
  $visual_block_start = nil
1496
- $current_dir = Dir.pwd # 2019-03-10 - so i don't keep doing in functions
1768
+ $current_dir = Dir.pwd
1497
1769
  screen_settings
1498
1770
 
1499
1771
  # goto last position cursor was in this dir
1500
- # THis only makes sense if called after read_directory but before redraw
1501
1772
  revert_dir_pos
1502
1773
  end
1503
1774
 
@@ -1562,9 +1833,9 @@ end
1562
1833
  def create_bookmark
1563
1834
 
1564
1835
  print 'Enter A to Z or 0-9 to create a bookmark: '
1565
- print "\e[?25h" # unhide cursor
1836
+ # print "\e[?25h" # unhide cursor
1566
1837
  key = get_char
1567
- print "\e[?25l" # hide cursor
1838
+ # print "\e[?25l" # hide cursor
1568
1839
  if key =~ /^[0-9A-Z]$/
1569
1840
  # $bookmarks[key] = "#{Dir.pwd}:#{$cursor}"
1570
1841
  $bookmarks[key] = Dir.pwd
@@ -1580,6 +1851,7 @@ end
1580
1851
  # Was this supposed to be augmented, or just remain limited like this
1581
1852
  # We should be able to do everything in the menus from here. TODO
1582
1853
  def subcommand
1854
+ last_line
1583
1855
  print 'Enter command: q x wq p w e r h '
1584
1856
  begin
1585
1857
  command = readline
@@ -1590,6 +1862,7 @@ def subcommand
1590
1862
  if command == 'q'
1591
1863
  # FIXME: 2019-03-22 - should this not call quit_command ?
1592
1864
  if $modified
1865
+ last_line
1593
1866
  print 'Do you want to save bookmarks? (y/n): '
1594
1867
  key = get_char
1595
1868
  if key == 'y'
@@ -1627,14 +1900,15 @@ end
1627
1900
 
1628
1901
  def quit_command
1629
1902
  if $modified
1630
- puts 'Press w to save bookmarks before quitting ' if $modified
1631
- print 'Press another q to quit '
1903
+ last_line
1904
+ puts 'Press y to save bookmarks before quitting ' if $modified
1905
+ print 'Press n to quit without saving'
1632
1906
  key = get_char
1633
1907
  else
1634
1908
  $quitting = true
1635
1909
  end
1636
- $quitting = true if key == 'q'
1637
- $quitting = $writing = true if key == 'w'
1910
+ $quitting = true if key == 'n'
1911
+ $quitting = $writing = true if key == 'y'
1638
1912
  end
1639
1913
 
1640
1914
  def views
@@ -1651,14 +1925,32 @@ end
1651
1925
 
1652
1926
  def child_dirs
1653
1927
  $title = 'Directories in current directory'
1654
- $files = `zsh -c 'print -rl -- *(/#{$sorto}#{$hidden}M)'`.split("\n")
1655
- message "#{$files.size} files."
1928
+ # M is MARK_DIRS option for putting trailing slash after dir
1929
+ # $files = `zsh -c 'print -rl -- *(/#{$sorto}#{$hidden}M)'`.split("\n")
1930
+ # $files = Dir.glob("*", File::FNM_DOTMATCH).select {|f| File.directory?(f)} - %w[ . ..]
1931
+ $files = dirs
1932
+ message "#{$files.size} directories."
1656
1933
  # redraw
1657
1934
  end
1658
1935
 
1936
+ def dirs dir='*'
1937
+ files = Dir.glob(dir, File::FNM_DOTMATCH).select {|f| File.directory?(f)} - %w[ . ..]
1938
+ files = add_slash files
1939
+ files
1940
+ end
1941
+
1942
+ def add_slash files
1943
+ return files.map do |f|
1944
+ File.directory?(f) ? f + '/' : f
1945
+ end
1946
+ end
1947
+
1659
1948
  def dirtree
1660
- $title = 'Child directories with depth'
1661
- $files = `zsh -c 'print -rl -- **/*(/#{$sorto}#{$hidden}M)'`.split("\n")
1949
+ $title = 'Child directories recursive'
1950
+ # zsh **/ is recursive
1951
+ files1 = `zsh -c 'print -rl -- **/*(/#{$sorto}#{$hidden}M)'`.split("\n")
1952
+ $files = Dir['**/']
1953
+ @log.debug "zsh #{files1} != rb #{$files}" if files1 != $files
1662
1954
  message "#{$files.size} files."
1663
1955
  # redraw
1664
1956
  end
@@ -1669,7 +1961,8 @@ end
1669
1961
  def tree
1670
1962
  # Caution: use only for small projects, don't use in root.
1671
1963
  $title = 'Full Tree'
1672
- $files = `zsh -c 'print -rl -- **/*(#{$sorto}#{$hidden}M)'`.split("\n")
1964
+ # $files = `zsh -c 'print -rl -- **/*(#{$sorto}#{$hidden}M)'`.split("\n")
1965
+ $files = Dir['**/*']
1673
1966
  message "#{$files.size} files."
1674
1967
  # redraw
1675
1968
  end
@@ -1679,6 +1972,8 @@ end
1679
1972
  def recent_files
1680
1973
  # print -rl -- **/*(Dom[1,10])
1681
1974
  $title = 'Recent files'
1975
+ # zsh D DOT_GLOB, show dot files
1976
+ # zsh om order on modification time
1682
1977
  $files = `zsh -c 'print -rl -- **/*(Dom[1,15])'`.split("\n").reject {|f| f[0] == '.'}
1683
1978
  # redraw
1684
1979
  end
@@ -1711,6 +2006,7 @@ def perror text
1711
2006
  end
1712
2007
 
1713
2008
  def pause text=' Press a key.'
2009
+ last_line
1714
2010
  print text
1715
2011
  get_char
1716
2012
  end
@@ -1795,10 +2091,12 @@ def ask_hint(deflt = nil)
1795
2091
  end
1796
2092
 
1797
2093
  ## check screen size and accordingly adjust some variables
2094
+ # NOTE: tput is ncurses dependent, so use stty
1798
2095
  #
1799
2096
  def screen_settings
1800
- $glines = `tput lines`.to_i
1801
- $gcols = `tput cols`.to_i
2097
+ $glines, $gcols = `stty size`.split.map{|e| e.to_i}
2098
+ # $glines = `tput lines`.to_i
2099
+ # $gcols = `tput cols`.to_i
1802
2100
  $grows = $glines - 3
1803
2101
  # $pagesize = 60
1804
2102
  # $gviscols = 3
@@ -1842,7 +2140,7 @@ end
1842
2140
  # from selection
1843
2141
  def file_actions(action = nil)
1844
2142
  # only add dtrx for gz
1845
- h = { d: :delete, m: :move, r: :rename, v: ENV['EDITOR'] || :vim,
2143
+ h = { d: :delete, D: '/bin/rm', m: :move, r: :rename, v: ENV['EDITOR'] || :vim,
1846
2144
  c: :copy, C: :chdir, W: :remspace, e: :execute, s: :page_stat_for_file,
1847
2145
  l: :less, p: :most, f: :file, o: :open, x: :dtrx, z: :zip }
1848
2146
  # acttext = h[action.to_sym] || action
@@ -1892,20 +2190,22 @@ def file_actions(action = nil)
1892
2190
 
1893
2191
  when :delete
1894
2192
  delcommand = 'rmtrash'
1895
- print "#{delcommand} #{text} ?[yn?]: "
2193
+ last_line
2194
+ print "#{delcommand} #{text} ? [yn?]: "
1896
2195
  key = get_char
1897
2196
  view_selected_files if key == '?'
1898
2197
  return if key != 'y'
1899
2198
 
2199
+ clear_status_line
2200
+ print "\r deleting ..."
1900
2201
  system "#{delcommand} #{files}"
1901
2202
  refresh
1902
2203
 
1903
2204
  when :move
1904
2205
  # 2019-03-07 - NOTE this will only work with single file selection
1905
- print "move #{text} to : "
1906
2206
  # target = gets().chomp
1907
2207
  # target = Readline.readline('>', true)
1908
- target = readline
2208
+ target = readline "Move #{text} to : "
1909
2209
  return unless target
1910
2210
 
1911
2211
  target = '.' if target == ''
@@ -1929,8 +2229,7 @@ def file_actions(action = nil)
1929
2229
 
1930
2230
  when :copy
1931
2231
  # Target must be directory
1932
- print "copy #{text} to : "
1933
- target = readline
2232
+ target = readline "Copy #{text} to : "
1934
2233
  return unless target # C-c
1935
2234
 
1936
2235
  target = '.' if target == ''
@@ -1953,9 +2252,8 @@ def file_actions(action = nil)
1953
2252
  change_dir File.dirname(text)
1954
2253
 
1955
2254
  when :zip
1956
- print 'Archive name: '
1957
2255
  # target = gets().chomp
1958
- target = readline
2256
+ target = readline 'Archive name: '
1959
2257
  return unless target
1960
2258
  return if target == ''
1961
2259
 
@@ -1973,8 +2271,7 @@ def file_actions(action = nil)
1973
2271
  when :rename
1974
2272
  # 2019-03-07 NOTE works for single file FIXME
1975
2273
  # 2019-03-07 - TODO for n files replace pattern with string
1976
- print "rename #{text} to : "
1977
- target = readline
2274
+ target = readline "Rename #{text} to : "
1978
2275
  return if target == ''
1979
2276
 
1980
2277
  text = File.expand_path(text)
@@ -2021,13 +2318,12 @@ def file_actions(action = nil)
2021
2318
  else
2022
2319
  return unless menu_text
2023
2320
 
2024
- print "#{menu_text} #{files}"
2025
- pause
2026
- print
2321
+ @log.debug "#{menu_text} #{files}"
2322
+ last_line
2323
+ pause "#{menu_text} #{files}"
2027
2324
  system "#{menu_text} #{files}"
2028
2325
  setup_terminal
2029
2326
  refresh
2030
- pause
2031
2327
  end
2032
2328
 
2033
2329
  # remove non-existent files from select list due to move or delete
@@ -2077,16 +2373,22 @@ def execute
2077
2373
  end
2078
2374
 
2079
2375
  def ag
2080
- print 'Enter a pattern to search (ag): '
2081
- pattern = readline
2376
+ pattern = readline 'Enter a pattern to search (ag): '
2082
2377
  return if pattern == ''
2083
2378
 
2084
- $title = "Files found using 'ag' #{pattern}"
2085
- system("ag #{pattern}")
2379
+ $title = "Files found using 'ag -t: ' #{pattern}"
2380
+
2381
+ ## ag options :
2382
+ # -t : all text files
2383
+ # -l : print only file names
2384
+ # -a : print all files, even ignored
2385
+ system %[ag -t "#{pattern}"]
2386
+
2086
2387
  pause
2087
- files = `ag -l #{pattern}`.split("\n")
2388
+ files = `ag -lt "#{pattern}"`.split("\n")
2088
2389
  if files.empty?
2089
- perror 'No files found for #{pattern}.'
2390
+ perror "No files found for #{pattern}."
2391
+ $title = nil
2090
2392
  return
2091
2393
  end
2092
2394
  $files = files
@@ -2094,12 +2396,13 @@ def ag
2094
2396
  end
2095
2397
 
2096
2398
  def ffind
2097
- print 'Enter a file name pattern to find: '
2098
- pattern = readline
2399
+ last_line
2400
+ # print 'Enter a file name pattern to find: '
2401
+ pattern = readline '! find . -iname :'
2099
2402
  return if pattern == ''
2100
2403
 
2101
2404
  $title = "Files found using 'find' #{pattern}"
2102
- files = `find . -name "#{pattern}"`.split("\n")
2405
+ files = `find . -iname "#{pattern}"`.split("\n")
2103
2406
  if files.empty?
2104
2407
  perror 'No files found. Try adding *'
2105
2408
  else
@@ -2169,10 +2472,14 @@ def cursor_scroll_up
2169
2472
  end
2170
2473
 
2171
2474
  def cursor_dn
2475
+ $movement = true
2476
+ $old_cursor = $cursor
2172
2477
  moveto(pos + 1)
2173
2478
  end
2174
2479
 
2175
2480
  def cursor_up
2481
+ $old_cursor = $cursor
2482
+ $movement = true
2176
2483
  moveto(pos - 1)
2177
2484
  end
2178
2485
 
@@ -2191,6 +2498,7 @@ def moveto(position)
2191
2498
  # FIXME not correct, it must stop at end or correctly cycle
2192
2499
  # sta goes to 0 but cursor remains at 70
2193
2500
  # viewport.size may be wrong here, maybe should be pagesize only
2501
+ oldsta = $sta
2194
2502
  if $cursor - $sta >= $pagesize
2195
2503
  $sta += $pagesize
2196
2504
  # elsif $sta - $cursor >= $viewport.size
@@ -2200,6 +2508,8 @@ def moveto(position)
2200
2508
  # $sta = $cursor
2201
2509
  end
2202
2510
 
2511
+ $movement = nil if oldsta != $sta # we need to redraw
2512
+
2203
2513
  star = [orig, $cursor].min
2204
2514
  fin = [orig, $cursor].max
2205
2515
  return unless $visual_mode
@@ -2215,7 +2525,7 @@ def moveto(position)
2215
2525
  # $selected_files.concat $view[star..fin]
2216
2526
  add_to_selection $view[star..fin]
2217
2527
  end
2218
- ensure
2528
+ # ensure
2219
2529
  # redraw
2220
2530
  end
2221
2531
 
@@ -2233,7 +2543,6 @@ def visited?(file)
2233
2543
  return $visited_files.index file
2234
2544
  end
2235
2545
 
2236
- # TODO: can be array
2237
2546
  def add_to_selection(file)
2238
2547
  ff = file
2239
2548
  case file
@@ -2247,7 +2556,6 @@ def add_to_selection(file)
2247
2556
  end
2248
2557
  end
2249
2558
 
2250
- # TODO: can be array
2251
2559
  def remove_from_selection(file)
2252
2560
  ff = file
2253
2561
  case file
@@ -2382,10 +2690,7 @@ def create_a_file
2382
2690
  print
2383
2691
  print 'Enter file name: '
2384
2692
  str = readline
2385
- return if str == ''
2386
-
2387
- # FIXME a file with space will not create single file
2388
- # 2019-03-10 - maybe touch a file
2693
+ return if str.nil? || str == ''
2389
2694
 
2390
2695
  system %($EDITOR "#{str}")
2391
2696
  setup_terminal
@@ -2408,6 +2713,9 @@ end
2408
2713
  # prompt for scripts to execute, giving selected file names
2409
2714
  # NOTE: TODO 2019-03-21 - some scripts can output a filelist to display
2410
2715
  def scripts
2716
+ write_selected_files
2717
+ # some scripts may work with the selected_files and not want to be called
2718
+ # with filenames.
2411
2719
  title = 'Select a script'
2412
2720
  script_path = '~/.config/cetus/scripts'
2413
2721
  # TODO write selected files to a known file before calling
@@ -2429,20 +2737,22 @@ end
2429
2737
 
2430
2738
  # ------------------- view_selected_files ------------------ #
2431
2739
  def view_selected_files
2432
- require 'tempfile'
2433
- file = Tempfile.new('selected_files')
2434
- begin
2435
- $selected_files.each { |row| file.puts row }
2436
- file.flush
2437
- system "$PAGER #{file.path}"
2438
- setup_terminal
2439
- rescue
2440
- file.close
2441
- file.unlink
2442
- end
2740
+ fname = write_selected_files
2741
+ system "$PAGER #{fname}"
2742
+ setup_terminal
2443
2743
  end
2444
2744
  # ------------- end of view_selected_files --------------------------------#
2445
2745
 
2746
+ # write selected files to a file and return path
2747
+ def write_selected_files
2748
+ fname = File.join(File.dirname(CONFIG_FILE), 'selected_files')
2749
+ fname = File.expand_path(fname)
2750
+
2751
+ File.open(fname, 'w') do |file|
2752
+ $selected_files.each { |row| file.puts row }
2753
+ end
2754
+ return fname
2755
+ end
2446
2756
  ##
2447
2757
  # Editing of the User Dir List.
2448
2758
  # remove current entry from used dirs list, since we may not want some entries being there
@@ -2491,15 +2801,27 @@ end
2491
2801
  def enhance_file_list
2492
2802
  return unless $enhanced_mode
2493
2803
 
2804
+ # zshglob: M = MARK_DIRS with slash
2805
+ # zshglob: N = NULL_GLOB no error if no result, this is causing space to split
2806
+ # file sometimes for single file.
2807
+
2494
2808
  # if only one entry and its a dir
2495
2809
  # get its children and maybe the recent mod files a few
2496
-
2810
+ # FIXME: simplify condition into one
2497
2811
  if $files.size == 1
2498
2812
  # its a dir, let give the next level at least
2499
2813
  if $files.first[-1] == '/'
2500
2814
  d = $files.first
2501
- f = `zsh -c 'print -rl -- #{d}*(omM)'`.split("\n")
2815
+ # zshglob: 'om' = ordered on modification time
2816
+ # f1 = `zsh -c 'print -rl -- #{d}*(omM)'`.split("\n")
2817
+ f = get_files_by_mtime(d)
2818
+
2819
+ # @log.warn "f1:#{f1} != f:#{f} in #{d}" if f1 != f
2820
+ # order returned by zsh and ruby are different since the time is the same
2821
+
2822
+ # TODO: use ruby this throws errors if not files
2502
2823
  if f && !f.empty?
2824
+ # @log.debug "CONCAT: #{f}" if @debug_flag
2503
2825
  $files.concat f
2504
2826
  $files.concat get_important_files(d)
2505
2827
  return
@@ -2511,29 +2833,40 @@ def enhance_file_list
2511
2833
  end
2512
2834
  #
2513
2835
  # check if a ruby project dir, although it could be a backup file too,
2514
- # if so , expand lib and maby bin, put a couple recent files
2515
- #
2836
+ # if so , expand lib and maybe bin, put a couple recent files
2837
+ # FIXME: gemspec file will be same as current folder
2516
2838
  if $files.index('Gemfile') || !$files.grep(/\.gemspec/).empty?
2517
2839
  # usually the lib dir has only one file and one dir
2518
2840
  flg = false
2519
2841
  $files.concat get_important_files(Dir.pwd)
2520
2842
  if $files.index('lib/')
2521
- f = `zsh -c 'print -rl -- lib/*(om[1,5]M)'`.split("\n")
2843
+ # get first five entries by modification time
2844
+ # f1 = `zsh -c 'print -rl -- lib/*(om[1,5]MN)'`.split("\n")
2845
+ f = get_files_by_mtime('lib')&.first(5)
2846
+ # @log.warn "f1 #{f1} != #{f} in lib" if f1 != f
2522
2847
  if f && !f.empty?
2523
2848
  insert_into_list('lib/', f)
2524
2849
  flg = true
2525
2850
  end
2851
+
2852
+ # look into lib file for that project
2526
2853
  dd = File.basename(Dir.pwd)
2527
2854
  if f.index("lib/#{dd}/")
2528
- f = `zsh -c 'print -rl -- lib/#{dd}/*(om[1,5]M)'`.split("\n")
2855
+ # f1 = `zsh -c 'print -rl -- lib/#{dd}/*(om[1,5]MN)'`.split("\n")
2856
+ f = get_files_by_mtime("lib/#{dd}")&.first(5)
2857
+ # @log.warn "2756 f1 #{f1} != #{f} in lib/#{dd}" if f1 != f
2529
2858
  if f && !f.empty?
2530
2859
  insert_into_list("lib/#{dd}/", f)
2531
2860
  flg = true
2532
2861
  end
2533
2862
  end
2534
2863
  end
2864
+
2865
+ # look into bin directory and get first five modified files
2535
2866
  if $files.index('bin/')
2536
- f = `zsh -c 'print -rl -- bin/*(om[1,5]M)'`.split("\n")
2867
+ # f1 = `zsh -c 'print -rl -- bin/*(om[1,5]MN)'`.split("\n")
2868
+ f = get_files_by_mtime("bin")&.first(5)
2869
+ # @log.warn "2768 f1 #{f1} != #{f} in bin/" if f1 != f
2537
2870
  insert_into_list('bin/', f) if f && !f.empty?
2538
2871
  flg = true
2539
2872
  end
@@ -2544,20 +2877,58 @@ def enhance_file_list
2544
2877
  end
2545
2878
  return if $files.size > 15
2546
2879
 
2547
- ## first check accessed else modified will change accessed
2548
- moda = `zsh -c 'print -rn -- *(/oa[1]M)'`
2880
+ # Get most recently accessed directory
2881
+ ## NOTE: first check accessed else modified will change accessed
2882
+ # 2019-03-28 - adding NULL_GLOB breaks file name on spaces
2883
+ # print -n : don't add newline
2884
+ # zzmoda = `zsh -c 'print -rn -- *(/oa[1]MN)'`
2885
+ # zzmoda = nil if zzmoda == ''
2886
+ moda = get_most_recently_accessed_dir
2887
+ # @log.warn "Error 2663 #{zzmoda} != #{moda}" if zzmoda != moda
2549
2888
  if moda && moda != ''
2550
- modf = `zsh -c 'print -rn -- #{moda}*(oa[1]M)'`
2889
+
2890
+ # get most recently accessed file in that directory
2891
+ # NOTE: adding NULL_GLOB splits files on spaces
2892
+ # FIXME: this zsh one gave a dir instead of file.
2893
+ # zzmodf = `zsh -c 'print -rl -- #{moda}*(oa[1]M)'`.chomp
2894
+ # zzmodf = nil if zzmodf == ''
2895
+ modf = get_most_recently_accessed_file moda
2896
+ # @log.warn "Error 2670 (#{zzmodf}) != (#{modf}) gmra in #{moda} #{zzmodf.class}, #{modf.class} : Loc: #{Dir.pwd}" if zzmodf != modf
2897
+
2898
+ raise "2784: #{modf}" if modf && !File.exist?(modf)
2899
+
2551
2900
  insert_into_list moda, modf if modf && modf != ''
2552
- modm = `zsh -c 'print -rn -- #{moda}*(om[1]M)'`
2901
+
2902
+ # get most recently modified file in that directory
2903
+ # zzmodm = `zsh -c 'print -rn -- #{moda}*(om[1]M)'`.chomp
2904
+ modm = get_most_recently_modified_file moda
2905
+ # zzmodm = nil if zzmodm == ''
2906
+ # @log.debug "Error 2678 (gmrmf) #{zzmodm} != #{modm} in #{moda}" if zzmodm != modm
2907
+ raise "2792: #{modm}" if modm && !File.exist?(modm)
2908
+
2553
2909
  insert_into_list moda, modm if modm && modm != '' && modm != modf
2554
2910
  end
2555
- ## get last modified dir
2556
- modm = `zsh -c 'print -rn -- *(/om[1]M)'`
2911
+
2912
+ ## get most recently modified dir
2913
+ # zzmodm = `zsh -c 'print -rn -- *(/om[1]M)'`
2914
+ # zzmodm = nil if zzmodm == ''
2915
+ modm = get_most_recently_modified_dir
2916
+ # @log.debug "Error 2686 rmd #{zzmodm} != #{modm}" if zzmodm != modm
2917
+
2557
2918
  if modm != moda
2558
- modmf = `zsh -c 'print -rn -- #{modm}*(oa[1]M)'`
2919
+
2920
+ # get most recently accessed file in that directory
2921
+ # modmf = `zsh -c 'print -rn -- #{modm}*(oa[1]M)'`
2922
+ modmf = get_most_recently_accessed_file modm
2923
+ raise "2806: #{modmf}" if modmf && !File.exist?(modmf)
2924
+
2559
2925
  insert_into_list modm, modmf
2560
- modmf1 = `zsh -c 'print -rn -- #{modm}*(om[1]M)'`
2926
+
2927
+ # get most recently modified file in that directory
2928
+ # modmf11 = `zsh -c 'print -rn -- #{modm}*(om[1]M)'`
2929
+ modmf1 = get_most_recently_modified_file modm
2930
+ raise "2812: #{modmf1}" if modmf1 && !File.exist?(modmf1)
2931
+
2561
2932
  insert_into_list(modm, modmf1) if modmf1 != modmf
2562
2933
  else
2563
2934
  # if both are same then our options get reduced so we need to get something more
@@ -2567,7 +2938,7 @@ def enhance_file_list
2567
2938
  end
2568
2939
 
2569
2940
  # insert important files to end of $files
2570
- def insert_into_list(_dir, file)
2941
+ def insert_into_list _dir, file
2571
2942
  # ix = $files.index(dir)
2572
2943
  # raise "something wrong can find #{dir}." unless ix
2573
2944
  # $files.insert ix, *file
@@ -2579,33 +2950,96 @@ end
2579
2950
  # 2019-03-23 - not exactly clear what is happening XXX
2580
2951
  # this gets a directory (containing '/' at end)
2581
2952
  def get_important_files dir
2953
+
2582
2954
  # checks various lists like visited_files and bookmarks
2583
2955
  # to see if files from this dir or below are in it.
2584
2956
  # More to be used in a dir with few files.
2585
2957
  list = []
2586
2958
  l = dir.size + 1
2959
+
2587
2960
  # 2019-03-23 - i think we are getting the basename of the file
2588
2961
  # if it is present in the given directory XXX
2589
2962
  $visited_files.each do |e|
2590
2963
  list << e[l..-1] if e.index(dir) == 0
2591
2964
  end
2965
+
2592
2966
  # bookmarks if it starts with this directory then add it
2593
2967
  # FIXME it puts same directory cetus into the list with full path
2594
2968
  # We need to remove the base until this dir. get relative part
2595
2969
  list1 = $bookmarks.values.select do |e|
2596
2970
  e.index(dir) == 0 && e != dir
2597
2971
  end
2972
+
2598
2973
  list.concat list1
2599
2974
  list
2600
2975
  end
2601
2976
 
2977
+ def get_most_recently_accessed_dir dir='.'
2978
+ gmr dir, :directory?, :atime
2979
+ end
2980
+
2981
+ def get_most_recently_accessed_file dir='.'
2982
+ gmr dir, :file?, :atime
2983
+ end
2984
+
2985
+ def get_most_recently_modified_file dir='.'
2986
+ gmr dir, :file?, :mtime
2987
+ end
2988
+
2989
+ def get_most_recently_modified_dir dir='.'
2990
+ file = gmr dir, :directory?, :mtime
2991
+ end
2992
+
2993
+ # get most recent file or directory, based on atime or mtime
2994
+ # dir is name of directory in which to get files, default is '.'
2995
+ # type is :file? or :directory?
2996
+ # func can be :mtime or :atime or :ctime or :birthtime
2997
+ def gmr dir, type, func
2998
+ file = Dir.glob(dir + '/*')
2999
+ .select { |f| File.send(type, f) }
3000
+ .max_by { |f| File.send(func, f) }
3001
+ file = File.basename(file) + '/' if file && type == :directory?
3002
+ return file.gsub('//', '/') if file
3003
+
3004
+ nil
3005
+ end
3006
+
3007
+ # return a list of entries sorted by mtime.
3008
+ # A / is added after to directories
3009
+ def get_files_by_mtime dir='*'
3010
+ gfb dir, :mtime
3011
+ end
3012
+
3013
+ def get_files_by_atime dir='.'
3014
+ gfb dir, :atime
3015
+ end
3016
+
3017
+ # get files ordered by mtime or atime, returning latest first
3018
+ # dir is dir to get files in, default '.'
3019
+ # func can be :atime or :mtime or even :size or :ftype
3020
+ def gfb dir, func
3021
+ dir += '/*' if File.directory?(dir)
3022
+ dir = dir.gsub('//', '/')
3023
+
3024
+ # sort by time and then reverse so latest first.
3025
+ sorted_files = Dir[dir].sort_by { |f| File.send(func, f) }.reverse
3026
+
3027
+ # add slash to directories
3028
+ sorted_files = add_slash sorted_files
3029
+ return sorted_files
3030
+ # sorted_files.map do |f|
3031
+ # File.directory?(f) ? f + '/' : f
3032
+ # end
3033
+ end
2602
3034
  # set message which will be displayed in status line
2603
3035
  def message mess
2604
3036
  $message = mess
2605
3037
  end
2606
3038
 
2607
3039
  def last_line
2608
- system "tput cup #{$glines} 0"
3040
+ # system "tput cup #{$glines} 0"
3041
+ # print "\e[#{$glines};0H"
3042
+ tput_cup $glines, 0
2609
3043
  end
2610
3044
 
2611
3045
  def clear_status_line
@@ -2615,9 +3049,11 @@ def clear_status_line
2615
3049
  # %*s - set blank spaces for entire line
2616
3050
  print "\e[33;4%sm%*s" % [$status_color || '1', $gcols, " "]
2617
3051
  end
3052
+
2618
3053
  def print_on_right text
2619
3054
  sz = text.size
2620
- system "tput cup #{$glines} #{$gcols - sz -1}"
3055
+ system "tput cup #{$glines} #{$gcols - sz - 1}"
3056
+ # tput_cup $glines, $gcols - sz - 1
2621
3057
  print text
2622
3058
  end
2623
3059
 
@@ -2636,8 +3072,10 @@ def run
2636
3072
 
2637
3073
  setup_terminal
2638
3074
  config_read
3075
+ parse_ls_colors
2639
3076
 
2640
3077
  redraw true
3078
+ place_cursor
2641
3079
 
2642
3080
  # do we need this, have they changed after redraw
2643
3081
  $patt = nil
@@ -2646,18 +3084,25 @@ def run
2646
3084
  # forever loop that prints dir and takes a key
2647
3085
  loop do
2648
3086
 
2649
- # moved the printing out of here
2650
- # TODO we need to call it from where required
2651
-
2652
3087
  key = get_char
2653
3088
  resolve_key key
3089
+ if $movement && $old_cursor
3090
+ clear_status_line
3091
+ print_debug_info if @debug_flag
3092
+ # we may want to print debug info if flag is on
3093
+ place_cursor
3094
+ $movement = false
3095
+ next
3096
+ end
2654
3097
  redraw rescan?
3098
+ place_cursor
2655
3099
 
2656
3100
  break if $quitting
2657
3101
  end
2658
3102
  write_curdir
2659
3103
  puts 'bye'
2660
3104
  config_write if $writing
3105
+ @log&.close
2661
3106
  end
2662
3107
 
2663
3108
  run