cygnus 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +38 -0
- data/Rakefile +1 -0
- data/bin/cygnus +900 -0
- data/cygnus.gemspec +23 -0
- data/lib/cygnus.rb +1520 -0
- data/lib/cygnus/textpad.rb +846 -0
- data/lib/cygnus/version.rb +3 -0
- metadata +95 -0
data/cygnus.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'cygnus/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "cygnus"
|
8
|
+
spec.version = Cygnus::VERSION
|
9
|
+
spec.authors = ["Rahul Kumar"]
|
10
|
+
spec.email = ["sentinel1879@gmail.com"]
|
11
|
+
spec.description = %q{best code browser eva}
|
12
|
+
spec.summary = %q{the finest code browser with exactly what you need and no more}
|
13
|
+
spec.homepage = "https://github.com/rkumar/cygnus"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
end
|
data/lib/cygnus.rb
ADDED
@@ -0,0 +1,1520 @@
|
|
1
|
+
require "cygnus/version"
|
2
|
+
require 'rbcurse/core/util/app'
|
3
|
+
|
4
|
+
module Cygnus
|
5
|
+
# Your code goes here...
|
6
|
+
# mmm what code ?
|
7
|
+
# http://www.ruby-doc.org/stdlib-1.9.3/libdoc/shellwords/rdoc/Shellwords.html
|
8
|
+
require 'shellwords'
|
9
|
+
require 'fileutils'
|
10
|
+
CONFIG_FILE="~/.lyrainfo"
|
11
|
+
|
12
|
+
|
13
|
+
$selected_files = Array.new
|
14
|
+
$bookmarks = {}
|
15
|
+
$mode = nil
|
16
|
+
$glines=%x(tput lines).to_i
|
17
|
+
$gcols=%x(tput cols).to_i
|
18
|
+
# grows depends on size of textpad @cols, not screen size, since this is no longer cli
|
19
|
+
$grows = $glines - 2
|
20
|
+
$pagesize = 60
|
21
|
+
$gviscols = 3
|
22
|
+
$pagesize = $grows * $gviscols
|
23
|
+
$stact = 0
|
24
|
+
$editor_mode = true
|
25
|
+
$enhanced_mode = true
|
26
|
+
$visual_block_start = nil
|
27
|
+
$pager_command = {
|
28
|
+
:text => 'most',
|
29
|
+
:image => 'open',
|
30
|
+
:zip => 'tar ztvf %% | most',
|
31
|
+
:unknown => 'open'
|
32
|
+
}
|
33
|
+
$dir_position = {}
|
34
|
+
## CONSTANTS
|
35
|
+
GMARK='*'
|
36
|
+
CURMARK='>'
|
37
|
+
MSCROLL = 10
|
38
|
+
SPACE=" "
|
39
|
+
#CLEAR = "\e[0m"
|
40
|
+
#BOLD = "\e[1m"
|
41
|
+
#BOLD_OFF = "\e[22m"
|
42
|
+
#RED = "\e[31m"
|
43
|
+
#ON_RED = "\e[41m"
|
44
|
+
#GREEN = "\e[32m"
|
45
|
+
#YELLOW = "\e[33m"
|
46
|
+
#BLUE = "\e[1;34m"
|
47
|
+
#
|
48
|
+
ON_BLUE = "\e[44m"
|
49
|
+
#REVERSE = "\e[7m"
|
50
|
+
CURSOR_COLOR = ""
|
51
|
+
CLEAR = ""
|
52
|
+
$patt=nil
|
53
|
+
$ignorecase = true
|
54
|
+
$quitting = false
|
55
|
+
$modified = $writing = false
|
56
|
+
$visited_files = []
|
57
|
+
## dir stack for popping
|
58
|
+
$visited_dirs = []
|
59
|
+
## dirs where some work has been done, for saving and restoring
|
60
|
+
$used_dirs = []
|
61
|
+
$default_sort_order = "om"
|
62
|
+
$sorto = $default_sort_order
|
63
|
+
$viewctr = 0
|
64
|
+
$history = []
|
65
|
+
$sta = $cursor = 0
|
66
|
+
$visual_mode = false
|
67
|
+
|
68
|
+
## main loop which calls all other programs
|
69
|
+
|
70
|
+
## code related to long listing of files
|
71
|
+
GIGA_SIZE = 1073741824.0
|
72
|
+
MEGA_SIZE = 1048576.0
|
73
|
+
KILO_SIZE = 1024.0
|
74
|
+
|
75
|
+
# Return the file size with a readable style.
|
76
|
+
def readable_file_size(size, precision)
|
77
|
+
case
|
78
|
+
#when size == 1 : "1 B"
|
79
|
+
when size < KILO_SIZE then "%d B" % size
|
80
|
+
when size < MEGA_SIZE then "%.#{precision}f K" % (size / KILO_SIZE)
|
81
|
+
when size < GIGA_SIZE then "%.#{precision}f M" % (size / MEGA_SIZE)
|
82
|
+
else "%.#{precision}f G" % (size / GIGA_SIZE)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
## format date for file given stat
|
86
|
+
def date_format t
|
87
|
+
t.strftime "%Y/%m/%d"
|
88
|
+
end
|
89
|
+
##
|
90
|
+
#
|
91
|
+
# print in columns
|
92
|
+
# ary - array of data
|
93
|
+
# sz - lines in one column
|
94
|
+
#
|
95
|
+
def columnate_with_indexing ary, sz
|
96
|
+
buff=Array.new
|
97
|
+
$log.warn "columnate_with_indexing got nil list " unless ary
|
98
|
+
return buff if ary.nil? || ary.size == 0
|
99
|
+
|
100
|
+
# determine width based on number of files to show
|
101
|
+
# if less than sz then 1 col and full width
|
102
|
+
#
|
103
|
+
wid = 30
|
104
|
+
ars = ary.size
|
105
|
+
ars = [$pagesize, ary.size].min
|
106
|
+
# 2 maybe for borders also
|
107
|
+
d = 0
|
108
|
+
if ars <= sz
|
109
|
+
wid = $gcols - d
|
110
|
+
else
|
111
|
+
tmp = (ars * 1.000/ sz).ceil
|
112
|
+
wid = $gcols / tmp - d
|
113
|
+
end
|
114
|
+
|
115
|
+
# ix refers to the index in the complete file list, wherease we only show 60 at a time
|
116
|
+
ix=0
|
117
|
+
while true
|
118
|
+
## ctr refers to the index in the column
|
119
|
+
ctr=0
|
120
|
+
while ctr < sz
|
121
|
+
|
122
|
+
cur=SPACE
|
123
|
+
cur = CURMARK if ix + $sta == $cursor
|
124
|
+
f = ary[ix]
|
125
|
+
|
126
|
+
if $long_listing
|
127
|
+
begin
|
128
|
+
# one of zsh's flags adds not just / but @ * and a space, so the a FNF error comes
|
129
|
+
unless File.exist? f
|
130
|
+
last = f[-1]
|
131
|
+
if last == " " || last == "@" || last == '*'
|
132
|
+
stat = File.stat(f.chop)
|
133
|
+
end
|
134
|
+
else
|
135
|
+
stat = File.stat(f)
|
136
|
+
end
|
137
|
+
f = "%10s %s %s" % [readable_file_size(stat.size,1), date_format(stat.mtime), f]
|
138
|
+
rescue Exception => e
|
139
|
+
f = "%10s %s %s" % ["?", "??????????", f]
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# be careful of modifying f or original array gets modified XXX
|
144
|
+
k = get_shortcut ix
|
145
|
+
isdir = f[-1] == "/"
|
146
|
+
fsz = f.size + k.to_s.size + 0
|
147
|
+
fsz = f.size + 1
|
148
|
+
if fsz > wid
|
149
|
+
# truncated since longer
|
150
|
+
f = f[0, wid-2]+"$ "
|
151
|
+
## we do the coloring after trunc so ANSI escpe seq does not get get
|
152
|
+
#if ix + $sta == $cursor
|
153
|
+
#f = "#{CURSOR_COLOR}#{f}#{CLEAR}"
|
154
|
+
#end
|
155
|
+
else
|
156
|
+
## we do the coloring before padding so the entire line does not get padded, only file name
|
157
|
+
#if ix + $sta == $cursor
|
158
|
+
#f = "#{CURSOR_COLOR}#{f}#{CLEAR}"
|
159
|
+
#end
|
160
|
+
f = f.ljust(wid)
|
161
|
+
# pad with spaces
|
162
|
+
#f << " " * (wid-fsz)
|
163
|
+
#f = f + " " * (wid-fsz)
|
164
|
+
end
|
165
|
+
# now we add the shortcut with the coloring (we need to adjust the space of the shortcut)
|
166
|
+
#
|
167
|
+
colr = "white"
|
168
|
+
colr = "blue, bold" if isdir
|
169
|
+
# this directly modified the damned index resulting in searches failing
|
170
|
+
#k << " " if k.length == 1
|
171
|
+
k = k + " " if k.length == 1
|
172
|
+
|
173
|
+
f = "#{cur}#[fg=yellow, bold]#{k}#[end] #[fg=#{colr}]#{f}#[end]"
|
174
|
+
|
175
|
+
if buff[ctr]
|
176
|
+
buff[ctr] += f
|
177
|
+
else
|
178
|
+
buff[ctr] = f
|
179
|
+
end
|
180
|
+
|
181
|
+
ctr+=1
|
182
|
+
ix+=1
|
183
|
+
break if ix >= ary.size
|
184
|
+
end
|
185
|
+
break if ix >= ary.size
|
186
|
+
end
|
187
|
+
return buff
|
188
|
+
end
|
189
|
+
## formats the data with number, mark and details
|
190
|
+
def format ary
|
191
|
+
#buff = Array.new
|
192
|
+
buff = Array.new(ary.size)
|
193
|
+
return buff if ary.nil? || ary.size == 0
|
194
|
+
|
195
|
+
# determine width based on number of files to show
|
196
|
+
# if less than sz then 1 col and full width
|
197
|
+
#
|
198
|
+
# ix refers to the index in the complete file list, wherease we only show 60 at a time
|
199
|
+
ix=0
|
200
|
+
ctr=0
|
201
|
+
ary.each do |f|
|
202
|
+
## ctr refers to the index in the column
|
203
|
+
ind = get_shortcut(ix)
|
204
|
+
mark=SPACE
|
205
|
+
cur=SPACE
|
206
|
+
cur = CURMARK if ix + $sta == $cursor
|
207
|
+
mark=GMARK if $selected_files.index(ary[ix])
|
208
|
+
|
209
|
+
if $long_listing
|
210
|
+
begin
|
211
|
+
unless File.exist? f
|
212
|
+
last = f[-1]
|
213
|
+
if last == " " || last == "@" || last == '*'
|
214
|
+
stat = File.stat(f.chop)
|
215
|
+
end
|
216
|
+
else
|
217
|
+
stat = File.stat(f)
|
218
|
+
end
|
219
|
+
f = "%10s %s %s" % [readable_file_size(stat.size,1), date_format(stat.mtime), f]
|
220
|
+
rescue Exception => e
|
221
|
+
f = "%10s %s %s" % ["?", "??????????", f]
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
s = "#{ind}#{mark}#{cur}#{f}"
|
226
|
+
# I cannot color the current line since format does the chopping
|
227
|
+
# so not only does the next lines alignment get skeweed, but also if the line is truncated
|
228
|
+
# then the color overflows.
|
229
|
+
#if ix + $sta == $cursor
|
230
|
+
#s = "#{RED}#{s}#{CLEAR}"
|
231
|
+
#end
|
232
|
+
|
233
|
+
buff[ctr] = s
|
234
|
+
|
235
|
+
ctr+=1
|
236
|
+
ix+=1
|
237
|
+
end
|
238
|
+
return buff
|
239
|
+
end
|
240
|
+
## select file based on key pressed
|
241
|
+
def select_hint view, ch
|
242
|
+
# a to y is direct
|
243
|
+
# if z or Z take a key IF there are those many
|
244
|
+
#
|
245
|
+
ix = get_index(ch, view.size)
|
246
|
+
if ix
|
247
|
+
f = view[ix]
|
248
|
+
return unless f
|
249
|
+
$cursor = $sta + ix
|
250
|
+
|
251
|
+
if $mode == 'SEL'
|
252
|
+
toggle_select f
|
253
|
+
elsif $mode == 'COM'
|
254
|
+
run_command f
|
255
|
+
else
|
256
|
+
open_file f
|
257
|
+
end
|
258
|
+
#selectedix=ix
|
259
|
+
end
|
260
|
+
end
|
261
|
+
## toggle selection state of file
|
262
|
+
def toggle_select f
|
263
|
+
if $selected_files.index f
|
264
|
+
$selected_files.delete f
|
265
|
+
else
|
266
|
+
$selected_files.push f
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
## run command on given file/s
|
271
|
+
# Accepts command from user
|
272
|
+
# After putting readline in place of gets, pressing a C-c has a delayed effect. It goes intot
|
273
|
+
# exception bloack after executing other commands and still does not do the return !
|
274
|
+
def run_command f
|
275
|
+
files=nil
|
276
|
+
case f
|
277
|
+
when Array
|
278
|
+
# escape the contents and create a string
|
279
|
+
files = Shellwords.join(f)
|
280
|
+
when String
|
281
|
+
files = Shellwords.escape(f)
|
282
|
+
end
|
283
|
+
begin
|
284
|
+
# TODO put all this get_line stuff into field history
|
285
|
+
command = get_line "Run a command on #{files}: "
|
286
|
+
return if command.size == 0
|
287
|
+
command2 = get_line "Second part of command: "
|
288
|
+
# FIXME we may need to go into cooked mode and all that for this
|
289
|
+
# cat and most mess with the output using system
|
290
|
+
c_system "#{command} #{files} #{command2}"
|
291
|
+
rescue Exception => ex
|
292
|
+
perror "Canceled command, (#{ex}) press a key"
|
293
|
+
return
|
294
|
+
end
|
295
|
+
|
296
|
+
c_refresh
|
297
|
+
push_used_dirs Dir.pwd
|
298
|
+
end
|
299
|
+
def c_system command
|
300
|
+
w = @window || @form.window
|
301
|
+
w.hide
|
302
|
+
Ncurses.endwin
|
303
|
+
ret = system command
|
304
|
+
Ncurses.refresh
|
305
|
+
w.show
|
306
|
+
return ret
|
307
|
+
end
|
308
|
+
|
309
|
+
## clear sort order and refresh listing, used typically if you are in some view
|
310
|
+
# such as visited dirs or files
|
311
|
+
def escape
|
312
|
+
$sorto = nil
|
313
|
+
$sorto = $default_sort_order
|
314
|
+
$viewctr = 0
|
315
|
+
$title = nil
|
316
|
+
$filterstr = "M"
|
317
|
+
visual_block_clear
|
318
|
+
c_refresh
|
319
|
+
end
|
320
|
+
|
321
|
+
## refresh listing after some change like option change, or toggle
|
322
|
+
# I think NCurses has a refresh which when called internally results in this chap
|
323
|
+
# getting called since both are included. or maybe App or somehting has a refresh
|
324
|
+
def c_refresh
|
325
|
+
$filterstr ||= "M"
|
326
|
+
#$files = `zsh -c 'print -rl -- *(#{$sorto}#{$hidden}#{$filterstr})'`.split("\n")
|
327
|
+
$patt=nil
|
328
|
+
$title = nil
|
329
|
+
display_dir
|
330
|
+
end
|
331
|
+
#
|
332
|
+
## unselect all files
|
333
|
+
def unselect_all
|
334
|
+
$selected_files = []
|
335
|
+
$visual_mode = nil
|
336
|
+
end
|
337
|
+
|
338
|
+
## select all files
|
339
|
+
def select_all
|
340
|
+
$selected_files = $view.dup
|
341
|
+
end
|
342
|
+
|
343
|
+
## accept dir to goto and change to that ( can be a file too)
|
344
|
+
def goto_dir
|
345
|
+
begin
|
346
|
+
path = get_line "Enter path: "
|
347
|
+
return if path.nil? || path == ""
|
348
|
+
rescue Exception => ex
|
349
|
+
perror "Cancelled cd, press a key"
|
350
|
+
return
|
351
|
+
end
|
352
|
+
f = File.expand_path(path)
|
353
|
+
unless File.directory? f
|
354
|
+
## check for env variable
|
355
|
+
tmp = ENV[path]
|
356
|
+
if tmp.nil? || !File.directory?( tmp )
|
357
|
+
## check for dir in home
|
358
|
+
tmp = File.expand_path("~/#{path}")
|
359
|
+
if File.directory? tmp
|
360
|
+
f = tmp
|
361
|
+
end
|
362
|
+
else
|
363
|
+
f = tmp
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
open_file f
|
368
|
+
end
|
369
|
+
|
370
|
+
## toggle mode to selection or not
|
371
|
+
# In selection, pressed hotkey selects a file without opening, one can keep selecting
|
372
|
+
# (or deselecting).
|
373
|
+
#
|
374
|
+
def selection_mode_toggle
|
375
|
+
if $mode == 'SEL'
|
376
|
+
# we seem to be coming out of select mode with some files
|
377
|
+
if $selected_files.size > 0
|
378
|
+
run_command $selected_files
|
379
|
+
end
|
380
|
+
$mode = nil
|
381
|
+
else
|
382
|
+
#$selection_mode = !$selection_mode
|
383
|
+
$mode = 'SEL'
|
384
|
+
end
|
385
|
+
end
|
386
|
+
## toggle command mode
|
387
|
+
def command_mode
|
388
|
+
if $mode == 'COM'
|
389
|
+
$mode = nil
|
390
|
+
return
|
391
|
+
end
|
392
|
+
$mode = 'COM'
|
393
|
+
end
|
394
|
+
def goto_parent_dir
|
395
|
+
change_dir ".."
|
396
|
+
end
|
397
|
+
## This actually filters, in zfm it goes to that entry since we have a cursor there
|
398
|
+
#
|
399
|
+
def goto_entry_starting_with fc=nil
|
400
|
+
unless fc
|
401
|
+
fc = get_single "Entries starting with: "
|
402
|
+
#fc = get_char
|
403
|
+
end
|
404
|
+
return if fc.size != 1
|
405
|
+
## this is wrong and duplicates the functionality of /
|
406
|
+
# It shoud go to cursor of item starting with fc
|
407
|
+
$patt = "^#{fc}"
|
408
|
+
end
|
409
|
+
|
410
|
+
|
411
|
+
## take regex from user, to run on files on screen, user can filter file names
|
412
|
+
def enter_regex
|
413
|
+
patt = get_line "Enter (regex) pattern: "
|
414
|
+
#$patt = gets().chomp
|
415
|
+
#$patt = Readline::readline('>', true)
|
416
|
+
$patt = patt
|
417
|
+
return patt
|
418
|
+
end
|
419
|
+
def next_page
|
420
|
+
# FIXME cursor position, take logic from zfm page calc
|
421
|
+
$sta += $pagesize
|
422
|
+
$cursor = $sta if $cursor < $sta
|
423
|
+
end
|
424
|
+
def prev_page
|
425
|
+
# FIXME cursor position, take logic from zfm page calc
|
426
|
+
$sta -= $pagesize
|
427
|
+
$cursor = $sta
|
428
|
+
end
|
429
|
+
def show_marks
|
430
|
+
list = []
|
431
|
+
$bookmarks.each_pair { |k, v| list << " #[fg=yellow, bold]#{k}#[/end] #[fg=green]#{v}#[/end]" }
|
432
|
+
# s="#[fg=green]hello there#[fg=yellow, bg=black, dim]"
|
433
|
+
config = {}
|
434
|
+
longestval = $bookmarks.values.max_by(&:length)
|
435
|
+
config[:title] = "Bookmarks"
|
436
|
+
config[:width] = [longestval.length + 5, FFI::NCurses.COLS - 5].min
|
437
|
+
$log.debug "XXX: LONGEST #{longestval}, #{longestval.length}"
|
438
|
+
ch = padpopup list, config
|
439
|
+
return unless ch
|
440
|
+
#$bookmarks.each_pair { |k, v| puts "#{k.ljust(7)} => #{v}" }
|
441
|
+
#puts
|
442
|
+
#print "Enter bookmark to goto: "
|
443
|
+
#ch = get_char
|
444
|
+
goto_bookmark(ch) if ch =~ /^[0-9A-Z]$/
|
445
|
+
end
|
446
|
+
# MENU MAIN -- keep consistent with zfm
|
447
|
+
def main_menu
|
448
|
+
h = {
|
449
|
+
:a => :ack,
|
450
|
+
"/" => :ffind,
|
451
|
+
:l => :locate,
|
452
|
+
:v => :viminfo,
|
453
|
+
:z => :z_interface,
|
454
|
+
:d => :child_dirs,
|
455
|
+
:r => :recent_files,
|
456
|
+
:t => :dirtree,
|
457
|
+
"4" => :tree,
|
458
|
+
:s => :sort_menu,
|
459
|
+
:F => :filter_menu,
|
460
|
+
:c => :command_menu ,
|
461
|
+
:B => :bindkey_ext_command,
|
462
|
+
:M => :newdir,
|
463
|
+
"%" => :newfile,
|
464
|
+
:x => :extras
|
465
|
+
}
|
466
|
+
menu "Main Menu", h
|
467
|
+
end
|
468
|
+
|
469
|
+
def toggle_menu
|
470
|
+
h = { :h => :toggle_hidden, :c => :toggle_case, :l => :toggle_long_list , "1" => :toggle_columns,
|
471
|
+
:p => :toggle_pager_mode, :e => :toggle_enhanced_list}
|
472
|
+
ch, menu_text = menu "Toggle Menu", h
|
473
|
+
case menu_text
|
474
|
+
when :toggle_hidden
|
475
|
+
#$hidden = $hidden ? nil : "D"
|
476
|
+
$hidden = !$hidden
|
477
|
+
c_refresh
|
478
|
+
when :toggle_case
|
479
|
+
#$ignorecase = $ignorecase ? "" : "i"
|
480
|
+
$ignorecase = !$ignorecase
|
481
|
+
c_refresh
|
482
|
+
when :toggle_columns
|
483
|
+
$gviscols = 3 if $gviscols == 1
|
484
|
+
#$long_listing = false if $gviscols > 1
|
485
|
+
x = $grows * $gviscols
|
486
|
+
$pagesize = $pagesize==x ? $grows : x
|
487
|
+
when :toggle_pager_mode
|
488
|
+
$editor_mode = !$editor_mode
|
489
|
+
if $editor_mode
|
490
|
+
$default_command = nil
|
491
|
+
else
|
492
|
+
$default_command = ENV['MANPAGER'] || ENV['PAGER']
|
493
|
+
end
|
494
|
+
when :toggle_enhanced_list
|
495
|
+
$enhanced_mode = !$enhanced_mode
|
496
|
+
|
497
|
+
when :toggle_long_list
|
498
|
+
$long_listing = !$long_listing
|
499
|
+
if $long_listing
|
500
|
+
$gviscols = 1
|
501
|
+
$pagesize = $grows
|
502
|
+
else
|
503
|
+
x = $grows * $gviscols
|
504
|
+
$pagesize = $pagesize==x ? $grows : x
|
505
|
+
end
|
506
|
+
c_refresh
|
507
|
+
end
|
508
|
+
end
|
509
|
+
|
510
|
+
def sort_menu
|
511
|
+
lo = nil
|
512
|
+
h = { :n => :newest, :a => :accessed, :o => :oldest,
|
513
|
+
:l => :largest, :s => :smallest , :m => :name , :r => :rname, :d => :dirs, :c => :clear }
|
514
|
+
ch, menu_text = menu "Sort Menu", h
|
515
|
+
case menu_text
|
516
|
+
when :newest
|
517
|
+
lo="om"
|
518
|
+
when :accessed
|
519
|
+
lo="oa"
|
520
|
+
when :oldest
|
521
|
+
lo="Om"
|
522
|
+
when :largest
|
523
|
+
lo="OL"
|
524
|
+
when :smallest
|
525
|
+
lo="oL"
|
526
|
+
when :name
|
527
|
+
lo="on"
|
528
|
+
when :rname
|
529
|
+
lo="On"
|
530
|
+
when :dirs
|
531
|
+
lo="/"
|
532
|
+
when :clear
|
533
|
+
lo=""
|
534
|
+
end
|
535
|
+
## This needs to persist and be a part of all listings, put in change_dir.
|
536
|
+
$sorto = lo
|
537
|
+
$files = `zsh -c 'print -rl -- *(#{lo}#{$hidden}M)'`.split("\n") if lo
|
538
|
+
$title = nil
|
539
|
+
#$files =$(eval "print -rl -- ${pattern}(${MFM_LISTORDER}$filterstr)")
|
540
|
+
end
|
541
|
+
|
542
|
+
def command_menu
|
543
|
+
##
|
544
|
+
# since these involve full paths, we need more space, like only one column
|
545
|
+
#
|
546
|
+
## in these cases, getting back to the earlier dir, back to earlier listing
|
547
|
+
# since we've basically overlaid the old listing
|
548
|
+
#
|
549
|
+
# should be able to sort THIS listing and not rerun command. But for that I'd need to use
|
550
|
+
# xargs ls -t etc rather than the zsh sort order. But we can run a filter using |.
|
551
|
+
#
|
552
|
+
h = { :t => :today, :D => :default_command , :R => :remove_from_list}
|
553
|
+
if $editor_mode
|
554
|
+
h[:e] = :pager_mode
|
555
|
+
else
|
556
|
+
h[:e] = :editor_mode
|
557
|
+
end
|
558
|
+
ch, menu_text = menu "Command Menu", h
|
559
|
+
case menu_text
|
560
|
+
when :pager_mode
|
561
|
+
$editor_mode = false
|
562
|
+
$default_command = ENV['MANPAGER'] || ENV['PAGER']
|
563
|
+
when :editor_mode
|
564
|
+
$editor_mode = true
|
565
|
+
$default_command = nil
|
566
|
+
when :ffind
|
567
|
+
ffind
|
568
|
+
when :locate
|
569
|
+
locate
|
570
|
+
when :today
|
571
|
+
$files = `zsh -c 'print -rl -- *(#{$hidden}Mm0)'`.split("\n")
|
572
|
+
$title = "Today's files"
|
573
|
+
when :default_command
|
574
|
+
print "Selecting a file usually invokes $EDITOR, what command do you want to use repeatedly on selected files: "
|
575
|
+
$default_command = gets().chomp
|
576
|
+
if $default_command != ""
|
577
|
+
print "Second part of command (maybe blank): "
|
578
|
+
$default_command2 = gets().chomp
|
579
|
+
else
|
580
|
+
print "Cleared default command, will default to $EDITOR"
|
581
|
+
$default_command2 = nil
|
582
|
+
$default_command = nil
|
583
|
+
end
|
584
|
+
end
|
585
|
+
end
|
586
|
+
def extras
|
587
|
+
h = { "1" => :one_column, "2" => :multi_column, :c => :columns, :r => :config_read , :w => :config_write}
|
588
|
+
ch, menu_text = menu "Extras Menu", h
|
589
|
+
case menu_text
|
590
|
+
when :one_column
|
591
|
+
$pagesize = $grows
|
592
|
+
when :multi_column
|
593
|
+
#$pagesize = 60
|
594
|
+
$pagesize = $grows * $gviscols
|
595
|
+
when :columns
|
596
|
+
ch = get_single "How many columns to show: 1-6 [current #{$gviscols}]? "
|
597
|
+
#ch = get_char
|
598
|
+
ch = ch.to_i
|
599
|
+
if ch > 0 && ch < 7
|
600
|
+
$gviscols = ch.to_i
|
601
|
+
$pagesize = $grows * $gviscols
|
602
|
+
end
|
603
|
+
end
|
604
|
+
end
|
605
|
+
def filter_menu
|
606
|
+
h = { :d => :dirs, :f => :files, :e => :emptydirs , "0" => :emptyfiles}
|
607
|
+
ch, menu_text = menu "Filter Menu", h
|
608
|
+
files = nil
|
609
|
+
case menu_text
|
610
|
+
when :dirs
|
611
|
+
$filterstr = "/M"
|
612
|
+
files = `zsh -c 'print -rl -- *(#{$sorto}/M)'`.split("\n")
|
613
|
+
$title = "Filter: directories only"
|
614
|
+
when :files
|
615
|
+
$filterstr = "."
|
616
|
+
files = `zsh -c 'print -rl -- *(#{$sorto}#{$hidden}.)'`.split("\n")
|
617
|
+
$title = "Filter: files only"
|
618
|
+
when :emptydirs
|
619
|
+
$filterstr = "/D^F"
|
620
|
+
files = `zsh -c 'print -rl -- *(#{$sorto}/D^F)'`.split("\n")
|
621
|
+
$title = "Filter: empty directories"
|
622
|
+
when :emptyfiles
|
623
|
+
$filterstr = ".L0"
|
624
|
+
files = `zsh -c 'print -rl -- *(#{$sorto}#{$hidden}.L0)'`.split("\n")
|
625
|
+
$title = "Filter: empty files"
|
626
|
+
end
|
627
|
+
if files
|
628
|
+
$files = files
|
629
|
+
show_list
|
630
|
+
$stact = 0
|
631
|
+
end
|
632
|
+
end
|
633
|
+
def select_used_dirs
|
634
|
+
$title = "Used Directories"
|
635
|
+
$files = $used_dirs.uniq
|
636
|
+
#show_list
|
637
|
+
end
|
638
|
+
def select_visited_files
|
639
|
+
# not yet a unique list, needs to be unique and have latest pushed to top
|
640
|
+
$title = "Visited Files"
|
641
|
+
files = $visited_files.uniq
|
642
|
+
show_list files
|
643
|
+
$title = nil
|
644
|
+
end
|
645
|
+
def select_bookmarks
|
646
|
+
$title = "Bookmarks"
|
647
|
+
$files = $bookmarks.values.collect do |x|
|
648
|
+
if x.include? ":"
|
649
|
+
ix = x.index ":"
|
650
|
+
x[0,ix]
|
651
|
+
else
|
652
|
+
x
|
653
|
+
end
|
654
|
+
end
|
655
|
+
#show_list files
|
656
|
+
end
|
657
|
+
|
658
|
+
## part copied and changed from change_dir since we don't dir going back on top
|
659
|
+
# or we'll be stuck in a cycle
|
660
|
+
def pop_dir
|
661
|
+
# the first time we pop, we need to put the current on stack
|
662
|
+
if !$visited_dirs.index(Dir.pwd)
|
663
|
+
$visited_dirs.push Dir.pwd
|
664
|
+
end
|
665
|
+
## XXX make sure thre is something to pop
|
666
|
+
d = $visited_dirs.delete_at 0
|
667
|
+
## XXX make sure the dir exists, cuold have been deleted. can be an error or crash otherwise
|
668
|
+
$visited_dirs.push d
|
669
|
+
Dir.chdir d
|
670
|
+
display_dir
|
671
|
+
|
672
|
+
return
|
673
|
+
# old stuff with zsh
|
674
|
+
$filterstr ||= "M"
|
675
|
+
$files = `zsh -c 'print -rl -- *(#{$sorto}#{$hidden}#{$filterstr})'`.split("\n")
|
676
|
+
post_cd
|
677
|
+
end
|
678
|
+
# TODO
|
679
|
+
def TODOpost_cd
|
680
|
+
$patt=nil
|
681
|
+
$sta = $cursor = 0
|
682
|
+
$title = nil
|
683
|
+
if $selected_files.size > 0
|
684
|
+
$selected_files = []
|
685
|
+
end
|
686
|
+
$visual_block_start = nil
|
687
|
+
$stact = 0
|
688
|
+
screen_settings
|
689
|
+
# i think this will screw with the dir_pos since it is not filename based.
|
690
|
+
enhance_file_list
|
691
|
+
revert_dir_pos
|
692
|
+
end
|
693
|
+
#
|
694
|
+
## read dirs and files and bookmarks from file
|
695
|
+
def config_read
|
696
|
+
#f = File.expand_path("~/.zfminfo")
|
697
|
+
f = File.expand_path(CONFIG_FILE)
|
698
|
+
if File.readable? f
|
699
|
+
load f
|
700
|
+
# maybe we should check for these existing else crash will happen.
|
701
|
+
$used_dirs.push(*(DIRS.split ":"))
|
702
|
+
$used_dirs.concat get_env_paths
|
703
|
+
$visited_files.push(*(FILES.split ":"))
|
704
|
+
#$bookmarks.push(*bookmarks) if bookmarks
|
705
|
+
chars = ('A'..'Z').to_a
|
706
|
+
chars.concat( ('0'..'9').to_a )
|
707
|
+
chars.each do |ch|
|
708
|
+
if Kernel.const_defined? "BM_#{ch}"
|
709
|
+
$bookmarks[ch] = Kernel.const_get "BM_#{ch}"
|
710
|
+
end
|
711
|
+
end
|
712
|
+
end
|
713
|
+
end
|
714
|
+
def get_env_paths
|
715
|
+
files = []
|
716
|
+
%w{ GEM_HOME PYTHONHOME}.each do |p|
|
717
|
+
d = ENV[p]
|
718
|
+
files.push d if d
|
719
|
+
end
|
720
|
+
%w{ RUBYLIB RUBYPATH GEM_PATH PYTHONPATH }.each do |p|
|
721
|
+
d = ENV[p]
|
722
|
+
files.concat d.split(":") if d
|
723
|
+
end
|
724
|
+
return files
|
725
|
+
end
|
726
|
+
|
727
|
+
## save dirs and files and bookmarks to a file
|
728
|
+
def config_write
|
729
|
+
# Putting it in a format that zfm can also read and write
|
730
|
+
f1 = File.expand_path("~/.cygnusinfo")
|
731
|
+
#f1 = File.expand_path(CONFIG_FILE)
|
732
|
+
d = $used_dirs.join ":"
|
733
|
+
f = $visited_files.join ":"
|
734
|
+
File.open(f1, 'w+') do |f2|
|
735
|
+
# use "\n" for two lines of text
|
736
|
+
f2.puts "DIRS=\"#{d}\""
|
737
|
+
f2.puts "FILES=\"#{f}\""
|
738
|
+
$bookmarks.each_pair { |k, val|
|
739
|
+
f2.puts "BM_#{k}=\"#{val}\""
|
740
|
+
#f2.puts "BOOKMARKS[\"#{k}\"]=\"#{val}\""
|
741
|
+
}
|
742
|
+
end
|
743
|
+
$writing = $modified = false
|
744
|
+
end
|
745
|
+
|
746
|
+
## accept a character to save this dir as a bookmark
|
747
|
+
def create_bookmark
|
748
|
+
ch = get_single "Enter A to Z or 0-9 for bookmark: "
|
749
|
+
#ch = get_char
|
750
|
+
if ch =~ /^[0-9A-Z]$/
|
751
|
+
#$bookmarks[ch] = "#{Dir.pwd}:#{$cursor}"
|
752
|
+
#
|
753
|
+
# The significance of putting a : and not a / is that with a
|
754
|
+
# : the dir will be opened with cursor on same object it was on, and not
|
755
|
+
# go into the dir. e.g, If bookmark is created with cursor on a dir, we don't want
|
756
|
+
# it to go into the dir.
|
757
|
+
$bookmarks[ch] = "#{Dir.pwd}:#{$view[$cursor]}"
|
758
|
+
$modified = true
|
759
|
+
else
|
760
|
+
perror "Bookmark must be upper-case character or number."
|
761
|
+
end
|
762
|
+
end
|
763
|
+
def subcommand
|
764
|
+
begin
|
765
|
+
command = get_line "Enter command: "
|
766
|
+
#command = gets().chomp
|
767
|
+
#command = Readline::readline('>', true)
|
768
|
+
return if command == ""
|
769
|
+
rescue Exception => ex
|
770
|
+
return
|
771
|
+
end
|
772
|
+
if command == "q"
|
773
|
+
if $modified
|
774
|
+
ch = get_single "Do you want to save bookmarks? (y/n): "
|
775
|
+
#ch = get_char
|
776
|
+
if ch == "y"
|
777
|
+
$writing = true
|
778
|
+
$quitting = true
|
779
|
+
elsif ch == "n"
|
780
|
+
$quitting = true
|
781
|
+
print "Quitting without saving bookmarks"
|
782
|
+
else
|
783
|
+
perror "No action taken."
|
784
|
+
end
|
785
|
+
else
|
786
|
+
$quitting = true
|
787
|
+
end
|
788
|
+
elsif command == "wq"
|
789
|
+
$quitting = true
|
790
|
+
$writing = true
|
791
|
+
elsif command == "x"
|
792
|
+
$quitting = true
|
793
|
+
$writing = true if $modified
|
794
|
+
elsif command == "p"
|
795
|
+
system "echo $PWD | pbcopy"
|
796
|
+
get_single "Stored PWD in clipboard (using pbcopy)"
|
797
|
+
end
|
798
|
+
end
|
799
|
+
def quit_command
|
800
|
+
if $modified
|
801
|
+
s = ""
|
802
|
+
s << "Press w to save bookmarks before quitting. " if $modified
|
803
|
+
s << "Press another q to quit "
|
804
|
+
ch = get_single s
|
805
|
+
#ch = get_char
|
806
|
+
else
|
807
|
+
$quitting = true
|
808
|
+
end
|
809
|
+
$quitting = true if ch == "q"
|
810
|
+
$quitting = $writing = true if ch == "w"
|
811
|
+
end
|
812
|
+
|
813
|
+
def views
|
814
|
+
views=%w[/ om oa Om OL oL On on]
|
815
|
+
viewlabels=%w[Dirs Newest Accessed Oldest Largest Smallest Reverse Name]
|
816
|
+
$sorto = views[$viewctr]
|
817
|
+
$title = viewlabels[$viewctr]
|
818
|
+
$viewctr += 1
|
819
|
+
$viewctr = 0 if $viewctr > views.size
|
820
|
+
|
821
|
+
$files = `zsh -c 'print -rl -- *(#{$sorto}#{$hidden}M)'`.split("\n")
|
822
|
+
|
823
|
+
end
|
824
|
+
def child_dirs
|
825
|
+
$title = "Child directories"
|
826
|
+
$files = `zsh -c 'print -rl -- *(/#{$sorto}#{$hidden}M)'`.split("\n")
|
827
|
+
end
|
828
|
+
def dirtree
|
829
|
+
$title = "Child directories"
|
830
|
+
$files = `zsh -c 'print -rl -- **/*(/#{$sorto}#{$hidden}M)'`.split("\n")
|
831
|
+
end
|
832
|
+
#
|
833
|
+
# Get a full recursive listing of what's in this dir - useful for small projects with more
|
834
|
+
# structure than files.
|
835
|
+
def tree
|
836
|
+
# Caution: use only for small projects, don't use in root.
|
837
|
+
$title = "Full Tree"
|
838
|
+
$files = `zsh -c 'print -rl -- **/*(#{$sorto}#{$hidden}M)'`.split("\n")
|
839
|
+
end
|
840
|
+
def recent_files
|
841
|
+
# print -rl -- **/*(Dom[1,10])
|
842
|
+
$title = "Recent files"
|
843
|
+
$files = `zsh -c 'print -rl -- **/*(Dom[1,15])'`.split("\n")
|
844
|
+
end
|
845
|
+
def select_current
|
846
|
+
## vp is local there, so i can do $vp[0]
|
847
|
+
#open_file $view[$sta] if $view[$sta]
|
848
|
+
open_file $view[$cursor] if $view[$cursor]
|
849
|
+
end
|
850
|
+
|
851
|
+
## create a list of dirs in which some action has happened, for saving
|
852
|
+
def push_used_dirs d=Dir.pwd
|
853
|
+
$used_dirs.index(d) || $used_dirs.push(d)
|
854
|
+
end
|
855
|
+
## I thin we need to make this like the command line one TODO
|
856
|
+
def get_char
|
857
|
+
c = @window.getchar
|
858
|
+
case c
|
859
|
+
when 13,10
|
860
|
+
return "ENTER"
|
861
|
+
when 32
|
862
|
+
return "SPACE"
|
863
|
+
when 127
|
864
|
+
return "BACKSPACE"
|
865
|
+
when 27
|
866
|
+
return "ESCAPE"
|
867
|
+
end
|
868
|
+
keycode_tos c
|
869
|
+
# if c > 32 && c < 127
|
870
|
+
#return c.chr
|
871
|
+
#end
|
872
|
+
## use keycode_tos from Utils.
|
873
|
+
end
|
874
|
+
|
875
|
+
def pbold text
|
876
|
+
#puts "#{BOLD}#{text}#{BOLD_OFF}"
|
877
|
+
get_single text, :color_pair => $reversecolor
|
878
|
+
end
|
879
|
+
def perror text
|
880
|
+
##puts "#{RED}#{text}#{CLEAR}"
|
881
|
+
#get_char
|
882
|
+
#alert text
|
883
|
+
get_single text + " Press a key...", :color_pair => $errorcolor
|
884
|
+
end
|
885
|
+
def pause text=" Press a key ..."
|
886
|
+
get_single text
|
887
|
+
#get_char
|
888
|
+
end
|
889
|
+
## return shortcut for an index (offset in file array)
|
890
|
+
# use 2 more arrays to make this faster
|
891
|
+
# if z or Z take another key if there are those many in view
|
892
|
+
# Also, display ROWS * COLS so now we are not limited to 60.
|
893
|
+
def get_shortcut ix
|
894
|
+
return "<" if ix < $stact
|
895
|
+
ix -= $stact
|
896
|
+
i = $IDX[ix]
|
897
|
+
return i if i
|
898
|
+
return "->"
|
899
|
+
end
|
900
|
+
## returns the integer offset in view (file array based on a-y za-zz and Za - Zz
|
901
|
+
# Called when user types a key
|
902
|
+
# should we even ask for a second key if there are not enough rows
|
903
|
+
# What if we want to also trap z with numbers for other purposes
|
904
|
+
def get_index key, vsz=999
|
905
|
+
i = $IDX.index(key)
|
906
|
+
return i+$stact if i
|
907
|
+
#sz = $IDX.size
|
908
|
+
zch = nil
|
909
|
+
if vsz > 25
|
910
|
+
if key == "z" || key == "Z"
|
911
|
+
#print key
|
912
|
+
zch = get_char
|
913
|
+
#print zch
|
914
|
+
i = $IDX.index("#{key}#{zch}")
|
915
|
+
return i+$stact if i
|
916
|
+
end
|
917
|
+
end
|
918
|
+
return nil
|
919
|
+
end
|
920
|
+
|
921
|
+
def delete_file
|
922
|
+
file_actions :delete
|
923
|
+
end
|
924
|
+
|
925
|
+
## generic external command program
|
926
|
+
# prompt is the user friendly text of command such as list for ls, or extract for dtrx, page for less
|
927
|
+
# pauseyn is whether to pause after command as in file or ls
|
928
|
+
#
|
929
|
+
def command_file prompt, *command
|
930
|
+
pauseyn = command.shift
|
931
|
+
command = command.join " "
|
932
|
+
#print "[#{prompt}] Choose a file [#{$view[$cursor]}]: "
|
933
|
+
t = "[#{prompt}] Choose a file [#{$view[$cursor]}]: "
|
934
|
+
file = ask_hint t, $view[$cursor]
|
935
|
+
#print "#{prompt} :: Enter file shortcut: "
|
936
|
+
#file = ask_hint
|
937
|
+
perror "Command Cancelled" unless file
|
938
|
+
return unless file
|
939
|
+
file = File.expand_path(file)
|
940
|
+
if File.exists? file
|
941
|
+
file = Shellwords.escape(file)
|
942
|
+
pbold "#{command} #{file} (#{pauseyn})"
|
943
|
+
system "#{command} #{file}"
|
944
|
+
pause if pauseyn == "y"
|
945
|
+
c_refresh
|
946
|
+
else
|
947
|
+
perror "File #{file} not found"
|
948
|
+
end
|
949
|
+
end
|
950
|
+
|
951
|
+
## prompt user for file shortcut and return file or nil
|
952
|
+
#
|
953
|
+
def ask_hint text, deflt=nil
|
954
|
+
f = nil
|
955
|
+
|
956
|
+
#ch = get_char
|
957
|
+
ch = get_single text
|
958
|
+
if ch == "ENTER"
|
959
|
+
return deflt
|
960
|
+
end
|
961
|
+
ix = get_index(ch, $viewport.size)
|
962
|
+
f = $viewport[ix] if ix
|
963
|
+
return f
|
964
|
+
end
|
965
|
+
|
966
|
+
## check screen size and accordingly adjust some variables
|
967
|
+
#
|
968
|
+
def screen_settings
|
969
|
+
# TODO these need to become part of our new full_indexer class, not hang about separately.
|
970
|
+
$glines=%x(tput lines).to_i
|
971
|
+
$gcols=%x(tput cols).to_i
|
972
|
+
# this depends now on textpad size not screen size TODO FIXME
|
973
|
+
$grows = $glines - 1
|
974
|
+
$pagesize = 60
|
975
|
+
#$gviscols = 3
|
976
|
+
$pagesize = $grows * $gviscols
|
977
|
+
end
|
978
|
+
## moves column offset so we can reach unindexed columns or entries
|
979
|
+
# 0 forward and any other back/prev
|
980
|
+
def column_next dir=0
|
981
|
+
if dir == 0
|
982
|
+
$stact += $grows
|
983
|
+
$stact = 0 if $stact >= $viewport.size
|
984
|
+
else
|
985
|
+
$stact -= $grows
|
986
|
+
$stact = 0 if $stact < 0
|
987
|
+
end
|
988
|
+
end
|
989
|
+
# currently i am only passing the action in from the list there as a key
|
990
|
+
# I should be able to pass in new actions that are external commands
|
991
|
+
def file_actions action=nil
|
992
|
+
h = { :d => :delete, :m => :move, :r => :rename, :v => ENV["EDITOR"] || :vim,
|
993
|
+
:c => :copy, :C => :chdir,
|
994
|
+
:l => :less, :s => :most , :f => :file , :o => :open, :x => :dtrx, :z => :zip }
|
995
|
+
#acttext = h[action.to_sym] || action
|
996
|
+
acttext = action || ""
|
997
|
+
file = nil
|
998
|
+
|
999
|
+
sct = $selected_files.size
|
1000
|
+
if sct > 0
|
1001
|
+
text = "#{sct} files"
|
1002
|
+
file = $selected_files
|
1003
|
+
else
|
1004
|
+
#print "[#{acttext}] Choose a file [#{$view[$cursor]}]: "
|
1005
|
+
t = "[#{acttext}] Choose a file [#{$view[$cursor]}]: "
|
1006
|
+
file = ask_hint t, $view[$cursor]
|
1007
|
+
return unless file
|
1008
|
+
text = file
|
1009
|
+
end
|
1010
|
+
|
1011
|
+
case file
|
1012
|
+
when Array
|
1013
|
+
# escape the contents and create a string
|
1014
|
+
files = Shellwords.join(file)
|
1015
|
+
when String
|
1016
|
+
files = Shellwords.escape(file)
|
1017
|
+
end
|
1018
|
+
|
1019
|
+
|
1020
|
+
ch = nil
|
1021
|
+
if action
|
1022
|
+
menu_text = action
|
1023
|
+
else
|
1024
|
+
ch, menu_text = menu "File Menu for #{text}", h
|
1025
|
+
menu_text = :quit if ch == "q"
|
1026
|
+
end
|
1027
|
+
return unless menu_text
|
1028
|
+
case menu_text.to_sym
|
1029
|
+
when :quit
|
1030
|
+
when :delete
|
1031
|
+
ch = get_single "rmtrash #{files} ?[yn]: "
|
1032
|
+
#print "rmtrash #{files} ?[yn]: "
|
1033
|
+
#ch = get_char
|
1034
|
+
return if ch != "y"
|
1035
|
+
c_system "rmtrash #{files}"
|
1036
|
+
c_refresh
|
1037
|
+
when :move
|
1038
|
+
#print "move #{text} to : "
|
1039
|
+
#target = gets().chomp
|
1040
|
+
#target = Readline::readline('>', true)
|
1041
|
+
target = get_line "move #{text} to : "
|
1042
|
+
text=File.expand_path(text)
|
1043
|
+
return if target.nil? || target == ""
|
1044
|
+
if File.directory? target
|
1045
|
+
FileUtils.mv text, target
|
1046
|
+
c_refresh
|
1047
|
+
else
|
1048
|
+
perror "Target not a dir"
|
1049
|
+
end
|
1050
|
+
when :copy
|
1051
|
+
target = get_line "copy #{text} to : "
|
1052
|
+
#target = Readline::readline('>', true)
|
1053
|
+
return if target.nil? || target == ""
|
1054
|
+
text=File.expand_path(text)
|
1055
|
+
target = File.basename(text) if target == "."
|
1056
|
+
if File.exists? target
|
1057
|
+
perror "Target (#{target}) exists"
|
1058
|
+
else
|
1059
|
+
FileUtils.cp text, target
|
1060
|
+
c_refresh
|
1061
|
+
end
|
1062
|
+
when :chdir
|
1063
|
+
change_dir File.dirname(text)
|
1064
|
+
when :zip
|
1065
|
+
target = get_line "Archive name: "
|
1066
|
+
#target = gets().chomp
|
1067
|
+
#target = Readline::readline('>', true)
|
1068
|
+
return if target.nil? || target == ""
|
1069
|
+
# don't want a blank space or something screwing up
|
1070
|
+
if target && target.size > 3
|
1071
|
+
if File.exists? target
|
1072
|
+
perror "Target (#{target}) exists"
|
1073
|
+
else
|
1074
|
+
c_system "tar zcvf #{target} #{files}"
|
1075
|
+
c_refresh
|
1076
|
+
end
|
1077
|
+
end
|
1078
|
+
when :rename
|
1079
|
+
when :most, :less, :vim, ENV['EDITOR']
|
1080
|
+
c_system "#{menu_text} #{files}"
|
1081
|
+
else
|
1082
|
+
return unless menu_text
|
1083
|
+
$log.debug "XXX: menu_text #{menu_text.to_sym}"
|
1084
|
+
get_single "#{menu_text} #{files}"
|
1085
|
+
#pause
|
1086
|
+
#print
|
1087
|
+
c_system "#{menu_text} #{files}"
|
1088
|
+
pause
|
1089
|
+
c_refresh
|
1090
|
+
end
|
1091
|
+
# remove non-existent files from select list due to move or delete or rename or whatever
|
1092
|
+
if sct > 0
|
1093
|
+
$selected_files.reject! {|x| x = File.expand_path(x); !File.exists?(x) }
|
1094
|
+
end
|
1095
|
+
end
|
1096
|
+
|
1097
|
+
def columns_incdec howmany
|
1098
|
+
$gviscols += howmany.to_i
|
1099
|
+
$gviscols = 1 if $gviscols < 1
|
1100
|
+
$gviscols = 6 if $gviscols > 6
|
1101
|
+
$pagesize = $grows * $gviscols
|
1102
|
+
end
|
1103
|
+
|
1104
|
+
# bind a key to an external command wich can be then be used for files
|
1105
|
+
def bindkey_ext_command
|
1106
|
+
#print
|
1107
|
+
#pbold "Bind a capital letter to an external command"
|
1108
|
+
ch = get_single "Enter a capital letter to bind: "
|
1109
|
+
#ch = get_char
|
1110
|
+
return if ch == "Q"
|
1111
|
+
if ch =~ /^[A-Z]$/
|
1112
|
+
print "Enter an external command to bind to #{ch}: "
|
1113
|
+
com = gets().chomp
|
1114
|
+
if com != ""
|
1115
|
+
print "Enter prompt for command (blank if same as command): "
|
1116
|
+
pro = gets().chomp
|
1117
|
+
pro = com if pro == ""
|
1118
|
+
end
|
1119
|
+
print "Pause after output [y/n]: "
|
1120
|
+
yn = get_char
|
1121
|
+
$bindings[ch] = "command_file #{pro} #{yn} #{com}"
|
1122
|
+
end
|
1123
|
+
end
|
1124
|
+
def viminfo
|
1125
|
+
file = File.expand_path("~/.viminfo")
|
1126
|
+
if File.exists? file
|
1127
|
+
$title = "Files from ~/.viminfo"
|
1128
|
+
#$files = `grep '^>' ~/.viminfo | cut -d ' ' -f 2- | sed "s#~#$HOME#g"`.split("\n")
|
1129
|
+
$files = `grep '^>' ~/.viminfo | cut -d ' ' -f 2- `.split("\n")
|
1130
|
+
$files.reject! {|x| x = File.expand_path(x); !File.exists?(x) }
|
1131
|
+
show_list
|
1132
|
+
end
|
1133
|
+
end
|
1134
|
+
def z_interface
|
1135
|
+
file = File.expand_path("~/.z")
|
1136
|
+
if File.exists? file
|
1137
|
+
$title = "Directories from ~/.z"
|
1138
|
+
$files = `sort -rn -k2 -t '|' ~/.z | cut -f1 -d '|'`.split("\n")
|
1139
|
+
home = ENV['HOME']
|
1140
|
+
$files.collect! do |f|
|
1141
|
+
f.sub(/#{home}/,"~")
|
1142
|
+
end
|
1143
|
+
show_list
|
1144
|
+
end
|
1145
|
+
end
|
1146
|
+
def ack
|
1147
|
+
pattern = get_line "Enter a pattern to search (ack): "
|
1148
|
+
return if pattern.nil? || pattern == ""
|
1149
|
+
$title = "Files found using 'ack' #{pattern}"
|
1150
|
+
#system("ack #{pattern}")
|
1151
|
+
#pause
|
1152
|
+
files = `ack -l #{pattern}`.split("\n")
|
1153
|
+
if files.size == 0
|
1154
|
+
perror "No files found."
|
1155
|
+
else
|
1156
|
+
$files = files
|
1157
|
+
show_list
|
1158
|
+
end
|
1159
|
+
end
|
1160
|
+
def ffind
|
1161
|
+
pattern = get_line "Enter a file name pattern to find: "
|
1162
|
+
return if pattern.nil? || pattern == ""
|
1163
|
+
$title = "Files found using 'find' #{pattern}"
|
1164
|
+
files = `find . -name '#{pattern}'`.split("\n")
|
1165
|
+
if files.size == 0
|
1166
|
+
perror "No files found."
|
1167
|
+
else
|
1168
|
+
$files = files
|
1169
|
+
show_list
|
1170
|
+
end
|
1171
|
+
end
|
1172
|
+
def locate
|
1173
|
+
pattern = get_line "Enter a file name pattern to locate: "
|
1174
|
+
return if pattern.nil? || pattern == ""
|
1175
|
+
$title = "Files found using 'locate' #{pattern}"
|
1176
|
+
files = `locate #{pattern}`.split("\n")
|
1177
|
+
files.reject! {|x| x = File.expand_path(x); !File.exists?(x) }
|
1178
|
+
if files.size == 0
|
1179
|
+
perror "No files found."
|
1180
|
+
else
|
1181
|
+
$files = files
|
1182
|
+
show_list
|
1183
|
+
end
|
1184
|
+
end
|
1185
|
+
|
1186
|
+
## Displays files from .viminfo file, if you use some other editor which tracks files opened
|
1187
|
+
# then you can modify this accordingly.
|
1188
|
+
#
|
1189
|
+
|
1190
|
+
## takes directories from the z program, if you use autojump you can
|
1191
|
+
# modify this accordingly
|
1192
|
+
#
|
1193
|
+
|
1194
|
+
## some cursor movement functions
|
1195
|
+
##
|
1196
|
+
#
|
1197
|
+
def cursor_scroll_dn
|
1198
|
+
moveto(pos() + MSCROLL)
|
1199
|
+
end
|
1200
|
+
def cursor_scroll_up
|
1201
|
+
moveto(pos() - MSCROLL)
|
1202
|
+
end
|
1203
|
+
def cursor_dn
|
1204
|
+
moveto(pos() + 1)
|
1205
|
+
end
|
1206
|
+
def cursor_up
|
1207
|
+
moveto(pos() - 1)
|
1208
|
+
end
|
1209
|
+
def pos
|
1210
|
+
$cursor
|
1211
|
+
end
|
1212
|
+
|
1213
|
+
def moveto pos
|
1214
|
+
orig = $cursor
|
1215
|
+
$cursor = pos
|
1216
|
+
$cursor = [$cursor, $view.size - 1].min
|
1217
|
+
$cursor = [$cursor, 0].max
|
1218
|
+
star = [orig, $cursor].min
|
1219
|
+
fin = [orig, $cursor].max
|
1220
|
+
if $visual_mode
|
1221
|
+
# PWD has to be there in selction
|
1222
|
+
if $selected_files.index $view[$cursor]
|
1223
|
+
# this depends on the direction
|
1224
|
+
$selected_files = $selected_files - $view[star..fin]
|
1225
|
+
## current row remains in selection always.
|
1226
|
+
$selected_files.push $view[$cursor]
|
1227
|
+
else
|
1228
|
+
$selected_files.concat $view[star..fin]
|
1229
|
+
end
|
1230
|
+
end
|
1231
|
+
end
|
1232
|
+
def visual_mode_toggle
|
1233
|
+
$visual_mode = !$visual_mode
|
1234
|
+
if $visual_mode
|
1235
|
+
$visual_block_start = $cursor
|
1236
|
+
$selected_files.push $view[$cursor]
|
1237
|
+
end
|
1238
|
+
end
|
1239
|
+
def visual_block_clear
|
1240
|
+
if $visual_block_start
|
1241
|
+
star = [$visual_block_start, $cursor].min
|
1242
|
+
fin = [$visual_block_start, $cursor].max
|
1243
|
+
$selected_files = $selected_files - $view[star..fin]
|
1244
|
+
end
|
1245
|
+
$visual_block_start = nil
|
1246
|
+
$visual_mode = nil
|
1247
|
+
end
|
1248
|
+
def file_matching? file, patt
|
1249
|
+
file =~ /#{patt}/
|
1250
|
+
end
|
1251
|
+
|
1252
|
+
## generic method to take cursor to next position for a given condition
|
1253
|
+
def return_next_match binding, *args
|
1254
|
+
first = nil
|
1255
|
+
ix = 0
|
1256
|
+
$view.each_with_index do |elem,ii|
|
1257
|
+
if binding.call(elem, *args)
|
1258
|
+
first ||= ii
|
1259
|
+
if ii > $cursor
|
1260
|
+
ix = ii
|
1261
|
+
break
|
1262
|
+
end
|
1263
|
+
end
|
1264
|
+
end
|
1265
|
+
return first if ix == 0
|
1266
|
+
return ix
|
1267
|
+
end
|
1268
|
+
##
|
1269
|
+
# position cursor on a specific line which could be on a nother page
|
1270
|
+
# therefore calculate the correct start offset of the display also.
|
1271
|
+
def goto_line pos
|
1272
|
+
pages = ((pos * 1.00)/$pagesize).ceil
|
1273
|
+
pages -= 1
|
1274
|
+
#$sta = pages * $pagesize + 1
|
1275
|
+
$sta = pages * $pagesize + 0
|
1276
|
+
$cursor = pos
|
1277
|
+
#$log.debug "XXX: GOTO_LINE #{$sta} :: #{$cursor}"
|
1278
|
+
end
|
1279
|
+
def filetype f
|
1280
|
+
return nil unless f
|
1281
|
+
f = Shellwords.escape(f)
|
1282
|
+
s = `file #{f}`
|
1283
|
+
if s.index "text"
|
1284
|
+
return :text
|
1285
|
+
elsif s.index(/[Zz]ip/)
|
1286
|
+
return :zip
|
1287
|
+
elsif s.index("archive")
|
1288
|
+
return :zip
|
1289
|
+
elsif s.index "image"
|
1290
|
+
return :image
|
1291
|
+
elsif s.index "data"
|
1292
|
+
return :text
|
1293
|
+
end
|
1294
|
+
nil
|
1295
|
+
end
|
1296
|
+
|
1297
|
+
def save_dir_pos
|
1298
|
+
return if $sta == 0 && $cursor == 0
|
1299
|
+
$dir_position[Dir.pwd] = [$sta, $cursor]
|
1300
|
+
end
|
1301
|
+
def revert_dir_pos
|
1302
|
+
$sta = 0
|
1303
|
+
$cursor = 0
|
1304
|
+
a = $dir_position[Dir.pwd]
|
1305
|
+
if a
|
1306
|
+
$sta = a.first
|
1307
|
+
$cursor = a[1]
|
1308
|
+
raise "sta is nil for #{Dir.pwd} : #{$dir_position[Dir.pwd]}" unless $sta
|
1309
|
+
raise "cursor is nil" unless $cursor
|
1310
|
+
end
|
1311
|
+
end
|
1312
|
+
def newdir
|
1313
|
+
#print
|
1314
|
+
#print "Enter directory name: "
|
1315
|
+
#str = Readline::readline('>', true)
|
1316
|
+
str = get_line "Enter directory name: "
|
1317
|
+
return if str.nil? || str == ""
|
1318
|
+
if File.exists? str
|
1319
|
+
perror "#{str} exists."
|
1320
|
+
return
|
1321
|
+
end
|
1322
|
+
begin
|
1323
|
+
FileUtils.mkdir str
|
1324
|
+
$used_dirs.insert(0, str) if File.exists?(str)
|
1325
|
+
c_refresh
|
1326
|
+
rescue Exception => ex
|
1327
|
+
perror "Error in newdir: #{ex}"
|
1328
|
+
end
|
1329
|
+
end
|
1330
|
+
def newfile
|
1331
|
+
#print
|
1332
|
+
str = get_line "Enter file name: "
|
1333
|
+
#str = Readline::readline('>', true)
|
1334
|
+
return if str.nil? || str == ""
|
1335
|
+
system "$EDITOR #{str}"
|
1336
|
+
$visited_files.insert(0, str) if File.exists?(str)
|
1337
|
+
c_refresh
|
1338
|
+
end
|
1339
|
+
|
1340
|
+
##
|
1341
|
+
# Editing of the User Dir List.
|
1342
|
+
# remove current entry from used dirs list, since we may not want some entries being there
|
1343
|
+
#
|
1344
|
+
def remove_from_list
|
1345
|
+
if $selected_files.size > 0
|
1346
|
+
sz = $selected_files.size
|
1347
|
+
ch = get_single "Remove #{sz} files from used list (y)?: "
|
1348
|
+
#ch = get_char
|
1349
|
+
return if ch != "y"
|
1350
|
+
$used_dirs = $used_dirs - $selected_files
|
1351
|
+
$visited_files = $visited_files - $selected_files
|
1352
|
+
unselect_all
|
1353
|
+
$modified = true
|
1354
|
+
return
|
1355
|
+
end
|
1356
|
+
#print
|
1357
|
+
## what if selected some rows
|
1358
|
+
file = $view[$cursor]
|
1359
|
+
ch = get_single "Remove #{file} from used list (y)?: "
|
1360
|
+
#ch = get_char
|
1361
|
+
return if ch != "y"
|
1362
|
+
file = File.expand_path(file)
|
1363
|
+
if File.directory? file
|
1364
|
+
$used_dirs.delete(file)
|
1365
|
+
else
|
1366
|
+
$visited_files.delete(file)
|
1367
|
+
end
|
1368
|
+
c_refresh
|
1369
|
+
$modified = true
|
1370
|
+
end
|
1371
|
+
#
|
1372
|
+
# If there's a short file list, take recently mod and accessed folders and put latest
|
1373
|
+
# files from there and insert it here. I take both since recent mod can be binaries / object
|
1374
|
+
# files and gems created by a process, and not actually edited files. Recent accessed gives
|
1375
|
+
# latest source, but in some cases even this can be misleading since running a program accesses
|
1376
|
+
# include files.
|
1377
|
+
def enhance_file_list
|
1378
|
+
return unless $enhanced_mode
|
1379
|
+
# if only one entry and its a dir
|
1380
|
+
# get its children and maybe the recent mod files a few
|
1381
|
+
|
1382
|
+
if $files.size == 1
|
1383
|
+
# its a dir, let give the next level at least
|
1384
|
+
if $files.first[-1] == "/"
|
1385
|
+
d = $files.first
|
1386
|
+
f = `zsh -c 'print -rl -- #{d}*(omM)'`.split("\n")
|
1387
|
+
if f && f.size > 0
|
1388
|
+
$files.concat f
|
1389
|
+
return
|
1390
|
+
end
|
1391
|
+
else
|
1392
|
+
# just a file, not dirs here
|
1393
|
+
return
|
1394
|
+
end
|
1395
|
+
end
|
1396
|
+
#
|
1397
|
+
# check if a ruby project dir, although it could be a backup file too,
|
1398
|
+
# if so , expand lib and maby bin, put a couple recent files
|
1399
|
+
#
|
1400
|
+
if $files.index("Gemfile") || $files.grep(/\.gemspec/).size > 0
|
1401
|
+
# usually the lib dir has only one file and one dir
|
1402
|
+
flg = false
|
1403
|
+
if $files.index("lib/")
|
1404
|
+
f = `zsh -c 'print -rl -- lib/*(om[1,5]M)'`.split("\n")
|
1405
|
+
if f && f.size() > 0
|
1406
|
+
insert_into_list("lib/", f)
|
1407
|
+
flg = true
|
1408
|
+
end
|
1409
|
+
dd = File.basename(Dir.pwd)
|
1410
|
+
if f.index("lib/#{dd}/")
|
1411
|
+
f = `zsh -c 'print -rl -- lib/#{dd}/*(om[1,5]M)'`.split("\n")
|
1412
|
+
if f && f.size() > 0
|
1413
|
+
insert_into_list("lib/#{dd}/", f)
|
1414
|
+
flg = true
|
1415
|
+
end
|
1416
|
+
end
|
1417
|
+
end
|
1418
|
+
if $files.index("bin/")
|
1419
|
+
f = `zsh -c 'print -rl -- bin/*(om[1,5]M)'`.split("\n")
|
1420
|
+
insert_into_list("bin/", f) if f && f.size() > 0
|
1421
|
+
flg = true
|
1422
|
+
end
|
1423
|
+
return if flg
|
1424
|
+
|
1425
|
+
# lib has a dir in it with the gem name
|
1426
|
+
|
1427
|
+
end
|
1428
|
+
return if $files.size > 15
|
1429
|
+
|
1430
|
+
## first check accessed else modified will change accessed
|
1431
|
+
moda = `zsh -c 'print -rn -- *(/oa[1]M)'`
|
1432
|
+
if moda && moda != ""
|
1433
|
+
modf = `zsh -c 'print -rn -- #{moda}*(oa[1]M)'`
|
1434
|
+
if modf && modf != ""
|
1435
|
+
insert_into_list moda, modf
|
1436
|
+
end
|
1437
|
+
modm = `zsh -c 'print -rn -- #{moda}*(om[1]M)'`
|
1438
|
+
if modm && modm != "" && modm != modf
|
1439
|
+
insert_into_list moda, modm
|
1440
|
+
end
|
1441
|
+
end
|
1442
|
+
## get last modified dir
|
1443
|
+
modm = `zsh -c 'print -rn -- *(/om[1]M)'`
|
1444
|
+
if modm != moda
|
1445
|
+
modmf = `zsh -c 'print -rn -- #{modm}*(oa[1]M)'`
|
1446
|
+
insert_into_list modm, modmf
|
1447
|
+
modmf1 = `zsh -c 'print -rn -- #{modm}*(om[1]M)'`
|
1448
|
+
insert_into_list(modm, modmf1) if modmf1 != modmf
|
1449
|
+
else
|
1450
|
+
# if both are same then our options get reduced so we need to get something more
|
1451
|
+
# If you access the latest mod dir, then come back you get only one, since mod and accessed
|
1452
|
+
# are the same dir, so we need to find the second modified dir
|
1453
|
+
end
|
1454
|
+
end
|
1455
|
+
def insert_into_list dir, file
|
1456
|
+
ix = $files.index(dir)
|
1457
|
+
raise "something wrong can find #{dir}." unless ix
|
1458
|
+
$files.insert ix, *file
|
1459
|
+
end
|
1460
|
+
#
|
1461
|
+
# prints a prompt at bottom of screen, takes a character and returns textual representation
|
1462
|
+
# of character (as per get_char) and not the int that window.getchar returns.
|
1463
|
+
# It uses a window, so underlying text is not touched.
|
1464
|
+
#
|
1465
|
+
def get_single text, config={}
|
1466
|
+
w = one_line_window
|
1467
|
+
x = y = 0
|
1468
|
+
color = config[:color_pair] || $datacolor
|
1469
|
+
color=Ncurses.COLOR_PAIR(color);
|
1470
|
+
w.attron(color);
|
1471
|
+
w.mvprintw(x, y, "%s" % text);
|
1472
|
+
w.attroff(color);
|
1473
|
+
w.wrefresh
|
1474
|
+
Ncurses::Panel.update_panels
|
1475
|
+
chr = get_char
|
1476
|
+
w.destroy
|
1477
|
+
w = nil
|
1478
|
+
return chr
|
1479
|
+
end
|
1480
|
+
|
1481
|
+
# identical to get_string but does not show as a popup with buttons, just ENTER
|
1482
|
+
# This is required if there are multiple inputs required and having several get_strings
|
1483
|
+
# one after the other seems really odd due to multiple popups
|
1484
|
+
# Unlike, get_string this does not return a nil if C-c pressed. Either returns a string if
|
1485
|
+
# ENTER pressed or a blank if C-c or Double Escape. So only blank to be checked
|
1486
|
+
def get_line text, config={}
|
1487
|
+
begin
|
1488
|
+
w = one_line_window
|
1489
|
+
form = RubyCurses::Form.new w
|
1490
|
+
|
1491
|
+
f = Field.new form, :label => text, :row => 0, :col => 1
|
1492
|
+
form.repaint
|
1493
|
+
w.wrefresh
|
1494
|
+
while((ch = w.getchar()) != FFI::NCurses::KEY_F10 )
|
1495
|
+
break if ch == 13
|
1496
|
+
if ch == 3 || ch == 27 || ch == 2727
|
1497
|
+
return ""
|
1498
|
+
end
|
1499
|
+
begin
|
1500
|
+
form.handle_key(ch)
|
1501
|
+
w.wrefresh
|
1502
|
+
rescue => err
|
1503
|
+
$log.debug( err) if err
|
1504
|
+
$log.debug(err.backtrace.join("\n")) if err
|
1505
|
+
textdialog ["Error in Messagebox: #{err} ", *err.backtrace], :title => "Exception"
|
1506
|
+
w.refresh # otherwise the window keeps showing (new FFI-ncurses issue)
|
1507
|
+
$error_message.value = ""
|
1508
|
+
ensure
|
1509
|
+
end
|
1510
|
+
end # while loop
|
1511
|
+
|
1512
|
+
ensure
|
1513
|
+
w.destroy
|
1514
|
+
w = nil
|
1515
|
+
end
|
1516
|
+
return f.text
|
1517
|
+
end
|
1518
|
+
|
1519
|
+
#run if __FILE__ == $PROGRAM_NAME
|
1520
|
+
end
|