ncurses-lyra 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: acdc28dedffd82821ba897ad9e89fa1a819f92f3281dd3d7277c87d62e28d14f
4
+ data.tar.gz: a6780129be3192e7adde4071395a23a7e47c74a195b78033d74b64d5c4ca8e0c
5
+ SHA512:
6
+ metadata.gz: 1a3e2de948cf59f3fef75c4625b0df6f2e0616672b8ff886bde71ea139d9c5fb4c8e90dd2d34f6b9c1ee4eba69089f7637a75a637eba21be4281d9a4d91d6196
7
+ data.tar.gz: 74f2403bfa38554936f8693a7762971da6985ce092c2d3e197a0549b74c75c38ab7a97f61ed70bd351669cd32a95a2f2871a3e6faa4314d66fe5a91bd80558c8
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ bin/console
10
+ bin/setup
11
+ lib/ncurses/lyra.rb
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in ncurses-lyra.gemspec
6
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018 kepler
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 kepler
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,60 @@
1
+ # ncurses-lyra
2
+
3
+ A simple small file lister and directory explorer.
4
+ One can use left and right arrow keys to open directories and file, or go back up to higher directory.
5
+ By default it opens files using PAGER, but files can be opened in EDITOR too using RETURN or e.
6
+
7
+ The code has been kept in one file so that the file can just be copied into one's bin folder or anywhere in the path.
8
+ This is simple ruby code, so you can edit it, or change colors or even expand this simple program.
9
+
10
+ Feel free to modify the code to your needs. You can submit patches if you feel it will help others.
11
+
12
+
13
+ I will add very little functionality to this as I use it, perhaps deleting a file, but I don't intend to let this become much larger.
14
+
15
+ File listing and sorting is done through `zsh` itself. So the `zsh` executable should be somewhere in path.
16
+
17
+ ## Installation
18
+
19
+ 1.
20
+ gem install ncurses-lyra
21
+
22
+ You might want to alias the lyra file to a single letter.
23
+
24
+ alias n='~/PATH/lyra.rb'
25
+
26
+ You may also copy lyra.rb to your PATH.
27
+
28
+ 2. Copying the executable file
29
+
30
+ This does depend on the `ffi-ncurses` gem by Sean Halpin.
31
+ That gem should have got installed when you installed the gem. If you did not install the gem, and just copied the lyra.rb file from the exe dir, then you will have to install that gem.
32
+
33
+ gem install ffi-ncurses
34
+
35
+
36
+ ## Usage
37
+
38
+ Call the program using the program name.
39
+
40
+ Use ARROW keys to view a file, or enter a directory, or go up.
41
+ Use '/' (slash) and enter a few characters and <RETURN> to see a filtered list.
42
+
43
+ There are a couple of menus with minimal options.
44
+ Tilde gives the main menu which contains a sort menu.
45
+ The toggle menu is on the equal key (=) and one can toggle hidden files, or long listing.
46
+ A few more options will be added with time.
47
+
48
+ Navigation can be accelerated with C-n, C-p, C-d, C-b, <space>, g (top), G (bottom).
49
+
50
+ Help on keys needs to come on pressing `?`
51
+
52
+
53
+
54
+ ## Contributing
55
+
56
+ Bug reports and pull requests are welcome on GitHub at https://github.com/mare-imbrium/ncurses-lyra.
57
+
58
+ ## License
59
+
60
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
@@ -0,0 +1,768 @@
1
+ #!/usr/bin/env ruby
2
+ require 'ffi-ncurses'
3
+ require 'ffi-ncurses/widechars'
4
+ # ----------------------------------------------------------------------------- #
5
+ # File: lyra.rb
6
+ # Description: a quick small directory lister aimed at being simple fast with minimal
7
+ # features, and mostly for viewing files quickly through PAGER
8
+ # Author: j kepler http://github.com/mare-imbrium/
9
+ # Date: 2018-03-09
10
+ # License: MIT
11
+ # Last update: 2018-05-05 12:40
12
+ # ----------------------------------------------------------------------------- #
13
+ # lyra.rb Copyright (C) 2012-2018 j kepler
14
+ # == TODO
15
+ # [ ] make a help screen on ?
16
+ # [ ] move to lyra or some gem and publish
17
+ # [ ] pop directories
18
+ # [ ] go back to start directory
19
+ # [ ] go to given directory
20
+ # [x] open files on RIGHT arrow in view (?)
21
+ # [ ] in a long listing, how to get to a file name. first char or pattern TODO
22
+ # [ ] pressing p should open PAGER, e EDITOR, m MOST, v - view
23
+ # [x] on zip file show contents in pager. x to extract.
24
+ # [x] when going up a directory keep cursor on the directory we came from
25
+ # [x] space bar to page down. also page up on c-n c-p top bottom
26
+ # [x] hide dot files
27
+ # [x] reveal dot files on toggle
28
+ # [x] long listing files on toggle
29
+ # [x] long file names not getting cleared
30
+ # [ ] allow entry of command and page output or show in PAGER
31
+ # [x] pressing ENTER should invoke EDITOR
32
+ # [x] scrolling up behavior not correct. we should scroll up from first row not last.
33
+ # see vifm for correct way. mc has different behavior
34
+ # ----------
35
+ # == CHANGELOG
36
+ #
37
+ #
38
+ # --------
39
+ #
40
+
41
+
42
+ BOLD = FFI::NCurses::A_BOLD
43
+ REVERSE = FFI::NCurses::A_REVERSE
44
+ UNDERLINE = FFI::NCurses::A_UNDERLINE
45
+ NORMAL = FFI::NCurses::A_NORMAL
46
+ COLOR_BLACK = FFI::NCurses::BLACK
47
+ COLOR_WHITE = FFI::NCurses::WHITE
48
+ COLOR_BLUE = FFI::NCurses::BLUE
49
+ COLOR_RED = FFI::NCurses::RED
50
+ COLOR_GREEN = FFI::NCurses::GREEN
51
+
52
+ def init_curses
53
+ FFI::NCurses.initscr
54
+ FFI::NCurses.curs_set 1
55
+ FFI::NCurses.raw
56
+ FFI::NCurses.noecho
57
+ FFI::NCurses.keypad FFI::NCurses.stdscr, true
58
+ FFI::NCurses.scrollok FFI::NCurses.stdscr, true
59
+ if FFI::NCurses.has_colors
60
+ FFI::NCurses.start_color
61
+ std_colors
62
+ end
63
+
64
+ end
65
+
66
+ # COLOR_BLACK 0
67
+ # COLOR_RED 1
68
+ # COLOR_GREEN 2
69
+ # COLOR_YELLOW 3
70
+ # COLOR_BLUE 4
71
+ # COLOR_MAGENTA 5
72
+ # COLOR_CYAN 6
73
+ # COLOR_WHITE 7
74
+
75
+ # In case, the init_pairs are changed, then update these as well, so that the programs use the correct pairs.
76
+ CP_BLACK = 0
77
+ CP_RED = 1
78
+ CP_GREEN = 2
79
+ CP_YELLOW = 3
80
+ CP_BLUE = 4
81
+ CP_MAGENTA = 5
82
+ CP_CYAN = 6
83
+ CP_WHITE = 7
84
+ # defining various colors
85
+ # NOTE this should be done by application or else we will be changing this all the time.
86
+ def std_colors
87
+ FFI::NCurses.use_default_colors
88
+ # 2018-03-17 - changing it to ncurses defaults
89
+ FFI::NCurses.init_pair(0, FFI::NCurses::BLACK, -1)
90
+ FFI::NCurses.init_pair(1, FFI::NCurses::RED, -1)
91
+ FFI::NCurses.init_pair(2, FFI::NCurses::GREEN, -1)
92
+ FFI::NCurses.init_pair(3, FFI::NCurses::YELLOW, -1)
93
+ FFI::NCurses.init_pair(4, FFI::NCurses::BLUE, -1)
94
+ FFI::NCurses.init_pair(5, FFI::NCurses::MAGENTA, -1)
95
+ FFI::NCurses.init_pair(6, FFI::NCurses::CYAN, -1)
96
+ FFI::NCurses.init_pair(7, FFI::NCurses::WHITE, -1)
97
+ # ideally the rest should be done by application
98
+ #FFI::NCurses.init_pair(8, FFI::NCurses::WHITE, -1)
99
+ #FFI::NCurses.init_pair(9, FFI::NCurses::BLUE, -1)
100
+ #FFI::NCurses.init_pair(10, FFI::NCurses::BLACK, FFI::NCurses::CYAN)
101
+ # alert
102
+ FFI::NCurses.init_pair(12, FFI::NCurses::BLACK, FFI::NCurses::BLUE)
103
+ #FFI::NCurses.init_pair(13, FFI::NCurses::BLACK, FFI::NCurses::MAGENTA)
104
+ #
105
+ # needed by menu
106
+ FFI::NCurses.init_pair(14, FFI::NCurses::WHITE, FFI::NCurses::CYAN)
107
+ end
108
+
109
+ # create and return a color_pair for a combination of bg and fg.
110
+ # This will always return the same color_pair so a duplicate one will not be created.
111
+ # @param bgcolor [Integer] color of background e.g., COLOR_BLACK
112
+ # @param fgcolor [Integer] color of foreground e.g., COLOR_WHITE
113
+ # @return [Integer] - color_pair which can be passed to #printstring, or used directly as #COLOR_PAIR(int)
114
+ def create_color_pair(bgcolor, fgcolor)
115
+ code = (bgcolor*10) + fgcolor
116
+ FFI::NCurses.init_pair(code, fgcolor, bgcolor)
117
+ return code
118
+ end
119
+ #
120
+ ## Window class
121
+ # Creates and manages the underlying window in which we write or place a form and fields.
122
+ # The two important methods here are the constructor, and +destroy()+.
123
+ # +pointer+ is important for making further direct calls to FFI::NCurses.
124
+ class Window
125
+ # pointer to FFI routines, use when calling FFI directly.
126
+ attr_reader :pointer # window pointer
127
+ attr_reader :panel # panel associated with window
128
+ attr_reader :width, :height, :top, :left
129
+
130
+ # creates a window with given height, width, top and left.
131
+ # If no args given, creates a root window (i.e. full size).
132
+ # @param height [Integer]
133
+ # @param width [Integer]
134
+ # @param top [Integer]
135
+ # @param left [Integer]
136
+ def initialize h=0, w=0, top=0, left=0
137
+ @height, @width, @top, @left = h, w, top, left
138
+
139
+ @height = FFI::NCurses.LINES if @height == 0 # 2011-11-14 added since tired of checking for zero
140
+ @width = FFI::NCurses.COLS if @width == 0
141
+ @pointer = FFI::NCurses.newwin(@height, @width, @top, @left) # added FFI 2011-09-6
142
+
143
+ @panel = FFI::NCurses.new_panel(@pointer)
144
+ FFI::NCurses.keypad(@pointer, true)
145
+ return @pointer
146
+ end
147
+
148
+ # print string at x, y coordinates. replace this with the original one below
149
+ # @deprecated
150
+ def printstr(str, x=0,y=0)
151
+ win = @pointer
152
+ FFI::NCurses.wmove(win, x, y)
153
+ FFI::NCurses.waddstr win, str
154
+ end
155
+
156
+ # 2018-03-08 - taken from canis reduced
157
+ # print given string at row, col with given color and attributes
158
+ # @param row [Integer] row to print on
159
+ # @param col [Integer] column to print on
160
+ # @param color [Integer] color_pair created earlier
161
+ # @param attr [Integer] any of the four FFI attributes, e.g. A_BOLD, A_REVERSE
162
+ def printstring(r,c,string, color=0, att = FFI::NCurses::A_NORMAL)
163
+
164
+ #$log.debug "printstring recvd nil row #{r} or col #{c}, color:#{color},att:#{att}." if $log
165
+ raise "printstring recvd nil row #{r} or col #{c}, color:#{color},att:#{att} " if r.nil? || c.nil?
166
+ att ||= FFI::NCurses::A_NORMAL
167
+ color ||= 0
168
+ raise "color is nil " unless color
169
+ raise "att is nil " unless att
170
+
171
+ FFI::NCurses.wattron(@pointer, FFI::NCurses.COLOR_PAIR(color) | att)
172
+ FFI::NCurses.mvwprintw(@pointer, r, c, "%s", :string, string);
173
+ FFI::NCurses.wattroff(@pointer, FFI::NCurses.COLOR_PAIR(color) | att)
174
+ end
175
+ ##
176
+ # Get a key from the standard input.
177
+ #
178
+ # This will get control keys and function keys but not Alt keys.
179
+ # This is usually called in a loop by the main program.
180
+ # It returns the ascii code (integer).
181
+ # 1 is Ctrl-a .... 27 is Esc
182
+ # FFI already has constants declared for function keys and control keys for checkin against.
183
+ # Can return a 3 or -1 if user pressed Control-C.
184
+ #
185
+ # NOTE: For ALT keys we need to check for 27/Esc and if so, then do another read
186
+ # with a timeout. If we get a key, then resolve. Otherwise, it is just ESC
187
+ # @return [Integer] ascii code of key
188
+ def getch
189
+ ch = FFI::NCurses.wgetch(@pointer)
190
+ rescue SystemExit, Interrupt
191
+ 3 # is C-c
192
+ rescue StandardError
193
+ -1 # is C-c
194
+ end
195
+ alias :getkey :getch
196
+
197
+ # refresh the window (wrapper)
198
+ # To be called after printing on a window.
199
+ def wrefresh
200
+ FFI::NCurses.wrefresh(@pointer)
201
+ end
202
+ # destroy the window and the panel.
203
+ # This is important. It should be placed in the ensure block of caller application, so it happens.
204
+ def destroy
205
+ FFI::NCurses.del_panel(@panel) if @panel
206
+ FFI::NCurses.delwin(@pointer) if @pointer
207
+ @panel = @pointer = nil # prevent call twice
208
+ end
209
+ # route other methods to ffi. {{{
210
+ # This should preferable NOT be used. Better to use the direct call itself.
211
+ # It attempts to route other calls to FFI::NCurses by trying to add w to the name and passing the pointer.
212
+ # I would like to remove this at some time.
213
+ def method_missing(name, *args)
214
+ name = name.to_s
215
+ raise "method missing !!! #{name}"
216
+ if (name[0,2] == "mv")
217
+ test_name = name.dup
218
+ test_name[2,0] = "w" # insert "w" after"mv"
219
+ if (FFI::NCurses.respond_to?(test_name))
220
+ return FFI::NCurses.send(test_name, @pointer, *args)
221
+ end
222
+ end
223
+ test_name = "w" + name
224
+ if (FFI::NCurses.respond_to?(test_name))
225
+ return FFI::NCurses.send(test_name, @pointer, *args)
226
+ end
227
+ FFI::NCurses.send(name, @pointer, *args)
228
+ end # }}}
229
+
230
+ # make a box around the window. Just a wrapper
231
+ def box
232
+ FFI::NCurses.box(@pointer, 0, 0)
233
+ end
234
+ # print a centered title on top of window
235
+ # This should be called after box, or else box will erase the title
236
+ # @param str [String] title to print
237
+ # @param color [Integer] color_pair
238
+ # @param att [Integer] attribute constant
239
+ def title str, color=0, att=BOLD
240
+ strl = str.length
241
+ col = (@width - strl)/2
242
+ printstring(0,col, str, color, att)
243
+ end
244
+ end # window
245
+
246
+ # a midnight commander like mc_menu
247
+ # Pass a hash of key and label.
248
+ # menu will only accept keys or arrow keys or C-c Esc to cancel
249
+ # returns nil if C-c or Esc pressed.
250
+ # Otherwise returns character pressed.
251
+ # == TODO
252
+ # depends on our window class which is minimal.
253
+ # [ ] cursor should show on the row that is highlighted
254
+ # [ ] Can we remove that dependency so this is independent
255
+ # Currently, we paint window each time user pressed up or down, but we can just repaint the attribute
256
+ # [ ] width of array items not checked. We could do that or have user pass it in.
257
+ # [ ] we are not scrolling if user sends in a large number of items. we should cap it to 10 or 20
258
+ # == CHANGELOG
259
+ #
260
+ class Menu
261
+
262
+ def initialize title, hash, config={}
263
+
264
+ @list = hash.values
265
+ @keys = hash.keys.collect { |x| x.to_s }
266
+ @hash = hash
267
+ bkgd = config[:bkgd] || FFI::NCurses.COLOR_PAIR(14) | BOLD
268
+ @attr = BOLD
269
+ @color_pair = config[:color_pair] || 14
270
+ ht = @list.size+2
271
+ wid = config[:width] || 40
272
+ top = (FFI::NCurses.LINES - ht)/2
273
+ left = (FFI::NCurses.COLS - wid)/2
274
+ @window = Window.new(ht, wid, top, left)
275
+ FFI::NCurses.wbkgd(@window.pointer, bkgd)
276
+ @window.box
277
+ @window.title(title)
278
+ @current = 0
279
+ print_items @hash
280
+ end
281
+ def print_items hash
282
+ ix = 0
283
+ hash.each_pair {|k, val|
284
+ attr = @attr
285
+ attr = REVERSE if ix == @current
286
+ @window.printstring(ix+1 , 2, "#{k} #{val}", @color_pair, attr )
287
+ ix += 1
288
+ }
289
+ @window.wrefresh
290
+ end
291
+ def getkey # in menu
292
+ ch = 0
293
+ char = nil
294
+ begin
295
+ while (ch = @window.getkey) != FFI::NCurses::KEY_CTRL_C
296
+ break if ch == 27 # ESC
297
+ tmpchar = FFI::NCurses.keyname(ch) rescue '?'
298
+ if @keys.include? tmpchar
299
+ char = tmpchar
300
+ break
301
+ end
302
+ case ch
303
+ when FFI::NCurses::KEY_DOWN
304
+ @current += 1
305
+ when FFI::NCurses::KEY_UP
306
+ @current -= 1
307
+ when ?g.getbyte(0)
308
+ @current_index = 0
309
+ when ?G.getbyte(0)
310
+ @current_index = @list.size-1
311
+ when FFI::NCurses::KEY_RETURN
312
+ char = @keys[@current]
313
+ break
314
+ end
315
+ @current = 0 if @current < 0
316
+ @current = @list.size-1 if @current >= @list.size
317
+ print_items @hash
318
+
319
+ # trap arrow keys here
320
+ end
321
+ ensure
322
+ @window.destroy
323
+ end
324
+ return char
325
+ end
326
+ end
327
+
328
+ # main program
329
+
330
+ TOPLINE="| ` Menu | = Toggle | q Quit | lyra 0.1"
331
+ $sorto = "on"
332
+ $hidden = nil
333
+ $long_listing = false
334
+ $patt = nil
335
+ _LINES = FFI::NCurses.LINES-1
336
+ def create_footer_window h = 2 , w = FFI::NCurses.COLS, t = FFI::NCurses.LINES-2, l = 0
337
+ ewin = Window.new(h, w , t, l)
338
+ end
339
+ def create_input_window h = 1 , w = FFI::NCurses.COLS, t = FFI::NCurses.LINES-1, l = 0
340
+ ewin = Window.new(h, w , t, l)
341
+ end
342
+ # accepts user input in current window
343
+ # and returns characters after RETURN pressed
344
+ def getchars win, max=20
345
+ str = ""
346
+ pos = 0
347
+ filler = " "*max
348
+ pointer = win.pointer
349
+ y, x = FFI::NCurses.getyx(pointer)
350
+ while (ch = win.getkey) != FFI::NCurses::KEY_RETURN
351
+ #str << ch.chr
352
+ if ch > 27 and ch < 127
353
+ str.insert(pos, ch.chr)
354
+ pos += 1
355
+ #FFI::NCurses.waddstr(win.pointer, ch.chr)
356
+ end
357
+ case ch
358
+ when FFI::NCurses::KEY_LEFT
359
+ pos -= 1
360
+ pos = 0 if pos < 0
361
+ when FFI::NCurses::KEY_RIGHT
362
+ pos += 1
363
+ pos = str.size if pos >= str.size
364
+ when 127
365
+ pos -= 1 if pos > 0
366
+ str.slice!(pos,1) if pos >= 0 # no backspace if on first pos
367
+ when 27, FFI::NCurses::KEY_CTRL_C
368
+ return nil
369
+ end
370
+ FFI::NCurses.wmove(pointer, y,x)
371
+ FFI::NCurses.waddstr(pointer, filler)
372
+ FFI::NCurses.wmove(pointer, y,x)
373
+ FFI::NCurses.waddstr(pointer, str)
374
+ FFI::NCurses.wmove(pointer, y,pos+1) # set cursor to correct position
375
+ break if str.size >= max
376
+ end
377
+ str
378
+ end
379
+ # runs given command and returns.
380
+ # Does not wait, so command should be like an editor or be paged to less.
381
+ def shell_out command
382
+ FFI::NCurses.endwin
383
+ ret = system command
384
+ FFI::NCurses.refresh
385
+ end
386
+
387
+ ## code related to long listing of files
388
+ GIGA_SIZE = 1073741824.0
389
+ MEGA_SIZE = 1048576.0
390
+ KILO_SIZE = 1024.0
391
+
392
+ # Return the file size with a readable style.
393
+ def readable_file_size(size, precision)
394
+ case
395
+ #when size == 1 : "1 B"
396
+ when size < KILO_SIZE then "%d B" % size
397
+ when size < MEGA_SIZE then "%.#{precision}f K" % (size / KILO_SIZE)
398
+ when size < GIGA_SIZE then "%.#{precision}f M" % (size / MEGA_SIZE)
399
+ else "%.#{precision}f G" % (size / GIGA_SIZE)
400
+ end
401
+ end
402
+ ## format date for file given stat
403
+ def date_format t
404
+ t.strftime "%Y/%m/%d"
405
+ end
406
+ # clears window but leaves top line
407
+ def clearwin(pointer)
408
+ FFI::NCurses.wmove(pointer, 1,0)
409
+ FFI::NCurses.wclrtobot(pointer)
410
+ end
411
+ ##
412
+ def file_edit win, fp
413
+ #$log.debug " edit #{fp}"
414
+ editor = ENV['EDITOR'] || 'vi'
415
+ vimp = %x[which #{editor}].chomp
416
+ shell_out "#{vimp} #{fp}"
417
+ end
418
+ def file_open win, fp
419
+ unless File.exists? fp
420
+ pwd = %x[pwd]
421
+ #alert "No such file. My pwd is #{pwd} "
422
+ alert win, "No such file. My pwd is #{pwd} "
423
+ return
424
+ end
425
+ ft=%x[file #{fp}]
426
+ if ft.index("text")
427
+ file_edit win, fp
428
+ elsif ft.index(/zip/i)
429
+ shell_out "tar tvf #{fp} | less"
430
+ elsif ft.index(/directory/i)
431
+ shell_out "ls -lh #{fp} | less"
432
+ else
433
+ alert "#{fp} is not text, not opening (#{ft}) "
434
+ end
435
+ end
436
+ def file_page win, fp
437
+ unless File.exists? fp
438
+ pwd = %x[pwd]
439
+ alert "No such file. My pwd is #{pwd} "
440
+ return
441
+ end
442
+ ft=%x[file #{fp}]
443
+ if ft.index("text")
444
+ pager = ENV['PAGER'] || 'less'
445
+ vimp = %x[which #{pager}].chomp
446
+ shell_out "#{vimp} #{fp}"
447
+ elsif ft.index(/zip/i)
448
+ shell_out "tar tvf #{fp} | less"
449
+ elsif ft.index(/directory/i)
450
+ shell_out "ls -lh #{fp} | less"
451
+ else
452
+ alert "#{fp} is not text, not paging "
453
+ #use_on_file "als", fp # only zip or archive
454
+ end
455
+ end
456
+ def get_files
457
+ #files = Dir.glob("*")
458
+ files = `zsh -c 'print -rl -- *(#{$sorto}#{$hidden}M)'`.split("\n")
459
+ if $patt
460
+ files = files.grep(/#{$patt}/)
461
+ end
462
+ return files
463
+ end
464
+ # a quick simple list with highlight row, and scrolling
465
+ #
466
+ # mark directories in color
467
+ # @return start row
468
+ def listing win, path, files, cur=0, pstart
469
+ curpos = 1
470
+ width = win.width-1
471
+ y = x = 1
472
+ ht = win.height-2
473
+ #st = 0
474
+ st = pstart # previous start
475
+ pend = pstart + ht -1 # previous end
476
+ if cur > pend
477
+ st = (cur -ht) +1
478
+ elsif cur < pstart
479
+ st = cur
480
+ end
481
+ hl = cur
482
+ #if cur >= ht
483
+ #st = (cur - ht ) +1
484
+ #hl = cur
485
+ ## we need to scroll the rows
486
+ #end
487
+ y = 0
488
+ ctr = 0
489
+ filler = " "*width
490
+ files.each_with_index {|f, y|
491
+ next if y < st
492
+ colr = CP_WHITE # white on bg -1
493
+ ctr += 1
494
+ mark = " "
495
+ if y == hl
496
+ attr = FFI::NCurses::A_REVERSE
497
+ mark = ">"
498
+ curpos = ctr
499
+ else
500
+ attr = FFI::NCurses::A_NORMAL
501
+ end
502
+ fullp = path + "/" + f
503
+
504
+ if $long_listing
505
+ begin
506
+ unless File.exist? f
507
+ last = f[-1]
508
+ if last == " " || last == "@" || last == '*'
509
+ stat = File.stat(f.chop)
510
+ end
511
+ else
512
+ stat = File.stat(f)
513
+ end
514
+ f = "%10s %s %s" % [readable_file_size(stat.size,1), date_format(stat.mtime), f]
515
+ rescue Exception => e
516
+ f = "%10s %s %s" % ["?", "??????????", f]
517
+ end
518
+ end
519
+ if File.directory? fullp
520
+ #ff = "#{mark} #{f}/"
521
+ # 2018-03-12 - removed slash at end since zsh puts it there
522
+ ff = "#{mark} #{f}"
523
+ colr = CP_BLUE # blue on background color_pair COLOR_PAIR
524
+ attr = attr | FFI::NCurses::A_BOLD
525
+ elsif File.executable? fullp
526
+ ff = "#{mark} #{f}*"
527
+ colr = CP_WHITE # yellow on background color_pair COLOR_PAIR
528
+ attr = attr | FFI::NCurses::A_BOLD
529
+ else
530
+ ff = "#{mark} #{f}"
531
+ end
532
+ win.printstring(ctr, x, filler, colr )
533
+ win.printstring(ctr, x, ff, colr, attr)
534
+ break if ctr >= ht
535
+ }
536
+ #curpos = cur + 1
537
+ #if curpos > ht
538
+ #curpos = ht
539
+ #end
540
+ #statusline(win, "#{cur+1}/#{files.size} #{files[cur]}. cur = #{cur}, pos:#{curpos},ht = #{ht} , hl #{hl}")
541
+ statusline(win, "#{cur+1}/#{files.size} #{files[cur]}. (#{$sorto}) ")
542
+ FFI::NCurses.wmove(win.pointer, curpos , 0) # +1 depends on offset of ctr
543
+ win.wrefresh
544
+ return st
545
+ end
546
+ def statusline win, str
547
+ win.printstring(win.height-1, 2, str, 1) # white on default
548
+ end
549
+ def topline win, str, column = 0
550
+ # LINES-2 prints on second last line so that box can be seen
551
+ win.printstring( 0, 0, " "*(win.width), 6, REVERSE)
552
+ str.split("|").each_with_index {|s,ix|
553
+ _color = 5
554
+ _color = 6 if ix%2==0
555
+ win.printstring( 0,column, s, _color, REVERSE)
556
+ column += s.length+1
557
+ }
558
+ end
559
+
560
+ def alert str
561
+ win = create_footer_window
562
+ # 10 is too much BLACK on CYAN
563
+ FFI::NCurses.wbkgd(win.pointer, FFI::NCurses.COLOR_PAIR(12))
564
+ win.printstring(0,1, str)
565
+ win.wrefresh
566
+ win.getkey
567
+ win.destroy
568
+ end
569
+ def main_menu
570
+ h = { :s => :sort_menu, :M => :newdir, "%" => :newfile }
571
+ m = Menu.new "Main Menu", h
572
+ ch = m.getkey
573
+ return nil if !ch
574
+
575
+ binding = h[ch]
576
+ binding = h[ch.to_sym] unless binding
577
+ if binding
578
+ if respond_to?(binding, true)
579
+ send(binding)
580
+ end
581
+ end
582
+ return ch, binding
583
+ end
584
+
585
+ def sort_menu
586
+ lo = nil
587
+ h = { :n => :newest, :a => :accessed, :o => :oldest,
588
+ :l => :largest, :s => :smallest , :m => :name , :r => :rname, :d => :dirs, :c => :clear }
589
+ m = Menu.new "Sort Menu", h
590
+ ch = m.getkey
591
+ return nil if !ch
592
+ menu_text = h[ch.to_sym]
593
+ case menu_text
594
+ when :newest
595
+ lo="om"
596
+ when :accessed
597
+ lo="oa"
598
+ when :oldest
599
+ lo="Om"
600
+ when :largest
601
+ lo="OL"
602
+ when :smallest
603
+ lo="oL"
604
+ when :name
605
+ lo="on"
606
+ when :rname
607
+ lo="On"
608
+ when :dirs
609
+ lo="/"
610
+ when :clear
611
+ lo=""
612
+ end
613
+ ## This needs to persist and be a part of all listings, put in change_dir.
614
+ $sorto = lo
615
+ #$files = `zsh -c 'print -rl -- *(#{lo}#{$hidden}M)'`.split("\n") if lo
616
+ end
617
+
618
+ begin
619
+ init_curses
620
+ win = Window.new
621
+ pointer = win.pointer
622
+ $ht = win.height
623
+ $wid = win.width
624
+ $pagecols = $ht / 2
625
+ $spacecols = $ht
626
+ win.printstr("Press Ctrl-Q to quit #{win.height}:#{win.width}", win.height-1, 20)
627
+
628
+ path = File.expand_path("./")
629
+ #win.printstring(0,0, "PATH: #{path} #{TOPLINE}",0)
630
+ topline(win, "PATH: #{path} #{TOPLINE}",0)
631
+ files = get_files
632
+ current = 0
633
+ prevstart = listing(win, path, files, current, 0)
634
+
635
+ ch = 0
636
+ xx = 1
637
+ yy = 1
638
+ y = x = 1
639
+ while (ch = win.getkey) != 113
640
+ #y, x = win.getbegyx(pointer)
641
+ old_y, old_x = y, x
642
+ case ch
643
+ when FFI::NCurses::KEY_RIGHT
644
+ # if directory then open it
645
+ fullp = path + "/" + files[current]
646
+ if File.directory? fullp
647
+ Dir.chdir(files[current])
648
+ $patt = nil
649
+ path = Dir.pwd
650
+ #win.printstring(0,0, "PATH: #{path} ",0)
651
+ #win.printstring(0,0, "PATH: #{path} #{TOPLINE}",0)
652
+ topline(win, "PATH: #{path} #{TOPLINE}",0)
653
+ files = get_files
654
+ current = 0
655
+ FFI::NCurses.wclrtobot(pointer)
656
+ #win.wclrtobot
657
+ elsif File.readable? fullp
658
+ file_page win, fullp
659
+ win.wrefresh
660
+ # open file
661
+ end
662
+ x += 1
663
+ when FFI::NCurses::KEY_LEFT
664
+ # go back higher level
665
+ oldpath = path
666
+ Dir.chdir("..")
667
+ path = Dir.pwd
668
+ $patt = nil
669
+ #win.printstring(0,0, "PATH: #{path} #{TOPLINE}",0)
670
+ topline(win, "PATH: #{path} #{TOPLINE}",0)
671
+ files = get_files
672
+ # when going up, keep focus on the dir we came from
673
+ current = files.index(File.basename(oldpath) + "/")
674
+ current = 0 if current.nil? or current == -1
675
+ #win.wclrtobot
676
+ FFI::NCurses.wclrtobot(pointer)
677
+ x -= 1
678
+ when FFI::NCurses::KEY_RETURN
679
+ # if directory then open it
680
+ fullp = path + "/" + files[current]
681
+ if File.directory? fullp
682
+ Dir.chdir(files[current])
683
+ $patt = nil
684
+ path = Dir.pwd
685
+ #win.printstring(0,0, "PATH: #{path} ",0)
686
+ #win.printstring(0,0, "PATH: #{path} #{TOPLINE}",0)
687
+ topline(win, "PATH: #{path} #{TOPLINE}",0)
688
+ files = get_files
689
+ #files = Dir.entries("./")
690
+ #files.delete(".")
691
+ #files.delete("..")
692
+ current = 0
693
+ FFI::NCurses.wclrtobot(pointer)
694
+ #win.wclrtobot
695
+ elsif File.readable? fullp
696
+ # open file
697
+ file_open win, fullp
698
+ win.wrefresh
699
+ end
700
+ when FFI::NCurses::KEY_UP, ?k.getbyte(0)
701
+ current -=1
702
+ when FFI::NCurses::KEY_DOWN, ?j.getbyte(0)
703
+ current +=1
704
+ when FFI::NCurses::KEY_CTRL_N
705
+ current += $pagecols
706
+ when FFI::NCurses::KEY_CTRL_P
707
+ current -= $pagecols
708
+ when 32, FFI::NCurses::KEY_CTRL_D
709
+ current += $spacecols
710
+ when ?g.getbyte(0)
711
+ current = 0
712
+ when ?G.getbyte(0)
713
+ current = files.size-1
714
+ when FFI::NCurses::KEY_BACKSPACE, FFI::NCurses::KEY_CTRL_B, 127
715
+ current -= $spacecols
716
+ when FFI::NCurses::KEY_CTRL_X
717
+ when ?=.getbyte(0)
718
+ #list = ["x this", "y that","z other","a foo", "b bar"]
719
+ list = { "h" => "hidden files toggle", "l" => "long listing toggle", "z" => "the other", "a" => "another one", "b" => "yet another" }
720
+ m = Menu.new "Toggle Options", list
721
+ key = m.getkey
722
+ win.wrefresh # otherwise menu popup remains till next key press.
723
+ case key
724
+ when 'h'
725
+ $hidden = $hidden ? nil : "D"
726
+ files = get_files
727
+ clearwin(pointer)
728
+ when 'l'
729
+ $long_listing = !$long_listing
730
+ clearwin(pointer)
731
+ end
732
+ when ?/.getbyte(0)
733
+ # search grep
734
+ # this is writing over the last line of the listing
735
+ ewin = create_input_window
736
+ ewin.printstr("/", 0, 0)
737
+ #win.wmove(1, _LINES-1)
738
+ str = getchars(ewin, 10)
739
+ ewin.destroy
740
+ #alert "Got #{str}"
741
+ $patt = str #if str
742
+ files = get_files
743
+ clearwin(pointer)
744
+ when ?`.getbyte(0)
745
+ main_menu
746
+ files = get_files
747
+ clearwin(pointer)
748
+ else
749
+ alert("key #{ch} not known")
750
+ end
751
+ #win.printstr("Pressed #{ch} on #{files[current]} ", 0, 70)
752
+ current = 0 if current < 0
753
+ current = files.size-1 if current >= files.size
754
+ # listing does not refresh files, so if files has changed, you need to refresh
755
+ prevstart = listing(win, path, files, current, prevstart)
756
+ win.wrefresh
757
+ end
758
+
759
+ rescue Object => e
760
+ @window.destroy if @window
761
+ FFI::NCurses.endwin
762
+ puts e
763
+ puts e.backtrace.join("\n")
764
+ ensure
765
+ @window.destroy if @window
766
+ FFI::NCurses.endwin
767
+ puts
768
+ end
@@ -0,0 +1,5 @@
1
+ module Ncurses
2
+ module Lyra
3
+ VERSION = "0.1.1"
4
+ end
5
+ end
@@ -0,0 +1,28 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "ncurses/lyra/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "ncurses-lyra"
8
+ spec.version = Ncurses::Lyra::VERSION
9
+ spec.authors = ["kepler"]
10
+ spec.email = ["githubkepler.50s@gishpuppy.com"]
11
+
12
+ spec.summary = %q{a small fast minimal file lister and explorer}
13
+ spec.description = %q{a light fast directory explorer in ncurses}
14
+ spec.homepage = "https://github.com/mare-imbrium/ncurses-lyra"
15
+ spec.license = "MIT"
16
+
17
+
18
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
19
+ f.match(%r{^(test|spec|features)/})
20
+ end
21
+ spec.bindir = "exe"
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ["lib"]
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.16"
26
+ spec.add_development_dependency "rake", "~> 10.0"
27
+ spec.add_runtime_dependency "ffi-ncurses", ">= 0.4.0", ">= 0.4.0"
28
+ end
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ncurses-lyra
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - kepler
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-05-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: ffi-ncurses
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 0.4.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 0.4.0
55
+ description: a light fast directory explorer in ncurses
56
+ email:
57
+ - githubkepler.50s@gishpuppy.com
58
+ executables:
59
+ - lyra.rb
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - Gemfile
65
+ - LICENSE
66
+ - LICENSE.txt
67
+ - README.md
68
+ - Rakefile
69
+ - exe/lyra.rb
70
+ - lib/ncurses/lyra/version.rb
71
+ - ncurses-lyra.gemspec
72
+ homepage: https://github.com/mare-imbrium/ncurses-lyra
73
+ licenses:
74
+ - MIT
75
+ metadata: {}
76
+ post_install_message:
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubyforge_project:
92
+ rubygems_version: 2.7.6
93
+ signing_key:
94
+ specification_version: 4
95
+ summary: a small fast minimal file lister and explorer
96
+ test_files: []