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