cetus 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (8) hide show
  1. data/.gitignore +18 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +94 -0
  5. data/Rakefile +1 -0
  6. data/bin/cetus.rb +1503 -0
  7. data/cetus.gemspec +23 -0
  8. metadata +92 -0
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in cetus.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Rahul Kumar
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,94 @@
1
+ cetus
2
+ =====
3
+
4
+ lightning-fast file navigator
5
+
6
+ fork of lyra with a different hotkey idea. Use this for quickly navigating your file system using hotkeys
7
+ and bookmarks, and executing commands on single or multiple files easily.
8
+
9
+ See https://github.com/rkumar/lyra for detailed info of usage.
10
+
11
+ *lyra* uses keys from 1-9a-zA-Z for hotkeying file. This means that it leaves no keys
12
+ for other commands other than control keys, alt-keys, and punctuation keys. Also,
13
+ only 60 files can be shown at a time on a screen.
14
+
15
+ *cetus* tries another approach: it only uses lower case alphabets a-z (thus allowing us to use upper case, and numbers for other purposes).
16
+
17
+ It also maps z and Z which are at a convenient location. If there are more than 24 then za-zz are used. if the file exceed even that, then the range Za-ZZ is used. This means that larger screens will be filled with file names (upto ROWS * 3), and the user can even specify the number of columns. I've tried with 6. The remainder files gets an index of "&rt;" (right arrow) which means that if one presses right arrow then the indexing starts from the second column. RIGHT and LEFT arrow can be used to move indexing.
18
+
19
+ Experimentally added cursor movements which currently is only used if you get into a so-called visual mode, by pressing C-Space, moving up and down arrow and C-d and C-b will start adding to selection.
20
+ I did this since I was selecting quite a few files to delete in some old directory and would have liked some range delete. One can, of course, select files for a regex by pressing "/" giving a regex, and then using M-a to select all. Clear selection with M-A (Alt-Shift-a).
21
+
22
+ The cursor position shows up as a greater than sign, I hope not to implement all of zfm here. It's mainly used only to select multiple files that are contiguous.
23
+
24
+
25
+ Press C-x to execute actions on selected files. A menu of actions is displayed.
26
+ Or Press one of several commands after selecting files such as "D" for delete, or "M" to use your man-pager.
27
+ Or else press "D" and you will be prompted to enter a file shortcut to delete. The `rmtrash` command is called for delete.
28
+
29
+ You can bind other capital letters to any external command. If there are selected files, they will be passed to the command, else you will be prompted to select a file.
30
+
31
+ The rest is similar to lyra. Some key points are highlighted here.
32
+
33
+ * Create bookmarks for often used directories using Alt-m. Then access them using single-quote and upper character.
34
+ You have to be inside the directory when saving a new bookmark. e.g. `'P`. This is a fast way of jumping directories. I've got "P" mapped to projects, and "Z" to zfm, and so forth.
35
+
36
+ * Single-quote and small letter jumps to the first file starting with given letter. e.g. `'s`
37
+
38
+ * Space bar pages, also Alt n and p. Ctrl-d and Ctrl-b goes down 10 rows.
39
+
40
+ * Backtick is the main menu, which has options for sorting, filtering, seeing often used dirs and files, choosing from dirs in the `z` database, choosing used files from the `.viminfo` file, etc.
41
+
42
+ Other than using bookmarks, you can jump quickly to other directories or open files using BACKTICK and the releant option which should become part of muscle memory very fast.
43
+
44
+ * Use Alt-d and Alt-f to see used directories and used files. Used directories are those dirs in which you have opened a file, not all dirs you've traversed.
45
+
46
+ * By default selecting a file invokes $EDITOR, the default command can be changed from the menu.
47
+
48
+ * Switch between editor mode and page mode. OFten you wish to quickly view files (maybe for deleting or moving). You don't want these files to get listed in .viminfo. Switching to pager mode, invokes your $MANPAGER on selected files which makes navigation faster. Set `most` to your MANPAGER.
49
+
50
+ * use Alt+ and Alt- to increase or decrease the number of columns shown on screen. By default 3 are shown.
51
+
52
+ * Use '=' to toggle hidden files, long listing, ignore case etc.
53
+
54
+ * Use slash "/" to run a regex on the dir listing.
55
+
56
+ * Use ESCAPE or C-c to clear any filter, regex, sort order, sub-listing etc
57
+
58
+ * TAB switches between various views such as order by modified time, order by access time, dirs only, oldest files etc.
59
+
60
+ * Comma goes to parent directory, period (dot) pops directory stack.
61
+
62
+ * Plus sign allows user to enter directory to go to. If it does not exist, home dir is checked, else ENV var by that names is checked, else if its a file that is opened.
63
+
64
+ * Use BACKTICK "a" for using `ack` in the current dir. Then you can select from listed files and edit or do whatever. Similarly, BACKTICK "l" for running `locate`, and BACKTICK "/" for running `find`.
65
+
66
+ * Note that I use readline for most entries, so you can press UP arrow to get previous entries.
67
+
68
+
69
+ ## Requirements ##
70
+
71
+ Requires ruby 1.9.3, and uses zsh for globbing.
72
+ Uses $EDITOR and $MANPAGER or $PAGER.
73
+
74
+ Optionally uses ack, locate, find for options with that name. You may replace ack with ag or other.
75
+ Optionally has interface to `z` and `viminfo` -- can be replaced with what you use.
76
+
77
+ ## INSTALL ##
78
+
79
+ Copy cetus.rb to somewhere on your path, e.g. $HOME/bin
80
+
81
+ cp cetus.rb ~/bin
82
+
83
+ alias c=~/bin/cetus.rb
84
+
85
+ $ c
86
+
87
+ To quit, press "Q" or :q or :wq or :x. If you have created bookmarks, they will be saved with :x and :wq. :q will warn if you quitting with unsaved bookmarks. Used files and dirs are also saved when saving happens. However, if you have not saved bookmarks then you will not be prompted to save used dirs and files.
88
+
89
+ Be sure to try zfm, too. zfm requires only zsh and contains a VIM mode too if that interests you.
90
+ See https://github.com/rkumar/zfm
91
+
92
+ ## Credits ##
93
+
94
+ Cetus refers to the constellation and means a whale if memory serves me correctly.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/cetus.rb ADDED
@@ -0,0 +1,1503 @@
1
+ #!/usr/bin/env ruby
2
+ # ----------------------------------------------------------------------------- #
3
+ # File: cetus.rb
4
+ # Description: Fast file navigation, a tiny version of zfm
5
+ # but with a diffrent indexing mechanism
6
+ # Author: rkumar http://github.com/rkumar/cetus/
7
+ # Date: 2013-02-17 - 17:48
8
+ # License: GPL
9
+ # Last update: 2013-03-03 18:01
10
+ # ----------------------------------------------------------------------------- #
11
+ # cetus.rb Copyright (C) 2012-2013 rahul kumar
12
+ require 'readline'
13
+ require 'io/wait'
14
+ # http://www.ruby-doc.org/stdlib-1.9.3/libdoc/shellwords/rdoc/Shellwords.html
15
+ require 'shellwords'
16
+ require 'fileutils'
17
+ # -- requires 1.9.3 for io/wait
18
+ # -- cannot do with Highline since we need a timeout on wait, not sure if HL can do that
19
+
20
+ ## INSTALLATION
21
+ # copy into PATH
22
+ # alias c=~/bin/cetus.rb
23
+ # c
24
+ VERSION="0.1.1-b"
25
+ O_CONFIG=true
26
+ CONFIG_FILE="~/.lyrainfo"
27
+
28
+ $bindings = {}
29
+ $bindings = {
30
+ "`" => "main_menu",
31
+ "=" => "toggle_menu",
32
+ "!" => "command_mode",
33
+ "@" => "selection_mode_toggle",
34
+ "M-a" => "select_all",
35
+ "M-A" => "unselect_all",
36
+ "," => "goto_parent_dir",
37
+ "+" => "goto_dir",
38
+ "." => "pop_dir",
39
+ ":" => "subcommand",
40
+ "'" => "goto_bookmark",
41
+ "/" => "enter_regex",
42
+ "M-p" => "prev_page",
43
+ "M-n" => "next_page",
44
+ "SPACE" => "next_page",
45
+ "M-f" => "select_visited_files",
46
+ "M-d" => "select_used_dirs",
47
+ "M-b" => "select_bookmarks",
48
+ "M-m" => "create_bookmark",
49
+ "M-M" => "show_marks",
50
+ "C-c" => "escape",
51
+ "ESCAPE" => "escape",
52
+ "TAB" => "views",
53
+ "C-i" => "views",
54
+ "?" => "child_dirs",
55
+ "ENTER" => "select_current",
56
+ "D" => "delete_file",
57
+ "M" => "file_actions most",
58
+ "Q" => "quit_command",
59
+ "RIGHT" => "column_next",
60
+ "LEFT" => "column_next 1",
61
+ "C-x" => "file_actions",
62
+ "M--" => "columns_incdec -1",
63
+ "M-+" => "columns_incdec 1",
64
+ "S" => "command_file list y ls -lh",
65
+ "L" => "command_file Page n less",
66
+ "C-d" => "cursor_scroll_dn",
67
+ "C-b" => "cursor_scroll_up",
68
+ "UP" => "cursor_up",
69
+ "DOWN" => "cursor_dn",
70
+ "C-SPACE" => "visual_mode_toggle",
71
+
72
+ "M-?" => "print_help",
73
+ "F1" => "print_help",
74
+ "F2" => "child_dirs"
75
+
76
+ }
77
+
78
+ ## clean this up a bit, copied from shell program and macro'd
79
+ $kh=Hash.new
80
+ $kh["OP"]="F1"
81
+ $kh[""]="UP"
82
+ $kh["[5~"]="PGUP"
83
+ $kh['']="ESCAPE"
84
+ KEY_PGDN="[6~"
85
+ KEY_PGUP="[5~"
86
+ ## I needed to replace the O with a [ for this to work
87
+ # in Vim Home comes as ^[OH whereas on the command line it is correct as ^[[H
88
+ KEY_HOME=''
89
+ KEY_END=""
90
+ KEY_F1="OP"
91
+ KEY_UP=""
92
+ KEY_DOWN=""
93
+
94
+ $kh[KEY_PGDN]="PgDn"
95
+ $kh[KEY_PGUP]="PgUp"
96
+ $kh[KEY_HOME]="Home"
97
+ $kh[KEY_END]="End"
98
+ $kh[KEY_F1]="F1"
99
+ $kh[KEY_UP]="UP"
100
+ $kh[KEY_DOWN]="DOWN"
101
+ KEY_LEFT=''
102
+ KEY_RIGHT=''
103
+ $kh["OQ"]="F2"
104
+ $kh["OR"]="F3"
105
+ $kh["OS"]="F4"
106
+ $kh[KEY_LEFT] = "LEFT"
107
+ $kh[KEY_RIGHT]= "RIGHT"
108
+ KEY_F5='[15~'
109
+ KEY_F6='[17~'
110
+ KEY_F7='[18~'
111
+ KEY_F8='[19~'
112
+ KEY_F9='[20~'
113
+ KEY_F10='[21~'
114
+ $kh[KEY_F5]="F5"
115
+ $kh[KEY_F6]="F6"
116
+ $kh[KEY_F7]="F7"
117
+ $kh[KEY_F8]="F8"
118
+ $kh[KEY_F9]="F9"
119
+ $kh[KEY_F10]="F10"
120
+
121
+ ## get a character from user and return as a string
122
+ # Adapted from:
123
+ #http://stackoverflow.com/questions/174933/how-to-get-a-single-character-without-pressing-enter/8274275#8274275
124
+ # Need to take complex keys and matc against a hash.
125
+ def get_char
126
+ begin
127
+ system("stty raw -echo 2>/dev/null") # turn raw input on
128
+ c = nil
129
+ #if $stdin.ready?
130
+ c = $stdin.getc
131
+ cn=c.ord
132
+ return "ENTER" if cn == 10 || cn == 13
133
+ return "BACKSPACE" if cn == 127
134
+ return "C-SPACE" if cn == 0
135
+ return "SPACE" if cn == 32
136
+ # next does not seem to work, you need to bind C-i
137
+ return "TAB" if cn == 8
138
+ if cn >= 0 && cn < 27
139
+ x= cn + 96
140
+ return "C-#{x.chr}"
141
+ end
142
+ if c == ''
143
+ buff=c.chr
144
+ while true
145
+ k = nil
146
+ if $stdin.ready?
147
+ k = $stdin.getc
148
+ #puts "got #{k}"
149
+ buff += k.chr
150
+ else
151
+ x=$kh[buff]
152
+ return x if x
153
+ #puts "returning with #{buff}"
154
+ if buff.size == 2
155
+ ## possibly a meta/alt char
156
+ k = buff[-1]
157
+ return "M-#{k.chr}"
158
+ end
159
+ return buff
160
+ end
161
+ end
162
+ end
163
+ #end
164
+ return c.chr if c
165
+ ensure
166
+ #system "stty -raw echo" # turn raw input off
167
+ system("stty -raw echo 2>/dev/null") # turn raw input on
168
+ end
169
+ end
170
+
171
+ ## GLOBALS
172
+ #$IDX="123456789abcdefghijklmnoprstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
173
+ #$IDX="abcdefghijklmnopqrstuvwxy"
174
+ $IDX=('a'..'y').to_a
175
+ $IDX.concat ('za'..'zz').to_a
176
+ $IDX.concat ('Za'..'Zz').to_a
177
+ $IDX.concat ('ZA'..'ZZ').to_a
178
+
179
+ $selected_files = Array.new
180
+ $bookmarks = {}
181
+ $mode = nil
182
+ $glines=%x(tput lines).to_i
183
+ $gcols=%x(tput cols).to_i
184
+ $grows = $glines - 3
185
+ $pagesize = 60
186
+ $gviscols = 3
187
+ $pagesize = $grows * $gviscols
188
+ $stact = 0
189
+ $editor_mode = true
190
+ $visual_block_start = nil
191
+ $pager_command = {
192
+ :text => 'most',
193
+ :image => 'open',
194
+ :zip => 'tar ztvf %% | most',
195
+ :unknown => 'open'
196
+ }
197
+ $dir_position = {}
198
+ ## CONSTANTS
199
+ GMARK='*'
200
+ CURMARK='>'
201
+ MSCROLL = 10
202
+ SPACE=" "
203
+ CLEAR = "\e[0m"
204
+ BOLD = "\e[1m"
205
+ BOLD_OFF = "\e[22m"
206
+ RED = "\e[31m"
207
+ ON_RED = "\e[41m"
208
+ GREEN = "\e[32m"
209
+ YELLOW = "\e[33m"
210
+ BLUE = "\e[34m"
211
+
212
+ ON_BLUE = "\e[44m"
213
+ REVERSE = "\e[7m"
214
+ CURSOR_COLOR = ON_BLUE
215
+ $patt=nil
216
+ $ignorecase = true
217
+ $quitting = false
218
+ $modified = $writing = false
219
+ $visited_files = []
220
+ ## dir stack for popping
221
+ $visited_dirs = []
222
+ ## dirs where some work has been done, for saving and restoring
223
+ $used_dirs = []
224
+ $sorto = nil
225
+ $viewctr = 0
226
+ $history = []
227
+ $sta = $cursor = 0
228
+ $visual_mode = false
229
+ #$help = "#{BOLD}1-9a-zA-Z#{BOLD_OFF} Select #{BOLD}/#{BOLD_OFF} Grep #{BOLD}'#{BOLD_OFF} First char #{BOLD}M-n/p#{BOLD_OFF} Paging #{BOLD}!#{BOLD_OFF} Command Mode #{BOLD}@#{BOLD_OFF} Selection Mode #{BOLD}q#{BOLD_OFF} Quit"
230
+
231
+ $help = "#{BOLD}M-?#{BOLD_OFF} Help #{BOLD}`#{BOLD_OFF} Menu #{BOLD}!#{BOLD_OFF} Command #{BOLD}=#{BOLD_OFF} Toggle #{BOLD}@#{BOLD_OFF} Selection Mode #{BOLD}Q#{BOLD_OFF} Quit "
232
+
233
+ ## main loop which calls all other programs
234
+ def run()
235
+ home=ENV['HOME']
236
+ ctr=0
237
+ config_read
238
+ $files = `zsh -c 'print -rl -- *(#{$hidden}M)'`.split("\n")
239
+ fl=$files.size
240
+
241
+ selectedix = nil
242
+ $patt=""
243
+ $sta=0
244
+ while true
245
+ i = 0
246
+ if $patt
247
+ if $ignorecase
248
+ $view = $files.grep(/#{$patt}/i)
249
+ else
250
+ $view = $files.grep(/#{$patt}/)
251
+ end
252
+ else
253
+ $view = $files
254
+ end
255
+ fl=$view.size
256
+ $sta = 0 if $sta >= fl || $sta < 0
257
+ $viewport = $view[$sta, $pagesize]
258
+ fin = $sta + $viewport.size
259
+ $title ||= Dir.pwd.sub(home, "~")
260
+ system("clear")
261
+ # title
262
+ print "#{GREEN}#{$help} #{BLUE}cetus #{VERSION}#{CLEAR}\n"
263
+ t = "#{$title} #{$sta + 1} to #{fin} of #{fl} #{$sorto} F:#{$filterstr}"
264
+ t = t[t.size-$gcols..-1] if t.size >= $gcols
265
+ print "#{BOLD}#{t}#{CLEAR}\n"
266
+ ## nilling the title means a superimposed one gets cleared.
267
+ #$title = nil
268
+ # split into 2 procedures so columnate can e clean and reused.
269
+ buff = format $viewport
270
+ buff = columnate buff, $grows
271
+ # needed the next line to see how much extra we were going in padding
272
+ #buff.each {|line| print "#{REVERSE}#{line}#{CLEAR}\n" }
273
+ buff.each {|line| print line, "\n" }
274
+ print
275
+ # prompt
276
+ #print "#{$files.size}, #{view.size} sta=#{sta} (#{patt}): "
277
+ _mm = ""
278
+ _mm = "[#{$mode}] " if $mode
279
+ print "\r#{_mm}#{$patt} >"
280
+ ch = get_char
281
+ #puts
282
+ #break if ch == "q"
283
+ #elsif ch =~ /^[1-9a-zA-Z]$/
284
+ if ch =~ /^[a-zZ]$/
285
+ # hint mode
286
+ select_hint $viewport, ch
287
+ ctr = 0
288
+ elsif ch == "BACKSPACE"
289
+ $patt = $patt[0..-2]
290
+ ctr = 0
291
+ else
292
+ #binding = $bindings[ch]
293
+ x = $bindings[ch]
294
+ x = x.split if x
295
+ if x
296
+ binding = x.shift
297
+ args = x
298
+ send(binding, *args) if binding
299
+ else
300
+ #perror "No binding for #{ch}"
301
+ end
302
+ #p ch
303
+ end
304
+ break if $quitting
305
+ end
306
+ puts "bye"
307
+ config_write if $writing
308
+ end
309
+
310
+ ## code related to long listing of files
311
+ GIGA_SIZE = 1073741824.0
312
+ MEGA_SIZE = 1048576.0
313
+ KILO_SIZE = 1024.0
314
+
315
+ # Return the file size with a readable style.
316
+ def readable_file_size(size, precision)
317
+ case
318
+ #when size == 1 : "1 B"
319
+ when size < KILO_SIZE then "%d B" % size
320
+ when size < MEGA_SIZE then "%.#{precision}f K" % (size / KILO_SIZE)
321
+ when size < GIGA_SIZE then "%.#{precision}f M" % (size / MEGA_SIZE)
322
+ else "%.#{precision}f G" % (size / GIGA_SIZE)
323
+ end
324
+ end
325
+ ## format date for file given stat
326
+ def date_format t
327
+ t.strftime "%Y/%m/%d"
328
+ end
329
+ ##
330
+ #
331
+ # print in columns
332
+ # ary - array of data
333
+ # sz - lines in one column
334
+ #
335
+ def columnate ary, sz
336
+ buff=Array.new
337
+ return buff if ary.nil? || ary.size == 0
338
+
339
+ # determine width based on number of files to show
340
+ # if less than sz then 1 col and full width
341
+ #
342
+ wid = 30
343
+ ars = ary.size
344
+ ars = [$pagesize, ary.size].min
345
+ d = 0
346
+ if ars <= sz
347
+ wid = $gcols - d
348
+ else
349
+ tmp = (ars * 1.000/ sz).ceil
350
+ wid = $gcols / tmp - d
351
+ end
352
+ #elsif ars < sz * 2
353
+ #wid = $gcols/2 - d
354
+ #elsif ars < sz * 3
355
+ #wid = $gcols/3 - d
356
+ #else
357
+ #wid = $gcols/$gviscols - d
358
+ #end
359
+
360
+ # ix refers to the index in the complete file list, wherease we only show 60 at a time
361
+ ix=0
362
+ while true
363
+ ## ctr refers to the index in the column
364
+ ctr=0
365
+ while ctr < sz
366
+
367
+ f = ary[ix]
368
+ fsz = f.size
369
+ if fsz > wid
370
+ f = f[0, wid-2]+"$ "
371
+ ## we do the coloring after trunc so ANSI escpe seq does not get get
372
+ if ix + $sta == $cursor
373
+ f = "#{CURSOR_COLOR}#{f}#{CLEAR}"
374
+ end
375
+ else
376
+ ## we do the coloring before padding so the entire line does not get padded, only file name
377
+ if ix + $sta == $cursor
378
+ f = "#{CURSOR_COLOR}#{f}#{CLEAR}"
379
+ end
380
+ #f = f.ljust(wid)
381
+ f << " " * (wid-fsz)
382
+ end
383
+
384
+ if buff[ctr]
385
+ buff[ctr] += f
386
+ else
387
+ buff[ctr] = f
388
+ end
389
+
390
+ ctr+=1
391
+ ix+=1
392
+ break if ix >= ary.size
393
+ end
394
+ break if ix >= ary.size
395
+ end
396
+ return buff
397
+ end
398
+ ## formats the data with number, mark and details
399
+ def format ary
400
+ #buff = Array.new
401
+ buff = Array.new(ary.size)
402
+ return buff if ary.nil? || ary.size == 0
403
+
404
+ # determine width based on number of files to show
405
+ # if less than sz then 1 col and full width
406
+ #
407
+ # ix refers to the index in the complete file list, wherease we only show 60 at a time
408
+ ix=0
409
+ ctr=0
410
+ ary.each do |f|
411
+ ## ctr refers to the index in the column
412
+ ind = get_shortcut(ix)
413
+ mark=SPACE
414
+ cur=SPACE
415
+ cur = CURMARK if ix + $sta == $cursor
416
+ mark=GMARK if $selected_files.index(ary[ix])
417
+
418
+ if $long_listing
419
+ begin
420
+ unless File.exist? f
421
+ last = f[-1]
422
+ if last == " " || last == "@" || last == '*'
423
+ stat = File.stat(f.chop)
424
+ end
425
+ else
426
+ stat = File.stat(f)
427
+ end
428
+ f = "%10s %s %s" % [readable_file_size(stat.size,1), date_format(stat.mtime), f]
429
+ rescue Exception => e
430
+ f = "%10s %s %s" % ["?", "??????????", f]
431
+ end
432
+ end
433
+
434
+ s = "#{ind}#{mark}#{cur}#{f}"
435
+ # I cannot color the current line since format does the chopping
436
+ # so not only does the next lines alignment get skeweed, but also if the line is truncated
437
+ # then the color overflows.
438
+ #if ix + $sta == $cursor
439
+ #s = "#{RED}#{s}#{CLEAR}"
440
+ #end
441
+
442
+ buff[ctr] = s
443
+
444
+ ctr+=1
445
+ ix+=1
446
+ end
447
+ return buff
448
+ end
449
+ ## select file based on key pressed
450
+ def select_hint view, ch
451
+ # a to y is direct
452
+ # if x or z take a key IF there are those many
453
+ #
454
+ ix = get_index(ch, view.size)
455
+ if ix
456
+ f = view[ix]
457
+ return unless f
458
+ $cursor = $sta + ix
459
+
460
+ if $mode == 'SEL'
461
+ toggle_select f
462
+ elsif $mode == 'COM'
463
+ run_command f
464
+ else
465
+ open_file f
466
+ end
467
+ #selectedix=ix
468
+ end
469
+ end
470
+ ## toggle selection state of file
471
+ def toggle_select f
472
+ if $selected_files.index f
473
+ $selected_files.delete f
474
+ else
475
+ $selected_files.push f
476
+ end
477
+ end
478
+ ## open file or directory
479
+ def open_file f
480
+ return unless f
481
+ if f[0] == "~"
482
+ f = File.expand_path(f)
483
+ end
484
+ unless File.exist? f
485
+ # this happens if we use (T) in place of (M)
486
+ # it places a space after normal files and @ and * which borks commands
487
+ last = f[-1]
488
+ if last == " " || last == "@" || last == '*'
489
+ f = f.chop
490
+ end
491
+ end
492
+ nextpos = nil
493
+
494
+ # could be a bookmark with position attached to it
495
+ if f.index(":")
496
+ f, nextpos = f.split(":")
497
+ end
498
+ if File.directory? f
499
+ save_dir_pos
500
+ change_dir f, nextpos
501
+ elsif File.readable? f
502
+ $default_command ||= "$EDITOR"
503
+ if !$editor_mode
504
+ ft = filetype f
505
+ if ft
506
+ comm = $pager_command[ft]
507
+ else
508
+ comm = $pager_command[File.extname(f)]
509
+ comm = $pager_command["unknown"] unless comm
510
+ end
511
+ else
512
+ comm = $default_command
513
+ end
514
+ comm ||= $default_command
515
+ if comm.index("%%")
516
+ comm = comm.gsub("%%", Shellwords.escape(f))
517
+ else
518
+ comm = comm + " #{Shellwords.escape(f)}"
519
+ end
520
+ system("#{comm}")
521
+ f = Dir.pwd + "/" + f if f[0] != '/'
522
+ $visited_files.insert(0, f)
523
+ push_used_dirs Dir.pwd
524
+ else
525
+ perror "open_file: (#{f}) not found"
526
+ # could check home dir or CDPATH env variable DO
527
+ end
528
+ end
529
+
530
+ ## run command on given file/s
531
+ # Accepts command from user
532
+ # After putting readline in place of gets, pressing a C-c has a delayed effect. It goes intot
533
+ # exception bloack after executing other commands and still does not do the return !
534
+ def run_command f
535
+ files=nil
536
+ case f
537
+ when Array
538
+ # escape the contents and create a string
539
+ files = Shellwords.join(f)
540
+ when String
541
+ files = Shellwords.escape(f)
542
+ end
543
+ print "Run a command on #{files}: "
544
+ begin
545
+ #Readline::HISTORY.push(*values)
546
+ command = Readline::readline('>', true)
547
+ #command = gets().chomp
548
+ return if command.size == 0
549
+ print "Second part of command: "
550
+ #command2 = gets().chomp
551
+ command2 = Readline::readline('>', true)
552
+ puts "#{command} #{files} #{command2}"
553
+ system "#{command} #{files} #{command2}"
554
+ rescue Exception => ex
555
+ perror "Canceled command, press a key"
556
+ return
557
+ end
558
+ begin
559
+ rescue Exception => ex
560
+ end
561
+
562
+ refresh
563
+ puts "Press a key ..."
564
+ push_used_dirs Dir.pwd
565
+ get_char
566
+ end
567
+
568
+ ## cd to a dir
569
+ def change_dir f, pos=nil
570
+ $visited_dirs.insert(0, Dir.pwd)
571
+ f = File.expand_path(f)
572
+ Dir.chdir f
573
+ $filterstr ||= "M"
574
+ $files = `zsh -c 'print -rl -- *(#{$sorto}#{$hidden}#{$filterstr})'`.split("\n")
575
+ post_cd
576
+ if pos
577
+ # convert curpos to sta also
578
+ #$cursor = pos.to_i
579
+ goto_line pos.to_i
580
+ end
581
+ end
582
+
583
+ ## clear sort order and refresh listing, used typically if you are in some view
584
+ # such as visited dirs or files
585
+ def escape
586
+ $sorto = nil
587
+ $viewctr = 0
588
+ $title = nil
589
+ $filterstr = "M"
590
+ visual_block_clear
591
+ refresh
592
+ end
593
+
594
+ ## refresh listing after some change like option change, or toggle
595
+ def refresh
596
+ $filterstr ||= "M"
597
+ $files = `zsh -c 'print -rl -- *(#{$sorto}#{$hidden}#{$filterstr})'`.split("\n")
598
+ $patt=nil
599
+ $title = nil
600
+ end
601
+ #
602
+ ## unselect all files
603
+ def unselect_all
604
+ $selected_files = []
605
+ end
606
+
607
+ ## select all files
608
+ def select_all
609
+ $selected_files = $view.dup
610
+ end
611
+
612
+ ## accept dir to goto and change to that ( can be a file too)
613
+ def goto_dir
614
+ print "Enter path: "
615
+ begin
616
+ path = gets.chomp
617
+ #rescue => ex
618
+ rescue Exception => ex
619
+ perror "Cancelled cd, press a key"
620
+ return
621
+ end
622
+ f = File.expand_path(path)
623
+ unless File.directory? f
624
+ ## check for env variable
625
+ tmp = ENV[path]
626
+ if tmp.nil? || !File.directory?( tmp )
627
+ ## check for dir in home
628
+ tmp = File.expand_path("~/#{path}")
629
+ if File.directory? tmp
630
+ f = tmp
631
+ end
632
+ else
633
+ f = tmp
634
+ end
635
+ end
636
+
637
+ open_file f
638
+ end
639
+
640
+ ## toggle mode to selection or not
641
+ # In selection, pressed hotkey selects a file without opening, one can keep selecting
642
+ # (or deselecting).
643
+ #
644
+ def selection_mode_toggle
645
+ if $mode == 'SEL'
646
+ # we seem to be coming out of select mode with some files
647
+ if $selected_files.size > 0
648
+ run_command $selected_files
649
+ end
650
+ $mode = nil
651
+ else
652
+ #$selection_mode = !$selection_mode
653
+ $mode = 'SEL'
654
+ end
655
+ end
656
+ ## toggle command mode
657
+ def command_mode
658
+ if $mode == 'COM'
659
+ $mode = nil
660
+ return
661
+ end
662
+ $mode = 'COM'
663
+ end
664
+ def goto_parent_dir
665
+ change_dir ".."
666
+ end
667
+ ## This actually filters, in zfm it goes to that entry since we have a cursor there
668
+ #
669
+ def goto_entry_starting_with fc=nil
670
+ unless fc
671
+ print "Entries starting with: "
672
+ fc = get_char
673
+ end
674
+ return if fc.size != 1
675
+ ## this is wrong and duplicates the functionality of /
676
+ # It shoud go to cursor of item starting with fc
677
+ $patt = "^#{fc}"
678
+ end
679
+ def goto_bookmark ch=nil
680
+ unless ch
681
+ print "Enter bookmark char: "
682
+ ch = get_char
683
+ end
684
+ if ch =~ /^[A-Z]$/
685
+ d = $bookmarks[ch]
686
+ # this is if we use zfm's bookmarks which have a position
687
+ # this way we leave the position as is, so it gets written back
688
+ nextpos = nil
689
+ if d
690
+ if d.index(":")
691
+ ix = d.index(":")
692
+ nextpos = d[ix+1..-1]
693
+ d = d[0,ix]
694
+ end
695
+ change_dir d, nextpos
696
+ else
697
+ perror "#{ch} not a bookmark"
698
+ end
699
+ else
700
+ #goto_entry_starting_with ch
701
+ file_starting_with ch
702
+ end
703
+ end
704
+
705
+
706
+ ## take regex from user, to run on files on screen, user can filter file names
707
+ def enter_regex
708
+ print "Enter (regex) pattern: "
709
+ $patt = gets().chomp
710
+ ctr = 0
711
+ end
712
+ def next_page
713
+ $sta += $pagesize
714
+ end
715
+ def prev_page
716
+ $sta -= $pagesize
717
+ end
718
+ def print_help
719
+ system("clear")
720
+ puts "HELP"
721
+ puts
722
+ puts "To open a file or dir press 1-9 a-z A-Z "
723
+ puts "Command Mode: Will prompt for a command to run on a file, after selecting using hotkey"
724
+ puts "Selection Mode: Each selection adds to selection list (toggles)"
725
+ puts " Upon exiting mode, user is prompted for a command to run on selected files"
726
+ puts
727
+ ary = []
728
+ $bindings.each_pair { |k, v| ary.push "#{k.ljust(7)} => #{v}" }
729
+ ary = columnate ary, $grows - 7
730
+ ary.each {|line| print line, "\n" }
731
+ get_char
732
+
733
+ end
734
+ def show_marks
735
+ puts
736
+ puts "Bookmarks: "
737
+ $bookmarks.each_pair { |k, v| puts "#{k.ljust(7)} => #{v}" }
738
+ puts
739
+ print "Enter bookmark to goto: "
740
+ ch = get_char
741
+ goto_bookmark(ch) if ch =~ /^[A-Z]$/
742
+ end
743
+ # MENU MAIN -- keep consistent with zfm
744
+ def main_menu
745
+ h = {
746
+ :a => :ack,
747
+ "/" => :ffind,
748
+ :l => :locate,
749
+ :v => :viminfo,
750
+ :z => :z_interface,
751
+ :d => :child_dirs,
752
+ :s => :sort_menu,
753
+ :F => :filter_menu,
754
+ :c => :command_menu ,
755
+ :B => :bindkey_ext_command,
756
+ :x => :extras
757
+ }
758
+ menu "Main Menu", h
759
+ end
760
+ def toggle_menu
761
+ h = { :h => :toggle_hidden, :c => :toggle_case, :l => :toggle_long_list , "1" => :toggle_columns}
762
+ menu "Toggle Menu", h
763
+ end
764
+ def menu title, h
765
+ return unless h
766
+
767
+ pbold "#{title}"
768
+ h.each_pair { |k, v| puts " #{k}: #{v}" }
769
+ ch = get_char
770
+ binding = h[ch]
771
+ binding = h[ch.to_sym] unless binding
772
+ if binding
773
+ if respond_to?(binding, true)
774
+ send(binding)
775
+ end
776
+ end
777
+ return ch, binding
778
+ end
779
+ def toggle_menu
780
+ h = { :h => :toggle_hidden, :c => :toggle_case, :l => :toggle_long_list , "1" => :toggle_columns}
781
+ ch, menu_text = menu "Toggle Menu", h
782
+ case menu_text
783
+ when :toggle_hidden
784
+ $hidden = $hidden ? nil : "D"
785
+ refresh
786
+ when :toggle_case
787
+ #$ignorecase = $ignorecase ? "" : "i"
788
+ $ignorecase = !$ignorecase
789
+ refresh
790
+ when :toggle_columns
791
+ $gviscols = 3 if $gviscols == 1
792
+ #$long_listing = false if $gviscols > 1
793
+ x = $grows * $gviscols
794
+ $pagesize = $pagesize==x ? $grows : x
795
+ when :toggle_long_list
796
+ $long_listing = !$long_listing
797
+ if $long_listing
798
+ $gviscols = 1
799
+ $pagesize = $grows
800
+ else
801
+ x = $grows * $gviscols
802
+ $pagesize = $pagesize==x ? $grows : x
803
+ end
804
+ refresh
805
+ end
806
+ end
807
+
808
+ def sort_menu
809
+ lo = nil
810
+ h = { :n => :newest, :a => :accessed, :o => :oldest,
811
+ :l => :largest, :s => :smallest , :m => :name , :r => :rname, :d => :dirs, :c => :clear }
812
+ ch, menu_text = menu "Sort Menu", h
813
+ case menu_text
814
+ when :newest
815
+ lo="om"
816
+ when :accessed
817
+ lo="oa"
818
+ when :oldest
819
+ lo="Om"
820
+ when :largest
821
+ lo="OL"
822
+ when :smallest
823
+ lo="oL"
824
+ when :name
825
+ lo="on"
826
+ when :rname
827
+ lo="On"
828
+ when :dirs
829
+ lo="/"
830
+ when :clear
831
+ lo=""
832
+ end
833
+ ## This needs to persist and be a part of all listings, put in change_dir.
834
+ $sorto = lo
835
+ $files = `zsh -c 'print -rl -- *(#{lo}#{$hidden}M)'`.split("\n") if lo
836
+ $title = nil
837
+ #$files =$(eval "print -rl -- ${pattern}(${MFM_LISTORDER}$filterstr)")
838
+ end
839
+
840
+ def command_menu
841
+ ##
842
+ # since these involve full paths, we need more space, like only one column
843
+ #
844
+ ## in these cases, getting back to the earlier dir, back to earlier listing
845
+ # since we've basically overlaid the old listing
846
+ #
847
+ # should be able to sort THIS listing and not rerun command. But for that I'd need to use
848
+ # xargs ls -t etc rather than the zsh sort order. But we can run a filter using |.
849
+ #
850
+ h = { :t => :today, :D => :default_command }
851
+ if $editor_mode
852
+ h[:e] = :pager_mode
853
+ else
854
+ h[:e] = :editor_mode
855
+ end
856
+ ch, menu_text = menu "Command Menu", h
857
+ case menu_text
858
+ when :pager_mode
859
+ $editor_mode = false
860
+ $default_command = ENV['MANPAGER'] || ENV['PAGER']
861
+ when :editor_mode
862
+ $editor_mode = true
863
+ $default_command = nil
864
+ when :ffind
865
+ ffind
866
+ when :locate
867
+ locate
868
+ when :today
869
+ $files = `zsh -c 'print -rl -- *(#{$hidden}Mm0)'`.split("\n")
870
+ $title = "Today's files"
871
+ when :default_command
872
+ print "Selecting a file usually invokes $EDITOR, what command do you want to use repeatedly on selected files: "
873
+ $default_command = gets().chomp
874
+ if $default_command != ""
875
+ print "Second part of command (maybe blank): "
876
+ $default_command2 = gets().chomp
877
+ else
878
+ print "Cleared default command, will default to $EDITOR"
879
+ $default_command2 = nil
880
+ $default_command = nil
881
+ end
882
+ end
883
+ end
884
+ def extras
885
+ h = { "1" => :one_column, "2" => :multi_column, :c => :columns, :r => :config_read , :w => :config_write}
886
+ ch, menu_text = menu "Extras Menu", h
887
+ case menu_text
888
+ when :one_column
889
+ $pagesize = $grows
890
+ when :multi_column
891
+ #$pagesize = 60
892
+ $pagesize = $grows * $gviscols
893
+ when :columns
894
+ print "How many columns to show: 1-6 [current #{$gviscols}]? "
895
+ ch = get_char
896
+ ch = ch.to_i
897
+ if ch > 0 && ch < 7
898
+ $gviscols = ch.to_i
899
+ $pagesize = $grows * $gviscols
900
+ end
901
+ end
902
+ end
903
+ def filter_menu
904
+ h = { :d => :dirs, :f => :files, :e => :emptydirs , "0" => :emptyfiles}
905
+ ch, menu_text = menu "Filter Menu", h
906
+ files = nil
907
+ case menu_text
908
+ when :dirs
909
+ $filterstr = "/M"
910
+ files = `zsh -c 'print -rl -- *(#{$sorto}/M)'`.split("\n")
911
+ $title = "Filter: directories only"
912
+ when :files
913
+ $filterstr = "."
914
+ files = `zsh -c 'print -rl -- *(#{$sorto}#{$hidden}.)'`.split("\n")
915
+ $title = "Filter: files only"
916
+ when :emptydirs
917
+ $filterstr = "/D^F"
918
+ files = `zsh -c 'print -rl -- *(#{$sorto}/D^F)'`.split("\n")
919
+ $title = "Filter: empty directories"
920
+ when :emptyfiles
921
+ $filterstr = ".L0"
922
+ files = `zsh -c 'print -rl -- *(#{$sorto}#{$hidden}.L0)'`.split("\n")
923
+ $title = "Filter: empty files"
924
+ end
925
+ if files
926
+ $files = files
927
+ $stact = 0
928
+ end
929
+ end
930
+ def select_used_dirs
931
+ $title = "Used Directories"
932
+ $files = $used_dirs.uniq
933
+ end
934
+ def select_visited_files
935
+ # not yet a unique list, needs to be unique and have latest pushed to top
936
+ $title = "Visited Files"
937
+ $files = $visited_files.uniq
938
+ end
939
+ def select_bookmarks
940
+ $title = "Bookmarks"
941
+ $files = $bookmarks.values
942
+ end
943
+
944
+ ## part copied and changed from change_dir since we don't dir going back on top
945
+ # or we'll be stuck in a cycle
946
+ def pop_dir
947
+ # the first time we pop, we need to put the current on stack
948
+ if !$visited_dirs.index(Dir.pwd)
949
+ $visited_dirs.push Dir.pwd
950
+ end
951
+ ## XXX make sure thre is something to pop
952
+ d = $visited_dirs.delete_at 0
953
+ ## XXX make sure the dir exists, cuold have been deleted. can be an error or crash otherwise
954
+ $visited_dirs.push d
955
+ Dir.chdir d
956
+ $filterstr ||= "M"
957
+ $files = `zsh -c 'print -rl -- *(#{$sorto}#{$hidden}#{$filterstr})'`.split("\n")
958
+ post_cd
959
+ end
960
+ def post_cd
961
+ $patt=nil
962
+ $sta = $cursor = 0
963
+ $title = nil
964
+ if $selected_files.size > 0
965
+ $selected_files = []
966
+ end
967
+ $visual_block_start = nil
968
+ $stact = 0
969
+ screen_settings
970
+ revert_dir_pos
971
+ end
972
+ #
973
+ ## read dirs and files and bookmarks from file
974
+ def config_read
975
+ #f = File.expand_path("~/.zfminfo")
976
+ f = File.expand_path(CONFIG_FILE)
977
+ if File.readable? f
978
+ load f
979
+ # maybe we should check for these existing else crash will happen.
980
+ $used_dirs.push(*(DIRS.split ":"))
981
+ $visited_files.push(*(FILES.split ":"))
982
+ #$bookmarks.push(*bookmarks) if bookmarks
983
+ chars = ('A'..'Z')
984
+ chars.each do |ch|
985
+ if Kernel.const_defined? "BM_#{ch}"
986
+ $bookmarks[ch] = Kernel.const_get "BM_#{ch}"
987
+ end
988
+ end
989
+ end
990
+ end
991
+
992
+ ## save dirs and files and bookmarks to a file
993
+ def config_write
994
+ # Putting it in a format that zfm can also read and write
995
+ #f1 = File.expand_path("~/.zfminfo")
996
+ f1 = File.expand_path(CONFIG_FILE)
997
+ d = $used_dirs.join ":"
998
+ f = $visited_files.join ":"
999
+ File.open(f1, 'w+') do |f2|
1000
+ # use "\n" for two lines of text
1001
+ f2.puts "DIRS=\"#{d}\""
1002
+ f2.puts "FILES=\"#{f}\""
1003
+ $bookmarks.each_pair { |k, val|
1004
+ f2.puts "BM_#{k}=\"#{val}\""
1005
+ #f2.puts "BOOKMARKS[\"#{k}\"]=\"#{val}\""
1006
+ }
1007
+ end
1008
+ $writing = $modified = false
1009
+ end
1010
+
1011
+ ## accept a character to save this dir as a bookmark
1012
+ def create_bookmark
1013
+ print "Enter (upper case) char for bookmark: "
1014
+ ch = get_char
1015
+ if ch =~ /^[A-Z]$/
1016
+ $bookmarks[ch] = "#{Dir.pwd}:#{$cursor}"
1017
+ $modified = true
1018
+ else
1019
+ perror "Bookmark must be upper-case character"
1020
+ end
1021
+ end
1022
+ def subcommand
1023
+ print "Enter command: "
1024
+ begin
1025
+ command = gets().chomp
1026
+ rescue Exception => ex
1027
+ return
1028
+ end
1029
+ if command == "q"
1030
+ if $modified
1031
+ print "Do you want to save bookmarks? (y/n): "
1032
+ ch = get_char
1033
+ if ch == "y"
1034
+ $writing = true
1035
+ $quitting = true
1036
+ elsif ch == "n"
1037
+ $quitting = true
1038
+ print "Quitting without saving bookmarks"
1039
+ else
1040
+ perror "No action taken."
1041
+ end
1042
+ else
1043
+ $quitting = true
1044
+ end
1045
+ elsif command == "wq"
1046
+ $quitting = true
1047
+ $writing = true
1048
+ elsif command == "x"
1049
+ $quitting = true
1050
+ $writing = true if $modified
1051
+ elsif command == "p"
1052
+ system "echo $PWD | pbcopy"
1053
+ puts "Stored PWD in clipboard (using pbcopy)"
1054
+ end
1055
+ end
1056
+ def quit_command
1057
+ if $modified
1058
+ puts "Press w to save bookmarks before quitting " if $modified
1059
+ print "Press another q to quit "
1060
+ ch = get_char
1061
+ else
1062
+ $quitting = true
1063
+ end
1064
+ $quitting = true if ch == "q"
1065
+ $quitting = $writing = true if ch == "w"
1066
+ end
1067
+
1068
+ def views
1069
+ views=%w[/ om oa Om OL oL On on]
1070
+ viewlabels=%w[Dirs Newest Accessed Oldest Largest Smallest Reverse Name]
1071
+ $sorto = views[$viewctr]
1072
+ $viewctr += 1
1073
+ $viewctr = 0 if $viewctr > views.size
1074
+
1075
+ $files = `zsh -c 'print -rl -- *(#{$sorto}#{$hidden}M)'`.split("\n")
1076
+ $title = viewlabels[$viewctr]
1077
+
1078
+ end
1079
+ def child_dirs
1080
+ $title = "Child directories"
1081
+ $files = `zsh -c 'print -rl -- *(/#{$sorto}#{$hidden}M)'`.split("\n")
1082
+ end
1083
+ def select_current
1084
+ ## vp is local there, so i can do $vp[0]
1085
+ #open_file $view[$sta] if $view[$sta]
1086
+ open_file $view[$cursor] if $view[$cursor]
1087
+ end
1088
+
1089
+ ## create a list of dirs in which some action has happened, for saving
1090
+ def push_used_dirs d=Dir.pwd
1091
+ $used_dirs.index(d) || $used_dirs.push(d)
1092
+ end
1093
+ def pbold text
1094
+ puts "#{BOLD}#{text}#{BOLD_OFF}"
1095
+ end
1096
+ def perror text
1097
+ puts "#{RED}#{text}#{CLEAR}"
1098
+ get_char
1099
+ end
1100
+ def pause text=" Press a key ..."
1101
+ print text
1102
+ get_char
1103
+ end
1104
+ ## return shortcut for an index (offset in file array)
1105
+ # use 2 more arrays to make this faster
1106
+ # if z or Z take another key if there are those many in view
1107
+ # Also, display ROWS * COLS so now we are not limited to 60.
1108
+ def get_shortcut ix
1109
+ return "<" if ix < $stact
1110
+ ix -= $stact
1111
+ i = $IDX[ix]
1112
+ return i if i
1113
+ return "->"
1114
+ end
1115
+ ## returns the integer offset in view (file array based on a-y za-zz and Za - Zz
1116
+ # Called when user types a key
1117
+ # should we even ask for a second key if there are not enough rows
1118
+ # What if we want to also trap z with numbers for other purposes
1119
+ def get_index key, vsz=999
1120
+ i = $IDX.index(key)
1121
+ return i+$stact if i
1122
+ #sz = $IDX.size
1123
+ zch = nil
1124
+ if vsz > 25
1125
+ if key == "z" || key == "Z"
1126
+ print key
1127
+ zch = get_char
1128
+ print zch
1129
+ i = $IDX.index("#{key}#{zch}")
1130
+ return i+$stact if i
1131
+ end
1132
+ end
1133
+ return nil
1134
+ end
1135
+
1136
+ def delete_file
1137
+ file_actions :delete
1138
+ end
1139
+
1140
+ ## generic external command program
1141
+ # prompt is the user friendly text of command such as list for ls, or extract for dtrx, page for less
1142
+ # pauseyn is whether to pause after command as in file or ls
1143
+ #
1144
+ def command_file prompt, *command
1145
+ pauseyn = command.shift
1146
+ command = command.join " "
1147
+ print "[#{prompt}] Choose a file [#{$view[$cursor]}]: "
1148
+ file = ask_hint $view[$cursor]
1149
+ #print "#{prompt} :: Enter file shortcut: "
1150
+ #file = ask_hint
1151
+ perror "Command Cancelled" unless file
1152
+ return unless file
1153
+ file = File.expand_path(file)
1154
+ if File.exists? file
1155
+ file = Shellwords.escape(file)
1156
+ pbold "#{command} #{file} (#{pauseyn})"
1157
+ system "#{command} #{file}"
1158
+ pause if pauseyn == "y"
1159
+ refresh
1160
+ else
1161
+ perror "File #{file} not found"
1162
+ end
1163
+ end
1164
+
1165
+ ## prompt user for file shortcut and return file or nil
1166
+ #
1167
+ def ask_hint deflt=nil
1168
+ f = nil
1169
+ ch = get_char
1170
+ if ch == "ENTER"
1171
+ return deflt
1172
+ end
1173
+ ix = get_index(ch, $viewport.size)
1174
+ f = $viewport[ix] if ix
1175
+ return f
1176
+ end
1177
+
1178
+ ## check screen size and accordingly adjust some variables
1179
+ #
1180
+ def screen_settings
1181
+ $glines=%x(tput lines).to_i
1182
+ $gcols=%x(tput cols).to_i
1183
+ $grows = $glines - 3
1184
+ $pagesize = 60
1185
+ #$gviscols = 3
1186
+ $pagesize = $grows * $gviscols
1187
+ end
1188
+ ## moves column offset so we can reach unindexed columns or entries
1189
+ # 0 forward and any other back/prev
1190
+ def column_next dir=0
1191
+ if dir == 0
1192
+ $stact += $grows
1193
+ $stact = 0 if $stact >= $viewport.size
1194
+ else
1195
+ $stact -= $grows
1196
+ $stact = 0 if $stact < 0
1197
+ end
1198
+ end
1199
+ # currently i am only passing the action in from the list there as a key
1200
+ # I should be able to pass in new actions that are external commands
1201
+ def file_actions action=nil
1202
+ h = { :d => :delete, :m => :move, :r => :rename, :v => ENV["EDITOR"] || :vim,
1203
+ :l => :less, :s => :most , :f => :file , :o => :open, :x => :dtrx, :z => :zip }
1204
+ #acttext = h[action.to_sym] || action
1205
+ acttext = action || ""
1206
+ file = nil
1207
+
1208
+ sct = $selected_files.size
1209
+ if sct > 0
1210
+ text = "#{sct} files"
1211
+ file = $selected_files
1212
+ else
1213
+ print "[#{acttext}] Choose a file [#{$view[$cursor]}]: "
1214
+ file = ask_hint $view[$cursor]
1215
+ return unless file
1216
+ text = file
1217
+ end
1218
+
1219
+ case file
1220
+ when Array
1221
+ # escape the contents and create a string
1222
+ files = Shellwords.join(file)
1223
+ when String
1224
+ files = Shellwords.escape(file)
1225
+ end
1226
+
1227
+
1228
+ ch = nil
1229
+ if action
1230
+ menu_text = action
1231
+ else
1232
+ ch, menu_text = menu "File Menu for #{text}", h
1233
+ menu_text = :quit if ch == "q"
1234
+ end
1235
+ case menu_text.to_sym
1236
+ when :quit
1237
+ when :delete
1238
+ print "rmtrash #{files} ?[yn]: "
1239
+ ch = get_char
1240
+ return if ch != "y"
1241
+ system "rmtrash #{files}"
1242
+ refresh
1243
+ when :move
1244
+ print "move #{text} to : "
1245
+ target = gets().chomp
1246
+ if target.size > 2
1247
+ if File.directory? target
1248
+ FileUtils.mv text, target
1249
+ refresh
1250
+ else
1251
+ perror "Target not a dir"
1252
+ end
1253
+ else
1254
+ perror "Cancelled move"
1255
+ end
1256
+ when :zip
1257
+ print "Archive name: "
1258
+ target = gets().chomp
1259
+ # don't want a blank space or something screwing up
1260
+ if target && target.size > 3
1261
+ if File.exists? target
1262
+ perror "Target (#{target}) exists"
1263
+ else
1264
+ system "tar zcvf #{target} #{files}"
1265
+ refresh
1266
+ end
1267
+ end
1268
+ when :rename
1269
+ when :most, :less, :vim
1270
+ system "#{menu_text} #{files}"
1271
+ else
1272
+ return unless menu_text
1273
+ print "#{menu_text} #{files}"
1274
+ pause
1275
+ print
1276
+ system "#{menu_text} #{files}"
1277
+ refresh
1278
+ pause
1279
+ end
1280
+ # remove non-existent files from select list due to move or delete or rename or whatever
1281
+ if sct > 0
1282
+ $selected_files.reject! {|x| x = File.expand_path(x); !File.exists?(x) }
1283
+ end
1284
+ end
1285
+
1286
+ def columns_incdec howmany
1287
+ $gviscols += howmany.to_i
1288
+ $gviscols = 1 if $gviscols < 1
1289
+ $gviscols = 6 if $gviscols > 6
1290
+ $pagesize = $grows * $gviscols
1291
+ end
1292
+
1293
+ # bind a key to an external command wich can be then be used for files
1294
+ def bindkey_ext_command
1295
+ print
1296
+ pbold "Bind a capital letter to an external command"
1297
+ print "Enter a capital letter to bind: "
1298
+ ch = get_char
1299
+ return if ch == "Q"
1300
+ if ch =~ /^[A-Z]$/
1301
+ print "Enter an external command to bind to #{ch}: "
1302
+ com = gets().chomp
1303
+ if com != ""
1304
+ print "Enter prompt for command (blank if same as command): "
1305
+ pro = gets().chomp
1306
+ pro = com if pro == ""
1307
+ end
1308
+ print "Pause after output [y/n]: "
1309
+ yn = get_char
1310
+ $bindings[ch] = "command_file #{pro} #{yn} #{com}"
1311
+ end
1312
+ end
1313
+ def ack
1314
+ print "Enter a pattern to search (ack): "
1315
+ #pattern = gets.chomp
1316
+ pattern = Readline::readline('>', true)
1317
+ return if pattern == ""
1318
+ $title = "Files found using 'ack' #{pattern}"
1319
+ system("ack #{pattern}")
1320
+ pause
1321
+ files = `ack -l #{pattern}`.split("\n")
1322
+ if files.size == 0
1323
+ perror "No files found."
1324
+ else
1325
+ $files = files
1326
+ end
1327
+ end
1328
+ def ffind
1329
+ print "Enter a file name pattern to find: "
1330
+ pattern = Readline::readline('>', true)
1331
+ return if pattern == ""
1332
+ $title = "Files found using 'find' #{pattern}"
1333
+ files = `find . -name '#{pattern}'`.split("\n")
1334
+ if files.size == 0
1335
+ perror "No files found."
1336
+ else
1337
+ $files = files
1338
+ end
1339
+ end
1340
+ def locate
1341
+ print "Enter a file name pattern to locate: "
1342
+ pattern = Readline::readline('>', true)
1343
+ return if pattern == ""
1344
+ $title = "Files found using 'locate' #{pattern}"
1345
+ files = `locate #{pattern}`.split("\n")
1346
+ if files.size == 0
1347
+ perror "No files found."
1348
+ else
1349
+ $files = files
1350
+ end
1351
+ end
1352
+
1353
+ ## Displays files from .viminfo file, if you use some other editor which tracks files opened
1354
+ # then you can modify this accordingly.
1355
+ #
1356
+ def viminfo
1357
+ file = File.expand_path("~/.viminfo")
1358
+ if File.exists? file
1359
+ $title = "Files from ~/.viminfo"
1360
+ #$files = `grep '^>' ~/.viminfo | cut -d ' ' -f 2- | sed "s#~#$HOME#g"`.split("\n")
1361
+ $files = `grep '^>' ~/.viminfo | cut -d ' ' -f 2- `.split("\n")
1362
+ $files.reject! {|x| x = File.expand_path(x); !File.exists?(x) }
1363
+ end
1364
+ end
1365
+
1366
+ ## takes directories from the z program, if you use autojump you can
1367
+ # modify this accordingly
1368
+ #
1369
+ def z_interface
1370
+ file = File.expand_path("~/.z")
1371
+ if File.exists? file
1372
+ $title = "Directories from ~/.z"
1373
+ $files = `sort -rn -k2 -t '|' ~/.z | cut -f1 -d '|'`.split("\n")
1374
+ end
1375
+ end
1376
+
1377
+ ## there is no one consisten way i am getting.
1378
+ # i need to do a shell join if I am to pipe ffiles to say: xargs ls -t
1379
+ # but if i want to pipe names to grep xxx then i need to join with newlines
1380
+ def pipe
1381
+ #print "Enter pipe to filter existing files through: "
1382
+ #pipe = gets().chomp
1383
+ #if pipe != ""
1384
+ #end
1385
+ end
1386
+ def cursor_scroll_dn
1387
+ moveto(pos() + MSCROLL)
1388
+ end
1389
+ def cursor_scroll_up
1390
+ moveto(pos() - MSCROLL)
1391
+ end
1392
+ def cursor_dn
1393
+ moveto(pos() + 1)
1394
+ end
1395
+ def cursor_up
1396
+ moveto(pos() - 1)
1397
+ end
1398
+ def pos
1399
+ $cursor
1400
+ end
1401
+
1402
+ def moveto pos
1403
+ orig = $cursor
1404
+ $cursor = pos
1405
+ $cursor = [$cursor, $view.size - 1].min
1406
+ $cursor = [$cursor, 0].max
1407
+ star = [orig, $cursor].min
1408
+ fin = [orig, $cursor].max
1409
+ if $visual_mode
1410
+ # PWD has to be there in selction
1411
+ if $selected_files.index $view[$cursor]
1412
+ # this depends on the direction
1413
+ $selected_files = $selected_files - $view[star..fin]
1414
+ ## current row remains in selection always.
1415
+ $selected_files.push $view[$cursor]
1416
+ else
1417
+ $selected_files.concat $view[star..fin]
1418
+ end
1419
+ end
1420
+ end
1421
+ def visual_mode_toggle
1422
+ $visual_mode = !$visual_mode
1423
+ if $visual_mode
1424
+ $visual_block_start = $cursor
1425
+ $selected_files.push $view[$cursor]
1426
+ end
1427
+ end
1428
+ def visual_block_clear
1429
+ if $visual_block_start
1430
+ star = [$visual_block_start, $cursor].min
1431
+ fin = [$visual_block_start, $cursor].max
1432
+ $selected_files = $selected_files - $view[star..fin]
1433
+ end
1434
+ $visual_block_start = nil
1435
+ $visual_mode = nil
1436
+ end
1437
+ def file_starting_with fc
1438
+ ix = return_next_match(method(:file_matching?), "^#{fc}")
1439
+ if ix
1440
+ goto_line ix
1441
+ end
1442
+ end
1443
+ def file_matching? file, patt
1444
+ file =~ /#{patt}/
1445
+ end
1446
+ def return_next_match binding, *args
1447
+ first = nil
1448
+ ix = 0
1449
+ $view.each_with_index do |elem,ii|
1450
+ if binding.call(elem, *args)
1451
+ first ||= ii
1452
+ if ii > $cursor
1453
+ ix = ii
1454
+ break
1455
+ end
1456
+ end
1457
+ end
1458
+ return first if ix == 0
1459
+ return ix
1460
+ end
1461
+ ##
1462
+ # position cursor on a specific line which could be on a nother page
1463
+ # therefore calculate the correct start offset of the display also.
1464
+ def goto_line pos
1465
+ pages = ((pos * 1.00)/$pagesize).ceil
1466
+ pages -= 1
1467
+ $sta = pages * $pagesize + 1
1468
+ $cursor = pos
1469
+ end
1470
+ def filetype f
1471
+ return nil unless f
1472
+ f = Shellwords.escape(f)
1473
+ s = `file #{f}`
1474
+ if s.index "text"
1475
+ return :text
1476
+ elsif s.index(/[Zz]ip/)
1477
+ return :zip
1478
+ elsif s.index("archive")
1479
+ return :zip
1480
+ elsif s.index "image"
1481
+ return :image
1482
+ elsif s.index "data"
1483
+ return :text
1484
+ end
1485
+ nil
1486
+ end
1487
+
1488
+ def save_dir_pos
1489
+ return if $sta == 0 && $cursor == 0
1490
+ $dir_position[Dir.pwd] = [$sta, $cursor]
1491
+ end
1492
+ def revert_dir_pos
1493
+ $sta = 0
1494
+ $cursor = 0
1495
+ a = $dir_position[Dir.pwd]
1496
+ if a
1497
+ $sta = a.first
1498
+ $cursor = a[1]
1499
+ raise "sta is nil for #{Dir.pwd} : #{$dir_position[Dir.pwd]}" unless $sta
1500
+ raise "cursor is nil" unless $cursor
1501
+ end
1502
+ end
1503
+ run if __FILE__ == $PROGRAM_NAME