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!
howl.app\pump_mainloop!
before_each ->
buffer = Buffer howl.mode.by_name 'default'
buffer.config.indent = 2
lines = buffer.lines
editor.buffer = buffer
selection\remove!
.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
delete_line() deletes the current line
buffer.text = 'hƏllo\nworld!'
cursor.pos = 3
editor\delete_line!
assert.equal 'world!', buffer.text
copy_line() copies the current line
buffer.text = 'hƏllo\n'
cursor.pos = 3
editor\copy_line!
cursor.pos = 1
editor\paste!
assert.equal 'hƏllo\nhƏllo\n', buffer.text
join_lines joins the current line with the one after
buffer.text = 'hƏllo\n world!\n'
cursor.pos = 1
editor\join_lines!
assert.equal 'hƏllo world!\n', 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 ->
editor[method] editor
assert.spy(buffer.mode[method]).was_called_with match.is_ref(buffer.mode), match.is_ref(editor)
if method == 'toggle_comment'
uses the buffer mode when the one to use is ambiguous
mode1 = comment_syntax: '//'
mode2 = comment_syntax: '#'
mode1_reg = name: 'toggle_comment_test1', create: -> mode1
mode2_reg = name: 'toggle_comment_test2', create: -> mode2
howl.mode.register mode1_reg
howl.mode.register mode2_reg
buffer.mode = howl.mode.by_name 'toggle_comment_test1'
buffer.text = 'ab\nc'
buffer._buffer.styling\apply 1, {
1, 'whitespace', 2,
2, { 1, 's1', 3 }, 'toggle_comment_test|s1',
}
selection\set 1, 5
editor[method] editor
assert.equal '// ab\n// c', buffer.text
selection\set 1, 11
editor[method] editor
assert.equal 'ab\nc', buffer.text
howl.mode.unregister 'toggle_comment_test1'
howl.mode.unregister 'toggle_comment_test2'
(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 ->
editor[method] editor
assert.spy(buffer.mode[method]).was_called_with match.is_ref(buffer.mode), match.is_ref(editor)
if method == 'toggle_comment'
uses the buffer mode when the one to use is ambiguous
mode1 = comment_syntax: '//'
mode2 = comment_syntax: '#'
mode1_reg = name: 'toggle_comment_test1', create: -> mode1
mode2_reg = name: 'toggle_comment_test2', create: -> mode2
howl.mode.register mode1_reg
howl.mode.register mode2_reg
buffer.mode = howl.mode.by_name 'toggle_comment_test1'
buffer.text = 'ab\nc'
buffer._buffer.styling\apply 1, {
1, 'whitespace', 2,
2, { 1, 's1', 3 }, 'toggle_comment_test|s1',
}
selection\set 1, 5
editor[method] editor
assert.equal '// ab\n// c', buffer.text
selection\set 1, 11
editor[method] editor
assert.equal 'ab\nc', buffer.text
howl.mode.unregister 'toggle_comment_test1'
howl.mode.unregister 'toggle_comment_test2'
(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 ->
editor[method] editor
assert.spy(buffer.mode[method]).was_called_with match.is_ref(buffer.mode), match.is_ref(editor)
if method == 'toggle_comment'
uses the buffer mode when the one to use is ambiguous
mode1 = comment_syntax: '//'
mode2 = comment_syntax: '#'
mode1_reg = name: 'toggle_comment_test1', create: -> mode1
mode2_reg = name: 'toggle_comment_test2', create: -> mode2
howl.mode.register mode1_reg
howl.mode.register mode2_reg
buffer.mode = howl.mode.by_name 'toggle_comment_test1'
buffer.text = 'ab\nc'
buffer._buffer.styling\apply 1, {
1, 'whitespace', 2,
2, { 1, 's1', 3 }, 'toggle_comment_test|s1',
}
selection\set 1, 5
editor[method] editor
assert.equal '// ab\n// c', buffer.text
selection\set 1, 11
editor[method] editor
assert.equal 'ab\nc', buffer.text
howl.mode.unregister 'toggle_comment_test1'
howl.mode.unregister 'toggle_comment_test2'
(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 ->
editor[method] editor
assert.spy(buffer.mode[method]).was_called_with match.is_ref(buffer.mode), match.is_ref(editor)
if method == 'toggle_comment'
uses the buffer mode when the one to use is ambiguous
mode1 = comment_syntax: '//'
mode2 = comment_syntax: '#'
mode1_reg = name: 'toggle_comment_test1', create: -> mode1
mode2_reg = name: 'toggle_comment_test2', create: -> mode2
howl.mode.register mode1_reg
howl.mode.register mode2_reg
buffer.mode = howl.mode.by_name 'toggle_comment_test1'
buffer.text = 'ab\nc'
buffer._buffer.styling\apply 1, {
1, 'whitespace', 2,
2, { 1, 's1', 3 }, 'toggle_comment_test|s1',
}
selection\set 1, 5
editor[method] editor
assert.equal '// ab\n// c', buffer.text
selection\set 1, 11
editor[method] editor
assert.equal 'ab\nc', buffer.text
howl.mode.unregister 'toggle_comment_test1'
howl.mode.unregister 'toggle_comment_test2'
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 match.is_ref(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!'
still restores the position
cursor.pos = 4
pcall editor.with_position_restored, editor, ->
cursor.pos = 2
error 'ARGH!'
assert.equals 4, cursor.pos
with_selection_preserved(f)
before_each ->
buffer.text = '\nhello hello hello\n'
selection\set 10, 8
calls <f> passing itself a parameter
f = spy.new -> nil
editor\with_selection_preserved f
assert.spy(f).was_called_with(match.is_ref(editor))
restores the selected region
editor\with_selection_preserved ->
selection\set 1, 2
assert.equals 10, selection.anchor
assert.equals 8, selection.cursor
(when buffer is modified outside the selection)
preserves the selected text
editor\with_selection_preserved ->
buffer\insert 'abc', 1
buffer\insert 'abc', 14
assert.equals 13, selection.anchor
assert.equals 11, selection.cursor
(when no selection present)
preserves relative position of cursor
selection\set 1, 0
editor.cursor.pos = 5
editor\with_selection_preserved ->
buffer\insert 'abc\n', 1
buffer\insert 'abc\n', 10
assert.equals 9, editor.cursor.pos
paste(opts = {})
pastes the current clip of the clipboard at the current position
buffer.text = 'hƏllo'
clipboard.push ' wörld'
cursor\eof!
editor\paste!
assert.equal 'hƏllo wörld', buffer.text
(when opts.clip is specified)
pastes that clip at the current position
clipboard.push 'hello'
clip = clipboard.current
clipboard.push 'wörld'
buffer.text = 'well '
cursor\eof!
editor\paste :clip
assert.equal 'well hello', buffer.text
(when opts.where is set to "after")
pastes the clip to the right of the current position
buffer.text = 'hƏllo\n'
clipboard.push 'yo'
cursor\move_to line: 1, column: 6
editor\paste where: 'after'
assert.equal 'hƏllo yo\n', buffer.text
cursor\eof!
editor\paste where: 'after'
assert.equal 'hƏllo yo\n yo', buffer.text
(when the clip item has .whole_lines set)
pastes the clip on a newly opened line above the current
buffer.text = 'hƏllo\nworld'
clipboard.push text: 'cruel', whole_lines: true
cursor.line = 2
cursor.column = 3
editor\paste!
assert.equal 'hƏllo\ncruel\nworld', buffer.text
pastes the clip at the start of a line if ends with a newline separator
buffer.text = 'hƏllo\nworld'
clipboard.push text: 'cruel\n', whole_lines: true
cursor.line = 2
cursor.column = 3
editor\paste!
assert.equal 'hƏllo\ncruel\nworld', buffer.text
positions the cursor at the start of the pasted clip
buffer.text = 'paste'
clipboard.push text: 'much', whole_lines: true
cursor.column = 3
editor\paste!
assert.equal 1, cursor.pos
(.. when opts.where is set to "after")
pastes the clip on a newly opened line below the current
buffer.text = 'hƏllo\nworld'
clipboard.push text: 'cruel', whole_lines: true
cursor.line = 1
cursor.column = 3
editor\paste where: 'after'
assert.equal 'hƏllo\ncruel\nworld', buffer.text
accounts for trailing newline separators
buffer.text = 'hƏllo\nworld'
clipboard.push text: 'cruel\n', whole_lines: true
cursor.line = 1
cursor.column = 3
editor\paste where: 'after'
assert.equal 'hƏllo\ncruel\nworld', buffer.text
handles pasting at the end of the buffer
buffer.text = 'at'
clipboard.push text: 'last\n', whole_lines: true
cursor.line = 1
cursor.column = 3
editor\paste where: 'after'
assert.equal 'at\nlast\n', buffer.text
(when a selection is present)
deletes the selection before pasting
buffer.text = 'hƏllo\nwonderful\nworld'
clipboard.push text: 'cruel'
selection\select lines[2].start_pos, lines[2].end_pos - 1
editor\paste!
assert.equal 'hƏllo\ncruel\nworld', buffer.text
assert.equal 'cruel', clipboard.current.text
delete_to_end_of_line(opts)
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 buffer.text, 'hƏllo world!\nnext'
handles lines without EOLs
buffer.text = 'abc'
cursor.pos = 3
editor\delete_to_end_of_line!
assert.equal 'ab', buffer.text
cursor.pos = 3
editor\delete_to_end_of_line!
assert.equal 'ab', buffer.text
deletes without copying if no_copy is specified
buffer.text = 'hƏllo world!'
cursor.pos = 3
editor\delete_to_end_of_line no_copy: true
assert.equal buffer.text, 'hƏ'
editor\paste!
assert.not_equal 'hƏllo world!', buffer.text
(buffer switching)
restores the position from buffer.properties.position if present
buf1 = Buffer {}
buf1.text = 'a whole different whale'
buf1.properties.position = pos: 10
editor.buffer = buf1
assert.equal 10, cursor.pos
buf2 = Buffer {}
buf2.text = '123\n567'
buf2.properties.position = line: 2, column: 2
editor.buffer = buf2
assert.equal 6, cursor.pos
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
updates .last_shown for buffer switched out
time = sys.time
now = time!
sys.time = -> now
pcall ->
editor.buffer = Buffer {}
sys.time = time
assert.same now, buffer.last_shown
(previewing)
does not update last_shown for previewed buffer
new_buffer = Buffer {}
new_buffer.last_shown = 2
editor\preview new_buffer
editor.buffer = Buffer {}
assert.same 2, new_buffer.last_shown
updates .last_shown for original buffer switched out
time = sys.time
now = time!
sys.time = -> now
pcall ->
editor\preview Buffer {}
sys.time= time
assert.same now, buffer.last_shown
(indentation, tabs, spaces and backspace)
defines a "tab_width" config variable, defaulting to 4
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
(when in whitespace and tab_indents is true)
before_each ->
config.tab_indents = true
config.use_tabs = false
config.indent = 2
indents the current line if in whitespace and tab_indents is 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'
moves the cursor to the beginning of the text
buffer.text = ' hƏllo'
cursor.pos = 1
editor\smart_tab!
assert.equal 5, cursor.pos
corrects any half-off indentation
buffer.text = ' hƏllo'
cursor.pos = 1
editor\smart_tab!
assert.equal 5, cursor.pos
assert.equal ' hƏllo', buffer.text
(when a selection is active)
right-shifts the lines included in a selection if any
config.indent = 2
buffer.text = 'hƏllo\nselected\nworld!'
selection\set 2, 10
editor\smart_tab!
assert.equal ' hƏllo\n selected\nworld!', buffer.text
(when in whitespace and tab_indents is false)
just inserts the corresponding tab or spaces
config.tab_indents = false
config.indent = 2
buffer.text = ' hƏllo'
cursor.pos = 1
config.use_tabs = false
editor\smart_tab!
assert.equal ' hƏllo', buffer.text
assert.equal 3, cursor.pos
config.use_tabs = true
editor\smart_tab!
assert.equal ' \t hƏllo', buffer.text
assert.equal 4, cursor.pos
smart_back_tab()
(when tab_indents is false)
moves the cursor back to the previous tab position
config.tab_indents = false
config.tab_width = 4
buffer.text = ' hƏ567890'
cursor.pos = 10
editor\smart_back_tab!
assert.equal 9, cursor.pos
editor\smart_back_tab!
assert.equal 5, cursor.pos
editor\smart_back_tab!
assert.equal 1, cursor.pos
editor\smart_back_tab!
assert.equal 1, cursor.pos
cursor.pos = 2
editor\smart_back_tab!
assert.equal 1, cursor.pos
(when tab_indents is true)
unindents when in whitespace
config.tab_indents = true
config.tab_width = 4
buffer.text = ' 567890'
cursor.pos = 10
editor\smart_back_tab!
assert.equal 9, cursor.pos
editor\smart_back_tab!
assert.equal 5, cursor.pos
editor\smart_back_tab!
assert.equal 3, cursor.pos
assert.equal ' 567890', buffer.text
editor\smart_back_tab!
assert.equal 1, cursor.pos
assert.equal '567890', buffer.text
(when a selection is active)
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\smart_back_tab!
assert.equal 'hƏllo\nselected\nworld!', buffer.text
.delete_back()
deletes back by one character
buffer.text = 'hƏllo'
cursor.pos = 2
editor\delete_back!
assert.equal buffer.text, 'Əllo'
deletes previous newline when at start of line
config.backspace_unindents = false
buffer.text = 'hƏllo\nworld'
cursor.pos = 7
editor\delete_back!
assert.equal 'hƏlloworld', buffer.text
config.backspace_unindents = true
buffer.text = 'hƏllo\nworld'
cursor.pos = 7
editor\delete_back!
assert.equal 'hƏlloworld', buffer.text
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'
assert.equal 1, cursor.pos
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'
(with a selection)
deletes the selection
buffer.text = ' 2\n 5'
cursor.pos = 5
selection\set 1, 5
editor\delete_back!
assert.equal buffer.text, '5'
.delete_back_word()
deletes back by one word
buffer.text = 'hello world'
cursor.pos = 12
editor\delete_back_word!
assert.equal buffer.text, 'hello '
(with a selection)
deletes the selection
buffer.text = ' 2\n 5'
cursor.pos = 5
selection\set 1, 5
editor\delete_back_word!
assert.equal buffer.text, '5'
.delete_forward()
deletes the character at cursor
buffer.text = 'hƏllo'
cursor.pos = 2
editor\delete_forward!
assert.equal 'hllo', buffer.text
(when a selection is active)
deletes the selection
buffer.text = 'hƏllo'
editor.selection\set 2, 5
editor\delete_forward!
assert.equal 'ho', buffer.text
assert.not_equal 'Əll', clipboard.current.text
(when at the end of a line)
deletes the line break
buffer.text = 'hƏllo\nworld'
cursor\move_to line: 1, column: 6
editor\delete_forward!
assert.equal 'hƏlloworld', buffer.text
(when at the end of the buffer)
does nothing
buffer.text = 'hƏllo'
cursor\eof!
editor\delete_forward!
assert.equal 'hƏllo', buffer.text
.delete_forward_word()
deletes forward by one word
buffer.text = 'hello world'
cursor.pos = 1
editor\delete_forward_word!
assert.equal buffer.text, 'world'
(when a selection is active)
deletes the selection
buffer.text = 'hƏllo'
editor.selection\set 2, 5
editor\delete_forward_word!
assert.equal 'ho', buffer.text
assert.not_equal 'Əll', clipboard.current.text
.shift_right()
before_each ->
config.use_tabs = false
config.indent = 2
right-shifts the current line when nothing is selected, remembering column
buffer.text = 'hƏllo\nworld!'
cursor.pos = 3
editor\shift_right!
assert.equal ' hƏllo\nworld!', buffer.text
assert.equal 5, cursor.pos
(with a selection)
right-shifts the lines included in the selection
buffer.text = 'hƏllo\nselected\nworld!'
selection\set 2, 10
editor\shift_right!
assert.equal ' hƏllo\n selected\nworld!', buffer.text
adjusts and keeps the selection
buffer.text = ' xx\nyy zz'
selection\set 3, 8 -- 'xx\nyy'
editor\shift_right!
assert.equal 'xx\n yy', selection.text
assert.same { 5, 12 }, { selection\range! }
.shift_left()
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 ' hƏllo\nworld!', buffer.text
assert.equal 2, cursor.pos
(with a selection)
left-shifts the lines included in the selection
config.indent = 2
buffer.text = ' hƏllo\n selected\nworld!'
selection\set 4, 12
editor\shift_left!
assert.equal 'hƏllo\nselected\nworld!', buffer.text
adjusts and keeps the selection
buffer.text = ' xx\n yy zz'
selection\set 3, 12 -- ' xx\nyy'
editor\shift_left!
assert.equal ' xx\nyy', selection.text
assert.same { 1, 8 }, { selection\range! }
cycle_case()
(with a selection active)
changes all lowercase selection to all uppercase
buffer.text = 'hello selectëd #world'
selection\set 7, 22
editor\cycle_case!
assert.equals 'hello SELECTËD #WORLD', buffer.text
changes all uppercase selection to titlecase
buffer.text = 'hello SELECTËD #WORLD HELLO'
selection\set 7, 28
editor\cycle_case!
assert.equals 'hello Selectëd #world Hello', buffer.text
changes mixed case selection to all lowercase
buffer.text = 'hello SelectËD #WorLd'
selection\set 7, 22
editor\cycle_case!
assert.equals 'hello selectëd #world', buffer.text
preserves selection
buffer.text = 'select'
selection\set 3, 5
editor\cycle_case!
assert.equals 3, selection.anchor
assert.equals 5, selection.cursor
(with no selection active)
changes all lowercase word to all uppercase
buffer.text = 'hello wörld'
cursor.pos = 7
editor\cycle_case!
assert.equals 'hello WÖRLD', buffer.text
changes all uppercase word to titlecase
buffer.text = 'hello WÖRLD'
cursor.pos = 7
editor\cycle_case!
assert.equals 'hello Wörld', buffer.text
changes mixed case word to all lowercase
buffer.text = 'hello WörLd'
cursor.pos = 7
editor\cycle_case!
assert.equals 'hello wörld', buffer.text
duplicate_current
(with an active selection)
duplicates the selection
buffer.text = 'hello\nwörld'
cursor.pos = 2
selection\set 2, 5 -- 'ell'
editor\duplicate_current!
assert.equals 'hellello\nwörld', buffer.text
keeps the cursor and current selection
buffer.text = '123456'
selection\set 5, 2
editor\duplicate_current!
assert.equals 2, cursor.pos
assert.equals 2, selection.cursor
assert.equals 5, selection.anchor
(with no active selection)
duplicates the current line
buffer.text = 'hello\nwörld'
cursor.pos = 3
editor\duplicate_current!
assert.equals 'hello\nhello\nwörld', buffer.text
cut
(with an active selection)
cuts the selection
buffer.text = 'hello\nwörld'
cursor.pos = 2
selection\set 2, 5 -- 'ell'
editor\cut!
assert.equals 'ho\nwörld', buffer.text
cursor.pos = 1
editor\paste!
assert.equal 'ellho\nwörld', buffer.text
(with no active selection)
cuts the current line
buffer.text = 'hello\nwörld'
cursor.pos = 3
editor\cut!
assert.equals 'wörld', buffer.text
cursor.pos = 1
editor\paste!
assert.equal 'hello\nwörld', buffer.text
cuts the empty line
buffer.text = '\nwörld'
cursor.pos = 1
editor\cut!
assert.equals 'wörld', buffer.text
editor\paste!
assert.equal '\nwörld', buffer.text
copy
(with an active selection)
copies the selection
buffer.text = 'hello\nwörld'
cursor.pos = 2
selection\set 2, 5 -- 'ell'
editor\copy!
cursor.pos = 1
editor\paste!
assert.equals 'ellhello\nwörld', buffer.text
(with no active selection)
copies the current line
buffer.text = 'hello\nwörld'
cursor.pos = 3
editor\copy!
cursor.pos = 1
editor\paste!
assert.equals 'hello\nhello\nwörld', buffer.text
(resource management)
editors are collected as they should
e = Editor Buffer {}
editors = setmetatable {e}, __mode: 'v'
e\to_gobject!\destroy!
e = nil
collect_memory!
assert.is_true editors[1] == nil
releases resources after buffer switching
b1 = Buffer {}
b2 = Buffer {}
e = Editor b1
buffers = setmetatable { b1, b2 }, __mode: 'v'
editors = setmetatable { e }, __mode: 'v'
e.buffer = b2
e.buffer = b1
e\to_gobject!\destroy!
e = nil
b1 = nil
b2 = nil
collectgarbage!
assert.is_nil editors[1]
assert.is_nil buffers[1]
assert.is_nil buffers[2]
(get_matching_brace)
finds position of matching opending/closing brace
editor.buffer.mode.auto_pairs = {'[': ']'}
editor.buffer.text = '[]'
assert.same 2, editor\get_matching_brace 1
assert.same 1, editor\get_matching_brace 2
assert.same nil, editor\get_matching_brace 3
editor.buffer.text = '[Ü]'
assert.same 3, editor\get_matching_brace 1
assert.same 1, editor\get_matching_brace 3
assert.same nil, editor\get_matching_brace 2
editor.buffer.text = '1ÜÜ4[6ÜÜ9]---'
assert.same 10, editor\get_matching_brace 5
assert.same 5, editor\get_matching_brace 10
assert.same nil, editor\get_matching_brace 1
assert.same nil, editor\get_matching_brace 4
assert.same nil, editor\get_matching_brace 6
assert.same nil, editor\get_matching_brace 11
returns nil for unmatched/mismatched braces
editor.buffer.mode.auto_pairs = {'[': ']'}
editor.buffer.text = ']['
assert.same nil, editor\get_matching_brace 1
assert.same nil, editor\get_matching_brace 2
editor.buffer.text = '([]]'
assert.same nil, editor\get_matching_brace 4
(config updates)
local editor2
before_each ->
editor2 = Editor Buffer {}
buffer config updates affect containing editor only
editor.buffer.config.line_numbers = true
editor2.buffer.config.line_numbers = true
assert.true editor.line_numbers
assert.true editor2.line_numbers
editor2.buffer.config.line_numbers = false
assert.true editor.line_numbers
assert.false editor2.line_numbers
buffer mode change triggers config refresh for containing editor
mode1 = {}
mode2 = {}
howl.mode.register name: 'test_mode1', create: -> mode1
howl.mode.register name: 'test_mode2', create: -> mode2
howl.mode.configure 'test_mode1',
line_numbers: false
howl.mode.configure 'test_mode2',
line_numbers: true
buffer.mode = howl.mode.by_name 'test_mode1'
assert.false editor.line_numbers
buffer.mode = howl.mode.by_name 'test_mode2'
assert.true editor.line_numbers
howl.mode.unregister 'test_mode1'
howl.mode.unregister 'test_mode2'