rtfm-filemanager 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/.rtfm.launch +28 -0
  3. data/bin/rtfm +1240 -0
  4. 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: []