rtfm-filemanager 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
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: []