howl.ui.List
local list, buf, items
before_each ->
buf = ActionBuffer!
list = List -> items
list\insert buf
list_size = ->
#[l for l in *buf.lines when not l.is_blank]
shows single column items, each on one line
items = {'one', 'two', 'three'}
list\update!
assert.equal 'one \ntwo \nthree\n', buf.text
allows items to be Chunks
source_buf = Buffer!
source_buf.text = 'source'
chunk = source_buf\chunk 1, 6
items = { chunk }
list\update!
assert.equal 'source\n', buf.text
.rows_shown is the number of rows drawn for the list
items = {'one', 'two'}
list\update!
assert.equal 'one\ntwo\n', buf.text
assert.equal 2, list.rows_shown
shows multi column items each on one line, in separate columns
items = {
{'first', 'item one'},
{'second', 'item two'}
}
list.columns = { {}, {} }
list\update!
assert.equal [[
first item one
second item two
]], buf.text
shows "(no items)" for an empty list
items = {}
list\update!
assert.equal '(no items)\n', buf.text
assert.equal 1, list.rows_shown
items = { {'first', 'item one'} }
list.columns = { {header: 'Header 1'}, {header: 'Header 2'} }
list\update!
assert.equal [[
Header 1 Header 2
first item one
]], buf.text
assert.equal 2, list.rows_shown
all properties can be changed after initial assignment
items = { 'one', 'two' }
list\update!
assert.equal buf.text, 'one\ntwo\n'
items = { 'three', 'four' }
list\update!
assert.equal buf.text, 'three\nfour \n'
items = { { 'three', 'four' } }
list\update!
list.columns = { { header: 'Header 1' }, { header: 'Header 2' } }
list\update!
assert.equal [[
Header 1 Header 2
three four ]] .. '\n', buf.text
(matcher integration)
shows matching items only, when update(match_text) called
list.matcher = Matcher {'one', 'twö', 'three'}
list\update 'o'
assert.equal 'one\n', buf.text
highlights matching parts of text with list_highlight
list.matcher = Matcher {'one', 'twö', 'three'}
list\update 'ne'
assert.equal 'one\n', buf.text
assert.not_includes highlight.at_pos(buf, 1), 'list_highlight'
assert.includes highlight.at_pos(buf, 2), 'list_highlight'
assert.includes highlight.at_pos(buf, 3), 'list_highlight'
handles higlighting of multibyte chars
list.matcher = Matcher {'åne', 'twö'}
list\update 'ån'
assert.equal 'åne\n', buf.text
assert.includes highlight.at_pos(buf, 1), 'list_highlight'
assert.includes highlight.at_pos(buf, 2), 'list_highlight'
assert.not_includes highlight.at_pos(buf, 3), 'list_highlight'
(when .max_rows is set)
shows only up to max_rows rows
items = {'one', 'two', 'three'}
list.max_rows = 2
list\update!
assert.equal 2, list_size!
assert.match buf.text, 'one'
assert.is_not.match buf.text, 'two'
assert.equal 2, list.rows_shown
assert.equal 1, list.page_size
list.max_rows = math.huge
list\update!
assert.equal 'one \ntwo \nthree\n', buf.text
assert.equal 3, list.rows_shown
assert.equal 3, list.page_size
errors if height is insufficient to show at least one item
items = {'one', 'two' }
list.max_rows = 0
assert.raises 'insufficient height', -> list\update!
items = {'one', 'two', 'three'}
list.columns = { {header:'Takes up one line' } }
list.max_rows = 3
list\update!
assert.match buf.text, 'one'
assert.is_not.match buf.text, 'two'
assert.equal 3, list.rows_shown
assert.equal 1, list.page_size
displays info about the currently shown items
items = {'one', 'two', 'three'}
list.max_rows = 2
list\update!
assert.match buf.text, 'showing 1 to 1 out of 3'
(when .min_rows is set)
is ignored when the list is bigger than the value
items = {'one', 'two', 'three'}
list.min_rows = 1
list\update!
assert.equals 3, list_size!
adds lines to ensure the given value
items = {}
list.min_rows = 3
list\update!
assert.equals '~\n~\n(no items)\n', buf.text
assert.equal 3, list.rows_shown
items = {'one'}
list.min_rows = 5
list\update!
assert.equals 'one\n~\n~\n~\n~\n', buf.text
assert.equal 5, list.rows_shown
sets .filler_text for each filler line if specified
items = {'one'}
list.opts.filler_text = '##'
list.min_rows = 2
list\update!
assert.equals 'one\n##\n', buf.text
(when items are not strings)
automatically converts items to strings using tostring before displaying
items = { 1, 2 }
list\update!
assert.equal '1\n2\n', buf.text
the selection is still the raw item
items = { 57, 59 }
list\update!
assert.equal list.selection, 57
styling
items = { { 'one' } }
list.columns = { { header: 'Header 1' } }
list\update!
header_style = style.at_pos(buf, 1)
assert.equal 'list_header', header_style
columns are styled using the styles specified in .columns[i].style
items = { { 'first', 'second' } }
list.columns = { { style: 'keyword'}, { style: 'identifier' } }
list\update!
assert.equal 'keyword', style.at_pos(buf, 1)
assert.equal 'identifier', style.at_pos(buf, 7)
(selection)
before_each ->
items = { 'one', 'two', 'three' }
list\update!
selects the first item by default
assert.equal 'one', list.selection
.selection is nil for an empty list
items = {}
list\update!
assert.is_nil list.selection
highlights the selected item with list_selection
assert.same { 'list_selection' }, highlight.at_pos(buf, 1)
assert.same {}, highlight.at_pos(buf, buf.lines[2].start_pos)
list.columns = { { header: 'Head' } }
list\update!
assert.same {}, highlight.at_pos(buf, 1)
assert.same { 'list_selection' }, highlight.at_pos(buf, buf.lines[2].start_pos)
assert.equal 5, #buf.lines[1]
assert.equal 5, #buf.lines[3]
.selection = <item>
causes <item> to be selected
list.selection = 'two'
assert.equal list.selection, 'two'
raises an error if <item> can not be found
assert.raises 'not found', -> list.selection = 'five'
highlights the new selection and clears any old highlight
list.selection = 'one'
assert.same highlight.at_pos(buf, 1), { 'list_selection' }
list.selection = 'two'
assert.same { 'list_selection' }, highlight.at_pos(buf, buf.lines[2].start_pos)
assert.same {}, highlight.at_pos(buf, 1)
list.max_rows = 2
list\update!
list.selection = 'three'
assert.match buf.text, 'three'
select_next()
selects the next item
list\select_next!
assert.equal 'two', list.selection
selects the first item if at the end of the list
list.selection = 'three'
list\select_next!
assert.equal 'one', list.selection
list.max_rows = 2
list\update!
list\select_next!
assert.equal 'two', list.selection
assert.match buf.text, 'two'
select_prev()
selects the previous item
list.selection = 'three'
list\select_prev!
assert.equal 'two', list.selection
selects the last item if at the start of the list
list\select_prev!
assert.equal list.selection, 'three'
list.max_rows = 2
list\update!
list.selection = 'three'
list\select_prev!
assert.equal 'two', list.selection
assert.match buf.text, 'two'
next_page()
scrolls to the next page
items = {'one', 'two', 'three'}
list.max_rows = 2
list\update!
list\next_page!
assert.equal 2, list.offset
scrolls to the first page if at the end of the list
items = {'one', 'two', 'three'}
list.max_rows = 2
list\update!
list.selection = 'three'
list\next_page!
assert.equal 1, list.offset
prev_page()
scrolls to the previous page
items = {'one', 'two', 'three'}
list.max_rows = 2
list\update!
list.selection = 'three'
list\prev_page!
assert.equal list.offset, 2
scrolls to the last page if at the start of the list
items = {'one', 'two', 'three'}
list.max_rows = 2
list\update!
list\prev_page!
assert.equal list.offset, 3
(when reverse is true)
before_each ->
items = { 'one', 'two', 'three' }
list\remove!
list = List (-> items), reverse: true
list\insert buf
list\update!
shows the items in reverse order
assert.equal 'three\ntwo \none \n', buf.text
selects the last item by default
assert.equal 'one', list.selection
on_refresh(listener)
causes <listener> to be called whenever the list is redrawn
listener = spy.new -> nil
list\on_refresh listener
list\update!
assert.spy(listener).was_called_with match.is_ref(list)
listener2 = spy.new -> nil
list\on_refresh listener2
list\draw!
assert.spy(listener2).was_called_with match.is_ref(list)
display management
is drawn correctly at the given insert position
items = {'item1', 'item2'}
buf.text = 'one\nend'
list\insert buf, 5
list\update 'it'
for i = 1, 2
assert.equal 'one\nitem1\nitem2\nend', buf.text
assert.equal 5, list.start_pos
for hl in *{5, 6, 11, 12}
assert.includes highlight.at_pos(buf, hl), 'list_highlight'
for hl in *{4, 7, 10, 13}
assert.not_includes highlight.at_pos(buf, hl), 'list_highlight'
for hl in *{5, 9}
assert.includes highlight.at_pos(buf, hl), 'list_selection'
for hl in *{4, 10}
assert.not_includes highlight.at_pos(buf, hl), 'list_selection'
moves along with other edits in the buffer
items = {'item'}
buf.text = '1\n7'
list\insert buf, 3
list\update!
assert.equal '1\nitem\n7', buf.text
buf\insert '23', 2
list\draw!
assert.equal '123\nitem\n7', buf.text
assert.equal 5, list.start_pos
buf\insert '56', 10
list\draw!
assert.equal '123\nitem\n567', buf.text
assert.equal 5, list.start_pos
buf\delete 1, 4
list\draw!
assert.equal 'item\n567', buf.text
assert.equal 1, list.start_pos
buf\insert 'X', 1
list\draw!
assert.equal 'Xitem\n567', buf.text
assert.equal 2, list.start_pos
item_at(pos)
returns the item at <pos> in the buffer
items = {'item'}
buf.text = 'X\nY'
list\insert buf, 3
list\update!
assert.equal 'X\nitem\nY', buf.text
assert.is_nil list\item_at(1)
assert.is_nil list\item_at(2)
assert.equal 'item', list\item_at(3)
assert.equal 'item', list\item_at(7)
assert.is_nil list\item_at(8)
buf\insert '\n', 2
assert.is_nil list\item_at(3)
assert.equal 'item', list\item_at(4)