howl.ui.Selection

buffer = Buffer {}
editor = Editor buffer
selection = editor.selection
cursor = editor.cursor
window = Gtk.OffscreenWindow!
window\add editor\to_gobject!
window\show_all!
howl.app\pump_mainloop!

before_each ->
  editor.view.selection\clear!
  buffer.text = 'Liñe 1 ʘf tƏxt\nLiñe 1 ʘf tƏxt'

.empty returns whether any selection exists

assert.is_true selection.empty
selection\set 1, 3
assert.is_false selection.empty

range() returns the [start, stop) range of the selection in ascending order

selection\set 2, 5
start, stop = selection\range!
assert.equal 2, start
assert.equal 5, stop

selection\set 5, 2
start, stop = selection\range!
assert.equal 2, start
assert.equal 5, stop

set(anchor, pos)

sets the anchor and cursor at the same time

buffer.text = 'Liñe 1'
selection\set 1, 5
assert.equal 'Liñe', selection.text

moves the editor cursor to <pos>

buffer.text = '12345'
selection\set 2, 4
assert.equal 4, cursor.pos

select(anchor, pos)

adjusts the selection to include the specified range

buffer.text = 'Liñe 1 ʘf tƏxt'
selection\select 1, 4
assert.equal 1, selection.anchor
assert.equal 5, selection.cursor
assert.equal 'Liñe', selection.text

selection\select 4, 2
assert.equal 5, selection.anchor
assert.equal 2, selection.cursor
assert.equal 'iñe', selection.text

moves the editor cursor to <pos>

buffer.text = '12345'
selection\select 2, 4
assert.equal 5, cursor.pos

select_all()

adjusts the selection to include the entire buffer

buffer.text = 'Liñe 1 ʘf tƏxt'
selection\select_all!
assert.equal 1, selection.anchor
assert.equal buffer.text.ulen + 1, selection.cursor

does not change the scroll position

buffer.text = '1\n2\n3\n4\n5\n6\n7\n8\n9\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n0'
cursor.pos = 1
editor.view.first_visible_line = 2
selection\select_all!
assert.equal 2, editor.view.first_visible_line

.anchor

is nil if nothing is selected

assert.is_nil selection.anchor

returns the start position of the selection with a selection active

selection\set 2, 5
assert.equal 2, selection.anchor

setting it to <pos> sets the selection to the text range [pos..<cursor>)

buffer.text = 'Liñe 1 ʘf tƏxt'
cursor.pos = 4
selection.anchor = 1
assert.equal 1, selection.anchor
assert.equal 4, selection.cursor
assert.equal 'Liñ', selection.text

.cursor

returns nil if nothing is selected

assert.is_nil selection.cursor

returns the end position of the selection with a selection active

buffer.text = 'Liñe 1 ʘf tƏxt'
selection\set 2, 5
assert.equal 5, selection.cursor

selection.anchor = 3
selection.cursor = 5
assert.equal 5, selection.cursor
assert.equal 'ñe', selection.text

.persistent

causes the selection to be extended with movement when true

buffer.text = 'line1\nline2'
cursor.pos = 1
selection.persistent = true
cursor\down!
assert.equal 'line1\n', selection.text

remove

removes the selection

selection\set 2, 5
selection\remove!
assert.is_true selection.empty

does not remove the selected text

buffer.text = 'foobar'
selection\set 2, 5
selection\remove!
assert.equal 'foobar', buffer.text

cut

removes the selected text

buffer.text = 'Liñe 1 ʘf tƏxt'
selection\set 1, 5
selection\cut!
assert.equal ' 1 ʘf tƏxt', buffer.text

removes the selection

selection\set 2, 5
selection\cut!
assert.is_true selection.empty

clears the persistent flag

selection\set 1, 5
selection.persistent = true
selection\cut!
assert.is_false selection.persistent

pushes the selection to the clipboard, with any options as specified

buffer.text = 'Liñe 1 ʘf tƏxt'
selection\set 1, 2
selection\cut!

assert.equal 'L', clipboard.current.text

