class Canis::Bottomline

some variables are polluting space of including app, we should make this a class.

Attributes

message_row[RW]
name[RW]
window[RW]

Public Class Methods

new(win=nil, row=nil) click to toggle source
# File lib/canis/core/util/extras/bottomline.rb, line 94
def initialize win=nil, row=nil
  @window = win
  @message_row = 0 # 2011-10-8
end

Public Instance Methods

XXXchoose( *items, &details ) click to toggle source

This method is HighLine's menu handler. For simple usage, you can just pass all the menu items you wish to display. At that point, choose() will build and display a menu, walk the user through selection, and return their choice amoung the provided items. You might use this in a case statement for quick and dirty menus.

However, choose() is capable of much more. If provided, a block will be passed a HighLine::Menu object to configure. Using this method, you can customize all the details of menu handling from index display, to building a complete shell-like menuing system. See HighLine::Menu for all the methods it responds to.

Raises EOFError if input is exhausted.

# File lib/canis/core/util/extras/bottomline.rb, line 1635
def XXXchoose( *items, &details )
  @menu = @question = Menu.new(&details)
  @menu.choices(*items) unless items.empty?

  # Set _answer_type_ so we can double as the Question for ask().
  @menu.answer_type = if @menu.shell
                        lambda do |command|    # shell-style selection
                          first_word = command.to_s.split.first || ""

                          options = @menu.options
                          options.extend(OptionParser::Completion)
                          answer = options.complete(first_word)

                          if answer.nil?
                            raise Question::NoAutoCompleteMatch
                          end

                          [answer.last, command.sub(/^\s*#{first_word}\s*/, "")]
                        end
                      else
                        @menu.options          # normal menu selection, by index or name
                      end

  # Provide hooks for ERb layouts.
  @header   = @menu.header
  @prompt   = @menu.prompt

  if @menu.shell
    selected = ask("Ignored", @menu.answer_type)
    @menu.select(self, *selected)
  else
    selected = ask("Ignored", @menu.answer_type)
    @menu.select(self, selected)
  end
end
agree( yes_or_no_question, character = nil ) { |q| ... } click to toggle source
# File lib/canis/core/util/extras/bottomline.rb, line 1503
def agree( yes_or_no_question, character = nil )
  ask(yes_or_no_question, lambda { |yn| yn.downcase[0] == ?y}) do |q|
    q.validate                 = /\Ay(?:es)?|no?\Z/i
    q.responses[:not_valid]    = 'Please enter "yes" or "no".'
    q.responses[:ask_on_error] = :question
    q.character                = character
    q.limit                    = 1 if character

    yield q if block_given?
  end
end
ask(question, answer_type=String, &details) click to toggle source

— highline classes }}}

# File lib/canis/core/util/extras/bottomline.rb, line 977
def ask(question, answer_type=String, &details)
 $log.debug "XXXX inside ask win #{@window} "
  @window ||= _create_footer_window
  #@window.show #unless @window.visible?

  @question ||= Question.new(question, answer_type, &details)
  say(@question) #unless @question.echo == true

  @completion_proc = @question.completion_proc
  @change_proc = @question.change_proc
  @key_handler_proc = @question.key_handler_proc
  @default = @question.default
  $log.debug "XXX: ASK RBGETS got default: #{@default} "
  @help_text = @question.help_text
  @answer_type = @question.answer_type
  if @question.answer_type.is_a? Array
    @completion_proc = Proc.new{|str| @answer_type.dup.grep Regexp.new("^#{str}") }
  end

  begin
    # FIXME a C-c still returns default to user !
    @answer = @question.answer_or_default(get_response) 
    unless @question.valid_answer?(@answer)
      explain_error(:not_valid)
      raise QuestionError
    end

    @answer = @question.convert(@answer)

    if @question.in_range?(@answer)
      if @question.confirm
        # need to add a layer of scope to ask a question inside a
        # question, without destroying instance data
        context_change = self.class.new(@input, @output, @wrap_at, @page_at)
        if @question.confirm == true
          confirm_question = "Are you sure?  "
        else
          # evaluate ERb under initial scope, so it will have
          # access to @question and @answer
          template  = ERB.new(@question.confirm, nil, "%")
          confirm_question = template.result(binding)
        end
        unless context_change.agree(confirm_question)
          explain_error(nil)
          raise QuestionError
        end
      end

      @answer
    else
      explain_error(:not_in_range)
      raise QuestionError
    end
  rescue QuestionError
    retry
  rescue ArgumentError, NameError => error
    #raise
    raise if error.is_a?(NoMethodError)
    if error.message =~ /ambiguous/
      # the assumption here is that OptionParser::Completion#complete
      # (used for ambiguity resolution) throws exceptions containing
      # the word 'ambiguous' whenever resolution fails
      explain_error(:ambiguous_completion)
    else
      explain_error(:invalid_type)
    end
    retry
  rescue Question::NoAutoCompleteMatch
    explain_error(:no_completion)
    retry
  rescue Interrupt
    $log.warn "User interrupted ask() get_response does not want operation to proceed"
    return nil
  ensure
    @question = nil    # Reset Question object.
    $log.debug "XXX: HIDE B AT ENSURE OF ASK"
    hide_bottomline # assuming this method made it visible, not sure if this is called.
  end
