rtfm-filemanager 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rtfm.launch +28 -0
- data/bin/rtfm +1240 -0
- metadata +49 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e53ed89ba2707d8182a3436b5d0141d145a715a4a51db721fd9b84cfb97d7c96
|
4
|
+
data.tar.gz: f4fe71f58c4a4c2e46b40034af4b78cb9afc45f9820f4c6de46f70ebf7591f15
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4aaf7f5b9a0ec542f20a1bca46256c1cff8d24cdb55418f7dad7a0a259611a3f4146e70c01cdd5381bd52ba2de39a3e46e8834bed9c589f57c6138dc4ebcb0bd
|
7
|
+
data.tar.gz: 4e01bc468ff57bdab8ce085b7c974b6c53588cae2114200970ac9fe82f8921e090813afb919d7b311e15ef6275236cda81188e83cb28bad4ecd1be6a6ea93be6
|
data/.rtfm.launch
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# This function starts RTFM and will cd to the exit dir
|
2
|
+
#
|
3
|
+
# Add this line to your .bashrc or .zshrc to make RTFM exit to the
|
4
|
+
# current directory by launching the file manager via r in the terminal:
|
5
|
+
# source ~/.rtfm.launch
|
6
|
+
# ... and place the file .rtfm.launch in your home directory.
|
7
|
+
# With this, you can jump around in your directory structure via RTFM, exit to
|
8
|
+
# the desired directory, do work in the terminal and go back into RTFM via r.
|
9
|
+
|
10
|
+
function r {
|
11
|
+
f=$(mktemp)
|
12
|
+
(
|
13
|
+
set +e
|
14
|
+
rtfm "$f"
|
15
|
+
code=$?
|
16
|
+
if [ "$code" != 0 ]; then
|
17
|
+
rm -f "$f"
|
18
|
+
exit "$code"
|
19
|
+
fi
|
20
|
+
)
|
21
|
+
code=$?
|
22
|
+
if [ "$code" != 0 ]; then
|
23
|
+
return "$code"
|
24
|
+
fi
|
25
|
+
d=$(<"$f")
|
26
|
+
rm -f "$f"
|
27
|
+
cd "$d"
|
28
|
+
}
|
data/bin/rtfm
ADDED
@@ -0,0 +1,1240 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
# SCRIPT INFO
|
5
|
+
# Name: RTFM - Ruby Terminal File Manager
|
6
|
+
# Language: Pure Ruby, best viewed in VIM
|
7
|
+
# Author: Geir Isene <g@isene.com>
|
8
|
+
# Web_site: http://isene.com/
|
9
|
+
# Github: https://github.com/isene/RTFM
|
10
|
+
# License: I release all copyright claims. This code is in the public domain.
|
11
|
+
# Permission is granted to use, copy modify, distribute, and sell
|
12
|
+
# this software for any purpose. I make no guarantee about the
|
13
|
+
# suitability of this software for any purpose and I am not liable
|
14
|
+
# for any damages resulting from its use. Further, I am under no
|
15
|
+
# obligation to maintain or extend this software. It is provided
|
16
|
+
# on an 'as is' basis without any expressed or implied warranty.
|
17
|
+
|
18
|
+
# PRELIMINARIES
|
19
|
+
@help = <<HELPTEXT
|
20
|
+
RTFM - Ruby Terminal File Manager (https://github.com/isene/RTFM)
|
21
|
+
|
22
|
+
BASIC KEYS
|
23
|
+
? = Show this help text
|
24
|
+
r = Refresh RTFM (recreates all windows. Use on terminal resize or when there is garbage somewhere)
|
25
|
+
R = Reload configuration (~/.rtfm.conf)
|
26
|
+
W = Write parameters to ~/.rtfm.conf (@lsall, @lslong, @border, @width, @preview, @tagged, @marks)
|
27
|
+
q = Quit
|
28
|
+
Q = QUIT (without writing changes to the config file)
|
29
|
+
|
30
|
+
MOTION
|
31
|
+
DOWN = Go one item down in left pane (rounds to top)
|
32
|
+
UP = Go one item up in left pane (rounds to bottom)
|
33
|
+
LEFT = Go up one directory level
|
34
|
+
RIGHT = Enter directory or open file (using run-mailcap or xdg-open)
|
35
|
+
Use the key 'x' to force open using xdg-open (or run-mailcap) - used for opening html files
|
36
|
+
in a browser rather than editing the file in your text editor
|
37
|
+
PgDown = Go one page down in left pane
|
38
|
+
PgUp = Go one page up in left pane
|
39
|
+
END = Go to last item in left pane
|
40
|
+
HOME = Go to first item in left pane
|
41
|
+
|
42
|
+
JUMPING AND MARKS
|
43
|
+
m = Mark current dir (persistent). Next letter is the name of the mark [a-zA-Z']
|
44
|
+
The special mark "'" jumps to the last directory (makes toggling dirs easy)
|
45
|
+
Press '-' and a letter to delete that mark
|
46
|
+
M = Show marked items in right pane
|
47
|
+
' = Jump to mark (next letter is the name of the mark [a-zA-Z'])
|
48
|
+
h = Jump to Home directory
|
49
|
+
f = Follow symlink to the directory where the target resides
|
50
|
+
L = Start 'locate' search for files, then use '#' to jump to desired line/directory
|
51
|
+
|
52
|
+
TAGGING
|
53
|
+
t = Tag item (toggles)
|
54
|
+
Ctrl-t = Add items matching a pattern to list of tagged items (Ctrl-t and then . will tag all items)
|
55
|
+
T = Show currently tagged items in right pane
|
56
|
+
u = Untag all tagged items
|
57
|
+
|
58
|
+
MANIPULATE ITEMS
|
59
|
+
p = Put (copy) tagged items here
|
60
|
+
P = PUT (move) tagged items here
|
61
|
+
s = Create symlink to tagged items here
|
62
|
+
d = Delete selected item and tagged items. Press 'd' to confirm
|
63
|
+
c = Change/rename selected (adds command to bottom window)
|
64
|
+
|
65
|
+
DIRECTORY VIEWS
|
66
|
+
a = Show all (also hidden) items
|
67
|
+
l = Show long info per item (show item attributes)
|
68
|
+
o = Change the order/sorting of directories (circular toggle)
|
69
|
+
i = Invert/reverse the sorting
|
70
|
+
O = Show the Ordering in the bottom window (the full ls command)
|
71
|
+
G = Show git status for current directory
|
72
|
+
H = Do a cryptographic hash of the current directory with subdirs
|
73
|
+
If a previous hash was made, compare and report if there has been any change
|
74
|
+
|
75
|
+
RIGHT PANE
|
76
|
+
ENTER = Refresh the right pane
|
77
|
+
TAB = Next page of the preview (if doc long and ∇ in the bottom right)
|
78
|
+
S-TAB = Previous page (if you have moved down the document first - ∆ in the top right)
|
79
|
+
w = Change the width of the left/right panes (left pane ⇒ ⅓ ⇒ ¼ ⇒ ⅕ ⇒ ⅙ ⇒ ½ ⇒ ⅓)
|
80
|
+
- = Toggle preview in right pane (turn it off for faster traversing of directories)
|
81
|
+
|
82
|
+
ADDITINAL COMMANDS
|
83
|
+
/ = Enter search string in bottom window to highlight matching items
|
84
|
+
: = Enter "command mode" in bottom window
|
85
|
+
; = Show command history in right pane
|
86
|
+
y = Copy path of selected item to primary selection (for pasting with middle mouse button)
|
87
|
+
Y = Copy path of selected item to clipboard
|
88
|
+
|
89
|
+
COPYRIGHT: Geir Isene, 2020-1. No rights reserved. See http://isene.com for more.
|
90
|
+
HELPTEXT
|
91
|
+
begin # BASIC SETUP
|
92
|
+
require 'fileutils'
|
93
|
+
require 'io/console'
|
94
|
+
require 'date'
|
95
|
+
require 'curses'
|
96
|
+
include Curses
|
97
|
+
|
98
|
+
def cmd?(command)
|
99
|
+
system("which #{command} > /dev/null 2>&1")
|
100
|
+
end
|
101
|
+
if cmd?('/usr/lib/w3m/w3mimgdisplay')
|
102
|
+
@w3mimgdisplay = "/usr/lib/w3m/w3mimgdisplay"
|
103
|
+
@showimage = true
|
104
|
+
else
|
105
|
+
@showimage = false
|
106
|
+
end
|
107
|
+
@showimage = false unless (cmd?('xwininfo') and cmd?('xdotool'))
|
108
|
+
|
109
|
+
STDIN.set_encoding(Encoding::UTF_8) # Set encoding for STDIN
|
110
|
+
LScolors = `echo $LS_COLORS` # Import LS_COLORS
|
111
|
+
|
112
|
+
## Curses setup
|
113
|
+
Curses.init_screen
|
114
|
+
Curses.start_color
|
115
|
+
Curses.curs_set(0)
|
116
|
+
Curses.noecho
|
117
|
+
Curses.cbreak
|
118
|
+
Curses.stdscr.keypad = true
|
119
|
+
|
120
|
+
# INITIALIZE VARIABLES
|
121
|
+
## These can be set by user in .rtfm.conf
|
122
|
+
@lsbase = "--group-directories-first" # Basic ls setup
|
123
|
+
@lslong = false # Set short form ls (toggled by pressing "l")
|
124
|
+
@lsall = "" # Set "ls -a" to false (toggled by pressing "a" - sets it to "-a")
|
125
|
+
@lsorder = "" # Change the order/sorting by pressing 'o' (circular toggle)
|
126
|
+
@lsinvert = "" # Set to "-r" to reverse/invert sorting order
|
127
|
+
@lsuser = "" # Set this variable in .rtfm.conf to any 'ls' switch you want to customize directory listings
|
128
|
+
@width = 3 # Set width of the left pane to the default ⅓ of the terminal width
|
129
|
+
@history = [] # Initialize the command line history array
|
130
|
+
@border = false
|
131
|
+
@preview = true
|
132
|
+
@runmailcap = false # Set to 'true' in .rtfm.conf if you want to use run-mailcap instead of xdg-open
|
133
|
+
## These are automatically written on exit
|
134
|
+
@marks = {} # Initialize (book)marks hash
|
135
|
+
@hash = {} # Initialize the sha directory hashing
|
136
|
+
@tagged = [] # Initialize the tagged array - for collecting all tagged items
|
137
|
+
## These should not be set by user in .rtfm.conf
|
138
|
+
@directory = {} # Initialize the directory hash for remembering directories visited
|
139
|
+
@searched = "" # Initialize the active searched for items
|
140
|
+
@index = 0 # Set chosen item to first on startup
|
141
|
+
@marks["'"] = Dir.pwd
|
142
|
+
## File type recognizers
|
143
|
+
@imagefile = /\.jpg$|\.JPG$|\.jpeg$|\.png$|\.bmp$|\.gif$|\.tif$|\.tiff$/
|
144
|
+
@pptfile = /\.ppt$/
|
145
|
+
@xlsfile = /\.xls$/
|
146
|
+
@docfile = /\.doc$/
|
147
|
+
@docxfile = /\.docx$/
|
148
|
+
@xlsxfile = /\.xlsx$/
|
149
|
+
@pptxfile = /\.pptx$/
|
150
|
+
@oolofile = /\.odt$|\.odc$|\.odp$|\.odg$/
|
151
|
+
@pdffile = /\.pdf$|\.ps$/
|
152
|
+
## Get variables from config file (written back to .rtf.conf upon exit via 'q')
|
153
|
+
if File.exist?(Dir.home+'/.rtfm.conf')
|
154
|
+
load(Dir.home+'/.rtfm.conf')
|
155
|
+
end
|
156
|
+
end
|
157
|
+
class Curses::Window # CLASS EXTENSION
|
158
|
+
attr_accessor :fg, :bg, :attr, :text, :update, :pager, :pager_more, :pager_cmd, :locate, :nohistory
|
159
|
+
# General extensions (see https://github.com/isene/Ruby-Curses-Class-Extension)
|
160
|
+
def clr
|
161
|
+
self.setpos(0, 0)
|
162
|
+
self.maxy.times {self.deleteln()}
|
163
|
+
self.refresh
|
164
|
+
self.setpos(0, 0)
|
165
|
+
end
|
166
|
+
def fill # Fill window with color as set by :bg
|
167
|
+
self.setpos(0, 0)
|
168
|
+
self.bg = 0 if self.bg == nil
|
169
|
+
self.fg = 255 if self.fg == nil
|
170
|
+
init_pair(self.fg, self.fg, self.bg)
|
171
|
+
blank = " " * self.maxx
|
172
|
+
self.maxy.times {self.attron(color_pair(self.fg)) {self << blank}}
|
173
|
+
self.refresh
|
174
|
+
self.setpos(0, 0)
|
175
|
+
end
|
176
|
+
def write # Write context of :text to window with attributes :attr
|
177
|
+
self.bg = 0 if self.bg == nil
|
178
|
+
self.fg = 255 if self.fg == nil
|
179
|
+
init_pair(self.fg, self.fg, self.bg)
|
180
|
+
self.attr = 0 if self.attr == nil
|
181
|
+
self.attron(color_pair(self.fg) | self.attr) { self << self.text }
|
182
|
+
self.refresh
|
183
|
+
end
|
184
|
+
# RTFM specific extensions
|
185
|
+
end
|
186
|
+
# GENERIC FUNCTIONS
|
187
|
+
def get_ls_color(type) # GET THE COLOR FOR THE FILETYPE FROM IMPORTED LS_COLORS
|
188
|
+
bold = 0
|
189
|
+
begin
|
190
|
+
color = LScolors.match(/#{type}=\d*;\d*;(\d*)/)[1]
|
191
|
+
bold = 1 if LScolors.match(/#{type}=\d*;\d*;\d*;1/)
|
192
|
+
rescue
|
193
|
+
color = 7 # Default color
|
194
|
+
end
|
195
|
+
return color.to_i, bold
|
196
|
+
end
|
197
|
+
def color_parse(input) # PARSE ANSI COLOR SEQUENCES
|
198
|
+
input.gsub!( /\e\[\d;38;5;(\d+)m/, '%-%\1%-%')
|
199
|
+
input.gsub!( /\e\[38;5;(\d+)m/, '%-%\1%-%')
|
200
|
+
input.gsub!( /\e\[0m/, "\t")
|
201
|
+
color_array = input.split("%-%")
|
202
|
+
color_array = color_array.drop(1)
|
203
|
+
output = color_array.each_slice(2).to_a
|
204
|
+
return output
|
205
|
+
end
|
206
|
+
def getchr # PROCESS KEY PRESSES
|
207
|
+
# Note: Curses.getch blanks out @w_t
|
208
|
+
# @w_l.getch makes Curses::KEY_DOWN etc not work
|
209
|
+
# Therefore resorting to the generic method
|
210
|
+
c = STDIN.getch(min: 0, time: 5)
|
211
|
+
case c
|
212
|
+
when "\e" # ANSI escape sequences
|
213
|
+
case $stdin.getc
|
214
|
+
when '[' # CSI
|
215
|
+
case $stdin.getc
|
216
|
+
when 'A' then chr = "UP"
|
217
|
+
when 'B' then chr = "DOWN"
|
218
|
+
when 'C' then chr = "RIGHT"
|
219
|
+
when 'D' then chr = "LEFT"
|
220
|
+
when 'Z' then chr = "S-TAB"
|
221
|
+
when '2' then chr = "INS" ; STDIN.getc
|
222
|
+
when '3' then chr = "DEL" ; STDIN.getc
|
223
|
+
when '5' then chr = "PgUP" ; STDIN.getc
|
224
|
+
when '6' then chr = "PgDOWN" ; STDIN.getc
|
225
|
+
when '7' then chr = "HOME" ; STDIN.getc
|
226
|
+
when '8' then chr = "END" ; STDIN.getc
|
227
|
+
end
|
228
|
+
end
|
229
|
+
when "", "" then chr = "BACK"
|
230
|
+
when "" then chr = "WBACK"
|
231
|
+
when "" then chr = "LDEL"
|
232
|
+
when "" then chr = "C-T"
|
233
|
+
when "\r" then chr = "ENTER"
|
234
|
+
when "\t" then chr = "TAB"
|
235
|
+
when /./ then chr = c
|
236
|
+
end
|
237
|
+
return chr
|
238
|
+
end
|
239
|
+
def main_getkey # GET KEY FROM USER
|
240
|
+
dir = Dir.pwd
|
241
|
+
chr = getchr
|
242
|
+
case chr
|
243
|
+
# BASIC KEYS
|
244
|
+
when '?' # Show helptext in right window
|
245
|
+
w_r_info(@help)
|
246
|
+
@w_b.update = true
|
247
|
+
when 'r' # Refresh all windows
|
248
|
+
@break = true
|
249
|
+
when 'R' # Reload .rtfm.conf
|
250
|
+
if File.exist?(Dir.home+'/.rtfm.conf')
|
251
|
+
load(Dir.home+'/.rtfm.conf')
|
252
|
+
end
|
253
|
+
w_b_info(" Config reloaded")
|
254
|
+
when 'W' # Write all parameters to .rtfm.conf
|
255
|
+
@write_conf_all = true
|
256
|
+
conf_write
|
257
|
+
@w_b.update = true
|
258
|
+
when 'q' # Exit
|
259
|
+
@write_conf = true
|
260
|
+
exit 0
|
261
|
+
when 'Q' # Exit without writing to .rtfm.conf
|
262
|
+
system("printf \"\033]0;#{Dir.pwd}\007\"")
|
263
|
+
@write_conf = false
|
264
|
+
exit 0
|
265
|
+
# MOTION
|
266
|
+
when 'DOWN'
|
267
|
+
var_resets
|
268
|
+
@index = @index >= @max_index ? @min_index : @index + 1
|
269
|
+
@w_r.update = true
|
270
|
+
@w_b.update = true
|
271
|
+
when 'UP'
|
272
|
+
var_resets
|
273
|
+
@index = @index <= @min_index ? @max_index : @index - 1
|
274
|
+
@w_r.update = true
|
275
|
+
@w_b.update = true
|
276
|
+
when 'LEFT'
|
277
|
+
var_resets
|
278
|
+
cur_dir = Dir.pwd
|
279
|
+
@directory[Dir.pwd] = @selected # Store this directory before leaving
|
280
|
+
@marks["'"] = Dir.pwd
|
281
|
+
Dir.chdir("..")
|
282
|
+
@directory[Dir.pwd] = File.basename(cur_dir) unless @directory.key?(Dir.pwd)
|
283
|
+
@w_r.update = true
|
284
|
+
@w_b.update = true
|
285
|
+
when 'RIGHT'
|
286
|
+
var_resets
|
287
|
+
@directory[Dir.pwd] = @selected # Store this directory before leaving
|
288
|
+
@marks["'"] = Dir.pwd
|
289
|
+
open_selected()
|
290
|
+
@w_r.update = true
|
291
|
+
@w_b.update = true
|
292
|
+
when 'x' # Force open with file opener (used to open HTML files in browser)
|
293
|
+
var_resets
|
294
|
+
@directory[Dir.pwd] = @selected # Store this directory before leaving
|
295
|
+
@marks["'"] = Dir.pwd
|
296
|
+
open_selected(true)
|
297
|
+
@w_r.update = true
|
298
|
+
@w_b.update = true
|
299
|
+
when 'PgDOWN'
|
300
|
+
var_resets
|
301
|
+
@index += @w_l.maxy - 2
|
302
|
+
@index = @max_index if @index > @max_index
|
303
|
+
@w_r.update = true
|
304
|
+
@w_b.update = true
|
305
|
+
when 'PgUP'
|
306
|
+
var_resets
|
307
|
+
@index -= @w_l.maxy - 2
|
308
|
+
@index = @min_index if @index < @min_index
|
309
|
+
@w_r.update = true
|
310
|
+
@w_b.update = true
|
311
|
+
when 'END'
|
312
|
+
var_resets
|
313
|
+
@index = @max_index
|
314
|
+
@w_r.update = true
|
315
|
+
@w_b.update = true
|
316
|
+
when 'HOME'
|
317
|
+
var_resets
|
318
|
+
@index = @min_index
|
319
|
+
@w_r.update = true
|
320
|
+
@w_b.update = true
|
321
|
+
# JUMPING AND MARKS
|
322
|
+
when 'm' # Set mark
|
323
|
+
marks_info
|
324
|
+
m = STDIN.getc
|
325
|
+
if m.match(/[\w']/)
|
326
|
+
@marks[m] = Dir.pwd
|
327
|
+
elsif m == "-"
|
328
|
+
r = STDIN.getc
|
329
|
+
@marks.delete(r)
|
330
|
+
end
|
331
|
+
marks_info
|
332
|
+
@w_r.update = false
|
333
|
+
@w_b.update = true
|
334
|
+
when 'M' # Show marks
|
335
|
+
@marks = @marks.sort.to_h
|
336
|
+
marks_info
|
337
|
+
@w_r.update = false
|
338
|
+
@w_b.update = true
|
339
|
+
when "'" # Jump to mark
|
340
|
+
marks_info
|
341
|
+
m = STDIN.getc
|
342
|
+
if m.match(/[\w']/) and @marks[m]
|
343
|
+
var_resets
|
344
|
+
@directory[Dir.pwd] = @selected # Store this directory before leaving
|
345
|
+
dir_before = Dir.pwd
|
346
|
+
begin
|
347
|
+
Dir.chdir(@marks[m])
|
348
|
+
rescue
|
349
|
+
w_b_info(" No such directory")
|
350
|
+
end
|
351
|
+
@marks["'"] = dir_before
|
352
|
+
end
|
353
|
+
@w_r.update = true
|
354
|
+
@w_b.update = true
|
355
|
+
when 'h' # Go to home dir
|
356
|
+
var_resets
|
357
|
+
@directory[Dir.pwd] = @selected # Store this directory before leaving
|
358
|
+
@marks["'"] = Dir.pwd
|
359
|
+
Dir.chdir
|
360
|
+
@w_r.update = true
|
361
|
+
@w_b.update = true
|
362
|
+
when 'f' # Follow symlink
|
363
|
+
@directory[Dir.pwd] = @selected # Store this directory before leaving
|
364
|
+
@marks["'"] = Dir.pwd
|
365
|
+
if File.symlink?(@selected)
|
366
|
+
Dir.chdir(File.dirname(File.readlink(@selected)))
|
367
|
+
end
|
368
|
+
@w_b.update = true
|
369
|
+
when 'L' # Run 'locate' and let user jump to a result (by '#')
|
370
|
+
cmd = w_b_getstr(": ", "locate ")
|
371
|
+
w_b_exec(cmd)
|
372
|
+
@w_r.locate = true
|
373
|
+
@w_b.update = true
|
374
|
+
when '#' # Jump to the line number in list of matches to 'locate'
|
375
|
+
if @w_r.locate
|
376
|
+
jumpnr = w_b_getstr("# ", "").to_i
|
377
|
+
jumpline = @w_r.text.lines[jumpnr - 1]
|
378
|
+
jumpdir = jumpline[/\/[^\e]*/]
|
379
|
+
unless Dir.exist?(jumpdir)
|
380
|
+
@searched = File.basename(jumpdir)
|
381
|
+
jumpdir = File.dirname(jumpdir)
|
382
|
+
end
|
383
|
+
@directory[Dir.pwd] = @selected # Store this directory before leaving
|
384
|
+
@marks["'"] = Dir.pwd
|
385
|
+
Dir.chdir(jumpdir)
|
386
|
+
@w_r.pager = 0
|
387
|
+
end
|
388
|
+
@w_b.update = true
|
389
|
+
# TAGGING
|
390
|
+
when 't' # Add item to tagged list
|
391
|
+
item = "\"#{Dir.pwd}/#{@selected}\""
|
392
|
+
if @tagged.include?(item)
|
393
|
+
@tagged.delete(item)
|
394
|
+
else
|
395
|
+
@tagged.push(item)
|
396
|
+
end
|
397
|
+
@index += 1
|
398
|
+
@w_r.update = true
|
399
|
+
@w_b.update = true
|
400
|
+
when 'C-T' # Tag items matching a pettern
|
401
|
+
@w_b.nohistory = true
|
402
|
+
@tag = w_b_getstr("~ ", "")
|
403
|
+
@w_r.update = true
|
404
|
+
@w_b.update = true
|
405
|
+
when 'T' # Show tagged list
|
406
|
+
tagged_info
|
407
|
+
@w_r.update = false
|
408
|
+
@w_b.update = true
|
409
|
+
when 'u' # Clear tagged list
|
410
|
+
@tagged = []
|
411
|
+
tagged_info
|
412
|
+
@w_r.update = false
|
413
|
+
@w_b.update = true
|
414
|
+
# MANIPULATE ITEMS
|
415
|
+
when 'p' # Copy tagged items here
|
416
|
+
copy_move_link("copy")
|
417
|
+
@w_r.update = true
|
418
|
+
@w_b.update = true
|
419
|
+
when 'P' # Move tagged items here
|
420
|
+
copy_move_link("move")
|
421
|
+
@w_r.update = true
|
422
|
+
@w_b.update = true
|
423
|
+
when 's' # Create symlink to tagged items here
|
424
|
+
copy_move_link("link")
|
425
|
+
@w_r.update = true
|
426
|
+
@w_b.update = true
|
427
|
+
when 'd' # Delete items tagged and @selected
|
428
|
+
tagged_info
|
429
|
+
w_b_info(" Delete selected and tagged? (press 'd' again to delete)")
|
430
|
+
begin
|
431
|
+
@tagged.push("\"#{Dir.pwd}/#{@selected}\"")
|
432
|
+
@tagged.uniq!
|
433
|
+
deletes = @tagged.join(" ")
|
434
|
+
`rm -rf #{deletes} 2>/dev/null` if STDIN.getc == 'd'
|
435
|
+
items_number = @tagged.length
|
436
|
+
@tagged = []
|
437
|
+
w_b_info("Deleted #{items_number} items: #{deletes}")
|
438
|
+
@w_r.update = true
|
439
|
+
rescue StandardError => err
|
440
|
+
w_b_info(err.to_s)
|
441
|
+
end
|
442
|
+
when 'c' # Change/rename selected @selected
|
443
|
+
cmd = w_b_getstr(": ", "mv \"#{@selected}\" \"#{@selected}\"")
|
444
|
+
begin
|
445
|
+
w_b_exec(cmd + " 2>/dev/null")
|
446
|
+
rescue StandardError => err
|
447
|
+
w_b_info(err.to_s)
|
448
|
+
end
|
449
|
+
@w_r.update = true
|
450
|
+
# DIRECTORY VIEWS
|
451
|
+
when 'a' # Show all items
|
452
|
+
@lsall == "" ? @lsall = "-a" : @lsall = ""
|
453
|
+
@w_r.update = true
|
454
|
+
@w_b.update = true
|
455
|
+
when 'l' # Show long info for all items
|
456
|
+
@lslong = !@lslong
|
457
|
+
@w_r.update = true
|
458
|
+
@w_b.update = true
|
459
|
+
when 'o' # Circular toggle the order/sorting of directory views
|
460
|
+
case @lsorder
|
461
|
+
when ""
|
462
|
+
@lsorder = "-S"
|
463
|
+
w_b_info(" Sorting by size, largest first")
|
464
|
+
when "-S"
|
465
|
+
@lsorder = "-t"
|
466
|
+
w_b_info(" Sorting by modification time")
|
467
|
+
when "-t"
|
468
|
+
@lsorder = "-X"
|
469
|
+
w_b_info(" Sorting by extension (alphabetically)")
|
470
|
+
when "-X"
|
471
|
+
@lsorder = ""
|
472
|
+
w_b_info(" Normal sorting")
|
473
|
+
end
|
474
|
+
@w_r.update = true
|
475
|
+
@orderchange = true
|
476
|
+
when 'i' # Invert the order/sorting of directory views
|
477
|
+
case @lsinvert
|
478
|
+
when ""
|
479
|
+
@lsinvert = "-r"
|
480
|
+
w_b_info(" Sorting inverted")
|
481
|
+
when "-r"
|
482
|
+
@lsinvert = ""
|
483
|
+
w_b_info(" Sorting NOT inverted")
|
484
|
+
end
|
485
|
+
@w_r.update = true
|
486
|
+
@orderchange = true
|
487
|
+
when 'O' # Show the Ordering in the bottom window (the full ls command)
|
488
|
+
w_b_info(" Full 'ls' command: ls <@s> #{@lsbase} #{@lsall} #{@lsorder} #{@lsinvert} #{@lsuser}")
|
489
|
+
when 'G' # Git status for selected item or current dir
|
490
|
+
if File.exist?(".git")
|
491
|
+
w_r_info(`git status 2>/dev/null`)
|
492
|
+
else
|
493
|
+
w_r_info("This is not a git repository.")
|
494
|
+
end
|
495
|
+
@w_r.update = false
|
496
|
+
@w_b.update = true
|
497
|
+
when 'H' # Compare with previous hash status or write hash status if no existing hash
|
498
|
+
hashcmd = "\(find #{Dir.pwd} -type f -print0 | sort -z | xargs -0 sha1sum; find #{Dir.pwd}"\
|
499
|
+
" \\( -type f -o -type d \\) -print0 | sort -z | xargs -0 stat -c '%n %a'\) | sha1sum | cut -c -40"
|
500
|
+
begin
|
501
|
+
hashdir = `#{hashcmd}`.chomp
|
502
|
+
rescue StandardError => e
|
503
|
+
w_r_info("Error: #{e.inspect}")
|
504
|
+
end
|
505
|
+
hashtime = DateTime.now.strftime "%Y-%m-%d %H:%M"
|
506
|
+
if @hash.include?(Dir.pwd)
|
507
|
+
if @hash[Dir.pwd][1] == hashdir
|
508
|
+
w_b_info(" Hash for #{Dir.pwd} has NOT changed since #{hashtime} (#{hashdir})")
|
509
|
+
else
|
510
|
+
w_b_info(" Hash for #{Dir.pwd} has CHANGED since #{hashtime} (#{@hash[Dir.pwd][1]} -> #{hashdir})")
|
511
|
+
@hash[Dir.pwd] = [hashtime, hashdir]
|
512
|
+
end
|
513
|
+
else
|
514
|
+
hashtime = DateTime.now.strftime "%Y-%m-%d %H:%M"
|
515
|
+
@hash[Dir.pwd] = [hashtime, hashdir]
|
516
|
+
w_b_info(" New hash for #{Dir.pwd}: #{hashtime}: #{hashdir}")
|
517
|
+
end
|
518
|
+
@w_r.update = true
|
519
|
+
@w_b.update = false
|
520
|
+
# RIGHT PANE
|
521
|
+
when 'ENTER' # Refresh right pane
|
522
|
+
@w_r.clr # First clear the window, then clear any previously showing image
|
523
|
+
image_show("clear") if @image; @image = false
|
524
|
+
@w_r.update = true
|
525
|
+
@w_b.update = true
|
526
|
+
when 'TAB' # Start paging
|
527
|
+
if @w_r.pager == 1 and @w_r.pager_cmd != ""
|
528
|
+
@w_r.text = `#{@w_r.pager_cmd} 2>/dev/null`
|
529
|
+
end
|
530
|
+
if @w_r.pager_more
|
531
|
+
@w_r.pager += 1
|
532
|
+
pager_show
|
533
|
+
end
|
534
|
+
@w_b.update = true
|
535
|
+
when 'S-TAB' # Up one page
|
536
|
+
if @w_r.pager > 1
|
537
|
+
@w_r.pager -= 1
|
538
|
+
pager_show
|
539
|
+
end
|
540
|
+
@w_b.update = true
|
541
|
+
when 'w' # Change width of left/right panes
|
542
|
+
@width += 1
|
543
|
+
@width = 2 if @width == 7
|
544
|
+
@break = true
|
545
|
+
@w_b.update = true
|
546
|
+
when '-'
|
547
|
+
@preview = !@preview
|
548
|
+
@break = true
|
549
|
+
@w_b.update = true
|
550
|
+
# ADDITIONAL COMMANDS
|
551
|
+
when '/' # Get search string to mark items that match #
|
552
|
+
@w_b.nohistory = true
|
553
|
+
@searched = w_b_getstr("/ ", "")
|
554
|
+
@w_r.update = true
|
555
|
+
when ':' # Enter "command mode" in the bottom window - tries to execute the given command
|
556
|
+
@w_r.nohistory = false
|
557
|
+
cmd = w_b_getstr(": ", "")
|
558
|
+
w_b_exec(cmd)
|
559
|
+
when ';' # Show command history
|
560
|
+
w_r_info("Command history (latest on top):\n\n" + @history.join("\n"))
|
561
|
+
@w_b.update = true
|
562
|
+
when 'y', 'Y'
|
563
|
+
if @selected == nil
|
564
|
+
w_b_info(" No selected item path to copy")
|
565
|
+
else
|
566
|
+
path = Dir.pwd + "/" + @selected
|
567
|
+
if chr == 'Y'
|
568
|
+
clip = "xclip -selection clipboard"
|
569
|
+
w_b_info(" Path copied to clipboard")
|
570
|
+
else
|
571
|
+
clip = "xclip"
|
572
|
+
w_b_info(" Path copied to primary selection (paste with middle mouse button)")
|
573
|
+
end
|
574
|
+
system("echo -n '#{path}' | #{clip}")
|
575
|
+
end
|
576
|
+
when '@' # Enter "Ruby debug"
|
577
|
+
@w_b.nohistory = true
|
578
|
+
cmd = w_b_getstr("◆ ", "")
|
579
|
+
@w_r.clr
|
580
|
+
@w_r << "Command: #{cmd}\n\n"
|
581
|
+
@w_r.refresh
|
582
|
+
begin
|
583
|
+
eval(cmd)
|
584
|
+
rescue StandardError => e
|
585
|
+
w_r_info("Error: #{e.inspect}")
|
586
|
+
end
|
587
|
+
@w_r.update = false
|
588
|
+
end
|
589
|
+
if @w_r.update == true
|
590
|
+
@w_r.locate = false
|
591
|
+
@w_r.pager = 0
|
592
|
+
@w_r.pager_more = false
|
593
|
+
end
|
594
|
+
@w_r.update = true if dir != Dir.pwd
|
595
|
+
end
|
596
|
+
def conf_write
|
597
|
+
if File.exist?(Dir.home+'/.rtfm.conf')
|
598
|
+
conf = File.read(Dir.home+'/.rtfm.conf')
|
599
|
+
else
|
600
|
+
conf = ""
|
601
|
+
end
|
602
|
+
conf.sub!(/^@marks.*{.*}\n/, "")
|
603
|
+
conf += "@marks = #{@marks}\n"
|
604
|
+
conf.sub!(/^@hash.*{.*}\n/, "")
|
605
|
+
conf += "@hash = #{@hash}\n"
|
606
|
+
conf.sub!(/^@tagged.*\[.*\]\n/, "")
|
607
|
+
conf += "@tagged = #{@tagged}\n"
|
608
|
+
if @write_conf_all
|
609
|
+
conf.sub!(/^@lslong.*\n/, "")
|
610
|
+
conf += "@lslong = #{@lslong}\n"
|
611
|
+
conf.sub!(/^@lsall.*\n/, "")
|
612
|
+
conf += "@lsall = \"#{@lsall}\"\n"
|
613
|
+
conf.sub!(/^@width.*\n/, "")
|
614
|
+
conf += "@width = #{@width}\n"
|
615
|
+
conf.sub!(/^@border.*\n/, "")
|
616
|
+
conf += "@border = #{@border}\n"
|
617
|
+
conf.sub!(/^@preview.*\n/, "")
|
618
|
+
conf += "@preview = #{@preview}\n"
|
619
|
+
w_r_info("Press W again to write this to .rtfm.conf:\n\n" + conf)
|
620
|
+
if getchr == 'W'
|
621
|
+
w_b_info(" Parameters written to .rtfm.conf")
|
622
|
+
@w_r.update = true
|
623
|
+
else
|
624
|
+
w_b_info(" Config NOT updated")
|
625
|
+
@w_r.update = true
|
626
|
+
return
|
627
|
+
end
|
628
|
+
end
|
629
|
+
File.write(Dir.home+'/.rtfm.conf', conf)
|
630
|
+
end
|
631
|
+
# TOP WINDOW FUNCTIONS
|
632
|
+
def w_t_info # SHOW INFO IN @w_t
|
633
|
+
text = " " + ENV['USER'].to_s + "@" + `hostname 2>/dev/null`.to_s.chop + ": " + Dir.pwd + "/"
|
634
|
+
unless @selected == nil
|
635
|
+
text += @selected
|
636
|
+
text += " → #{File.readlink(@selected)}" if File.symlink?(@selected)
|
637
|
+
end
|
638
|
+
begin
|
639
|
+
text += " (#{@fspes[@index]})"
|
640
|
+
rescue
|
641
|
+
end
|
642
|
+
begin
|
643
|
+
if @selected.match(@imagefile)
|
644
|
+
text += `identify #{@selected_safe} | awk '{printf " [%s %s %s %s] ", $3,$2,$5,$6}' 2>/dev/null` if cmd?('identify')
|
645
|
+
elsif @selected.match(@pdffile)
|
646
|
+
info = `pdfinfo #{@selected_safe} 2>/dev/null`
|
647
|
+
text += " [" + info.match(/Pages:.*?(\d+)/)[1]
|
648
|
+
text += " " + info.match(/Page size:.*\((.*)\)/)[1] + " pages] "
|
649
|
+
end
|
650
|
+
rescue
|
651
|
+
end
|
652
|
+
if Dir.exist?(@selected.to_s)
|
653
|
+
begin
|
654
|
+
text += " [" + Dir.glob(@selected+"/*").count.to_s + " " + Dir.children(@selected).count.to_s + "]"
|
655
|
+
rescue
|
656
|
+
text += " [Denied]"
|
657
|
+
end
|
658
|
+
end
|
659
|
+
text = text[1..(@w_t.maxx - 3)] + "…" if text.length + 3 > @w_t.maxx
|
660
|
+
text += " " * (@w_t.maxx - text.length) if text.length < @w_t.maxx
|
661
|
+
@w_t.clr
|
662
|
+
@w_t.text = text
|
663
|
+
@w_t.write
|
664
|
+
end
|
665
|
+
# LEFT WINDOW FUNCTIONS
|
666
|
+
def list_dir(active) # LIST CONTENT OF A DIRECTORY (BOTH active AND RIGHT WINDOWS)
|
667
|
+
ix = 0; t = 0
|
668
|
+
if active
|
669
|
+
win = @w_l
|
670
|
+
ix = @index - @w_l.maxy/2 if @index > @w_l.maxy/2 and @files.size > @w_l.maxy - 1
|
671
|
+
else
|
672
|
+
win = @w_r
|
673
|
+
end
|
674
|
+
while ix < @files.size and t < win.maxy do
|
675
|
+
str = @files[ix]
|
676
|
+
active ? str_path = str : str_path = "#{@selected}/#{str}"
|
677
|
+
begin # Add items matching @tag to @tagged
|
678
|
+
if str.match(/#{@tag}/) and @tag != false
|
679
|
+
@tagged.push("\"#{Dir.pwd}/#{str}\"")
|
680
|
+
@tagged.uniq!
|
681
|
+
end
|
682
|
+
rescue
|
683
|
+
end
|
684
|
+
# Determine the filetype of the item
|
685
|
+
ftype = ""
|
686
|
+
ftype = str.match(/\.([^.]*$)/)[1] if str.match?(/\.([^.]*$)/)
|
687
|
+
# Set special filetypes (sequence matters)
|
688
|
+
ftype = "bd" if File.blockdev?(str_path)
|
689
|
+
ftype = "cd" if File.chardev?(str_path)
|
690
|
+
ftype = "pi" if File.pipe?(str_path)
|
691
|
+
ftype = "st" if File.sticky?(str_path)
|
692
|
+
ftype = "so" if File.socket?(str_path)
|
693
|
+
ftype = "ex" if File.executable?(str_path)
|
694
|
+
ftype = "di" if File.directory?(str_path)
|
695
|
+
ftype = "ln" if File.symlink?(str_path)
|
696
|
+
begin
|
697
|
+
File.stat(str_path) # Checking if not an orphaned link
|
698
|
+
rescue
|
699
|
+
ftype = "or" # Set to orphant if no link target
|
700
|
+
end
|
701
|
+
fg = 7; bold = 0; bg = 0 # Set default color
|
702
|
+
fg, bold = get_ls_color(ftype) unless ftype == "" # Color from LS_COLORS
|
703
|
+
init_pair(fg, fg, bg)
|
704
|
+
file_marker = color_pair(fg)
|
705
|
+
file_marker = file_marker | Curses::A_BOLD if bold == 1
|
706
|
+
if ix == @index and active
|
707
|
+
str = "∶" + str
|
708
|
+
file_marker = file_marker | Curses::A_UNDERLINE
|
709
|
+
wixy = win.cury
|
710
|
+
else
|
711
|
+
str = " " + str
|
712
|
+
end
|
713
|
+
file_marker = file_marker | Curses::A_REVERSE if @tagged.include?("\"#{Dir.pwd}/#{str_path}\"")
|
714
|
+
file_marker = file_marker | Curses::A_BLINK if str.match(/#{@searched}/) and @searched != ""
|
715
|
+
File.directory?(str_path) ? dir = "/" : dir = ""
|
716
|
+
File.symlink?(str_path) ? link = "@" : link = ""
|
717
|
+
str = @fspes[ix] + " " + str if @lslong
|
718
|
+
if str.length > win.maxx - 4
|
719
|
+
base_name = File.basename(str, ".*")
|
720
|
+
base_length = base_name.length
|
721
|
+
ext_name = File.extname(str)
|
722
|
+
ext_length = ext_name.length
|
723
|
+
nbl = win.maxx - 5 - ext_length # nbl: new_base_length
|
724
|
+
str = base_name[0..nbl] + "…" + ext_name
|
725
|
+
end
|
726
|
+
if !active and ix == win.maxy - 1 # Add indicator of more at bottom @w_r list
|
727
|
+
win << " ..."
|
728
|
+
return
|
729
|
+
end
|
730
|
+
str += link + dir
|
731
|
+
win.attron(file_marker) { win << str } # Implement color/bold to the item
|
732
|
+
win.clrtoeol
|
733
|
+
win << "\n"
|
734
|
+
ix += 1; t += 1
|
735
|
+
end
|
736
|
+
(win.maxy - win.cury).times {win.deleteln()} # Clear to bottom of window
|
737
|
+
if active
|
738
|
+
init_pair(242, 242, 0)
|
739
|
+
if @index > @w_l.maxy/2
|
740
|
+
@w_l.setpos(0, @w_l.maxx - 1)
|
741
|
+
@w_l.attron(color_pair(242) | Curses::A_DIM) { @w_l << "∆" }
|
742
|
+
end
|
743
|
+
if @files.length > @w_l.maxy - 1 and @files.length > @index + @w_l.maxy/2 - 1
|
744
|
+
@w_l.setpos(@w_l.maxy - 2, @w_l.maxx - 1)
|
745
|
+
@w_l.attron(color_pair(242) | Curses::A_DIM) { @w_l << "∇" }
|
746
|
+
end
|
747
|
+
end
|
748
|
+
end
|
749
|
+
def open_selected(html = nil) # OPEN SELECTED ITEM (when pressing RIGHT)
|
750
|
+
if File.directory?(@selected) # Rescue for permission error
|
751
|
+
begin
|
752
|
+
@marks["'"] = Dir.pwd
|
753
|
+
Dir.chdir(@selected)
|
754
|
+
rescue
|
755
|
+
end
|
756
|
+
else
|
757
|
+
begin
|
758
|
+
if File.read(@selected).force_encoding("UTF-8").valid_encoding? and not html
|
759
|
+
system("exec $EDITOR #{@selected_safe}")
|
760
|
+
else
|
761
|
+
if @runmailcap
|
762
|
+
Thread.new { system("run-mailcap #{@selected_safe} 2>/dev/null") }
|
763
|
+
else
|
764
|
+
Thread.new { system("xdg-open #{@selected_safe} 2>/dev/null") }
|
765
|
+
end
|
766
|
+
end
|
767
|
+
@break = true
|
768
|
+
rescue
|
769
|
+
end
|
770
|
+
end
|
771
|
+
end
|
772
|
+
def copy_move_link(type) # COPY OR MOVE TAGGED ITEMS (COPY IF "keep == true")
|
773
|
+
@tagged.uniq!
|
774
|
+
@tagged.each do | item |
|
775
|
+
item = item[1..-2]
|
776
|
+
dest = Dir.pwd
|
777
|
+
dest += "/" + File.basename(item)
|
778
|
+
dest += "1" if File.exist?(dest)
|
779
|
+
while File.exist?(dest)
|
780
|
+
dest = dest.chop + (dest[-1].to_i + 1).to_s
|
781
|
+
end
|
782
|
+
begin
|
783
|
+
case type
|
784
|
+
when "copy"
|
785
|
+
FileUtils.cp_r(item, dest)
|
786
|
+
when "move"
|
787
|
+
FileUtils.mv(item, dest)
|
788
|
+
when "link"
|
789
|
+
FileUtils.ln_s(item, dest)
|
790
|
+
end
|
791
|
+
rescue StandardError => err
|
792
|
+
w_b_info(err.to_s)
|
793
|
+
end
|
794
|
+
end
|
795
|
+
@tagged = []
|
796
|
+
end
|
797
|
+
# RIGHT WINDOW FUNCTIONS
|
798
|
+
def w_r_show # SHOW CONTENTS IN THE RIGHT WINDOW
|
799
|
+
if @w_r.update
|
800
|
+
@w_r.clr # First clear the window, then clear any previously showing image
|
801
|
+
image_show("clear") if @image; @image = false
|
802
|
+
end
|
803
|
+
begin # Determine the specific programs to open/show content
|
804
|
+
if @w_r.pager > 0
|
805
|
+
pager_show
|
806
|
+
elsif File.directory?(@selected)
|
807
|
+
ls_cmd = "ls #{@selected_safe} #{@lsbase} #{@lsall} #{@lsorder} #{@lsinvert} #{@lsuser}"
|
808
|
+
@files = `#{ls_cmd} 2>/dev/null`.split("\n")
|
809
|
+
ls_cmd += %q[ -lhgGH --time-style="long-iso" | awk '{printf "%s%12s%6s%6s%5s", $1,$4,$5,$3,$2 "\n"}']
|
810
|
+
@fspes = `#{ls_cmd} 2>/dev/null`.split("\n").drop(1)
|
811
|
+
list_dir(false)
|
812
|
+
# TEXT
|
813
|
+
elsif File.read(@selected).force_encoding("UTF-8").valid_encoding? and @w_r.pager == 0
|
814
|
+
begin # View the file as text if it is utf-8
|
815
|
+
@w_r.pager_cmd = "batcat -n --color=always #{@selected_safe} 2>/dev/null"
|
816
|
+
@w_r.text = `batcat -n --color=always --line-range :#{@w_r.maxy} #{@selected_safe} 2>/dev/null`
|
817
|
+
pager_start
|
818
|
+
syntax_highlight(@w_r.text)
|
819
|
+
rescue
|
820
|
+
@w_r.pager_cmd = "cat #{@selected_safe} 2>/dev/null"
|
821
|
+
w_r_doc
|
822
|
+
end
|
823
|
+
# PDF
|
824
|
+
elsif @selected.match(@pdffile) and @w_r.pager == 0
|
825
|
+
@w_r.pager_cmd = "pdftotext #{@selected_safe} - 2>/dev/null | less"
|
826
|
+
@w_r.text = `pdftotext -f 1 -l 4 #{@selected_safe} - 2>/dev/null`
|
827
|
+
pager_start
|
828
|
+
@w_r << @w_r.text
|
829
|
+
# OPEN/LIBREOFFICE
|
830
|
+
elsif @selected.match(@oolofile) and @w_r.pager == 0
|
831
|
+
@w_r.pager_cmd = "odt2txt #{@selected_safe} 2>/dev/null"
|
832
|
+
w_r_doc
|
833
|
+
# MS DOCX
|
834
|
+
elsif @selected.match(@docxfile) and @w_r.pager == 0
|
835
|
+
@w_r.pager_cmd = "docx2txt #{@selected_safe} - 2>/dev/null"
|
836
|
+
w_r_doc
|
837
|
+
# MS XLSX
|
838
|
+
elsif @selected.match(@xlsxfile) and @w_r.pager == 0
|
839
|
+
@w_r.pager_cmd = "ssconvert -O 'separator= ' -T Gnumeric_stf:stf_assistant #{@selected_safe} fd://1 2>/dev/null"
|
840
|
+
w_r_doc
|
841
|
+
# MS PPTX
|
842
|
+
elsif @selected.match(@pptxfile) and @w_r.pager == 0
|
843
|
+
@w_r.pager_cmd = %Q[unzip -qc #{@selected_safe} | ruby -e '$stdin.each_line { |i| i.force_encoding("ISO-8859-1").scan(/<a:t>(.+?)<\\/a:t>/).each { |j| puts(j) } }' 2>/dev/null]
|
844
|
+
w_r_doc
|
845
|
+
# MS DOC
|
846
|
+
elsif @selected.match(@docfile) and @w_r.pager == 0
|
847
|
+
@w_r.pager_cmd = "catdoc #{@selected_safe} 2>/dev/null"
|
848
|
+
w_r_doc
|
849
|
+
# MS XLS
|
850
|
+
elsif @selected.match(@xlsfile) and @w_r.pager == 0
|
851
|
+
@w_r.pager_cmd = "xls2csv #{@selected_safe} 2>/dev/null"
|
852
|
+
w_r_doc
|
853
|
+
# MS PPT
|
854
|
+
elsif @selected.match(@pptfile) and @w_r.pager == 0
|
855
|
+
@w_r.pager_cmd = "catppt #{@selected_safe} 2>/dev/null"
|
856
|
+
w_r_doc
|
857
|
+
# IMAGES
|
858
|
+
elsif @selected.match(@imagefile)
|
859
|
+
image_show(@selected_safe)
|
860
|
+
@image = true
|
861
|
+
# VIDEOS (THUMBNAILS)
|
862
|
+
elsif @selected.match(/\.mpg$|\.mpeg$|\.avi$|\.mov$|\.mkv$|\.mp4$/)
|
863
|
+
begin
|
864
|
+
tmpfile = "/tmp/" + File.basename(@selected_safe,".*")
|
865
|
+
`ffmpegthumbnailer -s 1200 -i #{@selected_safe} -o /tmp/rtfm_video_tn.jpg 2>/dev/null`
|
866
|
+
image_show("/tmp/rtfm_video_tn.jpg")
|
867
|
+
@image = true
|
868
|
+
rescue
|
869
|
+
end
|
870
|
+
end
|
871
|
+
rescue
|
872
|
+
end
|
873
|
+
pager_add_markers # Add page markers, up and/or down
|
874
|
+
@w_r.update = false
|
875
|
+
@w_r.refresh
|
876
|
+
end
|
877
|
+
def w_r_doc # GET FULL CONTENT TO PAGE
|
878
|
+
@w_r.text = `#{@w_r.pager_cmd} 2>/dev/null`
|
879
|
+
pager_start
|
880
|
+
@w_r << @w_r.text
|
881
|
+
end
|
882
|
+
def w_r_info(info) # SHOW INFO IN THE RIGHT WINDOW
|
883
|
+
@w_r.text = info
|
884
|
+
@w_r.pager_cmd = ""
|
885
|
+
pager_start
|
886
|
+
pager_show
|
887
|
+
@w_r.update = false
|
888
|
+
image_show("clear") if @image; @image = false
|
889
|
+
end
|
890
|
+
def marks_info # SHOW MARKS IN RIGHT WINDOW
|
891
|
+
info = "Marks:\n"
|
892
|
+
unless @marks.empty?
|
893
|
+
@marks.each do |mark, dir|
|
894
|
+
info += "#{mark} = #{dir}\n"
|
895
|
+
end
|
896
|
+
else
|
897
|
+
info += "(none)"
|
898
|
+
end
|
899
|
+
w_r_info(info)
|
900
|
+
end
|
901
|
+
def tagged_info # SHOW THE LIST OF TAGGED ITEMS IN @w_r
|
902
|
+
info = "Tagged:\n"
|
903
|
+
@tagged.empty? ? info += "(None)" : info += @tagged.join("\n")
|
904
|
+
w_r_info(info)
|
905
|
+
end
|
906
|
+
def syntax_highlight(input) # BATCAT SYNTAX HIGHLIGHTING
|
907
|
+
color_ary = color_parse(input)
|
908
|
+
color_ary.each do | pair |
|
909
|
+
begin
|
910
|
+
fg = pair[0].to_i
|
911
|
+
text = pair[1]
|
912
|
+
text.gsub!(/\t/, '')
|
913
|
+
init_pair(fg, fg, 0)
|
914
|
+
@w_r.attron(color_pair(fg)) { @w_r << text }
|
915
|
+
rescue
|
916
|
+
end
|
917
|
+
end
|
918
|
+
end
|
919
|
+
def image_show(image)# SHOW THE SELECTED IMAGE IN THE RIGHT WINDOW
|
920
|
+
# Pass "clear" to clear the window for previous image
|
921
|
+
return unless @showimage
|
922
|
+
begin
|
923
|
+
terminfo = `xwininfo -id $(xdotool getactivewindow 2>/dev/null) 2>/dev/null`
|
924
|
+
term_w = terminfo.match(/Width: (\d+)/)[1].to_i
|
925
|
+
term_h = terminfo.match(/Height: (\d+)/)[1].to_i
|
926
|
+
char_w = term_w / Curses.cols
|
927
|
+
char_h = term_h / Curses.lines
|
928
|
+
img_x = char_w * (Curses.cols/@width + 1)
|
929
|
+
img_y = char_h * 2
|
930
|
+
img_max_w = char_w * (Curses.cols - Curses.cols/@width - 2)
|
931
|
+
img_max_h = char_h * (Curses.lines - 4)
|
932
|
+
if image == "clear"
|
933
|
+
img_x -= char_w
|
934
|
+
img_max_w += char_w + 2
|
935
|
+
img_max_h += 2
|
936
|
+
`echo "6;#{img_x};#{img_y};#{img_max_w};#{img_max_h};\n4;\n3;" | #{@w3mimgdisplay} 2>/dev/null`
|
937
|
+
else
|
938
|
+
img_w,img_h = `identify -format "%[fx:w]x%[fx:h]" #{image} 2>/dev/null`.split('x')
|
939
|
+
img_w = img_w.to_i
|
940
|
+
img_h = img_h.to_i
|
941
|
+
if img_w > img_max_w
|
942
|
+
img_h = img_h * img_max_w / img_w
|
943
|
+
img_w = img_max_w
|
944
|
+
end
|
945
|
+
if img_h > img_max_h
|
946
|
+
img_w = img_w * img_max_h / img_h
|
947
|
+
img_h = img_max_h
|
948
|
+
end
|
949
|
+
`echo "0;1;#{img_x};#{img_y};#{img_w};#{img_h};;;;;\"#{image}\"\n4;\n3;" | #{@w3mimgdisplay} 2>/dev/null`
|
950
|
+
end
|
951
|
+
rescue
|
952
|
+
@w_r.clr
|
953
|
+
@w_r << "Error showing image"
|
954
|
+
end
|
955
|
+
end
|
956
|
+
def pager_start # START PAGING
|
957
|
+
@w_r.pager = 1
|
958
|
+
if @w_r.text.lines.count > @w_r.maxy - 2
|
959
|
+
@w_r.pager_more = true
|
960
|
+
end
|
961
|
+
end
|
962
|
+
def pager_show # SHOW THE CURRENT PAGE CONTENT
|
963
|
+
@w_r.setpos(0,0)
|
964
|
+
beg_l = (@w_r.pager - 1) * (@w_r.maxy - 5)
|
965
|
+
end_l = beg_l + @w_r.maxy - 2
|
966
|
+
input = @w_r.text.lines[beg_l..end_l].join() + "\n"
|
967
|
+
input.lines.count > @w_r.maxy - 2 ? @w_r.pager_more = true : @w_r.pager_more = false
|
968
|
+
if @w_r.pager_cmd.match(/batcat/)
|
969
|
+
syntax_highlight(input)
|
970
|
+
else
|
971
|
+
@w_r << input
|
972
|
+
end
|
973
|
+
(@w_r.maxy - @w_r.cury).times {@w_r.deleteln()} # Clear to bottom of window
|
974
|
+
pager_add_markers
|
975
|
+
@w_r.refresh
|
976
|
+
end
|
977
|
+
def pager_add_markers # ADD MARKERS TOP/RIGHT & BOTTOM/RIGHT TO SHOW PAGING AS RELEVANT
|
978
|
+
if @w_r.pager > 1
|
979
|
+
@w_r.setpos(0, @w_r.maxx - 2)
|
980
|
+
@w_r << " ∆"
|
981
|
+
end
|
982
|
+
if @w_r.pager_more
|
983
|
+
@w_r.setpos(@w_r.maxy - 1, @w_r.maxx - 2)
|
984
|
+
@w_r << " ∇"
|
985
|
+
end
|
986
|
+
end
|
987
|
+
def var_resets # RESET PAGER VARIABLES
|
988
|
+
@pager = 0
|
989
|
+
@pager_more = false
|
990
|
+
@pager_cmd = ""
|
991
|
+
@info = false
|
992
|
+
end
|
993
|
+
# BOTTOM WINDOW FUNCTIONS
|
994
|
+
def w_b_info(info) # SHOW INFO IN @W_B
|
995
|
+
@w_b.clr
|
996
|
+
info = ": for command (use @s for selected item, @t for tagged items)" if info == nil
|
997
|
+
info = info[1..(@w_b.maxx - 3)] + "…" if info.length + 3 > @w_b.maxx
|
998
|
+
info += " " * (@w_b.maxx - info.length) if info.length < @w_b.maxx
|
999
|
+
@w_b.text = info
|
1000
|
+
@w_b.write
|
1001
|
+
@w_b.update = false
|
1002
|
+
end
|
1003
|
+
def w_b_getstr(pretext, text) # A SIMPLE READLINE-LIKE ROUTINE
|
1004
|
+
Curses.curs_set(1)
|
1005
|
+
Curses.echo
|
1006
|
+
stk = 0
|
1007
|
+
@history.insert(stk, text)
|
1008
|
+
pos = @history[stk].length
|
1009
|
+
chr = ""
|
1010
|
+
while chr != "ENTER"
|
1011
|
+
@w_b.setpos(0,0)
|
1012
|
+
init_pair(250, 250, 238)
|
1013
|
+
text = pretext + @history[stk]
|
1014
|
+
text += " " * (@w_b.maxx - text.length) if text.length < @w_b.maxx
|
1015
|
+
@w_b.attron(color_pair(250)) { @w_b << text }
|
1016
|
+
@w_b.setpos(0,pretext.length + pos)
|
1017
|
+
@w_b.refresh
|
1018
|
+
chr = getchr
|
1019
|
+
case chr
|
1020
|
+
when 'UP'
|
1021
|
+
unless @w_b.nohistory
|
1022
|
+
unless stk == @history.length - 1
|
1023
|
+
stk += 1
|
1024
|
+
pos = @history[stk].length
|
1025
|
+
end
|
1026
|
+
end
|
1027
|
+
when 'DOWN'
|
1028
|
+
unless @w_b.nohistory
|
1029
|
+
unless stk == 0
|
1030
|
+
stk -= 1
|
1031
|
+
pos = @history[stk].length
|
1032
|
+
end
|
1033
|
+
end
|
1034
|
+
when 'RIGHT'
|
1035
|
+
pos += 1 unless pos > @history[stk].length
|
1036
|
+
when 'LEFT'
|
1037
|
+
pos -= 1 unless pos == 0
|
1038
|
+
when 'HOME'
|
1039
|
+
pos = 0
|
1040
|
+
when 'END'
|
1041
|
+
pos = @history[stk].length
|
1042
|
+
when 'DEL'
|
1043
|
+
@history[stk][pos] = ""
|
1044
|
+
when 'BACK'
|
1045
|
+
unless pos == 0
|
1046
|
+
pos -= 1
|
1047
|
+
@history[stk][pos] = ""
|
1048
|
+
end
|
1049
|
+
when 'WBACK'
|
1050
|
+
unless pos == 0
|
1051
|
+
until @history[stk][pos - 1] == " " or pos == 0
|
1052
|
+
pos -= 1
|
1053
|
+
@history[stk][pos] = ""
|
1054
|
+
end
|
1055
|
+
if @history[stk][pos - 1] == " "
|
1056
|
+
pos -= 1
|
1057
|
+
@history[stk][pos] = ""
|
1058
|
+
end
|
1059
|
+
end
|
1060
|
+
when 'LDEL'
|
1061
|
+
@history[stk] = ""
|
1062
|
+
pos = 0
|
1063
|
+
when 'TAB' # Tab completion of dirs and files
|
1064
|
+
p1 = pos - 1
|
1065
|
+
c = @history[stk][0..(p1)].sub(/^.* /, '')
|
1066
|
+
p0 = p1 - c.length
|
1067
|
+
compl = File.expand_path(c)
|
1068
|
+
compl += "/" if Dir.exist?(compl)
|
1069
|
+
clist = Dir.glob(compl + "*")
|
1070
|
+
unless compl == clist[0].to_s and clist.length == 1
|
1071
|
+
if clist.length == 1
|
1072
|
+
compl = clist[0].to_s
|
1073
|
+
else
|
1074
|
+
ix = clist.find_index(compl)
|
1075
|
+
ix = 0 if ix == nil
|
1076
|
+
sel_item = ""
|
1077
|
+
begin
|
1078
|
+
Curses.curs_set(0)
|
1079
|
+
Curses.noecho
|
1080
|
+
@w_r.clr
|
1081
|
+
@w_r << "Completion list:\n\n"
|
1082
|
+
clist.each.with_index do |item, index|
|
1083
|
+
if index == ix
|
1084
|
+
@w_r.attron(Curses::A_BLINK) { @w_r << item }
|
1085
|
+
sel_item = item
|
1086
|
+
else
|
1087
|
+
@w_r << item
|
1088
|
+
end
|
1089
|
+
@w_r << "\n"
|
1090
|
+
end
|
1091
|
+
@w_r.refresh
|
1092
|
+
ix == clist.length ? ix = 0 : ix += 1
|
1093
|
+
end while getchr == 'TAB'
|
1094
|
+
compl = sel_item
|
1095
|
+
@w_r.clr
|
1096
|
+
Curses.curs_set(1)
|
1097
|
+
Curses.echo
|
1098
|
+
end
|
1099
|
+
end
|
1100
|
+
@history[stk].sub!(c,compl)
|
1101
|
+
pos = pos - c.length + compl.length
|
1102
|
+
when /^.$/
|
1103
|
+
@history[stk].insert(pos,chr)
|
1104
|
+
pos += 1
|
1105
|
+
end
|
1106
|
+
end
|
1107
|
+
curstr = @history[stk]
|
1108
|
+
@history.shift if @w_b.nohistory
|
1109
|
+
unless @w_b.nohistory
|
1110
|
+
@history.uniq!
|
1111
|
+
@history.compact!
|
1112
|
+
@history.delete("")
|
1113
|
+
end
|
1114
|
+
Curses.curs_set(0)
|
1115
|
+
Curses.noecho
|
1116
|
+
return curstr
|
1117
|
+
end
|
1118
|
+
def w_b_exec(cmd) # EXECUTE COMMAND FROM @W_B
|
1119
|
+
# Subsitute any '@s' with the selected item, @t with tagged items
|
1120
|
+
# 'rm @s' deletes the selected item, 'rm @t' deletes tagged items
|
1121
|
+
return if cmd == ""
|
1122
|
+
@s = "\"#{Dir.pwd}/#{@selected}\""
|
1123
|
+
cmd.gsub!(/@s/, @s)
|
1124
|
+
@t = @tagged.join(" ")
|
1125
|
+
cmd.gsub!(/@t/, @t)
|
1126
|
+
if cmd.match(/^cd /)
|
1127
|
+
cmd.sub!(/^cd (\S*).*/, '\1')
|
1128
|
+
Dir.chdir(cmd) if Dir.exist?(cmd)
|
1129
|
+
return
|
1130
|
+
end
|
1131
|
+
begin
|
1132
|
+
begin
|
1133
|
+
@w_r.pager_cmd = "#{cmd} | batcat -n --color=always 2>/dev/null"
|
1134
|
+
@w_r.text = `#{@w_r.pager_cmd} 2>/dev/null`
|
1135
|
+
rescue
|
1136
|
+
@w_r.text = `#{cmd} 2>/dev/null`
|
1137
|
+
end
|
1138
|
+
unless @w_r.text == "" or @w_r.text == nil
|
1139
|
+
pager_start
|
1140
|
+
pager_show
|
1141
|
+
@w_r.update = false
|
1142
|
+
end
|
1143
|
+
rescue
|
1144
|
+
w_b_info(" Failed to execute command (#{cmd})")
|
1145
|
+
end
|
1146
|
+
end
|
1147
|
+
|
1148
|
+
# MAIN PROGRAM
|
1149
|
+
loop do # OUTER LOOP - CATCHING REFRESHES VIA 'r'
|
1150
|
+
@break = false # Initialize @break variable (set if user hits 'r')
|
1151
|
+
@image = false # Set the image flag to false (set if image is displayed in @w_r)
|
1152
|
+
@tag = false # Set pattern tagging to nothing
|
1153
|
+
@orderchange = false
|
1154
|
+
begin # Create the four windows/panels
|
1155
|
+
if @border
|
1156
|
+
Curses.stdscr.bg = 236 # Use for borders
|
1157
|
+
Curses.stdscr.fill
|
1158
|
+
else
|
1159
|
+
Curses.stdscr.clear
|
1160
|
+
Curses.stdscr.refresh
|
1161
|
+
end
|
1162
|
+
maxx = Curses.cols
|
1163
|
+
maxy = Curses.lines
|
1164
|
+
# Curses::Window.new(h,w,y,x)
|
1165
|
+
@w_t = Curses::Window.new(1, 0, 0, 0)
|
1166
|
+
@w_b = Curses::Window.new(1, 0, maxy - 1, 0)
|
1167
|
+
@w_l = Curses::Window.new(maxy - 3, (maxx/@width) - 1, 2, 0)
|
1168
|
+
@w_r = Curses::Window.new(maxy - 4, maxx - (maxx/@width), 2, maxx/@width)
|
1169
|
+
@w_t.fg, @w_t.bg = 232, 249
|
1170
|
+
@w_t.attr = Curses::A_BOLD
|
1171
|
+
@w_b.fg, @w_b.bg = 250, 238
|
1172
|
+
@w_t.update = true
|
1173
|
+
@w_b.update = true
|
1174
|
+
@w_l.update = true
|
1175
|
+
@w_r.update = true
|
1176
|
+
@w_r.pager = 0
|
1177
|
+
@w_r.pager_more = false
|
1178
|
+
dir_old = Dir.pwd
|
1179
|
+
lsall_old = @lsall
|
1180
|
+
unless @tagged.empty?
|
1181
|
+
tagged_info
|
1182
|
+
@w_r.update = false
|
1183
|
+
end
|
1184
|
+
loop do # INNER, CORE LOOP
|
1185
|
+
system("printf \"\033]0;RTFM: #{Dir.pwd}\007\"") # Set Window title to path
|
1186
|
+
ls_cmd = "ls #{@lsbase} #{@lsall} #{@lsorder} #{@lsinvert} #{@lsuser}" # Get files in current directory
|
1187
|
+
@files = `#{ls_cmd} 2>/dev/null`.split("\n")
|
1188
|
+
ls_cmd += %q[ -lhgG --time-style="long-iso" | awk '{printf "%s%12s%6s%6s%5s", $1,$4,$5,$3,$2 "\n"}']
|
1189
|
+
@fspes = `#{ls_cmd} 2>/dev/null`.split("\n").drop(1)
|
1190
|
+
if Dir.pwd != dir_old
|
1191
|
+
if @directory.key?(Dir.pwd)
|
1192
|
+
@selected = @directory[Dir.pwd]
|
1193
|
+
@index = @files.index(@selected)
|
1194
|
+
else
|
1195
|
+
@index = 0
|
1196
|
+
end
|
1197
|
+
end
|
1198
|
+
dir_old = Dir.pwd
|
1199
|
+
@index = 0 if @index == nil
|
1200
|
+
index_old = @index
|
1201
|
+
if @orderchange # Change in ordering must be handled
|
1202
|
+
@index = @files.index(@selected)
|
1203
|
+
@orderchange = false
|
1204
|
+
end
|
1205
|
+
@index = @files.index(@selected) if @lsall != lsall_old # Change in showing all items must be handled
|
1206
|
+
@index = index_old if @files.index(@selected) == nil # If item no longer is shown
|
1207
|
+
@min_index = 0
|
1208
|
+
@max_index = @files.size - 1
|
1209
|
+
@index = @max_index if @index > @max_index # If deleted many items
|
1210
|
+
@index = 0 if @index < 0
|
1211
|
+
@selected = @files[@index] # Get text of selected item
|
1212
|
+
@selected_safe = "\"#{@selected}\"" # Make it safe for commands
|
1213
|
+
# Top window (info line)
|
1214
|
+
w_t_info
|
1215
|
+
# Bottom window (command line) Before @w_r to avoid image dropping out on startup
|
1216
|
+
w_b_info(nil) if @w_b.update
|
1217
|
+
# Left and right windows (browser & content viewer)
|
1218
|
+
@w_l.setpos(0,0)
|
1219
|
+
list_dir(true)
|
1220
|
+
@w_l.refresh
|
1221
|
+
w_r_show if @w_r.update and @preview
|
1222
|
+
Curses.curs_set(1) # Clear residual cursor
|
1223
|
+
Curses.curs_set(0) # ...from editing files
|
1224
|
+
@tag = false # Clear tag pattern
|
1225
|
+
lsall_old = @lsall
|
1226
|
+
main_getkey # Get key from user
|
1227
|
+
break if @break # Break to outer loop, redrawing windows, if user hit 'r'
|
1228
|
+
break if Curses.cols != maxx or Curses.lines != maxy # break on terminal resize
|
1229
|
+
end
|
1230
|
+
ensure # On exit: close curses, clear terminal
|
1231
|
+
@write_conf_all = false
|
1232
|
+
conf_write if @write_conf # Write marks to config file
|
1233
|
+
image_show("clear")
|
1234
|
+
close_screen
|
1235
|
+
# If launched via the script "r", return current dir and "r" will cd to that
|
1236
|
+
File.write(ARGV[0], Dir.pwd) if ARGV[0] and ARGV[0].match(/\/tmp\/tmp/)
|
1237
|
+
end
|
1238
|
+
end
|
1239
|
+
|
1240
|
+
# vim: set sw=2 sts=2 et fdm=syntax fdn=2 fcs=fold\:\ :
|
metadata
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rtfm-filemanager
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Geir Isene
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-09-23 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: RTFM lets you browse directories and view the content of directories
|
14
|
+
and files. Files are syntax highlighted, images are shown in the terminal, videos
|
15
|
+
are thumbnailed, etc. You can bookmark and jump around easily, delete, rename, copy,
|
16
|
+
symlink and move files. RTFM has a a wide range of other features.
|
17
|
+
email: g@isene.com
|
18
|
+
executables:
|
19
|
+
- rtfm
|
20
|
+
extensions: []
|
21
|
+
extra_rdoc_files: []
|
22
|
+
files:
|
23
|
+
- ".rtfm.launch"
|
24
|
+
- bin/rtfm
|
25
|
+
homepage: https://isene.com/
|
26
|
+
licenses:
|
27
|
+
- Unlicense
|
28
|
+
metadata:
|
29
|
+
source_code_uri: https://github.com/isene/RTFM
|
30
|
+
post_install_message:
|
31
|
+
rdoc_options: []
|
32
|
+
require_paths:
|
33
|
+
- lib
|
34
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ">="
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
requirements: []
|
45
|
+
rubygems_version: 3.1.2
|
46
|
+
signing_key:
|
47
|
+
specification_version: 4
|
48
|
+
summary: RTFM - Ruby Terminal File Manager
|
49
|
+
test_files: []
|