cetus 0.1.36 → 0.1.38
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 +1292 -1092
- data/cetus.gemspec +1 -1
- data/scripts/encrypt.sh +1 -0
- data/scripts/zip +1 -0
- 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: 0adbfa483176301769749c1f1756cf6ae616ee572ad5b2ca56c28b2339f0386f
|
4
|
+
data.tar.gz: 2506a1e5264337d8648022083d7c37a6616d36e1be12b65d29b5172211cbdc67
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7e18775e80ebc7cf5367e4040fb27c73eb2cff2e0cabac603afd1958de18cc1a82055b51c3b13eaaef2b03c0937b9aeff045a29f255b4ab8f7221821e0dc97f0
|
7
|
+
data.tar.gz: d7efa83ac9196fc74768e6fad5e87df973ec9ba49a44921310462cfe834cd53bb288be52d76f92d7ca6d45b9363e0545dac584ea5a3e47dad28e2fd0344c0daa
|
data/bin/cetus
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
2
4
|
# --------------------------------------------------------------------------- #
|
3
5
|
# File: cetus
|
4
6
|
# Description: Fast file navigation, a tiny version of zfm
|
@@ -6,7 +8,7 @@
|
|
6
8
|
# Author: rkumar http://github.com/rkumar/cetus/
|
7
9
|
# Date: 2013-02-17 - 17:48
|
8
10
|
# License: GPL
|
9
|
-
# Last update: 2019-04-
|
11
|
+
# Last update: 2019-04-19 09:51
|
10
12
|
# --------------------------------------------------------------------------- #
|
11
13
|
# cetus.rb Copyright (C) 2012-2019 rahul kumar
|
12
14
|
# == CHANGELOG
|
@@ -26,19 +28,19 @@ require 'shellwords'
|
|
26
28
|
# https://docs.ruby-lang.org/en/2.6.0/FileUtils.html
|
27
29
|
require 'fileutils'
|
28
30
|
require 'logger'
|
29
|
-
|
30
|
-
@log = Logger.new('log.txt')
|
31
|
+
@log = Logger.new(File.expand_path('~/tmp/log.txt'))
|
32
|
+
# @log = Logger.new('log.txt')
|
31
33
|
|
32
34
|
## INSTALLATION
|
33
35
|
# copy into PATH
|
34
36
|
# alias c=~/bin/cetus.rb
|
35
37
|
# c
|
36
38
|
|
37
|
-
VERSION = '0.1.
|
39
|
+
VERSION = '0.1.38.0'
|
38
40
|
CONFIG_PATH = ENV['XDG_CONFIG_HOME'] || File.join(ENV['HOME'], '.config')
|
39
|
-
CONFIG_FILE = "#{CONFIG_PATH}/cetus/conf.yml"
|
41
|
+
CONFIG_FILE = "#{CONFIG_PATH}/cetus/conf.yml"
|
40
42
|
|
41
|
-
|
43
|
+
@bindings = {
|
42
44
|
'`' => 'main_menu',
|
43
45
|
'=' => 'toggle_menu',
|
44
46
|
'M-s' => 'selection_menu',
|
@@ -50,14 +52,14 @@ $bindings = {
|
|
50
52
|
'C-s' => 'toggle_select',
|
51
53
|
'C-r' => 'reduce',
|
52
54
|
'C-g' => 'debug_vars',
|
53
|
-
'*' => '
|
55
|
+
'*' => 'toggle_multiple_selection',
|
54
56
|
'M-a' => 'select_all',
|
55
57
|
'M-A' => 'unselect_all',
|
56
58
|
'!' => 'execute',
|
57
59
|
',' => 'goto_parent_dir',
|
58
60
|
'~' => 'goto_home_dir',
|
59
61
|
'-' => 'goto_previous_dir',
|
60
|
-
'+' => 'goto_dir',
|
62
|
+
'+' => 'goto_dir', # 2019-03-07 - TODO: change binding
|
61
63
|
'.' => 'pop_dir',
|
62
64
|
':' => 'subcommand',
|
63
65
|
"'" => 'goto_bookmark',
|
@@ -65,6 +67,10 @@ $bindings = {
|
|
65
67
|
'/' => 'enter_regex',
|
66
68
|
'M-p' => 'prev_page',
|
67
69
|
'M-n' => 'next_page',
|
70
|
+
'PgUp' => 'prev_page',
|
71
|
+
'PgDn' => 'next_page',
|
72
|
+
'Home' => 'goto_top',
|
73
|
+
'End' => 'goto_end',
|
68
74
|
'SPACE' => 'next_page:Page Down',
|
69
75
|
'M-f' => 'select_from_visited_files',
|
70
76
|
'M-d' => 'select_from_used_dirs',
|
@@ -94,7 +100,7 @@ $bindings = {
|
|
94
100
|
'C-b' => 'cursor_scroll_up',
|
95
101
|
'UP' => 'cursor_up',
|
96
102
|
'DOWN' => 'cursor_dn',
|
97
|
-
'C-SPACE' => '
|
103
|
+
'C-SPACE' => 'toggle_visual_mode',
|
98
104
|
'@' => 'scripts',
|
99
105
|
'#' => 'generators',
|
100
106
|
|
@@ -109,51 +115,51 @@ $bindings = {
|
|
109
115
|
}
|
110
116
|
|
111
117
|
## clean this up a bit, copied from shell program and macro'd
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
KEY_PGDN = "\e[6~"
|
118
|
-
KEY_PGUP = "\e[5~"
|
118
|
+
@kh = {}
|
119
|
+
@kh["\eOP"] = 'F1'
|
120
|
+
@kh["\e[A"] = 'UP'
|
121
|
+
@kh["\e[5~"] = 'PGUP'
|
122
|
+
@kh[''] = 'ESCAPE'
|
123
|
+
KEY_PGDN = "\e[6~"
|
124
|
+
KEY_PGUP = "\e[5~"
|
119
125
|
## I needed to replace the O with a [ for this to work
|
120
126
|
# in Vim Home comes as ^[OH whereas on the command line it is correct as ^[[H
|
121
|
-
KEY_HOME = '[H'
|
122
|
-
KEY_END = "\e[F"
|
123
|
-
KEY_F1 = "\eOP"
|
124
|
-
KEY_UP = "\e[A"
|
125
|
-
KEY_DOWN = "\e[B"
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
KEY_LEFT = '[D'
|
135
|
-
KEY_RIGHT = '[C'
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
KEY_F5 = '[15~'
|
142
|
-
KEY_F6 = '[17~'
|
143
|
-
KEY_F7 = '[18~'
|
144
|
-
KEY_F8 = '[19~'
|
145
|
-
KEY_F9 = '[20~'
|
146
|
-
KEY_F10 = '[21~'
|
147
|
-
KEY_S_F1 = '[1;2P'
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
127
|
+
KEY_HOME = '[H'
|
128
|
+
KEY_END = "\e[F"
|
129
|
+
KEY_F1 = "\eOP"
|
130
|
+
KEY_UP = "\e[A"
|
131
|
+
KEY_DOWN = "\e[B"
|
132
|
+
|
133
|
+
@kh[KEY_PGDN] = 'PgDn'
|
134
|
+
@kh[KEY_PGUP] = 'PgUp'
|
135
|
+
@kh[KEY_HOME] = 'Home'
|
136
|
+
@kh[KEY_END] = 'End'
|
137
|
+
@kh[KEY_F1] = 'F1'
|
138
|
+
@kh[KEY_UP] = 'UP'
|
139
|
+
@kh[KEY_DOWN] = 'DOWN'
|
140
|
+
KEY_LEFT = '[D'
|
141
|
+
KEY_RIGHT = '[C'
|
142
|
+
@kh["\eOQ"] = 'F2'
|
143
|
+
@kh["\eOR"] = 'F3'
|
144
|
+
@kh["\eOS"] = 'F4'
|
145
|
+
@kh[KEY_LEFT] = 'LEFT'
|
146
|
+
@kh[KEY_RIGHT] = 'RIGHT'
|
147
|
+
KEY_F5 = '[15~'
|
148
|
+
KEY_F6 = '[17~'
|
149
|
+
KEY_F7 = '[18~'
|
150
|
+
KEY_F8 = '[19~'
|
151
|
+
KEY_F9 = '[20~'
|
152
|
+
KEY_F10 = '[21~'
|
153
|
+
KEY_S_F1 = '[1;2P'
|
154
|
+
@kh[KEY_F5] = 'F5'
|
155
|
+
@kh[KEY_F6] = 'F6'
|
156
|
+
@kh[KEY_F7] = 'F7'
|
157
|
+
@kh[KEY_F8] = 'F8'
|
158
|
+
@kh[KEY_F9] = 'F9'
|
159
|
+
@kh[KEY_F10] = 'F10'
|
154
160
|
# testing out shift+Function. these are the codes my kb generates
|
155
|
-
|
156
|
-
|
161
|
+
@kh[KEY_S_F1] = 'S-F1'
|
162
|
+
@kh['[1;2Q'] = 'S-F2'
|
157
163
|
|
158
164
|
# copied from fff
|
159
165
|
def clear_screen
|
@@ -166,8 +172,8 @@ def clear_screen
|
|
166
172
|
# Also sets cursor to (0,0).
|
167
173
|
# ENV["TMUX:+\e[2J]"],
|
168
174
|
printf("\e[%sH\e[9999C\e[1J\e[1;%sr", \
|
169
|
-
|
170
|
-
|
175
|
+
@glines - 0, # was 2
|
176
|
+
@glines) # was grows
|
171
177
|
end
|
172
178
|
|
173
179
|
# copied from fff
|
@@ -196,9 +202,9 @@ def setup_terminal
|
|
196
202
|
# '\e[2J': Clear the screen.
|
197
203
|
# '\e[1;Nr': Limit scrolling to scrolling area.
|
198
204
|
# Also sets cursor to (0,0).
|
199
|
-
# printf("\e[?1049h\e[?7l\e[?25l\e[2J\e[1;%sr",
|
205
|
+
# printf("\e[?1049h\e[?7l\e[?25l\e[2J\e[1;%sr", @glines)
|
200
206
|
# 2019-03-29 - XXX temporarily not hiding cursor to see if we can place it.
|
201
|
-
printf("\e[?1049h\e[?7l\e[?25h\e[2J\e[1;%sr",
|
207
|
+
printf("\e[?1049h\e[?7l\e[?25h\e[2J\e[1;%sr", @glines)
|
202
208
|
# earlier glines was grows
|
203
209
|
|
204
210
|
# Hide echoing of user input
|
@@ -214,6 +220,10 @@ def readline prompt='>'
|
|
214
220
|
print "\e[?25h"
|
215
221
|
system 'stty echo'
|
216
222
|
begin
|
223
|
+
if prompt.length > 40
|
224
|
+
puts prompt
|
225
|
+
prompt = '>'
|
226
|
+
end
|
217
227
|
target = Readline.readline(prompt, true)
|
218
228
|
rescue Interrupt
|
219
229
|
return nil
|
@@ -256,7 +266,7 @@ def get_char
|
|
256
266
|
# puts "got #{k}"
|
257
267
|
buff += k.chr
|
258
268
|
else
|
259
|
-
x =
|
269
|
+
x = @kh[buff]
|
260
270
|
return x if x
|
261
271
|
|
262
272
|
# puts "returning with #{buff}"
|
@@ -280,36 +290,37 @@ end
|
|
280
290
|
|
281
291
|
## GLOBALS
|
282
292
|
# hints or shortcuts to get to files without moving
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
#
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
#
|
300
|
-
|
301
|
-
|
302
|
-
|
293
|
+
IDX = ('a'..'y').to_a
|
294
|
+
IDX.delete 'q'
|
295
|
+
IDX.concat ('za'..'zz').to_a
|
296
|
+
IDX.concat ('Za'..'Zz').to_a
|
297
|
+
IDX.concat ('ZA'..'ZZ').to_a
|
298
|
+
IDX.freeze
|
299
|
+
|
300
|
+
@selected_files = []
|
301
|
+
@bookmarks = {}
|
302
|
+
@mode = nil
|
303
|
+
@glines = `tput lines`.to_i
|
304
|
+
@gcols = `tput cols`.to_i
|
305
|
+
@grows = @glines - 3 # can be a func
|
306
|
+
# @pagesize = 60
|
307
|
+
@gviscols = 3
|
308
|
+
@pagesize = @grows * @gviscols # can be a func
|
309
|
+
@stact = 0 # used when panning a folder to next column
|
310
|
+
# @editor_mode = true
|
311
|
+
@editor_mode = false # changed 2018-03-12 - so we start in pager mode
|
312
|
+
@enhanced_mode = true
|
313
|
+
@visual_block_start = nil
|
303
314
|
PAGER_COMMAND = {
|
304
315
|
text: 'most',
|
305
316
|
image: 'open',
|
306
317
|
zip: 'tar ztvf %% | most',
|
307
318
|
unknown: 'open'
|
308
|
-
}
|
309
|
-
|
310
|
-
@movement = @old_cursor = nil
|
311
|
-
$selection_mode = 1 # single select
|
319
|
+
}.freeze
|
320
|
+
@dir_position = {}
|
321
|
+
@movement = @old_cursor = nil # cursor movement has happened only, don't repaint
|
312
322
|
## FLAGS
|
323
|
+
@multiple_selection = true # single select
|
313
324
|
@group_dirs = true
|
314
325
|
# truncate long filenames from :right, :left or :center.
|
315
326
|
@truncate_from = :center
|
@@ -319,26 +330,56 @@ $selection_mode = 1 # single select
|
|
319
330
|
@selected_files_escaped_flag = false
|
320
331
|
@keys_to_clear = nil
|
321
332
|
|
333
|
+
# See toggle_value
|
334
|
+
# we need to set these on startup
|
335
|
+
@toggles = {
|
336
|
+
ignore_case: true,
|
337
|
+
# group_directories: true,
|
338
|
+
long_listing: false,
|
339
|
+
enhanced_mode: true,
|
340
|
+
visual_mode: false,
|
341
|
+
display_file_stats: true,
|
342
|
+
toggle_selected_files_fullpath_flag: false,
|
343
|
+
toggle_selected_files_escaped_flag: false,
|
344
|
+
multiple_selection: true,
|
345
|
+
editor_mode: false,
|
346
|
+
selection_mode: false, # typing hint adds to selection, does not open
|
347
|
+
debug_flag: false,
|
348
|
+
toggle_filename_status_line: true
|
349
|
+
}
|
350
|
+
# @cycles = { truncate_from: :center,
|
351
|
+
# show_hidden: nil }
|
352
|
+
# @enums = {
|
353
|
+
# truncate_from: %i[left right center],
|
354
|
+
# show_hidden: [nil, 'D']
|
355
|
+
# }
|
356
|
+
# var is name of variable to be set
|
357
|
+
@options = {
|
358
|
+
truncate_from: { current: :center, values: %i[left right center], var: :truncate_from },
|
359
|
+
group_directories: { current: :first, values: %i[first none last] },
|
360
|
+
show_hidden: { current: nil, values: [nil, 'D'], var: :hidden }
|
361
|
+
}
|
362
|
+
|
322
363
|
## ----------------- CONSTANTS ----------------- ##
|
323
|
-
GMARK = '*'
|
324
|
-
CURMARK = '>'
|
364
|
+
GMARK = '*'
|
365
|
+
CURMARK = '>'
|
325
366
|
MSCROLL = 10
|
326
|
-
SPACE = ' '
|
327
|
-
SEPARATOR = '-------'
|
328
|
-
CLEAR = "\e[0m"
|
329
|
-
BOLD = "\e[1m"
|
330
|
-
BOLD_OFF = "\e[22m"
|
331
|
-
RED = "\e[31m"
|
332
|
-
ON_RED = "\e[41m"
|
333
|
-
GREEN = "\e[32m"
|
334
|
-
YELLOW = "\e[33m"
|
335
|
-
BLUE = "\e[1;34m"
|
336
|
-
MAGENTA = "\e[35m"
|
337
|
-
CYAN = "\e[36m"
|
338
|
-
|
339
|
-
ON_BLUE = "\e[44m"
|
340
|
-
REVERSE = "\e[7m"
|
341
|
-
UNDERLINE = "\e[4m"
|
367
|
+
SPACE = ' '
|
368
|
+
SEPARATOR = '-------'
|
369
|
+
CLEAR = "\e[0m"
|
370
|
+
BOLD = "\e[1m"
|
371
|
+
BOLD_OFF = "\e[22m"
|
372
|
+
RED = "\e[31m"
|
373
|
+
ON_RED = "\e[41m"
|
374
|
+
GREEN = "\e[32m"
|
375
|
+
YELLOW = "\e[33m"
|
376
|
+
BLUE = "\e[1;34m"
|
377
|
+
MAGENTA = "\e[35m"
|
378
|
+
CYAN = "\e[36m"
|
379
|
+
|
380
|
+
ON_BLUE = "\e[44m"
|
381
|
+
REVERSE = "\e[7m"
|
382
|
+
UNDERLINE = "\e[4m"
|
342
383
|
CURSOR_COLOR = REVERSE
|
343
384
|
|
344
385
|
# NOTE: that osx uses LSCOLORS which only has colors for filetypes not
|
@@ -351,45 +392,44 @@ CURSOR_COLOR = REVERSE
|
|
351
392
|
|
352
393
|
# This hash contains color codes for extensions. It is updated from
|
353
394
|
# LS_COLORS.
|
354
|
-
|
395
|
+
@ls_color = {
|
355
396
|
'.rb' => RED,
|
356
397
|
'.tgz' => MAGENTA,
|
357
398
|
'.zip' => MAGENTA,
|
358
399
|
'.torrent' => GREEN,
|
359
400
|
'.srt' => GREEN,
|
360
|
-
'.part' =>
|
401
|
+
'.part' => "\e[40;31;01m",
|
361
402
|
'.sh' => CYAN
|
362
403
|
}
|
363
404
|
# This hash contains colors for file patterns, updated from LS_COLORS
|
364
|
-
|
405
|
+
@ls_pattern = {}
|
365
406
|
# This hash contains colors for file types, updated from LS_COLORS
|
366
407
|
# Default values in absence of LS_COLORS
|
367
|
-
|
408
|
+
@ls_ftype = {
|
368
409
|
'directory' => BLUE,
|
369
|
-
'link'
|
370
|
-
'mi'
|
371
|
-
'or'
|
372
|
-
'ex'
|
410
|
+
'link' => "\e[01;36m",
|
411
|
+
'mi' => "\e[01;31;7m",
|
412
|
+
'or' => "\e[40;31;01m",
|
413
|
+
'ex' => "\e[01;32m"
|
373
414
|
}
|
374
415
|
## --------------------------------------------- ##
|
375
416
|
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
417
|
+
@patt = nil
|
418
|
+
@ignorecase = true
|
419
|
+
@quitting = false
|
420
|
+
@modified = @writing = false
|
421
|
+
@visited_files = []
|
381
422
|
## dir stack for popping
|
382
|
-
|
423
|
+
@visited_dirs = []
|
383
424
|
## dirs where some work has been done, for saving and restoring
|
384
|
-
|
425
|
+
@used_dirs = []
|
385
426
|
# zsh o = order m = modified time
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
$history = []
|
427
|
+
@default_sort_order = 'Om'
|
428
|
+
@sorto = @default_sort_order
|
429
|
+
@viewctr = 0
|
390
430
|
## sta is where view (viewport) begins, cursor is current row/file
|
391
|
-
|
392
|
-
|
431
|
+
@sta = @cursor = 0
|
432
|
+
@visual_mode = false
|
393
433
|
@status_color = 4 # status line, can be 2 3 4 5 6
|
394
434
|
@status_color_right = 8 # status line right part
|
395
435
|
|
@@ -406,20 +446,21 @@ def read_directory
|
|
406
446
|
# -r Ignore the escape conventions of echo.
|
407
447
|
# zshglob M = MARK_DIRS
|
408
448
|
|
409
|
-
|
410
|
-
#
|
411
|
-
|
412
|
-
|
413
|
-
|
449
|
+
@filterstr ||= 'M' # XXX can we remove from here
|
450
|
+
# @files = `zsh -c 'print -rl -- *(#{@sorto}#{@hidden}#{@filterstr})'`.split("\n")
|
451
|
+
# @files = list_files
|
452
|
+
list_files
|
453
|
+
# @files = group_directories_first(@files) if @toggles[:group_directories]
|
454
|
+
group_directories_first
|
455
|
+
return unless cget(:enhanced_mode)
|
414
456
|
|
415
457
|
enhance_file_list
|
416
|
-
|
458
|
+
@files = @files.uniq
|
417
459
|
end
|
418
460
|
|
419
461
|
# return a list of directory contents sorted as per sort order
|
420
462
|
# NOTE: FNM_CASEFOLD does not work with Dir.glob
|
421
|
-
def list_files dir='*', sorto
|
422
|
-
|
463
|
+
def list_files dir='*', sorto=@sorto, hidden=@hidden, _filter=@filterstr
|
423
464
|
dir += '/*' if File.directory?(dir)
|
424
465
|
dir = dir.gsub('//', '/')
|
425
466
|
|
@@ -444,7 +485,7 @@ def list_files dir='*', sorto=$sorto, hidden=$hidden, filter=$filterstr
|
|
444
485
|
|
445
486
|
# sort by time and then reverse so latest first.
|
446
487
|
sorted_files = if hidden == 'D'
|
447
|
-
Dir.glob(dir, File::FNM_DOTMATCH)
|
488
|
+
Dir.glob(dir, File::FNM_DOTMATCH) - %w[. ..]
|
448
489
|
else
|
449
490
|
Dir.glob(dir)
|
450
491
|
end
|
@@ -461,14 +502,15 @@ def list_files dir='*', sorto=$sorto, hidden=$hidden, filter=$filterstr
|
|
461
502
|
end
|
462
503
|
end
|
463
504
|
|
464
|
-
sorted_files.sort! { |w1, w2| w1.casecmp(w2) } if func == :path &&
|
505
|
+
sorted_files.sort! { |w1, w2| w1.casecmp(w2) } if func == :path && cget(:ignore_case)
|
465
506
|
|
466
507
|
# zsh gives mtime sort with latest first, ruby gives latest last
|
467
508
|
sorted_files.reverse! if sorto[0] == 'O'
|
468
509
|
|
469
510
|
# add slash to directories
|
470
511
|
sorted_files = add_slash sorted_files
|
471
|
-
return sorted_files
|
512
|
+
# return sorted_files
|
513
|
+
@files = sorted_files
|
472
514
|
end
|
473
515
|
# ------------- end of read_directory --------------------------------#
|
474
516
|
|
@@ -484,61 +526,61 @@ end
|
|
484
526
|
|
485
527
|
# ------------------- create_viewport ------------------ #
|
486
528
|
def create_viewport
|
487
|
-
|
488
|
-
if
|
489
|
-
|
529
|
+
@view = if @patt
|
530
|
+
if cget(:ignore_case)
|
531
|
+
@files.grep(/#{@patt}/i)
|
490
532
|
else
|
491
|
-
|
533
|
+
@files.grep(/#{@patt}/)
|
492
534
|
end
|
493
535
|
else
|
494
|
-
|
536
|
+
@files
|
495
537
|
end
|
496
538
|
|
497
|
-
fl =
|
498
|
-
|
499
|
-
|
539
|
+
fl = @view.size
|
540
|
+
@sta = 0 if @sta >= fl || @sta < 0
|
541
|
+
@cursor = 0 if @cursor >= fl || @cursor < 0
|
500
542
|
|
501
543
|
# NOTE if we make cursor zero, then it can be < sta so in the next line
|
502
544
|
# it will be made equal to sta which we may not want
|
503
|
-
|
545
|
+
@cursor = @sta if @sta > @cursor
|
504
546
|
|
505
547
|
# viewport are the files that are visible, subset of view
|
506
|
-
|
548
|
+
@viewport = @view[@sta, @pagesize]
|
507
549
|
end
|
508
550
|
# ------------- end of create_viewport --------------------------------#
|
509
551
|
|
510
552
|
# ------------------- print_title ------------------ #
|
511
553
|
def print_title
|
512
|
-
|
513
554
|
# print help line and version
|
514
555
|
print "#{GREEN}#{@help} #{BLUE}cetus #{VERSION}#{CLEAR}\n"
|
515
556
|
|
516
557
|
# print 1 of n files, sort order, filter etc details
|
517
|
-
|
558
|
+
@title ||= Dir.pwd.sub(ENV['HOME'], '~')
|
518
559
|
|
519
560
|
# Add bookmark next to name of dir, if exists
|
520
|
-
|
561
|
+
# FIXME This should not happen in other listings like find selected files etc
|
562
|
+
bm = @bookmarks.key(Dir.pwd)
|
521
563
|
bm = " ('#{bm})" if bm
|
522
564
|
|
523
|
-
fin =
|
524
|
-
fl =
|
565
|
+
fin = @sta + @viewport.size
|
566
|
+
fl = @view.size
|
525
567
|
|
526
568
|
# fix count of entries so separator and enhanced entries don't show up
|
527
|
-
if
|
528
|
-
ix =
|
529
|
-
fin =
|
569
|
+
if cget(:enhanced_mode)
|
570
|
+
ix = @viewport.index SEPARATOR
|
571
|
+
fin = @sta + ix if ix
|
530
572
|
|
531
|
-
ix =
|
573
|
+
ix = @view.index SEPARATOR
|
532
574
|
fl = ix if ix
|
533
575
|
end
|
534
576
|
|
535
|
-
t = fl.zero? ? "#{
|
577
|
+
t = fl.zero? ? "#{@title}#{bm} No files." : "#{@title}#{bm} #{@sta + 1} to #{fin} of #{fl} #{@sorto} F:#{@filterstr}"
|
536
578
|
|
537
579
|
# don't exceed columns while printing
|
538
|
-
t = t[t.size -
|
580
|
+
t = t[t.size - @gcols..-1] if t.size >= @gcols
|
539
581
|
|
540
582
|
print "#{BOLD}#{t}#{CLEAR}\n"
|
541
|
-
print
|
583
|
+
print 'EMPTY' if fl.zero?
|
542
584
|
end
|
543
585
|
# ------------- end of print_title --------------------------------#
|
544
586
|
|
@@ -547,42 +589,41 @@ end
|
|
547
589
|
# called when page changes, so we only put directory name)
|
548
590
|
# NOTE: called only from draw_directory.
|
549
591
|
def status_line
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
592
|
+
# prompt
|
593
|
+
v_mm = @mode ? "[#{@mode}] " : ''
|
594
|
+
cf = current_file
|
595
|
+
@message = ' | No matches. Press ESCAPE' if @patt && !cf
|
554
596
|
|
555
|
-
|
597
|
+
clear_last_line
|
556
598
|
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
end
|
567
|
-
end
|
568
|
-
# move to beginning of line, reset text mode after printing
|
569
|
-
# patt and message are together, no gap, why not ? 2019-04-08 -
|
570
|
-
if $patt && $patt != ''
|
571
|
-
patt = "[/#{$patt}]"
|
572
|
-
patt[-1] = '/i]' if $ignorecase
|
599
|
+
# Print the filename at the right side of the status line
|
600
|
+
# sometimes due to search, there is no file
|
601
|
+
if cf
|
602
|
+
if cget(:debug_flag)
|
603
|
+
# XXX this will not work on file basis FIXME
|
604
|
+
print_debug_info cf
|
605
|
+
else
|
606
|
+
# print_on_right "#{Dir.pwd}"
|
607
|
+
print_filename_status_line if @filename_status_line
|
573
608
|
end
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
#
|
579
|
-
|
580
|
-
|
581
|
-
|
609
|
+
end
|
610
|
+
# move to beginning of line, reset text mode after printing
|
611
|
+
# patt and message are together, no gap, why not ? 2019-04-08 -
|
612
|
+
if @patt && @patt != ''
|
613
|
+
patt = "[/#{@patt}" + ']' # to get unfrozen string
|
614
|
+
patt[-1] = '/i]' if cget(:ignore_case)
|
615
|
+
end
|
616
|
+
# bring cursor to start of line
|
617
|
+
# add background color
|
618
|
+
# print mode
|
619
|
+
# print search pattern if any
|
620
|
+
# print message if any
|
621
|
+
# print "\r#{v_mm}#{patt}#{@message}\e[m"
|
622
|
+
print "\r\e[33;4#{@status_color}m#{v_mm}#{patt}#{@message}\e[m"
|
582
623
|
end
|
583
624
|
|
584
|
-
def print_debug_info cf=current_file
|
585
|
-
print_on_right "len:#{cf.length}/#{
|
625
|
+
def print_debug_info cf=current_file
|
626
|
+
print_on_right "len:#{cf.length}/#{@temp_wid} = #{@sta},#{@cursor},#{@stact},#{@viewport.size},#{@grows} | #{cf}"
|
586
627
|
end
|
587
628
|
|
588
629
|
def print_filename_status_line cf=current_file
|
@@ -594,7 +635,7 @@ def print_filename_status_line cf=current_file
|
|
594
635
|
end
|
595
636
|
|
596
637
|
mtime = if !File.exist? ff
|
597
|
-
# take care of dead links lstat
|
638
|
+
# take care of dead links lstat
|
598
639
|
date_format(File.lstat(ff).mtime) if File.symlink?(ff)
|
599
640
|
else
|
600
641
|
date_format(File.stat(ff).mtime)
|
@@ -606,11 +647,11 @@ end
|
|
606
647
|
|
607
648
|
# should we do a read of the dir
|
608
649
|
def rescan?
|
609
|
-
|
650
|
+
@rescan_required
|
610
651
|
end
|
611
652
|
|
612
653
|
def rescan_required flag=true
|
613
|
-
|
654
|
+
@rescan_required = flag
|
614
655
|
redraw_required if flag
|
615
656
|
end
|
616
657
|
|
@@ -624,7 +665,6 @@ def draw_directory
|
|
624
665
|
# all this seems to be happening for each keystroke even if
|
625
666
|
# not really required. FIXME maybe reduce and call when required
|
626
667
|
|
627
|
-
|
628
668
|
# view consists of all files (filtered by pattern if necessary)
|
629
669
|
# viewport is only that subset of view that is displayed on screen
|
630
670
|
create_viewport
|
@@ -632,11 +672,11 @@ def draw_directory
|
|
632
672
|
print_title
|
633
673
|
|
634
674
|
# add hint and format the line
|
635
|
-
buff = format_array
|
675
|
+
buff = format_array @viewport
|
636
676
|
|
637
677
|
# break viewport into as many columns as required
|
638
678
|
# This is where directories get their blue color
|
639
|
-
buff = columnate buff,
|
679
|
+
buff = columnate buff, @grows
|
640
680
|
|
641
681
|
# starts printing array on line 3
|
642
682
|
buff.each { |line| print line, "\n" }
|
@@ -655,18 +695,18 @@ end
|
|
655
695
|
# FIXME color of original hint lost
|
656
696
|
def place_cursor
|
657
697
|
# clear_cursor
|
658
|
-
# hint = get_shortcut(
|
659
|
-
c =
|
660
|
-
if c <
|
698
|
+
# hint = get_shortcut(@cursor)
|
699
|
+
c = @cursor - @sta
|
700
|
+
if c < @grows
|
661
701
|
# system "tput cup #{c + 2} 0"
|
662
702
|
# print "\e[#{c + 3};0H"
|
663
703
|
tput_cup c, 0
|
664
704
|
# print "#{CURSOR_COLOR}#{hint} >#{CLEAR}"
|
665
705
|
return
|
666
706
|
end
|
667
|
-
wid = get_width
|
668
|
-
rows = c %
|
669
|
-
cols = (c /
|
707
|
+
wid = get_width @viewport.size, @grows
|
708
|
+
rows = c % @grows
|
709
|
+
cols = (c / @grows) * wid
|
670
710
|
# system "tput cup #{rows + 2} #{cols}"
|
671
711
|
# print "\e[#{rows + 3};#{cols + 1}H"
|
672
712
|
tput_cup rows, cols
|
@@ -679,7 +719,7 @@ def clear_cursor
|
|
679
719
|
return unless @old_cursor
|
680
720
|
|
681
721
|
hint = get_shortcut(@old_cursor)
|
682
|
-
if @old_cursor <
|
722
|
+
if @old_cursor < @grows
|
683
723
|
# FIXME: faster way of getting here ? see fff
|
684
724
|
# system "tput cup #{@old_cursor + 2} 0"
|
685
725
|
tput_cup @old_cursor, 0
|
@@ -687,9 +727,9 @@ def clear_cursor
|
|
687
727
|
print "#{hint} "
|
688
728
|
return
|
689
729
|
end
|
690
|
-
wid = get_width
|
691
|
-
rows = @old_cursor %
|
692
|
-
cols = (@old_cursor /
|
730
|
+
wid = get_width @viewport.size, @grows
|
731
|
+
rows = @old_cursor % @grows
|
732
|
+
cols = (@old_cursor / @grows) * wid
|
693
733
|
# system "tput cup #{rows + 2} #{cols}"
|
694
734
|
tput_cup rows, cols
|
695
735
|
# print "#{CURSOR_COLOR}#{hint} >#{CLEAR}"
|
@@ -702,7 +742,10 @@ def tput_cup row, col
|
|
702
742
|
print "\e[#{row + 3};#{col + 1}H"
|
703
743
|
end
|
704
744
|
|
705
|
-
|
745
|
+
# FIXME flag is unused ???? XXX
|
746
|
+
def redraw_required flag=true
|
747
|
+
@redraw_required = flag
|
748
|
+
end
|
706
749
|
|
707
750
|
def resolve_key key
|
708
751
|
ret = true
|
@@ -710,11 +753,11 @@ def resolve_key key
|
|
710
753
|
|
711
754
|
if key.match?(/^[a-pr-zZ]$/)
|
712
755
|
# hint mode
|
713
|
-
ret = select_hint
|
756
|
+
ret = select_hint @viewport, key
|
714
757
|
elsif key == 'BACKSPACE'
|
715
758
|
# do we really need this TODO
|
716
|
-
|
717
|
-
|
759
|
+
@patt = @patt[0..-2] if @patt && !@patt.empty?
|
760
|
+
@message = @patt = nil if @patt == ''
|
718
761
|
elsif '0123456789'.include? key
|
719
762
|
resolve_numeric_key key
|
720
763
|
else
|
@@ -725,10 +768,10 @@ end
|
|
725
768
|
|
726
769
|
def resolve_binding key
|
727
770
|
# fetch binding for key
|
728
|
-
x =
|
771
|
+
x = @bindings[key]
|
729
772
|
|
730
773
|
# remove comment string so only binding is left
|
731
|
-
x,
|
774
|
+
x, = x.split(':') if x
|
732
775
|
|
733
776
|
# split into binding and args
|
734
777
|
x = x.split if x
|
@@ -738,13 +781,14 @@ def resolve_binding key
|
|
738
781
|
send(binding, *args) if binding
|
739
782
|
else
|
740
783
|
# perror "No binding for #{key}"
|
784
|
+
@log.debug "No binding for #{key}"
|
741
785
|
end
|
742
786
|
end
|
743
787
|
|
744
788
|
# numbers represent quick bookmarks
|
745
789
|
# if bookmark exists, go to else create it.
|
746
790
|
def resolve_numeric_key key
|
747
|
-
d =
|
791
|
+
d = @bookmarks[key]
|
748
792
|
if d
|
749
793
|
change_dir d
|
750
794
|
return
|
@@ -755,7 +799,7 @@ def resolve_numeric_key key
|
|
755
799
|
end
|
756
800
|
|
757
801
|
def set_bookmark key, dir=Dir.pwd
|
758
|
-
|
802
|
+
@bookmarks[key] = dir
|
759
803
|
end
|
760
804
|
|
761
805
|
## write current dir to a file so we can ccd to it when exiting
|
@@ -775,7 +819,7 @@ KILO_SIZE = 1024.0
|
|
775
819
|
|
776
820
|
# Return the file size with a readable style.
|
777
821
|
# NOTE format is a kernel method.
|
778
|
-
def readable_file_size
|
822
|
+
def readable_file_size size, precision
|
779
823
|
if size < KILO_SIZE then format('%d B', size)
|
780
824
|
elsif size < MEGA_SIZE then format("%.#{precision}f K", (size / KILO_SIZE))
|
781
825
|
elsif size < GIGA_SIZE then format("%.#{precision}f M", (size / MEGA_SIZE))
|
@@ -784,7 +828,7 @@ def readable_file_size(size, precision)
|
|
784
828
|
end
|
785
829
|
|
786
830
|
## format date for file given stat
|
787
|
-
def date_format
|
831
|
+
def date_format t
|
788
832
|
t.strftime '%Y/%m/%d'
|
789
833
|
end
|
790
834
|
|
@@ -802,7 +846,7 @@ def columnate ary, siz
|
|
802
846
|
# if less than sz then 1 col and full width
|
803
847
|
#
|
804
848
|
wid = get_width ary.size, siz
|
805
|
-
|
849
|
+
@temp_wid = wid
|
806
850
|
|
807
851
|
# ix refers to the index in the complete file list, wherease we only show 60 at a time
|
808
852
|
ix = 0
|
@@ -812,19 +856,18 @@ def columnate ary, siz
|
|
812
856
|
ctr = 0
|
813
857
|
while ctr < siz
|
814
858
|
|
815
|
-
f = ary[ix]
|
859
|
+
f = ary[ix].dup # 2019-04-14 - frozen_string_literal
|
816
860
|
# f is not just filename but marker and hint
|
817
861
|
|
818
862
|
# check to see if we need to truncate
|
819
863
|
# 2019-03-18 - truncate from middle of string.
|
820
864
|
# 2019-03-24 - truncate and size to take care of color codes
|
821
865
|
# fsz = f.size
|
822
|
-
unformatted_len,
|
866
|
+
unformatted_len, = filename_len(f)
|
823
867
|
if unformatted_len > wid
|
824
868
|
# take the excess out from the center on both sides
|
825
869
|
f = truncate_formatted_filename(f, unformatted_len, wid)
|
826
870
|
|
827
|
-
|
828
871
|
elsif unformatted_len < wid
|
829
872
|
|
830
873
|
# f = f.ljust(wid)
|
@@ -885,10 +928,10 @@ def truncate_formatted_filename f, unformatted_len, wid
|
|
885
928
|
# there could be escape codes of varying length
|
886
929
|
sindex = f.index ' '
|
887
930
|
# if f[0] == "\e"
|
888
|
-
|
889
|
-
|
931
|
+
# mindex = f.index('m')
|
932
|
+
# hintsize = sindex - mindex - 1
|
890
933
|
# else
|
891
|
-
|
934
|
+
# hintsize = sindex - 1
|
892
935
|
# end
|
893
936
|
# f[0..sindex + 1] + '<' + f[-wid + hintsize..-1] + ' '
|
894
937
|
# @log.debug "XXX #{excess}: #{f} / #{wid}"
|
@@ -901,12 +944,12 @@ def truncate_formatted_filename f, unformatted_len, wid
|
|
901
944
|
end
|
902
945
|
|
903
946
|
def get_width arysz, siz
|
904
|
-
ars = [
|
947
|
+
ars = [@pagesize, arysz].min
|
905
948
|
d = 0
|
906
|
-
return
|
949
|
+
return @gcols - d if ars <= siz
|
907
950
|
|
908
951
|
tmp = (ars * 1.000 / siz).ceil
|
909
|
-
wid =
|
952
|
+
wid = @gcols / tmp - d
|
910
953
|
wid
|
911
954
|
end
|
912
955
|
|
@@ -923,7 +966,7 @@ def filename_len name
|
|
923
966
|
end
|
924
967
|
|
925
968
|
## formats the data with number, mark and details
|
926
|
-
def format_array
|
969
|
+
def format_array ary
|
927
970
|
# buff = Array.new
|
928
971
|
buff = Array.new(ary.size)
|
929
972
|
return buff if ary.nil? || ary.empty?
|
@@ -941,15 +984,12 @@ def format_array(ary)
|
|
941
984
|
cur = SPACE
|
942
985
|
ind = get_shortcut(ix)
|
943
986
|
|
944
|
-
|
945
987
|
# Handle separator before enhanced file list.
|
946
988
|
# We do lost a shortcut
|
947
|
-
if f == SEPARATOR
|
948
|
-
ind = cur = mark = '-'
|
949
|
-
end
|
989
|
+
ind = cur = mark = '-' if f == SEPARATOR
|
950
990
|
|
951
991
|
mark = '+' if visited? f
|
952
|
-
# cur = CURMARK if ix +
|
992
|
+
# cur = CURMARK if ix + @sta == @cursor # 2019-03-29 - removed reduced calls
|
953
993
|
# NOTE seems like f and ary[ix] are the same
|
954
994
|
mark = GMARK if selected?(ary[ix])
|
955
995
|
|
@@ -957,37 +997,14 @@ def format_array(ary)
|
|
957
997
|
fullname = f[0] == '~' ? File.expand_path(f) : f
|
958
998
|
color = color_for fullname
|
959
999
|
|
960
|
-
if
|
961
|
-
begin
|
962
|
-
if File.exist? f
|
963
|
-
stat = File.stat(f)
|
964
|
-
else
|
965
|
-
# dead link
|
966
|
-
if File.symlink?(f)
|
967
|
-
stat = File.lstat(f)
|
968
|
-
else
|
969
|
-
# remove last character and get stat
|
970
|
-
last = f[-1]
|
971
|
-
stat = File.stat(f.chop) if last == ' ' || last == '@' || last == '*'
|
972
|
-
end
|
973
|
-
end
|
974
|
-
# this is for saved directories etc which are shortened
|
975
|
-
stat ||= File.stat(File.expand_path(f))
|
976
|
-
f = format('%10s %s %s', readable_file_size(stat.size, 1),
|
977
|
-
date_format(stat.mtime), f)
|
978
|
-
rescue StandardError => e
|
979
|
-
@log.warn "WARN::#{e}: FILE:: #{f}"
|
980
|
-
f = format('%10s %s %s', '?', '??????????', f)
|
981
|
-
end
|
982
|
-
end
|
1000
|
+
f = format_long_listing(f) if @long_listing
|
983
1001
|
|
984
1002
|
# 2019-03-31 - replace unprintable chars with ?
|
985
1003
|
f = f.gsub(/[^[:print:]]/, '?')
|
986
1004
|
|
1005
|
+
# FIXME do in one operation. otherwise creating string 2 times
|
987
1006
|
s = "#{ind}#{mark}#{cur}#{f}"
|
988
|
-
if color
|
989
|
-
s = "#{color}#{s}#{CLEAR}"
|
990
|
-
end
|
1007
|
+
s = "#{color}#{s}#{CLEAR}" if color
|
991
1008
|
|
992
1009
|
buff[ctr] = s
|
993
1010
|
|
@@ -997,22 +1014,53 @@ def format_array(ary)
|
|
997
1014
|
buff
|
998
1015
|
end
|
999
1016
|
|
1017
|
+
def format_long_listing f
|
1018
|
+
return unless @long_listing
|
1019
|
+
return format('%10s %s %s', '-', '----------', f) if f == SEPARATOR
|
1020
|
+
|
1021
|
+
begin
|
1022
|
+
if File.exist? f
|
1023
|
+
stat = File.stat(f)
|
1024
|
+
elsif f[0] == '~'
|
1025
|
+
stat = File.stat(File.expand_path(f))
|
1026
|
+
elsif File.symlink?(f)
|
1027
|
+
# dead link
|
1028
|
+
stat = File.lstat(f)
|
1029
|
+
else
|
1030
|
+
# remove last character and get stat
|
1031
|
+
last = f[-1]
|
1032
|
+
stat = File.stat(f.chop) if last == ' ' || last == '@' || last == '*'
|
1033
|
+
end
|
1034
|
+
|
1035
|
+
f = format('%10s %s %s',
|
1036
|
+
readable_file_size(stat.size, 1),
|
1037
|
+
date_format(stat.mtime),
|
1038
|
+
f)
|
1039
|
+
|
1040
|
+
rescue StandardError => e
|
1041
|
+
@log.warn "WARN::#{e}: FILE:: #{f}"
|
1042
|
+
f = format('%10s %s %s', '?', '??????????', f)
|
1043
|
+
end
|
1044
|
+
|
1045
|
+
return f
|
1046
|
+
end
|
1047
|
+
|
1000
1048
|
# determine color for a filename based on extension, then pattern, then filetype
|
1001
1049
|
def color_for fname
|
1002
1050
|
return nil if fname == SEPARATOR
|
1003
1051
|
|
1004
1052
|
extension = File.extname(fname)
|
1005
|
-
color =
|
1053
|
+
color = @ls_color[extension]
|
1006
1054
|
return color if color
|
1007
1055
|
|
1008
1056
|
# check file against patterns
|
1009
1057
|
if File.file?(fname)
|
1010
|
-
|
1058
|
+
@ls_pattern.each_pair do |k, v|
|
1011
1059
|
# if fname.match?(/k/)
|
1012
|
-
if
|
1060
|
+
if /#{k}/.match?(fname)
|
1013
1061
|
# @log.debug "#{fname} matched #{k}. color is #{v[1..-2]}"
|
1014
1062
|
return v
|
1015
|
-
|
1063
|
+
# else
|
1016
1064
|
# @log.debug "#{fname} di not match #{k}. color is #{v[1..-2]}"
|
1017
1065
|
end
|
1018
1066
|
end
|
@@ -1022,34 +1070,33 @@ def color_for fname
|
|
1022
1070
|
if File.exist? fname
|
1023
1071
|
# @log.debug "Filetype:#{File.ftype(fname)}"
|
1024
1072
|
|
1025
|
-
return
|
1026
|
-
return
|
1073
|
+
return @ls_ftype[File.ftype(fname)] if @ls_ftype.key? File.ftype(fname)
|
1074
|
+
return @ls_ftype['ex'] if File.executable?(fname)
|
1027
1075
|
else
|
1028
1076
|
# orphan file, but fff uses mi
|
1029
|
-
return
|
1077
|
+
return @ls_ftype['mi'] if File.symlink?(fname)
|
1030
1078
|
|
1031
1079
|
@log.warn "FILE WRONG: #{fname}"
|
1032
|
-
return
|
1080
|
+
return @ls_ftype['or']
|
1033
1081
|
end
|
1034
1082
|
|
1035
1083
|
nil
|
1036
1084
|
end
|
1037
1085
|
|
1038
1086
|
def parse_ls_colors
|
1039
|
-
|
1040
1087
|
colorvar = ENV['LS_COLORS']
|
1041
1088
|
if colorvar.nil?
|
1042
|
-
|
1089
|
+
@ls_colors_found = nil
|
1043
1090
|
return
|
1044
1091
|
end
|
1045
|
-
|
1092
|
+
@ls_colors_found = true
|
1046
1093
|
ls = colorvar.split(':')
|
1047
1094
|
ls.each do |e|
|
1048
1095
|
patt, colr = e.split '='
|
1049
1096
|
colr = "\e[" + colr + 'm'
|
1050
1097
|
if e.start_with? '*.'
|
1051
1098
|
# extension, avoid '*' and use the rest as key
|
1052
|
-
|
1099
|
+
@ls_color[patt[1..-1]] = colr
|
1053
1100
|
# @log.debug "COLOR: Writing extension (#{patt})."
|
1054
1101
|
elsif e[0] == '*'
|
1055
1102
|
# file pattern, this would be a glob pattern not regex
|
@@ -1057,11 +1104,11 @@ def parse_ls_colors
|
|
1057
1104
|
patt = patt.gsub('.', '\.')
|
1058
1105
|
patt = patt.sub('+', '\\\+') # if i put a plus it does not go at all
|
1059
1106
|
patt = patt.gsub('-', '\-')
|
1060
|
-
patt = patt.
|
1107
|
+
patt = patt.tr('?', '.')
|
1061
1108
|
patt = patt.gsub('*', '.*')
|
1062
1109
|
patt = "^#{patt}" if patt[0] != '.'
|
1063
1110
|
patt = "#{patt}$" if patt[-1] != '*'
|
1064
|
-
|
1111
|
+
@ls_pattern[patt] = colr
|
1065
1112
|
# @log.debug "COLOR: Writing file (#{patt})."
|
1066
1113
|
elsif patt.length == 2
|
1067
1114
|
# file type, needs to be mapped to what ruby will return
|
@@ -1078,19 +1125,19 @@ def parse_ls_colors
|
|
1078
1125
|
# ex = file which is executable (ie. has 'x' set in permissions).
|
1079
1126
|
case patt
|
1080
1127
|
when 'di'
|
1081
|
-
|
1128
|
+
@ls_ftype['directory'] = colr
|
1082
1129
|
when 'cd'
|
1083
|
-
|
1130
|
+
@ls_ftype['characterSpecial'] = colr
|
1084
1131
|
when 'bd'
|
1085
|
-
|
1132
|
+
@ls_ftype['blockSpecial'] = colr
|
1086
1133
|
when 'pi'
|
1087
|
-
|
1134
|
+
@ls_ftype['fifo'] = colr
|
1088
1135
|
when 'ln'
|
1089
|
-
|
1136
|
+
@ls_ftype['link'] = colr
|
1090
1137
|
when 'so'
|
1091
|
-
|
1138
|
+
@ls_ftype['socket'] = colr
|
1092
1139
|
else
|
1093
|
-
|
1140
|
+
@ls_ftype[patt] = colr
|
1094
1141
|
end
|
1095
1142
|
# @log.debug "COLOR: ftype #{patt}"
|
1096
1143
|
end
|
@@ -1099,7 +1146,6 @@ end
|
|
1099
1146
|
|
1100
1147
|
## select file based on key pressed
|
1101
1148
|
def select_hint view, key
|
1102
|
-
|
1103
1149
|
ix = get_index(key, view.size)
|
1104
1150
|
return nil unless ix
|
1105
1151
|
|
@@ -1107,11 +1153,11 @@ def select_hint view, key
|
|
1107
1153
|
return nil unless f
|
1108
1154
|
return nil if f == SEPARATOR
|
1109
1155
|
|
1110
|
-
|
1156
|
+
@cursor = @sta + ix
|
1111
1157
|
|
1112
|
-
if
|
1158
|
+
if @mode == 'SEL'
|
1113
1159
|
toggle_select f
|
1114
|
-
elsif
|
1160
|
+
elsif @mode == 'COM'
|
1115
1161
|
# not being called any longer I think
|
1116
1162
|
run_command f
|
1117
1163
|
else
|
@@ -1125,16 +1171,22 @@ def toggle_select f=current_file
|
|
1125
1171
|
if selected? f
|
1126
1172
|
remove_from_selection f
|
1127
1173
|
else
|
1128
|
-
|
1174
|
+
@selected_files.clear unless @multiple_selection
|
1129
1175
|
add_to_selection f
|
1130
1176
|
end
|
1131
|
-
message "#{
|
1177
|
+
message "#{@selected_files.count} files selected. "
|
1132
1178
|
# XXX is it possible to just redraw this line
|
1133
1179
|
redraw_required
|
1134
1180
|
end
|
1135
1181
|
|
1182
|
+
# allow single or multiple selection with C-s key
|
1183
|
+
def toggle_multiple_selection
|
1184
|
+
# @multiple_selection = !@multiple_selection
|
1185
|
+
toggle_value :multiple_selection
|
1186
|
+
end
|
1187
|
+
|
1136
1188
|
## open file or directory
|
1137
|
-
def open_file
|
1189
|
+
def open_file f
|
1138
1190
|
return unless f
|
1139
1191
|
|
1140
1192
|
f = File.expand_path(f) if f[0] == '~'
|
@@ -1149,9 +1201,8 @@ def open_file(f)
|
|
1149
1201
|
f, _nextpos = f.split(':') if f.index(':')
|
1150
1202
|
if File.directory? f
|
1151
1203
|
save_dir_pos
|
1152
|
-
change_dir f
|
1204
|
+
change_dir f # , nextpos
|
1153
1205
|
elsif File.readable? f
|
1154
|
-
# TODO: looks complex pls simplify !! XXX
|
1155
1206
|
comm = opener_for f
|
1156
1207
|
# '%%' will be substituted with the filename. See zip
|
1157
1208
|
comm = if comm.index('%%')
|
@@ -1165,7 +1216,7 @@ def open_file(f)
|
|
1165
1216
|
setup_terminal
|
1166
1217
|
# XXX maybe use absolute_path instead of hardcoding
|
1167
1218
|
f = Dir.pwd + '/' + f if f[0] != '/'
|
1168
|
-
|
1219
|
+
@visited_files.insert(0, f)
|
1169
1220
|
push_used_dirs Dir.pwd
|
1170
1221
|
else
|
1171
1222
|
perror "open_file: (#{f}) not found"
|
@@ -1184,22 +1235,24 @@ end
|
|
1184
1235
|
def edit_current
|
1185
1236
|
command = ENV['EDITOR'] || ENV['VISUAL'] || 'vim'
|
1186
1237
|
run_on_current command
|
1187
|
-
|
1238
|
+
@visited_files.insert(0, current_file)
|
1188
1239
|
end
|
1189
1240
|
|
1190
1241
|
def open_current
|
1191
|
-
opener = /darwin
|
1242
|
+
opener = /darwin/.match?(RUBY_PLATFORM) ? 'open' : 'xdg-open'
|
1192
1243
|
run_on_current opener
|
1193
|
-
|
1244
|
+
@visited_files.insert(0, current_file)
|
1194
1245
|
end
|
1195
1246
|
|
1196
1247
|
# run given command on current file
|
1197
|
-
def run_on_current
|
1248
|
+
def run_on_current command
|
1198
1249
|
f = current_file
|
1199
1250
|
return unless f
|
1251
|
+
|
1200
1252
|
f = File.expand_path(f)
|
1201
1253
|
return unless File.readable?(f)
|
1202
1254
|
|
1255
|
+
f = Shellwords.escape(f)
|
1203
1256
|
clear_screen
|
1204
1257
|
reset_terminal
|
1205
1258
|
comm = "#{command} #{f}"
|
@@ -1213,7 +1266,7 @@ end
|
|
1213
1266
|
# After putting readline in place of gets, pressing a C-c has a delayed effect.
|
1214
1267
|
# It goes into exception block after executing other commands and still
|
1215
1268
|
# does not do the return !
|
1216
|
-
def run_command
|
1269
|
+
def run_command f
|
1217
1270
|
files = Shellwords.join(f)
|
1218
1271
|
count = f.count
|
1219
1272
|
text = if count > 1
|
@@ -1234,7 +1287,7 @@ def run_command(f)
|
|
1234
1287
|
setup_terminal
|
1235
1288
|
rescue StandardError => ex
|
1236
1289
|
perror "Canceled or failed command, (#{ex}) press a key."
|
1237
|
-
@log.warn "RUNCOMMAND: #{ex
|
1290
|
+
@log.warn "RUNCOMMAND: #{ex}"
|
1238
1291
|
return
|
1239
1292
|
end
|
1240
1293
|
|
@@ -1252,7 +1305,7 @@ def change_dir f
|
|
1252
1305
|
|
1253
1306
|
# before leaving a dir we save it in the list, as well as the cursor
|
1254
1307
|
# position, so we can restore that position when we return
|
1255
|
-
|
1308
|
+
@visited_dirs.insert(0, Dir.pwd)
|
1256
1309
|
save_dir_pos
|
1257
1310
|
|
1258
1311
|
f = File.expand_path(f)
|
@@ -1264,24 +1317,25 @@ def change_dir f
|
|
1264
1317
|
end
|
1265
1318
|
|
1266
1319
|
def goto_previous_dir
|
1267
|
-
prev_dir =
|
1320
|
+
prev_dir = @visited_dirs.first
|
1268
1321
|
return unless prev_dir
|
1322
|
+
|
1269
1323
|
change_dir prev_dir
|
1270
1324
|
end
|
1271
1325
|
|
1272
1326
|
def index_of dir
|
1273
|
-
|
1327
|
+
@files.index(dir)
|
1274
1328
|
end
|
1275
1329
|
|
1276
1330
|
## clear sort order and refresh listing, used typically if you are in some view
|
1277
1331
|
# such as visited dirs or files
|
1278
1332
|
def escape
|
1279
|
-
|
1280
|
-
|
1281
|
-
|
1282
|
-
|
1283
|
-
|
1284
|
-
|
1333
|
+
@sorto = nil
|
1334
|
+
@sorto = @default_sort_order
|
1335
|
+
@viewctr = 0
|
1336
|
+
@title = nil
|
1337
|
+
@filterstr = 'M'
|
1338
|
+
@message = nil
|
1285
1339
|
visual_block_clear
|
1286
1340
|
refresh
|
1287
1341
|
end
|
@@ -1289,29 +1343,37 @@ end
|
|
1289
1343
|
## refresh listing after some change like option change, or toggle
|
1290
1344
|
# Should we check selected_files array also for deleted/renamed files
|
1291
1345
|
def refresh
|
1292
|
-
|
1293
|
-
|
1346
|
+
@patt = nil
|
1347
|
+
@title = nil
|
1294
1348
|
rescan_required
|
1295
1349
|
end
|
1296
1350
|
|
1297
1351
|
# put directories first, then files
|
1298
|
-
def group_directories_first
|
1352
|
+
def group_directories_first
|
1353
|
+
return if cget(:group_directories) == :none
|
1354
|
+
|
1355
|
+
files = @files
|
1299
1356
|
dirs = files.select { |f| File.directory?(f) }
|
1300
1357
|
# earlier I had File? which removed links, esp dead ones
|
1301
|
-
fi = files.
|
1302
|
-
|
1358
|
+
fi = files.reject { |f| File.directory?(f) }
|
1359
|
+
@files = if cget(:group_directories) == :first
|
1360
|
+
dirs + fi
|
1361
|
+
else
|
1362
|
+
fi + dirs
|
1363
|
+
end
|
1303
1364
|
end
|
1304
1365
|
|
1305
1366
|
## unselect all files
|
1306
1367
|
def unselect_all
|
1307
|
-
|
1308
|
-
|
1368
|
+
@selected_files = []
|
1369
|
+
@toggles[:visual_mode] = @visual_mode = false
|
1309
1370
|
end
|
1310
1371
|
|
1311
1372
|
## select all entries (files and directories)
|
1312
1373
|
def select_all
|
1313
1374
|
dir = Dir.pwd
|
1314
|
-
|
1375
|
+
@selected_files = @view.map { |file| File.join(dir, file) }
|
1376
|
+
message "#{@selected_files.count} files selected."
|
1315
1377
|
end
|
1316
1378
|
|
1317
1379
|
## accept dir to goto and change to that ( can be a file too)
|
@@ -1354,14 +1416,12 @@ end
|
|
1354
1416
|
# (or deselecting).
|
1355
1417
|
#
|
1356
1418
|
def toggle_selection_mode
|
1357
|
-
if
|
1358
|
-
$selection_mode = 1
|
1419
|
+
if @mode == 'SEL'
|
1359
1420
|
unselect_all
|
1360
|
-
|
1421
|
+
@mode = nil
|
1361
1422
|
message 'Selection mode is single. '
|
1362
1423
|
else
|
1363
|
-
|
1364
|
-
$mode = 'SEL'
|
1424
|
+
@mode = 'SEL'
|
1365
1425
|
message 'Typing a hint selects the file. Typing again will clear . '
|
1366
1426
|
end
|
1367
1427
|
end
|
@@ -1379,16 +1439,15 @@ def goto_parent_dir
|
|
1379
1439
|
return if curr == Dir.pwd
|
1380
1440
|
|
1381
1441
|
# get index of child dir in this dir, and set cursor to it.
|
1382
|
-
index =
|
1442
|
+
index = @files.index(curr + '/')
|
1383
1443
|
pause "WARNING: Could not find #{curr} in this directory." unless index
|
1384
|
-
|
1444
|
+
@cursor = index if index
|
1385
1445
|
end
|
1386
1446
|
|
1387
1447
|
def goto_home_dir
|
1388
1448
|
change_dir ENV['HOME']
|
1389
1449
|
end
|
1390
1450
|
|
1391
|
-
|
1392
1451
|
# Goes to directory bookmarked with number or upper case char.
|
1393
1452
|
# If lower case character given, then go to first file starting with char.
|
1394
1453
|
def goto_bookmark key=nil
|
@@ -1397,7 +1456,7 @@ def goto_bookmark key=nil
|
|
1397
1456
|
print 'Enter bookmark char: '
|
1398
1457
|
key = get_char
|
1399
1458
|
end
|
1400
|
-
d =
|
1459
|
+
d = @bookmarks[key]
|
1401
1460
|
if d
|
1402
1461
|
change_dir d
|
1403
1462
|
else
|
@@ -1410,27 +1469,39 @@ def enter_regex
|
|
1410
1469
|
# print 'Enter (regex) pattern: '
|
1411
1470
|
# move to beginning of line, and clear till EOL
|
1412
1471
|
# print "\r\e[K"
|
1413
|
-
|
1472
|
+
@patt = readline '/'
|
1414
1473
|
end
|
1415
1474
|
|
1416
1475
|
# page/scroll down.
|
1417
1476
|
def next_page
|
1418
|
-
|
1419
|
-
|
1420
|
-
|
1421
|
-
|
1477
|
+
@sta += @pagesize
|
1478
|
+
@cursor += @pagesize
|
1479
|
+
@sta = @cursor if @sta > @cursor
|
1480
|
+
@stact = 0
|
1422
1481
|
@old_cursor = nil
|
1423
1482
|
redraw_required
|
1424
1483
|
end
|
1425
1484
|
|
1426
1485
|
def prev_page
|
1427
|
-
|
1428
|
-
|
1486
|
+
@sta -= @pagesize
|
1487
|
+
@cursor -= @pagesize
|
1429
1488
|
@old_cursor = nil
|
1430
1489
|
# FIXME: check cursor sanity and if not changed then no redraw
|
1431
1490
|
redraw_required
|
1432
1491
|
end
|
1433
1492
|
|
1493
|
+
def goto_top
|
1494
|
+
@sta = @cursor = 0
|
1495
|
+
@old_cursor = nil
|
1496
|
+
end
|
1497
|
+
|
1498
|
+
# goto end / bottom
|
1499
|
+
def goto_end
|
1500
|
+
@cursor = @view.size - 1
|
1501
|
+
@sta = @view.size - @pagesize
|
1502
|
+
@old_cursor = nil
|
1503
|
+
end
|
1504
|
+
|
1434
1505
|
def print_help
|
1435
1506
|
page_with_tempfile do |file|
|
1436
1507
|
file.puts ' HELP'
|
@@ -1445,26 +1516,25 @@ def print_help
|
|
1445
1516
|
file.puts
|
1446
1517
|
ary = []
|
1447
1518
|
# 2019-03-19 - if : then show text after colon
|
1448
|
-
|
1519
|
+
@bindings.each_pair do |k, v|
|
1449
1520
|
vv = v.tr('_', ' ')
|
1450
1521
|
vv = vv.split(':')[1].strip if vv.include?(':')
|
1451
1522
|
ary.push "#{k.ljust(7)} => #{vv}"
|
1452
|
-
|
1453
|
-
# FIXME this works but not properly when long_listing is true.
|
1523
|
+
end
|
1524
|
+
# FIXME: this works but not properly when long_listing is true.
|
1454
1525
|
# We should avoid using columnate as there are several file related things.
|
1455
|
-
ary = columnate ary, (ary.size/2)+1
|
1526
|
+
ary = columnate ary, (ary.size / 2) + 1
|
1456
1527
|
ary.each { |line| file.puts line }
|
1457
1528
|
end
|
1458
1529
|
end
|
1459
1530
|
|
1460
1531
|
def page_stat_for_file
|
1461
|
-
stat =
|
1532
|
+
stat = `stat #{current_file}`
|
1462
1533
|
return unless stat
|
1463
1534
|
|
1464
1535
|
page_with_tempfile do |file|
|
1465
1536
|
file.puts stat
|
1466
1537
|
end
|
1467
|
-
|
1468
1538
|
end
|
1469
1539
|
|
1470
1540
|
def page_with_tempfile
|
@@ -1485,20 +1555,20 @@ def debug_vars
|
|
1485
1555
|
page_with_tempfile do |file|
|
1486
1556
|
file.puts "DEBUG VARIABLES for #{current_file}:"
|
1487
1557
|
file.puts
|
1488
|
-
file.puts "sta #{
|
1489
|
-
file.puts "cursor #{
|
1490
|
-
file.puts "stact #{
|
1491
|
-
file.puts "viewport.size #{
|
1492
|
-
file.puts "pagesize #{
|
1493
|
-
file.puts "view.size #{
|
1494
|
-
file.puts "grows #{
|
1558
|
+
file.puts "sta #{@sta}"
|
1559
|
+
file.puts "cursor #{@cursor}"
|
1560
|
+
file.puts "stact #{@stact}"
|
1561
|
+
file.puts "viewport.size #{@viewport.size}"
|
1562
|
+
file.puts "pagesize #{@pagesize}"
|
1563
|
+
file.puts "view.size #{@view.size}"
|
1564
|
+
file.puts "grows #{@grows}"
|
1495
1565
|
file.puts "File: #{current_file}"
|
1496
1566
|
file.puts
|
1497
1567
|
file.puts "Opener: #{opener_for(current_file)}"
|
1498
1568
|
file.puts
|
1499
1569
|
file.puts `file "#{current_file}"`
|
1500
1570
|
file.puts
|
1501
|
-
file.puts
|
1571
|
+
file.puts `stat "#{current_file}"`
|
1502
1572
|
end
|
1503
1573
|
redraw_required
|
1504
1574
|
end
|
@@ -1506,7 +1576,7 @@ end
|
|
1506
1576
|
def view_bookmarks
|
1507
1577
|
clear_last_line
|
1508
1578
|
puts 'Bookmarks: '
|
1509
|
-
|
1579
|
+
@bookmarks.each_pair { |k, v| puts "#{k.ljust(7)} => #{v}" }
|
1510
1580
|
puts
|
1511
1581
|
print 'Enter bookmark to goto: '
|
1512
1582
|
key = get_char
|
@@ -1517,16 +1587,17 @@ end
|
|
1517
1587
|
def main_menu
|
1518
1588
|
h = {
|
1519
1589
|
:a => :ag,
|
1520
|
-
|
1590
|
+
'/' => :ffind,
|
1521
1591
|
:l => :locate,
|
1522
1592
|
:v => :vidir,
|
1523
1593
|
:z => :z_interface,
|
1524
1594
|
:d => :child_dirs,
|
1525
1595
|
:r => :recent_files,
|
1526
|
-
|
1527
|
-
|
1596
|
+
'1' => :select_from_visited_files,
|
1597
|
+
'2' => :select_from_used_dirs,
|
1598
|
+
'3' => :list_selected_files,
|
1528
1599
|
:t => :dirtree,
|
1529
|
-
|
1600
|
+
'4' => :tree,
|
1530
1601
|
:o => :order_menu,
|
1531
1602
|
:F => :filter_menu,
|
1532
1603
|
:c => :create_menu,
|
@@ -1537,13 +1608,15 @@ def main_menu
|
|
1537
1608
|
menu 'Main Menu', h
|
1538
1609
|
end
|
1539
1610
|
|
1611
|
+
# copy and move here ?
|
1540
1612
|
def selection_menu
|
1541
1613
|
h = {
|
1542
1614
|
:a => :select_all,
|
1543
1615
|
:u => :unselect_all,
|
1544
1616
|
:s => :toggle_select,
|
1545
|
-
'*' => '
|
1546
|
-
'x' => '
|
1617
|
+
'*' => 'toggle_multiple_selection',
|
1618
|
+
'x' => 'toggle_visual_mode',
|
1619
|
+
'm' => 'toggle_selection_mode',
|
1547
1620
|
:v => :view_selected_files
|
1548
1621
|
}
|
1549
1622
|
menu 'Selection Menu', h
|
@@ -1568,18 +1641,17 @@ def menu title, h
|
|
1568
1641
|
# 2019-03-09 - trying out using `column` to print in cols
|
1569
1642
|
ary = []
|
1570
1643
|
|
1571
|
-
# 2019-04-07 - check
|
1644
|
+
# 2019-04-07 - check @bindings for shortcut and get key, add global
|
1572
1645
|
# binding in brackets
|
1573
1646
|
h.each_pair do |k, v|
|
1574
|
-
|
1575
1647
|
# get global binding
|
1576
|
-
scut =
|
1648
|
+
scut = @bindings.key(v.to_s)
|
1577
1649
|
scut = " (#{scut})" if scut
|
1578
1650
|
|
1579
1651
|
ary << " #{k}: #{v} #{scut}"
|
1580
1652
|
end
|
1581
1653
|
x = ary.join("\n")
|
1582
|
-
puts
|
1654
|
+
puts `echo "#{x}" | column`
|
1583
1655
|
|
1584
1656
|
key = get_char
|
1585
1657
|
binding = h[key]
|
@@ -1587,109 +1659,154 @@ def menu title, h
|
|
1587
1659
|
# TODO: 2019-03-21 - menu's do not have comments, they are symbols
|
1588
1660
|
# binding, _ = binding.split(':')
|
1589
1661
|
if binding
|
1662
|
+
# 2019-04-18 - true removed, else 'open' binds to ruby open not OS open
|
1663
|
+
# without true, many methods here don't get triggered
|
1590
1664
|
send(binding) if respond_to?(binding, true)
|
1665
|
+
# send(binding) if respond_to?(binding)
|
1591
1666
|
end
|
1592
1667
|
redraw_required
|
1593
1668
|
[key, binding]
|
1594
1669
|
end
|
1595
1670
|
|
1671
|
+
# some toggles can be invoked directly on a key, or through another menu.
|
1672
|
+
# Some go to methods like multiple_selection from a key
|
1673
|
+
# WARNING: method and toggle should not have same name, else flag will toggle twice.
|
1596
1674
|
def toggle_menu
|
1597
|
-
h = { h: :
|
1598
|
-
c: :
|
1599
|
-
l: :
|
1675
|
+
h = { h: :show_hidden,
|
1676
|
+
c: :ignore_case,
|
1677
|
+
l: :long_listing,
|
1600
1678
|
'1' => :toggle_columns,
|
1601
|
-
d: :
|
1602
|
-
:
|
1603
|
-
:D => :
|
1604
|
-
'8' => :
|
1605
|
-
'*' => :
|
1606
|
-
|
1607
|
-
|
1608
|
-
|
1609
|
-
|
1679
|
+
d: :group_directories,
|
1680
|
+
:e => :editor_mode,
|
1681
|
+
:D => :debug_flag,
|
1682
|
+
'8' => :multiple_selection,
|
1683
|
+
'*' => :multiple_selection,
|
1684
|
+
'S' => :selection_mode,
|
1685
|
+
v: :visual_mode,
|
1686
|
+
t: :truncate_from,
|
1687
|
+
n: :enhanced_mode }
|
1688
|
+
# TODO: add other values at end, what key ?
|
1610
1689
|
|
1611
1690
|
_, menu_text = menu 'Toggle Menu', h
|
1612
1691
|
return unless menu_text
|
1613
1692
|
|
1614
|
-
|
1615
|
-
|
1616
|
-
# NOTE: now that I am shifting queries to ruby not sure this will continue
|
1617
|
-
# FNM_DOTMATCH
|
1618
|
-
# working everywhere
|
1619
|
-
# zsh D - dot files should show
|
1620
|
-
$hidden = $hidden ? nil : 'D'
|
1621
|
-
message "Show hidden is now #{!$hidden.nil?}"
|
1622
|
-
rescan_required
|
1623
|
-
when :toggle_case
|
1624
|
-
$ignorecase = !$ignorecase
|
1625
|
-
message "Ignore Case is now #{$ignorecase}"
|
1626
|
-
rescan_required
|
1627
|
-
when :toggle_group_dirs
|
1628
|
-
@group_dirs = !@group_dirs
|
1629
|
-
message "Group Dirs First is now #{@group_dirs}"
|
1630
|
-
rescan_required
|
1631
|
-
when :toggle_columns
|
1632
|
-
if $gviscols == 1
|
1633
|
-
$gviscols = 3
|
1634
|
-
else
|
1635
|
-
$gviscols = 1
|
1636
|
-
end
|
1637
|
-
# $long_listing = false if $gviscols > 1
|
1638
|
-
x = $grows * $gviscols
|
1639
|
-
$pagesize = $pagesize == x ? $grows : x
|
1640
|
-
message "Visible columns now set to #{$gviscols}"
|
1641
|
-
rescan_required
|
1642
|
-
when :toggle_pager_mode
|
1643
|
-
$editor_mode = !$editor_mode
|
1644
|
-
$default_command = if $editor_mode
|
1645
|
-
ENV['EDITOR'] # earlier nil # 2019-03-10 -
|
1646
|
-
# it was nil so we could set a default command
|
1647
|
-
else
|
1648
|
-
ENV['MANPAGER'] || ENV['PAGER']
|
1649
|
-
end
|
1650
|
-
message "Default command is #{$default_command}"
|
1651
|
-
when :toggle_enhanced_list
|
1652
|
-
$enhanced_mode = !$enhanced_mode
|
1653
|
-
message "Enhanced mode is #{$enhanced_mode}"
|
1654
|
-
rescan_required
|
1693
|
+
# menu would have called if symbol only responds so return.
|
1694
|
+
return if respond_to?(menu_text, true) # already handled
|
1655
1695
|
|
1656
|
-
|
1657
|
-
|
1658
|
-
|
1659
|
-
|
1660
|
-
|
1661
|
-
|
1662
|
-
|
1663
|
-
|
1664
|
-
|
1665
|
-
|
1666
|
-
|
1667
|
-
|
1668
|
-
|
1669
|
-
|
1670
|
-
|
1671
|
-
|
1672
|
-
|
1673
|
-
|
1674
|
-
|
1675
|
-
|
1676
|
-
|
1677
|
-
|
1678
|
-
|
1679
|
-
|
1680
|
-
|
1681
|
-
|
1682
|
-
|
1683
|
-
|
1684
|
-
|
1685
|
-
|
1696
|
+
# for visual and selection mode
|
1697
|
+
# check if respond_to? symbol or toggle + symbol then call and return.
|
1698
|
+
symb = "toggle_#{menu_text}".to_sym
|
1699
|
+
@log.debug "trying #{symb}."
|
1700
|
+
if respond_to?(symb, true)
|
1701
|
+
@log.debug "calling #{symb}."
|
1702
|
+
send(symb)
|
1703
|
+
return
|
1704
|
+
end
|
1705
|
+
|
1706
|
+
cset menu_text
|
1707
|
+
end
|
1708
|
+
|
1709
|
+
def toggle_columns
|
1710
|
+
@gviscols = if @gviscols == 1
|
1711
|
+
3
|
1712
|
+
else
|
1713
|
+
1
|
1714
|
+
end
|
1715
|
+
x = @grows * @gviscols
|
1716
|
+
@pagesize = @pagesize == x ? @grows : x
|
1717
|
+
message "Visible columns now set to #{@gviscols}"
|
1718
|
+
rescan_required
|
1719
|
+
|
1720
|
+
end
|
1721
|
+
|
1722
|
+
def toggle_editor_mode
|
1723
|
+
toggle_value :editor_mode
|
1724
|
+
@default_command = if @editor_mode
|
1725
|
+
ENV['EDITOR'] # earlier nil # 2019-03-10 -
|
1726
|
+
# it was nil so we could set a default command
|
1686
1727
|
else
|
1687
|
-
|
1728
|
+
ENV['MANPAGER'] || ENV['PAGER']
|
1688
1729
|
end
|
1689
|
-
|
1730
|
+
message "Default command is #{@default_command}"
|
1731
|
+
end
|
1732
|
+
|
1733
|
+
def toggle_long_listing
|
1734
|
+
toggle_value :long_listing
|
1735
|
+
@long_listing = cget(:long_listing)
|
1736
|
+
if @long_listing
|
1737
|
+
@saved_gviscols = @gviscols
|
1738
|
+
@gviscols = 1
|
1739
|
+
@pagesize = @grows
|
1740
|
+
else
|
1741
|
+
@gviscols = @saved_gviscols || 3
|
1742
|
+
x = @grows * @gviscols
|
1743
|
+
@pagesize = @pagesize == x ? @grows : x
|
1744
|
+
end
|
1745
|
+
if @stact > 0
|
1746
|
+
@sta = @stact
|
1747
|
+
@stact = 0 # in case user was panned 2019-03-20 -
|
1690
1748
|
end
|
1749
|
+
message "Long listing is #{@long_listing}, visible columns is #{@gviscols}."
|
1750
|
+
rescan_required
|
1691
1751
|
end
|
1692
1752
|
|
1753
|
+
# ----------------- flag related functions -----------------------------------#
|
1754
|
+
# toggles the value of a toggle flag, also setting the variable if defined
|
1755
|
+
# WARN: be careful of variable being set directly. Replace such vars one by one.
|
1756
|
+
# NOTE: please use cset
|
1757
|
+
def toggle_value flag
|
1758
|
+
@toggles[flag] = !@toggles[flag]
|
1759
|
+
# TODO: 2019-04-16 - can we set a variable so we don't have to check
|
1760
|
+
# against a hash everywhere. variable name can be in hash too
|
1761
|
+
if instance_variable_defined? "@#{flag}"
|
1762
|
+
instance_variable_set "@#{flag}", @toggles[flag]
|
1763
|
+
@log.debug "instance_variable_set #{flag}, #{@toggles[flag]}"
|
1764
|
+
end
|
1765
|
+
message "#{flag} is set to #{@toggles[flag]}"
|
1766
|
+
end
|
1767
|
+
|
1768
|
+
# rotates the value of an option that has multiple values
|
1769
|
+
# NOTE: please use cset
|
1770
|
+
def rotate_value symb
|
1771
|
+
curr = cget(symb) # get current value
|
1772
|
+
index = @options[symb][:values].index(curr) || 0
|
1773
|
+
index += 1
|
1774
|
+
index = 0 if index >= @options[symb][:values].count
|
1775
|
+
x = @options[symb][:current] = @options[symb][:values][index]
|
1776
|
+
var = @options[symb][:var]
|
1777
|
+
instance_variable_set "@#{var}", x if var
|
1778
|
+
message "#{symb} is set to #{@options[symb][:current]}. "
|
1779
|
+
end
|
1780
|
+
|
1781
|
+
# get the value of a flag given the symbol.
|
1782
|
+
# NOTE: use this rather than access the hashes directly, since changes in structure
|
1783
|
+
# may happen
|
1784
|
+
def cget symb
|
1785
|
+
return @toggles[symb] if @toggles.key? symb
|
1786
|
+
return @options[symb][:current] if @options.key? symb
|
1787
|
+
|
1788
|
+
@log.warn "CGET: #{symb} does not exist. Please check code."
|
1789
|
+
raise ArgumentError, "CGET: #{symb} does not exist. Please check code."
|
1790
|
+
end
|
1791
|
+
|
1792
|
+
# toggles or rotates the value of a flag
|
1793
|
+
def cset symb
|
1794
|
+
if @toggles.key? symb
|
1795
|
+
toggle_value symb
|
1796
|
+
rescan_required
|
1797
|
+
return true
|
1798
|
+
end
|
1799
|
+
if @options.key? symb
|
1800
|
+
rotate_value symb
|
1801
|
+
rescan_required
|
1802
|
+
return true
|
1803
|
+
end
|
1804
|
+
|
1805
|
+
@log.warn "CSET: #{symb} does not exist. Please check code."
|
1806
|
+
raise ArgumentError, "CSET: #{symb} does not exist. Please check code."
|
1807
|
+
end
|
1808
|
+
# ----------------- end of flag related functions ----------------------------#
|
1809
|
+
|
1693
1810
|
def order_menu
|
1694
1811
|
# zsh o = order, O = reverse order
|
1695
1812
|
# ruby mtime/atime/ctime come reversed so we have to change o to O
|
@@ -1726,16 +1843,17 @@ def order_menu
|
|
1726
1843
|
lo = ''
|
1727
1844
|
end
|
1728
1845
|
## This needs to persist and be a part of all listings, put in change_dir.
|
1729
|
-
|
1846
|
+
@sorto = lo
|
1730
1847
|
message "Sorted on #{menu_text}"
|
1731
1848
|
rescan_required
|
1732
1849
|
end
|
1733
1850
|
|
1851
|
+
# TODO: create a link
|
1734
1852
|
def create_menu
|
1735
1853
|
h = { f: :create_a_file,
|
1736
1854
|
d: :create_a_dir,
|
1737
|
-
|
1738
|
-
|
1855
|
+
s: :create_dir_with_selection,
|
1856
|
+
b: :create_bookmark }
|
1739
1857
|
_, menu_text = menu 'Create Menu', h
|
1740
1858
|
end
|
1741
1859
|
|
@@ -1751,7 +1869,7 @@ def command_menu
|
|
1751
1869
|
#
|
1752
1870
|
h = { t: :today, D: :default_command, R: :remove_from_list,
|
1753
1871
|
v: :view_selected_files }
|
1754
|
-
h[:e] = if
|
1872
|
+
h[:e] = if @editor_mode
|
1755
1873
|
:pager_mode
|
1756
1874
|
else
|
1757
1875
|
:editor_mode
|
@@ -1759,11 +1877,11 @@ def command_menu
|
|
1759
1877
|
_, menu_text = menu 'Command Menu', h
|
1760
1878
|
case menu_text
|
1761
1879
|
when :pager_mode
|
1762
|
-
|
1763
|
-
|
1880
|
+
@editor_mode = false
|
1881
|
+
@default_command = ENV['MANPAGER'] || ENV['PAGER']
|
1764
1882
|
when :editor_mode
|
1765
|
-
|
1766
|
-
|
1883
|
+
@editor_mode = true
|
1884
|
+
@default_command = nil
|
1767
1885
|
when :ffind
|
1768
1886
|
ffind
|
1769
1887
|
when :locate
|
@@ -1771,20 +1889,20 @@ def command_menu
|
|
1771
1889
|
when :today
|
1772
1890
|
# zshglob: M = MARK_DIRS with slash
|
1773
1891
|
# zshglob: 'm0' 'm' = modified time, '0' = 0 days ago
|
1774
|
-
|
1775
|
-
|
1892
|
+
@files = `zsh -c 'print -rl -- *(#{@hidden}Mm0)'`.split("\n")
|
1893
|
+
@title = "Today's files"
|
1776
1894
|
when :default_command
|
1777
1895
|
puts ' This no longer works'
|
1778
1896
|
puts 'Selecting a file usually invokes $EDITOR'
|
1779
1897
|
puts 'What command do you want to use repeatedly on selected files: '
|
1780
|
-
|
1781
|
-
if
|
1898
|
+
@default_command = gets.chomp
|
1899
|
+
if @default_command != ''
|
1782
1900
|
print 'Second part of command (maybe blank): '
|
1783
|
-
|
1901
|
+
@default_command2 = gets.chomp
|
1784
1902
|
else
|
1785
1903
|
print 'Cleared default command, will default to $EDITOR'
|
1786
|
-
|
1787
|
-
|
1904
|
+
@default_command2 = nil
|
1905
|
+
@default_command = nil
|
1788
1906
|
end
|
1789
1907
|
end
|
1790
1908
|
# redraw
|
@@ -1799,65 +1917,64 @@ def extras
|
|
1799
1917
|
g: :generators,
|
1800
1918
|
:B => :bindkey_ext_command,
|
1801
1919
|
:r => :config_read,
|
1802
|
-
:w => :config_write
|
1803
|
-
}
|
1920
|
+
:w => :config_write }
|
1804
1921
|
key, menu_text = menu 'Extras Menu', h
|
1805
1922
|
case menu_text
|
1806
1923
|
when :one_column
|
1807
|
-
|
1924
|
+
@pagesize = @grows
|
1808
1925
|
when :multi_column
|
1809
|
-
#
|
1810
|
-
|
1926
|
+
# @pagesize = 60
|
1927
|
+
@pagesize = @grows * @gviscols
|
1811
1928
|
when :columns
|
1812
|
-
print "How many columns to show: 1-6 [current #{
|
1929
|
+
print "How many columns to show: 1-6 [current #{@gviscols}]? "
|
1813
1930
|
key = get_char
|
1814
1931
|
key = key.to_i
|
1815
1932
|
if key > 0 && key < 7
|
1816
|
-
|
1817
|
-
|
1933
|
+
@gviscols = key.to_i
|
1934
|
+
@pagesize = @grows * @gviscols
|
1818
1935
|
end
|
1819
1936
|
end
|
1820
1937
|
end
|
1821
1938
|
|
1822
1939
|
def filter_menu
|
1823
1940
|
h = { :d => :dirs, :f => :files, :e => :emptydirs, '0' => :emptyfiles,
|
1824
|
-
|
1941
|
+
:r => :reduce_list, :x => :extension }
|
1825
1942
|
_, menu_text = menu 'Filter Menu', h
|
1826
1943
|
files = nil
|
1827
1944
|
case menu_text
|
1828
1945
|
when :dirs
|
1829
|
-
|
1946
|
+
@filterstr = '/M'
|
1830
1947
|
# zsh /M MARK_DIRS appends trailing '/' to directories
|
1831
|
-
files = `zsh -c 'print -rl -- *(#{
|
1832
|
-
|
1948
|
+
files = `zsh -c 'print -rl -- *(#{@sorto}/M)'`.split("\n")
|
1949
|
+
@title = 'Filter: directories only'
|
1833
1950
|
when :files
|
1834
|
-
|
1951
|
+
@filterstr = '.'
|
1835
1952
|
# zsh '.' for files, '/' for dirs
|
1836
|
-
files = `zsh -c 'print -rl -- *(#{
|
1837
|
-
|
1953
|
+
files = `zsh -c 'print -rl -- *(#{@sorto}#{@hidden}.)'`.split("\n")
|
1954
|
+
@title = 'Filter: files only'
|
1838
1955
|
when :emptydirs
|
1839
|
-
|
1956
|
+
@filterstr = '/D^F'
|
1840
1957
|
# zsh F = full dirs, ^F empty dirs
|
1841
|
-
files = `zsh -c 'print -rl -- *(#{
|
1842
|
-
|
1958
|
+
files = `zsh -c 'print -rl -- *(#{@sorto}/D^F)'`.split("\n")
|
1959
|
+
@title = 'Filter: empty directories'
|
1843
1960
|
when :emptyfiles
|
1844
|
-
|
1961
|
+
@filterstr = '.L0'
|
1845
1962
|
# zsh .L size in bytes
|
1846
|
-
files = `zsh -c 'print -rl -- *(#{
|
1847
|
-
|
1963
|
+
files = `zsh -c 'print -rl -- *(#{@sorto}#{@hidden}.L0)'`.split("\n")
|
1964
|
+
@title = 'Filter: empty files'
|
1848
1965
|
when :reduce_list
|
1849
1966
|
files = reduce
|
1850
1967
|
when :extension
|
1851
1968
|
files = filter_for_current_extension
|
1852
1969
|
end
|
1853
1970
|
if files && files.count > 0
|
1854
|
-
|
1855
|
-
|
1856
|
-
|
1971
|
+
@files = files
|
1972
|
+
@stact = 0
|
1973
|
+
@message = "Filtered on #{menu_text}. Press ESC to return."
|
1857
1974
|
# redraw
|
1858
1975
|
else
|
1859
1976
|
perror 'Sorry, No files. '
|
1860
|
-
|
1977
|
+
@title = nil
|
1861
1978
|
end
|
1862
1979
|
end
|
1863
1980
|
|
@@ -1866,47 +1983,47 @@ def reduce pattern=nil
|
|
1866
1983
|
last_line 'Enter a pattern to reduce current list: '
|
1867
1984
|
pattern = gets.chomp
|
1868
1985
|
end
|
1869
|
-
|
1870
|
-
|
1986
|
+
@title = "Filter: pattern #{pattern}"
|
1987
|
+
@files = @files.select { |f| f.match(pattern) }
|
1871
1988
|
end
|
1872
1989
|
|
1873
1990
|
def filter_for_current_extension
|
1874
1991
|
extn = File.extname(current_file)
|
1875
1992
|
return unless extn
|
1876
1993
|
|
1877
|
-
|
1994
|
+
@files = @files.select { |f| !File.directory?(f) && extn == File.extname(f) }
|
1878
1995
|
end
|
1879
1996
|
|
1880
1997
|
def select_from_used_dirs
|
1881
|
-
|
1998
|
+
@title = 'Used Directories'
|
1882
1999
|
home = File.expand_path '~'
|
1883
|
-
|
2000
|
+
@files = @used_dirs.uniq.map { |path| path.sub(home.to_s, '~') }
|
1884
2001
|
# redraw
|
1885
2002
|
end
|
1886
2003
|
|
1887
2004
|
def select_from_visited_files
|
1888
2005
|
# not yet a unique list, needs to be unique and have latest pushed to top
|
1889
|
-
|
2006
|
+
@title = 'Visited Files'
|
1890
2007
|
home = File.expand_path '~'
|
1891
|
-
|
2008
|
+
@files = @visited_files.uniq.map { |path| path.sub(home.to_s, '~') }
|
1892
2009
|
# redraw
|
1893
2010
|
end
|
1894
2011
|
|
1895
2012
|
# maybe unused ??? XXX
|
1896
2013
|
def select_bookmarks
|
1897
|
-
|
1898
|
-
|
2014
|
+
@title = 'Bookmarks'
|
2015
|
+
@files = @bookmarks.values
|
1899
2016
|
end
|
1900
2017
|
|
1901
2018
|
## part copied and changed from change_dir since we don't dir going back on top
|
1902
2019
|
# or we'll be stuck in a cycle
|
1903
2020
|
def pop_dir
|
1904
2021
|
# the first time we pop, we need to put the current on stack
|
1905
|
-
|
2022
|
+
@visited_dirs.push Dir.pwd unless @visited_dirs.index(Dir.pwd)
|
1906
2023
|
## XXX make sure thre is something to pop
|
1907
|
-
d =
|
2024
|
+
d = @visited_dirs.delete_at 0
|
1908
2025
|
## XXX make sure the dir exists, cuold have been deleted. can be an error or crash otherwise
|
1909
|
-
|
2026
|
+
@visited_dirs.push d
|
1910
2027
|
Dir.chdir d
|
1911
2028
|
post_cd
|
1912
2029
|
rescan_required
|
@@ -1914,10 +2031,10 @@ end
|
|
1914
2031
|
|
1915
2032
|
# after changing directory
|
1916
2033
|
def post_cd
|
1917
|
-
|
1918
|
-
|
1919
|
-
|
1920
|
-
|
2034
|
+
@title = @patt = @message = nil
|
2035
|
+
@sta = @cursor = @stact = 0
|
2036
|
+
@visual_block_start = nil
|
2037
|
+
@current_dir = Dir.pwd
|
1921
2038
|
screen_settings
|
1922
2039
|
|
1923
2040
|
# goto last position cursor was in this dir
|
@@ -1930,10 +2047,10 @@ def config_read
|
|
1930
2047
|
return unless File.readable? f
|
1931
2048
|
|
1932
2049
|
hash = loadYML(f)
|
1933
|
-
|
1934
|
-
|
1935
|
-
|
1936
|
-
|
2050
|
+
@used_dirs = hash['DIRS']
|
2051
|
+
@visited_files = hash['FILES']
|
2052
|
+
@bookmarks = hash['BOOKMARKS']
|
2053
|
+
@used_dirs.concat get_env_paths
|
1937
2054
|
end
|
1938
2055
|
|
1939
2056
|
def get_env_paths
|
@@ -1955,44 +2072,40 @@ def config_write
|
|
1955
2072
|
# Putting it in a format that zfm can also read and write
|
1956
2073
|
f1 = File.expand_path(CONFIG_FILE)
|
1957
2074
|
hash = {}
|
1958
|
-
hash['DIRS'] =
|
1959
|
-
hash['FILES'] =
|
2075
|
+
hash['DIRS'] = @used_dirs.select { |dir| File.exist? dir }
|
2076
|
+
hash['FILES'] = @visited_files.select { |file| File.exist? file }
|
1960
2077
|
# NOTE bookmarks is a hash and contains FILE:cursor_pos
|
1961
|
-
hash['BOOKMARKS'] =
|
2078
|
+
hash['BOOKMARKS'] = @bookmarks # .select {|file| File.exist? file}
|
1962
2079
|
writeYML hash, f1
|
1963
|
-
|
2080
|
+
@writing = @modified = false
|
1964
2081
|
message "Saved #{f1}"
|
1965
2082
|
end
|
1966
2083
|
|
1967
2084
|
# {{{ YML
|
1968
2085
|
require 'yaml'
|
1969
|
-
def loadYML
|
1970
|
-
hash = YAML
|
1971
|
-
if
|
1972
|
-
$stderr.puts hash.keys.size
|
1973
|
-
end
|
2086
|
+
def loadYML filename
|
2087
|
+
hash = YAML.safe_load(File.open(filename))
|
2088
|
+
warn hash.keys.size if @opt_debug
|
1974
2089
|
return hash
|
1975
2090
|
end
|
2091
|
+
|
1976
2092
|
def writeYML obj, filename
|
1977
|
-
File.open(filename, 'w') {|f| f.write obj.to_yaml }
|
1978
|
-
if
|
1979
|
-
$stderr.puts "Written to file #{filename}"
|
1980
|
-
end
|
2093
|
+
File.open(filename, 'w') { |f| f.write obj.to_yaml }
|
2094
|
+
warn "Written to file #{filename}" if @opt_debug
|
1981
2095
|
end
|
1982
2096
|
# }}}
|
1983
2097
|
|
1984
2098
|
## accept a character to save this dir as a bookmark
|
1985
2099
|
def create_bookmark
|
1986
|
-
|
1987
2100
|
clear_last_line
|
1988
2101
|
print 'Enter A-Z, a-z or 0-9 to create a bookmark: '
|
1989
2102
|
# print "\e[?25h" # unhide cursor
|
1990
2103
|
key = get_char
|
1991
2104
|
# print "\e[?25l" # hide cursor
|
1992
|
-
if
|
1993
|
-
#
|
2105
|
+
if /^[0-9A-Za-z]$/.match?(key)
|
2106
|
+
# @bookmarks[key] = "#{Dir.pwd}:#{@cursor}"
|
1994
2107
|
set_bookmark key
|
1995
|
-
|
2108
|
+
@modified = true
|
1996
2109
|
message "Created bookmark #{key} for #{File.basename(Dir.pwd)}."
|
1997
2110
|
else
|
1998
2111
|
perror 'Bookmark must be alpha character or number.'
|
@@ -2014,35 +2127,35 @@ def subcommand
|
|
2014
2127
|
end
|
2015
2128
|
if command == 'q'
|
2016
2129
|
# FIXME: 2019-03-22 - should this not call quit_command ?
|
2017
|
-
if
|
2130
|
+
if @modified
|
2018
2131
|
last_line
|
2019
2132
|
print 'Do you want to save bookmarks? (y/n): '
|
2020
2133
|
key = get_char
|
2021
2134
|
if key == 'y'
|
2022
|
-
|
2023
|
-
|
2135
|
+
@writing = true
|
2136
|
+
@quitting = true
|
2024
2137
|
elsif key == 'n'
|
2025
|
-
|
2138
|
+
@quitting = true
|
2026
2139
|
print 'Quitting without saving bookmarks'
|
2027
2140
|
else
|
2028
2141
|
perror 'No action taken.'
|
2029
2142
|
end
|
2030
2143
|
else
|
2031
|
-
|
2144
|
+
@quitting = true
|
2032
2145
|
end
|
2033
2146
|
elsif command == 'wq'
|
2034
|
-
|
2035
|
-
|
2147
|
+
@quitting = true
|
2148
|
+
@writing = true
|
2036
2149
|
elsif command == 'w'
|
2037
2150
|
config_write
|
2038
2151
|
elsif command == 'x'
|
2039
|
-
|
2040
|
-
|
2152
|
+
@quitting = true
|
2153
|
+
@writing = true if @modified
|
2041
2154
|
elsif command == 'e'
|
2042
2155
|
edit_current
|
2043
2156
|
elsif command == 'o'
|
2044
2157
|
open_current
|
2045
|
-
elsif command == 'h'
|
2158
|
+
elsif (command == 'h') || (command == 'help') || (command == '?')
|
2046
2159
|
print_help
|
2047
2160
|
elsif command == 'p'
|
2048
2161
|
system 'echo $PWD | pbcopy'
|
@@ -2054,41 +2167,41 @@ def subcommand
|
|
2054
2167
|
end
|
2055
2168
|
|
2056
2169
|
def quit_command
|
2057
|
-
if
|
2170
|
+
if @modified
|
2058
2171
|
last_line
|
2059
|
-
puts 'Press y to save bookmarks before quitting ' if
|
2172
|
+
puts 'Press y to save bookmarks before quitting ' if @modified
|
2060
2173
|
print 'Press n to quit without saving'
|
2061
2174
|
key = get_char
|
2062
2175
|
else
|
2063
|
-
|
2176
|
+
@quitting = true
|
2064
2177
|
end
|
2065
|
-
|
2066
|
-
|
2178
|
+
@quitting = true if key == 'n'
|
2179
|
+
@quitting = @writing = true if key == 'y'
|
2067
2180
|
end
|
2068
2181
|
|
2069
2182
|
def views
|
2070
2183
|
views = %w[/ om oa Om OL oL On on]
|
2071
2184
|
viewlabels = %w[Dirs Newest Accessed Oldest Largest Smallest Reverse Name]
|
2072
|
-
|
2073
|
-
|
2074
|
-
|
2075
|
-
|
2185
|
+
@sorto = views[@viewctr]
|
2186
|
+
@title = viewlabels[@viewctr]
|
2187
|
+
@viewctr += 1
|
2188
|
+
@viewctr = 0 if @viewctr > views.size
|
2076
2189
|
|
2077
|
-
|
2190
|
+
@files = `zsh -c 'print -rl -- *(#{@sorto}#{@hidden}M)'`.split("\n")
|
2078
2191
|
# redraw
|
2079
2192
|
end
|
2080
2193
|
|
2081
2194
|
def child_dirs
|
2082
|
-
|
2195
|
+
@title = 'Directories in current directory'
|
2083
2196
|
# M is MARK_DIRS option for putting trailing slash after dir
|
2084
|
-
#
|
2085
|
-
|
2086
|
-
message "#{
|
2197
|
+
# @files = `zsh -c 'print -rl -- *(/#{@sorto}#{@hidden}M)'`.split("\n")
|
2198
|
+
@files = dirs
|
2199
|
+
message "#{@files.size} directories."
|
2087
2200
|
# redraw
|
2088
2201
|
end
|
2089
2202
|
|
2090
2203
|
def dirs dir='*'
|
2091
|
-
files = Dir.glob(dir, File::FNM_DOTMATCH).select {|f| File.directory?(f)} - %w[
|
2204
|
+
files = Dir.glob(dir, File::FNM_DOTMATCH).select { |f| File.directory?(f) } - %w[. ..]
|
2092
2205
|
files = add_slash files
|
2093
2206
|
files
|
2094
2207
|
end
|
@@ -2100,12 +2213,11 @@ def add_slash files
|
|
2100
2213
|
end
|
2101
2214
|
|
2102
2215
|
def dirtree
|
2103
|
-
|
2216
|
+
@title = 'Child directories recursive'
|
2104
2217
|
# zsh **/ is recursive
|
2105
|
-
files1 = `zsh -c 'print -rl -- **/*(/#{
|
2106
|
-
|
2107
|
-
|
2108
|
-
message "#{$files.size} files."
|
2218
|
+
# files1 = `zsh -c 'print -rl -- **/*(/#{@sorto}#{@hidden}M)'`.split("\n")
|
2219
|
+
@files = Dir['**/']
|
2220
|
+
message "#{@files.size} files."
|
2109
2221
|
# redraw
|
2110
2222
|
end
|
2111
2223
|
|
@@ -2114,10 +2226,10 @@ end
|
|
2114
2226
|
# structure than files.
|
2115
2227
|
def tree
|
2116
2228
|
# Caution: use only for small projects, don't use in root.
|
2117
|
-
|
2118
|
-
#
|
2119
|
-
|
2120
|
-
message "#{
|
2229
|
+
@title = 'Full Tree'
|
2230
|
+
# @files = `zsh -c 'print -rl -- **/*(#{@sorto}#{@hidden}M)'`.split("\n")
|
2231
|
+
@files = Dir['**/*']
|
2232
|
+
message "#{@files.size} files."
|
2121
2233
|
# redraw
|
2122
2234
|
end
|
2123
2235
|
|
@@ -2125,25 +2237,26 @@ end
|
|
2125
2237
|
# In some cases it shows mostly .git files, we need to prune those
|
2126
2238
|
def recent_files
|
2127
2239
|
# print -rl -- **/*(Dom[1,10])
|
2128
|
-
|
2240
|
+
@title = 'Recent files'
|
2129
2241
|
# zsh D DOT_GLOB, show dot files
|
2130
2242
|
# zsh om order on modification time
|
2131
|
-
|
2243
|
+
@files = `zsh -c 'print -rl -- **/*(Dom[1,15])'`.split("\n").reject { |f| f[0] == '.' }
|
2132
2244
|
# redraw
|
2133
2245
|
end
|
2134
2246
|
|
2135
2247
|
def select_current
|
2136
|
-
## vp is local there, so i can do
|
2137
|
-
# open_file
|
2138
|
-
open_file
|
2248
|
+
## vp is local there, so i can do @vp[0]
|
2249
|
+
# open_file @view[@sta] if @view[@sta]
|
2250
|
+
open_file @view[@cursor] if @view[@cursor]
|
2139
2251
|
end
|
2140
2252
|
|
2141
2253
|
## create a list of dirs in which some action has happened, for saving
|
2142
|
-
def push_used_dirs
|
2143
|
-
#
|
2144
|
-
return if
|
2145
|
-
|
2146
|
-
|
2254
|
+
def push_used_dirs d=Dir.pwd
|
2255
|
+
# @used_dirs.index(d) || @used_dirs.push(d)
|
2256
|
+
return if @used_dirs[0] == d
|
2257
|
+
|
2258
|
+
@used_dirs.delete(d) if @used_dirs.index(d)
|
2259
|
+
@used_dirs.insert(0, d)
|
2147
2260
|
end
|
2148
2261
|
|
2149
2262
|
def pbold text
|
@@ -2167,22 +2280,21 @@ end
|
|
2167
2280
|
## return shortcut/hint for an index (offset in file array)
|
2168
2281
|
# ix is the index of a file in the complete array (view)
|
2169
2282
|
def get_shortcut index
|
2170
|
-
|
2171
2283
|
# Case where user has panned to the right columns:
|
2172
2284
|
# Earlier, we showed '<' in left columns, if user has panned right.
|
2173
2285
|
# Now we show unused shortcuts after exhausting them.
|
2174
|
-
# return '<' if index <
|
2175
|
-
if index <
|
2176
|
-
index =
|
2177
|
-
i =
|
2286
|
+
# return '<' if index < @stact
|
2287
|
+
if index < @stact
|
2288
|
+
index = @viewport.size - @stact + index
|
2289
|
+
i = IDX[index]
|
2178
2290
|
return i if i
|
2179
2291
|
|
2180
2292
|
return '['
|
2181
2293
|
end
|
2182
2294
|
|
2183
2295
|
# Normal case (user has not panned columns)
|
2184
|
-
index -=
|
2185
|
-
i =
|
2296
|
+
index -= @stact
|
2297
|
+
i = IDX[index]
|
2186
2298
|
return i if i
|
2187
2299
|
|
2188
2300
|
'->'
|
@@ -2192,7 +2304,7 @@ end
|
|
2192
2304
|
# Called when user types a key
|
2193
2305
|
# should we even ask for a second key if there are not enough rows
|
2194
2306
|
# What if we want to also trap z with numbers for other purposes
|
2195
|
-
def get_index
|
2307
|
+
def get_index key, vsz=999
|
2196
2308
|
# @log.debug "Etners get_index with #{key}"
|
2197
2309
|
i = convert_key_to_index key
|
2198
2310
|
return i if i
|
@@ -2206,8 +2318,8 @@ def get_index(key, vsz = 999)
|
|
2206
2318
|
i = convert_key_to_index("#{key}#{zch}")
|
2207
2319
|
# @log.debug "convert returned #{i} for #{key}#{zch}"
|
2208
2320
|
return i if i
|
2209
|
-
# i =
|
2210
|
-
# return i +
|
2321
|
+
# i = IDX.index
|
2322
|
+
# return i + @stact if i
|
2211
2323
|
end
|
2212
2324
|
end
|
2213
2325
|
nil
|
@@ -2217,72 +2329,272 @@ end
|
|
2217
2329
|
# Earlier this was simple, but now that we put hints/shortcuts
|
2218
2330
|
# in rows on the left after panning, we need to account for cycled hints.
|
2219
2331
|
def convert_key_to_index key
|
2220
|
-
i =
|
2221
|
-
|
2222
|
-
|
2223
|
-
|
2224
|
-
|
2225
|
-
|
2226
|
-
|
2227
|
-
|
2228
|
-
|
2229
|
-
|
2230
|
-
|
2231
|
-
|
2232
|
-
|
2233
|
-
|
2234
|
-
|
2235
|
-
|
2332
|
+
i = IDX.index(key)
|
2333
|
+
return nil unless i
|
2334
|
+
|
2335
|
+
# @log.debug "get_index with #{key}: #{i}. #{@stact}. #{@viewport.size}"
|
2336
|
+
vps = @viewport.size
|
2337
|
+
# TODO: if very high key given, consider going to last file ?
|
2338
|
+
# that way one can press zz or ZZ to go to last file.
|
2339
|
+
# 2019-04-11 - XXX actually this doesnt place the cursor on last file
|
2340
|
+
# it opens it, which may not be what we want
|
2341
|
+
retnil = nil # vps - 1 # nil
|
2342
|
+
# user has entered a key that is outside of range, return nil
|
2343
|
+
return retnil if @stact == 0 && i + @stact >= vps
|
2344
|
+
|
2345
|
+
# again, key out of range
|
2346
|
+
# return nil if @stact > 0 && i + @stact >= vps && i + @stact - vps >= @stact
|
2347
|
+
return retnil if @stact > 0 && i + @stact >= vps && i - vps >= 0
|
2348
|
+
|
2349
|
+
# panning case, hints are recycled
|
2350
|
+
return (i + @stact) - vps if i + @stact >= vps
|
2351
|
+
|
2352
|
+
# regular hint
|
2353
|
+
return i + @stact # if i
|
2354
|
+
|
2355
|
+
end
|
2356
|
+
|
2357
|
+
def delete_file
|
2358
|
+
# file_actions :delete
|
2359
|
+
rbfiles = current_or_selected_files
|
2360
|
+
return if rbfiles.nil? || rbfiles.empty?
|
2361
|
+
|
2362
|
+
count = rbfiles.count
|
2363
|
+
first = rbfiles.first
|
2364
|
+
text = count == 1 ? File.basename(first) : "#{count} files"
|
2365
|
+
shfiles = Shellwords.join(rbfiles)
|
2366
|
+
|
2367
|
+
delcommand = 'rmtrash'
|
2368
|
+
clear_last_line
|
2369
|
+
print "#{delcommand} #{text[0..40]} ? [yn?]: "
|
2370
|
+
key = get_char
|
2371
|
+
view_selected_files if key == '?'
|
2372
|
+
return if key != 'y'
|
2373
|
+
|
2374
|
+
clear_last_line
|
2375
|
+
print "\r deleting ..."
|
2376
|
+
system "#{delcommand} #{shfiles}"
|
2377
|
+
@log.info "trashed #{shfiles}."
|
2378
|
+
message "Deleted #{text[0..40]}."
|
2379
|
+
refresh
|
2380
|
+
end
|
2381
|
+
|
2382
|
+
def move_file
|
2383
|
+
rbfiles = current_or_selected_files
|
2384
|
+
return if rbfiles.nil? || rbfiles.empty?
|
2385
|
+
|
2386
|
+
count = rbfiles.count
|
2387
|
+
first = rbfiles.first
|
2388
|
+
text = count == 1 ? File.basename(first) : "#{count} files"
|
2389
|
+
|
2390
|
+
# multiple files can only be moved to a directory
|
2391
|
+
default = @move_target.nil? ? '.' : @move_target
|
2392
|
+
target = readline "Move #{text[0..40]} to (#{default}): "
|
2393
|
+
return unless target
|
2394
|
+
|
2395
|
+
target = default if target == ''
|
2396
|
+
target = File.expand_path(target)
|
2397
|
+
return if target == ''
|
2398
|
+
|
2399
|
+
if count > 1 && !File.directory?(target)
|
2400
|
+
perror 'Move target must be a directory for multiple files.'
|
2401
|
+
return
|
2402
|
+
end
|
2403
|
+
|
2404
|
+
if count == 1 && !File.directory?(target) && File.exist?(target)
|
2405
|
+
perror "Target #{target} exists."
|
2406
|
+
return
|
2407
|
+
end
|
2408
|
+
|
2409
|
+
begin
|
2410
|
+
FileUtils.mv rbfiles, target
|
2411
|
+
message "Moved #{text} to #{target}."
|
2412
|
+
rescue StandardError => exc
|
2413
|
+
@log.warn "move_file: #{exc}."
|
2414
|
+
@log.warn "MOVE: files: #{rbfiles}, target:#{target}"
|
2415
|
+
perror exc.to_s
|
2416
|
+
end
|
2417
|
+
refresh
|
2418
|
+
end
|
2419
|
+
|
2420
|
+
def copy_file
|
2421
|
+
rbfiles = current_or_selected_files
|
2422
|
+
return if rbfiles.nil? || rbfiles.empty?
|
2423
|
+
|
2424
|
+
count = rbfiles.count
|
2425
|
+
first = rbfiles.first
|
2426
|
+
text = "#{count} files"
|
2427
|
+
|
2428
|
+
# Target must be directory for multiple files.
|
2429
|
+
# NOTE: target should not be same as source dir but there can be files
|
2430
|
+
# from multiple directories
|
2431
|
+
if count == 1
|
2432
|
+
if File.exist? File.basename(first)
|
2433
|
+
default = get_unique_file_name File.basename(first)
|
2434
|
+
Readline::HISTORY.push default
|
2236
2435
|
else
|
2237
|
-
|
2238
|
-
return i + $stact #if i
|
2436
|
+
default = '.'
|
2239
2437
|
end
|
2438
|
+
# default : if file exists here, then add suffix
|
2439
|
+
# if no file here, then directory is default.
|
2440
|
+
else
|
2441
|
+
default = if File.exist? File.basename(first)
|
2442
|
+
''
|
2443
|
+
else
|
2444
|
+
'.'
|
2445
|
+
end
|
2446
|
+
# current directory is default if first file does not exist
|
2447
|
+
# if first file exists here, then no default
|
2448
|
+
end
|
2449
|
+
target = readline "Copy to (#{default}): "
|
2450
|
+
return unless target # C-c
|
2451
|
+
|
2452
|
+
target = default if target == ''
|
2453
|
+
target = File.expand_path(target)
|
2454
|
+
return if target == ''
|
2455
|
+
|
2456
|
+
if count > 1 && !File.directory?(target)
|
2457
|
+
perror 'Copy target must be a directory for multiple files.'
|
2458
|
+
return
|
2240
2459
|
end
|
2241
|
-
|
2460
|
+
|
2461
|
+
if count == 1 && !File.directory?(target) && File.exist?(target)
|
2462
|
+
perror "Target #{target} exists."
|
2463
|
+
return
|
2464
|
+
end
|
2465
|
+
|
2466
|
+
# if rbfiles is array, then dest must be a directory.
|
2467
|
+
rbfiles = rbfiles.first if rbfiles.count == 1
|
2468
|
+
|
2469
|
+
begin
|
2470
|
+
FileUtils.cp rbfiles, target
|
2471
|
+
message "Copied #{text} to #{target}."
|
2472
|
+
rescue StandardError => exc
|
2473
|
+
@log.warn exc.to_s
|
2474
|
+
@log.warn "Target: #{target}, files:#{rbfiles}"
|
2475
|
+
perror exc.to_s
|
2476
|
+
end
|
2477
|
+
refresh
|
2242
2478
|
end
|
2243
2479
|
|
2244
|
-
|
2245
|
-
|
2480
|
+
# generate a unique filename by adding a zero padded number prior to the extension.
|
2481
|
+
# This is used only during copy operation.
|
2482
|
+
def get_unique_file_name fname
|
2483
|
+
100.times do |i|
|
2484
|
+
suffix = "%03d" % i
|
2485
|
+
extn = File.extname(fname)
|
2486
|
+
base = File.basename(fname, extn)
|
2487
|
+
# f = fname + '.' + suffix
|
2488
|
+
f = base + suffix + extn
|
2489
|
+
return f unless File.exist?(f)
|
2490
|
+
end
|
2491
|
+
|
2492
|
+
timestamp = Time.now.strftime("%Y%m%d-%H%M%S")
|
2493
|
+
return fname + '.' + timestamp
|
2246
2494
|
end
|
2247
2495
|
|
2248
|
-
def
|
2249
|
-
|
2496
|
+
def rename_file
|
2497
|
+
rbfiles = current_or_selected_files
|
2498
|
+
return if rbfiles.nil? || rbfiles.empty?
|
2250
2499
|
|
2251
|
-
|
2252
|
-
|
2253
|
-
|
2254
|
-
return if target == '' && @move_target.nil?
|
2500
|
+
count = rbfiles.count
|
2501
|
+
first = rbfiles.first
|
2502
|
+
text = count == 1 ? File.basename(first) : "#{count} files"
|
2255
2503
|
|
2256
|
-
|
2257
|
-
|
2258
|
-
return
|
2504
|
+
if count > 1
|
2505
|
+
perror 'Select only one file for rename.'
|
2506
|
+
return
|
2507
|
+
end
|
2259
2508
|
|
2260
|
-
|
2509
|
+
Readline::HISTORY.push File.basename(first)
|
2510
|
+
target = readline "Rename #{text[0..40]} to : "
|
2511
|
+
return if target == '' || target == '.' || target == '..'
|
2512
|
+
|
2513
|
+
if File.exist? target
|
2514
|
+
perror "Target (#{target}) exists."
|
2515
|
+
return
|
2516
|
+
end
|
2517
|
+
|
2518
|
+
begin
|
2519
|
+
FileUtils.mv first, target
|
2520
|
+
message "Renamed to #{target}."
|
2521
|
+
@log.info "Renamed #{first} to #{target}."
|
2522
|
+
rescue StandardError => exc
|
2523
|
+
@log.warn exc.to_s
|
2524
|
+
@log.warn "RENAME: files: #{first}, target:#{target}"
|
2525
|
+
pause exc.to_s
|
2526
|
+
end
|
2527
|
+
refresh
|
2528
|
+
end
|
2529
|
+
|
2530
|
+
# remove spaces and brackets from file name
|
2531
|
+
# replace space with underscore, removes square and round brackets
|
2532
|
+
def remove_spaces_from_name
|
2533
|
+
execute_script 'remove_brackets'
|
2534
|
+
end
|
2535
|
+
|
2536
|
+
def zip_file
|
2537
|
+
rbfiles = current_or_selected_files
|
2538
|
+
return if rbfiles.nil? || rbfiles.empty?
|
2539
|
+
|
2540
|
+
# count = rbfiles.count
|
2541
|
+
# first = rbfiles.first
|
2542
|
+
# text = count == 1 ? File.basename(first) : "#{count} files"
|
2543
|
+
|
2544
|
+
extn = '.tgz'
|
2545
|
+
default = "archive#{extn}"
|
2546
|
+
Readline::HISTORY.push default # check for exist before pushing
|
2547
|
+
target = readline "Archive name (#{default}): "
|
2548
|
+
return unless target
|
2549
|
+
return if target == ''
|
2550
|
+
|
2551
|
+
if target && target.size < 4
|
2552
|
+
perror 'Use target of more than 4 characters.'
|
2553
|
+
return
|
2554
|
+
end
|
2555
|
+
target += extn if File.extname(target) == ''
|
2556
|
+
|
2557
|
+
if File.exist? target
|
2558
|
+
perror "Target (#{target}) exists."
|
2559
|
+
return
|
2560
|
+
end
|
2561
|
+
|
2562
|
+
# convert absolute paths to relative ones in this zip
|
2563
|
+
# the problem with zip is that we have full paths
|
2564
|
+
# so the zip file has full paths and extraction sucks
|
2565
|
+
require 'pathname'
|
2566
|
+
base = Pathname.new Dir.pwd
|
2567
|
+
relfiles = rbfiles.map { |f| p = Pathname.new(f); p.relative_path_from(base) }
|
2568
|
+
zfiles = Shellwords.join relfiles
|
2569
|
+
|
2570
|
+
system "tar zcvf #{target} #{zfiles}"
|
2571
|
+
message "Created #{target} with #{relfiles.count} files."
|
2572
|
+
setup_terminal
|
2573
|
+
refresh
|
2574
|
+
end
|
2575
|
+
|
2576
|
+
# instantly move to move target. Use during bulk operations so you don't have to
|
2577
|
+
# enter or goto target dir and come back.
|
2578
|
+
def move_instant
|
2579
|
+
# FIXME: cannot be in target directory and do auto update
|
2580
|
+
# should we have a key for specifying move_target ?
|
2581
|
+
|
2582
|
+
# for the mo, lets not move if nothing selected
|
2583
|
+
if @selected_files.empty?
|
2584
|
+
target = readline "Set Move target #{@move_target}:"
|
2261
2585
|
target = Dir.pwd if target == '.'
|
2262
2586
|
target = File.expand_path(target)
|
2263
|
-
|
2264
|
-
|
2265
|
-
return
|
2266
|
-
end
|
2267
|
-
begin
|
2268
|
-
f = current_file
|
2269
|
-
FileUtils.mv f, target
|
2270
|
-
@move_target = target
|
2271
|
-
message "#{f} moved to #{File.basename(target)}. Target set."
|
2272
|
-
@log.info "1.#{f} moved to #{target}."
|
2273
|
-
rescue StandardError => exc
|
2274
|
-
@log.warn "Case 1:"
|
2275
|
-
@log.warn "Target is #{target}, file was #{f}"
|
2276
|
-
@log.warn exc.to_s
|
2277
|
-
end
|
2278
|
-
refresh
|
2587
|
+
@move_target = target
|
2588
|
+
message "Move target is #{@move_target}. Select files and press this key."
|
2279
2589
|
return
|
2280
2590
|
end
|
2281
2591
|
|
2282
2592
|
# files selected. Use earlier target if there, else ask
|
2593
|
+
# XXX how do we change it once set
|
2283
2594
|
target = @move_target
|
2284
2595
|
if target.nil? || target == ''
|
2285
|
-
|
2596
|
+
count = @selected_files.count
|
2597
|
+
target = readline "Move #{count} files to :"
|
2286
2598
|
return unless target
|
2287
2599
|
end
|
2288
2600
|
target = File.expand_path(target)
|
@@ -2291,15 +2603,14 @@ def move_instant
|
|
2291
2603
|
return
|
2292
2604
|
end
|
2293
2605
|
|
2294
|
-
files =
|
2295
|
-
# todo: shellwords
|
2606
|
+
files = @selected_files
|
2296
2607
|
ccount = 0
|
2297
2608
|
files.each do |f|
|
2298
2609
|
FileUtils.mv f, target
|
2299
2610
|
@log.info "2.#{f} moved to #{target}."
|
2300
2611
|
ccount += 1
|
2301
2612
|
rescue StandardError => exc
|
2302
|
-
@log.warn
|
2613
|
+
@log.warn 'Case 2:'
|
2303
2614
|
@log.warn "Target is #{target}, file was #{f}"
|
2304
2615
|
@log.warn exc.to_s
|
2305
2616
|
perror exc.to_s
|
@@ -2314,11 +2625,12 @@ end
|
|
2314
2625
|
# prompt is the user friendly text of command such as list for ls, or extract for dtrx, page for less
|
2315
2626
|
# pauseyn is whether to pause after command as in file or ls
|
2316
2627
|
#
|
2317
|
-
def command_file
|
2628
|
+
def command_file prompt, *command
|
2318
2629
|
pauseyn = command.shift
|
2319
2630
|
command = command.join ' '
|
2320
|
-
|
2321
|
-
|
2631
|
+
clear_last_line
|
2632
|
+
print "[#{prompt}] Choose a file [#{@view[@cursor]}]: "
|
2633
|
+
file = ask_hint @view[@cursor]
|
2322
2634
|
# print "#{prompt} :: Enter file shortcut: "
|
2323
2635
|
# file = ask_hint
|
2324
2636
|
perror 'Command Cancelled' unless file
|
@@ -2344,8 +2656,8 @@ def ask_hint deflt=nil
|
|
2344
2656
|
key = get_char
|
2345
2657
|
return deflt if key == 'ENTER'
|
2346
2658
|
|
2347
|
-
ix = get_index(key,
|
2348
|
-
f =
|
2659
|
+
ix = get_index(key, @viewport.size)
|
2660
|
+
f = @viewport[ix] if ix
|
2349
2661
|
f
|
2350
2662
|
end
|
2351
2663
|
|
@@ -2353,13 +2665,13 @@ end
|
|
2353
2665
|
# NOTE: tput is ncurses dependent, so use stty
|
2354
2666
|
#
|
2355
2667
|
def screen_settings
|
2356
|
-
|
2357
|
-
#
|
2358
|
-
#
|
2359
|
-
|
2360
|
-
#
|
2361
|
-
#
|
2362
|
-
|
2668
|
+
@glines, @gcols = `stty size`.split.map(&:to_i)
|
2669
|
+
# @glines = `tput lines`.to_i
|
2670
|
+
# @gcols = `tput cols`.to_i
|
2671
|
+
@grows = @glines - 3
|
2672
|
+
# @pagesize = 60
|
2673
|
+
# @gviscols = 3
|
2674
|
+
@pagesize = @grows * @gviscols
|
2363
2675
|
end
|
2364
2676
|
|
2365
2677
|
## Tabs to next column in multi-column displays.
|
@@ -2371,24 +2683,24 @@ def column_next direction=0
|
|
2371
2683
|
# right movement or panning cycles back to first column
|
2372
2684
|
# leftward movement stops at first column.
|
2373
2685
|
if direction == 0
|
2374
|
-
|
2375
|
-
|
2376
|
-
|
2686
|
+
@stact += @grows
|
2687
|
+
@stact = 0 if @stact >= @viewport.size
|
2688
|
+
@cursor += @grows
|
2377
2689
|
# 2019-03-18 - zero loses offset. we need to maintain it
|
2378
|
-
#
|
2379
|
-
if
|
2380
|
-
|
2381
|
-
|
2382
|
-
|
2383
|
-
|
2690
|
+
# @cursor = 0 if @cursor >= @viewport.size
|
2691
|
+
if @cursor - @sta >= @viewport.size
|
2692
|
+
@cursor -= @grows while @cursor > @sta
|
2693
|
+
@stact -= @grows while @stact > 0
|
2694
|
+
@cursor += @grows if @cursor < @sta
|
2695
|
+
@stact += @grows if @stact < 0
|
2384
2696
|
end
|
2385
2697
|
else
|
2386
|
-
|
2387
|
-
|
2388
|
-
|
2698
|
+
@stact -= @grows
|
2699
|
+
@cursor -= @grows
|
2700
|
+
@stact = 0 if @stact < 0
|
2389
2701
|
# setting cursor as zero loses the position or offset
|
2390
2702
|
# We are trying to maintain offset
|
2391
|
-
|
2703
|
+
@cursor += @grows if @cursor < 0
|
2392
2704
|
end
|
2393
2705
|
# redraw
|
2394
2706
|
end
|
@@ -2397,10 +2709,11 @@ end
|
|
2397
2709
|
# I should be able to pass in new actions that are external commands
|
2398
2710
|
# 2019-03-08 - TODO when a file name changes or moves it must be removed
|
2399
2711
|
# from selection
|
2400
|
-
def file_actions
|
2712
|
+
def file_actions action=nil
|
2401
2713
|
h = { d: :delete, D: '/bin/rm', m: :move, v: ENV['EDITOR'] || :vim,
|
2402
2714
|
c: :copy, e: :execute,
|
2403
|
-
l: :less, p: :most,
|
2715
|
+
l: :less, p: :most,
|
2716
|
+
o: :open_current }
|
2404
2717
|
|
2405
2718
|
rbfiles = current_or_selected_files # use with ruby FileUtils
|
2406
2719
|
return if rbfiles.nil? || rbfiles.empty?
|
@@ -2448,7 +2761,7 @@ def file_actions(action = nil)
|
|
2448
2761
|
if action
|
2449
2762
|
menu_text = action
|
2450
2763
|
else
|
2451
|
-
key, menu_text = menu "File Menu for #{text}", h
|
2764
|
+
key, menu_text = menu "File Menu for #{text[0..@gcols-20]}", h
|
2452
2765
|
menu_text = :quit if key == 'q'
|
2453
2766
|
end
|
2454
2767
|
return unless menu_text # pressed some wrong key
|
@@ -2456,150 +2769,32 @@ def file_actions(action = nil)
|
|
2456
2769
|
case menu_text.to_sym
|
2457
2770
|
|
2458
2771
|
when :quit
|
2459
|
-
|
2772
|
+
1
|
2460
2773
|
when :delete
|
2461
|
-
|
2462
|
-
clear_last_line
|
2463
|
-
print "#{delcommand} #{text} ? [yn?]: "
|
2464
|
-
key = get_char
|
2465
|
-
view_selected_files if key == '?'
|
2466
|
-
return if key != 'y'
|
2467
|
-
|
2468
|
-
clear_last_line
|
2469
|
-
print "\r deleting ..."
|
2470
|
-
system "#{delcommand} #{shfiles}"
|
2471
|
-
message "Deleted #{text}."
|
2472
|
-
refresh
|
2774
|
+
delete_file
|
2473
2775
|
|
2474
2776
|
when :move
|
2475
|
-
|
2476
|
-
default = @move_target.nil? ? '.' : @move_target
|
2477
|
-
target = readline "Move #{text} to (#{default}): "
|
2478
|
-
return unless target
|
2479
|
-
|
2480
|
-
target = default if target == ''
|
2481
|
-
target = File.expand_path(target)
|
2482
|
-
return if target == ''
|
2483
|
-
|
2484
|
-
if count > 1 && !File.directory?(target)
|
2485
|
-
perror 'Move target must be a directory for multiple files.'
|
2486
|
-
return
|
2487
|
-
end
|
2488
|
-
|
2489
|
-
if count == 1 && !File.directory?(target) && File.exist?(target)
|
2490
|
-
perror "Target #{target} exists."
|
2491
|
-
return
|
2492
|
-
end
|
2493
|
-
|
2494
|
-
begin
|
2495
|
-
FileUtils.mv rbfiles, target
|
2496
|
-
message "Moved #{text} to #{target}."
|
2497
|
-
rescue StandardError => exc
|
2498
|
-
@log.warn "C-x move: #{exc.to_s}."
|
2499
|
-
@log.warn "MOVE: files: #{rbfiles}, target:#{target}"
|
2500
|
-
perror exc.to_s
|
2501
|
-
end
|
2502
|
-
# 2019-03-08 - TODO if success remove from selection
|
2503
|
-
refresh
|
2777
|
+
move_file
|
2504
2778
|
|
2505
2779
|
when :copy
|
2506
|
-
|
2507
|
-
# NOTE: target should not be same as source dir but there can be files
|
2508
|
-
# from multiple directories
|
2509
|
-
default = @move_target.nil? ? '.' : @move_target
|
2510
|
-
target = readline "Copy #{text} to (#{default}): "
|
2511
|
-
return unless target # C-c
|
2512
|
-
|
2513
|
-
target = default if target == ''
|
2514
|
-
target = File.expand_path(target)
|
2515
|
-
return if target == ''
|
2516
|
-
|
2517
|
-
if count > 1 && !File.directory?(target)
|
2518
|
-
perror 'Copy target must be a directory for multiple files.'
|
2519
|
-
return
|
2520
|
-
end
|
2780
|
+
copy_file
|
2521
2781
|
|
2522
|
-
|
2523
|
-
|
2524
|
-
return
|
2525
|
-
end
|
2782
|
+
when :zip
|
2783
|
+
zip_file
|
2526
2784
|
|
2527
|
-
|
2528
|
-
|
2529
|
-
message "Copied #{text} to #{target}."
|
2530
|
-
rescue StandardError => exc
|
2531
|
-
perror exc.to_s
|
2532
|
-
end
|
2533
|
-
refresh
|
2785
|
+
when :rename
|
2786
|
+
rename_file
|
2534
2787
|
|
2535
2788
|
when :chdir
|
2536
2789
|
# will only work if one selected. Check this out
|
2537
2790
|
# This works if you have searched for files and got a list
|
2538
2791
|
# with different paths.
|
2539
2792
|
# This should not be shown in regular listings XXX
|
2540
|
-
if count == 1
|
2541
|
-
change_dir File.dirname(File.expand_path(rbfiles.first))
|
2542
|
-
end
|
2793
|
+
change_dir File.dirname(File.expand_path(rbfiles.first)) if count == 1
|
2543
2794
|
|
2544
2795
|
when :set_move_target
|
2545
2796
|
set_move_target current_file
|
2546
2797
|
|
2547
|
-
when :zip
|
2548
|
-
|
2549
|
-
target = readline 'Archive name: '
|
2550
|
-
return unless target
|
2551
|
-
return if target == ''
|
2552
|
-
|
2553
|
-
if File.exist? target
|
2554
|
-
perror "Target (#{target}) exists"
|
2555
|
-
return
|
2556
|
-
end
|
2557
|
-
if target && target.size < 4
|
2558
|
-
perror 'Use target of more than 4 characters.'
|
2559
|
-
return
|
2560
|
-
end
|
2561
|
-
|
2562
|
-
# convert absolute paths to relative ones in this zip
|
2563
|
-
require 'pathname'
|
2564
|
-
base = Pathname.new Dir.pwd
|
2565
|
-
relfiles = rbfiles.map {|f| p = Pathname.new(f); p.relative_path_from(base) }
|
2566
|
-
zfiles = Shellwords.join relfiles
|
2567
|
-
|
2568
|
-
# the problem with zip is that we have full paths
|
2569
|
-
# so the zip file has full paths and extraction sucks
|
2570
|
-
|
2571
|
-
# pause "(#{target}).zipping #{relfiles.count}: #{zfiles}"
|
2572
|
-
system "tar zcvf #{target} #{zfiles}"
|
2573
|
-
message "Created #{target} with #{relfiles.count} files."
|
2574
|
-
setup_terminal
|
2575
|
-
refresh
|
2576
|
-
|
2577
|
-
when :rename
|
2578
|
-
if count > 1
|
2579
|
-
perror 'Select only one file for rename.'
|
2580
|
-
return
|
2581
|
-
end
|
2582
|
-
|
2583
|
-
target = readline "Rename #{text} to : "
|
2584
|
-
return if target == '' || target == '.' || target == '..'
|
2585
|
-
|
2586
|
-
# file = File.expand_path(files)
|
2587
|
-
# target = File.basename(file) if target == '.'
|
2588
|
-
if File.exist? target
|
2589
|
-
perror "Target (#{target}) exists."
|
2590
|
-
return
|
2591
|
-
end
|
2592
|
-
|
2593
|
-
begin
|
2594
|
-
FileUtils.mv first, target
|
2595
|
-
message "Renamed #{first} to #{target}."
|
2596
|
-
rescue StandardError => exc
|
2597
|
-
@log.warn exc.to_s
|
2598
|
-
@log.warn "RENAME: files: #{first}, target:#{target}"
|
2599
|
-
pause exc.to_s
|
2600
|
-
end
|
2601
|
-
refresh
|
2602
|
-
|
2603
2798
|
when :most, :less, :vim
|
2604
2799
|
|
2605
2800
|
system "#{menu_text} #{shfiles}"
|
@@ -2607,32 +2802,7 @@ def file_actions(action = nil)
|
|
2607
2802
|
# should we remove from selection ?
|
2608
2803
|
|
2609
2804
|
when :remspace
|
2610
|
-
|
2611
|
-
# Issue. in many cases directory may have spaces,
|
2612
|
-
# so we are using basename only. So use in one directory.
|
2613
|
-
# TODO: move to script
|
2614
|
-
clear_last_line
|
2615
|
-
pause "Remove spaces from #{text}."
|
2616
|
-
|
2617
|
-
ccount = 0
|
2618
|
-
rbfiles.each do |name|
|
2619
|
-
f = File.basename(name)
|
2620
|
-
next unless File.exist?(f)
|
2621
|
-
next unless f.index ' '
|
2622
|
-
|
2623
|
-
target = f.tr(' ', '_')
|
2624
|
-
next if File.exist? target
|
2625
|
-
|
2626
|
-
begin
|
2627
|
-
FileUtils.mv f, target
|
2628
|
-
ccount += 1
|
2629
|
-
rescue StandardError => exc
|
2630
|
-
@log.warn "REMSPACE: #{exc.to_s}"
|
2631
|
-
perror exc.to_s
|
2632
|
-
end
|
2633
|
-
end
|
2634
|
-
message "Renamed #{ccount} files."
|
2635
|
-
refresh
|
2805
|
+
remove_spaces_from_name
|
2636
2806
|
|
2637
2807
|
when :execute
|
2638
2808
|
execute
|
@@ -2649,6 +2819,7 @@ def file_actions(action = nil)
|
|
2649
2819
|
system "#{menu_text} #{shfiles}"
|
2650
2820
|
pause # putting this back 2019-04-13 - file doesn't show anything
|
2651
2821
|
message "Ran #{menu_text}."
|
2822
|
+
@log.info "#{menu_text} #{shfiles}"
|
2652
2823
|
setup_terminal
|
2653
2824
|
refresh
|
2654
2825
|
end
|
@@ -2656,13 +2827,13 @@ def file_actions(action = nil)
|
|
2656
2827
|
return if count == 0
|
2657
2828
|
|
2658
2829
|
clean_selected_files
|
2659
|
-
|
2830
|
+
visual_block_clear # 2019-04-15 - get out of mode after operation over.
|
2660
2831
|
end
|
2661
2832
|
|
2662
2833
|
# remove non-existent files from select list due to move or delete
|
2663
2834
|
# or rename or whatever
|
2664
2835
|
def clean_selected_files
|
2665
|
-
|
2836
|
+
@selected_files.select! { |x| x = File.expand_path(x); File.exist?(x) }
|
2666
2837
|
end
|
2667
2838
|
|
2668
2839
|
# set the default target for further moves
|
@@ -2675,13 +2846,12 @@ def set_move_target cf=current_file
|
|
2675
2846
|
message "Move target set to #{cf}."
|
2676
2847
|
end
|
2677
2848
|
|
2678
|
-
|
2679
2849
|
# increase or decrease column
|
2680
|
-
def columns_incdec
|
2681
|
-
|
2682
|
-
|
2683
|
-
|
2684
|
-
|
2850
|
+
def columns_incdec howmany
|
2851
|
+
@gviscols += howmany.to_i
|
2852
|
+
@gviscols = 1 if @gviscols < 1
|
2853
|
+
@gviscols = 6 if @gviscols > 6
|
2854
|
+
@pagesize = @grows * @gviscols
|
2685
2855
|
end
|
2686
2856
|
|
2687
2857
|
# bind a key to an external command wich can be then be used for files
|
@@ -2692,7 +2862,7 @@ def bindkey_ext_command
|
|
2692
2862
|
key = get_char
|
2693
2863
|
return if key == 'Q'
|
2694
2864
|
|
2695
|
-
if
|
2865
|
+
if /^[A-Z]$/.match?(key)
|
2696
2866
|
print "Enter an external command to bind to #{key}: "
|
2697
2867
|
com = gets.chomp
|
2698
2868
|
if com != ''
|
@@ -2702,7 +2872,7 @@ def bindkey_ext_command
|
|
2702
2872
|
end
|
2703
2873
|
print 'Pause after output [y/n]: '
|
2704
2874
|
yn = get_char
|
2705
|
-
|
2875
|
+
@bindings[key] = "command_file #{pro} #{yn} #{com}"
|
2706
2876
|
end
|
2707
2877
|
end
|
2708
2878
|
|
@@ -2715,22 +2885,22 @@ def ag
|
|
2715
2885
|
pattern = readline 'Enter a pattern to search (ag): '
|
2716
2886
|
return if pattern == ''
|
2717
2887
|
|
2718
|
-
|
2888
|
+
@title = "Files found using 'ag -t: ' #{pattern}"
|
2719
2889
|
|
2720
2890
|
## ag options :
|
2721
2891
|
# -t : all text files
|
2722
2892
|
# -l : print only file names
|
2723
2893
|
# -a : print all files, even ignored
|
2724
|
-
system %
|
2894
|
+
system %(ag -t "#{pattern}" | less)
|
2725
2895
|
|
2726
2896
|
pause
|
2727
2897
|
files = `ag -lt "#{pattern}"`.split("\n")
|
2728
2898
|
if files.empty?
|
2729
2899
|
perror "No files found for #{pattern}."
|
2730
|
-
|
2900
|
+
@title = nil
|
2731
2901
|
return
|
2732
2902
|
end
|
2733
|
-
|
2903
|
+
@files = files
|
2734
2904
|
# redraw
|
2735
2905
|
end
|
2736
2906
|
|
@@ -2740,12 +2910,12 @@ def ffind
|
|
2740
2910
|
pattern = readline '! find . -iname :'
|
2741
2911
|
return if pattern == ''
|
2742
2912
|
|
2743
|
-
|
2913
|
+
@title = "Files found using 'find' #{pattern}"
|
2744
2914
|
files = `find . -iname "#{pattern}"`.split("\n")
|
2745
2915
|
if files.empty?
|
2746
2916
|
perror 'No files found. Try adding *'
|
2747
2917
|
else
|
2748
|
-
|
2918
|
+
@files = files
|
2749
2919
|
end
|
2750
2920
|
# redraw
|
2751
2921
|
end
|
@@ -2755,13 +2925,13 @@ def locate
|
|
2755
2925
|
pattern = readline
|
2756
2926
|
return if pattern == ''
|
2757
2927
|
|
2758
|
-
|
2928
|
+
@title = "Files found using 'locate' #{pattern}"
|
2759
2929
|
files = `locate #{pattern}`.split("\n")
|
2760
2930
|
files.select! { |x| x = File.expand_path(x); File.exist?(x) }
|
2761
2931
|
if files.empty?
|
2762
2932
|
perror 'No files found.'
|
2763
2933
|
else
|
2764
|
-
|
2934
|
+
@files = files
|
2765
2935
|
end
|
2766
2936
|
# redraw
|
2767
2937
|
end
|
@@ -2773,11 +2943,11 @@ def z_interface
|
|
2773
2943
|
file = File.expand_path('~/.z')
|
2774
2944
|
return unless File.exist? file
|
2775
2945
|
|
2776
|
-
|
2777
|
-
|
2946
|
+
@title = 'Directories from ~/.z'
|
2947
|
+
@files = `sort -rn -k2 -t '|' ~/.z | cut -f1 -d '|'`.split("\n")
|
2778
2948
|
home = ENV['HOME']
|
2779
2949
|
# shorten file names
|
2780
|
-
|
2950
|
+
@files.collect! do |f|
|
2781
2951
|
f.sub(/#{home}/, '~')
|
2782
2952
|
end
|
2783
2953
|
# redraw
|
@@ -2785,6 +2955,7 @@ end
|
|
2785
2955
|
|
2786
2956
|
def vidir
|
2787
2957
|
system 'vidir'
|
2958
|
+
refresh
|
2788
2959
|
setup_terminal
|
2789
2960
|
end
|
2790
2961
|
|
@@ -2804,140 +2975,142 @@ end
|
|
2804
2975
|
# move cursor down a line
|
2805
2976
|
def cursor_dn
|
2806
2977
|
@movement = :down
|
2807
|
-
@old_cursor =
|
2978
|
+
@old_cursor = @cursor
|
2808
2979
|
moveto(pos + 1)
|
2809
2980
|
end
|
2810
2981
|
|
2811
2982
|
def cursor_up
|
2812
|
-
@old_cursor =
|
2983
|
+
@old_cursor = @cursor
|
2813
2984
|
@movement = :up
|
2814
2985
|
moveto(pos - 1)
|
2815
2986
|
end
|
2816
2987
|
|
2817
2988
|
# return cursor position
|
2818
2989
|
def pos
|
2819
|
-
|
2990
|
+
@cursor
|
2820
2991
|
end
|
2821
2992
|
|
2822
2993
|
# move cursor to given position/line
|
2823
2994
|
def moveto position
|
2824
|
-
orig =
|
2825
|
-
|
2826
|
-
|
2827
|
-
|
2995
|
+
orig = @cursor
|
2996
|
+
@cursor = position
|
2997
|
+
@cursor = [@cursor, @view.size - 1].min
|
2998
|
+
@cursor = [@cursor, 0].max
|
2828
2999
|
|
2829
3000
|
# try to stop it from landing on separator
|
2830
3001
|
if current_file == SEPARATOR
|
2831
|
-
|
2832
|
-
|
3002
|
+
@cursor += 1 if @movement == :down
|
3003
|
+
@cursor -= 1 if @movement == :up
|
2833
3004
|
return
|
2834
3005
|
end
|
2835
3006
|
|
2836
3007
|
# 2019-03-18 - adding sta
|
2837
|
-
#
|
3008
|
+
# @sta = position - only when page flips and file not visible
|
2838
3009
|
# FIXME not correct, it must stop at end or correctly cycle
|
2839
3010
|
# sta goes to 0 but cursor remains at 70
|
2840
3011
|
# viewport.size may be wrong here, maybe should be pagesize only
|
2841
|
-
oldsta =
|
2842
|
-
if
|
2843
|
-
|
2844
|
-
|
3012
|
+
oldsta = @sta
|
3013
|
+
if @cursor - @sta >= @pagesize
|
3014
|
+
@sta += @pagesize
|
3015
|
+
# elsif @sta - @cursor >= @viewport.size
|
2845
3016
|
end
|
2846
|
-
if
|
2847
|
-
|
2848
|
-
#
|
3017
|
+
if @sta > @cursor
|
3018
|
+
@sta -= @pagesize
|
3019
|
+
# @sta = @cursor
|
2849
3020
|
end
|
2850
3021
|
|
2851
|
-
@movement = nil if oldsta !=
|
3022
|
+
@movement = nil if oldsta != @sta # we need to redraw
|
2852
3023
|
|
2853
|
-
star = [orig,
|
2854
|
-
fin = [orig,
|
2855
|
-
return unless
|
3024
|
+
star = [orig, @cursor].min
|
3025
|
+
fin = [orig, @cursor].max
|
3026
|
+
return unless @visual_mode
|
2856
3027
|
|
2857
3028
|
@movement = nil # visual mode needs to redraw page
|
2858
3029
|
|
2859
3030
|
# PWD has to be there in selction
|
2860
3031
|
if selected? current_file
|
2861
3032
|
# this depends on the direction
|
2862
|
-
#
|
2863
|
-
remove_from_selection
|
3033
|
+
# @selected_files = @selected_files - @view[star..fin]
|
3034
|
+
remove_from_selection @view[star..fin]
|
2864
3035
|
## current row remains in selection always.
|
2865
3036
|
add_to_selection current_file
|
2866
3037
|
else
|
2867
|
-
#
|
2868
|
-
add_to_selection
|
3038
|
+
# @selected_files.concat @view[star..fin]
|
3039
|
+
add_to_selection @view[star..fin]
|
2869
3040
|
end
|
2870
|
-
message "#{
|
2871
|
-
# ensure
|
3041
|
+
message "#{@selected_files.count} files selected. "
|
3042
|
+
# ensure
|
2872
3043
|
# redraw
|
2873
3044
|
end
|
2874
3045
|
# --
|
2875
3046
|
|
2876
3047
|
# is given file in selected array
|
2877
|
-
def visited?
|
2878
|
-
|
2879
|
-
file = File.join(
|
2880
|
-
return
|
3048
|
+
def visited? file
|
3049
|
+
@current_dir ||= Dir.pwd
|
3050
|
+
file = File.join(@current_dir, file)
|
3051
|
+
return @visited_files.index file
|
2881
3052
|
end
|
2882
3053
|
|
2883
3054
|
# ------------- selection related methods --------------------------------#
|
2884
3055
|
|
2885
3056
|
# is given file in selected array
|
2886
|
-
def selected?
|
2887
|
-
|
2888
|
-
file = File.join(
|
2889
|
-
return
|
3057
|
+
def selected? file
|
3058
|
+
@current_dir ||= Dir.pwd
|
3059
|
+
file = File.join(@current_dir, file)
|
3060
|
+
return @selected_files.index file
|
2890
3061
|
end
|
2891
3062
|
|
2892
3063
|
# add given file/s to selected file list
|
2893
|
-
def add_to_selection
|
3064
|
+
def add_to_selection file
|
2894
3065
|
ff = file
|
2895
3066
|
case file
|
2896
3067
|
when String
|
2897
3068
|
ff = [file]
|
2898
3069
|
end
|
2899
|
-
|
3070
|
+
@current_dir ||= Dir.pwd
|
2900
3071
|
ff.each do |f|
|
2901
|
-
full = File.join(
|
2902
|
-
|
3072
|
+
full = File.join(@current_dir, f)
|
3073
|
+
@selected_files.push(full) unless @selected_files.include?(full)
|
2903
3074
|
end
|
2904
3075
|
end
|
2905
3076
|
|
2906
|
-
def remove_from_selection
|
3077
|
+
def remove_from_selection file
|
2907
3078
|
ff = file
|
2908
3079
|
case file
|
2909
3080
|
when String
|
2910
3081
|
ff = [file]
|
2911
3082
|
end
|
2912
|
-
|
3083
|
+
@current_dir ||= Dir.pwd
|
2913
3084
|
ff.each do |f|
|
2914
|
-
full = File.join(
|
2915
|
-
|
3085
|
+
full = File.join(@current_dir, f)
|
3086
|
+
@selected_files.delete full
|
2916
3087
|
end
|
2917
3088
|
end
|
2918
3089
|
|
2919
3090
|
# ------------- visual mode methods --------------------------------#
|
2920
|
-
def
|
2921
|
-
|
2922
|
-
|
2923
|
-
|
2924
|
-
|
2925
|
-
|
2926
|
-
|
2927
|
-
|
2928
|
-
|
3091
|
+
def toggle_visual_mode
|
3092
|
+
@mode = nil
|
3093
|
+
# @visual_mode = !@visual_mode
|
3094
|
+
toggle_value :visual_mode
|
3095
|
+
return unless @visual_mode
|
3096
|
+
|
3097
|
+
@mode = 'VIS'
|
3098
|
+
@visual_block_start = @cursor
|
3099
|
+
add_to_selection current_file
|
2929
3100
|
end
|
2930
3101
|
|
2931
|
-
# Called from Escape key
|
3102
|
+
# Called from Escape key and scripts and file actions. Clears selection.
|
2932
3103
|
def visual_block_clear
|
2933
|
-
if
|
2934
|
-
star = [
|
2935
|
-
fin = [
|
2936
|
-
remove_from_selection
|
2937
|
-
end
|
2938
|
-
|
2939
|
-
|
2940
|
-
|
3104
|
+
if @visual_block_start
|
3105
|
+
star = [@visual_block_start, @cursor].min
|
3106
|
+
fin = [@visual_block_start, @cursor].max
|
3107
|
+
remove_from_selection @view[star..fin]
|
3108
|
+
end
|
3109
|
+
@visual_block_start = nil
|
3110
|
+
@toggles[:visual_mode] = @visual_mode = false
|
3111
|
+
@mode = nil if @mode == 'VIS'
|
3112
|
+
# is this the right place to put this ??? 2019-04-16 -
|
3113
|
+
clean_selected_files
|
2941
3114
|
end
|
2942
3115
|
|
2943
3116
|
# ------------- file matching methods --------------------------------#
|
@@ -2951,19 +3124,19 @@ def file_starting_with first_char=nil
|
|
2951
3124
|
goto_line ix if ix
|
2952
3125
|
end
|
2953
3126
|
|
2954
|
-
def file_matching?
|
3127
|
+
def file_matching? file, patt
|
2955
3128
|
file =~ /#{patt}/
|
2956
3129
|
end
|
2957
3130
|
|
2958
3131
|
## generic method to take cursor to next position for a given condition
|
2959
|
-
def return_next_match
|
3132
|
+
def return_next_match binding, *args
|
2960
3133
|
first = nil
|
2961
3134
|
ix = 0
|
2962
|
-
|
3135
|
+
@view.each_with_index do |elem, ii|
|
2963
3136
|
next unless binding.call(elem, *args)
|
2964
3137
|
|
2965
3138
|
first ||= ii
|
2966
|
-
if ii >
|
3139
|
+
if ii > @cursor
|
2967
3140
|
ix = ii
|
2968
3141
|
break
|
2969
3142
|
end
|
@@ -2977,10 +3150,10 @@ end
|
|
2977
3150
|
# position cursor on a specific line which could be on a nother page
|
2978
3151
|
# therefore calculate the correct start offset of the display also.
|
2979
3152
|
def goto_line pos
|
2980
|
-
pages = ((pos * 1.00) /
|
3153
|
+
pages = ((pos * 1.00) / @pagesize).ceil
|
2981
3154
|
pages -= 1
|
2982
|
-
|
2983
|
-
|
3155
|
+
@sta = pages * @pagesize + 1
|
3156
|
+
@cursor = pos
|
2984
3157
|
end
|
2985
3158
|
|
2986
3159
|
# return filetype of file using `file` external command.
|
@@ -3002,10 +3175,10 @@ end
|
|
3002
3175
|
|
3003
3176
|
def opener_for f
|
3004
3177
|
# by default, default command is nil. Changed in toggle_pager_mode
|
3005
|
-
|
3178
|
+
@default_command ||= '$PAGER'
|
3006
3179
|
# by default mode, is false, changed in toggle_pager_mode
|
3007
3180
|
# Get filetype, and check for command for type, else extn else unknown
|
3008
|
-
if
|
3181
|
+
if !@editor_mode
|
3009
3182
|
ft = filetype f
|
3010
3183
|
comm = PAGER_COMMAND[ft] if ft
|
3011
3184
|
comm ||= PAGER_COMMAND[File.extname(f)]
|
@@ -3015,30 +3188,30 @@ def opener_for f
|
|
3015
3188
|
# opens everything? what of images etc
|
3016
3189
|
# TODO use editor only for text, otherwise use filetype or another hash
|
3017
3190
|
# like editor_command
|
3018
|
-
comm =
|
3191
|
+
comm = @default_command
|
3019
3192
|
end
|
3020
|
-
comm ||=
|
3193
|
+
comm ||= @default_command
|
3021
3194
|
comm
|
3022
3195
|
end
|
3023
3196
|
|
3024
3197
|
# save offset in directory so we can revert to it when we return
|
3025
3198
|
def save_dir_pos
|
3026
3199
|
# the next line meant that it would not save first directory.
|
3027
|
-
# return if
|
3200
|
+
# return if @sta == 0 && @cursor == 0
|
3028
3201
|
|
3029
|
-
|
3202
|
+
@dir_position[Dir.pwd] = [@sta, @cursor]
|
3030
3203
|
end
|
3031
3204
|
|
3032
3205
|
# revert to the position we were at in this directory
|
3033
3206
|
def revert_dir_pos
|
3034
|
-
|
3035
|
-
|
3036
|
-
a =
|
3207
|
+
@sta = 0
|
3208
|
+
@cursor = 0
|
3209
|
+
a = @dir_position[Dir.pwd]
|
3037
3210
|
if a
|
3038
|
-
|
3039
|
-
|
3040
|
-
raise "sta is nil for #{Dir.pwd} : #{
|
3041
|
-
raise 'cursor is nil' unless
|
3211
|
+
@sta = a.first
|
3212
|
+
@cursor = a[1]
|
3213
|
+
raise "sta is nil for #{Dir.pwd} : #{@dir_position[Dir.pwd]}" unless @sta
|
3214
|
+
raise 'cursor is nil' unless @cursor
|
3042
3215
|
end
|
3043
3216
|
end
|
3044
3217
|
|
@@ -3052,7 +3225,7 @@ def create_a_dir
|
|
3052
3225
|
end
|
3053
3226
|
begin
|
3054
3227
|
FileUtils.mkdir str
|
3055
|
-
|
3228
|
+
@used_dirs.insert(0, str) if File.exist?(str)
|
3056
3229
|
refresh
|
3057
3230
|
rescue StandardError => ex
|
3058
3231
|
perror "Error in newdir: #{ex}"
|
@@ -3065,32 +3238,38 @@ def create_a_file
|
|
3065
3238
|
|
3066
3239
|
system %($EDITOR "#{str}")
|
3067
3240
|
setup_terminal
|
3068
|
-
|
3241
|
+
@visited_files.insert(0, str) if File.exist?(str)
|
3069
3242
|
refresh
|
3070
3243
|
end
|
3071
3244
|
|
3072
3245
|
# convenience method to return file under cursor
|
3073
3246
|
def current_file
|
3074
|
-
|
3247
|
+
@view[@cursor]
|
3075
3248
|
end
|
3076
3249
|
|
3077
3250
|
def current_or_selected_files
|
3078
|
-
return
|
3251
|
+
return @selected_files unless @selected_files.empty?
|
3079
3252
|
|
3080
3253
|
return [current_file]
|
3081
3254
|
end
|
3082
3255
|
|
3083
3256
|
# ------------------- scripts ------------------ #
|
3084
3257
|
# prompt for scripts to execute, giving file name under cursor
|
3085
|
-
def scripts
|
3258
|
+
def scripts binding=nil
|
3086
3259
|
# some scripts may work with the selected_files and not want to be called
|
3087
3260
|
# with filenames.
|
3088
3261
|
write_selected_files
|
3089
3262
|
|
3090
|
-
|
3091
|
-
|
3092
|
-
|
3093
|
-
|
3263
|
+
unless binding
|
3264
|
+
title = 'Select a script'
|
3265
|
+
script_path = '~/.config/cetus/scripts'
|
3266
|
+
binding = `find #{script_path} -type f | fzf --prompt="#{title} :"`.chomp
|
3267
|
+
return if binding.nil? || binding == ''
|
3268
|
+
end
|
3269
|
+
unless File.exist? binding
|
3270
|
+
@log.warn "Unable to find #{binding}"
|
3271
|
+
return
|
3272
|
+
end
|
3094
3273
|
|
3095
3274
|
# TODO: check if binding is a file and executable
|
3096
3275
|
# xargs only seems to take the first file
|
@@ -3098,20 +3277,38 @@ def scripts
|
|
3098
3277
|
# cf = Shellwords.join(current_or_selected_files)
|
3099
3278
|
# This was getting called repeatedly even if script used selected_files
|
3100
3279
|
# current_or_selected_files.each do |file|
|
3101
|
-
|
3280
|
+
# system %( #{binding} "#{file}" )
|
3102
3281
|
# end
|
3103
3282
|
|
3104
3283
|
# 2019-04-08 - to avoid confusion, we pass name of file under cursor
|
3105
3284
|
# script may ignore this and use selected_files
|
3285
|
+
|
3286
|
+
# reset clears the screen, we don't want that. just unhide cursor and echo keys TODO
|
3106
3287
|
reset_terminal
|
3107
3288
|
system %( #{binding} "#{current_file}" )
|
3108
3289
|
|
3109
3290
|
# system %(echo "#{cf}" | xargs #{binding})
|
3110
3291
|
pause
|
3111
3292
|
setup_terminal
|
3293
|
+
visual_block_clear
|
3112
3294
|
refresh
|
3113
3295
|
end
|
3114
3296
|
|
3297
|
+
# this is quite important and should not be left to a script
|
3298
|
+
# example of calling a script from somewhere directly without selection
|
3299
|
+
def execute_script filename
|
3300
|
+
script_path = '~/.config/cetus/scripts'
|
3301
|
+
script = File.expand_path(File.join(script_path, 'filename'))
|
3302
|
+
return unless File.exist? script
|
3303
|
+
|
3304
|
+
scripts script
|
3305
|
+
end
|
3306
|
+
|
3307
|
+
# maybe do this internally
|
3308
|
+
def create_dir_with_selection
|
3309
|
+
execute_script 'create_dir_with_selection'
|
3310
|
+
end
|
3311
|
+
|
3115
3312
|
# allow user to select a script that generates filenames which
|
3116
3313
|
# will be displayed for selection or action.
|
3117
3314
|
def generators
|
@@ -3123,9 +3320,8 @@ def generators
|
|
3123
3320
|
return if binding.nil? || binding == ''
|
3124
3321
|
|
3125
3322
|
# call generator and accept list of files
|
3126
|
-
|
3127
|
-
|
3128
|
-
|
3323
|
+
@title = "Files from #{File.basename(binding)}"
|
3324
|
+
@files = `#{binding} "#{current_file}"`.split("\n")
|
3129
3325
|
end
|
3130
3326
|
# ------------- end of scripts --------------------------------#
|
3131
3327
|
|
@@ -3134,7 +3330,7 @@ def view_selected_files
|
|
3134
3330
|
fname = write_selected_files
|
3135
3331
|
|
3136
3332
|
unless fname
|
3137
|
-
message
|
3333
|
+
message 'No file selected. '
|
3138
3334
|
return
|
3139
3335
|
end
|
3140
3336
|
|
@@ -3143,11 +3339,15 @@ def view_selected_files
|
|
3143
3339
|
end
|
3144
3340
|
# ------------- end of view_selected_files --------------------------------#
|
3145
3341
|
|
3342
|
+
def list_selected_files
|
3343
|
+
@title = 'Selected Files'
|
3344
|
+
@files = @selected_files
|
3345
|
+
end
|
3346
|
+
|
3146
3347
|
# write selected files to a file and return path
|
3147
3348
|
# if no selected files then blank out the file, or else
|
3148
3349
|
# script could use old selection again.
|
3149
3350
|
def write_selected_files
|
3150
|
-
|
3151
3351
|
require 'pathname'
|
3152
3352
|
# fname = File.join(File.dirname(CONFIG_FILE), 'selected_files')
|
3153
3353
|
# 2019-04-10 - changed to ~/tmp otherwise confusion about location
|
@@ -3155,7 +3355,7 @@ def write_selected_files
|
|
3155
3355
|
fname = File.expand_path(fname)
|
3156
3356
|
|
3157
3357
|
# remove file if no selection
|
3158
|
-
if
|
3358
|
+
if @selected_files.empty?
|
3159
3359
|
File.unlink(fname) if File.exist?(fname)
|
3160
3360
|
return nil
|
3161
3361
|
end
|
@@ -3164,8 +3364,7 @@ def write_selected_files
|
|
3164
3364
|
# TODO: what if unix commands need escaped files ?
|
3165
3365
|
base = Pathname.new Dir.pwd
|
3166
3366
|
File.open(fname, 'w') do |file|
|
3167
|
-
|
3168
|
-
|
3367
|
+
@selected_files.each do |row|
|
3169
3368
|
# use relative filename. Otherwise things like zip and tar run into issues
|
3170
3369
|
unless @selected_files_fullpath_flag
|
3171
3370
|
p = Pathname.new(row)
|
@@ -3178,23 +3377,24 @@ def write_selected_files
|
|
3178
3377
|
|
3179
3378
|
return fname
|
3180
3379
|
end
|
3380
|
+
|
3181
3381
|
##
|
3182
3382
|
# Editing of the User Dir List.
|
3183
3383
|
# remove current entry from used dirs list, since we may not want some entries being there
|
3184
3384
|
#
|
3185
3385
|
def remove_from_list
|
3186
|
-
unless
|
3187
|
-
sz =
|
3386
|
+
unless @selected_files.empty?
|
3387
|
+
sz = @selected_files.size
|
3188
3388
|
print "Remove #{sz} files from used list (y)?: "
|
3189
3389
|
key = get_char
|
3190
3390
|
return if key != 'y'
|
3191
3391
|
|
3192
|
-
arr =
|
3392
|
+
arr = @selected_files.map { |path| File.expand_path(path) }
|
3193
3393
|
|
3194
|
-
|
3195
|
-
|
3394
|
+
@used_dirs = @used_dirs - arr
|
3395
|
+
@visited_files = @visited_files - arr
|
3196
3396
|
unselect_all
|
3197
|
-
|
3397
|
+
@modified = true
|
3198
3398
|
refresh
|
3199
3399
|
return
|
3200
3400
|
end
|
@@ -3202,19 +3402,19 @@ def remove_from_list
|
|
3202
3402
|
# no file selected, use file under cursor
|
3203
3403
|
print
|
3204
3404
|
## what if selected some rows
|
3205
|
-
file =
|
3405
|
+
file = @view[@cursor]
|
3206
3406
|
print "Remove #{file} from used list (y)?: "
|
3207
3407
|
key = get_char
|
3208
3408
|
return if key != 'y'
|
3209
3409
|
|
3210
3410
|
file = File.expand_path(file)
|
3211
3411
|
if File.directory? file
|
3212
|
-
|
3412
|
+
@used_dirs.delete(file)
|
3213
3413
|
else
|
3214
|
-
|
3414
|
+
@visited_files.delete(file)
|
3215
3415
|
end
|
3216
3416
|
refresh
|
3217
|
-
|
3417
|
+
@modified = true
|
3218
3418
|
end
|
3219
3419
|
|
3220
3420
|
#
|
@@ -3224,158 +3424,152 @@ end
|
|
3224
3424
|
# latest source, but in some cases even this can be misleading since running a program accesses
|
3225
3425
|
# include files.
|
3226
3426
|
def enhance_file_list
|
3227
|
-
return unless
|
3427
|
+
return unless cget(:enhanced_mode)
|
3428
|
+
|
3228
3429
|
begin
|
3229
|
-
actr =
|
3230
|
-
|
3231
|
-
|
3232
|
-
|
3233
|
-
|
3234
|
-
|
3235
|
-
|
3236
|
-
|
3237
|
-
|
3238
|
-
|
3239
|
-
|
3240
|
-
|
3241
|
-
|
3242
|
-
|
3243
|
-
|
3244
|
-
|
3245
|
-
|
3246
|
-
|
3247
|
-
|
3248
|
-
|
3249
|
-
|
3250
|
-
|
3251
|
-
|
3252
|
-
# @log.warn "f1:#{f1} != f:#{f} in #{d}" if f1 != f
|
3253
|
-
# order returned by zsh and ruby are different since the time is the same
|
3254
|
-
|
3255
|
-
# TODO: use ruby this throws errors if not files
|
3256
|
-
if f && !f.empty?
|
3257
|
-
# @log.debug "CONCAT: #{f}" if @debug_flag
|
3258
|
-
$files.concat f
|
3259
|
-
$files.concat get_important_files(d)
|
3260
|
-
end
|
3261
|
-
return
|
3262
|
-
end
|
3263
|
-
#
|
3264
|
-
# check if a ruby project dir, although it could be a backup file too,
|
3265
|
-
# if so , expand lib and maybe bin, put a couple recent files
|
3266
|
-
# FIXME: gemspec file will be same as current folder
|
3267
|
-
if $files.index('Gemfile') || !$files.grep(/\.gemspec/).empty?
|
3268
|
-
# usually the lib dir has only one file and one dir
|
3269
|
-
flg = false
|
3270
|
-
$files.concat get_important_files(Dir.pwd)
|
3271
|
-
if $files.index('lib/')
|
3272
|
-
# get first five entries by modification time
|
3273
|
-
# f1 = `zsh -c 'print -rl -- lib/*(om[1,5]MN)'`.split("\n")
|
3274
|
-
f = get_files_by_mtime('lib')&.first(5)
|
3275
|
-
# @log.warn "f1 #{f1} != #{f} in lib" if f1 != f
|
3430
|
+
actr = @files.size
|
3431
|
+
|
3432
|
+
# zshglob: M = MARK_DIRS with slash
|
3433
|
+
# zshglob: N = NULL_GLOB no error if no result, this is causing space to split
|
3434
|
+
# file sometimes for single file.
|
3435
|
+
|
3436
|
+
# FIXME: append only if we are adding.
|
3437
|
+
# FIXME: this makes it possible to select this row,
|
3438
|
+
# FIXME: and count it as a file !!
|
3439
|
+
# @files.append SEPARATOR
|
3440
|
+
|
3441
|
+
# if only one entry and its a dir
|
3442
|
+
# get its children and maybe the recent mod files a few
|
3443
|
+
# FIXME: simplify condition into one
|
3444
|
+
if @files.size == 1
|
3445
|
+
# its a dir, let give the next level at least
|
3446
|
+
return unless @files.first[-1] == '/'
|
3447
|
+
|
3448
|
+
d = @files.first
|
3449
|
+
# zshglob: 'om' = ordered on modification time
|
3450
|
+
# f1 = `zsh -c 'print -rl -- #{d}*(omM)'`.split("\n")
|
3451
|
+
f = get_files_by_mtime(d)
|
3452
|
+
|
3276
3453
|
if f && !f.empty?
|
3277
|
-
|
3278
|
-
|
3454
|
+
@files.concat f
|
3455
|
+
@files.concat get_important_files(d)
|
3279
3456
|
end
|
3280
|
-
|
3281
|
-
|
3282
|
-
|
3283
|
-
|
3284
|
-
|
3285
|
-
|
3286
|
-
|
3457
|
+
return
|
3458
|
+
end
|
3459
|
+
#
|
3460
|
+
# check if a ruby project dir, although it could be a backup file too,
|
3461
|
+
# if so , expand lib and maybe bin, put a couple recent files
|
3462
|
+
# FIXME: gemspec file will be same as current folder
|
3463
|
+
if @files.index('Gemfile') || !@files.grep(/\.gemspec/).empty?
|
3464
|
+
# usually the lib dir has only one file and one dir
|
3465
|
+
flg = false
|
3466
|
+
@files.concat get_important_files(Dir.pwd)
|
3467
|
+
if @files.index('lib/')
|
3468
|
+
# get first five entries by modification time
|
3469
|
+
# f1 = `zsh -c 'print -rl -- lib/*(om[1,5]MN)'`.split("\n")
|
3470
|
+
f = get_files_by_mtime('lib')&.first(5)
|
3471
|
+
# @log.warn "f1 #{f1} != #{f} in lib" if f1 != f
|
3287
3472
|
if f && !f.empty?
|
3288
|
-
insert_into_list(
|
3473
|
+
insert_into_list('lib/', f)
|
3289
3474
|
flg = true
|
3290
3475
|
end
|
3291
|
-
end
|
3292
|
-
end
|
3293
|
-
|
3294
|
-
# look into bin directory and get first five modified files
|
3295
|
-
if $files.index('bin/')
|
3296
|
-
# f1 = `zsh -c 'print -rl -- bin/*(om[1,5]MN)'`.split("\n")
|
3297
|
-
f = get_files_by_mtime("bin")&.first(5)
|
3298
|
-
# @log.warn "2768 f1 #{f1} != #{f} in bin/" if f1 != f
|
3299
|
-
insert_into_list('bin/', f) if f && !f.empty?
|
3300
|
-
flg = true
|
3301
|
-
end
|
3302
|
-
return if flg
|
3303
|
-
|
3304
|
-
# lib has a dir in it with the gem name
|
3305
|
-
|
3306
|
-
end
|
3307
|
-
return if $files.size > 15
|
3308
3476
|
|
3309
|
-
|
3310
|
-
|
3311
|
-
|
3312
|
-
|
3313
|
-
|
3314
|
-
|
3315
|
-
|
3316
|
-
|
3317
|
-
|
3477
|
+
# look into lib file for that project
|
3478
|
+
dd = File.basename(Dir.pwd)
|
3479
|
+
if f.index("lib/#{dd}/")
|
3480
|
+
# f1 = `zsh -c 'print -rl -- lib/#{dd}/*(om[1,5]MN)'`.split("\n")
|
3481
|
+
f = get_files_by_mtime("lib/#{dd}")&.first(5)
|
3482
|
+
# @log.warn "2756 f1 #{f1} != #{f} in lib/#{dd}" if f1 != f
|
3483
|
+
if f && !f.empty?
|
3484
|
+
insert_into_list("lib/#{dd}/", f)
|
3485
|
+
flg = true
|
3486
|
+
end
|
3487
|
+
end
|
3488
|
+
end
|
3318
3489
|
|
3319
|
-
|
3320
|
-
|
3321
|
-
|
3322
|
-
|
3323
|
-
|
3324
|
-
|
3325
|
-
|
3490
|
+
# look into bin directory and get first five modified files
|
3491
|
+
if @files.index('bin/')
|
3492
|
+
# f1 = `zsh -c 'print -rl -- bin/*(om[1,5]MN)'`.split("\n")
|
3493
|
+
f = get_files_by_mtime('bin')&.first(5)
|
3494
|
+
# @log.warn "2768 f1 #{f1} != #{f} in bin/" if f1 != f
|
3495
|
+
insert_into_list('bin/', f) if f && !f.empty?
|
3496
|
+
flg = true
|
3497
|
+
end
|
3498
|
+
return if flg
|
3326
3499
|
|
3327
|
-
|
3500
|
+
# lib has a dir in it with the gem name
|
3328
3501
|
|
3329
|
-
|
3502
|
+
end
|
3503
|
+
return if @files.size > 15
|
3504
|
+
|
3505
|
+
# Get most recently accessed directory
|
3506
|
+
## NOTE: first check accessed else modified will change accessed
|
3507
|
+
# 2019-03-28 - adding NULL_GLOB breaks file name on spaces
|
3508
|
+
# print -n : don't add newline
|
3509
|
+
# zzmoda = `zsh -c 'print -rn -- *(/oa[1]MN)'`
|
3510
|
+
# zzmoda = nil if zzmoda == ''
|
3511
|
+
moda = get_most_recently_accessed_dir
|
3512
|
+
# @log.warn "Error 2663 #{zzmoda} != #{moda}" if zzmoda != moda
|
3513
|
+
if moda && moda != ''
|
3514
|
+
|
3515
|
+
# get most recently accessed file in that directory
|
3516
|
+
# NOTE: adding NULL_GLOB splits files on spaces
|
3517
|
+
# FIXME: this zsh one gave a dir instead of file.
|
3518
|
+
# zzmodf = `zsh -c 'print -rl -- #{moda}*(oa[1]M)'`.chomp
|
3519
|
+
# zzmodf = nil if zzmodf == ''
|
3520
|
+
modf = get_most_recently_accessed_file moda
|
3521
|
+
# @log.warn "Error 2670 (#{zzmodf}) != (#{modf}) gmra in #{moda} #{zzmodf.class}, #{modf.class} : Loc: #{Dir.pwd}" if zzmodf != modf
|
3522
|
+
|
3523
|
+
raise "2784: #{modf}" if modf && !File.exist?(modf)
|
3524
|
+
|
3525
|
+
insert_into_list moda, modf if modf && modf != ''
|
3526
|
+
|
3527
|
+
# get most recently modified file in that directory
|
3528
|
+
# zzmodm = `zsh -c 'print -rn -- #{moda}*(om[1]M)'`.chomp
|
3529
|
+
modm = get_most_recently_modified_file moda
|
3530
|
+
# zzmodm = nil if zzmodm == ''
|
3531
|
+
# @log.debug "Error 2678 (gmrmf) #{zzmodm} != #{modm} in #{moda}" if zzmodm != modm
|
3532
|
+
raise "2792: #{modm}" if modm && !File.exist?(modm)
|
3533
|
+
|
3534
|
+
insert_into_list moda, modm if modm && modm != '' && modm != modf
|
3535
|
+
end
|
3330
3536
|
|
3331
|
-
|
3332
|
-
# zzmodm = `zsh -c 'print -rn --
|
3333
|
-
modm = get_most_recently_modified_file moda
|
3537
|
+
## get most recently modified dir
|
3538
|
+
# zzmodm = `zsh -c 'print -rn -- *(/om[1]M)'`
|
3334
3539
|
# zzmodm = nil if zzmodm == ''
|
3335
|
-
|
3336
|
-
|
3337
|
-
|
3338
|
-
insert_into_list moda, modm if modm && modm != '' && modm != modf
|
3339
|
-
end
|
3340
|
-
|
3341
|
-
## get most recently modified dir
|
3342
|
-
# zzmodm = `zsh -c 'print -rn -- *(/om[1]M)'`
|
3343
|
-
# zzmodm = nil if zzmodm == ''
|
3344
|
-
modm = get_most_recently_modified_dir
|
3345
|
-
# @log.debug "Error 2686 rmd #{zzmodm} != #{modm}" if zzmodm != modm
|
3540
|
+
modm = get_most_recently_modified_dir
|
3541
|
+
# @log.debug "Error 2686 rmd #{zzmodm} != #{modm}" if zzmodm != modm
|
3346
3542
|
|
3347
|
-
|
3543
|
+
if modm != moda
|
3348
3544
|
|
3349
|
-
|
3350
|
-
|
3351
|
-
|
3352
|
-
|
3545
|
+
# get most recently accessed file in that directory
|
3546
|
+
# modmf = `zsh -c 'print -rn -- #{modm}*(oa[1]M)'`
|
3547
|
+
modmf = get_most_recently_accessed_file modm
|
3548
|
+
raise "2806: #{modmf}" if modmf && !File.exist?(modmf)
|
3353
3549
|
|
3354
|
-
|
3550
|
+
insert_into_list modm, modmf
|
3355
3551
|
|
3356
|
-
|
3357
|
-
|
3358
|
-
|
3359
|
-
|
3552
|
+
# get most recently modified file in that directory
|
3553
|
+
# modmf11 = `zsh -c 'print -rn -- #{modm}*(om[1]M)'`
|
3554
|
+
modmf1 = get_most_recently_modified_file modm
|
3555
|
+
raise "2812: #{modmf1}" if modmf1 && !File.exist?(modmf1)
|
3360
3556
|
|
3361
|
-
|
3362
|
-
|
3363
|
-
|
3364
|
-
|
3365
|
-
|
3366
|
-
|
3557
|
+
insert_into_list(modm, modmf1) if modmf1 != modmf
|
3558
|
+
else
|
3559
|
+
# if both are same then our options get reduced so we need to get something more
|
3560
|
+
# If you access the latest mod dir, then come back you get only one, since mod and accessed
|
3561
|
+
# are the same dir, so we need to find the second modified dir
|
3562
|
+
end
|
3367
3563
|
ensure
|
3368
3564
|
# if any files were added, then add a separator
|
3369
|
-
bctr =
|
3370
|
-
if actr < bctr
|
3371
|
-
$files.insert actr, SEPARATOR
|
3372
|
-
end
|
3565
|
+
bctr = @files.size
|
3566
|
+
@files.insert actr, SEPARATOR if actr < bctr
|
3373
3567
|
end
|
3374
3568
|
end
|
3375
3569
|
|
3376
|
-
# insert important files to end of
|
3570
|
+
# insert important files to end of @files
|
3377
3571
|
def insert_into_list _dir, file
|
3378
|
-
|
3572
|
+
@files.push(*file)
|
3379
3573
|
end
|
3380
3574
|
|
3381
3575
|
# Get visited files and bookmarks that are inside this directory
|
@@ -3383,7 +3577,6 @@ end
|
|
3383
3577
|
# 2019-03-23 - not exactly clear what is happening XXX
|
3384
3578
|
# this gets a directory (containing '/' at end)
|
3385
3579
|
def get_important_files dir
|
3386
|
-
|
3387
3580
|
# checks various lists like visited_files and bookmarks
|
3388
3581
|
# to see if files from this dir or below are in it.
|
3389
3582
|
# More to be used in a dir with few files.
|
@@ -3392,14 +3585,14 @@ def get_important_files dir
|
|
3392
3585
|
|
3393
3586
|
# 2019-03-23 - i think we are getting the basename of the file
|
3394
3587
|
# if it is present in the given directory XXX
|
3395
|
-
|
3588
|
+
@visited_files.each do |e|
|
3396
3589
|
list << e[l..-1] if e.index(dir) == 0
|
3397
3590
|
end
|
3398
3591
|
|
3399
3592
|
# bookmarks if it starts with this directory then add it
|
3400
3593
|
# FIXME it puts same directory cetus into the list with full path
|
3401
3594
|
# We need to remove the base until this dir. get relative part
|
3402
|
-
list1 =
|
3595
|
+
list1 = @bookmarks.values.select do |e|
|
3403
3596
|
e.index(dir) == 0 && e != dir
|
3404
3597
|
end
|
3405
3598
|
|
@@ -3429,8 +3622,8 @@ end
|
|
3429
3622
|
# func can be :mtime or :atime or :ctime or :birthtime
|
3430
3623
|
def gmr dir, type, func
|
3431
3624
|
file = Dir.glob(dir + '/*')
|
3432
|
-
|
3433
|
-
|
3625
|
+
.select { |f| File.send(type, f) }
|
3626
|
+
.max_by { |f| File.send(func, f) }
|
3434
3627
|
file = File.basename(file) + '/' if file && type == :directory?
|
3435
3628
|
return file.gsub('//', '/') if file
|
3436
3629
|
|
@@ -3465,14 +3658,14 @@ end
|
|
3465
3658
|
# set message which will be displayed in status line
|
3466
3659
|
# TODO: maybe we should pad it 2019-04-08 -
|
3467
3660
|
def message mess
|
3468
|
-
|
3661
|
+
@message = mess
|
3469
3662
|
@keys_to_clear = 2 if mess
|
3470
3663
|
end
|
3471
3664
|
|
3472
3665
|
def last_line
|
3473
|
-
# system "tput cup #{
|
3474
|
-
# print "\e[#{
|
3475
|
-
tput_cup
|
3666
|
+
# system "tput cup #{@glines} 0"
|
3667
|
+
# print "\e[#{@glines};0H"
|
3668
|
+
tput_cup @glines, 0
|
3476
3669
|
end
|
3477
3670
|
|
3478
3671
|
def clear_last_line
|
@@ -3482,7 +3675,7 @@ def clear_last_line
|
|
3482
3675
|
# %*s - set blank spaces for entire line
|
3483
3676
|
# \e[m - reset text mode
|
3484
3677
|
# \r - bring to start of line since callers will print.
|
3485
|
-
print "\e[33;4%sm%*s\e[m\r"
|
3678
|
+
print format("\e[33;4%sm%*s\e[m\r", @status_color || '1', @gcols, ' ')
|
3486
3679
|
end
|
3487
3680
|
|
3488
3681
|
# print right aligned
|
@@ -3491,14 +3684,12 @@ end
|
|
3491
3684
|
# should clear and reprint mode, message, patt and right text
|
3492
3685
|
def print_on_right text
|
3493
3686
|
sz = text.size
|
3494
|
-
col =
|
3687
|
+
col = @gcols - sz - 1
|
3495
3688
|
col = 2 if col < 2
|
3496
|
-
if sz >
|
3497
|
-
|
3498
|
-
|
3499
|
-
#
|
3500
|
-
system "tput cup #{$glines} #{col}"
|
3501
|
-
# tput_cup $glines, $gcols - sz - 1
|
3689
|
+
text = text[0..@gcols - 3] if sz > @gcols - 2
|
3690
|
+
# system "tput cup #{@glines} #{@gcols - sz - 1}"
|
3691
|
+
system "tput cup #{@glines} #{col}"
|
3692
|
+
# tput_cup @glines, @gcols - sz - 1
|
3502
3693
|
# print text
|
3503
3694
|
print "\e[33;4#{@status_color_right}m#{text}\e[m"
|
3504
3695
|
end
|
@@ -3519,9 +3710,34 @@ def clear_message
|
|
3519
3710
|
end
|
3520
3711
|
end
|
3521
3712
|
end
|
3713
|
+
|
3714
|
+
# returns true if only cursor moved and redrawing not required
|
3715
|
+
def only_cursor_moved?
|
3716
|
+
# only movement has happened within this page, don't redraw
|
3717
|
+
return unless @movement && @old_cursor
|
3718
|
+
|
3719
|
+
# if cursor has not moved (first or last item on screen)
|
3720
|
+
# keep testing this out 2019-04-14 -
|
3721
|
+
if @old_cursor == @cursor
|
3722
|
+
@movement = false
|
3723
|
+
return true # next in the loop
|
3724
|
+
end
|
3725
|
+
|
3726
|
+
# we may want to print debug info if flag is on
|
3727
|
+
if cget(:debug_flag)
|
3728
|
+
clear_last_line
|
3729
|
+
print_debug_info
|
3730
|
+
else
|
3731
|
+
status_line
|
3732
|
+
end
|
3733
|
+
|
3734
|
+
place_cursor
|
3735
|
+
@movement = false
|
3736
|
+
return true # next in the loop
|
3737
|
+
end
|
3738
|
+
|
3522
3739
|
# main loop which calls all other programs
|
3523
3740
|
def run
|
3524
|
-
|
3525
3741
|
Signal.trap('EXIT') do
|
3526
3742
|
reset_terminal
|
3527
3743
|
exit
|
@@ -3536,44 +3752,28 @@ def run
|
|
3536
3752
|
place_cursor
|
3537
3753
|
|
3538
3754
|
# do we need this, have they changed after redraw
|
3539
|
-
|
3540
|
-
|
3755
|
+
@patt = nil
|
3756
|
+
@sta = 0
|
3541
3757
|
|
3542
3758
|
# forever loop that prints dir and takes a key
|
3543
3759
|
loop do
|
3544
|
-
|
3545
3760
|
key = get_char
|
3546
3761
|
|
3547
|
-
|
3548
3762
|
unless resolve_key key # key did not map to file name, so don't redraw
|
3549
3763
|
place_cursor
|
3550
3764
|
next
|
3551
3765
|
end
|
3552
3766
|
|
3553
|
-
|
3554
|
-
if @movement && @old_cursor
|
3555
|
-
|
3556
|
-
# we may want to print debug info if flag is on
|
3557
|
-
if @debug_flag
|
3558
|
-
clear_last_line
|
3559
|
-
print_debug_info
|
3560
|
-
else
|
3561
|
-
status_line
|
3562
|
-
end
|
3563
|
-
|
3564
|
-
place_cursor
|
3565
|
-
@movement = false
|
3566
|
-
next
|
3567
|
-
end
|
3767
|
+
next if only_cursor_moved?
|
3568
3768
|
|
3569
3769
|
redraw rescan?
|
3570
3770
|
place_cursor
|
3571
3771
|
|
3572
|
-
break if
|
3772
|
+
break if @quitting
|
3573
3773
|
end
|
3574
3774
|
write_curdir
|
3575
3775
|
puts 'bye'
|
3576
|
-
config_write if
|
3776
|
+
config_write if @writing
|
3577
3777
|
@log&.close
|
3578
3778
|
end
|
3579
3779
|
|