rfd 0.0.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.
@@ -0,0 +1,234 @@
1
+ module Rfd
2
+ module Commands
3
+ # Change permission ("A"ttributes) of selected files and directories.
4
+ def a
5
+ process_command_line preset_command: 'chmod'
6
+ end
7
+
8
+ # "c"opy selected files and directories.
9
+ def c
10
+ process_command_line preset_command: 'cp'
11
+ end
12
+
13
+ # Soft "d"elete (actually mv to the trash folder) selected files and directories.
14
+ def d
15
+ if selected_items.any?
16
+ if ask %Q[Are your sure want to trash #{selected_items.one? ? selected_items.first.name : "these #{selected_items.size} files"}? (y/n)]
17
+ FileUtils.mv selected_items.map(&:path), File.expand_path('~/.Trash/')
18
+ @current_row -= selected_items.count {|i| i.index <= current_row}
19
+ ls
20
+ end
21
+ end
22
+ end
23
+
24
+ # Open current file or directory with the "e"ditor
25
+ def e
26
+ edit
27
+ end
28
+
29
+ # "f"ind the first flie or directory of which name starts with the given String.
30
+ def f
31
+ process_command_line preset_command: 'find'
32
+ end
33
+
34
+ # Move the cursor to the left pane.
35
+ def h
36
+ (y = current_row - maxy) >= 0 and move_cursor y
37
+ end
38
+
39
+ # Move the cursor down.
40
+ def j
41
+ if current_row + 1 >= items.size
42
+ move_cursor 0
43
+ else
44
+ move_cursor current_row + 1
45
+ end
46
+ end
47
+
48
+ # Move the cursor up.
49
+ def k
50
+ if current_row == 0
51
+ move_cursor items.size - 1
52
+ else
53
+ move_cursor current_row - 1
54
+ end
55
+ end
56
+
57
+ # Move the cursor to the right pane.
58
+ def l
59
+ (y = current_row + maxy) < items.size and move_cursor y
60
+ end
61
+
62
+ # "m"ove selected files and directories.
63
+ def m
64
+ process_command_line preset_command: 'mv'
65
+ end
66
+
67
+ # "o"pen selected files and directories with the OS "open" command.
68
+ def o
69
+ system "open #{selected_items.map {|i| %Q["#{i.path}"]}.join(' ')}"
70
+ end
71
+
72
+ # "q"uit the app.
73
+ def q
74
+ raise StopIteration if ask 'Are your sure want to exit? (y/n)'
75
+ end
76
+
77
+ # "r"ename selected files and directories.
78
+ def r
79
+ process_command_line preset_command: 'rename'
80
+ end
81
+
82
+ # "s"ort displayed files and directories in the given order.
83
+ def s
84
+ process_command_line preset_command: 'sort'
85
+ end
86
+
87
+ # Create a new file, or update its timestamp if the file already exists ("t"ouch).
88
+ def t
89
+ process_command_line preset_command: 'touch'
90
+ end
91
+
92
+ # "o"pen selected files and directories with the viewer.
93
+ def v
94
+ view
95
+ end
96
+
97
+ # Hard "d"elete selected files and directories.
98
+ def D
99
+ if selected_items.any?
100
+ if ask %Q[Are your sure want to delete #{selected_items.one? ? selected_items.first.name : "these #{selected_items.size} files"}? (y/n)]
101
+ FileUtils.rm_rf selected_items.map(&:path)
102
+ @current_row -= selected_items.count {|i| i.index <= current_row}
103
+ ls
104
+ end
105
+ end
106
+ end
107
+
108
+ # "f"ind the last flie or directory of which name starts with the given String.
109
+ def F
110
+ process_command_line preset_command: 'find_reverse'
111
+ end
112
+
113
+ # Move the cursor to the top.
114
+ def H
115
+ move_cursor current_page * max_items
116
+ end
117
+
118
+ # Ma"K"e a directory.
119
+ def K
120
+ process_command_line preset_command: 'mkdir'
121
+ end
122
+
123
+ # Move the cursor to the bottom.
124
+ def L
125
+ move_cursor current_page * max_items + displayed_items.size - 1
126
+ end
127
+
128
+ # Move the cursor to the "M"iddle.
129
+ def M
130
+ move_cursor current_page * max_items + displayed_items.size / 2
131
+ end
132
+
133
+ # Mark or unmark "a"ll files and directories.
134
+ def ctrl_a
135
+ mark = marked_items.size != (items.size - 2) # exclude . and ..
136
+ items.each {|i| i.toggle_mark unless i.marked? == mark}
137
+ draw_items
138
+ move_cursor current_row
139
+ draw_marked_items
140
+ header_r.wrefresh
141
+ end
142
+
143
+ # "b"ack to the previous page.
144
+ def ctrl_b
145
+ ctrl_p
146
+ end
147
+
148
+ # "f"orward to the next page.
149
+ def ctrl_f
150
+ ctrl_n
151
+ end
152
+
153
+ # Refresh the screen.
154
+ def ctrl_l
155
+ ls
156
+ end
157
+
158
+ # Forward to the "n"ext page.
159
+ def ctrl_n
160
+ if total_pages > 1
161
+ if last_page?
162
+ move_cursor 0
163
+ else
164
+ move_cursor (current_page + 1) * max_items
165
+ end
166
+ end
167
+ end
168
+
169
+ # Back to the "p"revious page.
170
+ def ctrl_p
171
+ if total_pages > 1
172
+ if first_page?
173
+ move_cursor (total_pages - 1) * max_items
174
+ else
175
+ move_cursor (current_page - 1) * max_items
176
+ end
177
+ end
178
+ end
179
+
180
+ # Change the number of columns in the main window.
181
+ (?1..?9).each do |n|
182
+ define_method(n) do
183
+ spawn_panes n.to_i
184
+ ls
185
+ end
186
+ end
187
+
188
+ # Return to the previous directory (popd).
189
+ def -
190
+ popd
191
+ end
192
+
193
+ # Search files and directories from the current directory.
194
+ def /
195
+ process_command_line preset_command: 'grep'
196
+ end
197
+
198
+ # Execute a shell command in an external shell.
199
+ define_method('!') do
200
+ process_shell_command
201
+ end
202
+
203
+ # Execute a command in the controller context.
204
+ define_method(':') do
205
+ process_command_line
206
+ end
207
+
208
+ # cd into a directory, or view a file.
209
+ def enter
210
+ if current_item.directory?
211
+ cd current_item
212
+ ls
213
+ else
214
+ v
215
+ end
216
+ end
217
+
218
+ # Toggle mark, and move down.
219
+ def space
220
+ toggle_mark
221
+ j
222
+ draw_marked_items
223
+ header_r.wrefresh
224
+ end
225
+
226
+ # cd to the upper hierarchy.
227
+ def del
228
+ if current_dir != '/'
229
+ cd '..'
230
+ ls
231
+ end
232
+ end
233
+ end
234
+ end
@@ -0,0 +1,176 @@
1
+ module Rfd
2
+ class Item
3
+ include Comparable
4
+ attr_reader :name
5
+ attr_accessor :index
6
+
7
+ def initialize(dir: nil, name: nil, window_width: nil)
8
+ @dir, @name, @window_width, @marked = dir, name, window_width, false
9
+ end
10
+
11
+ def path
12
+ @path ||= File.join @dir, @name
13
+ end
14
+
15
+ def basename
16
+ @basename ||= File.basename name, extname
17
+ end
18
+
19
+ def extname
20
+ @extname ||= File.extname name
21
+ end
22
+
23
+ def full_display_name
24
+ n = @name.dup
25
+ n << " -> #{target}" if symlink?
26
+ n
27
+ end
28
+
29
+ def display_name
30
+ @display_name ||= begin
31
+ n = full_display_name
32
+ if mb_size(n) <= @window_width - 15
33
+ n
34
+ else
35
+ if symlink?
36
+ mb_left n, @window_width - 16
37
+ else
38
+ "#{mb_left(basename, @window_width - 16 - extname.length)}…#{extname}"
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ def lstat
45
+ @lstat ||= File.lstat path
46
+ end
47
+
48
+ def stat
49
+ @stat ||= File.stat path
50
+ end
51
+
52
+ def color
53
+ if symlink?
54
+ Curses::COLOR_MAGENTA
55
+ elsif hidden?
56
+ Curses::COLOR_GREEN
57
+ elsif directory?
58
+ Curses::COLOR_CYAN
59
+ elsif executable?
60
+ Curses::COLOR_RED
61
+ else
62
+ Curses::COLOR_WHITE
63
+ end
64
+ end
65
+
66
+ def size
67
+ directory? ? 0 : lstat.size
68
+ end
69
+
70
+ def size_or_dir
71
+ directory? ? '<DIR>' : size.to_s
72
+ end
73
+
74
+ def atime
75
+ lstat.atime.strftime('%Y-%m-%d %H:%M:%S')
76
+ end
77
+
78
+ def ctime
79
+ lstat.ctime.strftime('%Y-%m-%d %H:%M:%S')
80
+ end
81
+
82
+ def mtime
83
+ lstat.mtime.strftime('%Y-%m-%d %H:%M:%S')
84
+ end
85
+
86
+ def mode
87
+ @mode ||= begin
88
+ m = lstat.mode
89
+ ret = directory? ? 'd' : symlink? ? 'l' : '-'
90
+ [(m & 0700) / 64, (m & 070) / 8, m & 07].inject(ret) do |str, s|
91
+ str << "#{s & 4 == 4 ? 'r' : '-'}#{s & 2 == 2 ? 'w' : '-'}#{s & 1 == 1 ? 'x' : '-'}"
92
+ end
93
+ if m & 04000 != 0
94
+ ret[3] = directory? ? 's' : 'S'
95
+ end
96
+ if m & 02000 != 0
97
+ ret[6] = directory? ? 's' : 'S'
98
+ end
99
+ if m & 01000 == 512
100
+ ret[-1] = directory? ? 't' : 'T'
101
+ end
102
+ ret
103
+ end
104
+ end
105
+
106
+ def directory?
107
+ stat.directory?
108
+ end
109
+
110
+ def symlink?
111
+ lstat.symlink?
112
+ end
113
+
114
+ def hidden?
115
+ name.start_with?('.') && (name != '.') && (name != '..')
116
+ end
117
+
118
+ def executable?
119
+ lstat.executable?
120
+ end
121
+
122
+ def target
123
+ File.readlink path if symlink?
124
+ end
125
+
126
+ def toggle_mark
127
+ unless %w(. ..).include? name
128
+ @marked = !@marked
129
+ true
130
+ end
131
+ end
132
+
133
+ def marked?
134
+ @marked
135
+ end
136
+
137
+ def current_mark
138
+ marked? ? '*' : ' '
139
+ end
140
+
141
+ def mb_left(str, size)
142
+ len = 0
143
+ index = str.each_char.with_index do |c, i|
144
+ break i if len + mb_char_size(c) > size
145
+ len += mb_size c
146
+ end
147
+ str[0, index]
148
+ end
149
+
150
+ def mb_char_size(c)
151
+ c == '…' ? 1 : c.bytesize == 1 ? 1 : 2
152
+ end
153
+
154
+ def mb_size(str)
155
+ str.each_char.inject(0) {|l, c| l += mb_char_size(c)}
156
+ end
157
+
158
+ def mb_ljust(str, size)
159
+ "#{str}#{' ' * [0, size - mb_size(str)].max}"
160
+ end
161
+
162
+ def to_s
163
+ "#{current_mark}#{mb_ljust(display_name, @window_width - 15)}#{size_or_dir.rjust(13)}"
164
+ end
165
+
166
+ def <=>(o)
167
+ if directory? && !o.directory?
168
+ 1
169
+ elsif !directory? && o.directory?
170
+ -1
171
+ else
172
+ name <=> o.name
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,227 @@
1
+ module Rfd
2
+ class Window
3
+ attr_reader :window
4
+
5
+ def wmove(y, x = 0)
6
+ Curses.wmove window, y, x
7
+ end
8
+
9
+ def waddstr(str, clear_to_eol_before_add: false)
10
+ wclrtoeol if clear_to_eol_before_add
11
+ Curses.waddstr window, str
12
+ end
13
+
14
+ def mvwaddstr(y, x, str)
15
+ Curses.mvwaddstr window, y, x, str
16
+ end
17
+
18
+ def wclear
19
+ Curses.wclear window
20
+ end
21
+
22
+ def wrefresh
23
+ Curses.wrefresh window
24
+ end
25
+
26
+ def maxx
27
+ Curses.getmaxx window
28
+ end
29
+
30
+ def maxy
31
+ Curses.getmaxy window
32
+ end
33
+
34
+ def begx
35
+ Curses.getbegx window
36
+ end
37
+
38
+ def begy
39
+ Curses.getbegy window
40
+ end
41
+
42
+ def subwin(height, width, top, left)
43
+ Curses.derwin Curses.stdscr, height, width, top, left
44
+ end
45
+
46
+ def wclrtoeol
47
+ Curses.wclrtoeol window
48
+ end
49
+
50
+ def draw_border(*border_param)
51
+ border_window = subwin maxy + 2, maxx + 2, begy - 1, begx - 1
52
+ Curses.wbkgd border_window, Curses.COLOR_PAIR(Curses::COLOR_CYAN)
53
+ Curses.wborder border_window, *border_param
54
+ end
55
+ end
56
+
57
+ class HeaderLeftWindow < Window
58
+ def initialize
59
+ @window = subwin 3, Curses.COLS - 32, 1, 1
60
+ draw_border 0, 0, 0, 0, 0, 0, Curses::ACS_LTEE, 0
61
+ end
62
+
63
+ def draw_path_and_page_number(path: nil, current: 1, total: nil)
64
+ @path_and_page_number = %Q[Page: #{"#{current}/ #{total}".ljust(11)} Path: #{path}]
65
+ wmove 0
66
+ waddstr @path_and_page_number, clear_to_eol_before_add: true
67
+ wrefresh
68
+ end
69
+
70
+ def draw_current_file_info(current_file)
71
+ draw_current_filename current_file.full_display_name
72
+ draw_stat current_file
73
+ end
74
+
75
+ def draw_current_filename(current_file_name)
76
+ @current_file_name = "File: #{current_file_name}"
77
+ wmove 1
78
+ waddstr @current_file_name, clear_to_eol_before_add: true
79
+ end
80
+
81
+ def draw_stat(item)
82
+ @stat = " #{item.size_or_dir.ljust(13)}#{item.mtime} #{item.mode}"
83
+ wmove 2
84
+ waddstr @stat, clear_to_eol_before_add: true
85
+ end
86
+ end
87
+
88
+ class HeaderRightWindow < Window
89
+ def initialize
90
+ @window = subwin 3, 29, 1, Curses.COLS - 30
91
+ draw_border 0, 0, 0, 0, Curses::ACS_TTEE, 0, Curses::ACS_BTEE, Curses::ACS_RTEE
92
+ end
93
+
94
+ def draw_marked_items(count: 0, size: 0)
95
+ wmove 1
96
+ waddstr %Q[#{"#{count}Marked".rjust(11)} #{size.to_s.reverse.gsub( /(\d{3})(?=\d)/, '\1,').reverse.rjust(16)}], clear_to_eol_before_add: true
97
+ end
98
+
99
+ def draw_total_items(count: 0, size: 0)
100
+ wmove 2
101
+ waddstr %Q[#{"#{count}Files".rjust(10)} #{size.to_s.reverse.gsub( /(\d{3})(?=\d)/, '\1,').reverse.rjust(17)}], clear_to_eol_before_add: true
102
+ wrefresh
103
+ end
104
+
105
+ def debug(s)
106
+ wmove 0, 0
107
+ wclrtoeol
108
+ waddstr s.to_s
109
+ wrefresh
110
+ end
111
+ end
112
+
113
+ class MainWindow < Window
114
+ class Panes
115
+ attr_reader :current_index
116
+
117
+ def initialize(panes)
118
+ @panes, @current_index = panes, 0
119
+ end
120
+
121
+ def active
122
+ @panes[@current_index]
123
+ end
124
+
125
+ def activate(index)
126
+ @current_index = index if index < @panes.size
127
+ end
128
+
129
+ def size
130
+ @panes.size
131
+ end
132
+
133
+ def close_all
134
+ @panes.each {|p| Curses.delwin p}
135
+ end
136
+ end
137
+
138
+ def initialize(dir = '.')
139
+ border_window = subwin Curses.LINES - 5, Curses.COLS, 4, 0
140
+ Curses.wbkgd border_window, Curses::COLOR_PAIR(Curses::COLOR_CYAN)
141
+ Curses.box border_window, 0, 0
142
+
143
+ spawn_panes 2
144
+ end
145
+
146
+ def spawn_panes(num)
147
+ @panes.close_all if defined? @panes
148
+ width = (Curses.COLS - 2) / num
149
+ windows = 0.upto(num - 1).inject([]) {|arr, i| arr << subwin(Curses.LINES - 7, width - 1, 5, width * i + 1)}
150
+ @panes = Panes.new windows
151
+ activate_pane 0
152
+ end
153
+
154
+ def activate_pane(num)
155
+ @panes.activate num
156
+ end
157
+
158
+ def window
159
+ @panes.active
160
+ end
161
+
162
+ def max_items
163
+ maxy * @panes.size
164
+ end
165
+
166
+ def draw_item(item, current: false)
167
+ Curses.wattr_set window, current ? Curses::A_UNDERLINE : Curses::A_NORMAL, item.color, nil
168
+ mvwaddstr item.index % maxy, 0, "#{item.to_s}\n"
169
+ Curses.wstandend window
170
+ wrefresh
171
+ end
172
+
173
+ def draw_items_to_each_pane(items)
174
+ original_active_pane_index = @panes.current_index
175
+
176
+ 0.upto(@panes.size - 1) do |index|
177
+ activate_pane index
178
+ wclear
179
+ wmove 0
180
+ items[maxy * index, maxy * (index + 1)].each do |item|
181
+ Curses.wattr_set window, Curses::A_NORMAL, item.color, nil
182
+ waddstr "#{item.to_s}\n"
183
+ end if items[maxy * index, maxy * (index + 1)]
184
+ Curses.wstandend window
185
+ wrefresh
186
+ end
187
+ activate_pane original_active_pane_index
188
+ end
189
+
190
+ def toggle_mark(item)
191
+ mvwaddstr item.index % maxy, 0, item.current_mark if item.toggle_mark
192
+ end
193
+ end
194
+
195
+ class CommandLineWindow < Window
196
+ def initialize
197
+ @window = subwin 1, Curses.COLS, Curses.LINES - 1, 0
198
+ end
199
+
200
+ def set_prompt(str)
201
+ Curses.wattr_set window, Curses::A_BOLD, Curses::COLOR_WHITE, nil
202
+ wmove 0
203
+ wclrtoeol
204
+ waddstr str
205
+ Curses.wstandend window
206
+ end
207
+
208
+ def get_command(prompt: nil)
209
+ Curses.echo
210
+ startx = prompt ? prompt.length : 1
211
+ s = ' ' * 100
212
+ Curses.mvwgetstr window, 0, startx, s
213
+ "#{prompt[1..-1] if prompt}#{s.strip}"
214
+ ensure
215
+ Curses.noecho
216
+ end
217
+
218
+ def show_error(str)
219
+ Curses.wattr_set window, Curses::A_BOLD, Curses::COLOR_RED, nil
220
+ wmove 0
221
+ wclrtoeol
222
+ waddstr str
223
+ wrefresh
224
+ Curses.wstandend window
225
+ end
226
+ end
227
+ end