Class: SearchUI::Search

Inherits:
Object
  • Object
show all
Extended by:
Term::ANSIColor
Includes:
Term::ANSIColor
Defined in:
lib/search_ui/search.rb

Overview

SearchUI::Search manages interactive console-based searching through an array of objects

This class provides an interactive search interface that allows users to filter and select objects from a collection based on text input patterns. It handles terminal input processing, display updates, and user navigation through the search results.

Examples:

SearchUI::Search.new(
  match: -> pattern { items.select { |item| item.name.include?(pattern) } },
  query: -> answer, matches, selector { matches[selector]&.name || 'No matches' },
  found: -> answer, matches, selector { matches[selector] }
).start

Defined Under Namespace

Classes: State

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(match:, query:, found:, output: STDOUT, prompt: 'Search? %s', state: nil) ⇒ Search

Initializes a new SearchUI::Search instance to manage an interactive console search interface. This method sets up the filtering logic, display formatting, and selection criteria required to drive the search loop, as well as the output stream and prompt.

Parameters:

  • match (Proc)

    a procedure that accepts a search pattern string and returns an array of matching objects.

  • query (Proc)

    a procedure that accepts the current answer, the list of matches, and the current selector index to generate the string representation of the results list.

  • found (Proc)

    a procedure that accepts the current answer, the list of matches, and the current selector index to determine the final selected object.

  • output (IO) (defaults to: STDOUT)

    the output stream where the interface will be rendered. Defaults to STDOUT.

  • prompt (String) (defaults to: 'Search? %s')

    a format string used as the user prompt. Defaults to 'Search? %s'.

  • state (SearchUI::Search::State, nil) (defaults to: nil)

    an initial state object containing the starting answer and selector position. Defaults to a new State with an empty answer and selector set to 0.



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/search_ui/search.rb', line 46

def initialize(
  match:,
  query:,
  found:,
  output: STDOUT,
  prompt: 'Search? %s',
  state:  nil
)
  @match        = match
  @query        = query
  @found        = found
  @output       = output
  @prompt       = prompt
  @state        = state || State.new('', 0)
end

Instance Attribute Details

#stateSearchUI::Search::State (readonly)

Reads the current internal state of the search interface, containing the current input answer and the active selection index.

Returns:



67
68
69
# File 'lib/search_ui/search.rb', line 67

def state
  @state
end

Instance Method Details

#getcBoolean? (private)

Reads and processes a single character input from STDIN, handling special key sequences and updating the search state accordingly.

This method manages raw terminal input to capture user keystrokes, interpreting control characters and ANSI escape sequences:

  • Up/Down arrows: Navigate the result selector.
  • Enter (\r): Confirms the current selection.
  • Ctrl-C (\x03): Cancels the search operation.
  • Ctrl-K (\v): Clears the current search answer.
  • Backspace (\x7f): Deletes the last character of the answer.
  • Note: Any modification to the search answer resets the selector to 0.

It temporarily disables terminal echo and sets raw mode to ensure proper input handling.

Returns:

  • (Boolean, nil)
    • true: The Enter key was pressed to confirm selection.
    • false: Ctrl-C was pressed to cancel the operation.
    • nil: Input updated the search state or was ignored.


120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/search_ui/search.rb', line 120

def getc
  print hide_cursor
  system 'stty raw -echo'
  c = STDIN.getc
  system 'stty cooked echo'
  case c
  when "\x03"
    false
  when "\e"
    STDIN.getc == ?[ or return nil
    STDIN.getc =~ /\A([AB])\z/ or return nil
    if $1 == ?A
      @state.selector -= 1
    else
      @state.selector += 1
    end
    @state.selector = [ @state.selector, 0 ].max
    nil
  when ?\r
    true
  when "\x7f"
    @state.selector = 0
    @state.answer.chop!
    nil
  when ?\v
    @state.selector = 0
    @state.answer.clear
    nil
  when /\A[\x00-\x1f]\z/
    nil
  else
    @state.selector = 0
    @state.answer << c
    nil
  end
ensure
  print show_cursor
end

#startObject?

Starts the interactive search interface and handles user input until a selection is made or the process is cancelled.

Returns:

  • (Object, nil)

    returns the selected result object when a selection is made, or nil if the process is cancelled



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/search_ui/search.rb', line 74

def start
  @output.print reset
  @matches = @match.(@state.answer)
  @state.selector = @state.selector.clamp(0, [ @matches.size - 1, 0 ].max)
  result = @query.(@state.answer, @matches, @state.selector)
  loop do
    @output.print clear_screen
    @output.print move_home { @prompt % @state.answer + ?\n + result }
    case getc
    when true
      @output.print clear_screen, move_home, reset
      if result = @found.(@state.answer, @matches, @state.selector)
        return result
      else
        return nil
      end
    when false
      return nil
    end
    @matches = @match.(@state.answer)
    @state.selector = @state.selector.clamp(0, [ @matches.size - 1, 0 ].max)
    result = @query.(@state.answer, @matches, @state.selector)
  end
end