end
choose(list1, config={}) click to toggle source

Allows a selection in which options are shown over prompt. As user types options are narrowed down. NOTE: For a directory we are not showing a slash, so currently you have to enter the slash manually when searching. FIXME we can put remarks in fron as in memacs such as [No matches] or [single completion] @param [Array] a list of items to select from NOTE: if you use this please copy it to your app. This does not conform to highline's choose, and I'd like to somehow get it to be identical.

# File lib/canis/core/util/extras/bottomline.rb, line 1524
def choose list1, config={}
  dirlist = true
  start = 0
  case list1
  when NilClass
    #list1 = Dir.glob("*")
    list1 = Dir.glob("*").collect { |f| File.directory?(f) ? f+"/" : f  }
  when String
    list1 = Dir.glob(list1).collect { |f| File.directory?(f) ? f+"/" : f  }
  when Array
    dirlist = false
    # let it be, that's how it should come
  else
    # Dir listing as default
    #list1 = Dir.glob("*")
    list1 = Dir.glob("*").collect { |f| File.directory?(f) ? f+"/" : f  }
  end
  require 'canis/core/util/rcommandwindow'
  prompt = config[:prompt] || "Choose: "
  layout = { :height => 5, :width => Ncurses.COLS-1, :top => Ncurses.LINES-6, :left => 0 }
  rc = CommandWindow.new nil, :layout => layout, :box => true, :title => config[:title]
  begin
    w = rc.window
    rc.display_menu list1
    # earlier wmove bombed, now move is (window.rb 121)
    str = ask(prompt) { |q| q.help_text = config[:help_text] ;  q.change_proc = Proc.new { |str| w.wmove(1,1) ; w.wclrtobot;  

      l = list1.select{|e| e.index(str)==0}  ;  # select those starting with str

      if (l.size == 0 || str[-1]=='/') && dirlist
        # used to help complete directories so we can drill up and down
        #l = Dir.glob(str+"*")
        l = Dir.glob(str +"*").collect { |f| File.directory?(f) ? f+"/" : f  }
      end
      rc.display_menu l; 
      l
    }
    q.key_handler_proc = Proc.new { |ch| 
      # this is not very good since it does not respect above list which is filtered
      # # need to clear the screen before printing - FIXME
      case ch
      when ?\C-n.getbyte(0)
        start += 2 if start < list1.length - 2

        w.wmove(1,1) ; w.wclrtobot;  
        rc.display_menu list1, :startindex => start
      when ?\C-p.getbyte(0)
        start -= 2 if start > 2
        w.wmove(1,1) ; w.wclrtobot;  
        rc.display_menu list1, :startindex => start
      else
        alert "unhalderlind by jey "
      end
    
    }
    }
    # need some validation here that its in the list TODO
  ensure
    rc.destroy
    rc = nil
    $log.debug "XXX: HIDE B IN ENSURE"
    hide_bottomline # since we called ask() we need to close bottomline
  end
    $log.debug "XXX: HIDE B AT END OF ASK"
  #hide_bottomline # since we called ask() we need to close bottomline
  return str
end
clear_line(len=100, from=0) click to toggle source

clears line from 0, not okay in some cases

# File lib/canis/core/util/extras/bottomline.rb, line 1477
def clear_line len=100, from=0
  print_str("%-*s" % [len," "], :y => from)
