class Cygnus::TextPad
Attributes
Public Class Methods
You may pass height, width, row and col for creating a window otherwise a fullscreen window will be created. If you pass a window from caller then that window will be used. Some keys are trapped, jkhl space, pgup, pgdown, end, home, t b This is currently very minimal and was created to get me started to integrating pads into other classes such as textview.
# File lib/cygnus/textpad.rb, line 47 def initialize form=nil, config={}, &block @editable = false @focusable = true @config = config @row = @col = 0 @prow = @pcol = 0 @startrow = 0 @startcol = 0 @list = [] super ## NOTE # --------------------------------------------------- # Since we are using pads, you need to get your height, width and rows correct # Make sure the height factors in the row, else nothing may show # --------------------------------------------------- #@height = @height.ifzero(FFI::NCurses.LINES) #@width = @width.ifzero(FFI::NCurses.COLS) @rows = @height @cols = @width # NOTE XXX if cols is > COLS then padrefresh can fail @startrow = @row @startcol = @col #@suppress_border = config[:suppress_border] @row_offset = @col_offset = 1 unless @suppress_border @startrow += 1 @startcol += 1 @rows -=3 # 3 is since print_border_only reduces one from width, to check whether this is correct @cols -=3 else # seeing why nothing is printing @rows -=0 # 3 is since print_border_only reduces one from width, to check whether this is correct ## if next is 0 then padrefresh doesn't print @cols -=1 end @row_offset = @col_offset = 0 if @suppress_borders @top = @row @left = @col @lastrow = @row + 1 @lastcol = @col + 1 @_events << :PRESS @_events << :ENTER_ROW @scrollatrows = @height - 3 init_vars end
Public Instance Methods
Ask user for string to search for This uses the dialog, but what if user wants the old style. Isn’t there a cleaner way to let user override style, or allow user to use own UI for getting pattern and then passing here.
# File lib/cygnus/textpad.rb, line 722 def ask_search str = get_string("Enter pattern: ") return if str.nil? str = @last_regex if str == "" return if str == "" ix = next_match str return unless ix @last_regex = str #@oldindex = @current_index @current_index = ix[0] @curpos = ix[1] ensure_visible end
# File lib/cygnus/textpad.rb, line 440 def bottom_of_window @current_index = @prow + @scrollatrows $multiplier ||= 0 if $multiplier > 0 @current_index -= $multiplier $multiplier = 0 end end
# File lib/cygnus/textpad.rb, line 223 def content raise "content is nil " unless @content return @content end
returns current value (what cursor is on)
# File lib/cygnus/textpad.rb, line 614 def current_value @content[@current_index] end
move cursor backward by one char (currently will not pan)
# File lib/cygnus/textpad.rb, line 824 def cursor_backward $multiplier = 1 if $multiplier == 0 if @curpos > 0 @curpos -= $multiplier @curpos = 0 if @curpos < 0 @repaint_required = true end $multiplier = 0 end
moves cursor to start of line, panning if required
# File lib/cygnus/textpad.rb, line 845 def cursor_bol # copy of C-a - start of line @repaint_required = true if @pcol > 0 @pcol = 0 @curpos = 0 end
moves cursor to end of line also panning window if necessary NOTE: if one line on another page (not displayed) is way longer than any displayed line, then this will pan way ahead, so may not be very intelligent in such situations.
# File lib/cygnus/textpad.rb, line 837 def cursor_eol # pcol is based on max length not current line's length @pcol = @content_cols - @cols - 1 @curpos = @content[@current_index].size @repaint_required = true end
move cursor forward by one char (currently will not pan)
# File lib/cygnus/textpad.rb, line 811 def cursor_forward $multiplier = 1 if $multiplier == 0 if @curpos < @cols @curpos += $multiplier if @curpos > @cols @curpos = @cols end @repaint_required = true end $multiplier = 0 end
Now since we use get_pad from window, upon the window being destroyed, it will call this. Else it will destroy pad
# File lib/cygnus/textpad.rb, line 635 def destroy FFI::NCurses.delwin(@pad) if @pad # when do i do this ? FIXME @pad = nil end
move down a line mimicking vim’s j key @param [int] multiplier entered prior to invoking key
# File lib/cygnus/textpad.rb, line 455 def down num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier) #@oldindex = @current_index if num > 10 @current_index += num ensure_visible $multiplier = 0 end
Ensure current row is visible, if not make it first row NOTE - need to check if its at end and then reduce scroll at rows, check_prow
does that
@param current_index
(default if not given)
# File lib/cygnus/textpad.rb, line 774 def ensure_visible row = @current_index unless is_visible? row @prow = @current_index end end
supply a filename as source for textpad Reads up file into @content One can optionally send in a method which takes a filename and returns an array of data This is required if you are processing files which are binary such as zip/archives and wish to print the contents. (e.g. cygnus gem sends in :get_file_contents).
filename("a.c", method(:get_file_contents))
# File lib/cygnus/textpad.rb, line 187 def filename(filename, reader=nil) @file = filename unless File.exists? filename alert "#{filename} does not exist" return end @filetype = File.extname filename if reader @content = reader.call(filename) else @content = File.open(filename,"r").readlines end if @filetype == "" if @content.first.index("ruby") @filetype = ".rb" end end init_vars @repaint_all = true @_populate_needed = true end
Find next matching row for string accepted in ask_search
# File lib/cygnus/textpad.rb, line 739 def find_more return unless @last_regex ix = next_match @last_regex return unless ix #@oldindex = @current_index @current_index = ix[0] @curpos = ix[1] ensure_visible end
event when user hits enter on a row, user would bind :PRESS
# File lib/cygnus/textpad.rb, line 606 def fire_action_event return if @content.nil? || @content.size == 0 require 'rbcurse/core/include/ractionevent' aev = TextActionEvent.new self, :PRESS, current_value().to_s, @current_index, @curpos fire_handler :PRESS, aev end
pass in formatted text along with parser (:tmux or :ansi)
# File lib/cygnus/textpad.rb, line 281 def formatted_text text, fmt require 'rbcurse/core/include/chunk' @formatted_text = text @color_parser = fmt @repaint_required = true # don't know if start is always required. so putting in caller #goto_start #remove_all end
jumps cursor to next work, like vim’s w key
# File lib/cygnus/textpad.rb, line 782 def forward_word $multiplier = 1 if !$multiplier || $multiplier == 0 line = @current_index buff = @content[line].to_s return unless buff pos = @curpos || 0 # list does not have curpos $multiplier.times { found = buff.index(/[[:punct:][:space:]]\w/, pos) if !found # if not found, we've lost a counter if line+1 < @content.length line += 1 else return end pos = 0 else pos = found + 1 end $log.debug " forward_word: pos #{pos} line #{line} buff: #{buff}" } $multiplier = 0 @current_index = line @curpos = pos ensure_visible @repaint_required = true end
goto last line of file
# File lib/cygnus/textpad.rb, line 413 def goto_end #@oldindex = @current_index $multiplier ||= 0 if $multiplier > 0 goto_line $multiplier - 1 return end @current_index = @content.count() - 1 @prow = @current_index - @scrollatrows $multiplier = 0 end
# File lib/cygnus/textpad.rb, line 513 def goto_last_position return unless @oldindex tmp = @current_index @current_index = @oldindex @oldindex = tmp bounds_check end
# File lib/cygnus/textpad.rb, line 424 def goto_line line ## we may need to calculate page, zfm style and place at right position for ensure visible #line -= 1 @current_index = line ensure_visible line bounds_check $multiplier = 0 end
goto first line of file
# File lib/cygnus/textpad.rb, line 399 def goto_start #@oldindex = @current_index $multiplier ||= 0 if $multiplier > 0 goto_line $multiplier - 1 return end @current_index = 0 @curpos = @pcol = @prow = 0 @prow = 0 $multiplier = 0 end
# File lib/cygnus/textpad.rb, line 547 def handle_key ch return :UNHANDLED unless @content map_keys unless @mapped_keys @oldrow = @prow @oldcol = @pcol $log.debug "XXX: PAD got #{ch} prow = #{@prow}" begin case ch when ?0.getbyte(0)..?9.getbyte(0) if ch == ?0.getbyte(0) && $multiplier == 0 cursor_bol return 0 end # storing digits entered so we can multiply motion actions $multiplier *= 10 ; $multiplier += (ch-48) return 0 when ?\C-c.getbyte(0) $multiplier = 0 return 0 else # check for bindings, these cannot override above keys since placed at end begin ret = process_key ch, self $multiplier = 0 bounds_check ## If i press C-x > i get an alert from rwidgets which blacks the screen # if i put a padrefresh here it becomes okay but only for one pad, # i still need to do it for all pads. rescue => err $log.error " TEXTPAD ERROR INS #{err} " $log.debug(err.backtrace.join("\n")) textdialog ["Error in TextPad: #{err} ", *err.backtrace], :title => "Exception" end ## NOTE if textpad does not handle the event and it goes to form which pops # up a messagebox, then padrefresh does not happen, since control does not # come back here, so a black rect is left on screen # please note that a bounds check will not happen for stuff that # is triggered by form, so you'll have to to it yourself or # call setrowcol explicity if the cursor is not updated return :UNHANDLED if ret == :UNHANDLED end rescue => err $log.error " TEXTPAD ERROR 591 #{err} " $log.debug( err) if err $log.debug(err.backtrace.join("\n")) if err textdialog ["Error in TextPad: #{err} ", *err.backtrace], :title => "Exception" $error_message.value = "" ensure padrefresh Ncurses::Panel.update_panels end return 0 end
# File lib/cygnus/textpad.rb, line 94 def init_vars $multiplier = 0 @oldindex = @current_index = 0 # column cursor @prow = @pcol = @curpos = 0 if @row && @col @lastrow = @row + 1 @lastcol = @col + 1 end @repaint_required = true end
return true if the given row is visible
# File lib/cygnus/textpad.rb, line 641 def is_visible? index j = index - @prow #@toprow j >= 0 && j <= @scrollatrows end
key mappings
# File lib/cygnus/textpad.rb, line 369 def map_keys @mapped_keys = true bind_key([?g,?g], 'goto_start'){ goto_start } # mapping double keys like vim bind_key(279, 'goto_start'){ goto_start } bind_keys([?G,277], 'goto end'){ goto_end } bind_keys([?k,KEY_UP], "Up"){ up } bind_keys([?j,KEY_DOWN], "Down"){ down } bind_key(?\C-e, "Scroll Window Down"){ scroll_window_down } bind_key(?\C-y, "Scroll Window Up"){ scroll_window_up } bind_keys([32,338, ?\C-d], "Scroll Forward"){ scroll_forward } bind_keys([?\C-b,339]){ scroll_backward } # the next one invalidates the single-quote binding for bookmarks #bind_key([?',?']){ goto_last_position } # vim , goto last row position (not column) bind_key(?/, :ask_search) bind_key(?n, :find_more) bind_key([?\C-x, ?>], :scroll_right) bind_key([?\C-x, ?<], :scroll_left) bind_key(?\M-l, :scroll_right) bind_key(?\M-h, :scroll_left) bind_key(?L, :bottom_of_window) bind_key(?M, :middle_of_window) bind_key(?H, :top_of_window) bind_key(?w, :forward_word) bind_key(?l, :cursor_forward) bind_key(?h, :cursor_backward) bind_key(?$, :cursor_eol) bind_key(KEY_ENTER, :fire_action_event) end
# File lib/cygnus/textpad.rb, line 448 def middle_of_window @current_index = @prow + (@scrollatrows/2) $multiplier = 0 end
Find the next row that contains given string @return row and col offset of match, or nil @param String to find
# File lib/cygnus/textpad.rb, line 753 def next_match str first = nil ## content can be string or Chunkline, so we had to write <tt>index</tt> for this. ## =~ does not give an error, but it does not work. @content.each_with_index do |line, ix| col = line.index str if col first ||= [ ix, col ] if ix > @current_index return [ix, col] end end end return first end
called when this widget is entered, by form
# File lib/cygnus/textpad.rb, line 647 def on_enter set_form_row end
execute binding when a row is entered, used more in lists to display some text in a header or footer as one traverses
# File lib/cygnus/textpad.rb, line 621 def on_enter_row arow return if @content.nil? || @content.size == 0 require 'rbcurse/core/include/ractionevent' aev = TextActionEvent.new self, :ENTER_ROW, current_value().to_s, @current_index, @curpos fire_handler :ENTER_ROW, aev @repaint_required = true end
write pad onto window
private
# File lib/cygnus/textpad.rb, line 293 def padrefresh top = @window.top left = @window.left sr = @startrow + top sc = @startcol + left retval = FFI::NCurses.prefresh(@pad,@prow,@pcol, sr , sc , @rows + sr , @cols+ sc ); $log.warn "XXX: PADREFRESH #{retval}, #{@prow}, #{@pcol}, #{sr}, #{sc}, #{@rows+sr}, #{@cols+sc}." if retval == -1 # padrefresh can fail if width is greater than NCurses.COLS #FFI::NCurses.prefresh(@pad,@prow,@pcol, @startrow + top, @startcol + left, @rows + @startrow + top, @cols+@startcol + left); end
2013-03-07 - 19:57 changed width to @content_cols since data not printing
in some cases fully when ansi sequences were present int some line but not in others lines without ansi were printing less by a few chars. This was prolly copied from rwindow, where it is okay since its for a specific width
# File lib/cygnus/textpad.rb, line 246 def print(string, _width = @content_cols) #return unless visible? w = _width == 0? Ncurses.COLS : _width FFI::NCurses.waddnstr(@pad,string.to_s, w) # changed 2011 dts end
default method for rendering a line
# File lib/cygnus/textpad.rb, line 158 def render pad, lineno, text if text.is_a? Chunks::ChunkLine FFI::NCurses.wmove @pad, lineno, 0 a = get_attrib @attrib show_colored_chunks text, nil, a return end if @renderer @renderer.render @pad, lineno, text else ## messabox does have a method to paint the whole window in bg color its in rwidget.rb att = NORMAL FFI::NCurses.wattron(@pad, @cp | att) FFI::NCurses.mvwaddstr(@pad,lineno, 0, @clearstring) if @clearstring FFI::NCurses.mvwaddstr(@pad,lineno, 0, @content[lineno]) #FFI::NCurses.mvwaddstr(pad, lineno, 0, text) FFI::NCurses.wattroff(@pad, @cp | att) end end
supply a custom renderer that implements +render()+ @see render
# File lib/cygnus/textpad.rb, line 152 def renderer r @renderer = r end
# File lib/cygnus/textpad.rb, line 322 def repaint ## 2013-03-08 - 21:01 This is the fix to the issue of form callign an event like ? or F1 # which throws up a messagebox which leaves a black rect. We have no place to put a refresh # However, form does call repaint for all objects, so we can do a padref here. Otherwise, # it would get rejected. UNfortunately this may happen more often we want, but we never know # when something pops up on the screen. padrefresh unless @repaint_required return unless @repaint_required if @formatted_text $log.debug "XXX: INSIDE FORMATTED TEXT " l = RubyCurses::Utils.parse_formatted_text(@color_parser, @formatted_text) text(l) @formatted_text = nil end ## moved this line up or else create_p was crashing @window ||= @graphic populate_pad if @_populate_needed #HERE we need to populate once so user can pass a renderer unless @suppress_border if @repaint_all ## XXX im not getting the background color. #@window.print_border_only @top, @left, @height-1, @width, $datacolor clr = get_color $datacolor, @color, @bgcolor #@window.print_border @top, @left, @height-1, @width, clr @window.print_border_only @top, @left, @height-1, @width, clr print_title @repaint_footer_required = true if @oldrow != @current_index print_foot if @print_footer && !@suppress_borders && @repaint_footer_required @window.wrefresh end end padrefresh Ncurses::Panel.update_panels @repaint_required = false @repaint_all = false end
scrolls lines backward a window full at a time, on pressing pageup C-u may not work since it is trapped by form earlier. Need to fix
# File lib/cygnus/textpad.rb, line 508 def scroll_backward #@oldindex = @current_index @current_index -= @scrollatrows @prow = @current_index - @scrollatrows end
scrolls lines a window full at a time, on pressing ENTER or C-d or pagedown
# File lib/cygnus/textpad.rb, line 500 def scroll_forward #@oldindex = @current_index @current_index += @scrollatrows @prow = @current_index - @scrollatrows end
# File lib/cygnus/textpad.rb, line 540 def scroll_left @pcol -= 1 end
# File lib/cygnus/textpad.rb, line 520 def scroll_right # I don't think it will ever be less since we've increased it to cols if @content_cols <= @cols maxpcol = 0 @pcol = 0 else maxpcol = @content_cols - @cols - 1 @pcol += 1 @pcol = maxpcol if @pcol > maxpcol end # to prevent right from retaining earlier painted values # padreader does not do a clear, yet works fine. # OK it has an update_panel after padrefresh, that clears it seems. #this clears entire window not just the pad #FFI::NCurses.wclear(@window.get_window) # so border and title is repainted after window clearing # # Next line was causing all sorts of problems when scrolling with ansi formatted text #@repaint_all = true end
scrolls window down mimicking vim C-e @param [int] multiplier entered prior to invoking key
# File lib/cygnus/textpad.rb, line 479 def scroll_window_down num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier) @prow += num if @prow > @current_index @current_index += 1 end #check_prow $multiplier = 0 end
scrolls window up mimicking vim C-y @param [int] multiplier entered prior to invoking key
# File lib/cygnus/textpad.rb, line 490 def scroll_window_up num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier) @prow -= num unless is_visible? @current_index # one more check may be needed here TODO @current_index -= num end $multiplier = 0 end
called by form
# File lib/cygnus/textpad.rb, line 655 def set_form_col end
called by form
# File lib/cygnus/textpad.rb, line 651 def set_form_row setrowcol @lastrow, @lastcol end
# File lib/cygnus/textpad.rb, line 252 def show_colored_chunks(chunks, defcolor = nil, defattr = nil) #return unless visible? chunks.each do |chunk| #|color, chunk, attrib| case chunk when Chunks::Chunk color = chunk.color attrib = chunk.attrib text = chunk.text when Array # for earlier demos that used an array color = chunk[0] attrib = chunk[2] text = chunk[1] end color ||= defcolor attrib ||= defattr || NORMAL #cc, bg = ColorMap.get_colors_for_pair color #$log.debug "XXX: CHUNK textpad #{text}, cp #{color} , attrib #{attrib}. #{cc}, #{bg} " FFI::NCurses.wcolor_set(@pad, color,nil) if color FFI::NCurses.wattron(@pad, attrib) if attrib print(text) FFI::NCurses.wattroff(@pad, attrib) if attrib end end
XXX in list text returns the selected row, list returns the full thing, keep consistent
# File lib/cygnus/textpad.rb, line 213 def text(lines) #raise "text() receiving null content" unless lines return @content if lines.empty? @content = lines @_populate_needed = true @repaint_all = true init_vars self end
# File lib/cygnus/textpad.rb, line 432 def top_of_window @current_index = @prow $multiplier ||= 0 if $multiplier > 0 @current_index += $multiplier $multiplier = 0 end end
move up a line mimicking vim’s k key @param [int] multiplier entered prior to invoking key
# File lib/cygnus/textpad.rb, line 464 def up num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier) #@oldindex = @current_index if num > 10 @current_index -= num #unless is_visible? @current_index #if @prow > @current_index ##$status_message.value = "1 #{@prow} > #{@current_index} " #@prow -= 1 #else #end #end $multiplier = 0 end
Private Instance Methods
check that current_index
and prow are within correct ranges sets row (and someday col too) sets repaint_required
# File lib/cygnus/textpad.rb, line 664 def bounds_check r,c = rowcol @current_index = 0 if @current_index < 0 @current_index = @content.count()-1 if @current_index > @content.count()-1 ensure_visible check_prow #$log.debug "XXX: PAD BOUNDS ci:#{@current_index} , old #{@oldrow},pr #{@prow}, max #{@maxrow} pcol #{@pcol} maxcol #{@maxcol}" @crow = @current_index + r - @prow @crow = r if @crow < r # 2 depends on whetehr suppressborders @crow = @row + @height -2 if @crow >= r + @height -2 setrowcol @crow, @curpos+c lastcurpos @crow, @curpos+c if @oldindex != @current_index on_enter_row @current_index @oldindex = @current_index end if @oldrow != @prow || @oldcol != @pcol # only if scrolling has happened. @repaint_required = true end end
check that prow and pcol are within bounds
# File lib/cygnus/textpad.rb, line 697 def check_prow @prow = 0 if @prow < 0 @pcol = 0 if @pcol < 0 cc = @content.count if cc < @rows @prow = 0 else maxrow = cc - @rows - 1 if @prow > maxrow @prow = maxrow end end # we still need to check the max that prow can go otherwise # the pad shows earlier stuff. # return end
length of longest string in array This will give a ‘wrong’ max length if the array has ansi color escape sequences in it which inc the length but won’t be printed. Such lines actually have less length when printed So in such cases, give more space to the pad.
# File lib/cygnus/textpad.rb, line 314 def content_cols longest = @content.max_by(&:length) ## 2013-03-06 - 20:41 crashes here for some reason when man gives error message no man entry return 0 unless longest longest.length end
XXX in list text returns the selected row, list returns the full thing, keep consistent
# File lib/cygnus/textpad.rb, line 111 def create_pad destroy if @pad #@pad = FFI::NCurses.newpad(@content_rows, @content_cols) @pad = @window.get_pad(@content_rows, @content_cols ) end
convenience method to return byte
# File lib/cygnus/textpad.rb, line 306 def key x x.getbyte(0) end
save last cursor position so when reentering, cursor can be repositioned
# File lib/cygnus/textpad.rb, line 689 def lastcurpos r,c @lastrow = r @lastcol = c end
create and populate pad
# File lib/cygnus/textpad.rb, line 119 def populate_pad @_populate_needed = false # how can we make this more sensible ? FIXME #@renderer ||= DefaultFileRenderer.new #if ".rb" == @filetype @content_rows = @content.count @content_cols = content_cols() # this should be explicit and not "intelligent" #@title += " [ #{@content_rows},#{@content_cols}] " if @cols > 50 @content_rows = @rows if @content_rows < @rows @content_cols = @cols if @content_cols < @cols #$log.debug "XXXX content_cols = #{@content_cols}" create_pad # clearstring is the string required to clear the pad to backgroud color @clearstring = nil cp = get_color($datacolor, @color, @bgcolor) @cp = FFI::NCurses.COLOR_PAIR(cp) if cp != $datacolor @clearstring ||= " " * @width end Ncurses::Panel.update_panels @content.each_index { |ix| #FFI::NCurses.mvwaddstr(@pad,ix, 0, @content[ix]) render @pad, ix, @content[ix] } end