cetus 0.2.0 → 0.3.0
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.
- checksums.yaml +4 -4
- data/bin/cetus +467 -420
- data/cetus.gemspec +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e62bb94776477721739341ff4f03ac93390501da588709f3115a2a4c4ee3ca0f
|
4
|
+
data.tar.gz: 496df10e6710d503de9d4aa2334ac932286b1cb7c23ccc4ef57af02bb310496d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a3fc6945a92dde16f2c4dbb6dac0b2e1ffd93968caca6cfaadba473ab183b612d88c6916792fbdeffd1d5c5cbcd561c794c4588f6a76c35a9fe6dc1221a2d747
|
7
|
+
data.tar.gz: 62f47e97a020a759d14f31c6eb3c6cdefaccea57fb1a14997f535e53b6e6fd914b67b72ce0027cb4d8bf82fb652650654aa09c1742d44a70d60dc92981990b08
|
data/bin/cetus
CHANGED
@@ -8,7 +8,7 @@
|
|
8
8
|
# Author: rkumar http://github.com/rkumar/cetus/
|
9
9
|
# Date: 2013-02-17 - 17:48
|
10
10
|
# License: GPL
|
11
|
-
# Last update: 2019-
|
11
|
+
# Last update: 2019-05-14 10:21
|
12
12
|
# --------------------------------------------------------------------------- #
|
13
13
|
# cetus.rb Copyright (C) 2012-2019 rahul kumar
|
14
14
|
# == CHANGELOG
|
@@ -21,6 +21,7 @@
|
|
21
21
|
# 2019-03-22 - refactoring the code, esp run()
|
22
22
|
# 2019-04-21 - search_as_you_type
|
23
23
|
# 2019-04-22 - new search_as_you_type
|
24
|
+
# 2019-05-13 - add "\r" so WINCH can work
|
24
25
|
# == TODO
|
25
26
|
|
26
27
|
require 'readline'
|
@@ -40,82 +41,82 @@ now = Time.now.strftime("%Y-%m-%d %H:%M:%S")
|
|
40
41
|
# alias c=~/bin/cetus.rb
|
41
42
|
# c
|
42
43
|
|
43
|
-
VERSION = '0.
|
44
|
+
VERSION = '0.3.0.0'
|
44
45
|
CONFIG_PATH = ENV['XDG_CONFIG_HOME'] || File.join(ENV['HOME'], '.config')
|
45
46
|
CONFIG_FILE = "#{CONFIG_PATH}/cetus/conf.yml"
|
46
47
|
|
48
|
+
# NOTE: if changing binding to symbol, there are many places where searched as
|
49
|
+
# string.
|
47
50
|
@bindings = {
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
51
|
+
'`' => 'main_menu',
|
52
|
+
'=' => 'toggle_menu',
|
53
|
+
'%' => 'filter_menu',
|
54
|
+
'M-s' => 'selection_menu',
|
55
|
+
'M-o' => 'order_menu',
|
56
|
+
'ENTER' => 'select_current',
|
57
|
+
'C-p' => 'page_current',
|
58
|
+
'C-e' => 'edit_current',
|
59
|
+
'C-o' => 'open_current',
|
60
|
+
'C-s' => 'toggle_select',
|
61
|
+
'C-r' => 'reduce',
|
62
|
+
'C-g' => 'debug_vars',
|
63
|
+
'*' => 'toggle_multiple_selection',
|
64
|
+
'M-a' => 'select_all',
|
65
|
+
'M-A' => 'unselect_all',
|
66
|
+
'!' => 'execute',
|
67
|
+
',' => 'goto_parent_dir',
|
68
|
+
'~' => 'goto_home_dir',
|
69
|
+
'-' => 'goto_previous_dir',
|
70
|
+
'+' => 'goto_dir', # 2019-03-07 - TODO: change binding
|
71
|
+
'.' => 'pop_dir',
|
72
|
+
':' => 'subcommand',
|
73
|
+
"'" => 'goto_bookmark',
|
74
|
+
'"' => 'file_starting_with',
|
75
|
+
'/' => 'filter_files_by_pattern',
|
76
|
+
'M-p' => 'prev_page',
|
77
|
+
'M-n' => 'next_page',
|
78
|
+
'PgUp' => 'prev_page',
|
79
|
+
'PgDn' => 'next_page',
|
80
|
+
'Home' => 'goto_top',
|
81
|
+
'End' => 'goto_end',
|
82
|
+
'SPACE' => 'next_page:Page Down',
|
83
|
+
'M-f' => 'select_from_visited_files',
|
84
|
+
'M-d' => 'select_from_used_dirs',
|
85
|
+
'M-b' => 'bookmark_menu',
|
86
|
+
'M-m' => 'create_bookmark',
|
87
|
+
'M-M' => 'view_bookmarks',
|
88
|
+
'C-c' => 'escape',
|
89
|
+
'ESCAPE' => 'escape',
|
90
|
+
'TAB' => 'views',
|
91
|
+
'C-i' => 'views',
|
88
92
|
# '?' => 'dirtree',
|
89
|
-
|
93
|
+
'D' => 'delete_file',
|
90
94
|
# 'M' => 'file_actions most',
|
91
|
-
|
92
|
-
|
95
|
+
'M' => 'move_instant',
|
96
|
+
'q' => 'quit_command', # was Q now q 2019-03-04 -
|
93
97
|
# "RIGHT" => "column_next",
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
'M--' => 'columns_incdec -1: reduce column width',
|
100
|
-
'M-+' => 'columns_incdec 1: increase column width',
|
101
|
-
'
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
'DOWN' => 'cursor_dn',
|
98
|
+
'RIGHT' => 'select_current', # changed 2018-03-12 - for faster navigation
|
99
|
+
'LEFT' => 'goto_parent_dir', # changed on 2018-03-12 - earlier column_next 1
|
100
|
+
']' => 'column_next: goto next column',
|
101
|
+
'[' => 'column_next 1: goto previous column',
|
102
|
+
'C-x' => 'file_actions',
|
103
|
+
# 'M--' => 'columns_incdec -1: reduce column width',
|
104
|
+
# 'M-+' => 'columns_incdec 1: increase column width',
|
105
|
+
# 'L' => 'command_file Page n less: Page current file',
|
106
|
+
'C-d' => 'cursor_scroll_dn',
|
107
|
+
'C-b' => 'cursor_scroll_up',
|
108
|
+
'UP' => 'cursor_up',
|
109
|
+
'DOWN' => 'cursor_dn',
|
107
110
|
'C-SPACE' => 'toggle_visual_mode',
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
'S-F2' => 'tree'
|
118
|
-
|
111
|
+
'@' => 'scripts',
|
112
|
+
'#' => 'generators',
|
113
|
+
'?' => 'print_help',
|
114
|
+
'F1' => 'print_help',
|
115
|
+
'F2' => 'child_dirs',
|
116
|
+
'F3' => 'dirtree',
|
117
|
+
'F4' => 'tree',
|
118
|
+
'S-F1' => 'dirtree',
|
119
|
+
'S-F2' => 'tree'
|
119
120
|
}
|
120
121
|
|
121
122
|
## clean this up a bit, copied from shell program and macro'd
|
@@ -327,7 +328,6 @@ IDX.freeze
|
|
327
328
|
@stact = 0 # used when panning a folder to next column
|
328
329
|
# @editor_mode = true
|
329
330
|
@editor_mode = false # changed 2018-03-12 - so we start in pager mode
|
330
|
-
@enhanced_mode = true
|
331
331
|
@visual_block_start = nil
|
332
332
|
PAGER_COMMAND = {
|
333
333
|
text: 'most',
|
@@ -338,7 +338,11 @@ PAGER_COMMAND = {
|
|
338
338
|
}.freeze
|
339
339
|
@dir_position = {}
|
340
340
|
@cursor_movement = @old_cursor = nil # cursor movement has happened only, don't repaint
|
341
|
+
@keys_to_clear = nil # in how many strokes should message be cleared, set later.
|
342
|
+
# ---------------------------------------------
|
341
343
|
## FLAGS
|
344
|
+
@visual_mode = false
|
345
|
+
@enhanced_mode = true
|
342
346
|
@multiple_selection = true # single select
|
343
347
|
@group_directories = :first
|
344
348
|
# truncate long filenames from :right, :left or :center.
|
@@ -347,39 +351,41 @@ PAGER_COMMAND = {
|
|
347
351
|
@display_file_stats = true
|
348
352
|
@selected_files_fullpath_flag = false
|
349
353
|
@selected_files_escaped_flag = false
|
350
|
-
@keys_to_clear = nil
|
351
354
|
@ignore_case = true
|
352
355
|
@highlight_row_flag = true # false
|
353
356
|
@debug_flag = false
|
357
|
+
@date_func = :mtime # which date to display in long listing.
|
354
358
|
|
355
359
|
# See toggle_value
|
356
360
|
# we need to set these on startup
|
357
361
|
@toggles = {
|
358
362
|
ignore_case: true,
|
359
|
-
# group_directories: true,
|
360
363
|
long_listing: false,
|
361
364
|
enhanced_mode: true,
|
362
365
|
visual_mode: false,
|
363
366
|
display_file_stats: true,
|
364
|
-
|
365
|
-
|
367
|
+
selected_files_fullpath_flag: false,
|
368
|
+
selected_files_escaped_flag: false,
|
366
369
|
multiple_selection: true,
|
367
370
|
editor_mode: false,
|
368
371
|
selection_mode: false, # typing hint adds to selection, does not open
|
369
372
|
debug_flag: false,
|
370
|
-
|
373
|
+
filename_status_line: true,
|
371
374
|
instant_search: true,
|
372
375
|
highlight_row_flag: @highlight_row_flag
|
373
376
|
}
|
377
|
+
# These are flags that have multiple values.
|
374
378
|
# var is name of variable to be set
|
375
379
|
@options = {
|
376
380
|
truncate_from: { current: :center, values: %i[left right center], var: :truncate_from },
|
377
381
|
group_directories: { current: :first, values: %i[first none last], var: :group_directories },
|
378
|
-
show_hidden: { current: nil, values: [nil, 'D'], var: :hidden }
|
382
|
+
show_hidden: { current: nil, values: [nil, 'D'], var: :hidden },
|
383
|
+
date_func: { current: :mtime, values: [:mtime, :atime, :ctime], var: :date_func }
|
379
384
|
}
|
380
385
|
|
381
386
|
## ----------------- CONSTANTS ----------------- ##
|
382
387
|
GMARK = '*'
|
388
|
+
VMARK = '+'
|
383
389
|
CURMARK = '>'
|
384
390
|
MSCROLL = 10
|
385
391
|
SPACE = ' '
|
@@ -436,7 +442,6 @@ CURSOR_COLOR = REVERSE
|
|
436
442
|
## --------------------------------------------- ##
|
437
443
|
|
438
444
|
@patt = nil
|
439
|
-
@ignorecase = true
|
440
445
|
@quitting = false
|
441
446
|
@modified = @writing = false
|
442
447
|
@visited_files = []
|
@@ -450,22 +455,20 @@ CURSOR_COLOR = REVERSE
|
|
450
455
|
@viewctr = 0
|
451
456
|
## sta is where view (viewport) begins, cursor is current row/file
|
452
457
|
@sta = @cursor = 0
|
453
|
-
@visual_mode = false
|
454
458
|
@status_color = 4 # status line, can be 2 3 4 5 6
|
455
459
|
@status_color_right = 8 # status line right part
|
456
460
|
|
457
461
|
# Menubar on top of screen
|
458
462
|
@help = "#{BOLD}?#{BOLD_OFF} Help #{BOLD}`#{BOLD_OFF} Menu #{BOLD}!#{BOLD_OFF} Execute #{BOLD}=#{BOLD_OFF} Toggle #{BOLD}C-x#{BOLD_OFF} File Actions #{BOLD}q#{BOLD_OFF} Quit "
|
459
463
|
|
460
|
-
# ------------------- read_directory ------------------ #
|
461
464
|
def read_directory
|
462
465
|
rescan_required false
|
463
466
|
|
464
467
|
@filterstr ||= 'M' # XXX can we remove from here
|
468
|
+
@current_dir ||= Dir.pwd
|
465
469
|
list_files
|
466
470
|
|
467
471
|
group_directories_first
|
468
|
-
# return unless cget(:enhanced_mode)
|
469
472
|
return unless @enhanced_mode
|
470
473
|
|
471
474
|
enhance_file_list
|
@@ -505,8 +508,7 @@ def list_files dir='*', sorto=@sorto, hidden=@hidden, _filter=@filterstr
|
|
505
508
|
Dir.glob(dir)
|
506
509
|
end
|
507
510
|
|
508
|
-
# WARN: crashes on a
|
509
|
-
# HACK: use first files return value for bad link
|
511
|
+
# WARN: crashes on a deadlink since no mtime
|
510
512
|
if func
|
511
513
|
sorted_files = sorted_files.sort_by do |f|
|
512
514
|
if File.exist? f
|
@@ -520,16 +522,16 @@ def list_files dir='*', sorto=@sorto, hidden=@hidden, _filter=@filterstr
|
|
520
522
|
sorted_files.sort! { |w1, w2| w1.casecmp(w2) } if func == :path && @ignore_case
|
521
523
|
|
522
524
|
# zsh gives mtime sort with latest first, ruby gives latest last
|
523
|
-
sorted_files.reverse! if sorto[0] == 'O'
|
525
|
+
sorted_files.reverse! if sorto && sorto[0] == 'O'
|
524
526
|
|
525
527
|
# add slash to directories
|
526
528
|
sorted_files = add_slash sorted_files
|
527
529
|
# return sorted_files
|
528
530
|
@files = sorted_files
|
531
|
+
calculate_bookmarks_for_dir # we don't want to override those set by others
|
529
532
|
end
|
530
|
-
# ------------- end of read_directory --------------------------------#
|
531
533
|
|
532
|
-
# Deal with
|
534
|
+
# Deal with deadlinks.
|
533
535
|
def sys_stat func, file
|
534
536
|
return unless File.symlink? file
|
535
537
|
|
@@ -542,7 +544,6 @@ end
|
|
542
544
|
# ------------------- create_viewport ------------------ #
|
543
545
|
def create_viewport
|
544
546
|
@view = if @patt
|
545
|
-
# if cget(:ignore_case)
|
546
547
|
if @ignore_case
|
547
548
|
@files.grep(/#{@patt}/i)
|
548
549
|
else
|
@@ -562,13 +563,21 @@ def create_viewport
|
|
562
563
|
|
563
564
|
# viewport are the files that are visible, subset of view
|
564
565
|
@viewport = @view[@sta, @pagesize]
|
566
|
+
@vps = @viewport.size
|
565
567
|
end
|
566
568
|
# ------------- end of create_viewport --------------------------------#
|
567
569
|
|
570
|
+
# FIXME: need to update when bookmark created or deleted
|
571
|
+
def calculate_bookmarks_for_dir
|
572
|
+
|
573
|
+
bm = @bookmarks.select { |k, v| v == @current_dir }.keys.join(',')
|
574
|
+
bm = " ('#{bm})" if bm && bm != ''
|
575
|
+
@bm = bm
|
576
|
+
end
|
568
577
|
# ------------------- print_title ------------------ #
|
569
578
|
def print_title
|
570
579
|
# print help line and version
|
571
|
-
print "#{GREEN}#{@help} #{BLUE}cetus #{VERSION}#{CLEAR}\n"
|
580
|
+
print "\r#{GREEN}#{@help} #{BLUE}cetus #{VERSION}#{CLEAR}\n"
|
572
581
|
@current_dir ||= Dir.pwd
|
573
582
|
|
574
583
|
# print 1 of n files, sort order, filter etc details
|
@@ -576,16 +585,11 @@ def print_title
|
|
576
585
|
|
577
586
|
# Add bookmark next to name of dir, if exists
|
578
587
|
# FIXME This should not happen in other listings like find selected files etc
|
579
|
-
# bm = @bookmarks.key(Dir.pwd)
|
580
|
-
# Display multiple bookmarks. Can be calculated on changing directory ?
|
581
|
-
bm = @bookmarks.select { |k, v| v == @current_dir }.keys.join(',')
|
582
|
-
bm = " ('#{bm})" if bm && bm != ''
|
583
588
|
|
584
|
-
fin = @sta + @
|
589
|
+
fin = @sta + @vps
|
585
590
|
fl = @view.size
|
586
591
|
|
587
592
|
# fix count of entries so separator and enhanced entries don't show up
|
588
|
-
# if cget(:enhanced_mode)
|
589
593
|
if @enhanced_mode
|
590
594
|
ix = @viewport.index SEPARATOR
|
591
595
|
fin = @sta + ix if ix
|
@@ -594,15 +598,16 @@ def print_title
|
|
594
598
|
fl = ix if ix
|
595
599
|
end
|
596
600
|
|
597
|
-
t = fl.zero? ? "#{@title}#{bm} No files." : "#{@title}#{bm} #{@sta + 1} to #{fin} of #{fl} #{@sorto} F:#{@filterstr}"
|
601
|
+
t = fl.zero? ? "#{@title}#{@bm} No files." : "#{@title}#{@bm} #{@sta + 1} to #{fin} of #{fl} #{@sorto} F:#{@filterstr}"
|
598
602
|
|
599
603
|
# don't exceed columns while printing
|
600
604
|
t = t[t.size - @gcols..-1] if t.size >= @gcols
|
601
605
|
|
602
|
-
print "#{BOLD}#{t}#{CLEAR}"
|
606
|
+
print "\r#{BOLD}#{t}#{CLEAR}"
|
603
607
|
|
604
608
|
tput_cup(-1, @gcols - @hk.length - 2)
|
605
609
|
puts '[' + @hk + ']'
|
610
|
+
print "\r"
|
606
611
|
|
607
612
|
print "#{CURSOR_COLOR}EMPTY#{CLEAR}" if fl == 0
|
608
613
|
end
|
@@ -623,7 +628,6 @@ def status_line
|
|
623
628
|
# Print the filename at the right side of the status line
|
624
629
|
# sometimes due to search, there is no file
|
625
630
|
if cf
|
626
|
-
# if cget(:debug_flag)
|
627
631
|
if @debug_flag
|
628
632
|
print_debug_info cf
|
629
633
|
else
|
@@ -635,7 +639,6 @@ def status_line
|
|
635
639
|
# patt and message are together, no gap, why not ? 2019-04-08 -
|
636
640
|
if @patt && @patt != ''
|
637
641
|
patt = "[/#{@patt}" + ']' # to get unfrozen string
|
638
|
-
# patt[-1] = '/i]' if cget(:ignore_case)
|
639
642
|
patt[-1] = '/i]' if @ignore_case
|
640
643
|
end
|
641
644
|
# bring cursor to start of line
|
@@ -648,12 +651,12 @@ def status_line
|
|
648
651
|
end
|
649
652
|
|
650
653
|
def print_debug_info cf=current_file
|
651
|
-
print_on_right "len:#{cf.length}/#{@temp_wid} = #{@sta},#{@cursor},#{@stact},#{@
|
654
|
+
print_on_right "len:#{cf.length}/#{@temp_wid} = #{@sta},#{@cursor},#{@stact},#{@vps},#{@grows} | #{cf}"
|
652
655
|
end
|
653
656
|
|
654
657
|
def print_filename_status_line cf=current_file
|
655
658
|
if @display_file_stats
|
656
|
-
ff = if cf
|
659
|
+
ff = if cf.start_with?("~/")
|
657
660
|
File.expand_path(cf)
|
658
661
|
else
|
659
662
|
cf
|
@@ -666,8 +669,9 @@ def print_filename_status_line cf=current_file
|
|
666
669
|
date_format(File.stat(ff).mtime)
|
667
670
|
end
|
668
671
|
end
|
672
|
+
mtime = "| #{mtime} |" if mtime
|
669
673
|
# print size and mtime only if more data requested.
|
670
|
-
print_on_right "
|
674
|
+
print_on_right "#{mtime} #{cf}".rjust(40)
|
671
675
|
end
|
672
676
|
|
673
677
|
# should we do a read of the dir
|
@@ -698,7 +702,7 @@ def draw_directory
|
|
698
702
|
buff = columnate @viewport, @grows
|
699
703
|
|
700
704
|
# starts printing array on line 3
|
701
|
-
buff.each { |line| print line, "\n" }
|
705
|
+
buff.each { |line| print "\r", line, "\n" }
|
702
706
|
print
|
703
707
|
|
704
708
|
status_line
|
@@ -717,16 +721,14 @@ end
|
|
717
721
|
# if CLEAR then we use original colors from get_formatted_filename.
|
718
722
|
# Otherwise we need to use CURSOR_COLOR in place of current color
|
719
723
|
def place_cursor color=CURSOR_COLOR
|
720
|
-
vps = @viewport.size
|
721
|
-
|
722
724
|
# empty directory
|
723
|
-
if vps == 0
|
725
|
+
if @vps == 0
|
724
726
|
tput_cup 0, 0
|
725
727
|
return
|
726
728
|
end
|
727
729
|
|
728
730
|
c = @cursor - @sta
|
729
|
-
wid = get_width vps, @grows
|
731
|
+
wid = get_width @vps, @grows
|
730
732
|
if c < @grows
|
731
733
|
rows = c
|
732
734
|
cols = 0
|
@@ -790,6 +792,7 @@ def resolve_binding key
|
|
790
792
|
# split into binding and args
|
791
793
|
x = x.split if x
|
792
794
|
if x
|
795
|
+
redraw_required # trying here so default 2019-04-26 -
|
793
796
|
binding = x.shift
|
794
797
|
args = x
|
795
798
|
send(binding, *args) if binding
|
@@ -849,6 +852,10 @@ end
|
|
849
852
|
|
850
853
|
def set_bookmark key, dir=Dir.pwd
|
851
854
|
@bookmarks[key] = dir
|
855
|
+
calculate_numeric_hotkeys
|
856
|
+
end
|
857
|
+
|
858
|
+
def calculate_numeric_hotkeys
|
852
859
|
@hk = @bookmarks.select { |k, _| k.match?(/[0-9]/) }.keys.sort.join('')
|
853
860
|
end
|
854
861
|
|
@@ -878,8 +885,9 @@ def readable_file_size size, precision
|
|
878
885
|
end
|
879
886
|
|
880
887
|
## format date for file given stat
|
881
|
-
def date_format
|
882
|
-
|
888
|
+
def date_format tim
|
889
|
+
# tim.strftime '%Y/%m/%d %H:%M:%S'
|
890
|
+
tim.strftime '%Y/%m/%d %H:%M'
|
883
891
|
end
|
884
892
|
|
885
893
|
##
|
@@ -936,7 +944,7 @@ def truncate_formatted_filename f, unformatted_len, wid
|
|
936
944
|
|
937
945
|
when :right
|
938
946
|
# FIXME: 2019-04-23 - do we need the control code at end ??
|
939
|
-
f[0..wid - 3] +
|
947
|
+
f[0..wid - 3] + '$ '
|
940
948
|
|
941
949
|
when :center
|
942
950
|
|
@@ -957,8 +965,6 @@ def truncate_formatted_filename f, unformatted_len, wid
|
|
957
965
|
sindex = f.index ' '
|
958
966
|
# 4 = 2 for literals, 2 to get ahead of sindex+1
|
959
967
|
f[0..sindex + 1] + '<' + f[sindex + 4 + excess..-1] + ' '
|
960
|
-
# in some cases 29/32 we are actually overlapping 2 parts above
|
961
|
-
# same for 34/32
|
962
968
|
end
|
963
969
|
return f
|
964
970
|
end
|
@@ -971,13 +977,15 @@ def get_formatted_filename ix, wid
|
|
971
977
|
|
972
978
|
ind = get_shortcut(ix)
|
973
979
|
mark = get_mark(f)
|
980
|
+
# make visited files bold
|
981
|
+
bcolor = BOLD if mark == VMARK
|
974
982
|
# Handle separator before enhanced file list.
|
975
983
|
# We do lose a shortcut
|
976
984
|
ind = cur = mark = '-' if f == SEPARATOR
|
977
985
|
prefix = "#{ind}#{mark}#{cur}"
|
978
986
|
|
979
|
-
|
980
|
-
color
|
987
|
+
color = color_for(f)
|
988
|
+
color = "#{bcolor}#{color}"
|
981
989
|
|
982
990
|
f = format_long_listing(f) if @long_listing
|
983
991
|
|
@@ -989,9 +997,9 @@ def get_formatted_filename ix, wid
|
|
989
997
|
unformatted_len = f.length
|
990
998
|
if unformatted_len > wid
|
991
999
|
# take the excess out from the center on both sides
|
992
|
-
|
1000
|
+
|
993
1001
|
f = truncate_formatted_filename(f, unformatted_len, wid)
|
994
|
-
f = "#{color}#{f}#{CLEAR}" if color
|
1002
|
+
f = "#{color}#{f}#{CLEAR}" if color # NOTE clear not added if not color
|
995
1003
|
|
996
1004
|
elsif unformatted_len < wid
|
997
1005
|
|
@@ -1016,14 +1024,22 @@ def get_mark file
|
|
1016
1024
|
return SPACE if @selected_files.empty? && @visited_files.empty?
|
1017
1025
|
|
1018
1026
|
@current_dir ||= Dir.pwd
|
1019
|
-
|
1027
|
+
# this crashes on filename that starts with a ~. 2019-05-04 -
|
1028
|
+
fullname = if file.start_with?("~/")
|
1029
|
+
File.expand_path(file)
|
1030
|
+
else
|
1031
|
+
File.join(@current_dir, file)
|
1032
|
+
end
|
1020
1033
|
|
1021
1034
|
return GMARK if selected?(fullname)
|
1022
|
-
return
|
1035
|
+
return VMARK if visited? fullname
|
1036
|
+
|
1037
|
+
# 2019-04-27 - this boldfaces visited files, but should only be done if color
|
1038
|
+
# otherwise it will overflow.
|
1039
|
+
# This was nice but it messes with width in multiple columns truncate
|
1040
|
+
# return "#{BOLD}+" if visited? fullname
|
1023
1041
|
|
1024
1042
|
SPACE
|
1025
|
-
# cur = CURMARK if ix + @sta == @cursor # 2019-03-29 - removed reduced calls
|
1026
|
-
# NOTE seems like f and ary[ix] are the same
|
1027
1043
|
end
|
1028
1044
|
|
1029
1045
|
def format_long_listing f
|
@@ -1034,7 +1050,7 @@ def format_long_listing f
|
|
1034
1050
|
if File.exist? f
|
1035
1051
|
stat = File.stat(f)
|
1036
1052
|
elsif f[0] == '~'
|
1037
|
-
stat = File.stat(
|
1053
|
+
stat = File.stat(expand_path(f))
|
1038
1054
|
elsif File.symlink?(f)
|
1039
1055
|
# dead link
|
1040
1056
|
stat = File.lstat(f)
|
@@ -1043,10 +1059,11 @@ def format_long_listing f
|
|
1043
1059
|
last = f[-1]
|
1044
1060
|
stat = File.stat(f.chop) if last == ' ' || last == '@' || last == '*'
|
1045
1061
|
end
|
1062
|
+
# TODO select date_func from toggles
|
1046
1063
|
|
1047
1064
|
f = format('%10s %s %s',
|
1048
1065
|
readable_file_size(stat.size, 1),
|
1049
|
-
date_format(stat.
|
1066
|
+
date_format(stat.send(@date_func)),
|
1050
1067
|
f)
|
1051
1068
|
|
1052
1069
|
rescue StandardError => e
|
@@ -1060,7 +1077,8 @@ end
|
|
1060
1077
|
# determine color for a filename based on extension, then pattern, then filetype
|
1061
1078
|
def color_for f
|
1062
1079
|
return nil if f == SEPARATOR
|
1063
|
-
|
1080
|
+
|
1081
|
+
fname = f.start_with?("~/") ? File.expand_path(f) : f
|
1064
1082
|
|
1065
1083
|
extension = File.extname(fname)
|
1066
1084
|
color = @ls_color[extension]
|
@@ -1155,6 +1173,7 @@ def parse_ls_colors
|
|
1155
1173
|
# @log.debug "COLOR: ftype #{patt}"
|
1156
1174
|
end
|
1157
1175
|
end
|
1176
|
+
# @log.debug "LSP: #{@ls_pattern.values}"
|
1158
1177
|
end
|
1159
1178
|
|
1160
1179
|
## select file based on key pressed
|
@@ -1181,20 +1200,27 @@ end
|
|
1181
1200
|
|
1182
1201
|
## toggle selection state of file
|
1183
1202
|
def toggle_select f=current_file
|
1184
|
-
if selected?
|
1203
|
+
# if selected? File.join(@current_dir, current_file)
|
1204
|
+
if selected? expand_path(current_file)
|
1185
1205
|
remove_from_selection f
|
1186
1206
|
else
|
1187
1207
|
@selected_files.clear unless @multiple_selection
|
1188
1208
|
add_to_selection f
|
1189
1209
|
end
|
1190
1210
|
message "#{@selected_files.count} files selected. "
|
1191
|
-
|
1192
|
-
|
1211
|
+
|
1212
|
+
# 2019-04-24 - trying to avoid redrawing entire screen.
|
1213
|
+
# If multiple_selection then current selection is either added or removed,
|
1214
|
+
# nothing else changes, so we redraw only if not multi. Also place cursor
|
1215
|
+
# i.e. redraw current row if mutliple selection
|
1216
|
+
if @multiple_selection
|
1217
|
+
redraw_required false
|
1218
|
+
place_cursor
|
1219
|
+
end
|
1193
1220
|
end
|
1194
1221
|
|
1195
1222
|
# allow single or multiple selection with C-s key
|
1196
1223
|
def toggle_multiple_selection
|
1197
|
-
# @multiple_selection = !@multiple_selection
|
1198
1224
|
toggle_value :multiple_selection
|
1199
1225
|
end
|
1200
1226
|
|
@@ -1202,7 +1228,7 @@ end
|
|
1202
1228
|
def open_file f
|
1203
1229
|
return unless f
|
1204
1230
|
|
1205
|
-
f = File.expand_path(f) if f
|
1231
|
+
f = File.expand_path(f) if f.start_with?("~/")
|
1206
1232
|
unless File.exist? f
|
1207
1233
|
# this happens if we use (T) in place of (M)
|
1208
1234
|
# it places a space after normal files and @ and * which borks commands
|
@@ -1228,7 +1254,7 @@ def open_file f
|
|
1228
1254
|
system(comm.to_s)
|
1229
1255
|
setup_terminal
|
1230
1256
|
# XXX maybe use absolute_path instead of hardcoding
|
1231
|
-
f =
|
1257
|
+
f = expand_path(f)
|
1232
1258
|
@visited_files.insert(0, f)
|
1233
1259
|
push_used_dirs @current_dir
|
1234
1260
|
else
|
@@ -1248,13 +1274,13 @@ end
|
|
1248
1274
|
def edit_current
|
1249
1275
|
command = ENV['EDITOR'] || ENV['VISUAL'] || 'vim'
|
1250
1276
|
run_on_current command
|
1251
|
-
@visited_files.insert(0, current_file)
|
1277
|
+
@visited_files.insert(0, expand_path(current_file))
|
1252
1278
|
end
|
1253
1279
|
|
1254
1280
|
def open_current
|
1255
1281
|
opener = /darwin/.match?(RUBY_PLATFORM) ? 'open' : 'xdg-open'
|
1256
1282
|
run_on_current opener
|
1257
|
-
@visited_files.insert(0, current_file)
|
1283
|
+
@visited_files.insert(0, expand_path(current_file))
|
1258
1284
|
end
|
1259
1285
|
|
1260
1286
|
# run given command on current file
|
@@ -1262,7 +1288,7 @@ def run_on_current command
|
|
1262
1288
|
f = current_file
|
1263
1289
|
return unless f
|
1264
1290
|
|
1265
|
-
f =
|
1291
|
+
f = expand_path(f)
|
1266
1292
|
return unless File.readable?(f)
|
1267
1293
|
|
1268
1294
|
f = Shellwords.escape(f)
|
@@ -1272,6 +1298,7 @@ def run_on_current command
|
|
1272
1298
|
system(comm.to_s)
|
1273
1299
|
push_used_dirs
|
1274
1300
|
setup_terminal
|
1301
|
+
redraw_required
|
1275
1302
|
end
|
1276
1303
|
|
1277
1304
|
## run system command on given file/s
|
@@ -1321,7 +1348,7 @@ def change_dir f
|
|
1321
1348
|
@visited_dirs.insert(0, Dir.pwd)
|
1322
1349
|
save_dir_pos
|
1323
1350
|
|
1324
|
-
f =
|
1351
|
+
f = expand_path(f)
|
1325
1352
|
Dir.chdir f
|
1326
1353
|
@current_dir = Dir.pwd # 2019-04-24 - earlier was in post_cd but too late
|
1327
1354
|
read_directory
|
@@ -1344,7 +1371,6 @@ end
|
|
1344
1371
|
## clear sort order and refresh listing, used typically if you are in some view
|
1345
1372
|
# such as visited dirs or files
|
1346
1373
|
def escape
|
1347
|
-
@sorto = nil
|
1348
1374
|
@sorto = @default_sort_order
|
1349
1375
|
@viewctr = 0
|
1350
1376
|
@title = nil
|
@@ -1365,7 +1391,6 @@ end
|
|
1365
1391
|
|
1366
1392
|
# put directories first, then files
|
1367
1393
|
def group_directories_first
|
1368
|
-
# return if cget(:group_directories) == :none
|
1369
1394
|
return if @group_directories == :none
|
1370
1395
|
|
1371
1396
|
files = @files
|
@@ -1388,6 +1413,7 @@ end
|
|
1388
1413
|
## select all entries (files and directories)
|
1389
1414
|
def select_all
|
1390
1415
|
dir = Dir.pwd
|
1416
|
+
# check this out with visited_files TODO FIXME
|
1391
1417
|
@selected_files = @view.map { |file| File.join(dir, file) }
|
1392
1418
|
message "#{@selected_files.count} files selected."
|
1393
1419
|
end
|
@@ -1411,7 +1437,7 @@ def goto_dir
|
|
1411
1437
|
ensure
|
1412
1438
|
# print "\e[?25l"
|
1413
1439
|
end
|
1414
|
-
f =
|
1440
|
+
f = expand_path(path)
|
1415
1441
|
unless File.directory? f
|
1416
1442
|
## check for env variable
|
1417
1443
|
tmp = ENV[path]
|
@@ -1464,14 +1490,18 @@ def goto_home_dir
|
|
1464
1490
|
change_dir ENV['HOME']
|
1465
1491
|
end
|
1466
1492
|
|
1467
|
-
# Goes to directory bookmarked with number or
|
1468
|
-
# If lower case character given, then go to first file starting with char.
|
1493
|
+
# Goes to directory bookmarked with number or char.
|
1469
1494
|
def goto_bookmark key=nil
|
1470
1495
|
unless key
|
1471
1496
|
clear_last_line
|
1472
|
-
print 'Enter bookmark char: '
|
1497
|
+
print 'Enter bookmark char (? to view): '
|
1473
1498
|
key = get_char
|
1474
1499
|
end
|
1500
|
+
if key == '?'
|
1501
|
+
view_bookmarks
|
1502
|
+
return
|
1503
|
+
end
|
1504
|
+
|
1475
1505
|
d = @bookmarks[key]
|
1476
1506
|
if d
|
1477
1507
|
change_dir d
|
@@ -1481,10 +1511,10 @@ def goto_bookmark key=nil
|
|
1481
1511
|
end
|
1482
1512
|
|
1483
1513
|
## take regex from user, to run on files on screen, user can filter file names
|
1484
|
-
def
|
1485
|
-
@title = 'Search Results: (Press
|
1514
|
+
def filter_files_by_pattern
|
1515
|
+
@title = 'Search Results: (Press Esc to cancel)'
|
1486
1516
|
@mode = 'SEARCH'
|
1487
|
-
if
|
1517
|
+
if @toggles[:instant_search]
|
1488
1518
|
search_as_you_type
|
1489
1519
|
else
|
1490
1520
|
@patt = readline '/'
|
@@ -1512,6 +1542,7 @@ end
|
|
1512
1542
|
def goto_top
|
1513
1543
|
@sta = @cursor = 0
|
1514
1544
|
@old_cursor = nil
|
1545
|
+
redraw_required
|
1515
1546
|
end
|
1516
1547
|
|
1517
1548
|
# goto end / bottom
|
@@ -1519,32 +1550,40 @@ def goto_end
|
|
1519
1550
|
@cursor = @view.size - 1
|
1520
1551
|
@sta = @view.size - @pagesize
|
1521
1552
|
@old_cursor = nil
|
1553
|
+
redraw_required
|
1522
1554
|
end
|
1523
1555
|
|
1524
1556
|
def print_help
|
1525
1557
|
page_with_tempfile do |file|
|
1526
|
-
file.puts
|
1558
|
+
file.puts %(
|
1559
|
+
#{REVERSE} HELP #{CLEAR}
|
1527
1560
|
|
1528
|
-
|
1529
|
-
|
1530
|
-
file.puts 'Ctrl-s to select file under cursor. * for multiple select.'
|
1531
|
-
file.puts 'Ctrl-Space: Enter and exit Visual Selection mode'
|
1532
|
-
file.puts 'Ctrl-x: file actions for selected files, or file under cursor'
|
1533
|
-
file.puts '1-9: bookmark a dir, and go to it.'
|
1561
|
+
Tilde (`) is the main menu key. Many important operations are
|
1562
|
+
available through it, or through its sub-menus.
|
1534
1563
|
|
1535
|
-
file
|
1536
|
-
file.
|
1564
|
+
To open a file or dir, press a-z A-Z (shortcut on left of file)
|
1565
|
+
Ctrl-s to select file under cursor. * for multiple select.
|
1566
|
+
Ctrl-Space: Enter and exit Visual Selection mode
|
1567
|
+
Ctrl-x: file actions for selected files, or file under cursor
|
1568
|
+
1-9: bookmark a dir, and go to it.
|
1569
|
+
|
1570
|
+
Use left and right arrows to move through directories
|
1571
|
+
|
1572
|
+
)
|
1537
1573
|
ary = []
|
1538
1574
|
# 2019-03-19 - if : then show text after colon
|
1539
1575
|
@bindings.each_pair do |k, v|
|
1540
1576
|
vv = v.tr('_', ' ')
|
1541
1577
|
vv = vv.split(':')[1].strip if vv.include?(':')
|
1542
|
-
ary.push "#{k.ljust(7)} => #{vv}"
|
1578
|
+
ary.push " #{k.ljust(7)} => #{vv}"
|
1543
1579
|
end
|
1544
1580
|
# FIXME: this works but not properly when long_listing is true.
|
1545
1581
|
# We should avoid using columnate as there are several file related things.
|
1546
|
-
|
1582
|
+
# next line no longer working.
|
1583
|
+
# ary = columnate ary, (ary.size / 2) + 1
|
1547
1584
|
ary.each { |line| file.puts line }
|
1585
|
+
# TODO: 2019-04-26 - add other hashes for other menus here ?
|
1586
|
+
# but those hashes are not available
|
1548
1587
|
end
|
1549
1588
|
end # print_help
|
1550
1589
|
|
@@ -1601,7 +1640,7 @@ def debug_vars
|
|
1601
1640
|
file.puts "sta #{@sta}"
|
1602
1641
|
file.puts "cursor #{@cursor}"
|
1603
1642
|
file.puts "stact #{@stact}"
|
1604
|
-
file.puts "viewport.size #{@
|
1643
|
+
file.puts "viewport.size #{@vps}"
|
1605
1644
|
file.puts "pagesize #{@pagesize}"
|
1606
1645
|
file.puts "view.size #{@view.size}"
|
1607
1646
|
file.puts "grows #{@grows}"
|
@@ -1627,40 +1666,51 @@ def view_bookmarks
|
|
1627
1666
|
end
|
1628
1667
|
|
1629
1668
|
# MENU MAIN
|
1669
|
+
# maybe a list menu - options for date format, size format, age, truncate_from, inode etc
|
1630
1670
|
def main_menu
|
1631
1671
|
h = {
|
1632
|
-
:
|
1633
|
-
|
1634
|
-
:
|
1635
|
-
:
|
1636
|
-
:
|
1637
|
-
:
|
1638
|
-
:
|
1639
|
-
|
1640
|
-
|
1641
|
-
|
1642
|
-
|
1643
|
-
|
1644
|
-
|
1645
|
-
:F => :filter_menu,
|
1646
|
-
:c => :create_menu,
|
1647
|
-
:b => :bookmark_menu,
|
1648
|
-
:s => :selection_menu,
|
1649
|
-
:x => :extras
|
1650
|
-
}
|
1672
|
+
a: :ag,
|
1673
|
+
z: :z_interface,
|
1674
|
+
# f: :file_actions,
|
1675
|
+
b: :bookmark_menu,
|
1676
|
+
c: :create_menu,
|
1677
|
+
f: :filter_menu,
|
1678
|
+
o: :order_menu,
|
1679
|
+
s: :selection_menu,
|
1680
|
+
t: :toggle_menu,
|
1681
|
+
v: :view_menu,
|
1682
|
+
'`' => :goto_parent_dir,
|
1683
|
+
x: :extras
|
1684
|
+
}
|
1651
1685
|
menu 'Main Menu', h
|
1652
1686
|
end
|
1653
1687
|
|
1688
|
+
# if menu options returns a hash, then treat as another level menu and process
|
1689
|
+
# rather than having to create more of these. but these can be called directly too.
|
1690
|
+
def view_menu
|
1691
|
+
h = {
|
1692
|
+
f: :select_from_visited_files,
|
1693
|
+
d: :select_from_used_dirs,
|
1694
|
+
b: :view_bookmarks,
|
1695
|
+
s: :list_selected_files,
|
1696
|
+
c: :child_dirs,
|
1697
|
+
r: :recent_files,
|
1698
|
+
t: :tree,
|
1699
|
+
e: :dirtree
|
1700
|
+
}
|
1701
|
+
menu 'View Menu', h
|
1702
|
+
end
|
1703
|
+
|
1654
1704
|
# copy and move here ?
|
1655
1705
|
def selection_menu
|
1656
1706
|
h = {
|
1657
|
-
:
|
1658
|
-
:
|
1659
|
-
:
|
1660
|
-
|
1661
|
-
|
1662
|
-
|
1663
|
-
:
|
1707
|
+
a: :select_all,
|
1708
|
+
u: :unselect_all,
|
1709
|
+
s: :toggle_select,
|
1710
|
+
'*' => 'toggle_multiple_selection',
|
1711
|
+
'x' => 'toggle_visual_mode',
|
1712
|
+
'm' => 'toggle_selection_mode',
|
1713
|
+
v: :view_selected_files
|
1664
1714
|
}
|
1665
1715
|
menu 'Selection Menu', h
|
1666
1716
|
end
|
@@ -1689,13 +1739,22 @@ def menu title, h
|
|
1689
1739
|
# binding in brackets
|
1690
1740
|
h.each_pair do |k, v|
|
1691
1741
|
# get global binding
|
1692
|
-
|
1742
|
+
vs = v.to_s
|
1743
|
+
scut = @bindings.key(vs)
|
1693
1744
|
scut = " (#{scut})" if scut
|
1694
1745
|
|
1695
|
-
ary << " #{k}: #{v} #{scut}"
|
1746
|
+
# ary << " #{k}: #{v} #{scut}"
|
1747
|
+
vs = vs.sub('_menu', '...') if vs.end_with?('_menu')
|
1748
|
+
vs = vs.tr('_', ' ')
|
1749
|
+
ary << " [#{k}] #{vs} #{scut}"
|
1696
1750
|
end
|
1697
1751
|
x = ary.join("\n")
|
1698
|
-
|
1752
|
+
# echo column line bombs when x contains a single quote.
|
1753
|
+
# If i double quote it then it bombs with double quote or backtick
|
1754
|
+
# and prints the entire main menu two times.
|
1755
|
+
x = x.gsub("'", 'single quote')
|
1756
|
+
# x = x.gsub("`", "back-tick")
|
1757
|
+
puts `echo '#{x}' | column`
|
1699
1758
|
|
1700
1759
|
key = get_char
|
1701
1760
|
binding = h[key]
|
@@ -1712,46 +1771,6 @@ def menu title, h
|
|
1712
1771
|
[key, binding]
|
1713
1772
|
end
|
1714
1773
|
|
1715
|
-
# some toggles can be invoked directly on a key, or through another menu.
|
1716
|
-
# Some go to methods like multiple_selection from a key
|
1717
|
-
# WARNING: method and toggle should not have same name, else flag will toggle twice.
|
1718
|
-
def toggle_menu
|
1719
|
-
h = { h: :show_hidden,
|
1720
|
-
c: :ignore_case,
|
1721
|
-
l: :long_listing,
|
1722
|
-
'1' => :toggle_columns,
|
1723
|
-
d: :group_directories,
|
1724
|
-
:e => :editor_mode,
|
1725
|
-
:D => :debug_flag,
|
1726
|
-
'8' => :multiple_selection,
|
1727
|
-
'*' => :multiple_selection,
|
1728
|
-
'S' => :selection_mode,
|
1729
|
-
v: :visual_mode,
|
1730
|
-
t: :truncate_from,
|
1731
|
-
n: :enhanced_mode,
|
1732
|
-
i: :instant_search,
|
1733
|
-
H: :highlight_row_flag }
|
1734
|
-
# TODO: add other values at end, what key ?
|
1735
|
-
|
1736
|
-
_, menu_text = menu 'Toggle Menu', h
|
1737
|
-
return unless menu_text
|
1738
|
-
|
1739
|
-
# menu would have called if symbol only responds so return.
|
1740
|
-
return if respond_to?(menu_text, true) # already handled
|
1741
|
-
|
1742
|
-
# for visual and selection mode
|
1743
|
-
# check if respond_to? symbol or toggle + symbol then call and return.
|
1744
|
-
symb = "toggle_#{menu_text}".to_sym
|
1745
|
-
@log.debug "trying #{symb}."
|
1746
|
-
if respond_to?(symb, true)
|
1747
|
-
@log.debug "calling #{symb}."
|
1748
|
-
send(symb)
|
1749
|
-
return
|
1750
|
-
end
|
1751
|
-
|
1752
|
-
cset menu_text
|
1753
|
-
end
|
1754
|
-
|
1755
1774
|
def toggle_columns
|
1756
1775
|
@gviscols = if @gviscols == 1
|
1757
1776
|
3
|
@@ -1778,7 +1797,7 @@ end
|
|
1778
1797
|
|
1779
1798
|
def toggle_long_listing
|
1780
1799
|
toggle_value :long_listing
|
1781
|
-
@long_listing =
|
1800
|
+
@long_listing = @toggles[:long_listing]
|
1782
1801
|
if @long_listing
|
1783
1802
|
@saved_gviscols = @gviscols
|
1784
1803
|
@gviscols = 1
|
@@ -1792,64 +1811,48 @@ def toggle_long_listing
|
|
1792
1811
|
@sta = @stact
|
1793
1812
|
@stact = 0 # in case user was panned 2019-03-20 -
|
1794
1813
|
end
|
1795
|
-
message "Long listing is #{@long_listing}, visible columns is #{@gviscols}."
|
1796
|
-
rescan_required
|
1814
|
+
message "Long listing is #{@long_listing}, date_func is #{@date_func}. visible columns is #{@gviscols}."
|
1815
|
+
# rescan_required
|
1797
1816
|
end
|
1798
1817
|
|
1799
1818
|
# ----------------- flag related functions -----------------------------------#
|
1800
1819
|
# toggles the value of a toggle flag, also setting the variable if defined
|
1801
1820
|
# WARN: be careful of variable being set directly. Replace such vars one by one.
|
1802
|
-
# NOTE: please use cset
|
1803
1821
|
def toggle_value flag
|
1804
|
-
@toggles[flag] = !@toggles[flag]
|
1805
|
-
# TODO: 2019-04-16 - can we set a variable so we don't have to check
|
1806
|
-
# against a hash everywhere. variable name can be in hash too
|
1822
|
+
x = @toggles[flag] = !@toggles[flag]
|
1807
1823
|
if instance_variable_defined? "@#{flag}"
|
1808
|
-
instance_variable_set "@#{flag}",
|
1809
|
-
@log.debug "instance_variable_set #{flag}, #{
|
1824
|
+
instance_variable_set "@#{flag}", x
|
1825
|
+
@log.debug "instance_variable_set #{flag}, #{x}"
|
1810
1826
|
end
|
1811
|
-
message "#{flag} is set to #{
|
1827
|
+
message "#{flag} is set to #{x}"
|
1812
1828
|
end
|
1813
1829
|
|
1814
1830
|
# rotates the value of an option that has multiple values
|
1815
|
-
# NOTE: please use cset
|
1816
1831
|
def rotate_value symb
|
1817
|
-
|
1818
|
-
|
1832
|
+
hash = @options[symb]
|
1833
|
+
curr = hash[:current]
|
1834
|
+
index = hash[:values].index(curr) || 0
|
1819
1835
|
index += 1
|
1820
|
-
index = 0 if index >=
|
1821
|
-
x =
|
1822
|
-
var =
|
1836
|
+
index = 0 if index >= hash[:values].count
|
1837
|
+
x = hash[:current] = hash[:values][index]
|
1838
|
+
var = hash[:var]
|
1823
1839
|
instance_variable_set "@#{var}", x if var
|
1824
|
-
message "#{symb} is set to #{
|
1840
|
+
message "#{symb} is set to #{x}. "
|
1825
1841
|
end
|
1826
1842
|
|
1827
|
-
# get the value of a flag given the symbol.
|
1828
|
-
# NOTE: use this rather than access the hashes directly, since changes in structure
|
1829
|
-
# may happen
|
1830
|
-
def cget symb
|
1831
|
-
return @toggles[symb] if @toggles.key? symb
|
1832
|
-
return @options[symb][:current] if @options.key? symb
|
1833
|
-
|
1834
|
-
@log.warn "CGET: #{symb} does not exist. Please check code."
|
1835
|
-
raise ArgumentError, "CGET: #{symb} does not exist. Please check code."
|
1836
|
-
end
|
1837
|
-
|
1838
|
-
# toggles or rotates the value of a flag
|
1839
1843
|
def cset symb
|
1844
|
+
return if symb.nil? || symb == :''
|
1845
|
+
|
1840
1846
|
if @toggles.key? symb
|
1841
1847
|
toggle_value symb
|
1842
|
-
|
1843
|
-
return true
|
1844
|
-
end
|
1845
|
-
if @options.key? symb
|
1848
|
+
elsif @options.key? symb
|
1846
1849
|
rotate_value symb
|
1847
|
-
|
1848
|
-
|
1850
|
+
else
|
1851
|
+
@log.warn "CSET: #{symb} does not exist. Please check code."
|
1852
|
+
raise ArgumentError, "CSET: (#{symb}) does not exist. Please check code."
|
1849
1853
|
end
|
1850
|
-
|
1851
|
-
|
1852
|
-
raise ArgumentError, "CSET: #{symb} does not exist. Please check code."
|
1854
|
+
rescan_required
|
1855
|
+
return true
|
1853
1856
|
end
|
1854
1857
|
# ----------------- end of flag related functions ----------------------------#
|
1855
1858
|
|
@@ -1887,6 +1890,8 @@ def order_menu
|
|
1887
1890
|
lo = '/'
|
1888
1891
|
when :clear
|
1889
1892
|
lo = ''
|
1893
|
+
else
|
1894
|
+
return
|
1890
1895
|
end
|
1891
1896
|
## This needs to persist and be a part of all listings, put in change_dir.
|
1892
1897
|
@sorto = lo
|
@@ -1903,74 +1908,26 @@ def create_menu
|
|
1903
1908
|
_, menu_text = menu 'Create Menu', h
|
1904
1909
|
end
|
1905
1910
|
|
1906
|
-
# thse need to be placed in correct position, some do nothing
|
1907
|
-
# and some like ffind have no menu item
|
1908
|
-
# TODO FIXME uncalled now. look into this
|
1909
|
-
def command_menu
|
1910
|
-
# since these involve full paths, we need more space, like only one column
|
1911
|
-
## in these cases, getting back to the earlier dir, back to earlier listing
|
1912
|
-
# since we've basically overlaid the old listing
|
1913
|
-
# should be able to sort THIS listing and not rerun command. But for that I'd need to use
|
1914
|
-
# xargs ls -t etc rather than the zsh sort order. But we can run a filter using |.
|
1915
|
-
#
|
1916
|
-
h = { t: :today, D: :default_command, R: :remove_from_list,
|
1917
|
-
v: :view_selected_files }
|
1918
|
-
h[:e] = if @editor_mode
|
1919
|
-
:pager_mode
|
1920
|
-
else
|
1921
|
-
:editor_mode
|
1922
|
-
end
|
1923
|
-
_, menu_text = menu 'Command Menu', h
|
1924
|
-
case menu_text
|
1925
|
-
when :pager_mode
|
1926
|
-
@editor_mode = false
|
1927
|
-
@default_command = ENV['MANPAGER'] || ENV['PAGER']
|
1928
|
-
when :editor_mode
|
1929
|
-
@editor_mode = true
|
1930
|
-
@default_command = nil
|
1931
|
-
when :ffind
|
1932
|
-
ffind
|
1933
|
-
when :locate
|
1934
|
-
locate
|
1935
|
-
when :today
|
1936
|
-
# zshglob: M = MARK_DIRS with slash
|
1937
|
-
# zshglob: 'm0' 'm' = modified time, '0' = 0 days ago
|
1938
|
-
@files = `zsh -c 'print -rl -- *(#{@hidden}Mm0)'`.split("\n")
|
1939
|
-
@title = "Today's files"
|
1940
|
-
when :default_command
|
1941
|
-
puts ' This no longer works'
|
1942
|
-
puts 'Selecting a file usually invokes $EDITOR'
|
1943
|
-
puts 'What command do you want to use repeatedly on selected files: '
|
1944
|
-
@default_command = gets.chomp
|
1945
|
-
if @default_command != ''
|
1946
|
-
print 'Second part of command (maybe blank): '
|
1947
|
-
@default_command2 = gets.chomp
|
1948
|
-
else
|
1949
|
-
print 'Cleared default command, will default to $EDITOR'
|
1950
|
-
@default_command2 = nil
|
1951
|
-
@default_command = nil
|
1952
|
-
end
|
1953
|
-
end
|
1954
|
-
# redraw
|
1955
|
-
end
|
1956
|
-
|
1957
1911
|
# This is quite badly placed and named. Maybe these should go elsewhere
|
1958
1912
|
def extras
|
1959
|
-
h = {
|
1960
|
-
|
1961
|
-
|
1962
|
-
|
1963
|
-
|
1964
|
-
|
1965
|
-
|
1966
|
-
|
1967
|
-
|
1913
|
+
h = {
|
1914
|
+
'1' => :one_column,
|
1915
|
+
'2' => :multi_column,
|
1916
|
+
c: :columns,
|
1917
|
+
s: :scripts,
|
1918
|
+
g: :generators,
|
1919
|
+
B: :bindkey_ext_command,
|
1920
|
+
f: :page_flags,
|
1921
|
+
R: :remove_from_list,
|
1922
|
+
v: :vidir,
|
1923
|
+
r: :config_read,
|
1924
|
+
w: :config_write
|
1925
|
+
}
|
1968
1926
|
key, menu_text = menu 'Extras Menu', h
|
1969
1927
|
case menu_text
|
1970
1928
|
when :one_column
|
1971
1929
|
@pagesize = @grows
|
1972
1930
|
when :multi_column
|
1973
|
-
# @pagesize = 60
|
1974
1931
|
@pagesize = @grows * @gviscols
|
1975
1932
|
when :columns
|
1976
1933
|
print "How many columns to show: 1-6 [current #{@gviscols}]? "
|
@@ -1984,8 +1941,11 @@ def extras
|
|
1984
1941
|
end
|
1985
1942
|
|
1986
1943
|
def filter_menu
|
1987
|
-
h = { :
|
1988
|
-
:
|
1944
|
+
h = { d: :dirs, f: :files,
|
1945
|
+
e: :emptydirs, '0' => :emptyfiles,
|
1946
|
+
r: :recent_files,
|
1947
|
+
a: :reduce_list, # key ??? XXX
|
1948
|
+
x: :extension }
|
1989
1949
|
_, menu_text = menu 'Filter Menu', h
|
1990
1950
|
files = nil
|
1991
1951
|
case menu_text
|
@@ -2013,11 +1973,18 @@ def filter_menu
|
|
2013
1973
|
files = reduce
|
2014
1974
|
when :extension
|
2015
1975
|
files = filter_for_current_extension
|
1976
|
+
when :recent_files
|
1977
|
+
# files = recent_files
|
1978
|
+
@title = 'Filter: files by mtime'
|
1979
|
+
files = get_files_by_mtime.first(10)
|
1980
|
+
else
|
1981
|
+
return
|
2016
1982
|
end
|
2017
1983
|
if files && files.count > 0
|
2018
1984
|
@files = files
|
2019
1985
|
@stact = 0
|
2020
1986
|
@message = "Filtered on #{menu_text}. Press ESC to return."
|
1987
|
+
@bm = ' (' + @bindings.key('filter_menu') + ') '
|
2021
1988
|
else
|
2022
1989
|
perror 'Sorry, No files. '
|
2023
1990
|
@title = nil
|
@@ -2026,11 +1993,11 @@ end
|
|
2026
1993
|
|
2027
1994
|
def reduce pattern=nil
|
2028
1995
|
unless pattern
|
2029
|
-
|
2030
|
-
pattern = gets.chomp
|
1996
|
+
pattern = readline 'Enter a pattern to reduce current list: '
|
2031
1997
|
end
|
2032
1998
|
@title = "Filter: pattern #{pattern}"
|
2033
|
-
@
|
1999
|
+
@bm = "(%r)"
|
2000
|
+
@files = @files.select { |f| f.match?(pattern) }
|
2034
2001
|
end
|
2035
2002
|
|
2036
2003
|
def filter_for_current_extension
|
@@ -2044,19 +2011,18 @@ def select_from_used_dirs
|
|
2044
2011
|
@title = 'Used Directories'
|
2045
2012
|
home = File.expand_path '~'
|
2046
2013
|
@files = @used_dirs.uniq.map { |path| path.sub(home.to_s, '~') }
|
2014
|
+
@bm = @bindings.key('select_from_used_dirs')
|
2015
|
+
@bm = ' (' + @bm + ')' if @bm
|
2016
|
+
# redraw_required
|
2047
2017
|
end
|
2048
2018
|
|
2049
2019
|
def select_from_visited_files
|
2050
|
-
# not yet a unique list, needs to be unique and have latest pushed to top
|
2051
2020
|
@title = 'Visited Files'
|
2052
2021
|
home = File.expand_path '~'
|
2053
2022
|
@files = @visited_files.uniq.map { |path| path.sub(home.to_s, '~') }
|
2054
|
-
|
2055
|
-
|
2056
|
-
#
|
2057
|
-
def select_bookmarks
|
2058
|
-
@title = 'Bookmarks'
|
2059
|
-
@files = @bookmarks.values
|
2023
|
+
@bm = @bindings.key('select_from_visited_files')
|
2024
|
+
@bm = ' (' + @bm + ')' if @bm
|
2025
|
+
# redraw_required
|
2060
2026
|
end
|
2061
2027
|
|
2062
2028
|
## part copied and changed from change_dir since we don't dir going back on top
|
@@ -2069,6 +2035,7 @@ def pop_dir
|
|
2069
2035
|
## XXX make sure the dir exists, cuold have been deleted. can be an error or crash otherwise
|
2070
2036
|
@visited_dirs.push d
|
2071
2037
|
Dir.chdir d
|
2038
|
+
@current_dir = Dir.pwd # 2019-04-24 - earlier was in post_cd but too late
|
2072
2039
|
post_cd
|
2073
2040
|
rescan_required
|
2074
2041
|
end
|
@@ -2079,6 +2046,7 @@ def post_cd
|
|
2079
2046
|
@sta = @cursor = @stact = 0
|
2080
2047
|
@visual_block_start = nil
|
2081
2048
|
screen_settings
|
2049
|
+
calculate_bookmarks_for_dir
|
2082
2050
|
|
2083
2051
|
# goto last position cursor was in this dir
|
2084
2052
|
revert_dir_pos
|
@@ -2172,37 +2140,31 @@ end
|
|
2172
2140
|
# Was this supposed to be augmented, or just remain limited like this
|
2173
2141
|
# We should be able to do everything in the menus from here. TODO
|
2174
2142
|
def subcommand
|
2175
|
-
|
2176
|
-
|
2143
|
+
# clear_last_line
|
2144
|
+
# pbold 'Subcommand:'
|
2177
2145
|
begin
|
2178
|
-
|
2146
|
+
prompt = %(
|
2147
|
+
[q] quit [w] config write [d] delete
|
2148
|
+
[x] update config + quit [r] config read [r] rename
|
2149
|
+
[wq] write config + quit [e] edit file under cursor [m] move
|
2150
|
+
[P] copy PWD to clipboard [o] open file under cursor [c] copy
|
2151
|
+
[p] copy filename to clipboard [h] help [t] toggle flags
|
2152
|
+
)
|
2153
|
+
# command = readline 'Enter command: q x wq P p w e r h :'
|
2154
|
+
command = readline prompt
|
2179
2155
|
return if command == ''
|
2180
2156
|
rescue StandardError
|
2181
2157
|
return
|
2182
2158
|
end
|
2183
2159
|
if command == 'q'
|
2184
|
-
|
2185
|
-
if @modified
|
2186
|
-
last_line
|
2187
|
-
print 'Do you want to save bookmarks? (y/n): '
|
2188
|
-
key = get_char
|
2189
|
-
if key == 'y'
|
2190
|
-
@writing = true
|
2191
|
-
@quitting = true
|
2192
|
-
elsif key == 'n'
|
2193
|
-
@quitting = true
|
2194
|
-
print 'Quitting without saving bookmarks'
|
2195
|
-
else
|
2196
|
-
perror 'No action taken.'
|
2197
|
-
end
|
2198
|
-
else
|
2199
|
-
@quitting = true
|
2200
|
-
end
|
2160
|
+
quit_command
|
2201
2161
|
elsif command == 'wq'
|
2202
2162
|
@quitting = true
|
2203
2163
|
@writing = true
|
2204
2164
|
elsif command == 'w'
|
2205
2165
|
config_write
|
2166
|
+
elsif command == 'r'
|
2167
|
+
config_read
|
2206
2168
|
elsif command == 'x'
|
2207
2169
|
@quitting = true
|
2208
2170
|
@writing = true if @modified
|
@@ -2210,16 +2172,66 @@ def subcommand
|
|
2210
2172
|
edit_current
|
2211
2173
|
elsif command == 'o'
|
2212
2174
|
open_current
|
2213
|
-
elsif
|
2175
|
+
elsif command == 'd'
|
2176
|
+
delete_file
|
2177
|
+
elsif command == 'c'
|
2178
|
+
copy_file
|
2179
|
+
elsif command == 'r'
|
2180
|
+
rename_file
|
2181
|
+
elsif command == 'm'
|
2182
|
+
move_file
|
2183
|
+
elsif command == 'h' || command == 'help' || command == '?'
|
2214
2184
|
print_help
|
2215
|
-
elsif command == '
|
2185
|
+
elsif command == 'P'
|
2186
|
+
# or should be put current file in clip ?
|
2216
2187
|
system 'echo $PWD | pbcopy'
|
2217
|
-
|
2188
|
+
message 'Stored PWD in clipboard (using pbcopy)'
|
2189
|
+
elsif command == 'p'
|
2190
|
+
system "echo #{current_file} | pbcopy"
|
2191
|
+
message "Stored #{current_file} in clipboard (using pbcopy)"
|
2192
|
+
elsif command == 't' || command == 'toggle'
|
2193
|
+
toggle_menu
|
2218
2194
|
else
|
2219
2195
|
perror "Don't know about command #{command}. Try :h or :help"
|
2220
2196
|
end
|
2221
2197
|
end
|
2222
2198
|
|
2199
|
+
# 2019-03-08 - 23:46
|
2200
|
+
# FIXME returns a String not symbol, so some callers can fail
|
2201
|
+
def fzfmenu(title, h)
|
2202
|
+
return unless h
|
2203
|
+
|
2204
|
+
pbold title.to_s
|
2205
|
+
# XXX using keys not values since toggle hash being used
|
2206
|
+
values = h.keys.join("\n")
|
2207
|
+
binding = `echo "#{values}" | fzf --prompt="#{title.to_s} :"`
|
2208
|
+
if binding
|
2209
|
+
binding = binding.chomp
|
2210
|
+
send(binding) if respond_to?(binding, true)
|
2211
|
+
end
|
2212
|
+
binding
|
2213
|
+
end
|
2214
|
+
|
2215
|
+
def toggle_menu
|
2216
|
+
binding = fzfmenu 'Toggles', @toggles.merge(@options)
|
2217
|
+
return if binding.nil? || binding == ''
|
2218
|
+
|
2219
|
+
menu_text = binding.to_sym
|
2220
|
+
return if respond_to?(menu_text, true) # already handled
|
2221
|
+
|
2222
|
+
# for visual and selection mode
|
2223
|
+
# check if respond_to? symbol or toggle + symbol then call and return.
|
2224
|
+
symb = "toggle_#{menu_text}".to_sym
|
2225
|
+
@log.debug "trying #{symb}."
|
2226
|
+
if respond_to?(symb, true)
|
2227
|
+
@log.debug "calling #{symb}."
|
2228
|
+
send(symb)
|
2229
|
+
return
|
2230
|
+
end
|
2231
|
+
|
2232
|
+
cset menu_text
|
2233
|
+
end
|
2234
|
+
|
2223
2235
|
def quit_command
|
2224
2236
|
# if we are in some mode, like search results then 'q' should come out.
|
2225
2237
|
if @mode
|
@@ -2248,6 +2260,7 @@ def views
|
|
2248
2260
|
@viewctr = 0 if @viewctr > views.size
|
2249
2261
|
|
2250
2262
|
@files = `zsh -c 'print -rl -- *(#{@sorto}#{@hidden}M)'`.split("\n")
|
2263
|
+
redraw_required
|
2251
2264
|
end
|
2252
2265
|
|
2253
2266
|
def child_dirs
|
@@ -2340,7 +2353,7 @@ def get_shortcut index
|
|
2340
2353
|
# Now we show unused shortcuts after exhausting them.
|
2341
2354
|
# return '<' if index < @stact
|
2342
2355
|
if index < @stact
|
2343
|
-
index = @
|
2356
|
+
index = @vps - @stact + index
|
2344
2357
|
i = IDX[index]
|
2345
2358
|
return i if i
|
2346
2359
|
|
@@ -2387,22 +2400,21 @@ def convert_key_to_index key
|
|
2387
2400
|
i = IDX.index(key)
|
2388
2401
|
return nil unless i
|
2389
2402
|
|
2390
|
-
# @log.debug "get_index with #{key}: #{i}. #{@stact}. #{@
|
2391
|
-
vps = @viewport.size
|
2403
|
+
# @log.debug "get_index with #{key}: #{i}. #{@stact}. #{@vps}"
|
2392
2404
|
# TODO: if very high key given, consider going to last file ?
|
2393
2405
|
# that way one can press zz or ZZ to go to last file.
|
2394
2406
|
# 2019-04-11 - XXX actually this doesnt place the cursor on last file
|
2395
2407
|
# it opens it, which may not be what we want
|
2396
2408
|
retnil = nil # vps - 1 # nil
|
2397
2409
|
# user has entered a key that is outside of range, return nil
|
2398
|
-
return retnil if @stact == 0 && i + @stact >= vps
|
2410
|
+
return retnil if @stact == 0 && i + @stact >= @vps
|
2399
2411
|
|
2400
2412
|
# again, key out of range
|
2401
|
-
# return nil if @stact > 0 && i + @stact >= vps && i + @stact - vps >= @stact
|
2402
|
-
return retnil if @stact > 0 && i + @stact >= vps && i - vps >= 0
|
2413
|
+
# return nil if @stact > 0 && i + @stact >= @vps && i + @stact - @vps >= @stact
|
2414
|
+
return retnil if @stact > 0 && i + @stact >= @vps && i - @vps >= 0
|
2403
2415
|
|
2404
2416
|
# panning case, hints are recycled
|
2405
|
-
return (i + @stact) - vps if i + @stact >= vps
|
2417
|
+
return (i + @stact) - @vps if i + @stact >= @vps
|
2406
2418
|
|
2407
2419
|
# regular hint
|
2408
2420
|
return i + @stact # if i
|
@@ -2544,7 +2556,7 @@ def get_unique_file_name fname
|
|
2544
2556
|
return f unless File.exist?(f)
|
2545
2557
|
end
|
2546
2558
|
|
2547
|
-
timestamp = Time.now.strftime(
|
2559
|
+
timestamp = Time.now.strftime('%Y%m%d-%H%M%S')
|
2548
2560
|
return fname + '.' + timestamp
|
2549
2561
|
end
|
2550
2562
|
|
@@ -2630,6 +2642,7 @@ end
|
|
2630
2642
|
|
2631
2643
|
# instantly move to move target. Use during bulk operations so you don't have to
|
2632
2644
|
# enter or goto target dir and come back.
|
2645
|
+
# XXX try removing this, making things complex.
|
2633
2646
|
def move_instant
|
2634
2647
|
# FIXME: cannot be in target directory and do auto update
|
2635
2648
|
# should we have a key for specifying move_target ?
|
@@ -2691,7 +2704,7 @@ def command_file prompt, *command
|
|
2691
2704
|
perror 'Command Cancelled' unless file
|
2692
2705
|
return unless file
|
2693
2706
|
|
2694
|
-
file =
|
2707
|
+
file = expand_path(file)
|
2695
2708
|
if File.exist? file
|
2696
2709
|
file = Shellwords.escape(file)
|
2697
2710
|
pbold "#{command} #{file} (#{pauseyn})"
|
@@ -2711,7 +2724,7 @@ def ask_hint deflt=nil
|
|
2711
2724
|
key = get_char
|
2712
2725
|
return deflt if key == 'ENTER'
|
2713
2726
|
|
2714
|
-
ix = get_index(key, @
|
2727
|
+
ix = get_index(key, @vps)
|
2715
2728
|
f = @viewport[ix] if ix
|
2716
2729
|
f
|
2717
2730
|
end
|
@@ -2739,11 +2752,11 @@ def column_next direction=0
|
|
2739
2752
|
# leftward movement stops at first column.
|
2740
2753
|
if direction == 0
|
2741
2754
|
@stact += @grows
|
2742
|
-
@stact = 0 if @stact >= @
|
2755
|
+
@stact = 0 if @stact >= @vps
|
2743
2756
|
@cursor += @grows
|
2744
2757
|
# 2019-03-18 - zero loses offset. we need to maintain it
|
2745
|
-
# @cursor = 0 if @cursor >= @
|
2746
|
-
if @cursor - @sta >= @
|
2758
|
+
# @cursor = 0 if @cursor >= @vps
|
2759
|
+
if @cursor - @sta >= @vps
|
2747
2760
|
@cursor -= @grows while @cursor > @sta
|
2748
2761
|
@stact -= @grows while @stact > 0
|
2749
2762
|
@cursor += @grows if @cursor < @sta
|
@@ -2764,18 +2777,27 @@ end
|
|
2764
2777
|
# 2019-03-08 - TODO when a file name changes or moves it must be removed
|
2765
2778
|
# from selection
|
2766
2779
|
def file_actions action=nil
|
2767
|
-
h = {
|
2768
|
-
|
2769
|
-
|
2770
|
-
|
2780
|
+
h = {
|
2781
|
+
d: :delete,
|
2782
|
+
D: '/bin/rm',
|
2783
|
+
m: :move,
|
2784
|
+
c: :copy,
|
2785
|
+
r: :rename,
|
2786
|
+
e: :execute,
|
2787
|
+
v: ENV['EDITOR'] || :vim,
|
2788
|
+
o: :open_current,
|
2789
|
+
p: :most
|
2790
|
+
}
|
2771
2791
|
|
2772
2792
|
rbfiles = current_or_selected_files # use with ruby FileUtils
|
2773
2793
|
return if rbfiles.nil? || rbfiles.empty?
|
2774
2794
|
|
2775
2795
|
count = rbfiles.count
|
2776
2796
|
first = rbfiles.first
|
2797
|
+
|
2798
|
+
h.delete(:r) if count > 1
|
2799
|
+
|
2777
2800
|
if count == 1
|
2778
|
-
h[:r] = :rename
|
2779
2801
|
|
2780
2802
|
# add chdir if dir of file under cursor is not same as current dir
|
2781
2803
|
h[:C] = :chdir if File.dirname(File.expand_path(first)) != Dir.pwd
|
@@ -2795,6 +2817,8 @@ def file_actions action=nil
|
|
2795
2817
|
end
|
2796
2818
|
h[:M] = :set_move_target if File.directory?(current_file)
|
2797
2819
|
h[:z] = :zip unless filetype(first) == :zip
|
2820
|
+
h['/'] = :ffind
|
2821
|
+
h[:l] = :locate
|
2798
2822
|
|
2799
2823
|
# if first file has spaces then add remspace method
|
2800
2824
|
# TODO: put this in scripts
|
@@ -2840,10 +2864,6 @@ def file_actions action=nil
|
|
2840
2864
|
rename_file
|
2841
2865
|
|
2842
2866
|
when :chdir
|
2843
|
-
# will only work if one selected. Check this out
|
2844
|
-
# This works if you have searched for files and got a list
|
2845
|
-
# with different paths.
|
2846
|
-
# This should not be shown in regular listings XXX
|
2847
2867
|
change_dir File.dirname(File.expand_path(rbfiles.first)) if count == 1
|
2848
2868
|
|
2849
2869
|
when :set_move_target
|
@@ -2865,6 +2885,8 @@ def file_actions action=nil
|
|
2865
2885
|
1
|
2866
2886
|
# already been executed by menu
|
2867
2887
|
# We could have just put 'stat' in the menu but that doesn't look so nice
|
2888
|
+
when :locate
|
2889
|
+
1
|
2868
2890
|
else
|
2869
2891
|
return unless menu_text
|
2870
2892
|
|
@@ -2887,13 +2909,13 @@ end
|
|
2887
2909
|
# remove non-existent files from select list due to move or delete
|
2888
2910
|
# or rename or whatever
|
2889
2911
|
def clean_selected_files
|
2890
|
-
@selected_files.select! { |x| x =
|
2912
|
+
@selected_files.select! { |x| x = expand_path(x); File.exist?(x) }
|
2891
2913
|
end
|
2892
2914
|
|
2893
2915
|
# set the default target for further moves
|
2894
2916
|
# We need to also be able to set it to current dir in which user is. TODO
|
2895
2917
|
def set_move_target cf=current_file
|
2896
|
-
ff =
|
2918
|
+
ff = expand_path(cf)
|
2897
2919
|
return unless File.directory? ff
|
2898
2920
|
|
2899
2921
|
@move_target = ff
|
@@ -2973,18 +2995,19 @@ def ffind
|
|
2973
2995
|
end
|
2974
2996
|
|
2975
2997
|
def locate
|
2976
|
-
|
2977
|
-
pattern = readline
|
2998
|
+
@locate_command ||= /darwin/.match?(RUBY_PLATFORM) ? 'mdfind -name' : 'locate'
|
2999
|
+
pattern = readline "Enter a file name pattern to #{@locate_command}: "
|
2978
3000
|
return if pattern == ''
|
2979
3001
|
|
2980
|
-
@title = "Files found using
|
2981
|
-
files =
|
2982
|
-
files.select! { |x| x =
|
3002
|
+
@title = "Files found using: (#{@locate_command} #{pattern})"
|
3003
|
+
files = `#{@locate_command} #{pattern}`.split("\n")
|
3004
|
+
files.select! { |x| x = expand_path(x); File.exist?(x) }
|
2983
3005
|
if files.empty?
|
2984
3006
|
perror 'No files found.'
|
2985
|
-
|
2986
|
-
@files = files
|
3007
|
+
return
|
2987
3008
|
end
|
3009
|
+
@files = files
|
3010
|
+
@bm = nil
|
2988
3011
|
end
|
2989
3012
|
|
2990
3013
|
## takes directories from the z program, if you use autojump you can
|
@@ -3065,7 +3088,7 @@ def move_to position
|
|
3065
3088
|
oldsta = @sta
|
3066
3089
|
if @cursor - @sta >= @pagesize
|
3067
3090
|
@sta += @pagesize
|
3068
|
-
# elsif @sta - @cursor >= @
|
3091
|
+
# elsif @sta - @cursor >= @vps
|
3069
3092
|
end
|
3070
3093
|
if @sta > @cursor
|
3071
3094
|
@sta -= @pagesize
|
@@ -3082,7 +3105,8 @@ def move_to position
|
|
3082
3105
|
@cursor_movement = nil # visual mode needs to redraw page
|
3083
3106
|
|
3084
3107
|
# PWD has to be there in selction
|
3085
|
-
|
3108
|
+
# FIXME with visited_files
|
3109
|
+
if selected? File.join(@current_dir, current_file)
|
3086
3110
|
# this depends on the direction
|
3087
3111
|
# @selected_files = @selected_files - @view[star..fin]
|
3088
3112
|
remove_from_selection @view[star..fin]
|
@@ -3104,6 +3128,8 @@ end
|
|
3104
3128
|
# ------------- selection related methods --------------------------------#
|
3105
3129
|
|
3106
3130
|
# is given file in selected array
|
3131
|
+
# 2019-04-24 - now takes fullname so path addition does not keep happening in
|
3132
|
+
# a loop in draw directory.
|
3107
3133
|
def selected? fullname
|
3108
3134
|
return @selected_files.index fullname
|
3109
3135
|
end
|
@@ -3117,7 +3143,9 @@ def add_to_selection file
|
|
3117
3143
|
end
|
3118
3144
|
@current_dir ||= Dir.pwd
|
3119
3145
|
ff.each do |f|
|
3120
|
-
|
3146
|
+
# this is wrong if the file is a dir listing or visited files listing.
|
3147
|
+
# full = File.join(@current_dir, f)
|
3148
|
+
full = expand_path(f)
|
3121
3149
|
@selected_files.push(full) unless @selected_files.include?(full)
|
3122
3150
|
end
|
3123
3151
|
end
|
@@ -3130,7 +3158,8 @@ def remove_from_selection file
|
|
3130
3158
|
end
|
3131
3159
|
@current_dir ||= Dir.pwd
|
3132
3160
|
ff.each do |f|
|
3133
|
-
full = File.join(@current_dir, f)
|
3161
|
+
# full = File.join(@current_dir, f)
|
3162
|
+
full = expand_path(f)
|
3134
3163
|
@selected_files.delete full
|
3135
3164
|
end
|
3136
3165
|
end
|
@@ -3290,7 +3319,7 @@ def create_a_file
|
|
3290
3319
|
|
3291
3320
|
system %($EDITOR "#{str}")
|
3292
3321
|
setup_terminal
|
3293
|
-
@visited_files.insert(0, str) if File.exist?(str)
|
3322
|
+
@visited_files.insert(0, expand_path(str)) if File.exist?(str)
|
3294
3323
|
refresh
|
3295
3324
|
end
|
3296
3325
|
|
@@ -3338,6 +3367,7 @@ def scripts binding=nil
|
|
3338
3367
|
|
3339
3368
|
# reset clears the screen, we don't want that. just unhide cursor and echo keys TODO
|
3340
3369
|
partial_reset_terminal
|
3370
|
+
@log.info "Calling #{binding}."
|
3341
3371
|
system %( #{binding} "#{current_file}" )
|
3342
3372
|
|
3343
3373
|
# system %(echo "#{cf}" | xargs #{binding})
|
@@ -3400,6 +3430,9 @@ end
|
|
3400
3430
|
def list_selected_files
|
3401
3431
|
@title = 'Selected Files'
|
3402
3432
|
@files = @selected_files
|
3433
|
+
|
3434
|
+
@bm = @bindings.key('list_selected_files')
|
3435
|
+
@bm = ' (' + @bm + ')' if @bm
|
3403
3436
|
end
|
3404
3437
|
|
3405
3438
|
# write selected files to a file and return path
|
@@ -3410,7 +3443,7 @@ def write_selected_files
|
|
3410
3443
|
# fname = File.join(File.dirname(CONFIG_FILE), 'selected_files')
|
3411
3444
|
# 2019-04-10 - changed to ~/tmp otherwise confusion about location
|
3412
3445
|
fname = File.join('~/tmp/', 'selected_files')
|
3413
|
-
fname =
|
3446
|
+
fname = expand_path(fname)
|
3414
3447
|
|
3415
3448
|
# remove file if no selection
|
3416
3449
|
if @selected_files.empty?
|
@@ -3439,40 +3472,36 @@ end
|
|
3439
3472
|
##
|
3440
3473
|
# Editing of the User Dir List.
|
3441
3474
|
# remove current entry from used dirs list, since we may not want some entries being there
|
3442
|
-
#
|
3475
|
+
# Need to call this from somewhere. Test it out again.
|
3476
|
+
# Usage. Invoke `1` or `2` and select some files and then call remove
|
3443
3477
|
def remove_from_list
|
3444
|
-
unless @selected_files.empty?
|
3445
|
-
sz = @selected_files.size
|
3446
|
-
print "Remove #{sz} files from used list (y)?: "
|
3447
|
-
key = get_char
|
3448
|
-
return if key != 'y'
|
3449
|
-
|
3450
|
-
arr = @selected_files.map { |path| File.expand_path(path) }
|
3451
3478
|
|
3452
|
-
|
3453
|
-
|
3454
|
-
|
3455
|
-
|
3456
|
-
refresh
|
3457
|
-
return
|
3458
|
-
end
|
3459
|
-
|
3460
|
-
# no file selected, use file under cursor
|
3461
|
-
print
|
3462
|
-
## what if selected some rows
|
3463
|
-
file = @view[@cursor]
|
3464
|
-
print "Remove #{file} from used list (y)?: "
|
3479
|
+
# XXX key to invoke this is difficult. make it easier
|
3480
|
+
selfiles = current_or_selected_files
|
3481
|
+
sz = selfiles.size
|
3482
|
+
print "Remove #{sz} files from used list (y)?: "
|
3465
3483
|
key = get_char
|
3466
3484
|
return if key != 'y'
|
3467
3485
|
|
3468
|
-
|
3469
|
-
|
3470
|
-
|
3486
|
+
# arr = @selected_files.map { |path| File.expand_path(path) }
|
3487
|
+
# @log.debug "BEFORE: Selected files are: #{@selected_files}"
|
3488
|
+
arr = selfiles.map { |path|
|
3489
|
+
if path[0] != '/'
|
3490
|
+
expand_path(path)
|
3491
|
+
else
|
3492
|
+
path
|
3493
|
+
end
|
3494
|
+
}
|
3495
|
+
if File.directory? arr.first
|
3496
|
+
@used_dirs -= arr
|
3497
|
+
select_from_used_dirs
|
3471
3498
|
else
|
3472
|
-
@visited_files
|
3499
|
+
@visited_files -= arr
|
3500
|
+
select_from_visited_files
|
3473
3501
|
end
|
3474
|
-
|
3502
|
+
unselect_all
|
3475
3503
|
@modified = true
|
3504
|
+
# redraw_required
|
3476
3505
|
end
|
3477
3506
|
|
3478
3507
|
#
|
@@ -3482,7 +3511,6 @@ end
|
|
3482
3511
|
# latest source, but in some cases even this can be misleading since running a program accesses
|
3483
3512
|
# include files.
|
3484
3513
|
def enhance_file_list
|
3485
|
-
# return unless cget(:enhanced_mode)
|
3486
3514
|
return unless @enhanced_mode
|
3487
3515
|
@current_dir ||= Dir.pwd
|
3488
3516
|
|
@@ -3703,7 +3731,13 @@ def gfb dir, func
|
|
3703
3731
|
dir = dir.gsub('//', '/')
|
3704
3732
|
|
3705
3733
|
# sort by time and then reverse so latest first.
|
3706
|
-
sorted_files = Dir[dir].sort_by { |f|
|
3734
|
+
sorted_files = Dir[dir].sort_by { |f|
|
3735
|
+
if File.exist? f
|
3736
|
+
File.send(func, f)
|
3737
|
+
else
|
3738
|
+
sys_stat(func, f)
|
3739
|
+
end
|
3740
|
+
}.reverse
|
3707
3741
|
|
3708
3742
|
# add slash to directories
|
3709
3743
|
sorted_files = add_slash sorted_files
|
@@ -3789,12 +3823,25 @@ def only_cursor_moved flag=true
|
|
3789
3823
|
@cursor_movement = flag
|
3790
3824
|
end
|
3791
3825
|
|
3826
|
+
# Had to create this since ruby crashes on files start with '~'
|
3827
|
+
def expand_path file
|
3828
|
+
return File.expand_path(file) if file.start_with?("~/")
|
3829
|
+
return file if file[0] == '/'
|
3830
|
+
|
3831
|
+
return File.join(@current_dir, file)
|
3832
|
+
end
|
3833
|
+
|
3792
3834
|
# main loop which calls all other programs
|
3793
3835
|
def run
|
3794
3836
|
Signal.trap('EXIT') do
|
3795
3837
|
reset_terminal
|
3796
3838
|
exit
|
3797
3839
|
end
|
3840
|
+
Signal.trap('WINCH') do
|
3841
|
+
screen_settings
|
3842
|
+
redraw
|
3843
|
+
place_cursor
|
3844
|
+
end
|
3798
3845
|
|
3799
3846
|
setup_terminal
|
3800
3847
|
config_read
|