cetus 0.1.29 → 0.1.32

Sign up to get free protection for your applications and to get access to all the features.
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