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.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +38 -0
- data/Rakefile +1 -0
- data/bin/cygnus +900 -0
- data/cygnus.gemspec +23 -0
- data/lib/cygnus.rb +1520 -0
- data/lib/cygnus/textpad.rb +846 -0
- data/lib/cygnus/version.rb +3 -0
- metadata +95 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/cygnus
ADDED
@@ -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
|