mancurses 0.0.1

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.
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: []