end
destroy() click to toggle source

destroy window, to be called by app when shutting down since we are normally hiding the window only.

# File lib/canis/core/util/extras/bottomline.rb, line 1083
def destroy 
  $log.debug "bottomline destroy... #{@window} "
  @window.destroy if @window
  @window = nil
end
display_list(text, config={}) click to toggle source
XXX FIXME this uses only rcommand so what is it doing here.

def display_list_interactive text, config={}

returns a ListObject since you may not know what the list itself contained
You can do ret.list[ret.current_index] to get value
# File lib/canis/core/util/extras/bottomline.rb, line 1610
def display_list text, config={}
  require 'canis/core/util/rcommandwindow'
  ht = config[:height] || 15
  layout = { :height => ht, :width => Ncurses.COLS-1, :top => Ncurses.LINES-ht+1, :left => 0 }
  rc = CommandWindow.new nil, :layout => layout, :box => true, :title => config[:title]
  w = rc.window
  ret = rc.display_interactive text
  rc = nil
  ret
end
display_text_interactive(text, config={}) click to toggle source

XXX FIXME this uses only rcommand so what is it doing here.

# File lib/canis/core/util/extras/bottomline.rb, line 1592
def display_text_interactive text, config={}
  require 'canis/core/util/rcommandwindow'
  ht = config[:height] || 15
  layout = { :height => ht, :width => Ncurses.COLS-1, :top => Ncurses.LINES-ht+1, :left => 0 }
  rc = CommandWindow.new nil, :layout => layout, :box => true, :title => config[:title]
  w = rc.window
  #rc.text "There was a quick  brown fox who ran over the lazy dog and then went over the moon over and over again and again"
  rc.display_interactive(text) { |l|
    l.focussed_attrib = 'bold' # Ncurses::A_UNDERLINE
    l.focussed_symbol = '>'
  }
  rc = nil
end
explain_error( error ) click to toggle source

A helper method for sending the output stream and error and repeat of the question.

FIXME: since we write on one line in say, this often gets overidden by next say or ask

# File lib/canis/core/util/extras/bottomline.rb, line 1160
def explain_error( error )
  say_with_pause(@question.responses[error]) unless error.nil?
  if @question.responses[:ask_on_error] == :question
    say(@question)
  elsif @question.responses[:ask_on_error]
    say(@question.responses[:ask_on_error])
  end
end
get_response() click to toggle source
# File lib/canis/core/util/extras/bottomline.rb, line 1487
def get_response
  return @question.first_answer if @question.first_answer?
  # we always use character reader, so user's value does not matter

  #if @question.character.nil?
  #  if @question.echo == true #and @question.limit.nil?
  $log.debug "XXX: before RBGETS got default: #{@default} "
  ret, str = rb_getstr
  if ret == 0
    return @question.change_case(@question.remove_whitespace(str))                
  end
  if ret == -1
    raise Interrupt
  end
  return ""
end
hide(wait=nil) click to toggle source

bottomline user has to hide window if he called say().

Call this if you find the window persists after using some method from here
 usually say or ask.

 NOTE: after callign this you must call window.show. Otherwise, next time
 you call this, it will not hide.

@param [int, float] time to sleep before hiding window.

# File lib/canis/core/util/extras/bottomline.rb, line 1066
def hide wait=nil
  if @window
    $log.debug "XXX: HIDE BOTTOMLINE INSIDE"
    sleep(wait) if wait
    #if @window.visible?
    #@window.hide    # THIS HAS SUDDENLY STOPPED WORKING
    @window.destroy
    @window = nil
    #@window.wrefresh
    #Ncurses::Panel.update_panels
    #end
  end
end
Also aliased as: hide_bottomline
hide_bottomline(wait=nil)
Alias for: hide
list( items, mode = :rows, option = nil ) click to toggle source

Each member of the items Array is passed through ERb and thus can contain their own expansions. Color escape expansions do not contribute to the final field width.

