howl.ui.Editor
local buffer, lines
editor = Editor Buffer {}
cursor = editor.cursor
selection = editor.selection
window = Gtk.OffscreenWindow!
window\add editor\to_gobject!
window\show_all!
before_each ->
buffer = Buffer {}
buffer.config.indent = 2
lines = buffer.lines
editor.buffer = buffer
.current_line is a shortcut for the current buffer line
buffer.text = 'hƏllo\nworld'
cursor.pos = 2
assert.equal editor.current_line, buffer.lines[1]
.current_context returns the buffer context at the current position
buffer.text = 'hƏllo\nwʘrld'
cursor.pos = 2
context = editor.current_context
assert.equal 'Context', typeof context
assert.equal 2, context.pos
.newline() adds a newline at the current position
buffer.text = 'hƏllo'
cursor.pos = 3
editor\newline!
assert.equal buffer.text, 'hƏ\nllo'
insert(text) inserts the text at the cursor, and moves cursor after text
buffer.text = 'hƏllo'
cursor.pos = 6
editor\insert ' world'
assert.equal 'hƏllo world', buffer.text
assert.equal 12, cursor.pos, 12
paste pastes the contents of the clipboard at the current position
buffer.text = 'hƏllo'
editor.selection\set 1, 2
editor.selection\copy!
editor\paste!
assert.equal buffer.text, 'hhƏllo'
delete_line deletes the current line
buffer.text = 'hƏllo\nworld!'
cursor.pos = 3
editor\delete_line!
assert.equal buffer.text, 'world!'
copy_line copies the current line
buffer.text = 'hƏllo\n'
cursor.pos = 3
editor\copy_line!
cursor.pos = 1
editor\paste!
assert.equal buffer.text, 'hƏllo\nhƏllo\n'
join_lines joins the current line with the one after
buffer.text = 'hƏllo\n world!'
cursor.pos = 1
editor\join_lines!
assert.equal 'hƏllo world!', buffer.text
assert.equal 6, cursor.pos
forward_to_match(string) moves the cursor to next occurence of <string>, if found in the line
buffer.text = 'hƏll\to\n world!'
cursor.pos = 1
editor\forward_to_match 'l'
assert.equal 3, cursor.pos
editor\forward_to_match 'l'
assert.equal 4, cursor.pos
editor\forward_to_match 'o'
assert.equal 6, cursor.pos
editor\forward_to_match 'w'
assert.equal 6, cursor.pos
backward_to_match(string) moves the cursor back to previous occurence of <string>, if found in the line
buffer.text = 'h\tƏllo\n world!'
cursor.pos = 6
editor\backward_to_match 'l'
assert.equal 5, cursor.pos
editor\backward_to_match 'l'
assert.equal 4, cursor.pos
editor\backward_to_match 'h'
assert.equal 1, cursor.pos
editor\backward_to_match 'w'
assert.equal 1, cursor.pos
.active_lines
(with no selection active)
is a table containing .current_line
buffer.text = 'hƏllo\nworld'
lines = editor.active_lines
assert.equals 1, #lines
assert.equals editor.current_line, lines[1]
(with a selection active)
is a table of lines involved in the selection
buffer.text = 'hƏllo\nworld'
selection\set 3, 8
active_lines = editor.active_lines
assert.equals 2, #active_lines
assert.equals lines[1], active_lines[1]
assert.equals lines[2], active_lines[2]
.active_chunk
is a chunk
assert.equals 'Chunk', typeof editor.active_chunk
(with no selection active)
is a chunk encompassing the entire buffer text
buffer.text = 'hƏllo\nworld'
assert.equals 'hƏllo\nworld', editor.active_chunk.text
(with a selection active)
is a chunk containing the current the selection
buffer.text = 'hƏllo\nworld'
selection\set 3, 8
assert.equals 'llo\nw', editor.active_chunk.text
indent()
(when mode does not provide a indent method)
does nothing
text = buffer.text
editor[method] editor
assert.equal text, buffer.text
(when mode provides a indent method)
calls that passing itself a parameter
buffer.mode = [method]: spy.new -> nil
editor[method] editor
assert.spy(buffer.mode[method]).was_called_with buffer.mode, editor
(when mode does not provide a comment method)
does nothing
text = buffer.text
editor[method] editor
assert.equal text, buffer.text
(when mode provides a comment method)
calls that passing itself a parameter
buffer.mode = [method]: spy.new -> nil
editor[method] editor
assert.spy(buffer.mode[method]).was_called_with buffer.mode, editor
(when mode does not provide a uncomment method)
does nothing
text = buffer.text
editor[method] editor
assert.equal text, buffer.text
(when mode provides a uncomment method)
calls that passing itself a parameter
buffer.mode = [method]: spy.new -> nil
editor[method] editor
assert.spy(buffer.mode[method]).was_called_with buffer.mode, editor
(when mode does not provide a toggle_comment method)
does nothing
text = buffer.text
editor[method] editor
assert.equal text, buffer.text
(when mode provides a toggle_comment method)
calls that passing itself a parameter
buffer.mode = [method]: spy.new -> nil
editor[method] editor
assert.spy(buffer.mode[method]).was_called_with buffer.mode, editor
with_position_restored(f)
before_each ->
buffer.text = ' yowser!\n yikes!'
cursor.line = 2
cursor.column = 4
calls <f> passing itself a parameter
f = spy.new -> nil
editor\with_position_restored f
assert.spy(f).was_called_with editor
restores the cursor position afterwards
editor\with_position_restored -> cursor.pos = 2
assert.equals 2, cursor.line
assert.equals 4, cursor.column
adjusts the position should the indentation have changed
editor\with_position_restored ->
lines[1].indentation = 0
lines[2].indentation = 0
assert.equals 2, cursor.line
assert.equals 2, cursor.column
editor\with_position_restored ->
lines[1].indentation = 3
lines[2].indentation = 3
assert.equals 2, cursor.line
assert.equals 5, cursor.column
(when <f> raises an error)
propagates the error
assert.raises 'ARGH!', -> editor\with_position_restored -> error 'ARGH!'
the position is still restored
cursor.pos = 4
pcall editor.with_position_restored, editor, ->
cursor.pos = 2
error 'ARGH!'
assert.equals 4, cursor.pos
delete_to_end_of_line(no_copy)
cuts text from cursor up to end of line
buffer.text = 'hƏllo world!\nnext'
cursor.pos = 6
editor\delete_to_end_of_line!
assert.equal buffer.text, 'hƏllo\nnext'
editor\paste!
assert.equal 'hƏllo world!\nnext', buffer.text
deletes without copying if no_copy is specified
buffer.text = 'hƏllo world!'
cursor.pos = 3
editor\delete_to_end_of_line true
assert.equal buffer.text, 'hƏ'
editor\paste!
assert.not_equal 'hƏllo world!', buffer.text
(buffer switching)
remembers the position for different buffers
buffer.text = 'hƏllo\n world!'
cursor.pos = 8
buffer2 = Buffer {}
buffer2.text = 'a whole different whale'
editor.buffer = buffer2
cursor.pos = 15
editor.buffer = buffer
assert.equal 8, cursor.pos
editor.buffer = buffer2
assert.equal 15, cursor.pos
(indentation, tabs, spaces and backspace)
defines a "tab_width" config variable, defaulting to 8
assert.equal config.tab_width, 4
defines a "use_tabs" config variable, defaulting to false
assert.equal config.use_tabs, false
defines a "indent" config variable, defaulting to 2
assert.equal config.indent, 2
defines a "tab_indents" config variable, defaulting to true
assert.equal config.tab_indents, true
defines a "backspace_unindents" config variable, defaulting to true
assert.equal config.backspace_unindents, true
smart_tab()
inserts a tab character if use_tabs is true
config.use_tabs = true
buffer.text = 'hƏllo'
cursor.pos = 2
editor\smart_tab!
assert.equal buffer.text, 'h\tƏllo'
inserts spaces to move to the next tab if use_tabs is false
config.use_tabs = false
buffer.text = 'hƏllo'
cursor.pos = 1
editor\smart_tab!
assert.equal string.rep(' ', config.indent) .. 'hƏllo', buffer.text
inserts a tab to move to the next tab stop if use_tabs is true
config.use_tabs = true
config.tab_width = config.indent
buffer.text = 'hƏllo'
cursor.pos = 1
editor\smart_tab!
assert.equal '\thƏllo', buffer.text
indents the current line if in whitespace and tab_indents is true
config.use_tabs = false
config.tab_indents = true
indent = string.rep ' ', config.indent
buffer.text = indent .. 'hƏllo'
cursor.pos = 2
editor\smart_tab!
assert.equal buffer.text, string.rep(indent, 2) .. 'hƏllo'
.delete_back()
deletes back by one character
buffer.text = 'hƏllo'
cursor.pos = 2
editor\delete_back!
assert.equal buffer.text, 'Əllo'
unindents if in whitespace and backspace_unindents is true
config.indent = 2
buffer.text = ' hƏllo'
cursor.pos = 3
config.backspace_unindents = true
editor\delete_back!
assert.equal buffer.text, 'hƏllo'
deletes back if in whitespace and backspace_unindents is false
config.indent = 2
buffer.text = ' hƏllo'
cursor.pos = 3
config.backspace_unindents = false
editor\delete_back!
assert.equal buffer.text, ' hƏllo'
.shift_right()
right-shifts the lines included in a selection if any
config.indent = 2
buffer.text = 'hƏllo\nselected\nworld!'
selection\set 2, 10
editor\shift_right!
assert.equal buffer.text, ' hƏllo\n selected\nworld!'
right-shifts the current line when nothing is selected, remembering column
config.indent = 2
buffer.text = 'hƏllo\nworld!'
cursor.pos = 3
editor\shift_right!
assert.equal buffer.text, ' hƏllo\nworld!'
assert.equal cursor.pos, 5
.shift_left()
left-shifts the lines included in a selection if any
config.indent = 2
buffer.text = ' hƏllo\n selected\nworld!'
selection\set 4, 12
editor\shift_left!
assert.equal buffer.text, 'hƏllo\nselected\nworld!'
left-shifts the current line when nothing is selected, remembering column
config.indent = 2
buffer.text = ' hƏllo\nworld!'
cursor.pos = 4
editor\shift_left!
assert.equal buffer.text, ' hƏllo\nworld!'
assert.equal cursor.pos, 2
(events)
on char added
emits a character-added event with the passed arguments merged with the editor reference
handler = spy.new -> true
signal.connect 'character-added', handler
args = key_name: 'a'
editor\_on_char_added args
signal.disconnect 'character-added', handler
args.editor = editor
assert.spy(handler).was_called_with args
invokes mode.on_char_added if present, passing (arguments, editor)
buffer.mode = on_char_added: spy.new -> nil
args = key_name: 'a', :editor
editor\_on_char_added args
assert.spy(buffer.mode.on_char_added).was_called_with buffer.mode, args, editor
(resource management)
editors are collected as they should
e = Editor Buffer {}
editors = setmetatable {}, __mode: 'v'
append editors, e
e = nil
collectgarbage!
assert.is_nil editors[1]