cygnus 0.0.2

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,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ rbc13.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in cygnus.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Rahul Kumar
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,38 @@
1
+ # Cygnus
2
+
3
+ Finally presenting the code browser you've waited for all your life.
4
+
5
+ You spent 20 years learning vim -- ahem -- a *fraction* of vim.
6
+ You can master this tool in about 20 seconds -- just j k gg G H L M. And w.
7
+ Not even an e or b.
8
+ Only / to search not even an ? to search back.
9
+
10
+ No plugins to distract you, no complex config files, in fact no config files at all. No battling with pathogen, bundle, vundle, vimballs and git submodules.
11
+
12
+ And best of all a colorscheme to die for. Go ahead, just `gem install` this baby and be set for life.
13
+
14
+ ## Installation
15
+
16
+ $ gem install cygnus
17
+
18
+ ## Usage
19
+
20
+ cygnus
21
+
22
+ or map it to an alias and use the alias
23
+
24
+ alias x=~/path_to_cygnus/cygnus
25
+
26
+ ## Warning
27
+
28
+ This baby is seductive and addictive.
29
+
30
+ ## Contributing
31
+
32
+ 1. Fork it
33
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
34
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
35
+ 4. Push to the branch (`git push origin my-new-feature`)
36
+ 5. Create new Pull Request
37
+
38
+ Cygnus is named after a constellation, and contains deneb, a massive star.
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,900 @@
1
+ #!/usr/bin/env ruby
2
+ # ----------------------------------------------------------------------------- #
3
+ # File: cygnus
4
+ # Description: A class that displays text using a pad.
5
+ # The motivation for this is to put formatted text and not care about truncating and
6
+ # stuff. Also, there will be only one write, not each time scrolling happens.
7
+ # I found textview code for repaint being more complex than required.
8
+ # Author: rkumar http://github.com/rkumar/mancurses/
9
+ # Date: 2011-11-09 - 16:59
10
+ # License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
11
+ # Last update: 2013-03-19 01:03
12
+ #
13
+ # == CHANGES
14
+ # == BUGS
15
+ # _ we have overridden textpad bindings yet they show / n gg etc.
16
+ # Need to write our own from the hash bindings and add form ones to it.
17
+ # _ our bindings from the hash do not show up..
18
+ # _ white line at bottom and right
19
+ #
20
+ # == TODO
21
+ #
22
+ # _ show how many files 1-5 or 15 etc
23
+ # _ toggle edit mode and view mode
24
+ # _ long listing toggle + other toggles
25
+ #
26
+ # --- release first gem version after above are done ---
27
+ #
28
+ # _ we can try no truncating at all, the next row will automatically truncate
29
+ # for first row use full length to truncated, second row use full - first row etc
30
+ # that way the lower rows will not get truncated, if there are some below. They
31
+ # can continue displaying.
32
+ # _ need to get the keys, in fact make this the first thing on top.
33
+ # such as parent. pop. + etc. bookmark etc
34
+ # _ work on common file, so functionality of cetus can be shared easily
35
+ # x use same keys as cetus for parent pop etc , . ' etc maybe even backtick for menu
36
+ # _ jump to single pane and back.
37
+ # _ save bookmarks
38
+ #
39
+ #
40
+ # when moving right, also don't pan straight away
41
+ # x add mappings and process key in handle_keys and other widget things
42
+ # x user can put text or list
43
+ # . handle putting data again and overwriting existing
44
+ # x search and other features
45
+ # - can pad movement and other ops be abstracted into module for reuse
46
+ # / get scrolling like in vim (C-f e y b d)
47
+ # - alert issue of leaving a blank is poss due to using prefresh i/o copywin
48
+ #
49
+ # == TODO 2013-03-07 - 20:34
50
+ # _ key bindings not showing up -- bind properly
51
+ # _ F1 screen leaves everything blank, so does bindings
52
+ # ----------------------------------------------------------------------------- #
53
+ #
54
+ require 'rbcurse'
55
+ require 'cygnus'
56
+ require 'cygnus/textpad'
57
+ require 'fileutils'
58
+ include Cygnus
59
+
60
+
61
+ class DirRenderer
62
+ def render pad, lineno, text
63
+ bg = :black
64
+ fg = :white
65
+ att = NORMAL
66
+ cp = $datacolor
67
+ if File.directory? text
68
+ fg = :blue
69
+ att = BOLD
70
+ text << "/" unless text[-1] == "/"
71
+ cp = get_color($datacolor, fg, bg)
72
+ elsif File.symlink? text
73
+ fg = :magenta
74
+ #text << "->"
75
+ text = text + "->"
76
+ cp = get_color($datacolor, fg, bg)
77
+ elsif File.executable? text
78
+ fg = :yellow
79
+ #text << "*"
80
+ text = text + "*"
81
+ cp = get_color($datacolor, fg, bg)
82
+ elsif text =~ /(~|swp)$/
83
+ fg = :blue
84
+ #att = BOLD
85
+ cp = get_color($datacolor, fg, bg)
86
+ else
87
+ end
88
+ FFI::NCurses.wattron(pad,FFI::NCurses.COLOR_PAIR(cp) | att)
89
+ FFI::NCurses.mvwaddstr(pad, lineno, 0, text)
90
+ FFI::NCurses.wattroff(pad,FFI::NCurses.COLOR_PAIR(cp) | att)
91
+
92
+ end # def
93
+ end # class
94
+ def padpopuplist list, config={}, &block
95
+ raise ArgumentError, "Nil list received by popuplist" unless list
96
+ #require 'cygnus/textpad'
97
+
98
+ max_visible_items = config[:max_visible_items]
99
+ row = config[:row] || 5
100
+ col = config[:col] || 5
101
+ relative_to = config[:relative_to]
102
+ if relative_to
103
+ layout = relative_to.form.window.layout
104
+ row += layout[:top]
105
+ col += layout[:left]
106
+ end
107
+ config.delete :relative_to
108
+ longest = list.max_by(&:length)
109
+ width = config[:width] || longest.size()+2 # borders take 2
110
+ if config[:title]
111
+ width = config[:title].size + 2 if width < config[:title].size
112
+ end
113
+ height = config[:height]
114
+ height ||= [max_visible_items || 10+2, list.length+2].min
115
+ #layout(1+height, width+4, row, col)
116
+ layout = { :height => 0+height, :width => 0+width, :top => row, :left => col }
117
+ window = VER::Window.new(layout)
118
+ form = RubyCurses::Form.new window
119
+
120
+ listconfig = config[:listconfig] || {}
121
+ listconfig[:list] = list
122
+ listconfig[:width] = width
123
+ listconfig[:height] = height
124
+ #listconfig[:selection_mode] ||= :single
125
+ listconfig.merge!(config)
126
+ listconfig.delete(:row);
127
+ listconfig.delete(:col);
128
+ # trying to pass populists block to listbox
129
+ #lb = RubyCurses::List.new form, listconfig, &block
130
+ lb = Cygnus::TextPad.new form, listconfig, &block
131
+ #lb = Cygnus::TextPad.new form, :height => height, :width => width, :row => 0, :col => 0 , :title => "A title", :name => "popup"
132
+ lb.text(list)
133
+ #
134
+ #window.bkgd(Ncurses.COLOR_PAIR($reversecolor));
135
+ form.repaint
136
+ Ncurses::Panel.update_panels
137
+ begin
138
+ while((ch = window.getchar()) != 999 )
139
+ case ch
140
+ when -1
141
+ next
142
+ when ?\C-q.getbyte(0)
143
+ break
144
+ else
145
+ lb.handle_key ch
146
+ form.repaint
147
+ if ch == 13 || ch == 10
148
+ return lb.current_index #if lb.selection_mode != :multiple
149
+
150
+ # x = lb.selected_indices
151
+ return x if x
152
+ x = lb.current_index unless x
153
+ return [x]
154
+ # if multiple selection, then return list of selected_indices and don't catch 32
155
+ ##elsif ch == 32 # if single selection
156
+ return lb.current_index #if lb.selection_mode != :multiple
157
+ end
158
+ #yield ch if block_given?
159
+ end
160
+ end
161
+ ensure
162
+ window.destroy
163
+ end
164
+ return nil
165
+ end
166
+ def main_menu
167
+ h = {
168
+ :a => :ack,
169
+ "/" => :ffind,
170
+ :l => :locate,
171
+ :v => :viminfo,
172
+ :z => :z_interface,
173
+ :d => :child_dirs,
174
+ :r => :recent_files,
175
+ :t => :dirtree,
176
+ "4" => :tree,
177
+ :s => :sort_menu,
178
+ :F => :filter_menu,
179
+ :c => :command_menu ,
180
+ :B => :bindkey_ext_command,
181
+ :M => :newdir,
182
+ "%" => :newfile,
183
+ :X => :file_navigator,
184
+ :x => :extras
185
+ }
186
+ ch, binding = menu "Main Menu", h
187
+ #alert "Menu got #{ch}, #{binding}" if ch
188
+ end
189
+ ##
190
+ #
191
+ # make one which takes a single key and returns that key
192
+ # Then call that from menu, indexed list etc, so we are not copying
193
+ # that same thing over and over for tiny variations.
194
+ #
195
+ def menu title, hash, config={}, &block
196
+ raise ArgumentError, "Nil hash received by menu" unless hash
197
+ list = []
198
+ hash.each_pair { |k, v| list << " #[fg=yellow, bold] #{k} #[/end] #[fg=green] #{v} #[/end]" }
199
+ # s="#[fg=green]hello there#[fg=yellow, bg=black, dim]"
200
+ config[:title] = title
201
+ ch = padpopup list, config, &block
202
+ return unless ch
203
+ if ch.size > 1
204
+ # could be a string due to pressing enter
205
+ # but what if we format into multiple columns
206
+ ch = ch.strip[0]
207
+ end
208
+
209
+ binding = hash[ch]
210
+ binding = hash[ch.to_sym] unless binding
211
+ if binding
212
+ if respond_to?(binding, true)
213
+ send(binding)
214
+ end
215
+ end
216
+ return ch, binding
217
+ end
218
+
219
+ # pops up a list, taking a single key and returning if it is in range of 33 and 126
220
+ def padpopup list, config={}, &block
221
+ #require 'cygnus/textpad'
222
+ max_visible_items = config[:max_visible_items]
223
+ row = config[:row] || 5
224
+ col = config[:col] || 5
225
+ relative_to = config[:relative_to]
226
+ if relative_to
227
+ layout = relative_to.form.window.layout
228
+ row += layout[:top]
229
+ col += layout[:left]
230
+ end
231
+ config.delete :relative_to
232
+ # still has the formatting in the string so length is wrong.
233
+ #longest = list.max_by(&:length)
234
+ width = config[:width] || 60
235
+ if config[:title]
236
+ width = config[:title].size + 2 if width < config[:title].size
237
+ end
238
+ height = config[:height]
239
+ height ||= [max_visible_items || 25, list.length+2].min
240
+ #layout(1+height, width+4, row, col)
241
+ layout = { :height => 0+height, :width => 0+width, :top => row, :left => col }
242
+ window = VER::Window.new(layout)
243
+ form = RubyCurses::Form.new window
244
+
245
+ ## added 2013-03-13 - 18:07 so caller can be more specific on what is to be returned
246
+ valid_keys_int = config.delete :valid_keys_int
247
+ valid_keys_char = config.delete :valid_keys_char
248
+
249
+ listconfig = config[:listconfig] || {}
250
+ #listconfig[:list] = list
251
+ listconfig[:width] = width
252
+ listconfig[:height] = height
253
+ #listconfig[:selection_mode] ||= :single
254
+ listconfig.merge!(config)
255
+ listconfig.delete(:row);
256
+ listconfig.delete(:col);
257
+ # trying to pass populists block to listbox
258
+ #lb = RubyCurses::List.new form, listconfig, &block
259
+ lb = Cygnus::TextPad.new form, listconfig, &block
260
+ #lb = Cygnus::TextPad.new form, :height => height, :width => width, :row => 0, :col => 0 , :title => "A title", :name => "popup"
261
+ #lb.text(list)
262
+ ## TODO FIXME hardocoding tmux format
263
+ lb.formatted_text(list, :tmux)
264
+ #
265
+ #window.bkgd(Ncurses.COLOR_PAIR($reversecolor));
266
+ form.repaint
267
+ Ncurses::Panel.update_panels
268
+ if valid_keys_int.nil? && valid_keys_char.nil?
269
+ valid_keys_int = (32..126)
270
+ end
271
+
272
+ begin
273
+ while((ch = window.getchar()) != 999 )
274
+
275
+ # if a char range or array has been sent, check if the key is in it and send back
276
+ # else just stay here
277
+ if valid_keys_char
278
+ if ch > 32 && ch < 127
279
+ chr = ch.chr
280
+ return chr if valid_keys_char.include? chr
281
+ end
282
+ end
283
+
284
+ # if the user specified an array or range of ints check against that
285
+ # therwise use the range of 33 .. 126
286
+ return ch.chr if valid_keys_int.include? ch
287
+
288
+ case ch
289
+ when -1
290
+ alert "does it really come here? -1 XXXXXXX"
291
+ next
292
+ when ?\C-q.getbyte(0)
293
+ break
294
+ else
295
+ #if ch > 32 && ch < 127
296
+ #ch = ch.chr
297
+ #return ch
298
+ #end
299
+ if ch == 13 || ch == 10
300
+ s = lb.current_value.to_s # .strip #if lb.selection_mode != :multiple
301
+ return s
302
+ #ch = s[0].ord
303
+ end
304
+ # close if escape or double escape
305
+ if ch == 27 || ch == 2727
306
+ return nil
307
+ end
308
+ lb.handle_key ch
309
+ form.repaint
310
+ end
311
+ end
312
+ ensure
313
+ window.destroy
314
+ end
315
+ return nil
316
+ end
317
+ # pops up a list, taking a single key and returning if it is in range of 33 and 126
318
+ def full_indexed_list dir=Dir.pwd, config={}, &block
319
+ #require 'cygnus/textpad'
320
+ config[:row] ||= 0
321
+ config[:col] ||= 0
322
+ config[:width] ||= FFI::NCurses.COLS - config[:col]
323
+ if config[:title]
324
+ width = config[:title].size + 2 if width < config[:title].size
325
+ end
326
+ height = config[:height]
327
+ #height ||= [max_visible_items || 25, list.length+2].min
328
+ height ||= FFI::NCurses.LINES - config[:row]
329
+ config[:height] = height
330
+ config[:name] = "fitp"
331
+ #layout(1+height, width+4, row, col)
332
+
333
+
334
+ #config[:suppress_border] = true
335
+ $files = get_file_list(dir)
336
+ lb = Cygnus::TextPad.new @form, config, &block
337
+ form_bindings @form
338
+ $grows = lb.rows
339
+ $gviscols ||= 3
340
+ $pagesize = $grows * $gviscols
341
+
342
+ while true
343
+ break if $quitting
344
+ if $patt
345
+ if $ignorecase
346
+ $view = $files.grep(/#{$patt}/i)
347
+ else
348
+ $view = $files.grep(/#{$patt}/)
349
+ end
350
+ else
351
+ $view = $files
352
+ end
353
+ fl=$view.size
354
+ $sta = 0 if $sta < 0
355
+ $cursor = fl -1 if $cursor >= fl
356
+ $cursor = 0 if $cursor < 0
357
+ $sta = calc_sta $cursor
358
+ $log.debug "XXX: sta is #{$sta}, size is #{fl}"
359
+ $viewport = $view[$sta, $pagesize]
360
+ fin = $sta + $viewport.size
361
+ #alist = index_this_list list
362
+ #alist = columnate alist, $grows
363
+ # NOTE XXX we are supposed to page the list with space, using pagelist
364
+ alist = columnate_with_indexing $viewport, $grows
365
+ # trying to pass populists block to listbox
366
+ #lb.text(list)
367
+ ## TODO FIXME hardocoding tmux format
368
+ lb.formatted_text(alist, :tmux)
369
+ @header.text_center($title || Dir.pwd)
370
+ @header.text_right "#{$sta}, #{$cursor}"
371
+ #
372
+ @form.repaint
373
+ Ncurses::Panel.update_panels
374
+
375
+ begin
376
+ #while((ch = @window.getchar()) != 999 )
377
+ ch = @window.getchar()
378
+
379
+ # if a char range or array has been sent, check if the key is in it and send back
380
+ # else just stay here
381
+ #if ( ( ch >= ?a.ord && ch <= ?z.ord ) || ( ch >= ?A.ord && ch <= ?Z.ord ) )
382
+ if ( ( ch >= ?a.ord && ch <= ?z.ord ) || ( ch == ?Z.ord ) )
383
+ #alert "char got (#{ch.chr})"
384
+ chr = ch.chr
385
+ select_hint $viewport, chr
386
+ #ix = get_index chr
387
+ #if ix
388
+ #file = $viewport[ix]
389
+ #if file
390
+ #file = File.expand_path file
391
+ #if File.directory? file
392
+ #change_dir file
393
+ #next
394
+ #elsif File.file? file
395
+ #pad_display_file file, DefaultFileRenderer.new
396
+ #next
397
+ #else
398
+ #alert "Some new error has crept in. Neither file not directory: (#{file})"
399
+ #$log.warn "XXXXX: FILE Neither file not directory: (#{file})"
400
+ #end
401
+ #end
402
+ #else
403
+ #end
404
+ next
405
+ end
406
+
407
+
408
+ case ch
409
+ when 32, "SPACE"
410
+ next_page
411
+ next
412
+ when ?\C-q.getbyte(0)
413
+ break
414
+ else
415
+ # close if escape or double escape
416
+ if ch == 27 || ch == 2727
417
+ # this just closes the app ! since my finger remains on Ctrl which is Escape
418
+ c_refresh
419
+ next
420
+ end
421
+ # lets check our own bindings so textpad doesn't take over
422
+ # Either that or check form's first
423
+ # but this way we can just reuse from cetus
424
+ retval = c_process_key ch
425
+ next if retval
426
+
427
+ retval = @form.handle_key ch #if retval == :UNHANDLED
428
+ next if retval != :UNHANDLED
429
+ $log.debug "XXXX form returned #{retval} for #{ch}"
430
+ #alert "got key before lb.handle #{ch.chr}"
431
+ retval = lb.handle_key ch if retval.nil? || retval == :UNHANDLED
432
+ # if retval == :UNHANDLED
433
+ #alert "got key in unhdnalde lb.handle #{ch}, #{retval}"
434
+ $log.debug "XXXX textpad returned #{retval} for #{ch}"
435
+ end
436
+
437
+ @form.repaint
438
+ #end # while getchar
439
+ ensure
440
+ #@window.destroy if destroying
441
+ end
442
+ end # while true
443
+ return nil
444
+ end
445
+ def key_down
446
+ $cursor += 1
447
+ alert "down #{$cursor}"
448
+ end
449
+ def key_up
450
+ $cursor -= 1
451
+ end
452
+ def c_process_key c
453
+ ch = keycode_tos c
454
+ h = @bindings
455
+ case ch
456
+ when "C-m", "13", "10"
457
+ ch = "ENTER"
458
+ when "339"
459
+ ch = "page_up"
460
+ when "338"
461
+ ch = "page_down"
462
+ when "279"
463
+ ch = "home"
464
+ when "277"
465
+ ch = "end"
466
+ end
467
+
468
+ binding = h[ch]
469
+ binding = h[ch.to_sym] unless binding
470
+ if binding
471
+ if respond_to?(binding, true)
472
+ send(binding)
473
+ return true
474
+ end
475
+ end
476
+ return false
477
+ end
478
+ def pad_display_file filename, renderer, config={}, &block
479
+ #require 'cygnus/textpad'
480
+ row = config[:row] || 0
481
+ col = config[:col] || 0
482
+ width = config[:width] || FFI::NCurses.COLS
483
+ height = config[:height]
484
+ height ||= FFI::NCurses.LINES - 1
485
+ #layout(1+height, width+4, row, col)
486
+ layout = { :height => 0+height, :width => 0+width, :top => row, :left => col }
487
+ window = VER::Window.new(layout)
488
+ form = RubyCurses::Form.new window
489
+
490
+ listconfig = config[:listconfig] || {}
491
+ #listconfig[:list] = list
492
+ listconfig[:width] = width
493
+ listconfig[:height] = height
494
+ #listconfig[:selection_mode] ||= :single
495
+ listconfig.merge!(config)
496
+ listconfig.delete(:row);
497
+ listconfig.delete(:col);
498
+ listconfig[:filename] = filename
499
+ listconfig[:title] = filename
500
+ listconfig[:row] = 0
501
+ listconfig[:col] = 0
502
+ #alist = index_this_list list
503
+ #alist = columnate alist, $grows
504
+ # NOTE XXX we are supposed to page the list with space, using pagelist
505
+ #alist = columnate_with_indexing list, $grows
506
+ # trying to pass populists block to listbox
507
+ #lb = RubyCurses::List.new form, listconfig, &block
508
+ lb = Cygnus::TextPad.new form, listconfig, &block
509
+ lb.renderer renderer if renderer
510
+ #lb.text(list)
511
+ ## TODO FIXME hardocoding tmux format
512
+ #lb.formatted_text(alist, :tmux)
513
+ #
514
+ #window.bkgd(Ncurses.COLOR_PAIR($reversecolor));
515
+ form.repaint
516
+ Ncurses::Panel.update_panels
517
+
518
+ begin
519
+ while((ch = window.getchar()) != 999 )
520
+
521
+ case ch
522
+ when ?q.getbyte(0), ?\C-q.getbyte(0), 13, 10, 27, 2727
523
+ break
524
+ else
525
+ lb.handle_key ch
526
+ form.repaint
527
+ end
528
+ end
529
+ ensure
530
+ window.destroy
531
+ end
532
+ return nil
533
+ end
534
+ def index_this_list list
535
+ alist = []
536
+ list.each_with_index { |v, ix|
537
+ k = get_shortcut ix
538
+ #alist << " #[fg=yellow, bold] #{k} #[end] #[fg=green]#{v}#[end]"
539
+ # above gets truncated by columnate and results in errors in colorparsers etc
540
+ alist << " #{k} #{v}"
541
+ }
542
+ return alist
543
+ end
544
+
545
+ $IDX=('a'..'y').to_a
546
+ $IDX.concat ('za'..'zz').to_a
547
+ $IDX.concat ('Za'..'Zz').to_a
548
+ $IDX.concat ('ZA'..'ZZ').to_a
549
+ def indexed_list title, list, config={}, &block
550
+ raise ArgumentError, "Nil list received by indexed_list" unless list
551
+ alist = index_this_list list
552
+ longest = list.max_by(&:length)
553
+ # s="#[fg=green]hello there#[fg=yellow, bg=black, dim]"
554
+ config[:title] = title
555
+ # if width is greater than size of screen then padfresh will return -1 and nothing will print
556
+ config[:width] = [ longest.size() + 10, FFI::NCurses.COLS - 1 ].min
557
+ config[:row] = config[:col] = 0
558
+ ch = padpopup alist, config, &block
559
+ return unless ch
560
+ if ch.size > 1
561
+ # could be a string due to pressing enter
562
+ # but what if we format into multiple columns
563
+ ch = ch.strip[0]
564
+ end
565
+ ch = get_index ch
566
+ return nil unless ch
567
+
568
+ return list[ch]
569
+ end
570
+
571
+ # This displays the list provided with alpha indexing
572
+ # The user types one char to select the file which is then displayed
573
+ # using your specific open file -- which could open in an ncurses window
574
+ # or in vim or some pager in your CLI program.
575
+ #
576
+ # this is a specfic functionthat has to be implemented separately by CLI and ncurses
577
+ # programs
578
+ def show_list files=$files
579
+ file = indexed_list $title || "Some Files", files
580
+ if file
581
+ open_file file
582
+ end
583
+ end
584
+
585
+
586
+ def ischar ch
587
+ return ( ( ch >= ?a.ord && ch <= ?z.ord ) || ( ch >= ?A.ord && ch <= ?Z.ord ) )
588
+ end
589
+ def goto_bookmark ch=nil
590
+ unless ch
591
+ #ch = get_string "Enter bookmark char: "
592
+ ch = @window.getchar()
593
+ return if ch.nil? || ch == "" || !ischar(ch)
594
+ ch = ch.chr
595
+ end
596
+ if ch =~ /^[0-9A-Z]$/
597
+ d = $bookmarks[ch]
598
+ # old ones imported from lyrainfo have a : and cursor position
599
+ if d
600
+ if d.index ":"
601
+ d, filename = d.split ":"
602
+ end
603
+ if File.file? d
604
+ filename = File.basename(d)
605
+ dir = File.dirname(d)
606
+ else
607
+ dir = d
608
+ end
609
+ change_dir dir, filename
610
+ else
611
+ perror "#{ch} not a bookmark"
612
+ end
613
+ else
614
+ # goto_entry_starting_with ch
615
+ file_starting_with ch
616
+ end
617
+ end
618
+ def file_starting_with fc
619
+ ix = return_next_match(method(:file_matching?), "^#{fc}")
620
+ if ix
621
+ #$view.goto_line ix
622
+ goto_line ix
623
+ else
624
+ perror "Got no match for #{fc}"
625
+ end
626
+ end
627
+ def file_matching? file, patt
628
+ # WARN crashes with [ or other characters
629
+ file =~ /#{patt}/
630
+ end
631
+ def filelist
632
+ return $files
633
+ end
634
+
635
+ ## generic method to take cursor to next position for a given condition
636
+ def return_next_match binding, *args
637
+ first = nil
638
+ ix = 0
639
+ filelist().each_with_index do |elem,ii|
640
+ if binding.call(elem.to_s, *args)
641
+ first ||= ii
642
+ if ii > $cursor
643
+ ix = ii
644
+ break
645
+ end
646
+ end
647
+ end
648
+ return first if ix == 0
649
+ return ix
650
+ end
651
+ @dir_stack = []
652
+ def change_dir dir, filename=nil
653
+ @dir_stack << Dir.pwd unless @dir_stack.include? Dir.pwd
654
+ $sta = $cursor = 0
655
+ if dir.index(":")
656
+ dir, $cursor = dir.split(":")
657
+ end
658
+ FileUtils.cd dir
659
+ display_dir filename
660
+ # TODO also position cursor on file if passed
661
+ #display_file filename
662
+ end
663
+ def display_file filename
664
+ if filename && File.exists?(filename) && !File.directory?(filename)
665
+ else
666
+ return
667
+ end
668
+ pad_display_file filename, DefaultFileRenderer.new
669
+ return
670
+ end
671
+ # sets $files - i have no other way till we make this into a class
672
+ # FIXME take an optional file name and position cursor on that position
673
+ def display_dir filename=nil
674
+ $sta = $cursor = 0
675
+ list = get_file_list
676
+ if filename
677
+ ix = list.index filename
678
+ if ix
679
+ ## FIXME sta needs to be calced based on cursor
680
+ $cursor = ix
681
+ else
682
+ # could be a curpos passed in, old style bookmark
683
+ $cursor = filename.to_i
684
+ end
685
+ end
686
+ #alist = index_this_list list
687
+ alist = columnate_with_indexing list, $grows
688
+ lb = @form.by_name["fitp"]
689
+ lb.formatted_text(alist, :tmux)
690
+ lb.title = Dir.pwd
691
+ $title = nil
692
+ lb.padrefresh
693
+ @header.text_center Dir.pwd
694
+ @header.text_right "#{$sta}, #{$cursor}"
695
+ @form.repaint
696
+ $files = list
697
+ return list
698
+ end
699
+ def get_file_list dir="."
700
+ list = Dir.entries dir
701
+ list.delete_at 0
702
+ list.reject! {|x| x[0] == "." && x[1] != "." } unless $hidden
703
+ list.collect! do |e|
704
+ if File.directory? e
705
+ e << "/"
706
+ else
707
+ e
708
+ end
709
+ end
710
+ return list
711
+ end
712
+ def pop_dir
713
+ d = @dir_stack.pop
714
+ return unless d
715
+ FileUtils.cd d
716
+ display_dir
717
+ end
718
+ def filetype f
719
+ text = `file #{f}`
720
+ if text.include? "text"
721
+ return :html if text.include?("HTML")
722
+ return :text
723
+ elsif text =~ /(archive|zip)/
724
+ return :zip
725
+ else
726
+ return :unknown
727
+ end
728
+ end
729
+ def get_file_contents f
730
+ ft = filetype f
731
+ case ft
732
+ when :text
733
+ lines = File.open(f,"r").readlines
734
+ # check line 1
735
+ if lines.first =~ /[\t\r]/
736
+ $log.debug "Found carriage returns (or tabs) in #{f}. stripping ..."
737
+ lines = lines.collect do |line|
738
+ line.gsub(/[\r\t]/,"")
739
+ end
740
+ end
741
+ return lines
742
+ when :html
743
+ t = `html2text #{f} 2>&1`.split "\n"
744
+ if t.first.include? "command not found"
745
+ return File.open(f,"r").readlines
746
+ end
747
+ return t
748
+ when :zip
749
+ return `tar ztvf #{f}`.split("\n")
750
+ else
751
+ return ["unknown type", `file #{f}`]
752
+ end
753
+ end
754
+ def show
755
+ list = get_file_list
756
+ ix = padpopuplist list, :title => "Files", :bgcolor => :blue, :color => :white
757
+ return unless ix
758
+ open_file list[ix]
759
+ #alert "Got #{list[ix]}" if ix
760
+ end
761
+ def open_file filename
762
+ filename = File.expand_path filename
763
+ if File.directory? filename
764
+ change_dir filename
765
+ else
766
+ display_file filename
767
+ end
768
+ end
769
+ def form_bindings form
770
+ @bindings = {
771
+ "`" => "main_menu",
772
+ "=" => "toggle_menu",
773
+ "!" => "command_mode",
774
+ "@" => "selection_mode_toggle",
775
+ "M-a" => "select_all",
776
+ "M-A" => "unselect_all",
777
+ "," => "goto_parent_dir",
778
+ "+" => "goto_dir",
779
+ "." => "pop_dir",
780
+ ":" => "subcommand",
781
+ "'" => "goto_bookmark",
782
+ "/" => "enter_regex",
783
+ "M-p" => "prev_page",
784
+ "M-n" => "next_page",
785
+ "SPACE" => "next_page",
786
+ "M-f" => "select_visited_files",
787
+ "M-d" => "select_used_dirs",
788
+ "M-b" => "select_bookmarks",
789
+ "M-m" => "create_bookmark",
790
+ "M-M" => "show_marks",
791
+ "C-c" => "escape",
792
+ "esc" => "escape",
793
+ "TAB" => "views",
794
+ "C-i" => "views",
795
+ "?" => "dirtree",
796
+ "ENTER" => "select_current",
797
+ "D" => "delete_file",
798
+ "M" => "file_actions most",
799
+ "Q" => "quit_command",
800
+ "right" => "column_next",
801
+ "left" => "column_next 1",
802
+ "C-x" => "file_actions",
803
+ "M--" => "columns_incdec -1",
804
+ "M-+" => "columns_incdec 1",
805
+ "S" => "command_file list y ls -lh",
806
+ "L" => "command_file Page n less",
807
+ "C-d" => "cursor_scroll_dn",
808
+ "C-b" => "cursor_scroll_up",
809
+ "up" => "cursor_up",
810
+ "down" => "cursor_dn",
811
+ "C-space" => "visual_mode_toggle",
812
+
813
+ "home" => "goto_top",
814
+ "end" => "goto_end",
815
+ "page_up" => "prev_page",
816
+ "page_down" => "next_page",
817
+ "M-?" => "print_help",
818
+ "F1" => "print_help",
819
+ "F2" => "child_dirs",
820
+ "F3" => "dirtree",
821
+ "F4" => "tree"
822
+
823
+ }
824
+ ## remember that widgets map keys on handle_key, as late as possible
825
+ # so these need to go on form not object, and form needs to be called first
826
+ # since we really don't care for textpads bindings
827
+ form.bind_key(?`, 'main_menu') { main_menu }
828
+ form.bind_key(?+, 'goto_dir') { goto_dir }
829
+ form.bind_key(?\,, 'goto_parent_dir') { goto_parent_dir }
830
+ form.bind_key(?\M-n, 'next_page') { next_page }
831
+ form.bind_key(?\M-p, 'prev_page') { prev_page }
832
+ form.bind_key(32, 'next_page') { next_page }
833
+ form.bind_key(?\M--, 'decrease columns') { columns_incdec -1 }
834
+ form.bind_key(?\M-+, 'increase columns') { columns_incdec 1 }
835
+ #form.bind_key(KEY_RIGHT, 'increase columns') { column_next }
836
+ #form.bind_key(KEY_LEFT, 'increase columns') { column_next 1 }
837
+ #form.bind_key(KEY_UP, 'up') { key_up }
838
+ # getting overridden by textpad
839
+ #form.bind_key(KEY_DOWN, 'down') { key_down }
840
+ form.bind_key(?/, 'ask regex') { enter_regex }
841
+ end
842
+ def goto_top
843
+ $sta = $cursor = 0
844
+ end
845
+ def goto_end
846
+ $cursor = $view.size - 1
847
+ # FIXME cursor should be visible, take page calc logic from zfm or lyra accounting for 1 offset
848
+ $sta = calc_sta $cursor
849
+ end
850
+ # calculate the starting position of the page given
851
+ # a line to go to
852
+ # check edge case of pagesize iteself 87 it should go on page2 not page 1
853
+ def calc_sta cur
854
+ pages = (cur * 1.001 / $pagesize).ceil
855
+ pages -= 1 if pages > 0
856
+ return pages * $pagesize
857
+ end
858
+ include RubyCurses
859
+
860
+ begin
861
+ # Initialize curses
862
+ VER::start_ncurses # this is initializing colors via ColorMap.setup
863
+ logfilename = File.join(ENV["LOGDIR"] || "./" ,"rbc13.log")
864
+ $log = Logger.new(logfilename)
865
+ $log.level = Logger::DEBUG
866
+ @window = VER::Window.root_window
867
+ @form = RubyCurses::Form.new @window
868
+
869
+ catch(:close) do
870
+ ##@window = VER::Window.root_window
871
+ $catch_alt_digits = true; # emacs like alt-1..9 numeric arguments
872
+ #install_help_text my_help_text
873
+
874
+ config_read
875
+ @header = app_header "cygnus", :text_center => Dir.pwd, :text_right =>"^q Quit" , :color => :green, :bgcolor => :black , :attr => :bold
876
+ full_indexed_list Dir.pwd, :row => 1, :col => 0, :suppress_border => true
877
+ config_write if $writing
878
+ #@form = Form.new @window
879
+ #@form.bind_key(KEY_F1, 'help'){ display_app_help }
880
+ #@form.bind_key(?\M-c, 'select class') do
881
+ #ask_classes
882
+ #end
883
+ #@form.bind_key(?', 'select bookmark') do
884
+ #ask_bookmark
885
+ #end
886
+ #@form.bind_key(?\M-d, 'View history') do
887
+ #popup_history
888
+ #end
889
+
890
+ end
891
+ rescue => ex
892
+ textdialog ["Error in cygnus: #{ex} ", *ex.backtrace], :title => "Exception"
893
+ $log.debug( ex) if ex
894
+ $log.debug(ex.backtrace.join("\n")) if ex
895
+ ensure
896
+ @window.destroy if !@window.nil?
897
+ VER::stop_ncurses
898
+ p ex if ex
899
+ p(ex.backtrace.join("\n")) if ex
900
+ end