# File lib/canis/core/util/extras/bottomline.rb, line 1675
def list( items, mode = :rows, option = nil )
  items = items.to_ary.map do |item|
    ERB.new(item, nil, "%").result(binding)
  end
  
  case mode
  when :inline
    option = " or " if option.nil?
    
    case items.size
    when 0
      ""
    when 1
      items.first
    when 2
      "#{items.first}#{option}#{items.last}"
    else
      items[0..-2].join(", ") + "#{option}#{items.last}"
    end
  when :columns_across, :columns_down
    max_length = actual_length(
      items.max { |a, b| actual_length(a) <=> actual_length(b) }
    )

    if option.nil?
      limit  = @wrap_at || 80
      option = (limit + 2) / (max_length + 2)
    end

    items     = items.map do |item|
      pad = max_length + (item.length - actual_length(item))
      "%-#{pad}s" % item
    end
    row_count = (items.size / option.to_f).ceil
    
    if mode == :columns_across
      rows = Array.new(row_count) { Array.new }
      items.each_with_index do |item, index|
        rows[index / option] << item
      end

      rows.map { |row| row.join("  ") + "\n" }.join
    else
      columns = Array.new(option) { Array.new }
      items.each_with_index do |item, index|
        columns[index / row_count] << item
      end
    
      list = ""
      columns.first.size.times do |index|
        list << columns.map { |column| column[index] }.
                        compact.join("  ") + "\n"
      end
      list
    end
  else
    items.map { |i| "#{i}\n" }.join
  end
end
print_help(help_text) click to toggle source
print_str(text, config={}) click to toggle source

Internal method for printing a string

rb_getstr() click to toggle source

actual input routine, gets each character from user, taking care of echo, limit, completion proc, and some control characters such as C-a, C-e, C-k Taken from io.rb, has some improvements to it. However, does not print the prompt any longer Completion proc is vim style, on pressing tab it cycles through options

