cygnus 0.0.2

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