support selections in the source editor
I've only tested side A so far, and included a statement of how I want side B to behave.
This commit is contained in:
parent
516944b572
commit
73fefa7d09
2
main.lua
2
main.lua
|
@ -60,7 +60,7 @@ function App.load()
|
|||
load_file_from_source_or_save_directory('log_browser.lua')
|
||||
load_file_from_source_or_save_directory('source_text.lua')
|
||||
load_file_from_source_or_save_directory('search.lua')
|
||||
load_file_from_source_or_save_directory('select.lua')
|
||||
load_file_from_source_or_save_directory('source_select.lua')
|
||||
load_file_from_source_or_save_directory('source_undo.lua')
|
||||
load_file_from_source_or_save_directory('colorize.lua')
|
||||
load_file_from_source_or_save_directory('source_text_tests.lua')
|
||||
|
|
|
@ -76,6 +76,14 @@ function edit.initialize_state(top, left, right, font_height, line_height) -- c
|
|||
cursor1 = {line=1, pos=1, posB=nil}, -- position of cursor
|
||||
screen_bottom1 = {line=1, pos=1, posB=nil}, -- position of start of screen line at bottom of screen
|
||||
|
||||
selection1 = {},
|
||||
-- some extra state to compute selection between mouse press and release
|
||||
old_cursor1 = nil,
|
||||
old_selection1 = nil,
|
||||
mousepress_shift = nil,
|
||||
-- when selecting text, avoid recomputing some state on every single frame
|
||||
recent_mouse = {},
|
||||
|
||||
-- cursor coordinates in pixels
|
||||
cursor_x = 0,
|
||||
cursor_y = 0,
|
||||
|
@ -208,9 +216,22 @@ function edit.mouse_pressed(State, x,y, mouse_button)
|
|||
for line_index,line in ipairs(State.lines) do
|
||||
if line.mode == 'text' then
|
||||
if Text.in_line(State, line_index, x,y) then
|
||||
-- delicate dance between cursor, selection and old cursor/selection
|
||||
-- scenarios:
|
||||
-- regular press+release: sets cursor, clears selection
|
||||
-- shift press+release:
|
||||
-- sets selection to old cursor if not set otherwise leaves it untouched
|
||||
-- sets cursor
|
||||
-- press and hold to start a selection: sets selection on press, cursor on release
|
||||
-- press and hold, then press shift: ignore shift
|
||||
-- i.e. mouse_released should never look at shift state
|
||||
State.old_cursor1 = State.cursor1
|
||||
State.old_selection1 = State.selection1
|
||||
State.mousepress_shift = App.shift_down()
|
||||
local pos,posB = Text.to_pos_on_line(State, line_index, x, y)
|
||||
--? print(x,y, 'setting cursor:', line_index, pos, posB)
|
||||
State.cursor1 = {line=line_index, pos=pos, posB=posB}
|
||||
State.selection1 = {line=line_index, pos=pos, posB=posB}
|
||||
--? print('selection', State.selection1.line, State.selection1.pos, State.selection1.posB)
|
||||
break
|
||||
end
|
||||
elseif line.mode == 'drawing' then
|
||||
|
@ -236,6 +257,30 @@ function edit.mouse_released(State, x,y, mouse_button)
|
|||
record_undo_event(State, {before=Drawing.before, after=snapshot(State, State.lines.current_drawing_index)})
|
||||
Drawing.before = nil
|
||||
end
|
||||
else
|
||||
for line_index,line in ipairs(State.lines) do
|
||||
if line.mode == 'text' then
|
||||
if Text.in_line(State, line_index, x,y) then
|
||||
--? print('reset selection')
|
||||
local pos,posB = Text.to_pos_on_line(State, line_index, x, y)
|
||||
State.cursor1 = {line=line_index, pos=pos, posB=posB}
|
||||
--? print('cursor', State.cursor1.line, State.cursor1.pos, State.cursor1.posB)
|
||||
if State.mousepress_shift then
|
||||
if State.old_selection1.line == nil then
|
||||
State.selection1 = State.old_cursor1
|
||||
else
|
||||
State.selection1 = State.old_selection1
|
||||
end
|
||||
end
|
||||
State.old_cursor1, State.old_selection1, State.mousepress_shift = nil
|
||||
if eq(State.cursor1, State.selection1) then
|
||||
State.selection1 = {}
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
--? print('selection:', State.selection1.line, State.selection1.pos)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -258,6 +303,14 @@ function edit.textinput(State, t)
|
|||
end
|
||||
|
||||
function edit.keychord_pressed(State, chord, key)
|
||||
if State.selection1.line and
|
||||
not State.lines.current_drawing and
|
||||
-- printable character created using shift key => delete selection
|
||||
-- (we're not creating any ctrl-shift- or alt-shift- combinations using regular/printable keys)
|
||||
(not App.shift_down() or utf8.len(key) == 1) and
|
||||
chord ~= 'C-c' and chord ~= 'C-x' and chord ~= 'backspace' and backspace ~= 'delete' and not App.is_cursor_movement(chord) then
|
||||
Text.delete_selection(State, State.left, State.right)
|
||||
end
|
||||
if State.search_term then
|
||||
if chord == 'escape' then
|
||||
State.search_term = nil
|
||||
|
@ -336,6 +389,7 @@ function edit.keychord_pressed(State, chord, key)
|
|||
local src = event.before
|
||||
State.screen_top1 = deepcopy(src.screen_top)
|
||||
State.cursor1 = deepcopy(src.cursor)
|
||||
State.selection1 = deepcopy(src.selection)
|
||||
patch(State.lines, event.after, event.before)
|
||||
patch_placeholders(State.line_cache, event.after, event.before)
|
||||
-- invalidate various cached bits of lines
|
||||
|
@ -351,6 +405,7 @@ function edit.keychord_pressed(State, chord, key)
|
|||
local src = event.after
|
||||
State.screen_top1 = deepcopy(src.screen_top)
|
||||
State.cursor1 = deepcopy(src.cursor)
|
||||
State.selection1 = deepcopy(src.selection)
|
||||
patch(State.lines, event.before, event.after)
|
||||
-- invalidate various cached bits of lines
|
||||
State.lines.current_drawing = nil
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
-- helpers for selecting portions of text
|
||||
-- To keep things simple, we'll ignore the B side when selections start on the
|
||||
-- A side, and stick to within a single B side selections start in.
|
||||
|
||||
-- Return any intersection of the region from State.selection1 to State.cursor1 (or
|
||||
-- current mouse, if mouse is pressed; or recent mouse if mouse is pressed and
|
||||
-- currently over a drawing) with the region between {line=line_index, pos=apos}
|
||||
-- and {line=line_index, pos=bpos}.
|
||||
-- apos must be less than bpos. However State.selection1 and State.cursor1 can be in any order.
|
||||
-- Result: positions spos,epos between apos,bpos.
|
||||
function Text.clip_selection(State, line_index, apos, bpos)
|
||||
if State.selection1.line == nil then return nil,nil end
|
||||
-- min,max = sorted(State.selection1,State.cursor1)
|
||||
local minl,minp = State.selection1.line,State.selection1.pos
|
||||
local maxl,maxp
|
||||
if App.mouse_down(1) then
|
||||
maxl,maxp = Text.mouse_pos(State)
|
||||
else
|
||||
maxl,maxp = State.cursor1.line,State.cursor1.pos
|
||||
end
|
||||
if Text.lt1({line=maxl, pos=maxp},
|
||||
{line=minl, pos=minp}) then
|
||||
minl,maxl = maxl,minl
|
||||
minp,maxp = maxp,minp
|
||||
end
|
||||
-- check if intervals are disjoint
|
||||
if line_index < minl then return nil,nil end
|
||||
if line_index > maxl then return nil,nil end
|
||||
if line_index == minl and bpos <= minp then return nil,nil end
|
||||
if line_index == maxl and apos >= maxp then return nil,nil end
|
||||
-- compare bounds more carefully (start inclusive, end exclusive)
|
||||
local a_ge = Text.le1({line=minl, pos=minp}, {line=line_index, pos=apos})
|
||||
local b_lt = Text.lt1({line=line_index, pos=bpos}, {line=maxl, pos=maxp})
|
||||
--? print(minl,line_index,maxl, '--', minp,apos,bpos,maxp, '--', a_ge,b_lt)
|
||||
if a_ge and b_lt then
|
||||
-- fully contained
|
||||
return apos,bpos
|
||||
elseif a_ge then
|
||||
assert(maxl == line_index)
|
||||
return apos,maxp
|
||||
elseif b_lt then
|
||||
assert(minl == line_index)
|
||||
return minp,bpos
|
||||
else
|
||||
assert(minl == maxl and minl == line_index)
|
||||
return minp,maxp
|
||||
end
|
||||
end
|
||||
|
||||
-- draw highlight for line corresponding to (lo,hi) given an approximate x,y and pos on the same screen line
|
||||
-- Creates text objects every time, so use this sparingly.
|
||||
-- Returns some intermediate computation useful elsewhere.
|
||||
function Text.draw_highlight(State, line, x,y, pos, lo,hi)
|
||||
if lo then
|
||||
local lo_offset = Text.offset(line.data, lo)
|
||||
local hi_offset = Text.offset(line.data, hi)
|
||||
local pos_offset = Text.offset(line.data, pos)
|
||||
local lo_px
|
||||
if pos == lo then
|
||||
lo_px = 0
|
||||
else
|
||||
local before = line.data:sub(pos_offset, lo_offset-1)
|
||||
local before_text = App.newText(love.graphics.getFont(), before)
|
||||
lo_px = App.width(before_text)
|
||||
end
|
||||
--? print(lo,pos,hi, '--', lo_offset,pos_offset,hi_offset, '--', lo_px)
|
||||
local s = line.data:sub(lo_offset, hi_offset-1)
|
||||
local text = App.newText(love.graphics.getFont(), s)
|
||||
local text_width = App.width(text)
|
||||
App.color(Highlight_color)
|
||||
love.graphics.rectangle('fill', x+lo_px,y, text_width,State.line_height)
|
||||
App.color(Text_color)
|
||||
return lo_px
|
||||
end
|
||||
end
|
||||
|
||||
-- inefficient for some reason, so don't do it on every frame
|
||||
function Text.mouse_pos(State)
|
||||
local time = love.timer.getTime()
|
||||
if State.recent_mouse.time and State.recent_mouse.time > time-0.1 then
|
||||
return State.recent_mouse.line, State.recent_mouse.pos
|
||||
end
|
||||
State.recent_mouse.time = time
|
||||
local line,pos = Text.to_pos(State, App.mouse_x(), App.mouse_y())
|
||||
if line then
|
||||
State.recent_mouse.line = line
|
||||
State.recent_mouse.pos = pos
|
||||
end
|
||||
return State.recent_mouse.line, State.recent_mouse.pos
|
||||
end
|
||||
|
||||
function Text.to_pos(State, x,y)
|
||||
for line_index,line in ipairs(State.lines) do
|
||||
if line.mode == 'text' then
|
||||
if Text.in_line(State, line_index, x,y) then
|
||||
return line_index, Text.to_pos_on_line(State, line_index, x,y)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Text.cut_selection(State)
|
||||
if State.selection1.line == nil then return end
|
||||
local result = Text.selection(State)
|
||||
Text.delete_selection(State)
|
||||
return result
|
||||
end
|
||||
|
||||
function Text.delete_selection(State)
|
||||
if State.selection1.line == nil then return end
|
||||
local minl,maxl = minmax(State.selection1.line, State.cursor1.line)
|
||||
local before = snapshot(State, minl, maxl)
|
||||
Text.delete_selection_without_undo(State)
|
||||
record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})
|
||||
end
|
||||
|
||||
function Text.delete_selection_without_undo(State)
|
||||
if State.selection1.line == nil then return end
|
||||
-- min,max = sorted(State.selection1,State.cursor1)
|
||||
local minl,minp = State.selection1.line,State.selection1.pos
|
||||
local maxl,maxp = State.cursor1.line,State.cursor1.pos
|
||||
if minl > maxl then
|
||||
minl,maxl = maxl,minl
|
||||
minp,maxp = maxp,minp
|
||||
elseif minl == maxl then
|
||||
if minp > maxp then
|
||||
minp,maxp = maxp,minp
|
||||
end
|
||||
end
|
||||
-- update State.cursor1 and State.selection1
|
||||
State.cursor1.line = minl
|
||||
State.cursor1.pos = minp
|
||||
if Text.lt1(State.cursor1, State.screen_top1) then
|
||||
State.screen_top1.line = State.cursor1.line
|
||||
State.screen_top1.pos = Text.pos_at_start_of_screen_line(State, State.cursor1)
|
||||
end
|
||||
State.selection1 = {}
|
||||
-- delete everything between min (inclusive) and max (exclusive)
|
||||
Text.clear_screen_line_cache(State, minl)
|
||||
local min_offset = Text.offset(State.lines[minl].data, minp)
|
||||
local max_offset = Text.offset(State.lines[maxl].data, maxp)
|
||||
if minl == maxl then
|
||||
--? print('minl == maxl')
|
||||
State.lines[minl].data = State.lines[minl].data:sub(1, min_offset-1)..State.lines[minl].data:sub(max_offset)
|
||||
return
|
||||
end
|
||||
assert(minl < maxl)
|
||||
local rhs = State.lines[maxl].data:sub(max_offset)
|
||||
for i=maxl,minl+1,-1 do
|
||||
table.remove(State.lines, i)
|
||||
table.remove(State.line_cache, i)
|
||||
end
|
||||
State.lines[minl].data = State.lines[minl].data:sub(1, min_offset-1)..rhs
|
||||
end
|
||||
|
||||
function Text.selection(State)
|
||||
if State.selection1.line == nil then return end
|
||||
-- min,max = sorted(State.selection1,State.cursor1)
|
||||
local minl,minp = State.selection1.line,State.selection1.pos
|
||||
local maxl,maxp = State.cursor1.line,State.cursor1.pos
|
||||
if minl > maxl then
|
||||
minl,maxl = maxl,minl
|
||||
minp,maxp = maxp,minp
|
||||
elseif minl == maxl then
|
||||
if minp > maxp then
|
||||
minp,maxp = maxp,minp
|
||||
end
|
||||
end
|
||||
local min_offset = Text.offset(State.lines[minl].data, minp)
|
||||
local max_offset = Text.offset(State.lines[maxl].data, maxp)
|
||||
if minl == maxl then
|
||||
return State.lines[minl].data:sub(min_offset, max_offset-1)
|
||||
end
|
||||
assert(minl < maxl)
|
||||
local result = {State.lines[minl].data:sub(min_offset)}
|
||||
for i=minl+1,maxl-1 do
|
||||
if State.lines[i].mode == 'text' then
|
||||
table.insert(result, State.lines[i].data)
|
||||
end
|
||||
end
|
||||
table.insert(result, State.lines[maxl].data:sub(1, max_offset-1))
|
||||
return table.concat(result, '\n')
|
||||
end
|
|
@ -110,6 +110,10 @@ function Text.draw_wrapping_line(State, line_index, x,y, startpos)
|
|||
screen_line_starting_pos = pos
|
||||
x = State.left
|
||||
end
|
||||
if State.selection1.line then
|
||||
local lo, hi = Text.clip_selection(State, line_index, pos, pos+frag_len)
|
||||
Text.draw_highlight(State, line, x,y, pos, lo,hi)
|
||||
end
|
||||
-- Make [[WikiWords]] (single word, all in one screen line) clickable.
|
||||
local trimmed_word = rtrim(frag) -- compute_fragments puts whitespace at the end
|
||||
if starts_with(trimmed_word, '[[') and ends_with(trimmed_word, ']]') then
|
||||
|
@ -172,6 +176,10 @@ function Text.draw_wrapping_lineB(State, line_index, x,y, startpos)
|
|||
screen_line_starting_pos = pos
|
||||
x = State.left
|
||||
end
|
||||
if State.selection1.line then
|
||||
local lo, hi = Text.clip_selection(State, line_index, pos, pos+frag_len)
|
||||
Text.draw_highlight(State, line, x,y, pos, lo,hi)
|
||||
end
|
||||
App.screen.draw(frag_text, x,y)
|
||||
-- render cursor if necessary
|
||||
if State.cursor1.posB and line_index == State.cursor1.line then
|
||||
|
@ -375,12 +383,13 @@ end
|
|||
|
||||
-- Don't handle any keys here that would trigger love.textinput above.
|
||||
function Text.keychord_pressed(State, chord)
|
||||
--? print('chord', chord)
|
||||
--? print('chord', chord, State.selection1.line, State.selection1.pos)
|
||||
--== shortcuts that mutate text
|
||||
if chord == 'return' then
|
||||
local before_line = State.cursor1.line
|
||||
local before = snapshot(State, before_line)
|
||||
Text.insert_return(State)
|
||||
State.selection1 = {}
|
||||
if State.cursor_y > App.screen.height - State.line_height then
|
||||
Text.snap_cursor_to_bottom_of_screen(State, State.left, State.right)
|
||||
end
|
||||
|
@ -398,6 +407,11 @@ function Text.keychord_pressed(State, chord)
|
|||
schedule_save(State)
|
||||
record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})
|
||||
elseif chord == 'backspace' then
|
||||
if State.selection1.line then
|
||||
Text.delete_selection(State, State.left, State.right)
|
||||
schedule_save(State)
|
||||
return
|
||||
end
|
||||
local before
|
||||
if State.cursor1.pos and State.cursor1.pos > 1 then
|
||||
before = snapshot(State, State.cursor1.line)
|
||||
|
@ -456,6 +470,11 @@ function Text.keychord_pressed(State, chord)
|
|||
schedule_save(State)
|
||||
record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})
|
||||
elseif chord == 'delete' then
|
||||
if State.selection1.line then
|
||||
Text.delete_selection(State, State.left, State.right)
|
||||
schedule_save(State)
|
||||
return
|
||||
end
|
||||
local before
|
||||
if State.cursor1.posB or State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) then
|
||||
before = snapshot(State, State.cursor1.line)
|
||||
|
@ -504,44 +523,84 @@ function Text.keychord_pressed(State, chord)
|
|||
--== shortcuts that move the cursor
|
||||
elseif chord == 'left' then
|
||||
Text.left(State)
|
||||
State.selection1 = {}
|
||||
elseif chord == 'right' then
|
||||
Text.right(State)
|
||||
State.selection1 = {}
|
||||
elseif chord == 'S-left' then
|
||||
if State.selection1.line == nil then
|
||||
State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
|
||||
end
|
||||
Text.left(State)
|
||||
elseif chord == 'S-right' then
|
||||
if State.selection1.line == nil then
|
||||
State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
|
||||
end
|
||||
Text.right(State)
|
||||
-- C- hotkeys reserved for drawings, so we'll use M-
|
||||
elseif chord == 'M-left' then
|
||||
Text.word_left(State)
|
||||
State.selection1 = {}
|
||||
elseif chord == 'M-right' then
|
||||
Text.word_right(State)
|
||||
State.selection1 = {}
|
||||
elseif chord == 'M-S-left' then
|
||||
if State.selection1.line == nil then
|
||||
State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
|
||||
end
|
||||
Text.word_left(State)
|
||||
elseif chord == 'M-S-right' then
|
||||
if State.selection1.line == nil then
|
||||
State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
|
||||
end
|
||||
Text.word_right(State)
|
||||
elseif chord == 'home' then
|
||||
Text.start_of_line(State)
|
||||
State.selection1 = {}
|
||||
elseif chord == 'end' then
|
||||
Text.end_of_line(State)
|
||||
State.selection1 = {}
|
||||
elseif chord == 'S-home' then
|
||||
if State.selection1.line == nil then
|
||||
State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
|
||||
end
|
||||
Text.start_of_line(State)
|
||||
elseif chord == 'S-end' then
|
||||
if State.selection1.line == nil then
|
||||
State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
|
||||
end
|
||||
Text.end_of_line(State)
|
||||
elseif chord == 'up' then
|
||||
Text.up(State)
|
||||
State.selection1 = {}
|
||||
elseif chord == 'down' then
|
||||
Text.down(State)
|
||||
State.selection1 = {}
|
||||
elseif chord == 'S-up' then
|
||||
if State.selection1.line == nil then
|
||||
State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
|
||||
end
|
||||
Text.up(State)
|
||||
elseif chord == 'S-down' then
|
||||
if State.selection1.line == nil then
|
||||
State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
|
||||
end
|
||||
Text.down(State)
|
||||
elseif chord == 'pageup' then
|
||||
Text.pageup(State)
|
||||
State.selection1 = {}
|
||||
elseif chord == 'pagedown' then
|
||||
Text.pagedown(State)
|
||||
State.selection1 = {}
|
||||
elseif chord == 'S-pageup' then
|
||||
if State.selection1.line == nil then
|
||||
State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
|
||||
end
|
||||
Text.pageup(State)
|
||||
elseif chord == 'S-pagedown' then
|
||||
if State.selection1.line == nil then
|
||||
State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}
|
||||
end
|
||||
Text.pagedown(State)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -282,6 +282,7 @@ function test_click_with_mouse()
|
|||
edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
|
||||
-- cursor moves
|
||||
check_eq(Editor_state.cursor1.line, 1, 'F - test_click_with_mouse/cursor:line')
|
||||
check_nil(Editor_state.selection1.line, 'F - test_click_with_mouse/selection is empty to avoid perturbing future edits')
|
||||
end
|
||||
|
||||
function test_click_with_mouse_to_left_of_line()
|
||||
|
@ -300,6 +301,7 @@ function test_click_with_mouse_to_left_of_line()
|
|||
-- cursor moves to start of line
|
||||
check_eq(Editor_state.cursor1.line, 1, 'F - test_click_with_mouse_to_left_of_line/cursor:line')
|
||||
check_eq(Editor_state.cursor1.pos, 1, 'F - test_click_with_mouse_to_left_of_line/cursor:pos')
|
||||
check_nil(Editor_state.selection1.line, 'F - test_click_with_mouse_to_left_of_line/selection is empty to avoid perturbing future edits')
|
||||
end
|
||||
|
||||
function test_click_with_mouse_takes_margins_into_account()
|
||||
|
@ -319,6 +321,7 @@ function test_click_with_mouse_takes_margins_into_account()
|
|||
-- cursor moves
|
||||
check_eq(Editor_state.cursor1.line, 1, 'F - test_click_with_mouse_takes_margins_into_account/cursor:line')
|
||||
check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_with_mouse_takes_margins_into_account/cursor:pos')
|
||||
check_nil(Editor_state.selection1.line, 'F - test_click_with_mouse_takes_margins_into_account/selection is empty to avoid perturbing future edits')
|
||||
end
|
||||
|
||||
function test_click_with_mouse_on_empty_line()
|
||||
|
@ -408,6 +411,7 @@ function test_click_with_mouse_on_wrapping_line()
|
|||
-- cursor moves
|
||||
check_eq(Editor_state.cursor1.line, 1, 'F - test_click_with_mouse_on_wrapping_line/cursor:line')
|
||||
check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_with_mouse_on_wrapping_line/cursor:pos')
|
||||
check_nil(Editor_state.selection1.line, 'F - test_click_with_mouse_on_wrapping_line/selection is empty to avoid perturbing future edits')
|
||||
end
|
||||
|
||||
function test_click_with_mouse_on_wrapping_line_takes_margins_into_account()
|
||||
|
@ -427,6 +431,7 @@ function test_click_with_mouse_on_wrapping_line_takes_margins_into_account()
|
|||
-- cursor moves
|
||||
check_eq(Editor_state.cursor1.line, 1, 'F - test_click_with_mouse_on_wrapping_line_takes_margins_into_account/cursor:line')
|
||||
check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_with_mouse_on_wrapping_line_takes_margins_into_account/cursor:pos')
|
||||
check_nil(Editor_state.selection1.line, 'F - test_click_with_mouse_on_wrapping_line_takes_margins_into_account/selection is empty to avoid perturbing future edits')
|
||||
end
|
||||
|
||||
function test_draw_text_wrapping_within_word()
|
||||
|
@ -585,6 +590,174 @@ function test_click_past_end_of_word_wrapping_line()
|
|||
check_eq(Editor_state.cursor1.pos, 20, 'F - test_click_past_end_of_word_wrapping_line/cursor')
|
||||
end
|
||||
|
||||
function test_select_text()
|
||||
io.write('\ntest_select_text')
|
||||
-- display a line of text
|
||||
App.screen.init{width=75, height=80}
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'abc def'}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.cursor1 = {line=1, pos=1}
|
||||
Editor_state.screen_top1 = {line=1, pos=1}
|
||||
Editor_state.screen_bottom1 = {}
|
||||
edit.draw(Editor_state)
|
||||
-- select a letter
|
||||
App.fake_key_press('lshift')
|
||||
edit.run_after_keychord(Editor_state, 'S-right')
|
||||
App.fake_key_release('lshift')
|
||||
edit.key_released(Editor_state, 'lshift')
|
||||
-- selection persists even after shift is released
|
||||
check_eq(Editor_state.selection1.line, 1, 'F - test_select_text/selection:line')
|
||||
check_eq(Editor_state.selection1.pos, 1, 'F - test_select_text/selection:pos')
|
||||
check_eq(Editor_state.cursor1.line, 1, 'F - test_select_text/cursor:line')
|
||||
check_eq(Editor_state.cursor1.pos, 2, 'F - test_select_text/cursor:pos')
|
||||
end
|
||||
|
||||
function test_cursor_movement_without_shift_resets_selection()
|
||||
io.write('\ntest_cursor_movement_without_shift_resets_selection')
|
||||
-- display a line of text with some part selected
|
||||
App.screen.init{width=75, height=80}
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'abc'}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.cursor1 = {line=1, pos=1}
|
||||
Editor_state.selection1 = {line=1, pos=2}
|
||||
Editor_state.screen_top1 = {line=1, pos=1}
|
||||
Editor_state.screen_bottom1 = {}
|
||||
edit.draw(Editor_state)
|
||||
-- press an arrow key without shift
|
||||
edit.run_after_keychord(Editor_state, 'right')
|
||||
-- no change to data, selection is reset
|
||||
check_nil(Editor_state.selection1.line, 'F - test_cursor_movement_without_shift_resets_selection')
|
||||
check_eq(Editor_state.lines[1].data, 'abc', 'F - test_cursor_movement_without_shift_resets_selection/data')
|
||||
end
|
||||
|
||||
function test_edit_deletes_selection()
|
||||
io.write('\ntest_edit_deletes_selection')
|
||||
-- display a line of text with some part selected
|
||||
App.screen.init{width=75, height=80}
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'abc'}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.cursor1 = {line=1, pos=1}
|
||||
Editor_state.selection1 = {line=1, pos=2}
|
||||
Editor_state.screen_top1 = {line=1, pos=1}
|
||||
Editor_state.screen_bottom1 = {}
|
||||
edit.draw(Editor_state)
|
||||
-- press a key
|
||||
edit.run_after_textinput(Editor_state, 'x')
|
||||
-- selected text is deleted and replaced with the key
|
||||
check_eq(Editor_state.lines[1].data, 'xbc', 'F - test_edit_deletes_selection')
|
||||
end
|
||||
|
||||
function test_edit_with_shift_key_deletes_selection()
|
||||
io.write('\ntest_edit_with_shift_key_deletes_selection')
|
||||
-- display a line of text with some part selected
|
||||
App.screen.init{width=75, height=80}
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'abc'}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.cursor1 = {line=1, pos=1}
|
||||
Editor_state.selection1 = {line=1, pos=2}
|
||||
Editor_state.screen_top1 = {line=1, pos=1}
|
||||
Editor_state.screen_bottom1 = {}
|
||||
edit.draw(Editor_state)
|
||||
-- mimic precise keypresses for a capital letter
|
||||
App.fake_key_press('lshift')
|
||||
edit.keychord_pressed(Editor_state, 'd', 'd')
|
||||
edit.textinput(Editor_state, 'D')
|
||||
edit.key_released(Editor_state, 'd')
|
||||
App.fake_key_release('lshift')
|
||||
-- selected text is deleted and replaced with the key
|
||||
check_nil(Editor_state.selection1.line, 'F - test_edit_with_shift_key_deletes_selection')
|
||||
check_eq(Editor_state.lines[1].data, 'Dbc', 'F - test_edit_with_shift_key_deletes_selection/data')
|
||||
end
|
||||
|
||||
function test_copy_does_not_reset_selection()
|
||||
io.write('\ntest_copy_does_not_reset_selection')
|
||||
-- display a line of text with a selection
|
||||
App.screen.init{width=75, height=80}
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'abc'}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.cursor1 = {line=1, pos=1}
|
||||
Editor_state.selection1 = {line=1, pos=2}
|
||||
Editor_state.screen_top1 = {line=1, pos=1}
|
||||
Editor_state.screen_bottom1 = {}
|
||||
edit.draw(Editor_state)
|
||||
-- copy selection
|
||||
edit.run_after_keychord(Editor_state, 'C-c')
|
||||
check_eq(App.clipboard, 'a', 'F - test_copy_does_not_reset_selection/clipboard')
|
||||
-- selection is reset since shift key is not pressed
|
||||
check(Editor_state.selection1.line, 'F - test_copy_does_not_reset_selection')
|
||||
end
|
||||
|
||||
function test_cut()
|
||||
io.write('\ntest_cut')
|
||||
-- display a line of text with some part selected
|
||||
App.screen.init{width=75, height=80}
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'abc'}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.cursor1 = {line=1, pos=1}
|
||||
Editor_state.selection1 = {line=1, pos=2}
|
||||
Editor_state.screen_top1 = {line=1, pos=1}
|
||||
Editor_state.screen_bottom1 = {}
|
||||
edit.draw(Editor_state)
|
||||
-- press a key
|
||||
edit.run_after_keychord(Editor_state, 'C-x')
|
||||
check_eq(App.clipboard, 'a', 'F - test_cut/clipboard')
|
||||
-- selected text is deleted
|
||||
check_eq(Editor_state.lines[1].data, 'bc', 'F - test_cut/data')
|
||||
end
|
||||
|
||||
function test_paste_replaces_selection()
|
||||
io.write('\ntest_paste_replaces_selection')
|
||||
-- display a line of text with a selection
|
||||
App.screen.init{width=75, height=80}
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'abc', 'def'}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.cursor1 = {line=2, pos=1}
|
||||
Editor_state.selection1 = {line=1, pos=1}
|
||||
Editor_state.screen_top1 = {line=1, pos=1}
|
||||
Editor_state.screen_bottom1 = {}
|
||||
edit.draw(Editor_state)
|
||||
-- set clipboard
|
||||
App.clipboard = 'xyz'
|
||||
-- paste selection
|
||||
edit.run_after_keychord(Editor_state, 'C-v')
|
||||
-- selection is reset since shift key is not pressed
|
||||
-- selection includes the newline, so it's also deleted
|
||||
check_eq(Editor_state.lines[1].data, 'xyzdef', 'F - test_paste_replaces_selection')
|
||||
end
|
||||
|
||||
function test_deleting_selection_may_scroll()
|
||||
io.write('\ntest_deleting_selection_may_scroll')
|
||||
-- display lines 2/3/4
|
||||
App.screen.init{width=120, height=60}
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.cursor1 = {line=3, pos=2}
|
||||
Editor_state.screen_top1 = {line=2, pos=1}
|
||||
Editor_state.screen_bottom1 = {}
|
||||
edit.draw(Editor_state)
|
||||
local y = Editor_state.top
|
||||
App.screen.check(y, 'def', 'F - test_deleting_selection_may_scroll/baseline/screen:1')
|
||||
y = y + Editor_state.line_height
|
||||
App.screen.check(y, 'ghi', 'F - test_deleting_selection_may_scroll/baseline/screen:2')
|
||||
y = y + Editor_state.line_height
|
||||
App.screen.check(y, 'jkl', 'F - test_deleting_selection_may_scroll/baseline/screen:3')
|
||||
-- set up a selection starting above the currently displayed page
|
||||
Editor_state.selection1 = {line=1, pos=2}
|
||||
-- delete selection
|
||||
edit.run_after_keychord(Editor_state, 'backspace')
|
||||
-- page scrolls up
|
||||
check_eq(Editor_state.screen_top1.line, 1, 'F - test_deleting_selection_may_scroll')
|
||||
check_eq(Editor_state.lines[1].data, 'ahi', 'F - test_deleting_selection_may_scroll/data')
|
||||
end
|
||||
|
||||
function test_edit_wrapping_text()
|
||||
io.write('\ntest_edit_wrapping_text')
|
||||
App.screen.init{width=50, height=60}
|
||||
|
@ -692,10 +865,108 @@ function test_move_cursor_using_mouse()
|
|||
Editor_state.cursor1 = {line=1, pos=1}
|
||||
Editor_state.screen_top1 = {line=1, pos=1}
|
||||
Editor_state.screen_bottom1 = {}
|
||||
Editor_state.selection1 = {}
|
||||
edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache
|
||||
edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
|
||||
edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
|
||||
check_eq(Editor_state.cursor1.line, 1, 'F - test_move_cursor_using_mouse/cursor:line')
|
||||
check_eq(Editor_state.cursor1.pos, 2, 'F - test_move_cursor_using_mouse/cursor:pos')
|
||||
check_nil(Editor_state.selection1.line, 'F - test_move_cursor_using_mouse/selection:line')
|
||||
check_nil(Editor_state.selection1.pos, 'F - test_move_cursor_using_mouse/selection:pos')
|
||||
end
|
||||
|
||||
function test_select_text_using_mouse()
|
||||
io.write('\ntest_select_text_using_mouse')
|
||||
App.screen.init{width=50, height=60}
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'abc', 'def', 'xyz'}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.cursor1 = {line=1, pos=1}
|
||||
Editor_state.screen_top1 = {line=1, pos=1}
|
||||
Editor_state.screen_bottom1 = {}
|
||||
Editor_state.selection1 = {}
|
||||
edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache
|
||||
-- press and hold on first location
|
||||
edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
|
||||
-- drag and release somewhere else
|
||||
edit.run_after_mouse_release(Editor_state, Editor_state.left+20,Editor_state.top+Editor_state.line_height+5, 1)
|
||||
check_eq(Editor_state.selection1.line, 1, 'F - test_select_text_using_mouse/selection:line')
|
||||
check_eq(Editor_state.selection1.pos, 2, 'F - test_select_text_using_mouse/selection:pos')
|
||||
check_eq(Editor_state.cursor1.line, 2, 'F - test_select_text_using_mouse/cursor:line')
|
||||
check_eq(Editor_state.cursor1.pos, 4, 'F - test_select_text_using_mouse/cursor:pos')
|
||||
end
|
||||
|
||||
function test_select_text_using_mouse_and_shift()
|
||||
io.write('\ntest_select_text_using_mouse_and_shift')
|
||||
App.screen.init{width=50, height=60}
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'abc', 'def', 'xyz'}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.cursor1 = {line=1, pos=1}
|
||||
Editor_state.screen_top1 = {line=1, pos=1}
|
||||
Editor_state.screen_bottom1 = {}
|
||||
Editor_state.selection1 = {}
|
||||
edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache
|
||||
-- click on first location
|
||||
edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
|
||||
edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
|
||||
-- hold down shift and click somewhere else
|
||||
App.fake_key_press('lshift')
|
||||
edit.run_after_mouse_press(Editor_state, Editor_state.left+20,Editor_state.top+5, 1)
|
||||
edit.run_after_mouse_release(Editor_state, Editor_state.left+20,Editor_state.top+Editor_state.line_height+5, 1)
|
||||
App.fake_key_release('lshift')
|
||||
check_eq(Editor_state.selection1.line, 1, 'F - test_select_text_using_mouse_and_shift/selection:line')
|
||||
check_eq(Editor_state.selection1.pos, 2, 'F - test_select_text_using_mouse_and_shift/selection:pos')
|
||||
check_eq(Editor_state.cursor1.line, 2, 'F - test_select_text_using_mouse_and_shift/cursor:line')
|
||||
check_eq(Editor_state.cursor1.pos, 4, 'F - test_select_text_using_mouse_and_shift/cursor:pos')
|
||||
end
|
||||
|
||||
function test_select_text_repeatedly_using_mouse_and_shift()
|
||||
io.write('\ntest_select_text_repeatedly_using_mouse_and_shift')
|
||||
App.screen.init{width=50, height=60}
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'abc', 'def', 'xyz'}
|
||||
Text.redraw_all(Editor_state)
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.cursor1 = {line=1, pos=1}
|
||||
Editor_state.screen_top1 = {line=1, pos=1}
|
||||
Editor_state.screen_bottom1 = {}
|
||||
Editor_state.selection1 = {}
|
||||
edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache
|
||||
-- click on first location
|
||||
edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
|
||||
edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
|
||||
-- hold down shift and click on a second location
|
||||
App.fake_key_press('lshift')
|
||||
edit.run_after_mouse_press(Editor_state, Editor_state.left+20,Editor_state.top+5, 1)
|
||||
edit.run_after_mouse_release(Editor_state, Editor_state.left+20,Editor_state.top+Editor_state.line_height+5, 1)
|
||||
-- hold down shift and click at a third location
|
||||
App.fake_key_press('lshift')
|
||||
edit.run_after_mouse_press(Editor_state, Editor_state.left+20,Editor_state.top+5, 1)
|
||||
edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+Editor_state.line_height+5, 1)
|
||||
App.fake_key_release('lshift')
|
||||
-- selection is between first and third location. forget the second location, not the first.
|
||||
check_eq(Editor_state.selection1.line, 1, 'F - test_select_text_repeatedly_using_mouse_and_shift/selection:line')
|
||||
check_eq(Editor_state.selection1.pos, 2, 'F - test_select_text_repeatedly_using_mouse_and_shift/selection:pos')
|
||||
check_eq(Editor_state.cursor1.line, 2, 'F - test_select_text_repeatedly_using_mouse_and_shift/cursor:line')
|
||||
check_eq(Editor_state.cursor1.pos, 2, 'F - test_select_text_repeatedly_using_mouse_and_shift/cursor:pos')
|
||||
end
|
||||
|
||||
function test_cut_without_selection()
|
||||
io.write('\ntest_cut_without_selection')
|
||||
-- display a few lines
|
||||
App.screen.init{width=Editor_state.left+30, height=60}
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.cursor1 = {line=1, pos=2}
|
||||
Editor_state.screen_top1 = {line=1, pos=1}
|
||||
Editor_state.screen_bottom1 = {}
|
||||
Editor_state.selection1 = {}
|
||||
edit.draw(Editor_state)
|
||||
-- try to cut without selecting text
|
||||
edit.run_after_keychord(Editor_state, 'C-x')
|
||||
-- no crash
|
||||
check_nil(Editor_state.selection1.line, 'F - test_cut_without_selection')
|
||||
end
|
||||
|
||||
function test_pagedown()
|
||||
|
@ -1440,7 +1711,7 @@ function test_position_cursor_on_recently_edited_wrapping_line()
|
|||
y = y + Editor_state.line_height
|
||||
App.screen.check(y, 'stu', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline2/screen:3')
|
||||
-- try to move the cursor earlier in the third screen line by clicking the mouse
|
||||
edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+Editor_state.line_height*2+5, 1)
|
||||
edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+Editor_state.line_height*2+5, 1)
|
||||
-- cursor should move
|
||||
check_eq(Editor_state.cursor1.line, 1, 'F - test_position_cursor_on_recently_edited_wrapping_line/cursor:line')
|
||||
check_eq(Editor_state.cursor1.pos, 26, 'F - test_position_cursor_on_recently_edited_wrapping_line/cursor:pos')
|
||||
|
@ -1517,6 +1788,107 @@ function test_backspace_past_line_boundary()
|
|||
check_eq(Editor_state.lines[1].data, 'abcdef', "F - test_backspace_past_line_boundary")
|
||||
end
|
||||
|
||||
-- some tests for operating over selections created using Shift- chords
|
||||
-- we're just testing delete_selection, and it works the same for all keys
|
||||
|
||||
function test_backspace_over_selection()
|
||||
io.write('\ntest_backspace_over_selection')
|
||||
-- select just one character within a line with cursor before selection
|
||||
App.screen.init{width=Editor_state.left+30, height=60}
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.cursor1 = {line=1, pos=1}
|
||||
Editor_state.selection1 = {line=1, pos=2}
|
||||
-- backspace deletes the selected character, even though it's after the cursor
|
||||
edit.run_after_keychord(Editor_state, 'backspace')
|
||||
check_eq(Editor_state.lines[1].data, 'bc', "F - test_backspace_over_selection/data")
|
||||
-- cursor (remains) at start of selection
|
||||
check_eq(Editor_state.cursor1.line, 1, "F - test_backspace_over_selection/cursor:line")
|
||||
check_eq(Editor_state.cursor1.pos, 1, "F - test_backspace_over_selection/cursor:pos")
|
||||
-- selection is cleared
|
||||
check_nil(Editor_state.selection1.line, "F - test_backspace_over_selection/selection")
|
||||
end
|
||||
|
||||
function test_backspace_over_selection_reverse()
|
||||
io.write('\ntest_backspace_over_selection_reverse')
|
||||
-- select just one character within a line with cursor after selection
|
||||
App.screen.init{width=Editor_state.left+30, height=60}
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.cursor1 = {line=1, pos=2}
|
||||
Editor_state.selection1 = {line=1, pos=1}
|
||||
-- backspace deletes the selected character
|
||||
edit.run_after_keychord(Editor_state, 'backspace')
|
||||
check_eq(Editor_state.lines[1].data, 'bc', "F - test_backspace_over_selection_reverse/data")
|
||||
-- cursor moves to start of selection
|
||||
check_eq(Editor_state.cursor1.line, 1, "F - test_backspace_over_selection_reverse/cursor:line")
|
||||
check_eq(Editor_state.cursor1.pos, 1, "F - test_backspace_over_selection_reverse/cursor:pos")
|
||||
-- selection is cleared
|
||||
check_nil(Editor_state.selection1.line, "F - test_backspace_over_selection_reverse/selection")
|
||||
end
|
||||
|
||||
function test_backspace_over_multiple_lines()
|
||||
io.write('\ntest_backspace_over_multiple_lines')
|
||||
-- select just one character within a line with cursor after selection
|
||||
App.screen.init{width=Editor_state.left+30, height=60}
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.cursor1 = {line=1, pos=2}
|
||||
Editor_state.selection1 = {line=4, pos=2}
|
||||
-- backspace deletes the region and joins the remaining portions of lines on either side
|
||||
edit.run_after_keychord(Editor_state, 'backspace')
|
||||
check_eq(Editor_state.lines[1].data, 'akl', "F - test_backspace_over_multiple_lines/data:1")
|
||||
check_eq(Editor_state.lines[2].data, 'mno', "F - test_backspace_over_multiple_lines/data:2")
|
||||
-- cursor remains at start of selection
|
||||
check_eq(Editor_state.cursor1.line, 1, "F - test_backspace_over_multiple_lines/cursor:line")
|
||||
check_eq(Editor_state.cursor1.pos, 2, "F - test_backspace_over_multiple_lines/cursor:pos")
|
||||
-- selection is cleared
|
||||
check_nil(Editor_state.selection1.line, "F - test_backspace_over_multiple_lines/selection")
|
||||
end
|
||||
|
||||
function test_backspace_to_end_of_line()
|
||||
io.write('\ntest_backspace_to_end_of_line')
|
||||
-- select region from cursor to end of line
|
||||
App.screen.init{width=Editor_state.left+30, height=60}
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.cursor1 = {line=1, pos=2}
|
||||
Editor_state.selection1 = {line=1, pos=4}
|
||||
-- backspace deletes rest of line without joining to any other line
|
||||
edit.run_after_keychord(Editor_state, 'backspace')
|
||||
check_eq(Editor_state.lines[1].data, 'a', "F - test_backspace_to_start_of_line/data:1")
|
||||
check_eq(Editor_state.lines[2].data, 'def', "F - test_backspace_to_start_of_line/data:2")
|
||||
-- cursor remains at start of selection
|
||||
check_eq(Editor_state.cursor1.line, 1, "F - test_backspace_to_start_of_line/cursor:line")
|
||||
check_eq(Editor_state.cursor1.pos, 2, "F - test_backspace_to_start_of_line/cursor:pos")
|
||||
-- selection is cleared
|
||||
check_nil(Editor_state.selection1.line, "F - test_backspace_to_start_of_line/selection")
|
||||
end
|
||||
|
||||
function test_backspace_to_start_of_line()
|
||||
io.write('\ntest_backspace_to_start_of_line')
|
||||
-- select region from cursor to start of line
|
||||
App.screen.init{width=Editor_state.left+30, height=60}
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.cursor1 = {line=2, pos=1}
|
||||
Editor_state.selection1 = {line=2, pos=3}
|
||||
-- backspace deletes beginning of line without joining to any other line
|
||||
edit.run_after_keychord(Editor_state, 'backspace')
|
||||
check_eq(Editor_state.lines[1].data, 'abc', "F - test_backspace_to_start_of_line/data:1")
|
||||
check_eq(Editor_state.lines[2].data, 'f', "F - test_backspace_to_start_of_line/data:2")
|
||||
-- cursor remains at start of selection
|
||||
check_eq(Editor_state.cursor1.line, 2, "F - test_backspace_to_start_of_line/cursor:line")
|
||||
check_eq(Editor_state.cursor1.pos, 1, "F - test_backspace_to_start_of_line/cursor:pos")
|
||||
-- selection is cleared
|
||||
check_nil(Editor_state.selection1.line, "F - test_backspace_to_start_of_line/selection")
|
||||
end
|
||||
|
||||
function test_undo_insert_text()
|
||||
io.write('\ntest_undo_insert_text')
|
||||
App.screen.init{width=120, height=60}
|
||||
|
@ -1531,6 +1903,8 @@ function test_undo_insert_text()
|
|||
edit.run_after_textinput(Editor_state, 'g')
|
||||
check_eq(Editor_state.cursor1.line, 2, 'F - test_undo_insert_text/baseline/cursor:line')
|
||||
check_eq(Editor_state.cursor1.pos, 5, 'F - test_undo_insert_text/baseline/cursor:pos')
|
||||
check_nil(Editor_state.selection1.line, 'F - test_undo_insert_text/baseline/selection:line')
|
||||
check_nil(Editor_state.selection1.pos, 'F - test_undo_insert_text/baseline/selection:pos')
|
||||
local y = Editor_state.top
|
||||
App.screen.check(y, 'abc', 'F - test_undo_insert_text/baseline/screen:1')
|
||||
y = y + Editor_state.line_height
|
||||
|
@ -1541,6 +1915,8 @@ function test_undo_insert_text()
|
|||
edit.run_after_keychord(Editor_state, 'C-z')
|
||||
check_eq(Editor_state.cursor1.line, 2, 'F - test_undo_insert_text/cursor:line')
|
||||
check_eq(Editor_state.cursor1.pos, 4, 'F - test_undo_insert_text/cursor:pos')
|
||||
check_nil(Editor_state.selection1.line, 'F - test_undo_insert_text/selection:line')
|
||||
check_nil(Editor_state.selection1.pos, 'F - test_undo_insert_text/selection:pos')
|
||||
y = Editor_state.top
|
||||
App.screen.check(y, 'abc', 'F - test_undo_insert_text/screen:1')
|
||||
y = y + Editor_state.line_height
|
||||
|
@ -1562,6 +1938,8 @@ function test_undo_delete_text()
|
|||
edit.run_after_keychord(Editor_state, 'backspace')
|
||||
check_eq(Editor_state.cursor1.line, 2, 'F - test_undo_delete_text/baseline/cursor:line')
|
||||
check_eq(Editor_state.cursor1.pos, 4, 'F - test_undo_delete_text/baseline/cursor:pos')
|
||||
check_nil(Editor_state.selection1.line, 'F - test_undo_delete_text/baseline/selection:line')
|
||||
check_nil(Editor_state.selection1.pos, 'F - test_undo_delete_text/baseline/selection:pos')
|
||||
local y = Editor_state.top
|
||||
App.screen.check(y, 'abc', 'F - test_undo_delete_text/baseline/screen:1')
|
||||
y = y + Editor_state.line_height
|
||||
|
@ -1573,6 +1951,10 @@ function test_undo_delete_text()
|
|||
edit.run_after_keychord(Editor_state, 'C-z')
|
||||
check_eq(Editor_state.cursor1.line, 2, 'F - test_undo_delete_text/cursor:line')
|
||||
check_eq(Editor_state.cursor1.pos, 5, 'F - test_undo_delete_text/cursor:pos')
|
||||
check_nil(Editor_state.selection1.line, 'F - test_undo_delete_text/selection:line')
|
||||
check_nil(Editor_state.selection1.pos, 'F - test_undo_delete_text/selection:pos')
|
||||
--? check_eq(Editor_state.selection1.line, 2, 'F - test_undo_delete_text/selection:line')
|
||||
--? check_eq(Editor_state.selection1.pos, 4, 'F - test_undo_delete_text/selection:pos')
|
||||
y = Editor_state.top
|
||||
App.screen.check(y, 'abc', 'F - test_undo_delete_text/screen:1')
|
||||
y = y + Editor_state.line_height
|
||||
|
@ -1581,6 +1963,30 @@ function test_undo_delete_text()
|
|||
App.screen.check(y, 'xyz', 'F - test_undo_delete_text/screen:3')
|
||||
end
|
||||
|
||||
function test_undo_restores_selection()
|
||||
io.write('\ntest_undo_restores_selection')
|
||||
-- display a line of text with some part selected
|
||||
App.screen.init{width=75, height=80}
|
||||
Editor_state = edit.initialize_test_state()
|
||||
Editor_state.lines = load_array{'abc'}
|
||||
Text.redraw_all(Editor_state)
|
||||
Editor_state.cursor1 = {line=1, pos=1}
|
||||
Editor_state.selection1 = {line=1, pos=2}
|
||||
Editor_state.screen_top1 = {line=1, pos=1}
|
||||
Editor_state.screen_bottom1 = {}
|
||||
edit.draw(Editor_state)
|
||||
-- delete selected text
|
||||
edit.run_after_textinput(Editor_state, 'x')
|
||||
check_eq(Editor_state.lines[1].data, 'xbc', 'F - test_undo_restores_selection/baseline')
|
||||
check_nil(Editor_state.selection1.line, 'F - test_undo_restores_selection/baseline:selection')
|
||||
-- undo
|
||||
edit.run_after_keychord(Editor_state, 'C-z')
|
||||
edit.run_after_keychord(Editor_state, 'C-z')
|
||||
-- selection is restored
|
||||
check_eq(Editor_state.selection1.line, 1, 'F - test_undo_restores_selection/line')
|
||||
check_eq(Editor_state.selection1.pos, 2, 'F - test_undo_restores_selection/pos')
|
||||
end
|
||||
|
||||
function test_search()
|
||||
io.write('\ntest_search')
|
||||
App.screen.init{width=120, height=60}
|
||||
|
|
1
text.lua
1
text.lua
|
@ -931,7 +931,6 @@ end
|
|||
|
||||
-- resize helper
|
||||
function Text.tweak_screen_top_and_cursor(State)
|
||||
--? print('a', State.selection1.line)
|
||||
if State.screen_top1.pos == 1 then return end
|
||||
Text.populate_screen_line_starting_pos(State, State.screen_top1.line)
|
||||
local line = State.lines[State.screen_top1.line]
|
||||
|
|
Loading…
Reference in New Issue