# File lib/canis/core/util/extras/bottomline.rb, line 1192
def rb_getstr
  r = @message_row
  c = 0
  win = @window
  @limit = @question.limit
  @history = @question.history
  @history_list = History.new(@history) 
  maxlen = @limit || 100 # fixme


  raise "rb_getstr got no window. bottomline.rb" if win.nil?
  ins_mode = false
  oldstr = nil # for tab completion, origal word entered by user
  default = @default || ""
  $log.debug "XXX: RBGETS got default: #{@default} "
  if @default && @history
    if !@history.include?(default)
      @history_list.push default 
    end
  end

  len = @prompt_length

  # clear the area of len+maxlen
  color = $datacolor
  str = ""
  #str = default
  cpentries = nil
  #clear_line len+maxlen+1
  #print_str(prompt+str)
  print_str(str, :y => @prompt_length+0) if @default
  len = @prompt_length + str.length
  begin
    Ncurses.noecho();
    curpos = str.length
    prevchar = 0
    entries = nil
    while true
      ch=win.getchar()
      $log.debug " XXXX FFI rb_getstr got ch:#{ch}, str:#{str}. "
      case ch
      when 3 # -1 # C-c  # sometimes this causes an interrupt and crash
        return -1, nil
      when ?\C-g.getbyte(0)                              # ABORT, emacs style
        return -1, nil
      when 10, 13 # hits ENTER, complete entry and return
        @history_list.push str
        break
      when ?\C-h.getbyte(0), ?\C-?.getbyte(0), KEY_BSPACE, 263 # delete previous character/backspace
        # C-h is giving 263 i/o 8. 2011-09-19
        len -= 1 if len > @prompt_length
        curpos -= 1 if curpos > 0
        str.slice!(curpos)
        clear_line len+maxlen+1, @prompt_length
      when 330 # delete character on cursor
        str.slice!(curpos) #rescue next
        clear_line len+maxlen+1, @prompt_length
      when ?\M-h.getbyte(0) #                            HELP KEY
        help_text = @help_text || "No help provided..."
        print_help(help_text) 
        clear_line len+maxlen+1
        print_str @statement # UGH
        #return 7, nil
        #next
      when KEY_LEFT
        curpos -= 1 if curpos > 0
        len -= 1 if len > @prompt_length
        win.move r, c+len # since getchar is not going back on del and bs wmove to move FFIWINDOW
        win.wrefresh
        next
      when KEY_RIGHT
        if curpos < str.length
          curpos += 1 #if curpos < str.length
          len += 1 
          win.move r, c+len # since getchar is not going back on del and bs
          win.wrefresh
        end
        next
      when ?\C-a.getbyte(0)
        #olen = str.length
        clear_line len+maxlen+1, @prompt_length
        len -= curpos
        curpos = 0
        win.move r, c+len # since getchar is not going back on del and bs
      when ?\C-e.getbyte(0)
        olen = str.length
        len += (olen - curpos)
        curpos = olen
        clear_line len+maxlen+1, @prompt_length
        win.move r, c+len # since getchar is not going back on del and bs

      when ?\M-i.getbyte(0) 
        ins_mode = !ins_mode
        next
      when ?\C-k.getbyte(0) # delete forward
        @delete_buffer = str.slice!(curpos..-1) #rescue next
        clear_line len+maxlen+1, @prompt_length
      when ?\C-u.getbyte(0) # delete to the left of cursor till start of line
        @delete_buffer = str.slice!(0..curpos-1) #rescue next
        curpos = 0
        clear_line len+maxlen+1, @prompt_length
        len = @prompt_length
      when ?\C-y.getbyte(0) # paste what's in delete buffer
        if @delete_buffer
          olen = str.length
          str << @delete_buffer if @delete_buffer
          curpos = str.length
          len += str.length - olen
        end
      when KEY_TAB # TAB
        if !@completion_proc.nil?
          # place cursor at end of completion
          # after all completions, what user entered should come back so he can edit it
          if prevchar == 9
            if !entries.nil? and !entries.empty?
              olen = str.length
              str = entries.delete_at(0)
              str = str.to_s.dup
              #str = entries[@current_index].dup
              #@current_index += 1
              #@current_index = 0 if @current_index == entries.length
              curpos = str.length
              len += str.length - olen
              clear_line len+maxlen+1, @prompt_length
            else
              olen = str.length
              str = oldstr if oldstr
              curpos = str.length
              len += str.length - olen
              clear_line len+maxlen+1, @prompt_length
              prevchar = ch = nil # so it can start again completing
            end
          else
            #@current_index = 0
            tabc = @completion_proc unless tabc
            next unless tabc
            oldstr = str.dup
            olen = str.length
            entries = tabc.call(str).dup
            $log.debug "XXX tab [#{str}] got #{entries} "
            str = entries.delete_at(0) unless entries.nil? or entries.empty?
            #str = entries[@current_index].dup unless entries.nil? or entries.empty?
            #@current_index += 1
            #@current_index = 0 if @current_index == entries.length
            str = str.to_s.dup
            if str
              curpos = str.length
              len += str.length - olen
            else
              alert "NO MORE 2"
            end
          end
        else
          # there's another type of completion that bash does, which is irritating
          # compared to what vim does, it does partial completion
          if cpentries
            olen = str.length
            if cpentries.size == 1
              str = cpentries.first.dup
            elsif cpentries.size > 1
              str = shortest_match(cpentries).dup
            end
            curpos = str.length
            len += str.length - olen
          end
        end
      when ?\C-a.getbyte(0) .. ?\C-z.getbyte(0)
        # here' swhere i wish i could pass stuff back without closing
        # I'd like the user to be able to scroll list or do something based on
        # control or other keys
        if @key_handler_proc  # added  2011-11-3 7:38 PM
          @key_handler_proc.call(ch) 
          next
        else
          Ncurses.beep
        end
      when KEY_UP
        if @history && !@history.empty?
          olen = str.length
          str = if prevchar == KEY_UP
                   @history_list.previous
                 elsif prevchar == KEY_DOWN
                   @history_list.previous
                 else
                   @history_list.last
                 end
          str = str.dup
          curpos = str.length
          len += str.length - olen
          clear_line len+maxlen+1, @prompt_length
        else # try to pick up default, seems we don't get it 2011-10-14
          if @default
            olen = str.length
            str = @default
            str = str.dup
            curpos = str.length
            len += str.length - olen
            clear_line len+maxlen+1, @prompt_length
          end
        end
      when KEY_DOWN
        if @history && !@history.empty?
          olen = str.length
          str = if prevchar == KEY_UP
                   @history_list.next
                 elsif prevchar == KEY_DOWN
                   @history_list.next
                 else
                   @history_list.first
                 end
          str = str.dup
          curpos = str.length
          len += str.length - olen
          clear_line len+maxlen+1, @prompt_length
        end

      else
        if ch < 0 || ch > 255
          Ncurses.beep
          next
        end
        # if control char, beep
        if ch.chr =~ /[[:cntrl:]]/
          Ncurses.beep
          next
        end
        # we need to trap KEY_LEFT and RIGHT and what of UP for history ?
        if ins_mode
          str[curpos] = ch.chr
        else
          str.insert(curpos, ch.chr) # FIXME index out of range due to changeproc
        end
        len += 1
        curpos += 1
        break if str.length >= maxlen
      end
      case @question.echo
      when true
        begin
          cpentries = @change_proc.call(str) if @change_proc # added 2010-11-09 23:28
        rescue => exc
          $log.error "bottomline: change_proc EXC #{exc} " if $log.debug? 
          $log.error( exc) if exc
          $log.error(exc.backtrace.join("\n")) if exc
          Ncurses.error
        end
        print_str(str, :y => @prompt_length+0)
      when false
        # noop, no echoing what is typed
      else
        print_str(@question.echo * str.length, :y => @prompt_length+0)
      end
      win.move r, c+len # more for arrow keys, curpos may not be end
      win.wrefresh # 2011-10-10
      prevchar = ch
    end
          $log.debug "XXXW bottomline: after while loop"

    str = default if str == ""
  ensure
    Ncurses.noecho();
  end
  return 0, str
