mancurses 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (8) hide show
  1. data/.gitignore +19 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +43 -0
  5. data/Rakefile +1 -0
  6. data/bin/mancurses +724 -0
  7. data/mancurses.gemspec +21 -0
  8. metadata +109 -0
data/.gitignore ADDED
@@ -0,0 +1,19 @@
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
+ color.2
19
+ rbc13.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in mancurses.gemspec
4
+ gemspec
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,43 @@
1
+ # Mancurses
2
+
3
+ Browse manpages inside ncurses. Jump around to other man pages
4
+
5
+
6
+ ## Installation
7
+
8
+ $ gem install mancurses
9
+
10
+ ## Usage
11
+
12
+ Use Alt-c to enter program name to search such as "grep", "strcmp" etc.
13
+ Use "/" to search for strings within the page displayed.
14
+
15
+ Use '?' to see bindings.
16
+
17
+ When pressing F1 for help or "?" for bindings, and returning, the background is black. You need to
18
+ press a key for it to refresh. I am figuring this out.
19
+
20
+ ## General
21
+
22
+ This gem is not really gonna make much difference to you. It's faster to just type `man grep` on the
23
+ command line and 'q' to get out. It's not like you keep manning pages one after another.
24
+
25
+ I am writing this to test out a new text widget which uses a pad. I hope to replace the current
26
+ text widgets such as textview and list and maybe tabular and tree in rbcurse-core with this.
27
+
28
+ Currently, using a window requires a lot of work each time one scrolls around. Too much string creation , truncation, sanitizing and gc going on repeatedly. Using a pad simplifies all this.
29
+
30
+ However, pad is not without its issues. If I have two pads on the screen, and a popup is displayed,
31
+ then a black rectangle is left on the other pad. I would have to tab there and scroll for a `prefresh` to happen. Otherwise, the app needs to do some book-keeping of underlying pads created and refresh them when a messagebox or window closes.
32
+
33
+ If i cannot manage that reliably, then i cannot include this in the main rbcurse-core.
34
+
35
+ ## Contributing
36
+
37
+ 1. Fork it
38
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
39
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
40
+ 4. Push to the branch (`git push origin my-new-feature`)
41
+ 5. Create new Pull Request
42
+
43
+ https://rubygems.org/profiles/rkumar
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/mancurses ADDED
@@ -0,0 +1,724 @@
1
+ #!/usr/bin/env ruby
2
+ # ----------------------------------------------------------------------------- #
3
+ # File: mancurses
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-08 01:14
12
+ #
13
+ # == CHANGES
14
+ # == TODO
15
+ # when moving right, also don't pan straight away
16
+ # x add mappings and process key in handle_keys and other widget things
17
+ # x user can put text or list
18
+ # . handle putting data again and overwriting existing
19
+ # . formatted text
20
+ # x search and other features
21
+ # - can pad movement and other ops be abstracted into module for reuse
22
+ # / get scrolling like in vim (C-f e y b d)
23
+ # - alert issue of leaving a blank is poss due to using prefresh i/o copywin
24
+ #
25
+ # == TODO 2013-03-07 - 20:34
26
+ # _ key bindings not showing up -- bind properly
27
+ # _ F1 screen leaves everything blank, so does bindings
28
+ # ----------------------------------------------------------------------------- #
29
+ #
30
+ require 'rbcurse'
31
+ require 'rbcurse/core/include/bordertitle'
32
+
33
+ include RubyCurses
34
+ module RubyCurses
35
+ extend self
36
+ class TextPad < Widget
37
+ include BorderTitle
38
+
39
+ dsl_accessor :suppress_border
40
+ # You may pass height, width, row and col for creating a window otherwise a fullscreen window
41
+ # will be created. If you pass a window from caller then that window will be used.
42
+ # Some keys are trapped, jkhl space, pgup, pgdown, end, home, t b
43
+ # This is currently very minimal and was created to get me started to integrating
44
+ # pads into other classes such as textview.
45
+ def initialize form=nil, config={}, &block
46
+
47
+ @editable = false
48
+ @focusable = true
49
+ @config = config
50
+ @prow = @pcol = 0
51
+ @startrow = 0
52
+ @startcol = 0
53
+ @list = []
54
+ super
55
+
56
+ # FIXME 0 as height craps out. need to make it LINES
57
+
58
+ #@height = @height.ifzero(FFI::NCurses.LINES)
59
+ #@width = @width.ifzero(FFI::NCurses.COLS)
60
+ @rows = @height
61
+ @cols = @width
62
+ @startrow = @row
63
+ @startcol = @col
64
+ #@suppress_border = config[:suppress_border]
65
+ @row_offset = @col_offset = 1
66
+ unless @suppress_border
67
+ @startrow += 1
68
+ @startcol += 1
69
+ @rows -=3 # 3 is since print_border_only reduces one from width, to check whether this is correct
70
+ @cols -=3
71
+ end
72
+ @row_offset = @col_offset = 0 if @suppress_borders
73
+ @top = @row
74
+ @left = @col
75
+ @lastrow = @row + 1
76
+ @lastcol = @col + 1
77
+ init_vars
78
+ end
79
+ def init_vars
80
+ @scrollatrows = @height - 3
81
+ @oldindex = @current_index = 0
82
+ # column cursor
83
+ @ccol = 0
84
+ @repaint_required = true
85
+ end
86
+ def rowcol #:nodoc:
87
+ return @row+@row_offset, @col+@col_offset
88
+ end
89
+
90
+ private
91
+ def create_pad
92
+ destroy if @pad
93
+ #@pad = FFI::NCurses.newpad(@content_rows, @content_cols)
94
+ @pad = @window.get_pad(@content_rows, @content_cols )
95
+ end
96
+
97
+ private
98
+ # create and populate pad
99
+ def populate_pad
100
+ @_populate_needed = false
101
+ # how can we make this more sensible ? FIXME
102
+ @renderer ||= DefaultRubyRenderer.new if ".rb" == @filetype
103
+ @content_rows = @content.count
104
+ @content_cols = content_cols()
105
+ @content_rows = @rows if @content_rows < @rows
106
+ @content_cols = @cols if @content_cols < @cols
107
+ $log.debug "XXXX content_cols = #{@content_cols}"
108
+
109
+ create_pad
110
+
111
+ Ncurses::Panel.update_panels
112
+ @content.each_index { |ix|
113
+ #FFI::NCurses.mvwaddstr(@pad,ix, 0, @content[ix])
114
+ render @pad, ix, @content[ix]
115
+ }
116
+
117
+ end
118
+
119
+ public
120
+ # supply a custom renderer that implements +render()+
121
+ # @see render
122
+ def renderer r
123
+ @renderer = r
124
+ end
125
+ #
126
+ # default method for rendering a line
127
+ #
128
+ def render pad, lineno, text
129
+ if text.is_a? Chunks::ChunkLine
130
+ FFI::NCurses.wmove @pad, lineno, 0
131
+ a = get_attrib @attrib
132
+
133
+ show_colored_chunks text, nil, a
134
+ return
135
+ end
136
+ if @renderer
137
+ @renderer.render @pad, lineno, text
138
+ else
139
+ FFI::NCurses.mvwaddstr(@pad,lineno, 0, @content[lineno])
140
+ end
141
+ end
142
+
143
+ # supply a filename as source for textpad
144
+ # Reads up file into @content
145
+
146
+ def filename(filename)
147
+ @file = filename
148
+ @filetype = File.extname filename
149
+ @content = File.open(filename,"r").readlines
150
+ if @filetype == ""
151
+ if @content.first.index("ruby")
152
+ @filetype = ".rb"
153
+ end
154
+ end
155
+ @_populate_needed = true
156
+ end
157
+
158
+ # Supply an array of string to be displayed
159
+ # This will replace existing text
160
+
161
+ def text lines
162
+ raise "text() receiving null content" unless lines
163
+ @content = lines
164
+ @_populate_needed = true
165
+ end
166
+
167
+ ## ---- the next 2 methods deal with printing chunks
168
+ # we should put it int a common module and include it
169
+ # in Window and Pad stuff and perhaps include it conditionally.
170
+
171
+ ## 2013-03-07 - 19:57 changed width to @content_cols since data not printing
172
+ # in some cases fully when ansi sequences were present int some line but not in others
173
+ # lines without ansi were printing less by a few chars.
174
+ def print(string, _width = @content_cols)
175
+ #return unless visible?
176
+ w = _width == 0? Ncurses.COLS : _width
177
+ FFI::NCurses.waddnstr(@pad,string.to_s, w) # changed 2011 dts
178
+ end
179
+
180
+ def show_colored_chunks(chunks, defcolor = nil, defattr = nil)
181
+ #return unless visible?
182
+ chunks.each do |chunk| #|color, chunk, attrib|
183
+ case chunk
184
+ when Chunks::Chunk
185
+ color = chunk.color
186
+ attrib = chunk.attrib
187
+ text = chunk.text
188
+ when Array
189
+ # for earlier demos that used an array
190
+ color = chunk[0]
191
+ attrib = chunk[2]
192
+ text = chunk[1]
193
+ end
194
+
195
+ color ||= defcolor
196
+ attrib ||= defattr || NORMAL
197
+
198
+ #cc, bg = ColorMap.get_colors_for_pair color
199
+ #$log.debug "XXX: CHUNK textpad #{text}, cp #{color} , attrib #{attrib}. #{cc}, #{bg} "
200
+ FFI::NCurses.wcolor_set(@pad, color,nil) if color
201
+ FFI::NCurses.wattron(@pad, attrib) if attrib
202
+ print(text)
203
+ FFI::NCurses.wattroff(@pad, attrib) if attrib
204
+ end
205
+ end
206
+
207
+ def formatted_text text, fmt
208
+ require 'rbcurse/core/include/chunk'
209
+ @formatted_text = text
210
+ @color_parser = fmt
211
+ @repaint_required = true
212
+ # don't know if start is always required. so putting in caller
213
+ #goto_start
214
+ #remove_all
215
+ end
216
+
217
+ # write pad onto window
218
+ #private
219
+ def padrefresh
220
+ FFI::NCurses.prefresh(@pad,@prow,@pcol, @startrow, @startcol, @rows + @startrow, @cols+@startcol);
221
+ end
222
+
223
+ # convenience method to return byte
224
+ private
225
+ def key x
226
+ x.getbyte(0)
227
+ end
228
+
229
+ # length of longest string in array
230
+ # This will give a 'wrong' max length if the array has ansi color escape sequences in it
231
+ # which inc the length but won't be printed. Such lines actually have less length when printed
232
+ # So in such cases, give more space to the pad.
233
+ def content_cols
234
+ longest = @content.max_by(&:length)
235
+ ## 2013-03-06 - 20:41 crashes here for some reason when man gives error message no man entry
236
+ return 0 unless longest
237
+ longest.length
238
+ end
239
+
240
+ public
241
+ def repaint
242
+ return unless @repaint_required
243
+ if @formatted_text
244
+ $log.debug "XXX: INSIDE FORMATTED TEXT "
245
+
246
+ l = RubyCurses::Utils.parse_formatted_text(@color_parser,
247
+ @formatted_text)
248
+
249
+ text(l)
250
+ @formatted_text = nil
251
+ end
252
+
253
+ ## moved this line up or else create_p was crashing
254
+ @window ||= @graphic
255
+ populate_pad if @_populate_needed
256
+ #HERE we need to populate once so user can pass a renderer
257
+ unless @suppress_border
258
+ if @repaint_all
259
+ @window.print_border_only @top, @left, @height-1, @width, $datacolor
260
+ print_title
261
+ @window.wrefresh
262
+ end
263
+ end
264
+
265
+ padrefresh
266
+ Ncurses::Panel.update_panels
267
+ @repaint_required = false
268
+ @repaint_all = false
269
+ end
270
+
271
+ #
272
+ # key mappings
273
+ #
274
+ def map_keys
275
+ @mapped_keys = true
276
+ bind_key([?g,?g], 'goto_start'){ goto_start } # mapping double keys like vim
277
+ bind_key(279, 'goto_start'){ goto_start }
278
+ bind_keys([?G,277], 'goto end'){ goto_end }
279
+ bind_keys([?k,KEY_UP], "Up"){ up }
280
+ bind_keys([?j,KEY_DOWN], "Down"){ down }
281
+ bind_key(?\C-e, "Scroll Window Down"){ scroll_window_down }
282
+ bind_key(?\C-y, "Scroll Window Up"){ scroll_window_up }
283
+ bind_keys([32,338, ?\C-d], "Scroll Forward"){ scroll_forward }
284
+ bind_keys([?\C-b,339]){ scroll_backward }
285
+ bind_key([?',?']){ goto_last_position } # vim , goto last row position (not column)
286
+ bind_key(?/, :ask_search)
287
+ bind_key(?n, :find_more)
288
+ bind_key([?\C-x, ?>], :scroll_right)
289
+ bind_key([?\C-x, ?<], :scroll_left)
290
+ bind_key(?\M-l, :scroll_right)
291
+ bind_key(?\M-h, :scroll_left)
292
+ #bind_key([?\C-x, ?\C-s], :saveas)
293
+ #bind_key(?r) { getstr("Enter a word: ") }
294
+ #bind_key(?m, :disp_menu)
295
+ end
296
+
297
+ # goto first line of file
298
+ def goto_start
299
+ @oldindex = @current_index
300
+ @current_index = @ccol = 0
301
+ @pcol = @prow = 0
302
+ end
303
+
304
+ # goto last line of file
305
+ def goto_end
306
+ @oldindex = @current_index
307
+ @current_index = @content_rows-1
308
+ @prow = @current_index - @scrollatrows
309
+ end
310
+
311
+ # move down a line mimicking vim's j key
312
+ # @param [int] multiplier entered prior to invoking key
313
+ def down num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier)
314
+ @oldindex = @current_index if num > 10
315
+ @current_index += num
316
+ unless is_visible? @current_index
317
+ if @current_index > @scrollatrows
318
+ @prow += 1
319
+ end
320
+ end
321
+ $multiplier = 0
322
+ end
323
+
324
+ # move up a line mimicking vim's k key
325
+ # @param [int] multiplier entered prior to invoking key
326
+ def up num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier)
327
+ @oldindex = @current_index if num > 10
328
+ @current_index -= num
329
+ unless is_visible? @current_index
330
+ if @prow > @current_index
331
+ $status_message.value = "1 #{@prow} > #{@current_index} "
332
+ @prow -= 1
333
+ else
334
+ end
335
+ end
336
+ $multiplier = 0
337
+ end
338
+
339
+ # scrolls window down mimicking vim C-e
340
+ # @param [int] multiplier entered prior to invoking key
341
+ def scroll_window_down num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier)
342
+ @prow += num
343
+ if @prow > @current_index
344
+ @current_index += 1
345
+ end
346
+ #check_prow
347
+ $multiplier = 0
348
+ end
349
+
350
+ # scrolls window up mimicking vim C-y
351
+ # @param [int] multiplier entered prior to invoking key
352
+ def scroll_window_up num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier)
353
+ @prow -= num
354
+ unless is_visible? @current_index
355
+ # one more check may be needed here TODO
356
+ @current_index -= num
357
+ end
358
+ $multiplier = 0
359
+ end
360
+
361
+ # scrolls lines a window full at a time, on pressing ENTER or C-d or pagedown
362
+ def scroll_forward
363
+ @oldindex = @current_index
364
+ @current_index += @scrollatrows
365
+ @prow = @current_index - @scrollatrows
366
+ end
367
+
368
+ # scrolls lines backward a window full at a time, on pressing pageup
369
+ # C-u may not work since it is trapped by form earlier. Need to fix
370
+ def scroll_backward
371
+ @oldindex = @current_index
372
+ @current_index -= @scrollatrows
373
+ @prow = @current_index - @scrollatrows
374
+ end
375
+ def goto_last_position
376
+ return unless @oldindex
377
+ tmp = @current_index
378
+ @current_index = @oldindex
379
+ @oldindex = tmp
380
+ bounds_check
381
+ end
382
+ def scroll_right
383
+ if @content_cols < @cols
384
+ maxpcol = 0
385
+ else
386
+ maxpcol = @content_cols - @cols
387
+ end
388
+ @pcol += 1
389
+ @pcol = maxpcol if @pcol > maxpcol
390
+ # to prevent right from retaining earlier painted values
391
+ # padreader does not do a clear, yet works fine.
392
+ # OK it has an update_panel after padrefresh, that clears it seems.
393
+ #this clears entire window not just the pad
394
+ #FFI::NCurses.wclear(@window.get_window)
395
+ # so border and title is repainted after window clearing
396
+ #
397
+ # Next line was causing all sorts of problems when scrolling with ansi formatted text
398
+ #@repaint_all = true
399
+ end
400
+ def scroll_left
401
+ @pcol -= 1
402
+ end
403
+ #
404
+ #
405
+ #
406
+ # NOTE : if advancing pcol one has to clear the pad or something or else
407
+ # there'll be older content on the right side.
408
+ #
409
+ def handle_key ch
410
+ return :UNHANDLED unless @content
411
+ map_keys unless @mapped_keys
412
+
413
+ @maxrow = @content_rows - @rows
414
+ @maxcol = @content_cols - @cols
415
+
416
+ # need to understand the above line, can go below zero.
417
+ # all this seems to work fine in padreader.rb in util.
418
+ # somehow maxcol going to -33
419
+ @oldrow = @prow
420
+ @oldcol = @pcol
421
+ #$log.debug "XXX: PAD got #{ch} maxcol = #{@maxcol} cols=#{@cols}, maxpcol = #{maxpcol}"
422
+ begin
423
+ case ch
424
+ when key(?H)
425
+ when key(?l)
426
+ # TODO take multipler
427
+ #@pcol += 1
428
+ if @ccol < @cols
429
+ @ccol += 1
430
+ end
431
+ when key(?$)
432
+ #@pcol = @maxcol - 1
433
+ @ccol = [@content[@current_index].size, @cols].min
434
+ when key(?h)
435
+ # TODO take multipler
436
+ if @ccol > 0
437
+ @ccol -= 1
438
+ end
439
+ when key(?0)
440
+ @ccol = 0
441
+ when ?0.getbyte(0)..?9.getbyte(0)
442
+ if ch == ?0.getbyte(0) && $multiplier == 0
443
+ # copy of C-a - start of line
444
+ @repaint_required = true if @pcol > 0 # tried other things but did not work
445
+ @pcol = 0
446
+ return 0
447
+ end
448
+ # storing digits entered so we can multiply motion actions
449
+ $multiplier *= 10 ; $multiplier += (ch-48)
450
+ return 0
451
+ when ?\C-c.getbyte(0)
452
+ $multiplier = 0
453
+ return 0
454
+ else
455
+ # check for bindings, these cannot override above keys since placed at end
456
+ begin
457
+ ret = process_key ch, self
458
+ ## If i press C-x > i get an alert from rwidgets which blacks the screen
459
+ # if i put a padrefresh here it becomes okay but only for one pad,
460
+ # i still need to do it for all pads.
461
+ rescue => err
462
+ $log.error " TEXTPAD ERROR INS #{err} "
463
+ $log.debug(err.backtrace.join("\n"))
464
+ textdialog ["Error in TextPad: #{err} ", *err.backtrace], :title => "Exception"
465
+ # FIXME why does this result in a blank spot on screen till its refreshed again
466
+ # should not happen if its deleting its panel and doing an update panel
467
+ end
468
+ return :UNHANDLED if ret == :UNHANDLED
469
+ end
470
+ bounds_check
471
+ rescue => err
472
+ $log.error " TEXTPAD ERROR 111 #{err} "
473
+ $log.debug( err) if err
474
+ $log.debug(err.backtrace.join("\n")) if err
475
+ textdialog ["Error in TextPad: #{err} ", *err.backtrace], :title => "Exception"
476
+ $error_message.value = ""
477
+ ensure
478
+ padrefresh
479
+ Ncurses::Panel.update_panels
480
+ end
481
+ return 0
482
+ end # while loop
483
+
484
+ # destroy the pad, this needs to be called from somewhere, like when the app
485
+ # closes or the current window closes , or else we could have a seg fault
486
+ # or some ugliness on the screen below this one (if nested).
487
+
488
+ # Now since we use get_pad from window, upon the window being destroyed,
489
+ # it will call this. Else it will destroy pad
490
+ def destroy
491
+ FFI::NCurses.delwin(@pad) if @pad # when do i do this ? FIXME
492
+ @pad = nil
493
+ end
494
+ def is_visible? index
495
+ j = index - @prow #@toprow
496
+ j >= 0 && j <= @scrollatrows
497
+ end
498
+ def on_enter
499
+ set_form_row
500
+ end
501
+ def set_form_row
502
+ setrowcol @lastrow, @lastcol
503
+ end
504
+ def set_form_col
505
+ end
506
+
507
+ private
508
+
509
+ # check that current_index and prow are within correct ranges
510
+ # sets row (and someday col too)
511
+ # sets repaint_required
512
+
513
+ def bounds_check
514
+ r,c = rowcol
515
+ @current_index = 0 if @current_index < 0
516
+ @current_index = @content_rows-1 if @current_index > @content_rows-1
517
+ $status_message.value = "visible #{@prow} , #{@current_index} "
518
+ unless is_visible? @current_index
519
+ if @prow > @current_index
520
+ $status_message.value = "1 #{@prow} > #{@current_index} "
521
+ @prow -= 1
522
+ else
523
+ end
524
+ end
525
+ #end
526
+ check_prow
527
+ #$log.debug "XXX: PAD BOUNDS ci:#{@current_index} , old #{@oldrow},pr #{@prow}, max #{@maxrow} pcol #{@pcol} maxcol #{@maxcol}"
528
+ @crow = @current_index + r - @prow
529
+ @crow = r if @crow < r
530
+ # 2 depends on whetehr suppressborders
531
+ @crow = @row + @height -2 if @crow >= r + @height -2
532
+ setrowcol @crow, @ccol+c
533
+ lastcurpos @crow, @ccol+c
534
+ if @oldrow != @prow || @oldcol != @pcol
535
+ @repaint_required = true
536
+ end
537
+ end
538
+ def lastcurpos r,c
539
+ @lastrow = r
540
+ @lastcol = c
541
+ end
542
+
543
+
544
+ # check that prow and pcol are within bounds
545
+
546
+ def check_prow
547
+ @prow = 0 if @prow < 0
548
+ if @prow > @maxrow-1
549
+ @prow = @maxrow-1
550
+ end
551
+ if @pcol > @maxcol-1
552
+ @pcol = @maxcol-1
553
+ end
554
+ @pcol = 0 if @pcol < 0
555
+ end
556
+ public
557
+ ##
558
+ # Ask user for string to search for
559
+ def ask_search
560
+ str = get_string("Enter pattern: ")
561
+ return if str.nil?
562
+ str = @last_regex if str == ""
563
+ return if str == ""
564
+ ix = next_match str
565
+ return unless ix
566
+ @last_regex = str
567
+
568
+ @oldindex = @current_index
569
+ @current_index = ix[0]
570
+ @ccol = ix[1]
571
+ ensure_visible
572
+ end
573
+ ##
574
+ # Find next matching row for string accepted in ask_search
575
+ #
576
+ def find_more
577
+ return unless @last_regex
578
+ ix = next_match @last_regex
579
+ return unless ix
580
+ @oldindex = @current_index
581
+ @current_index = ix[0]
582
+ @ccol = ix[1]
583
+ ensure_visible
584
+ end
585
+
586
+ ##
587
+ # Find the next row that contains given string
588
+ # @return row and col offset of match, or nil
589
+ # @param String to find
590
+ def next_match str
591
+ first = nil
592
+ ## content can be string or Chunkline, so we had to write <tt>index</tt> for this.
593
+ ## =~ does not give an error, but it does not work.
594
+ @content.each_with_index do |line, ix|
595
+ col = line.index str
596
+ if col
597
+ first ||= [ ix, col ]
598
+ if ix > @current_index
599
+ return [ix, col]
600
+ end
601
+ end
602
+ end
603
+ return first
604
+ end
605
+ ##
606
+ # Ensure current row is visible, if not make it first row
607
+ # TODO - need to check if its at end and then reduce scroll at rows,
608
+ # @param current_index (default if not given)
609
+ #
610
+ def ensure_visible row = @current_line
611
+ unless is_visible? row
612
+ @prow = @current_index
613
+ end
614
+ end
615
+
616
+ end # class textpad
617
+
618
+ # a test renderer to see how things go
619
+ class DefaultRubyRenderer
620
+ def render pad, lineno, text
621
+ bg = :black
622
+ fg = :white
623
+ att = NORMAL
624
+ cp = $datacolor
625
+ if text =~ /^\s*# /
626
+ fg = :red
627
+ cp = get_color($datacolor, fg, bg)
628
+ elsif text =~ /^\s*#/
629
+ fg = :blue
630
+ cp = get_color($datacolor, fg, bg)
631
+ elsif text =~ /^\s*class /
632
+ fg = :magenta
633
+ cp = get_color($datacolor, fg, bg)
634
+ elsif text =~ /^\s*def /
635
+ fg = :yellow
636
+ att = BOLD
637
+ cp = get_color($datacolor, fg, bg)
638
+ elsif text =~ /^\s*(begin|rescue|ensure|end)/
639
+ fg = :magenta
640
+ att = BOLD
641
+ cp = get_color($datacolor, fg, bg)
642
+ end
643
+ FFI::NCurses.wattron(pad,FFI::NCurses.COLOR_PAIR(cp) | att)
644
+ FFI::NCurses.mvwaddstr(pad, lineno, 0, text)
645
+ FFI::NCurses.wattroff(pad,FFI::NCurses.COLOR_PAIR(cp) | att)
646
+
647
+ end
648
+ end
649
+ end
650
+ ## Since we already have an ansi sequence parser, why not convert to that and use that.
651
+ #
652
+ # man pages have some kind of sucky format probably related to sme ancient hardware.
653
+ # I notice two patterns:
654
+ # 1. a character is followed by a ^H and then the same character repeated.
655
+ # Such a char is to be printed in one color. Oh it get it, LOL, ^H is a backspace
656
+ # so basically the printer is giogn back and printing that char again. So its printed
657
+ # two times, aka bold.
658
+ # 2. The second is an underline folowed by BS and then any character, this goes in another
659
+ # color and is obviously meant to be underlined text. NOW it get it, bird-brained me!
660
+ #
661
+ #
662
+ def convert_man_to_ansi file
663
+ lines = file.split "\n"
664
+ l = nil
665
+ lines.each_with_index do |line, ix|
666
+ # convert underlined words to yellow or one color, these are usually params
667
+ line.gsub!(/((_[^ ])+)/,'\1')
668
+ line.gsub!(/_/,'')
669
+ # convert bold words to red or one color, these are usually headers and other words
670
+ l= line.gsub(/(([^ ][^ ])+)/,'\1').gsub(/[^ ]/,'').gsub(//,'')
671
+ # ==
672
+ #line.gsub!(/((_[^ ])+)/,'_\1_')
673
+ #line.gsub!(/_/,'')
674
+ ### convert bold words to red or one color, these are usually headers and other words
675
+ #l= line.gsub(/(([^ ][^ ])+)/,'*\1*').gsub(/[^ ]/,'').gsub(//,'')
676
+ # ==
677
+ lines[ix] = l
678
+ end
679
+ lines
680
+ end
681
+ def ask_program
682
+ p = @form.by_name["textpad"];
683
+ prog = get_string("Program to man:")
684
+ p.padrefresh
685
+ return unless prog
686
+ return if prog == ""
687
+ file = `man #{prog} 2>&1`
688
+ return unless file
689
+ text = convert_man_to_ansi(file)
690
+ p.formatted_text(text, :ansi)
691
+ p.goto_start
692
+ end
693
+ if __FILE__ == $PROGRAM_NAME
694
+ require 'rbcurse/core/util/app'
695
+ App.new do
696
+ @form.bind_key(?\M-c, "Ask program name: ") { ask_program }
697
+ @form.bind_key(?q, "quit: ") { throw :close }
698
+ single = true
699
+ w = 50
700
+ w2 = FFI::NCurses.COLS-w-1
701
+ if single
702
+ w = FFI::NCurses.COLS-1
703
+ ## no matter what you do, man's output gets wrapped to 80 cols if not going to a terminal.
704
+ end
705
+ ## create two side by side pads on for ansi and one for ruby
706
+ p = RubyCurses::TextPad.new @form, :height => FFI::NCurses.LINES-1, :width => w, :row => 0, :col => 0 , :title => " mancurses ", :name => "textpad"
707
+ #fn = "m.m"
708
+ #text = File.open(fn,"r").readlines
709
+ file = `man man`
710
+ text = convert_man_to_ansi(file)
711
+ #File.open("t.t", 'w') { |file| file.write(text.join "\n") }
712
+ p.formatted_text(text, :ansi)
713
+ #p.text(text)
714
+ if !single
715
+ RubyCurses::TextPad.new @form, :filename => "bin/mancurses", :height => FFI::NCurses.LINES, :width => w2, :row => 0, :col => w+1 , :title => " ruby "
716
+ end
717
+ #throw :close
718
+ @status_line = status_line :row => Ncurses.LINES-1
719
+
720
+ @status_line.command {
721
+ "q Quit | ? Keys | M-c Program | "
722
+ }
723
+ end
724
+ end
data/mancurses.gemspec ADDED
@@ -0,0 +1,21 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "mancurses"
7
+ spec.version = "0.0.1"
8
+ spec.authors = ["Rahul Kumar"]
9
+ spec.email = ["sentinel1879@gmail.com"]
10
+ spec.description = %q{view manpages in an ncurses window and navigate with vim bindings}
11
+ spec.summary = %q{view manpages in an ncurses window and navigate with vim bindings and much much moah}
12
+ spec.homepage = "https://github.com/rkumar/mancurses"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files`.split($/)
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+
18
+ spec.add_development_dependency "bundler", "~> 1.3"
19
+ spec.add_development_dependency "rake"
20
+ spec.add_dependency "rbcurse-core"
21
+ end
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mancurses
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Rahul Kumar
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-03-07 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.3'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.3'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rbcurse-core
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: view manpages in an ncurses window and navigate with vim bindings
63
+ email:
64
+ - sentinel1879@gmail.com
65
+ executables:
66
+ - mancurses
67
+ extensions: []
68
+ extra_rdoc_files: []
69
+ files:
70
+ - .gitignore
71
+ - Gemfile
72
+ - LICENSE.txt
73
+ - README.md
74
+ - Rakefile
75
+ - bin/mancurses
76
+ - mancurses.gemspec
77
+ homepage: https://github.com/rkumar/mancurses
78
+ licenses:
79
+ - MIT
80
+ post_install_message:
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ segments:
91
+ - 0
92
+ hash: -1947742288534939193
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ segments:
100
+ - 0
101
+ hash: -1947742288534939193
102
+ requirements: []
103
+ rubyforge_project:
104
+ rubygems_version: 1.8.25
105
+ signing_key:
106
+ specification_version: 3
107
+ summary: view manpages in an ncurses window and navigate with vim bindings and much
108
+ much moah
109
+ test_files: []