Class: Chamomile::List

Inherits:
Object
  • Object
show all
Defined in:
lib/chamomile/components/list.rb,
lib/chamomile/components/list/key_map.rb

Overview

Batteries-included item browser composing Paginator, TextInput, Spinner, Help.

Constant Summary collapse

FILTER_UNFILTERED =
:unfiltered
FILTER_FILTERING =
:filtering
FILTER_APPLIED =
:applied
DEFAULT_KEY_MAP =
KeyBinding.normalize({
  cursor_up: [[:up, []], ["k", []]],
  cursor_down: [[:down, []], ["j", []]],
  next_page: [[:page_down, []], ["n", [:ctrl]]],
  prev_page: [[:page_up, []], ["p", [:ctrl]]],
  goto_start: [["g", []], [:home, []]],
  goto_end: [["G", [:shift]], [:end_key, []]],
  filter: [["/", []]],
  clear_filter: [[:escape, []]],
  accept_filter: [[:enter, []]],
  show_full_help: [["?", []]],
})

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(items:, width: 80, height: 24, delegate: nil, key_map: DEFAULT_KEY_MAP) ⇒ List

Returns a new instance of List.



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/chamomile/components/list.rb', line 35

def initialize(items:, width: 80, height: 24, delegate: nil, key_map: DEFAULT_KEY_MAP)
  @id = self.class.next_id
  @all_items = items.dup
  @filtered_items = @all_items.dup
  @width = width
  @height = height
  @delegate = delegate
  @key_map = key_map
  @title = ""
  @cursor = 0
  @show_title = true
  @show_filter = true
  @show_status_bar = true
  @show_pagination = true
  @show_help = true
  @filtering_enabled = true
  @filter_state = FILTER_UNFILTERED
  @infinite_scroll = false
  @spinner_visible = false
  @status_message = nil

  @paginator = Paginator.new(per_page: items_per_page)
  @filter_input = TextInput.new(prompt: "/ ")
  @spinner = Spinner.new
  @help = Help.new(width: width)
  @help_bindings = [
    { key: "/", desc: "filter" },
    { key: "esc", desc: "clear" },
    { key: "q", desc: "quit" },
    { key: "?", desc: "more" },
  ]

  recalculate
end

Instance Attribute Details

#cursorObject (readonly)

Returns the value of attribute cursor.



29
30
31
# File 'lib/chamomile/components/list.rb', line 29

def cursor
  @cursor
end

#delegateObject

Returns the value of attribute delegate.



30
31
32
# File 'lib/chamomile/components/list.rb', line 30

def delegate
  @delegate
end

#filter_stateObject (readonly)

Returns the value of attribute filter_state.



29
30
31
# File 'lib/chamomile/components/list.rb', line 29

def filter_state
  @filter_state
end

#filtering_enabledObject

Returns the value of attribute filtering_enabled.



30
31
32
# File 'lib/chamomile/components/list.rb', line 30

def filtering_enabled
  @filtering_enabled
end

#heightObject

Returns the value of attribute height.



30
31
32
# File 'lib/chamomile/components/list.rb', line 30

def height
  @height
end

#idObject (readonly)

Returns the value of attribute id.



29
30
31
# File 'lib/chamomile/components/list.rb', line 29

def id
  @id
end

#infinite_scrollObject

Returns the value of attribute infinite_scroll.



30
31
32
# File 'lib/chamomile/components/list.rb', line 30

def infinite_scroll
  @infinite_scroll
end

#key_mapObject

Returns the value of attribute key_map.



30
31
32
# File 'lib/chamomile/components/list.rb', line 30

def key_map
  @key_map
end

#show_filterObject

Returns the value of attribute show_filter.



30
31
32
# File 'lib/chamomile/components/list.rb', line 30

def show_filter
  @show_filter
end

#show_helpObject

Returns the value of attribute show_help.



30
31
32
# File 'lib/chamomile/components/list.rb', line 30