end
say(statement, config={}) click to toggle source

The basic output method for HighLine objects.

The statement parameter is processed as an ERb template, supporting embedded Ruby code. The template is evaluated with a binding inside the HighLine instance. NOTE: modified from original highline, does not care about space at end of question. Also, ansi color constants will not work. Be careful what ruby code you pass in.

NOTE: This uses a window, so it will persist in the last row. You must call hide_bottomline to remove the window. It is preferable to call say_with_pause from user programs

# File lib/canis/core/util/extras/bottomline.rb, line 1103
def say statement, config={}
  @window ||= _create_footer_window
  #@window.show #unless @window.visible?
  $log.debug "XXX: inside say win #{@window} !"
  case statement
  when Question

    if config.has_key? :color_pair
      $log.debug "INSIDE QUESTION 2 " if $log.debug? 
    else
      $log.debug "XXXX SAY using colorpair: #{statement.color_pair} " if $log.debug? 
      config[:color_pair] = statement.color_pair
    end
  else
    $log.debug "XXX INSDIE SAY #{statement.class}  " if $log.debug? 
  end
  statement =  statement.to_str
  template  = ERB.new(statement, nil, "%")
  statement = template.result(binding)

  @prompt_length = statement.length # required by ask since it prints after
  @statement = statement #
  clear_line
  print_str statement, config
end
say_with_pause(statement, config={}) click to toggle source

display some text at bottom and wait for a key before hiding window

# File lib/canis/core/util/extras/bottomline.rb, line 1131
def say_with_pause statement, config={}
  @window ||= _create_footer_window
  #@window.show #unless @window.visible? # 2011-10-14 23:52:52
  say statement, config
  @window.wrefresh
  Ncurses::Panel.update_panels
  ch=@window.getchar()
  hide_bottomline
  ## return char so we can use for asking for one character
  return ch
end
say_with_wait(statement, config={}) click to toggle source

since say does not leave the screen, it is not exactly recommended

as it will hide what's below. It's better to call pause, or this, which
will quickly go off. If the message is not important enough to ask for a pause,
the will flicker on screen, but not for too long.
# File lib/canis/core/util/extras/bottomline.rb, line 1146
def say_with_wait statement, config={}
  @window ||= _create_footer_window
  #@window.show #unless @window.visible? # 2011-10-14 23:52:59
  say statement, config
  @window.wrefresh
  Ncurses::Panel.update_panels
  sleep 0.5
  hide_bottomline
end
shortest_match(a) click to toggle source

compares entries in array and returns longest common starting string as happens in bash when pressing tab abc abd abe will return ab

# File lib/canis/core/util/extras/bottomline.rb, line 1460
def shortest_match a
 #return "" if a.nil? || a.empty? # should not be called in such situations
 raise "shortest_match should not be called with nil or empty array" if a.nil? || a.empty? # should not be called in such situations as caller program will err.

  l = a.inject do |memo,word|
    str = ""
    0.upto(memo.size) do |i|
      if memo[0..i] == word[0..i]
        str = memo[0..i]
      else
        break
      end
    end
    str
  end
end