selection\set 1, 2
selection\cut whole_lines: true
assert.equal true, clipboard.current.whole_lines

selection\set 1, 3
selection\cut {}, to: 'abc'
assert.equal 'ñe', clipboard.registers.abc.text

signals "selection-cut"

with_signal_handler 'selection-cut', nil, (handler) ->
  selection\set 1, 5
  selection\cut!
  assert.spy(handler).was_called!

copy(clip_options = nil, clipboard_options = nil)

keeps the selection

selection\set 1, 5
selection\copy!
assert.is_false selection.empty

keeps the persistent flag

selection\set 1, 5
selection.persistent = true
selection\copy!
assert.is_true selection.persistent

pushes the selection to the clipboard, with any options as specified

buffer.text = 'Liñe 1 ʘf tƏxt'
selection\set 1, 5
selection\copy!

assert.equal 'Liñe', clipboard.current.text

selection\set 1, 5
selection\copy whole_lines: true
assert.equal true, clipboard.current.whole_lines

selection\set 1, 4
selection\copy {}, to: 'abc'
assert.equal 'Liñ', clipboard.registers.abc.text

signals "selection-copied"

with_signal_handler 'selection-copied', nil, (handler) ->
  selection\set 1, 5
  selection\copy!
  assert.spy(handler).was_called!

.text

returns nil if nothing is selected

assert.is_nil selection.text

returns the currently selected text when the selection is not empty

selection\set 1, 3
assert.equal 'Li', selection.text

.text = <text>

replaces the selection with <text> and removes the selection

buffer.text = 'Liñe 1 ʘf tƏxt'
selection\set 1, 3
selection.text = 'Shi'
assert.equal 'Shiñe 1 ʘf tƏxt', buffer.text
assert.is_true selection.empty

raises an error if the selection is empty

assert.raises 'empty', -> selection.text = 'Yowser!'

when .includes_cursor is set to true

before_each -> selection.includes_cursor = true
after_each -> selection.includes_cursor = false

select(anchor, pos) adjusts pos if needed to only point at the end of selection

buffer.text = 'Liñe 1 ʘf tƏxt'
selection\select 1, 4
assert.equal 4, selection.cursor
assert.equal 'Liñe', selection.text

selection\select 4, 2
assert.equal 5, selection.anchor
assert.equal 2, selection.cursor
assert.equal 'iñe', selection.text

selection\select 1, 1
assert.equal 1, selection.anchor
assert.equal 1, selection.cursor
assert.equal 'L', selection.text

.text includes the current character

selection\set 1, 3
assert.equal 'Liñ', selection.text

.text = <text> replaces the current character as well

selection\set 1, 2
selection.text = 'Shi'
assert.equal 'Shiñe 1 ʘf tƏxt', buffer.lines[1].text

cut() removes the current character as well

selection\set 1, 5
selection\cut!
assert.equal '1 ʘf tƏxt', buffer.lines[1].text

copy() copies the current character as well

selection\set 1, 4
selection\copy!
cursor.column = 1
editor\paste!
assert.equal 'LiñeLiñe 1 ʘf tƏxt', buffer.lines[1].text

(when the selection ends at a end-of-line character)

before_each ->
  buffer.text = 'liñe1\nline2'
  selection\set 1, 6

the end-of-line character is not included in the selection

assert.equal 'liñe1', selection.text

range()

is {nil, nil} for an empty selection

assert.same {nil, nil}, { selection\range! }

includes the cursor position if needed

selection\set 2, 5
start, stop = selection\range!
assert.equal 2, start
assert.equal 6, stop

selection\set 5, 2
start, stop = selection\range!
assert.equal 2, start
assert.equal 5, stop

does not include an position after eof however

selection\set #buffer - 1, #buffer + 1
start, stop = selection\range!
assert.equal #buffer - 1, start
assert.equal #buffer + 1, stop

.empty

is true if the selection is really removed

assert.is_true selection.empty

is only empty at EOF if a selection is set

buffer.text = '123'
selection\select 1, 1
assert.is_false selection.empty

selection\select 4, 4
assert.is_true selection.empty