def show_help
  @show_help
end

#show_paginationObject

Returns the value of attribute show_pagination.



30
31
32
# File 'lib/chamomile/components/list.rb', line 30

def show_pagination
  @show_pagination
end

#show_status_barObject

Returns the value of attribute show_status_bar.



30
31
32
# File 'lib/chamomile/components/list.rb', line 30

def show_status_bar
  @show_status_bar
end

#show_titleObject

Returns the value of attribute show_title.



30
31
32
# File 'lib/chamomile/components/list.rb', line 30

def show_title
  @show_title
end

#status_messageObject (readonly)

Returns the value of attribute status_message.



29
30
31
# File 'lib/chamomile/components/list.rb', line 29

def status_message
  @status_message
end

#titleObject

Returns the value of attribute title.



30
31
32
# File 'lib/chamomile/components/list.rb', line 30

def title
  @title
end

#widthObject

Returns the value of attribute width.



30
31
32
# File 'lib/chamomile/components/list.rb', line 30

def width
  @width
end

Class Method Details

.next_idObject



17
18
19
20
21
22
23
24
25
26
27
# File 'lib/chamomile/components/list.rb', line 17

def self.next_id
  @id_mutex.synchronize do
    if Process.pid != @id_pid
      @id_pid = Process.pid
      @next_id = 0
      @id_mutex = Mutex.new
    end
    @next_id += 1
    "#{@id_pid}-lst-#{@next_id}"
  end
end

Instance Method Details

#cursor_downObject



130
131
132
133
134
135
136
137
# File 'lib/chamomile/components/list.rb', line 130

def cursor_down
  @cursor = if @infinite_scroll && @cursor >= filtered_count - 1 && filtered_count.positive?
              0
            else
              (@cursor + 1).clamp(0, [filtered_count - 1, 0].max)
            end
  update_paginator_page
end

#cursor_upObject



121
122
123
124
125
126
127
128
# File 'lib/chamomile/components/list.rb', line 121

def cursor_up
  @cursor = if @infinite_scroll && @cursor.zero? && filtered_count.positive?
              filtered_count - 1
            else
              (@cursor - 1).clamp(0, [filtered_count - 1, 0].max)
            end
  update_paginator_page
end

#filter_valueObject



161
162
163
# File 'lib/chamomile/components/list.rb', line 161

def filter_value
  @filter_input.value
end

#global_indexObject



114
115
116
117
118
119
# File 'lib/chamomile/components/list.rb', line 114

def global_index
  return nil if @filtered_items.empty?

  item = selected_item
  @all_items.index(item)
end

#goto_endObject



144
145
146
147
# File 'lib/chamomile/components/list.rb', line 144

def goto_end
  @cursor = [filtered_count - 1, 0].max
  update_paginator_page
end

#goto_startObject



139
140
141
142
# File 'lib/chamomile/components/list.rb', line 139

def goto_start
  @cursor = 0
  update_paginator_page
end

#handle(msg) ⇒ Object Also known as: update



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/chamomile/components/list.rb', line 215

def handle(msg)
  case msg
  when Chamomile::KeyEvent
    handle_key(msg)
  when SpinnerTickMsg
    @spinner.update(msg)
    nil
  when FilterMatchesMsg
    @filtered_items = msg.matches
    @cursor = @cursor.clamp(0, [filtered_count - 1, 0].max)
    recalculate
    nil
  when ListStatusTimeoutMsg
    @status_message = nil if msg.id == @id
    nil
  end
end

#indexObject



110
111
112
# File 'lib/chamomile/components/list.rb', line 110

def index
  @cursor
end

#insert_item(index, item) ⇒ Object



88
89
90
91
92
93
# File 'lib/chamomile/components/list.rb', line 88

def insert_item(index, item)
  index = index.clamp(0, @all_items.length)
  @all_items.insert(index, item)
  apply_filter
  recalculate
end

#itemsObject



