cetus 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +94 -0
- data/Rakefile +1 -0
- data/bin/cetus.rb +1503 -0
- data/cetus.gemspec +23 -0
- metadata +92 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
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["[A"]="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='[H'
|
89
|
+
KEY_END="[F"
|
90
|
+
KEY_F1="OP"
|
91
|
+
KEY_UP="[A"
|
92
|
+
KEY_DOWN="[B"
|
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='[D'
|
102
|
+
KEY_RIGHT='[C'
|
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
|