bring back everything from commit a68647ae22
We only want to unwind the source editing.
This commit is contained in:
parent
ee18ce96a1
commit
b64a60a509
|
@ -3,6 +3,9 @@ program before it ever runs. However, some things don't have tests yet, either
|
|||
because I don't know how to test them or because I've been lazy. I'll at least
|
||||
record those here.
|
||||
|
||||
Startup:
|
||||
- terminal log shows unit tests running
|
||||
|
||||
* Initializing settings:
|
||||
- from previous session
|
||||
- Filename as absolute path
|
||||
|
|
42
app.lua
42
app.lua
|
@ -11,6 +11,7 @@
|
|||
--
|
||||
-- Scroll below this function for more details.
|
||||
function love.run()
|
||||
App.snapshot_love()
|
||||
-- Tests always run at the start.
|
||||
App.run_tests()
|
||||
|
||||
|
@ -123,6 +124,18 @@ end
|
|||
|
||||
App = {screen={}}
|
||||
|
||||
-- save/restore various framework globals we care about -- only on very first load
|
||||
function App.snapshot_love()
|
||||
if Love_snapshot then return end
|
||||
Love_snapshot = {}
|
||||
-- save the entire initial font; it doesn't seem reliably recreated using newFont
|
||||
Love_snapshot.initial_font = love.graphics.getFont()
|
||||
end
|
||||
|
||||
function App.undo_initialize()
|
||||
love.graphics.setFont(Love_snapshot.initial_font)
|
||||
end
|
||||
|
||||
function App.initialize_for_test()
|
||||
App.screen.init({width=100, height=50})
|
||||
App.screen.contents = {} -- clear screen
|
||||
|
@ -137,6 +150,27 @@ function App.screen.init(dims)
|
|||
App.screen.height = dims.height
|
||||
end
|
||||
|
||||
-- operations on the LÖVE window within the monitor/display
|
||||
function App.screen.resize(width, height, flags)
|
||||
App.screen.width = width
|
||||
App.screen.height = height
|
||||
App.screen.flags = flags
|
||||
end
|
||||
|
||||
function App.screen.size()
|
||||
return App.screen.width, App.screen.height, App.screen.flags
|
||||
end
|
||||
|
||||
function App.screen.move(x,y, displayindex)
|
||||
App.screen.x = x
|
||||
App.screen.y = y
|
||||
App.screen.displayindex = displayindex
|
||||
end
|
||||
|
||||
function App.screen.position()
|
||||
return App.screen.x, App.screen.y, App.screen.displayindex
|
||||
end
|
||||
|
||||
function App.screen.print(msg, x,y)
|
||||
local screen_row = 'y'..tostring(y)
|
||||
--? print('drawing "'..msg..'" at y '..tostring(y))
|
||||
|
@ -156,6 +190,10 @@ function App.color(color)
|
|||
love.graphics.setColor(color.r, color.g, color.b, color.a)
|
||||
end
|
||||
|
||||
function colortable(app_color)
|
||||
return {app_color.r, app_color.g, app_color.b, app_color.a}
|
||||
end
|
||||
|
||||
App.time = 1
|
||||
function App.getTime()
|
||||
return App.time
|
||||
|
@ -362,6 +400,10 @@ function App.disable_tests()
|
|||
App.fake_mouse_press = nil
|
||||
App.fake_mouse_release = nil
|
||||
-- other methods dispatch to real hardware
|
||||
App.screen.resize = love.window.setMode
|
||||
App.screen.size = love.window.getMode
|
||||
App.screen.move = love.window.setPosition
|
||||
App.screen.position = love.window.getPosition
|
||||
App.screen.print = love.graphics.print
|
||||
App.newText = love.graphics.newText
|
||||
App.screen.draw = love.graphics.draw
|
||||
|
|
14
edit.lua
14
edit.lua
|
@ -64,7 +64,7 @@ function edit.initialize_state(top, left, right, font_height, line_height) -- c
|
|||
right = right,
|
||||
width = right-left,
|
||||
|
||||
filename = love.filesystem.getUserDirectory()..'/lines.txt',
|
||||
filename = love.filesystem.getUserDirectory()..'/lines.txt', -- '/' should work even on Windows
|
||||
next_save = nil,
|
||||
|
||||
-- undo
|
||||
|
@ -94,7 +94,7 @@ function edit.draw(State)
|
|||
local line = State.lines[line_index]
|
||||
--? print('draw:', y, line_index, line)
|
||||
if y + State.line_height > App.screen.height then break end
|
||||
State.screen_bottom1.line = line_index
|
||||
State.screen_bottom1 = {line=line_index, pos=nil}
|
||||
--? print('text.draw', y, line_index)
|
||||
local startpos = 1
|
||||
if line_index == State.screen_top1.line then
|
||||
|
@ -231,7 +231,10 @@ function edit.keychord_pressed(State, chord, key)
|
|||
return
|
||||
elseif chord == 'C-f' then
|
||||
State.search_term = ''
|
||||
State.search_backup = {cursor={line=State.cursor1.line, pos=State.cursor1.pos}, screen_top={line=State.screen_top1.line, pos=State.screen_top1.pos}}
|
||||
State.search_backup = {
|
||||
cursor={line=State.cursor1.line, pos=State.cursor1.pos},
|
||||
screen_top={line=State.screen_top1.line, pos=State.screen_top1.pos},
|
||||
}
|
||||
assert(State.search_text == nil)
|
||||
-- zoom
|
||||
elseif chord == 'C-=' then
|
||||
|
@ -324,14 +327,17 @@ end
|
|||
|
||||
--== some methods for tests
|
||||
|
||||
-- Insulate tests from some key globals so I don't have to change the vast
|
||||
-- majority of tests when they're modified for the real app.
|
||||
Test_margin_left = 25
|
||||
Test_margin_right = 0
|
||||
|
||||
function edit.initialize_test_state()
|
||||
-- if you change these values, tests will start failing
|
||||
return edit.initialize_state(
|
||||
15, -- top margin
|
||||
Test_margin_left,
|
||||
App.screen.width, -- right margin = 0
|
||||
App.screen.width - Test_margin_right,
|
||||
14, -- font height assuming default LÖVE font
|
||||
15) -- line height
|
||||
end
|
||||
|
|
21
file.lua
21
file.lua
|
@ -37,7 +37,8 @@ function save_to_disk(State)
|
|||
error('failed to write to "'..State.filename..'"')
|
||||
end
|
||||
for _,line in ipairs(State.lines) do
|
||||
outfile:write(line.data, '\n')
|
||||
outfile:write(line.data)
|
||||
outfile:write('\n')
|
||||
end
|
||||
outfile:close()
|
||||
end
|
||||
|
@ -57,3 +58,21 @@ function load_array(a)
|
|||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function is_absolute_path(path)
|
||||
local os_path_separator = package.config:sub(1,1)
|
||||
if os_path_separator == '/' then
|
||||
-- POSIX systems permit backslashes in filenames
|
||||
return path:sub(1,1) == '/'
|
||||
elseif os_path_separator == '\\' then
|
||||
if path:sub(2,2) == ':' then return true end -- DOS drive letter followed by volume separator
|
||||
local f = path:sub(1,1)
|
||||
return f == '/' or f == '\\'
|
||||
else
|
||||
error('What OS is this? LÖVE reports that the path separator is "'..os_path_separator..'"')
|
||||
end
|
||||
end
|
||||
|
||||
function is_relative_path(path)
|
||||
return not is_absolute_path(path)
|
||||
end
|
||||
|
|
14
keychord.lua
14
keychord.lua
|
@ -56,9 +56,17 @@ end
|
|||
array = {}
|
||||
|
||||
function array.find(arr, elem)
|
||||
for i,x in ipairs(arr) do
|
||||
if x == elem then
|
||||
return i
|
||||
if type(elem) == 'function' then
|
||||
for i,x in ipairs(arr) do
|
||||
if elem(x) then
|
||||
return i
|
||||
end
|
||||
end
|
||||
else
|
||||
for i,x in ipairs(arr) do
|
||||
if x == elem then
|
||||
return i
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
|
|
4
main.lua
4
main.lua
|
@ -70,12 +70,12 @@ function load_settings()
|
|||
-- maximize window to determine maximum allowable dimensions
|
||||
App.screen.width, App.screen.height, App.screen.flags = love.window.getMode()
|
||||
-- set up desired window dimensions
|
||||
love.window.setPosition(settings.x, settings.y, settings.displayindex)
|
||||
App.screen.flags.resizable = true
|
||||
App.screen.flags.minwidth = math.min(App.screen.width, 200)
|
||||
App.screen.flags.minheight = math.min(App.screen.width, 200)
|
||||
App.screen.width, App.screen.height = settings.width, settings.height
|
||||
love.window.setMode(App.screen.width, App.screen.height, App.screen.flags)
|
||||
love.window.setPosition(settings.x, settings.y, settings.displayindex)
|
||||
Editor_state = edit.initialize_state(Margin_top, Margin_left, App.screen.width-Margin_right, settings.font_height, math.floor(settings.font_height*1.3))
|
||||
Editor_state.filename = settings.filename
|
||||
Editor_state.screen_top1 = settings.screen_top
|
||||
|
@ -151,7 +151,7 @@ function love.quit()
|
|||
-- save some important settings
|
||||
local x,y,displayindex = love.window.getPosition()
|
||||
local filename = Editor_state.filename
|
||||
if filename:sub(1,1) ~= '/' then
|
||||
if is_relative_path(filename) then
|
||||
filename = love.filesystem.getWorkingDirectory()..'/'..filename -- '/' should work even on Windows
|
||||
end
|
||||
local settings = {
|
||||
|
|
|
@ -6,13 +6,14 @@ function test_resize_window()
|
|||
check_eq(App.screen.width, 300, 'F - test_resize_window/baseline/width')
|
||||
check_eq(App.screen.height, 300, 'F - test_resize_window/baseline/height')
|
||||
check_eq(Editor_state.left, Test_margin_left, 'F - test_resize_window/baseline/left_margin')
|
||||
check_eq(Editor_state.right, 300 - Test_margin_right, 'F - test_resize_window/baseline/left_margin')
|
||||
App.resize(200, 400)
|
||||
-- ugly; resize switches to real, non-test margins
|
||||
check_eq(App.screen.width, 200, 'F - test_resize_window/width')
|
||||
check_eq(App.screen.height, 400, 'F - test_resize_window/height')
|
||||
check_eq(Editor_state.left, Test_margin_left, 'F - test_resize_window/left_margin')
|
||||
-- ugly; right margin switches from 0 after resize
|
||||
check_eq(Editor_state.left, Margin_left, 'F - test_resize_window/left_margin')
|
||||
check_eq(Editor_state.right, 200-Margin_right, 'F - test_resize_window/right_margin')
|
||||
check_eq(Editor_state.width, 200-Test_margin_left-Margin_right, 'F - test_resize_window/drawing_width')
|
||||
check_eq(Editor_state.width, 200-Margin_left-Margin_right, 'F - test_resize_window/drawing_width')
|
||||
-- TODO: how to make assertions about when App.update got past the early exit?
|
||||
end
|
||||
|
||||
|
|
36
search.lua
36
search.lua
|
@ -21,17 +21,16 @@ end
|
|||
|
||||
function Text.search_next(State)
|
||||
-- search current line from cursor
|
||||
local pos = find(State.lines[State.cursor1.line].data, State.search_term, State.cursor1.pos)
|
||||
local pos = find(State.lines[State.cursor1.line].data, State.search_term, State.cursor1.pos, --[[literal]] true)
|
||||
if pos then
|
||||
State.cursor1.pos = pos
|
||||
end
|
||||
if pos == nil then
|
||||
-- search lines below cursor
|
||||
for i=State.cursor1.line+1,#State.lines do
|
||||
pos = find(State.lines[i].data, State.search_term)
|
||||
pos = find(State.lines[i].data, State.search_term, --[[from start]] nil, --[[literal]] true)
|
||||
if pos then
|
||||
State.cursor1.line = i
|
||||
State.cursor1.pos = pos
|
||||
State.cursor1 = {line=i, pos=pos}
|
||||
break
|
||||
end
|
||||
end
|
||||
|
@ -39,17 +38,16 @@ function Text.search_next(State)
|
|||
if pos == nil then
|
||||
-- wrap around
|
||||
for i=1,State.cursor1.line-1 do
|
||||
pos = find(State.lines[i].data, State.search_term)
|
||||
pos = find(State.lines[i].data, State.search_term, --[[from start]] nil, --[[literal]] true)
|
||||
if pos then
|
||||
State.cursor1.line = i
|
||||
State.cursor1.pos = pos
|
||||
State.cursor1 = {line=i, pos=pos}
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if pos == nil then
|
||||
-- search current line until cursor
|
||||
pos = find(State.lines[State.cursor1.line].data, State.search_term)
|
||||
pos = find(State.lines[State.cursor1.line].data, State.search_term, --[[from start]] nil, --[[literal]] true)
|
||||
if pos and pos < State.cursor1.pos then
|
||||
State.cursor1.pos = pos
|
||||
end
|
||||
|
@ -69,17 +67,16 @@ end
|
|||
|
||||
function Text.search_previous(State)
|
||||
-- search current line before cursor
|
||||
local pos = rfind(State.lines[State.cursor1.line].data, State.search_term, State.cursor1.pos-1)
|
||||
local pos = rfind(State.lines[State.cursor1.line].data, State.search_term, State.cursor1.pos-1, --[[literal]] true)
|
||||
if pos then
|
||||
State.cursor1.pos = pos
|
||||
end
|
||||
if pos == nil then
|
||||
-- search lines above cursor
|
||||
for i=State.cursor1.line-1,1,-1 do
|
||||
pos = rfind(State.lines[i].data, State.search_term)
|
||||
pos = rfind(State.lines[i].data, State.search_term, --[[from end]] nil, --[[literal]] true)
|
||||
if pos then
|
||||
State.cursor1.line = i
|
||||
State.cursor1.pos = pos
|
||||
State.cursor1 = {line=i, pos=pos}
|
||||
break
|
||||
end
|
||||
end
|
||||
|
@ -87,17 +84,16 @@ function Text.search_previous(State)
|
|||
if pos == nil then
|
||||
-- wrap around
|
||||
for i=#State.lines,State.cursor1.line+1,-1 do
|
||||
pos = rfind(State.lines[i].data, State.search_term)
|
||||
pos = rfind(State.lines[i].data, State.search_term, --[[from end]] nil, --[[literal]] true)
|
||||
if pos then
|
||||
State.cursor1.line = i
|
||||
State.cursor1.pos = pos
|
||||
State.cursor1 = {line=i, pos=pos}
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if pos == nil then
|
||||
-- search current line after cursor
|
||||
pos = rfind(State.lines[State.cursor1.line].data, State.search_term)
|
||||
pos = rfind(State.lines[State.cursor1.line].data, State.search_term, --[[from end]] nil, --[[literal]] true)
|
||||
if pos and pos > State.cursor1.pos then
|
||||
State.cursor1.pos = pos
|
||||
end
|
||||
|
@ -115,18 +111,18 @@ function Text.search_previous(State)
|
|||
end
|
||||
end
|
||||
|
||||
function find(s, pat, i)
|
||||
function find(s, pat, i, plain)
|
||||
if s == nil then return end
|
||||
return s:find(pat, i)
|
||||
return s:find(pat, i, plain)
|
||||
end
|
||||
|
||||
function rfind(s, pat, i)
|
||||
function rfind(s, pat, i, plain)
|
||||
if s == nil then return end
|
||||
local rs = s:reverse()
|
||||
local rpat = pat:reverse()
|
||||
if i == nil then i = #s end
|
||||
local ri = #s - i + 1
|
||||
local rendpos = rs:find(rpat, ri)
|
||||
local rendpos = rs:find(rpat, ri, plain)
|
||||
if rendpos == nil then return nil end
|
||||
local endpos = #s - rendpos + 1
|
||||
assert (endpos >= #pat)
|
||||
|
|
22
text.lua
22
text.lua
|
@ -118,7 +118,7 @@ function Text.compute_fragments(State, line_index)
|
|||
for frag in line.data:gmatch('%S*%s*') do
|
||||
local frag_text = App.newText(love.graphics.getFont(), frag)
|
||||
local frag_width = App.width(frag_text)
|
||||
--? print('x: '..tostring(x)..'; '..tostring(State.right-x)..'px to go')
|
||||
--? print('x: '..tostring(x)..'; frag_width: '..tostring(frag_width)..'; '..tostring(State.right-x)..'px to go')
|
||||
while x + frag_width > State.right do
|
||||
--? print(('checking whether to split fragment ^%s$ of width %d when rendering from %d'):format(frag, frag_width, x))
|
||||
if (x-State.left) < 0.8 * (State.right-State.left) then
|
||||
|
@ -361,8 +361,7 @@ function Text.insert_return(State)
|
|||
table.insert(State.line_cache, State.cursor1.line+1, {})
|
||||
State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_offset-1)
|
||||
Text.clear_screen_line_cache(State, State.cursor1.line)
|
||||
State.cursor1.line = State.cursor1.line+1
|
||||
State.cursor1.pos = 1
|
||||
State.cursor1 = {line=State.cursor1.line+1, pos=1}
|
||||
end
|
||||
|
||||
function Text.pageup(State)
|
||||
|
@ -378,8 +377,7 @@ function Text.pageup(State)
|
|||
top2 = Text.previous_screen_line(State, top2)
|
||||
end
|
||||
State.screen_top1 = Text.to1(State, top2)
|
||||
State.cursor1.line = State.screen_top1.line
|
||||
State.cursor1.pos = State.screen_top1.pos
|
||||
State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}
|
||||
Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State)
|
||||
--? print(State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos)
|
||||
--? print('pageup end')
|
||||
|
@ -398,12 +396,10 @@ function Text.pagedown(State)
|
|||
if Text.lt1(State.screen_top1, new_top1) then
|
||||
State.screen_top1 = new_top1
|
||||
else
|
||||
State.screen_top1.line = State.screen_bottom1.line
|
||||
State.screen_top1.pos = State.screen_bottom1.pos
|
||||
State.screen_top1 = {line=State.screen_bottom1.line, pos=State.screen_bottom1.pos}
|
||||
end
|
||||
--? print('setting top to', State.screen_top1.line, State.screen_top1.pos)
|
||||
State.cursor1.line = State.screen_top1.line
|
||||
State.cursor1.pos = State.screen_top1.pos
|
||||
State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}
|
||||
Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State)
|
||||
--? print('top now', State.screen_top1.line)
|
||||
Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks
|
||||
|
@ -468,6 +464,7 @@ function Text.down(State)
|
|||
local scroll_down = Text.le1(State.screen_bottom1, State.cursor1)
|
||||
--? print('cursor is NOT at final screen line of its line')
|
||||
local screen_line_starting_pos, screen_line_index = Text.pos_at_start_of_screen_line(State, State.cursor1)
|
||||
Text.populate_screen_line_starting_pos(State, State.cursor1.line)
|
||||
local new_screen_line_starting_pos = State.line_cache[State.cursor1.line].screen_line_starting_pos[screen_line_index+1]
|
||||
--? print('switching pos of screen line at cursor from '..tostring(screen_line_starting_pos)..' to '..tostring(new_screen_line_starting_pos))
|
||||
local new_screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line].data, new_screen_line_starting_pos)
|
||||
|
@ -613,8 +610,12 @@ end
|
|||
|
||||
-- should never modify State.cursor1
|
||||
function Text.snap_cursor_to_bottom_of_screen(State)
|
||||
--? print('to2:', State.cursor1.line, State.cursor1.pos)
|
||||
local top2 = Text.to2(State, State.cursor1)
|
||||
--? print('to2: =>', top2.line, top2.screen_line, top2.screen_pos)
|
||||
-- slide to start of screen line
|
||||
top2.screen_pos = 1 -- start of screen line
|
||||
--? print('snap', State.screen_top1.line, State.screen_top1.pos, State.screen_top1.posB, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)
|
||||
--? print('cursor pos '..tostring(State.cursor1.pos)..' is on the #'..tostring(top2.screen_line)..' screen line down')
|
||||
local y = App.screen.height - State.line_height
|
||||
-- duplicate some logic from love.draw
|
||||
|
@ -631,6 +632,7 @@ function Text.snap_cursor_to_bottom_of_screen(State)
|
|||
--? print('top2 finally:', top2.line, top2.screen_line, top2.screen_pos)
|
||||
State.screen_top1 = Text.to1(State, top2)
|
||||
--? print('top1 finally:', State.screen_top1.line, State.screen_top1.pos)
|
||||
--? print('snap =>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)
|
||||
Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks
|
||||
end
|
||||
|
||||
|
@ -787,7 +789,7 @@ function Text.x(s, pos)
|
|||
end
|
||||
|
||||
function Text.to2(State, loc1)
|
||||
local result = {line=loc1.line, screen_line=1}
|
||||
local result = {line=loc1.line}
|
||||
local line_cache = State.line_cache[loc1.line]
|
||||
Text.populate_screen_line_starting_pos(State, loc1.line)
|
||||
for i=#line_cache.screen_line_starting_pos,1,-1 do
|
||||
|
|
Loading…
Reference in New Issue