70
71
72
# File 'lib/chamomile/components/list.rb', line 70

def items
  @filtered_items
end

#items=(new_items) ⇒ Object



74
75
76
77
78
# File 'lib/chamomile/components/list.rb', line 74

def items=(new_items)
  @all_items = new_items.dup
  apply_filter
  recalculate
end

#new_status_message(text, lifetime: 1.0) ⇒ Object



192
193
194
195
196
197
198
199
# File 'lib/chamomile/components/list.rb', line 192

def new_status_message(text, lifetime: 1.0)
  @status_message = text
  captured_id = @id
  -> {
    sleep(lifetime)
    ListStatusTimeoutMsg.new(id: captured_id)
  }
end

#next_pageObject



155
156
157
158
159
# File 'lib/chamomile/components/list.rb', line 155

def next_page
  @paginator.next_page
  @cursor = @paginator.page * items_per_page
  @cursor = @cursor.clamp(0, [filtered_count - 1, 0].max)
end

#prev_pageObject



149
150
151
152
153
# File 'lib/chamomile/components/list.rb', line 149

def prev_page
  @paginator.prev_page
  @cursor = @paginator.page * items_per_page
  @cursor = @cursor.clamp(0, [filtered_count - 1, 0].max)
end

#remove_item(index) ⇒ Object



95
96
97
98
99
100
101
102
# File 'lib/chamomile/components/list.rb', line 95

def remove_item(index)
  return nil if index.negative? || index >= @all_items.length

  removed = @all_items.delete_at(index)
  apply_filter
  recalculate
  removed
end

#reset_filterObject



165
166
167
168
169
170
171
172
# File 'lib/chamomile/components/list.rb', line 165

def reset_filter
  @filter_input.value = ""
  @filter_state = FILTER_UNFILTERED
  @filter_input.blur
  @filtered_items = @all_items.dup
  @cursor = 0
  recalculate
end

#selected_itemObject



104
105
106
107
108
# File 'lib/chamomile/components/list.rb', line 104

def selected_item
  return nil if @filtered_items.empty?

  @filtered_items[@cursor]
end

#set_filter_text(text) ⇒ Object



174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/chamomile/components/list.rb', line 174

def set_filter_text(text)
  if text.nil? || text.empty?
    reset_filter
    return
  end

  @filter_input.value = text
  @filter_state = FILTER_APPLIED
  @filter_input.blur
  apply_filter
  recalculate
end

#set_item(index, item) ⇒ Object



80
81
82
83
84
85
86
# File 'lib/chamomile/components/list.rb', line 80

def set_item(index, item)
  return if index.negative? || index >= @all_items.length

  @all_items[index] = item
  apply_filter
  recalculate
end

#spinner_visible?Boolean

Returns:

  • (Boolean)


211
212
213
# File 'lib/chamomile/components/list.rb', line 211

def spinner_visible?
  @spinner_visible
end

#start_spinnerObject



201
202
203
204
# File 'lib/chamomile/components/list.rb', line 201

def start_spinner
  @spinner_visible = true
  @spinner.tick_cmd
end

#stop_spinnerObject



206
207
208
209
# File 'lib/chamomile/components/list.rb', line 206

def stop_spinner
  @spinner_visible = false
  nil
end

#viewObject



235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/chamomile/components/list.rb', line 235

def view
  sections = []

  sections << render_title if @show_title && !@title.empty?
  sections << render_filter if @show_filter && @filter_state != FILTER_UNFILTERED
  sections << render_status_bar if @show_status_bar
  sections << render_items
  sections << @paginator.view if @show_pagination && @paginator.total_pages > 1
  sections << render_help if @show_help

  sections.compact.reject(&:empty?).join("\n")
end

#visible_itemsObject



187
188
189
190
# File 'lib/chamomile/components/list.rb', line 187

def visible_items
  start, length = @paginator.slice_bounds(filtered_count)
  @filtered_items[start, length] || []
end