cygnus 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,846 @@
1
+ #!/usr/bin/env ruby
2
+ # ----------------------------------------------------------------------------- #
3
+ # File: textpad.rb
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-16 17:41
12
+ #
13
+ # == CHANGES
14
+ # == TODO
15
+ # _ in popup case allowing scrolling when it should not so you get an extra char at end
16
+ # _ The list one needs a f-char like functionality.
17
+ # x handle putting data again and overwriting existing
18
+ # When reputting data, the underlying pad needs to be properly cleared
19
+ # esp last row and last col
20
+ #
21
+ # x add mappings and process key in handle_keys and other widget things
22
+ # - can pad movement and other ops be abstracted into module for reuse
23
+ # / get scrolling like in vim (C-f e y b d)
24
+ #
25
+ # == TODO 2013-03-07 - 20:34
26
+ # _ key bindings not showing up -- bind properly
27
+ # ----------------------------------------------------------------------------- #
28
+ #
29
+ require 'rbcurse'
30
+ require 'rbcurse/core/include/bordertitle'
31
+
32
+ include RubyCurses
33
+ module Cygnus
34
+ extend self
35
+ class TextPad < Widget
36
+ include BorderTitle
37
+
38
+ dsl_accessor :suppress_border
39
+ attr_reader :current_index
40
+ attr_reader :rows , :cols
41
+ # You may pass height, width, row and col for creating a window otherwise a fullscreen window
42
+ # will be created. If you pass a window from caller then that window will be used.
43
+ # Some keys are trapped, jkhl space, pgup, pgdown, end, home, t b
44
+ # This is currently very minimal and was created to get me started to integrating
45
+ # pads into other classes such as textview.
46
+ def initialize form=nil, config={}, &block
47
+
48
+ @editable = false
49
+ @focusable = true
50
+ @config = config
51
+ @row = @col = 0
52
+ @prow = @pcol = 0
53
+ @startrow = 0
54
+ @startcol = 0
55
+ @list = []
56
+ super
57
+
58
+ ## NOTE
59
+ # ---------------------------------------------------
60
+ # Since we are using pads, you need to get your height, width and rows correct
61
+ # Make sure the height factors in the row, else nothing may show
62
+ # ---------------------------------------------------
63
+ #@height = @height.ifzero(FFI::NCurses.LINES)
64
+ #@width = @width.ifzero(FFI::NCurses.COLS)
65
+ @rows = @height
66
+ @cols = @width
67
+ # NOTE XXX if cols is > COLS then padrefresh can fail
68
+ @startrow = @row
69
+ @startcol = @col
70
+ #@suppress_border = config[:suppress_border]
71
+ @row_offset = @col_offset = 1
72
+ unless @suppress_border
73
+ @startrow += 1
74
+ @startcol += 1
75
+ @rows -=3 # 3 is since print_border_only reduces one from width, to check whether this is correct
76
+ @cols -=3
77
+ else
78
+ # seeing why nothing is printing
79
+ @rows -=0 # 3 is since print_border_only reduces one from width, to check whether this is correct
80
+ ## if next is 0 then padrefresh doesn't print
81
+ @cols -=1
82
+ end
83
+ @row_offset = @col_offset = 0 if @suppress_borders
84
+ @top = @row
85
+ @left = @col
86
+ @lastrow = @row + 1
87
+ @lastcol = @col + 1
88
+ @_events << :PRESS
89
+ @_events << :ENTER_ROW
90
+ @scrollatrows = @height - 3
91
+ init_vars
92
+ end
93
+ def init_vars
94
+ $multiplier = 0
95
+ @oldindex = @current_index = 0
96
+ # column cursor
97
+ @prow = @pcol = @curpos = 0
98
+ if @row && @col
99
+ @lastrow = @row + 1
100
+ @lastcol = @col + 1
101
+ end
102
+ @repaint_required = true
103
+ end
104
+ def rowcol #:nodoc:
105
+ return @row+@row_offset, @col+@col_offset
106
+ end
107
+
108
+ private
109
+ ## XXX in list text returns the selected row, list returns the full thing, keep consistent
110
+ def create_pad
111
+ destroy if @pad
112
+ #@pad = FFI::NCurses.newpad(@content_rows, @content_cols)
113
+ @pad = @window.get_pad(@content_rows, @content_cols )
114
+ end
115
+
116
+ private
117
+ # create and populate pad
118
+ def populate_pad
119
+ @_populate_needed = false
120
+ # how can we make this more sensible ? FIXME
121
+ #@renderer ||= DefaultFileRenderer.new #if ".rb" == @filetype
122
+ @content_rows = @content.count
123
+ @content_cols = content_cols()
124
+ # this should be explicit and not "intelligent"
125
+ #@title += " [ #{@content_rows},#{@content_cols}] " if @cols > 50
126
+ @content_rows = @rows if @content_rows < @rows
127
+ @content_cols = @cols if @content_cols < @cols
128
+ #$log.debug "XXXX content_cols = #{@content_cols}"
129
+
130
+ create_pad
131
+
132
+ # clearstring is the string required to clear the pad to backgroud color
133
+ @clearstring = nil
134
+ cp = get_color($datacolor, @color, @bgcolor)
135
+ @cp = FFI::NCurses.COLOR_PAIR(cp)
136
+ if cp != $datacolor
137
+ @clearstring ||= " " * @width
138
+ end
139
+
140
+ Ncurses::Panel.update_panels
141
+ @content.each_index { |ix|
142
+ #FFI::NCurses.mvwaddstr(@pad,ix, 0, @content[ix])
143
+ render @pad, ix, @content[ix]
144
+ }
145
+
146
+ end
147
+
148
+ public
149
+ # supply a custom renderer that implements +render()+
150
+ # @see render
151
+ def renderer r
152
+ @renderer = r
153
+ end
154
+ #
155
+ # default method for rendering a line
156
+ #
157
+ def render pad, lineno, text
158
+ if text.is_a? Chunks::ChunkLine
159
+ FFI::NCurses.wmove @pad, lineno, 0
160
+ a = get_attrib @attrib
161
+
162
+ show_colored_chunks text, nil, a
163
+ return
164
+ end
165
+ if @renderer
166
+ @renderer.render @pad, lineno, text
167
+ else
168
+ ## messabox does have a method to paint the whole window in bg color its in rwidget.rb
169
+ att = NORMAL
170
+ FFI::NCurses.wattron(@pad, @cp | att)
171
+ FFI::NCurses.mvwaddstr(@pad,lineno, 0, @clearstring) if @clearstring
172
+ FFI::NCurses.mvwaddstr(@pad,lineno, 0, @content[lineno])
173
+
174
+ #FFI::NCurses.mvwaddstr(pad, lineno, 0, text)
175
+ FFI::NCurses.wattroff(@pad, @cp | att)
176
+ end
177
+ end
178
+
179
+ # supply a filename as source for textpad
180
+ # Reads up file into @content
181
+
182
+ def filename(filename)
183
+ @file = filename
184
+ unless File.exists? filename
185
+ alert "#{filename} does not exist"
186
+ return
187
+ end
188
+ @filetype = File.extname filename
189
+ @content = File.open(filename,"r").readlines
190
+ if @filetype == ""
191
+ if @content.first.index("ruby")
192
+ @filetype = ".rb"
193
+ end
194
+ end
195
+ init_vars
196
+ @repaint_all = true
197
+ @_populate_needed = true
198
+ end
199
+
200
+ # Supply an array of string to be displayed
201
+ # This will replace existing text
202
+
203
+ ## XXX in list text returns the selected row, list returns the full thing, keep consistent
204
+ def text lines
205
+ raise "text() receiving null content" unless lines
206
+ @content = lines
207
+ @_populate_needed = true
208
+ @repaint_all = true
209
+ init_vars
210
+ end
211
+ alias :list :text
212
+ def content
213
+ raise "content is nil " unless @content
214
+ return @content
215
+ end
216
+
217
+ ## ---- the next 2 methods deal with printing chunks
218
+ # we should put it int a common module and include it
219
+ # in Window and Pad stuff and perhaps include it conditionally.
220
+
221
+ ## 2013-03-07 - 19:57 changed width to @content_cols since data not printing
222
+ # in some cases fully when ansi sequences were present int some line but not in others
223
+ # lines without ansi were printing less by a few chars.
224
+ # This was prolly copied from rwindow, where it is okay since its for a specific width
225
+ def print(string, _width = @content_cols)
226
+ #return unless visible?
227
+ w = _width == 0? Ncurses.COLS : _width
228
+ FFI::NCurses.waddnstr(@pad,string.to_s, w) # changed 2011 dts
229
+ end
230
+
231
+ def show_colored_chunks(chunks, defcolor = nil, defattr = nil)
232
+ #return unless visible?
233
+ chunks.each do |chunk| #|color, chunk, attrib|
234
+ case chunk
235
+ when Chunks::Chunk
236
+ color = chunk.color
237
+ attrib = chunk.attrib
238
+ text = chunk.text
239
+ when Array
240
+ # for earlier demos that used an array
241
+ color = chunk[0]
242
+ attrib = chunk[2]
243
+ text = chunk[1]
244
+ end
245
+
246
+ color ||= defcolor
247
+ attrib ||= defattr || NORMAL
248
+
249
+ #cc, bg = ColorMap.get_colors_for_pair color
250
+ #$log.debug "XXX: CHUNK textpad #{text}, cp #{color} , attrib #{attrib}. #{cc}, #{bg} "
251
+ FFI::NCurses.wcolor_set(@pad, color,nil) if color
252
+ FFI::NCurses.wattron(@pad, attrib) if attrib
253
+ print(text)
254
+ FFI::NCurses.wattroff(@pad, attrib) if attrib
255
+ end
256
+ end
257
+
258
+ def formatted_text text, fmt
259
+ require 'rbcurse/core/include/chunk'
260
+ @formatted_text = text
261
+ @color_parser = fmt
262
+ @repaint_required = true
263
+ # don't know if start is always required. so putting in caller
264
+ #goto_start
265
+ #remove_all
266
+ end
267
+
268
+ # write pad onto window
269
+ #private
270
+ def padrefresh
271
+ top = @window.top
272
+ left = @window.left
273
+ sr = @startrow + top
274
+ sc = @startcol + left
275
+ retval = FFI::NCurses.prefresh(@pad,@prow,@pcol, sr , sc , @rows + sr , @cols+ sc );
276
+ $log.warn "XXX: PADREFRESH #{retval}, #{@prow}, #{@pcol}, #{sr}, #{sc}, #{@rows+sr}, #{@cols+sc}." if retval == -1
277
+ # padrefresh can fail if width is greater than NCurses.COLS
278
+ #FFI::NCurses.prefresh(@pad,@prow,@pcol, @startrow + top, @startcol + left, @rows + @startrow + top, @cols+@startcol + left);
279
+ end
280
+
281
+ # convenience method to return byte
282
+ private
283
+ def key x
284
+ x.getbyte(0)
285
+ end
286
+
287
+ # length of longest string in array
288
+ # This will give a 'wrong' max length if the array has ansi color escape sequences in it
289
+ # which inc the length but won't be printed. Such lines actually have less length when printed
290
+ # So in such cases, give more space to the pad.
291
+ def content_cols
292
+ longest = @content.max_by(&:length)
293
+ ## 2013-03-06 - 20:41 crashes here for some reason when man gives error message no man entry
294
+ return 0 unless longest
295
+ longest.length
296
+ end
297
+
298
+ public
299
+ def repaint
300
+ ## 2013-03-08 - 21:01 This is the fix to the issue of form callign an event like ? or F1
301
+ # which throws up a messagebox which leaves a black rect. We have no place to put a refresh
302
+ # However, form does call repaint for all objects, so we can do a padref here. Otherwise,
303
+ # it would get rejected. UNfortunately this may happen more often we want, but we never know
304
+ # when something pops up on the screen.
305
+ padrefresh unless @repaint_required
306
+ return unless @repaint_required
307
+ if @formatted_text
308
+ $log.debug "XXX: INSIDE FORMATTED TEXT "
309
+
310
+ l = RubyCurses::Utils.parse_formatted_text(@color_parser,
311
+ @formatted_text)
312
+
313
+ text(l)
314
+ @formatted_text = nil
315
+ end
316
+
317
+ ## moved this line up or else create_p was crashing
318
+ @window ||= @graphic
319
+ populate_pad if @_populate_needed
320
+ #HERE we need to populate once so user can pass a renderer
321
+ unless @suppress_border
322
+ if @repaint_all
323
+ ## XXX im not getting the background color.
324
+ #@window.print_border_only @top, @left, @height-1, @width, $datacolor
325
+ clr = get_color $datacolor, @color, @bgcolor
326
+ #@window.print_border @top, @left, @height-1, @width, clr
327
+ @window.print_border_only @top, @left, @height-1, @width, clr
328
+ print_title
329
+ @window.wrefresh
330
+ end
331
+ end
332
+
333
+ padrefresh
334
+ Ncurses::Panel.update_panels
335
+ @repaint_required = false
336
+ @repaint_all = false
337
+ end
338
+
339
+ #
340
+ # key mappings
341
+ #
342
+ def map_keys
343
+ @mapped_keys = true
344
+ bind_key([?g,?g], 'goto_start'){ goto_start } # mapping double keys like vim
345
+ bind_key(279, 'goto_start'){ goto_start }
346
+ bind_keys([?G,277], 'goto end'){ goto_end }
347
+ bind_keys([?k,KEY_UP], "Up"){ up }
348
+ bind_keys([?j,KEY_DOWN], "Down"){ down }
349
+ bind_key(?\C-e, "Scroll Window Down"){ scroll_window_down }
350
+ bind_key(?\C-y, "Scroll Window Up"){ scroll_window_up }
351
+ bind_keys([32,338, ?\C-d], "Scroll Forward"){ scroll_forward }
352
+ bind_keys([?\C-b,339]){ scroll_backward }
353
+ # the next one invalidates the single-quote binding for bookmarks
354
+ #bind_key([?',?']){ goto_last_position } # vim , goto last row position (not column)
355
+ bind_key(?/, :ask_search)
356
+ bind_key(?n, :find_more)
357
+ bind_key([?\C-x, ?>], :scroll_right)
358
+ bind_key([?\C-x, ?<], :scroll_left)
359
+ bind_key(?\M-l, :scroll_right)
360
+ bind_key(?\M-h, :scroll_left)
361
+ bind_key(?L, :bottom_of_window)
362
+ bind_key(?M, :middle_of_window)
363
+ bind_key(?H, :top_of_window)
364
+ bind_key(?w, :forward_word)
365
+ bind_key(KEY_ENTER, :fire_action_event)
366
+ end
367
+
368
+ # goto first line of file
369
+ def goto_start
370
+ #@oldindex = @current_index
371
+ $multiplier ||= 0
372
+ if $multiplier > 0
373
+ goto_line $multiplier - 1
374
+ return
375
+ end
376
+ @current_index = 0
377
+ @curpos = @pcol = @prow = 0
378
+ @prow = 0
379
+ $multiplier = 0
380
+ end
381
+
382
+ # goto last line of file
383
+ def goto_end
384
+ #@oldindex = @current_index
385
+ $multiplier ||= 0
386
+ if $multiplier > 0
387
+ goto_line $multiplier - 1
388
+ return
389
+ end
390
+ @current_index = @content.count() - 1
391
+ @prow = @current_index - @scrollatrows
392
+ $multiplier = 0
393
+ end
394
+ def goto_line line
395
+ ## we may need to calculate page, zfm style and place at right position for ensure visible
396
+ #line -= 1
397
+ @current_index = line
398
+ ensure_visible line
399
+ bounds_check
400
+ $multiplier = 0
401
+ end
402
+ def top_of_window
403
+ @current_index = @prow
404
+ $multiplier ||= 0
405
+ if $multiplier > 0
406
+ @current_index += $multiplier
407
+ $multiplier = 0
408
+ end
409
+ end
410
+ def bottom_of_window
411
+ @current_index = @prow + @scrollatrows
412
+ $multiplier ||= 0
413
+ if $multiplier > 0
414
+ @current_index -= $multiplier
415
+ $multiplier = 0
416
+ end
417
+ end
418
+ def middle_of_window
419
+ @current_index = @prow + (@scrollatrows/2)
420
+ $multiplier = 0
421
+ end
422
+
423
+ # move down a line mimicking vim's j key
424
+ # @param [int] multiplier entered prior to invoking key
425
+ def down num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier)
426
+ #@oldindex = @current_index if num > 10
427
+ @current_index += num
428
+ ensure_visible
429
+ $multiplier = 0
430
+ end
431
+
432
+ # move up a line mimicking vim's k key
433
+ # @param [int] multiplier entered prior to invoking key
434
+ def up num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier)
435
+ #@oldindex = @current_index if num > 10
436
+ @current_index -= num
437
+ #unless is_visible? @current_index
438
+ #if @prow > @current_index
439
+ ##$status_message.value = "1 #{@prow} > #{@current_index} "
440
+ #@prow -= 1
441
+ #else
442
+ #end
443
+ #end
444
+ $multiplier = 0
445
+ end
446
+
447
+ # scrolls window down mimicking vim C-e
448
+ # @param [int] multiplier entered prior to invoking key
449
+ def scroll_window_down num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier)
450
+ @prow += num
451
+ if @prow > @current_index
452
+ @current_index += 1
453
+ end
454
+ #check_prow
455
+ $multiplier = 0
456
+ end
457
+
458
+ # scrolls window up mimicking vim C-y
459
+ # @param [int] multiplier entered prior to invoking key
460
+ def scroll_window_up num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier)
461
+ @prow -= num
462
+ unless is_visible? @current_index
463
+ # one more check may be needed here TODO
464
+ @current_index -= num
465
+ end
466
+ $multiplier = 0
467
+ end
468
+
469
+ # scrolls lines a window full at a time, on pressing ENTER or C-d or pagedown
470
+ def scroll_forward
471
+ #@oldindex = @current_index
472
+ @current_index += @scrollatrows
473
+ @prow = @current_index - @scrollatrows
474
+ end
475
+
476
+ # scrolls lines backward a window full at a time, on pressing pageup
477
+ # C-u may not work since it is trapped by form earlier. Need to fix
478
+ def scroll_backward
479
+ #@oldindex = @current_index
480
+ @current_index -= @scrollatrows
481
+ @prow = @current_index - @scrollatrows
482
+ end
483
+ def goto_last_position
484
+ return unless @oldindex
485
+ tmp = @current_index
486
+ @current_index = @oldindex
487
+ @oldindex = tmp
488
+ bounds_check
489
+ end
490
+ def scroll_right
491
+ # I don't think it will ever be less since we've increased it to cols
492
+ if @content_cols <= @cols
493
+ maxpcol = 0
494
+ @pcol = 0
495
+ else
496
+ maxpcol = @content_cols - @cols - 1
497
+ @pcol += 1
498
+ @pcol = maxpcol if @pcol > maxpcol
499
+ end
500
+ # to prevent right from retaining earlier painted values
501
+ # padreader does not do a clear, yet works fine.
502
+ # OK it has an update_panel after padrefresh, that clears it seems.
503
+ #this clears entire window not just the pad
504
+ #FFI::NCurses.wclear(@window.get_window)
505
+ # so border and title is repainted after window clearing
506
+ #
507
+ # Next line was causing all sorts of problems when scrolling with ansi formatted text
508
+ #@repaint_all = true
509
+ end
510
+ def scroll_left
511
+ @pcol -= 1
512
+ end
513
+ #
514
+ #
515
+ #
516
+ # NOTE : if advancing pcol one has to clear the pad or something or else
517
+ # there'll be older content on the right side.
518
+ #
519
+ def handle_key ch
520
+ return :UNHANDLED unless @content
521
+ map_keys unless @mapped_keys
522
+
523
+ @maxrow = @content_rows - @rows
524
+ @maxcol = @content_cols - @cols
525
+
526
+ # need to understand the above line, can go below zero.
527
+ # all this seems to work fine in padreader.rb in util.
528
+ # somehow maxcol going to -33
529
+ @oldrow = @prow
530
+ @oldcol = @pcol
531
+ $log.debug "XXX: PAD got #{ch} prow = #{@prow}"
532
+ begin
533
+ case ch
534
+ when key(?l)
535
+ # TODO take multipler
536
+ #@pcol += 1
537
+ if @curpos < @cols
538
+ @curpos += 1
539
+ end
540
+ when key(?$)
541
+ #@pcol = @maxcol - 1
542
+ @curpos = [@content[@current_index].size, @cols].min
543
+ when key(?h)
544
+ # TODO take multipler
545
+ if @curpos > 0
546
+ @curpos -= 1
547
+ end
548
+ #when key(?0)
549
+ #@curpos = 0
550
+ when ?0.getbyte(0)..?9.getbyte(0)
551
+ if ch == ?0.getbyte(0) && $multiplier == 0
552
+ # copy of C-a - start of line
553
+ @repaint_required = true if @pcol > 0 # tried other things but did not work
554
+ @pcol = 0
555
+ @curpos = 0
556
+ return 0
557
+ end
558
+ # storing digits entered so we can multiply motion actions
559
+ $multiplier *= 10 ; $multiplier += (ch-48)
560
+ return 0
561
+ when ?\C-c.getbyte(0)
562
+ $multiplier = 0
563
+ return 0
564
+ else
565
+ # check for bindings, these cannot override above keys since placed at end
566
+ begin
567
+ ret = process_key ch, self
568
+ $multiplier = 0
569
+ bounds_check
570
+ ## If i press C-x > i get an alert from rwidgets which blacks the screen
571
+ # if i put a padrefresh here it becomes okay but only for one pad,
572
+ # i still need to do it for all pads.
573
+ rescue => err
574
+ $log.error " TEXTPAD ERROR INS #{err} "
575
+ $log.debug(err.backtrace.join("\n"))
576
+ textdialog ["Error in TextPad: #{err} ", *err.backtrace], :title => "Exception"
577
+ end
578
+ ## NOTE if textpad does not handle the event and it goes to form which pops
579
+ # up a messagebox, then padrefresh does not happen, since control does not
580
+ # come back here, so a black rect is left on screen
581
+ # please note that a bounds check will not happen for stuff that
582
+ # is triggered by form, so you'll have to to it yourself or
583
+ # call setrowcol explicity if the cursor is not updated
584
+ return :UNHANDLED if ret == :UNHANDLED
585
+ end
586
+ rescue => err
587
+ $log.error " TEXTPAD ERROR 111 #{err} "
588
+ $log.debug( err) if err
589
+ $log.debug(err.backtrace.join("\n")) if err
590
+ textdialog ["Error in TextPad: #{err} ", *err.backtrace], :title => "Exception"
591
+ $error_message.value = ""
592
+ ensure
593
+ padrefresh
594
+ Ncurses::Panel.update_panels
595
+ end
596
+ return 0
597
+ end # while loop
598
+ def fire_action_event
599
+ return if @content.nil? || @content.size == 0
600
+ require 'rbcurse/core/include/ractionevent'
601
+ aev = TextActionEvent.new self, :PRESS, current_value().to_s, @current_index, @curpos
602
+ fire_handler :PRESS, aev
603
+ end
604
+ def current_value
605
+ @content[@current_index]
606
+ end
607
+ #
608
+ def on_enter_row arow
609
+ return if @content.nil? || @content.size == 0
610
+ require 'rbcurse/core/include/ractionevent'
611
+ aev = TextActionEvent.new self, :ENTER_ROW, current_value().to_s, @current_index, @curpos
612
+ fire_handler :ENTER_ROW, aev
613
+ @repaint_required = true
614
+ end
615
+
616
+ # destroy the pad, this needs to be called from somewhere, like when the app
617
+ # closes or the current window closes , or else we could have a seg fault
618
+ # or some ugliness on the screen below this one (if nested).
619
+
620
+ # Now since we use get_pad from window, upon the window being destroyed,
621
+ # it will call this. Else it will destroy pad
622
+ def destroy
623
+ FFI::NCurses.delwin(@pad) if @pad # when do i do this ? FIXME
624
+ @pad = nil
625
+ end
626
+ def is_visible? index
627
+ j = index - @prow #@toprow
628
+ j >= 0 && j <= @scrollatrows
629
+ end
630
+ def on_enter
631
+ set_form_row
632
+ end
633
+ def set_form_row
634
+ setrowcol @lastrow, @lastcol
635
+ end
636
+ def set_form_col
637
+ end
638
+
639
+ private
640
+
641
+ # check that current_index and prow are within correct ranges
642
+ # sets row (and someday col too)
643
+ # sets repaint_required
644
+
645
+ def bounds_check
646
+ r,c = rowcol
647
+ @current_index = 0 if @current_index < 0
648
+ @current_index = @content.count()-1 if @current_index > @content.count()-1
649
+ ensure_visible
650
+
651
+ #$status_message.value = "visible #{@prow} , #{@current_index} "
652
+ #unless is_visible? @current_index
653
+ #if @prow > @current_index
654
+ ##$status_message.value = "1 #{@prow} > #{@current_index} "
655
+ #@prow -= 1
656
+ #else
657
+ #end
658
+ #end
659
+ #end
660
+ check_prow
661
+ #$log.debug "XXX: PAD BOUNDS ci:#{@current_index} , old #{@oldrow},pr #{@prow}, max #{@maxrow} pcol #{@pcol} maxcol #{@maxcol}"
662
+ @crow = @current_index + r - @prow
663
+ @crow = r if @crow < r
664
+ # 2 depends on whetehr suppressborders
665
+ @crow = @row + @height -2 if @crow >= r + @height -2
666
+ setrowcol @crow, @curpos+c
667
+ lastcurpos @crow, @curpos+c
668
+ if @oldindex != @current_index
669
+ on_enter_row @current_index
670
+ @oldindex = @current_index
671
+ end
672
+ if @oldrow != @prow || @oldcol != @pcol
673
+ # only if scrolling has happened.
674
+ @repaint_required = true
675
+ end
676
+ end
677
+ def lastcurpos r,c
678
+ @lastrow = r
679
+ @lastcol = c
680
+ end
681
+
682
+
683
+ # check that prow and pcol are within bounds
684
+
685
+ def check_prow
686
+ @prow = 0 if @prow < 0
687
+ @pcol = 0 if @pcol < 0
688
+
689
+ cc = @content.count
690
+
691
+ if cc < @rows
692
+ @prow = 0
693
+ else
694
+ maxrow = cc - @rows - 1
695
+ if @prow > maxrow
696
+ @prow = maxrow
697
+ end
698
+ end
699
+ # we still need to check the max that prow can go otherwise
700
+ # the pad shows earlier stuff.
701
+ #
702
+ return
703
+ # the following was causing bugs if content was smaller than pad
704
+ if @prow > @maxrow-1
705
+ @prow = @maxrow-1
706
+ end
707
+ if @pcol > @maxcol-1
708
+ @pcol = @maxcol-1
709
+ end
710
+ end
711
+ public
712
+ ##
713
+ # Ask user for string to search for
714
+ def ask_search
715
+ str = get_string("Enter pattern: ")
716
+ return if str.nil?
717
+ str = @last_regex if str == ""
718
+ return if str == ""
719
+ ix = next_match str
720
+ return unless ix
721
+ @last_regex = str
722
+
723
+ #@oldindex = @current_index
724
+ @current_index = ix[0]
725
+ @curpos = ix[1]
726
+ ensure_visible
727
+ end
728
+ ##
729
+ # Find next matching row for string accepted in ask_search
730
+ #
731
+ def find_more
732
+ return unless @last_regex
733
+ ix = next_match @last_regex
734
+ return unless ix
735
+ #@oldindex = @current_index
736
+ @current_index = ix[0]
737
+ @curpos = ix[1]
738
+ ensure_visible
739
+ end
740
+
741
+ ##
742
+ # Find the next row that contains given string
743
+ # @return row and col offset of match, or nil
744
+ # @param String to find
745
+ def next_match str
746
+ first = nil
747
+ ## content can be string or Chunkline, so we had to write <tt>index</tt> for this.
748
+ ## =~ does not give an error, but it does not work.
749
+ @content.each_with_index do |line, ix|
750
+ col = line.index str
751
+ if col
752
+ first ||= [ ix, col ]
753
+ if ix > @current_index
754
+ return [ix, col]
755
+ end
756
+ end
757
+ end
758
+ return first
759
+ end
760
+ ##
761
+ # Ensure current row is visible, if not make it first row
762
+ # NOTE - need to check if its at end and then reduce scroll at rows, check_prow does that
763
+ #
764
+ # @param current_index (default if not given)
765
+ #
766
+ def ensure_visible row = @current_index
767
+ unless is_visible? row
768
+ @prow = @current_index
769
+ end
770
+ end
771
+ def forward_word
772
+ $multiplier = 1 if !$multiplier || $multiplier == 0
773
+ line = @current_index
774
+ buff = @content[line].to_s
775
+ return unless buff
776
+ pos = @curpos || 0 # list does not have curpos
777
+ $multiplier.times {
778
+ found = buff.index(/[[:punct:][:space:]]\w/, pos)
779
+ if !found
780
+ # if not found, we've lost a counter
781
+ if line+1 < @content.length
782
+ line += 1
783
+ else
784
+ return
785
+ end
786
+ pos = 0
787
+ else
788
+ pos = found + 1
789
+ end
790
+ $log.debug " forward_word: pos #{pos} line #{line} buff: #{buff}"
791
+ }
792
+ $multiplier = 0
793
+ @current_index = line
794
+ @curpos = pos
795
+ ensure_visible
796
+ #@buffer = @list[@current_index].to_s
797
+ #set_form_row
798
+ #set_form_col pos
799
+ @repaint_required = true
800
+ end
801
+
802
+ end # class textpad
803
+
804
+ # a test renderer to see how things go
805
+ class DefaultFileRenderer
806
+ def render pad, lineno, text
807
+ bg = :black
808
+ fg = :white
809
+ att = NORMAL
810
+ #cp = $datacolor
811
+ cp = get_color($datacolor, fg, bg)
812
+ ## XXX believe it or not, the next line can give you "invalid byte sequence in UTF-8
813
+ # even when processing filename at times.
814
+ if text =~ /^\s*# / || text =~ /^\s*## /
815
+ fg = :red
816
+ #att = BOLD
817
+ cp = get_color($datacolor, fg, bg)
818
+ elsif text =~ /^\s*#/
819
+ fg = :blue
820
+ cp = get_color($datacolor, fg, bg)
821
+ elsif text =~ /^\s*(class|module) /
822
+ fg = :cyan
823
+ att = BOLD
824
+ cp = get_color($datacolor, fg, bg)
825
+ elsif text =~ /^\s*def / || text =~ /^\s*function /
826
+ fg = :yellow
827
+ att = BOLD
828
+ cp = get_color($datacolor, fg, bg)
829
+ elsif text =~ /^\s*(end|if |elsif|else|begin|rescue|ensure|include|extend|while|unless|case |when )/
830
+ fg = :magenta
831
+ att = BOLD
832
+ cp = get_color($datacolor, fg, bg)
833
+ elsif text =~ /^\s*=/
834
+ # rdoc case
835
+ fg = :blue
836
+ bg = :white
837
+ cp = get_color($datacolor, fg, bg)
838
+ att = REVERSE
839
+ end
840
+ FFI::NCurses.wattron(pad,FFI::NCurses.COLOR_PAIR(cp) | att)
841
+ FFI::NCurses.mvwaddstr(pad, lineno, 0, text)
842
+ FFI::NCurses.wattroff(pad,FFI::NCurses.COLOR_PAIR(cp) | att)
843
+
844
+ end
845
+ end
846
+ end