From 9501f01ca0a8ddcf54e12584f79833c884fde939 Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Sun, 9 Jun 2024 13:17:55 -0700 Subject: [PATCH 1/9] fix a crash involving mouse and drawings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks Alex Schroeder for reporting this crash. The scenario: * Edit a file like say this repo's Readme. * The second line is empty and there's a '+' to insert a drawing. Click on that. * Resize the window so just the first line of text and the drawing are visible. * Close the window. * Reopen lines.love, it will reopen the same file. * Click on the left margin to the left of the drawing. Before this commit these steps yielded the following crash: Error: bad argument #1 to 'len' (string expected, got nil) text.lua:626: in function 'pos_at_end_of_screen_line' edit.lua:298: in function 'mouse_press' There were two distinct problems here: 1. State.screen_bottom1 is not required to point to a text line, it could just as well be a drawing. I have been sloppy in handling that. 2. The bug was partially masked (the need to close and reopen the window) by a second bug: inserting a drawing was not invalidating the cache I save of starty coordinates for each line. (I've inserted and deleted starty invalidations a few times in the past, but it looks like I'd never had one in this particular location edit.draw before.) How did these issues get missed for years? - Even though I use lines.love on a daily basis, it turns out I don't actually create line drawings all that often. - When I do, I'm still living in files that are mostly text with only an occasional drawing. - I keep my windows fairly large. Between these 3 patterns, the odds of running into a drawing as the first or bottom-most line on the screen were fairly small. And then I had to interact with it. I suspect I tend to interact with drawings after centering them vertically. --- Bug #1 in particular has some interesting past history. * Near the start of the project, when I implemented line-wrapping I started saving screen_bottom, the bottom-most line displayed on screen. I did this so I could scroll down easily just by assigning `screen_top = screen_bottom`. (On the other hand, scrolling up still required some work. I should perhaps get rid of it and just compute scrolls from scratch each time.) * Also near the start of the project, I supported selecting text by a complex state machine spanning keypress, mouse press and mouse release: mouse click (press and immediate release) moves cursor mouse drag (press and much later release) creates selection shift-click selects from current cursor to click location shift-movement creates/grows a selection * On 2023-06-01, inscript reported a bug. Opening a window with just a little bit of text (lots of unused space in the window), selecting all the text and then clicking below all the text would crash the editor. To fix this I added code at the bottom of edit.mouse_press which computed the final visible line+pos location and used that in the cursor-move/text-selection state machine. It did this computation based on.. screen_bottom. But I didn't notice that screen_bottom could be a drawing (which has no pos). This commit's bug/regression was created. * On 2023-09-20, Matt Wynne encountered a crash which got me to realize I need code at the bottom of edit.mouse_release symmetric to the code at the bottom of edit.mouse_press. I still didn't notice that screen_bottom could be a drawing. So in fixing inscript's bug report, I introduced (at least) 2 regressions, because I either had no idea or quickly forgot that screen_bottom could point at a drawing. While I created regressions, the underlying mental bug feels new. I just never focused on the fact that screen_bottom could point at a drawing. This past history makes me suspicious of my mouse_press/mouse_release code. I think I'm going to get rid of screen_bottom entirely as a concept. I'll still have to be careful though about the remaining locations and which of them are allowed to point at drawings: - cursor and selection are not allowed to point at drawings - screen_top and screen_bottom are allowed to point at drawings I sometimes copy between these 4 location variables. Auditing shows no gaps where cursor could ever end up pointing at a drawing. It's just when I started using screen_bottom for a whole new purpose (in the mouse_press/release state machine) that I went wrong. I should also try getting rid of starty entirely. Is it _really_ needed for a responsive editor? I think I introduced it back when I didn't know what I was doing with LÖVE and was profligately creating text objects willy-nilly just to compute widths. Getting rid of these two fairly global bits of mutable state will hopefully make lines much more robust when the next person tries it out in 6 months :-/ X-( Thanks everyone for the conversation around this bug: https://merveilles.town/@akkartik/112567862542495637 --- Bug #2 has some complexity as well, and might lead to some follow-on cleanup. When I click on the button to insert a new drawing, the mouse_release hook triggers and moves the cursor below the new drawing. This is desirable, but I'd never noticed this happy accident. It stops working when I invalidate starty for all lines (which gets recomputed and cached for all visible lines on every frame). Fixing this caused a couple of unit tests start crashing for 2 reasons that required their own minor fixes: - My emulated mouse press and release didn't have an intervening frame and so mouse_release no longer receives starty. Now I've added a call to edit.draw() between press and release. This might actually bite someone for real someday, if they're running on a slow computer or something like that. I've tried to click really fast but I can't seem to put mouse_press and release in the same frame (assuming 30 frames per second) - My tests' window dimensions often violate my constraint that the screen always have one line of text for showing the cursor. They're unrealistically small or have a really wide aspect ratio (width 2x of height). I suspect lines.love will itself crash in those situations, but hopefully they're unrealistic. Hmm, I wonder what would happen if someone maximized in a 16:9 screen, that's almost 2x.. Anyways, I've cleaned a couple of tests up, but might need to fix up others at some point. I'd have to rejigger all my brittle line-wrapping tests if I modify the screen width :-/ X-( --- README.md | 5 +++++ drawing_tests.lua | 2 +- edit.lua | 13 +++++++------ text.lua | 19 +++++++++++++++++++ text_tests.lua | 2 +- 5 files changed, 33 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 1a27ac7..690d94c 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,11 @@ found anything amiss: http://akkartik.name/contact * No clipping yet for drawings. In particular, circles/squares/rectangles and point labels can overflow a drawing. +* If you ever see a crash when clicking on the mouse, it might be because a + mouse press and release need to happen in separate frames. Try pressing and + releasing more slowly and let me know if that helps or not. This is klunky, + sorry. + * Touchpads can drag the mouse pointer using a light touch or a heavy click. On Linux, drags using the light touch get interrupted when a key is pressed. You'll have to press down to drag. diff --git a/drawing_tests.lua b/drawing_tests.lua index ede25f0..64188df 100644 --- a/drawing_tests.lua +++ b/drawing_tests.lua @@ -3,7 +3,7 @@ -- of specific shapes. In particular, no tests of freehand drawings. function test_creating_drawing_saves() - App.screen.init{width=120, height=60} + App.screen.init{width=800, height=600} Editor_state = edit.initialize_test_state() Editor_state.filename = 'foo' Editor_state.lines = load_array{} diff --git a/edit.lua b/edit.lua index b096274..42309df 100644 --- a/edit.lua +++ b/edit.lua @@ -66,8 +66,10 @@ function edit.initialize_state(top, left, right, font, font_height, line_height) -- -- Make sure these coordinates are never aliased, so that changing one causes -- action at a distance. + -- + -- On lines that are drawings, pos will be nil. screen_top1 = {line=1, pos=1}, -- position of start of screen line at top of screen - cursor1 = {line=1, pos=1}, -- position of cursor + cursor1 = {line=1, pos=1}, -- position of cursor; must be on a text line screen_bottom1 = {line=1, pos=1}, -- position of start of screen line at bottom of screen selection1 = {}, @@ -185,6 +187,7 @@ function edit.draw(State) Drawing.before = snapshot(State, line_index-1, line_index) table.insert(State.lines, line_index, {mode='drawing', y=y, h=256/2, points={}, shapes={}, pending={}}) table.insert(State.line_cache, line_index, {}) + for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end if State.cursor1.line >= line_index then State.cursor1.line = State.cursor1.line+1 end @@ -292,10 +295,7 @@ function edit.mouse_press(State, x,y, mouse_button) State.old_cursor1 = State.cursor1 State.old_selection1 = State.selection1 State.mousepress_shift = App.shift_down() - State.selection1 = { - line=State.screen_bottom1.line, - pos=Text.pos_at_end_of_screen_line(State, State.screen_bottom1), - } + State.selection1 = Text.final_text_loc_on_screen(State) end function edit.mouse_release(State, x,y, mouse_button) @@ -333,7 +333,7 @@ function edit.mouse_release(State, x,y, mouse_button) end -- still here? mouse release is below all screen lines - State.cursor1.line, State.cursor1.pos = State.screen_bottom1.line, Text.pos_at_end_of_screen_line(State, State.screen_bottom1) + State.cursor1 = Text.final_text_loc_on_screen(State) edit.clean_up_mouse_press(State) --? print_and_log(('edit.mouse_release: finally selection %s,%s cursor %d,%d'):format(tostring(State.selection1.line), tostring(State.selection1.pos), State.cursor1.line, State.cursor1.pos)) end @@ -596,6 +596,7 @@ end function edit.run_after_mouse_click(State, x,y, mouse_button) App.fake_mouse_press(x,y, mouse_button) edit.mouse_press(State, x,y, mouse_button) + edit.draw(State) App.fake_mouse_release(x,y, mouse_button) edit.mouse_release(State, x,y, mouse_button) App.screen.contents = {} diff --git a/text.lua b/text.lua index 9c27bde..d9e7653 100644 --- a/text.lua +++ b/text.lua @@ -621,6 +621,7 @@ function Text.pos_at_start_of_screen_line(State, loc1) end function Text.pos_at_end_of_screen_line(State, loc1) + assert(State.lines[loc1.line].mode == 'text') Text.populate_screen_line_starting_pos(State, loc1.line) local line_cache = State.line_cache[loc1.line] local most_recent_final_pos = utf8.len(State.lines[loc1.line].data)+1 @@ -634,6 +635,24 @@ function Text.pos_at_end_of_screen_line(State, loc1) assert(false, ('invalid pos %d'):format(loc1.pos)) end +function Text.final_text_loc_on_screen(State) + if State.lines[State.screen_bottom1.line].mode == 'text' then + return { + line=State.screen_bottom1.line, + pos=Text.pos_at_end_of_screen_line(State, State.screen_bottom1), + } + end + local loc2 = Text.to2(State, State.screen_bottom1) + while true do + if State.lines[loc2.line].mode == 'text' then break end + assert(loc2.line > 1 or loc2.screen_line > 1 and loc2.screen_pos > 1) -- elsewhere we're making sure there's always at least one text line on screen + loc2 = Text.previous_screen_line(State, loc2) + end + local result = Text.to1(State, loc2) + result.pos = Text.pos_at_end_of_screen_line(State, result) + return result +end + function Text.cursor_at_final_screen_line(State) Text.populate_screen_line_starting_pos(State, State.cursor1.line) local screen_lines = State.line_cache[State.cursor1.line].screen_line_starting_pos diff --git a/text_tests.lua b/text_tests.lua index b8f89db..44cdafc 100644 --- a/text_tests.lua +++ b/text_tests.lua @@ -16,7 +16,7 @@ function test_initial_state() end function test_click_to_create_drawing() - App.screen.init{width=120, height=60} + App.screen.init{width=800, height=600} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{} Text.redraw_all(Editor_state) From 9b5a78d3c507a616a59e2933b061603b1e421695 Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Sun, 9 Jun 2024 20:35:50 -0700 Subject: [PATCH 2/9] bugfix in source editor --- source_edit.lua | 13 +++++++------ source_text.lua | 19 +++++++++++++++++++ source_text_tests.lua | 2 +- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/source_edit.lua b/source_edit.lua index e376537..1e8259c 100644 --- a/source_edit.lua +++ b/source_edit.lua @@ -68,8 +68,10 @@ function edit.initialize_state(top, left, right, font, font_height, line_height) -- -- Make sure these coordinates are never aliased, so that changing one causes -- action at a distance. + -- + -- On lines that are drawings, pos will be nil. screen_top1 = {line=1, pos=1}, -- position of start of screen line at top of screen - cursor1 = {line=1, pos=1}, -- position of cursor + cursor1 = {line=1, pos=1}, -- position of cursor; must be on a text line screen_bottom1 = {line=1, pos=1}, -- position of start of screen line at bottom of screen selection1 = {}, @@ -190,6 +192,7 @@ function edit.draw(State, hide_cursor, show_line_numbers) Drawing.before = snapshot(State, line_index-1, line_index) table.insert(State.lines, line_index, {mode='drawing', y=y, h=256/2, points={}, shapes={}, pending={}}) table.insert(State.line_cache, line_index, {}) + for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end if State.cursor1.line >= line_index then State.cursor1.line = State.cursor1.line+1 end @@ -296,10 +299,7 @@ function edit.mouse_press(State, x,y, mouse_button) State.old_cursor1 = State.cursor1 State.old_selection1 = State.selection1 State.mousepress_shift = App.shift_down() - State.selection1 = { - line=State.screen_bottom1.line, - pos=Text.pos_at_end_of_screen_line(State, State.screen_bottom1), - } + State.selection1 = Text.final_text_loc_on_screen(State) end function edit.mouse_release(State, x,y, mouse_button) @@ -337,7 +337,7 @@ function edit.mouse_release(State, x,y, mouse_button) end -- still here? mouse release is below all screen lines - State.cursor1.line, State.cursor1.pos = State.screen_bottom1.line, Text.pos_at_end_of_screen_line(State, State.screen_bottom1) + State.cursor1 = Text.final_text_loc_on_screen(State) edit.clean_up_mouse_press(State) --? print_and_log(('edit.mouse_release: finally selection %s,%s cursor %d,%d'):format(tostring(State.selection1.line), tostring(State.selection1.pos), State.cursor1.line, State.cursor1.pos)) end @@ -600,6 +600,7 @@ end function edit.run_after_mouse_click(State, x,y, mouse_button) App.fake_mouse_press(x,y, mouse_button) edit.mouse_press(State, x,y, mouse_button) + edit.draw(State) App.fake_mouse_release(x,y, mouse_button) edit.mouse_release(State, x,y, mouse_button) App.screen.contents = {} diff --git a/source_text.lua b/source_text.lua index 7c1838c..0fced93 100644 --- a/source_text.lua +++ b/source_text.lua @@ -683,6 +683,7 @@ function Text.pos_at_start_of_screen_line(State, loc1) end function Text.pos_at_end_of_screen_line(State, loc1) + assert(State.lines[loc1.line].mode == 'text') Text.populate_screen_line_starting_pos(State, loc1.line) local line_cache = State.line_cache[loc1.line] local most_recent_final_pos = utf8.len(State.lines[loc1.line].data)+1 @@ -696,6 +697,24 @@ function Text.pos_at_end_of_screen_line(State, loc1) assert(false, ('invalid pos %d'):format(loc1.pos)) end +function Text.final_text_loc_on_screen(State) + if State.lines[State.screen_bottom1.line].mode == 'text' then + return { + line=State.screen_bottom1.line, + pos=Text.pos_at_end_of_screen_line(State, State.screen_bottom1), + } + end + local loc2 = Text.to2(State, State.screen_bottom1) + while true do + if State.lines[loc2.line].mode == 'text' then break end + assert(loc2.line > 1 or loc2.screen_line > 1 and loc2.screen_pos > 1) -- elsewhere we're making sure there's always at least one text line on screen + loc2 = Text.previous_screen_line(State, loc2) + end + local result = Text.to1(State, loc2) + result.pos = Text.pos_at_end_of_screen_line(State, result) + return result +end + function Text.cursor_at_final_screen_line(State) Text.populate_screen_line_starting_pos(State, State.cursor1.line) local screen_lines = State.line_cache[State.cursor1.line].screen_line_starting_pos diff --git a/source_text_tests.lua b/source_text_tests.lua index 6376ec8..b8f29d1 100644 --- a/source_text_tests.lua +++ b/source_text_tests.lua @@ -16,7 +16,7 @@ function test_initial_state() end function test_click_to_create_drawing() - App.screen.init{width=120, height=60} + App.screen.init{width=800, height=600} Editor_state = edit.initialize_test_state() Editor_state.lines = load_array{} Text.redraw_all(Editor_state) From 19615eade0106ad5a3a988b3f1f257367aceb7ec Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Sun, 9 Jun 2024 20:38:53 -0700 Subject: [PATCH 3/9] bugfix in source editor: don't clear selection on M-arrow --- source_edit.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source_edit.lua b/source_edit.lua index 1e8259c..53b50eb 100644 --- a/source_edit.lua +++ b/source_edit.lua @@ -399,7 +399,7 @@ function edit.keychord_press(State, chord, key) -- 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-a' and chord ~= 'C-c' and chord ~= 'C-x' and chord ~= 'backspace' and chord ~= 'delete' and chord ~= 'C-z' and chord ~= 'C-y' and not App.is_cursor_movement(chord) then + chord ~= 'C-a' and chord ~= 'C-c' and chord ~= 'C-x' and chord ~= 'backspace' and chord ~= 'delete' and chord ~= 'C-z' and chord ~= 'C-y' and not App.is_cursor_movement(key) then Text.delete_selection(State, State.left, State.right) end if State.search_term then From f2299cb422d0fc07a1b01f0c31f88e9ae5ab168f Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Tue, 11 Jun 2024 06:58:07 -0700 Subject: [PATCH 4/9] stop caching screen_bottom1 I'm not sure this is very useful. I had an initial idea to stop using screen_bottom1 in final_text_loc_on_screen, by starting from screen_top1 rather than screen_bottom1. But that changes the direction in which we scan for the text line in situations where there is somehow no text on screen (something that should never happen but I have zero confidence in that). Still, it doesn't seem like a bad thing to drastically reduce the lifetime of some derived state. Really what I need to do is throw this whole UX out and allow the cursor to be on a drawing as a whole. So up arrow or left arrow below a drawing would focus the whole drawing in a red border, and another up arrow and left arrow would skip the drawing and continue upward. I think that change to the UX will eliminate a whole class of special cases in the code. --- app.lua | 5 +- edit.lua | 8 +-- search.lua | 6 +- select.lua | 3 +- source_edit.lua | 8 +-- source_select.lua | 3 +- source_text.lua | 131 +++++++++++++++++++++++-------------- source_text_tests.lua | 76 ---------------------- text.lua | 146 ++++++++++++++++++++++++------------------ text_tests.lua | 106 ------------------------------ 10 files changed, 181 insertions(+), 311 deletions(-) diff --git a/app.lua b/app.lua index 627a438..5e61d47 100644 --- a/app.lua +++ b/app.lua @@ -130,6 +130,8 @@ function App.run_tests() end end table.sort(sorted_names) +--? App.initialize_for_test() -- debug: run a single test at a time like these 2 lines +--? test_pagedown_skips_drawings() for _,name in ipairs(sorted_names) do App.initialize_for_test() --? print('=== '..name) @@ -404,9 +406,10 @@ end -- prepend file/line/test function prepend_debug_info_to_test_failure(test_name, err) local err_without_line_number = err:gsub('^[^:]*:[^:]*: ', '') - local stack_trace = debug.traceback('', --[[stack frame]]5) + local stack_trace = debug.traceback('', --[[stack frame]]5) -- most likely to be useful, but set to 0 for a complete stack trace local file_and_line_number = stack_trace:gsub('stack traceback:\n', ''):gsub(': .*', '') local full_error = file_and_line_number..':'..test_name..' -- '..err_without_line_number + -- uncomment this line for a complete stack trace --? local full_error = file_and_line_number..':'..test_name..' -- '..err_without_line_number..'\t\t'..stack_trace:gsub('\n', '\n\t\t') table.insert(Test_errors, full_error) end diff --git a/edit.lua b/edit.lua index 42309df..5a2ab28 100644 --- a/edit.lua +++ b/edit.lua @@ -70,7 +70,6 @@ function edit.initialize_state(top, left, right, font, font_height, line_height) -- On lines that are drawings, pos will be nil. screen_top1 = {line=1, pos=1}, -- position of start of screen line at top of screen cursor1 = {line=1, pos=1}, -- position of cursor; must be on a text line - screen_bottom1 = {line=1, pos=1}, -- position of start of screen line at bottom of screen selection1 = {}, -- some extra state to compute selection between mouse press and release @@ -166,13 +165,11 @@ function edit.draw(State) State.cursor_x = nil State.cursor_y = nil local y = State.top - local screen_bottom1 = {line=nil, pos=nil} --? print('== draw') for line_index = State.screen_top1.line,#State.lines do local line = State.lines[line_index] --? print('draw:', y, line_index, line) if y + State.line_height > App.screen.height then break end - screen_bottom1.line = line_index if line.mode == 'text' then --? print('text.draw', y, line_index) local startpos = 1 @@ -196,7 +193,7 @@ function edit.draw(State) end, }) end - y, screen_bottom1.pos = Text.draw(State, line_index, y, startpos) + y = Text.draw(State, line_index, y, startpos) --? print('=> y', y) elseif line.mode == 'drawing' then y = y+Drawing_padding_top @@ -206,7 +203,6 @@ function edit.draw(State) assert(false, ('unknown line mode %s'):format(line.mode)) end end - State.screen_bottom1 = screen_bottom1 if State.search_term then Text.draw_search_bar(State) end @@ -361,7 +357,7 @@ function edit.mouse_wheel_move(State, dx,dy) Text.up(State) end elseif dy < 0 then - State.cursor1 = {line=State.screen_bottom1.line, pos=State.screen_bottom1.pos} + State.cursor1 = Text.screen_bottom1(State) edit.put_cursor_on_next_text_line(State) for i=1,math.floor(-dy) do Text.down(State) diff --git a/search.lua b/search.lua index 54bab14..d3a5fea 100644 --- a/search.lua +++ b/search.lua @@ -62,7 +62,8 @@ function Text.search_next(State) State.screen_top1.line = State.search_backup.screen_top.line State.screen_top1.pos = State.search_backup.screen_top.pos end - if Text.lt1(State.cursor1, State.screen_top1) or Text.lt1(State.screen_bottom1, State.cursor1) then + local screen_bottom1 = Text.screen_bottom1(State) + if Text.lt1(State.cursor1, State.screen_top1) or Text.lt1(screen_bottom1, State.cursor1) then State.screen_top1.line = State.cursor1.line local pos = Text.pos_at_start_of_screen_line(State, State.cursor1) State.screen_top1.pos = pos @@ -115,7 +116,8 @@ function Text.search_previous(State) State.screen_top1.line = State.search_backup.screen_top.line State.screen_top1.pos = State.search_backup.screen_top.pos end - if Text.lt1(State.cursor1, State.screen_top1) or Text.lt1(State.screen_bottom1, State.cursor1) then + local screen_bottom1 = Text.screen_bottom1(State) + if Text.lt1(State.cursor1, State.screen_top1) or Text.lt1(screen_bottom1, State.cursor1) then State.screen_top1.line = State.cursor1.line local pos = Text.pos_at_start_of_screen_line(State, State.cursor1) State.screen_top1.pos = pos diff --git a/select.lua b/select.lua index 6e21c7b..78d18db 100644 --- a/select.lua +++ b/select.lua @@ -79,7 +79,8 @@ function Text.mouse_pos(State) end end end - return State.screen_bottom1.line, Text.pos_at_end_of_screen_line(State, State.screen_bottom1) + local screen_bottom1 = Text.screen_bottom1(State) + return screen_bottom1.line, Text.pos_at_end_of_screen_line(State, screen_bottom1) end function Text.cut_selection(State) diff --git a/source_edit.lua b/source_edit.lua index 53b50eb..64c8980 100644 --- a/source_edit.lua +++ b/source_edit.lua @@ -72,7 +72,6 @@ function edit.initialize_state(top, left, right, font, font_height, line_height) -- On lines that are drawings, pos will be nil. screen_top1 = {line=1, pos=1}, -- position of start of screen line at top of screen cursor1 = {line=1, pos=1}, -- position of cursor; must be on a text line - screen_bottom1 = {line=1, pos=1}, -- position of start of screen line at bottom of screen selection1 = {}, -- some extra state to compute selection between mouse press and release @@ -167,13 +166,11 @@ function edit.draw(State, hide_cursor, show_line_numbers) State.cursor_x = nil State.cursor_y = nil local y = State.top - local screen_bottom1 = {line=nil, pos=nil} --? print('== draw') for line_index = State.screen_top1.line,#State.lines do local line = State.lines[line_index] --? print('draw:', y, line_index, line) if y + State.line_height > App.screen.height then break end - screen_bottom1.line = line_index if line.mode == 'text' then --? print('text.draw', y, line_index) local startpos = 1 @@ -201,7 +198,7 @@ function edit.draw(State, hide_cursor, show_line_numbers) end, }) end - y, screen_bottom1.pos = Text.draw(State, line_index, y, startpos, hide_cursor, show_line_numbers) + y = Text.draw(State, line_index, y, startpos, hide_cursor, show_line_numbers) --? print('=> y', y) elseif line.mode == 'drawing' then y = y+Drawing_padding_top @@ -211,7 +208,6 @@ function edit.draw(State, hide_cursor, show_line_numbers) assert(false, ('unknown line mode %s'):format(line.mode)) end end - State.screen_bottom1 = screen_bottom1 if State.search_term then Text.draw_search_bar(State) end @@ -365,7 +361,7 @@ function edit.mouse_wheel_move(State, dx,dy) Text.up(State) end elseif dy < 0 then - State.cursor1 = {line=State.screen_bottom1.line, pos=State.screen_bottom1.pos} + State.cursor1 = Text.screen_bottom1(State) edit.put_cursor_on_next_text_line(State) for i=1,math.floor(-dy) do Text.down(State) diff --git a/source_select.lua b/source_select.lua index 6e21c7b..78d18db 100644 --- a/source_select.lua +++ b/source_select.lua @@ -79,7 +79,8 @@ function Text.mouse_pos(State) end end end - return State.screen_bottom1.line, Text.pos_at_end_of_screen_line(State, State.screen_bottom1) + local screen_bottom1 = Text.screen_bottom1(State) + return screen_bottom1.line, Text.pos_at_end_of_screen_line(State, screen_bottom1) end function Text.cut_selection(State) diff --git a/source_text.lua b/source_text.lua index 0fced93..0fb1147 100644 --- a/source_text.lua +++ b/source_text.lua @@ -2,14 +2,13 @@ Text = {} -- draw a line starting from startpos to screen at y between State.left and State.right --- return y for the next line, and position of start of final screen line drawn +-- return y for the next line function Text.draw(State, line_index, y, startpos, hide_cursor, show_line_numbers) local line = State.lines[line_index] local line_cache = State.line_cache[line_index] line_cache.starty = y line_cache.startpos = startpos -- wrap long lines - local final_screen_line_starting_pos = startpos -- track value to return Text.populate_screen_line_starting_pos(State, line_index) Text.populate_link_offsets(State, line_index) if show_line_numbers then @@ -24,7 +23,6 @@ function Text.draw(State, line_index, y, startpos, hide_cursor, show_line_number -- render nothing --? print('skipping', screen_line) else - final_screen_line_starting_pos = pos local screen_line = Text.screen_line(line, line_cache, i) --? print('text.draw:', screen_line, 'at', line_index,pos, 'after', x,y) local frag_len = utf8.len(screen_line) @@ -84,7 +82,7 @@ function Text.draw(State, line_index, y, startpos, hide_cursor, show_line_number end end end - return y, final_screen_line_starting_pos + return y end function Text.screen_line(line, line_cache, i) @@ -208,7 +206,7 @@ function Text.text_input(State, t) end end local before = snapshot(State, State.cursor1.line) ---? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) +--? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos) Text.insert_at_cursor(State, t) if State.cursor_y > App.screen.height - State.line_height then Text.populate_screen_line_starting_pos(State, State.cursor1.line) @@ -241,12 +239,12 @@ function Text.keychord_press(State, chord) record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)}) elseif chord == 'tab' then local before = snapshot(State, State.cursor1.line) ---? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) +--? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos) Text.insert_at_cursor(State, '\t') if State.cursor_y > App.screen.height - State.line_height then Text.populate_screen_line_starting_pos(State, State.cursor1.line) Text.snap_cursor_to_bottom_of_screen(State, State.left, State.right) ---? print('=>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) +--? print('=>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos) end schedule_save(State) record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)}) @@ -427,37 +425,54 @@ function Text.insert_return(State) end function Text.pageup(State) ---? print('pageup') - -- duplicate some logic from love.draw - local top2 = Text.to2(State, State.screen_top1) ---? print(App.screen.height) - local y = App.screen.height - State.line_height - while y >= State.top do ---? print(y, top2.line, top2.screen_line, top2.screen_pos) - if State.screen_top1.line == 1 and State.screen_top1.pos == 1 then break end - if State.lines[State.screen_top1.line].mode == 'text' then - y = y - State.line_height - elseif State.lines[State.screen_top1.line].mode == 'drawing' then - y = y - Drawing_padding_height - Drawing.pixels(State.lines[State.screen_top1.line].h, State.width) - end - top2 = Text.previous_screen_line(State, top2) - end - State.screen_top1 = Text.to1(State, top2) + State.screen_top1 = Text.previous_screen_top1(State) 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') + Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks +end + +function Text.previous_screen_top1(State) + -- duplicate some logic from love.draw + -- does not modify State (except to populate line_cache) + local loc2 = Text.to2(State, State.screen_top1) + local y = App.screen.height - State.line_height + while y >= State.top do + if loc2.line == 1 and loc2.screen_line == 1 and loc2.screen_pos == 1 then break end + if State.lines[loc2.line].mode == 'text' then + y = y - State.line_height + elseif State.lines[loc2.line].mode == 'drawing' then + y = y - Drawing_padding_height - Drawing.pixels(State.lines[loc2.line].h, State.width) + end + loc2 = Text.previous_screen_line(State, loc2) + end + return Text.to1(State, loc2) end function Text.pagedown(State) ---? print('pagedown') - State.screen_top1 = {line=State.screen_bottom1.line, pos=State.screen_bottom1.pos} ---? print('setting top to', State.screen_top1.line, State.screen_top1.pos) + State.screen_top1 = Text.screen_bottom1(State) 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 ---? print('pagedown end') +end + +-- return the location of the start of the bottom-most line on screen +function Text.screen_bottom1(State) + -- duplicate some logic from love.draw + -- does not modify State (except to populate line_cache) + local loc2 = Text.to2(State, State.screen_top1) + local y = State.top + while true do + if State.lines[loc2.line].mode == 'text' then + y = y + State.line_height + elseif State.lines[loc2.line].mode == 'drawing' then + y = y + Drawing_padding_height + Drawing.pixels(State.lines[loc2.line].h, State.width) + end + if y + State.line_height > App.screen.height then break end + local next_loc2 = Text.next_screen_line(State, loc2) + if Text.eq2(next_loc2, loc2) then break end + loc2 = next_loc2 + end + return Text.to1(State, loc2) end function Text.up(State) @@ -505,7 +520,7 @@ end function Text.down(State) assert(State.lines[State.cursor1.line].mode == 'text', 'line is not text') ---? print('down', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) +--? print('down', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos) assert(State.cursor1.pos, 'cursor has no pos') if Text.cursor_at_final_screen_line(State) then -- line is done, skip to next text line @@ -522,7 +537,9 @@ function Text.down(State) break end end - if State.cursor1.line > State.screen_bottom1.line then + local screen_bottom1 = Text.screen_bottom1(State) +--? print('down 2', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, screen_bottom1.line, screen_bottom1.pos) + if State.cursor1.line > screen_bottom1.line then --? print('screen top before:', State.screen_top1.line, State.screen_top1.pos) --? print('scroll up preserving cursor') Text.snap_cursor_to_bottom_of_screen(State) @@ -530,7 +547,8 @@ function Text.down(State) end else -- move down one screen line in current line - local scroll_down = Text.le1(State.screen_bottom1, State.cursor1) + local screen_bottom1 = Text.screen_bottom1(State) + local scroll_down = Text.le1(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) @@ -546,7 +564,7 @@ function Text.down(State) --? print('screen top after:', State.screen_top1.line, State.screen_top1.pos) end end ---? print('=>', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) +--? print('=>', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos) end function Text.start_of_line(State) @@ -698,13 +716,14 @@ function Text.pos_at_end_of_screen_line(State, loc1) end function Text.final_text_loc_on_screen(State) - if State.lines[State.screen_bottom1.line].mode == 'text' then + local screen_bottom1 = Text.screen_bottom1(State) + if State.lines[screen_bottom1.line].mode == 'text' then return { - line=State.screen_bottom1.line, - pos=Text.pos_at_end_of_screen_line(State, State.screen_bottom1), + line=screen_bottom1.line, + pos=Text.pos_at_end_of_screen_line(State, screen_bottom1), } end - local loc2 = Text.to2(State, State.screen_bottom1) + local loc2 = Text.to2(State, screen_bottom1) while true do if State.lines[loc2.line].mode == 'text' then break end assert(loc2.line > 1 or loc2.screen_line > 1 and loc2.screen_pos > 1) -- elsewhere we're making sure there's always at least one text line on screen @@ -755,7 +774,7 @@ function Text.snap_cursor_to_bottom_of_screen(State) --? 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.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) +--? print('snap', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.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 @@ -785,7 +804,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) +--? print('snap =>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos) Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks end @@ -990,6 +1009,10 @@ function Text.le1(a, b) return a.pos <= b.pos end +function Text.eq2(a, b) + return a.line == b.line and a.screen_line == b.screen_line and a.screen_pos == b.screen_pos +end + function Text.offset(s, pos1) if pos1 == 1 then return 1 end local result = utf8.offset(s, pos1) @@ -1013,6 +1036,22 @@ function Text.previous_screen_line(State, loc2) end end +function Text.next_screen_line(State, loc2) + if State.lines[loc2.line].mode == 'drawing' then + return {line=loc2.line+1, screen_line=1, screen_pos=1} + end + Text.populate_screen_line_starting_pos(State, loc2.line) + if loc2.screen_line >= #State.line_cache[loc2.line].screen_line_starting_pos then + if loc2.line < #State.lines then + return {line=loc2.line+1, screen_line=1, screen_pos=1} + else + return loc2 + end + else + return {line=loc2.line, screen_line=loc2.screen_line+1, screen_pos=1} + end +end + -- resize helper function Text.tweak_screen_top_and_cursor(State) if State.screen_top1.pos == 1 then return end @@ -1036,15 +1075,16 @@ function Text.tweak_screen_top_and_cursor(State) end end -- make sure cursor is on screen + local screen_bottom1 = Text.screen_bottom1(State) if Text.lt1(State.cursor1, State.screen_top1) then State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos} - elseif State.cursor1.line >= State.screen_bottom1.line then + elseif State.cursor1.line >= screen_bottom1.line then --? print('too low') if Text.cursor_out_of_screen(State) then --? print('tweak') State.cursor1 = { - line=State.screen_bottom1.line, - pos=Text.to_pos_on_line(State, State.screen_bottom1.line, State.right-5, App.screen.height-5), + line=screen_bottom1.line, + pos=Text.to_pos_on_line(State, screen_bottom1.line, State.right-5, App.screen.height-5), } end end @@ -1054,11 +1094,6 @@ end function Text.cursor_out_of_screen(State) edit.draw(State) return State.cursor_y == nil - -- this approach is cheaper and almost works, except on the final screen - -- where file ends above bottom of screen ---? local botpos = Text.pos_at_start_of_screen_line(State, State.cursor1) ---? local botline1 = {line=State.cursor1.line, pos=botpos} ---? return Text.lt1(State.screen_bottom1, botline1) end function Text.redraw_all(State) diff --git a/source_text_tests.lua b/source_text_tests.lua index b8f29d1..c17842a 100644 --- a/source_text_tests.lua +++ b/source_text_tests.lua @@ -75,7 +75,6 @@ function test_press_ctrl() 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.run_after_keychord(Editor_state, 'C-m', 'm') end @@ -255,7 +254,6 @@ function test_click_moves_cursor() 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 edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1) @@ -274,7 +272,6 @@ function test_click_to_left_of_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=3} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} Editor_state.selection1 = {} -- click to the left of the line edit.draw(Editor_state) @@ -294,7 +291,6 @@ function test_click_takes_margins_into_account() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=1} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} Editor_state.selection1 = {} -- click on the other line edit.draw(Editor_state) @@ -313,7 +309,6 @@ function test_click_on_empty_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=1} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} Editor_state.selection1 = {} -- click on the empty line edit.draw(Editor_state) @@ -332,7 +327,6 @@ function test_click_below_all_lines() 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 = {} -- click below first line edit.draw(Editor_state) @@ -350,7 +344,6 @@ function test_draw_text() 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) local y = Editor_state.top App.screen.check(y, 'abc', 'screen:1') @@ -367,7 +360,6 @@ function test_draw_wrapping_text() 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) local y = Editor_state.top App.screen.check(y, 'abc', 'screen:1') @@ -384,7 +376,6 @@ function test_draw_word_wrapping_text() 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) local y = Editor_state.top App.screen.check(y, 'abc ', 'screen:1') @@ -402,7 +393,6 @@ function test_click_on_wrapping_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=20} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} -- click on the other line edit.draw(Editor_state) edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1) @@ -421,7 +411,6 @@ function test_click_on_wrapping_line_takes_margins_into_account() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=20} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} -- click on the other line edit.draw(Editor_state) edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1) @@ -439,7 +428,6 @@ function test_draw_text_wrapping_within_word() 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) local y = Editor_state.top App.screen.check(y, 'abcd ', 'screen:1') @@ -457,7 +445,6 @@ function test_draw_wrapping_text_containing_non_ascii() 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) local y = Editor_state.top App.screen.check(y, 'mad', 'screen:1') @@ -476,7 +463,6 @@ function test_click_past_end_of_screen_line() 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) local y = Editor_state.top App.screen.check(y, 'madam ', 'baseline/screen:1') @@ -499,7 +485,6 @@ function test_click_on_wrapping_line_rendered_from_partway_at_top_of_screen() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=8} Editor_state.screen_top1 = {line=1, pos=7} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, "I'm ad", 'baseline/screen:2') @@ -520,7 +505,6 @@ function test_click_past_end_of_wrapping_line() 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) local y = Editor_state.top App.screen.check(y, 'madam ', 'baseline/screen:1') @@ -544,7 +528,6 @@ function test_click_past_end_of_wrapping_line_containing_non_ascii() 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) local y = Editor_state.top App.screen.check(y, 'madam ', 'baseline/screen:1') @@ -569,7 +552,6 @@ function test_click_past_end_of_word_wrapping_line() 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) local y = Editor_state.top App.screen.check(y, 'the quick brown fox ', 'baseline/screen:1') @@ -588,7 +570,6 @@ function test_select_text() 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') @@ -611,7 +592,6 @@ function test_cursor_movement_without_shift_resets_selection() 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', 'right') @@ -629,7 +609,6 @@ function test_edit_deletes_selection() 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_text_input(Editor_state, 'x') @@ -646,7 +625,6 @@ function test_edit_with_shift_key_deletes_selection() 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') @@ -668,7 +646,6 @@ function test_copy_does_not_reset_selection() 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', 'c') @@ -686,7 +663,6 @@ function test_cut() 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', 'x') @@ -704,7 +680,6 @@ function test_paste_replaces_selection() 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' @@ -723,7 +698,6 @@ function test_deleting_selection_may_scroll() 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', 'baseline/screen:1') @@ -747,7 +721,6 @@ function test_edit_wrapping_text() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=4} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) edit.run_after_text_input(Editor_state, 'g') local y = Editor_state.top @@ -766,7 +739,6 @@ function test_insert_newline() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=2} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'abc', 'baseline/screen:1') @@ -795,7 +767,6 @@ function test_insert_newline_at_start_of_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} -- hitting the enter key splits the line edit.run_after_keychord(Editor_state, 'return', 'return') check_eq(Editor_state.cursor1.line, 2, 'cursor:line') @@ -812,7 +783,6 @@ function test_insert_from_clipboard() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=2} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'abc', 'baseline/screen:1') @@ -841,7 +811,6 @@ function test_select_text_using_mouse() 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 @@ -861,7 +830,6 @@ function test_select_text_using_mouse_starting_above_text() 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 mouse above first line of text @@ -879,7 +847,6 @@ function test_select_text_using_mouse_starting_above_text_wrapping_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=5} Editor_state.screen_top1 = {line=2, pos=3} - Editor_state.screen_bottom1 = {} -- press mouse above first line of text edit.draw(Editor_state) edit.run_after_mouse_press(Editor_state, Editor_state.left+8,5, 1) @@ -902,7 +869,6 @@ function test_select_text_using_mouse_starting_below_text() 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) local y = Editor_state.top App.screen.check(y, 'ab', 'baseline:screen:1') @@ -923,7 +889,6 @@ function test_select_text_using_mouse_and_shift() 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 @@ -948,7 +913,6 @@ function test_select_text_repeatedly_using_mouse_and_shift() 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 @@ -978,7 +942,6 @@ function test_select_all_text() 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 all App.fake_key_press('lctrl') @@ -1000,7 +963,6 @@ function test_cut_without_selection() 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 @@ -1016,7 +978,6 @@ function test_pagedown() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} -- initially the first two lines are displayed edit.draw(Editor_state) local y = Editor_state.top @@ -1046,7 +1007,6 @@ function test_pagedown_skips_drawings() check_eq(Editor_state.lines[2].mode, 'drawing', 'baseline/lines') Editor_state.cursor1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} local drawing_height = Drawing_padding_height + drawing_width/2 -- default -- initially the screen displays the first line and the drawing -- 15px margin + 15px line1 + 10px margin + 25px drawing + 10px margin = 75px < screen height 80px @@ -1070,7 +1030,6 @@ function test_pagedown_can_start_from_middle_of_long_wrapping_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=2} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'abc ', 'baseline/screen:1') @@ -1105,7 +1064,6 @@ function test_pagedown_never_moves_up() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=9} Editor_state.screen_top1 = {line=1, pos=9} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) -- pagedown makes no change edit.run_after_keychord(Editor_state, 'pagedown', 'pagedown') @@ -1120,7 +1078,6 @@ function test_down_arrow_moves_cursor() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} -- initially the first three lines are displayed edit.draw(Editor_state) local y = Editor_state.top @@ -1153,7 +1110,6 @@ function test_down_arrow_skips_drawing() 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) local y = Editor_state.top App.screen.check(y, 'abc', 'baseline/screen:1') @@ -1175,7 +1131,6 @@ function test_down_arrow_scrolls_down_by_one_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=3, pos=1} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'abc', 'baseline/screen:1') @@ -1203,7 +1158,6 @@ function test_down_arrow_scrolls_down_by_one_screen_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=3, pos=1} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'abc', 'baseline/screen:1') @@ -1232,7 +1186,6 @@ function test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_ Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=3, pos=1} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'abc', 'baseline/screen:1') @@ -1260,7 +1213,6 @@ function test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=3, pos=1} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'abc', 'baseline/screen:1') @@ -1294,7 +1246,6 @@ function test_up_arrow_moves_cursor() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=3, pos=1} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'abc', 'baseline/screen:1') @@ -1326,7 +1277,6 @@ function test_up_arrow_skips_drawing() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=3, pos=1} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'abc', 'baseline/screen:1') @@ -1348,7 +1298,6 @@ function test_up_arrow_scrolls_up_by_one_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=1} 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', 'baseline/screen:1') @@ -1376,7 +1325,6 @@ function test_up_arrow_scrolls_up_by_one_line_skipping_drawing() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=3, pos=1} Editor_state.screen_top1 = {line=3, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'def', 'baseline/screen:1') @@ -1398,7 +1346,6 @@ function test_up_arrow_scrolls_up_by_one_screen_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=3, pos=6} Editor_state.screen_top1 = {line=3, pos=5} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'jkl', 'baseline/screen:1') @@ -1426,7 +1373,6 @@ function test_up_arrow_scrolls_up_to_final_screen_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=1} 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, 'ghi', 'baseline/screen:1') @@ -1456,7 +1402,6 @@ function test_up_arrow_scrolls_up_to_empty_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=1} 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, 'abc', 'baseline/screen:1') @@ -1483,7 +1428,6 @@ function test_pageup() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=1} Editor_state.screen_top1 = {line=2, pos=1} - Editor_state.screen_bottom1 = {} -- initially the last two lines are displayed edit.draw(Editor_state) local y = Editor_state.top @@ -1508,7 +1452,6 @@ function test_pageup_scrolls_up_by_screen_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=1} 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, 'ghi', 'baseline/screen:1') @@ -1537,7 +1480,6 @@ function test_pageup_scrolls_up_from_middle_screen_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=5} Editor_state.screen_top1 = {line=2, pos=5} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'jkl', 'baseline/screen:2') @@ -1564,7 +1506,6 @@ function test_enter_on_bottom_line_scrolls_down() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=3, pos=2} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'abc', 'baseline/screen:1') @@ -1593,7 +1534,6 @@ function test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=4, pos=2} Editor_state.screen_top1 = {line=4, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'jkl', 'baseline/screen:1') @@ -1616,7 +1556,6 @@ function test_inserting_text_on_final_line_avoids_scrolling_down_when_not_at_bot Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=1} Editor_state.screen_top1 = {line=2, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) -- after hitting the inserting_text key the screen does not scroll down edit.run_after_text_input(Editor_state, 'a') @@ -1635,7 +1574,6 @@ function test_typing_on_bottom_line_scrolls_down() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=3, pos=4} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'abc', 'baseline/screen:1') @@ -1665,7 +1603,6 @@ function test_left_arrow_scrolls_up_in_wrapped_line() Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'} Text.redraw_all(Editor_state) Editor_state.screen_top1 = {line=3, pos=5} - Editor_state.screen_bottom1 = {} -- cursor is at top of screen Editor_state.cursor1 = {line=3, pos=5} edit.draw(Editor_state) @@ -1694,7 +1631,6 @@ function test_right_arrow_scrolls_down_in_wrapped_line() Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'} Text.redraw_all(Editor_state) Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} -- cursor is at bottom right of screen Editor_state.cursor1 = {line=3, pos=5} edit.draw(Editor_state) @@ -1724,7 +1660,6 @@ function test_home_scrolls_up_in_wrapped_line() Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'} Text.redraw_all(Editor_state) Editor_state.screen_top1 = {line=3, pos=5} - Editor_state.screen_bottom1 = {} -- cursor is at top of screen Editor_state.cursor1 = {line=3, pos=5} edit.draw(Editor_state) @@ -1753,7 +1688,6 @@ function test_end_scrolls_down_in_wrapped_line() Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'} Text.redraw_all(Editor_state) Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} -- cursor is at bottom right of screen Editor_state.cursor1 = {line=3, pos=5} edit.draw(Editor_state) @@ -1784,7 +1718,6 @@ function test_position_cursor_on_recently_edited_wrapping_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=25} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'abc def ghi ', 'baseline1/screen:1') @@ -1818,7 +1751,6 @@ function test_backspace_can_scroll_up() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=1} 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', 'baseline/screen:1') @@ -1846,7 +1778,6 @@ function test_backspace_can_scroll_up_screen_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=3, pos=5} Editor_state.screen_top1 = {line=3, pos=5} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'jkl', 'baseline/screen:1') @@ -1981,7 +1912,6 @@ function test_undo_insert_text() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=4} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} -- insert a character edit.draw(Editor_state) edit.run_after_text_input(Editor_state, 'g') @@ -2016,7 +1946,6 @@ function test_undo_delete_text() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=5} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} -- delete a character edit.run_after_keychord(Editor_state, 'backspace', 'backspace') check_eq(Editor_state.cursor1.line, 2, 'baseline/cursor:line') @@ -2055,7 +1984,6 @@ function test_undo_restores_selection() 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_text_input(Editor_state, 'x') @@ -2076,7 +2004,6 @@ function test_search() 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) -- search for a string edit.run_after_keychord(Editor_state, 'C-f', 'f') @@ -2103,7 +2030,6 @@ function test_search_upwards() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=1} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) -- search for a string edit.run_after_keychord(Editor_state, 'C-f', 'f') @@ -2121,7 +2047,6 @@ function test_search_wrap() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=1} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) -- search for a string edit.run_after_keychord(Editor_state, 'C-f', 'f') @@ -2139,7 +2064,6 @@ function test_search_wrap_upwards() 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) -- search upwards for a string edit.run_after_keychord(Editor_state, 'C-f', 'f') diff --git a/text.lua b/text.lua index d9e7653..f203950 100644 --- a/text.lua +++ b/text.lua @@ -2,7 +2,7 @@ Text = {} -- draw a line starting from startpos to screen at y between State.left and State.right --- return y for the next line, and position of start of final screen line drawn +-- return y for the next line function Text.draw(State, line_index, y, startpos) --? print('text.draw', line_index, y) local line = State.lines[line_index] @@ -10,7 +10,6 @@ function Text.draw(State, line_index, y, startpos) line_cache.starty = y line_cache.startpos = startpos -- wrap long lines - local final_screen_line_starting_pos = startpos -- track value to return Text.populate_screen_line_starting_pos(State, line_index) assert(#line_cache.screen_line_starting_pos >= 1, 'line cache missing screen line info') for i=1,#line_cache.screen_line_starting_pos do @@ -18,7 +17,6 @@ function Text.draw(State, line_index, y, startpos) if pos < startpos then -- render nothing else - final_screen_line_starting_pos = pos local screen_line = Text.screen_line(line, line_cache, i) --? print('text.draw:', screen_line, 'at', line_index,pos, 'after', x,y) local frag_len = utf8.len(screen_line) @@ -59,7 +57,7 @@ function Text.draw(State, line_index, y, startpos) end end end - return y, final_screen_line_starting_pos + return y end function Text.screen_line(line, line_cache, i) @@ -134,7 +132,7 @@ function Text.text_input(State, t) end end local before = snapshot(State, State.cursor1.line) ---? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) +--? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos) Text.insert_at_cursor(State, t) if State.cursor_y > App.screen.height - State.line_height then Text.populate_screen_line_starting_pos(State, State.cursor1.line) @@ -167,12 +165,12 @@ function Text.keychord_press(State, chord) record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)}) elseif chord == 'tab' then local before = snapshot(State, State.cursor1.line) ---? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) +--? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos) Text.insert_at_cursor(State, '\t') if State.cursor_y > App.screen.height - State.line_height then Text.populate_screen_line_starting_pos(State, State.cursor1.line) Text.snap_cursor_to_bottom_of_screen(State, State.left, State.right) ---? print('=>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) +--? print('=>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos) end schedule_save(State) record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)}) @@ -353,49 +351,54 @@ function Text.insert_return(State) end function Text.pageup(State) ---? print('pageup') - -- duplicate some logic from love.draw - local top2 = Text.to2(State, State.screen_top1) ---? print(App.screen.height) - local y = App.screen.height - State.line_height - while y >= State.top do ---? print(y, top2.line, top2.screen_line, top2.screen_pos) - if State.screen_top1.line == 1 and State.screen_top1.pos == 1 then break end - if State.lines[State.screen_top1.line].mode == 'text' then - y = y - State.line_height - elseif State.lines[State.screen_top1.line].mode == 'drawing' then - y = y - Drawing_padding_height - Drawing.pixels(State.lines[State.screen_top1.line].h, State.width) - end - top2 = Text.previous_screen_line(State, top2) - end - State.screen_top1 = Text.to1(State, top2) + State.screen_top1 = Text.previous_screen_top1(State) 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') + Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks +end + +function Text.previous_screen_top1(State) + -- duplicate some logic from love.draw + -- does not modify State (except to populate line_cache) + local loc2 = Text.to2(State, State.screen_top1) + local y = App.screen.height - State.line_height + while y >= State.top do + if loc2.line == 1 and loc2.screen_line == 1 and loc2.screen_pos == 1 then break end + if State.lines[loc2.line].mode == 'text' then + y = y - State.line_height + elseif State.lines[loc2.line].mode == 'drawing' then + y = y - Drawing_padding_height - Drawing.pixels(State.lines[loc2.line].h, State.width) + end + loc2 = Text.previous_screen_line(State, loc2) + end + return Text.to1(State, loc2) end function Text.pagedown(State) ---? print('pagedown') - -- If a line/paragraph gets to a page boundary, I often want to scroll - -- before I get to the bottom. - -- However, only do this if it makes forward progress. - local bot2 = Text.to2(State, State.screen_bottom1) - if bot2.screen_line > 1 then - bot2.screen_line = math.max(bot2.screen_line-10, 1) - end - local new_top1 = Text.to1(State, bot2) - if Text.lt1(State.screen_top1, new_top1) then - State.screen_top1 = new_top1 - else - 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.screen_top1 = Text.screen_bottom1(State) 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 ---? print('pagedown end') +end + +-- return the location of the start of the bottom-most line on screen +function Text.screen_bottom1(State) + -- duplicate some logic from love.draw + -- does not modify State (except to populate line_cache) + local loc2 = Text.to2(State, State.screen_top1) + local y = State.top + while true do + if State.lines[loc2.line].mode == 'text' then + y = y + State.line_height + elseif State.lines[loc2.line].mode == 'drawing' then + y = y + Drawing_padding_height + Drawing.pixels(State.lines[loc2.line].h, State.width) + end + if y + State.line_height > App.screen.height then break end + local next_loc2 = Text.next_screen_line(State, loc2) + if Text.eq2(next_loc2, loc2) then break end + loc2 = next_loc2 + end + return Text.to1(State, loc2) end function Text.up(State) @@ -443,7 +446,7 @@ end function Text.down(State) assert(State.lines[State.cursor1.line].mode == 'text', 'line is not text') ---? print('down', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) +--? print('down', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos) assert(State.cursor1.pos, 'cursor has no pos') if Text.cursor_at_final_screen_line(State) then -- line is done, skip to next text line @@ -460,7 +463,9 @@ function Text.down(State) break end end - if State.cursor1.line > State.screen_bottom1.line then + local screen_bottom1 = Text.screen_bottom1(State) +--? print('down 2', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, screen_bottom1.line, screen_bottom1.pos) + if State.cursor1.line > screen_bottom1.line then --? print('screen top before:', State.screen_top1.line, State.screen_top1.pos) --? print('scroll up preserving cursor') Text.snap_cursor_to_bottom_of_screen(State) @@ -468,7 +473,8 @@ function Text.down(State) end else -- move down one screen line in current line - local scroll_down = Text.le1(State.screen_bottom1, State.cursor1) + local screen_bottom1 = Text.screen_bottom1(State) + local scroll_down = Text.le1(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) @@ -484,7 +490,7 @@ function Text.down(State) --? print('screen top after:', State.screen_top1.line, State.screen_top1.pos) end end ---? print('=>', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) +--? print('=>', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos) end function Text.start_of_line(State) @@ -636,13 +642,14 @@ function Text.pos_at_end_of_screen_line(State, loc1) end function Text.final_text_loc_on_screen(State) - if State.lines[State.screen_bottom1.line].mode == 'text' then + local screen_bottom1 = Text.screen_bottom1(State) + if State.lines[screen_bottom1.line].mode == 'text' then return { - line=State.screen_bottom1.line, - pos=Text.pos_at_end_of_screen_line(State, State.screen_bottom1), + line=screen_bottom1.line, + pos=Text.pos_at_end_of_screen_line(State, screen_bottom1), } end - local loc2 = Text.to2(State, State.screen_bottom1) + local loc2 = Text.to2(State, screen_bottom1) while true do if State.lines[loc2.line].mode == 'text' then break end assert(loc2.line > 1 or loc2.screen_line > 1 and loc2.screen_pos > 1) -- elsewhere we're making sure there's always at least one text line on screen @@ -693,7 +700,7 @@ function Text.snap_cursor_to_bottom_of_screen(State) --? 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.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) +--? print('snap', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.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 @@ -723,7 +730,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) +--? print('snap =>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos) Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks end @@ -928,6 +935,10 @@ function Text.le1(a, b) return a.pos <= b.pos end +function Text.eq2(a, b) + return a.line == b.line and a.screen_line == b.screen_line and a.screen_pos == b.screen_pos +end + function Text.offset(s, pos1) if pos1 == 1 then return 1 end local result = utf8.offset(s, pos1) @@ -951,6 +962,22 @@ function Text.previous_screen_line(State, loc2) end end +function Text.next_screen_line(State, loc2) + if State.lines[loc2.line].mode == 'drawing' then + return {line=loc2.line+1, screen_line=1, screen_pos=1} + end + Text.populate_screen_line_starting_pos(State, loc2.line) + if loc2.screen_line >= #State.line_cache[loc2.line].screen_line_starting_pos then + if loc2.line < #State.lines then + return {line=loc2.line+1, screen_line=1, screen_pos=1} + else + return loc2 + end + else + return {line=loc2.line, screen_line=loc2.screen_line+1, screen_pos=1} + end +end + -- resize helper function Text.tweak_screen_top_and_cursor(State) if State.screen_top1.pos == 1 then return end @@ -974,16 +1001,12 @@ function Text.tweak_screen_top_and_cursor(State) end end -- make sure cursor is on screen + local screen_bottom1 = Text.screen_bottom1(State) if Text.lt1(State.cursor1, State.screen_top1) then State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos} - elseif State.cursor1.line >= State.screen_bottom1.line then ---? print('too low') + elseif State.cursor1.line >= screen_bottom1.line then if Text.cursor_out_of_screen(State) then ---? print('tweak') - State.cursor1 = { - line=State.screen_bottom1.line, - pos=Text.to_pos_on_line(State, State.screen_bottom1.line, State.right-5, App.screen.height-5), - } + State.cursor1 = Text.final_text_loc_on_screen(State) end end end @@ -992,11 +1015,6 @@ end function Text.cursor_out_of_screen(State) edit.draw(State) return State.cursor_y == nil - -- this approach is cheaper and almost works, except on the final screen - -- where file ends above bottom of screen ---? local botpos = Text.pos_at_start_of_screen_line(State, State.cursor1) ---? local botline1 = {line=State.cursor1.line, pos=botpos} ---? return Text.lt1(State.screen_bottom1, botline1) end function Text.redraw_all(State) diff --git a/text_tests.lua b/text_tests.lua index 44cdafc..40320b1 100644 --- a/text_tests.lua +++ b/text_tests.lua @@ -75,7 +75,6 @@ function test_press_ctrl() 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.run_after_keychord(Editor_state, 'C-m', 'm') end @@ -255,7 +254,6 @@ function test_click_moves_cursor() 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 edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1) @@ -274,7 +272,6 @@ function test_click_to_left_of_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=3} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} Editor_state.selection1 = {} -- click to the left of the line edit.draw(Editor_state) @@ -294,7 +291,6 @@ function test_click_takes_margins_into_account() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=1} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} Editor_state.selection1 = {} -- click on the other line edit.draw(Editor_state) @@ -313,7 +309,6 @@ function test_click_on_empty_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=1} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} Editor_state.selection1 = {} -- click on the empty line edit.draw(Editor_state) @@ -332,7 +327,6 @@ function test_click_below_all_lines() 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 = {} -- click below first line edit.draw(Editor_state) @@ -350,7 +344,6 @@ function test_draw_text() 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) local y = Editor_state.top App.screen.check(y, 'abc', 'screen:1') @@ -367,7 +360,6 @@ function test_draw_wrapping_text() 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) local y = Editor_state.top App.screen.check(y, 'abc', 'screen:1') @@ -384,7 +376,6 @@ function test_draw_word_wrapping_text() 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) local y = Editor_state.top App.screen.check(y, 'abc ', 'screen:1') @@ -402,7 +393,6 @@ function test_click_on_wrapping_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=20} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} -- click on the other line edit.draw(Editor_state) edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1) @@ -421,7 +411,6 @@ function test_click_on_wrapping_line_takes_margins_into_account() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=20} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} -- click on the other line edit.draw(Editor_state) edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1) @@ -439,7 +428,6 @@ function test_draw_text_wrapping_within_word() 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) local y = Editor_state.top App.screen.check(y, 'abcd ', 'screen:1') @@ -457,7 +445,6 @@ function test_draw_wrapping_text_containing_non_ascii() 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) local y = Editor_state.top App.screen.check(y, 'mad', 'screen:1') @@ -476,7 +463,6 @@ function test_click_past_end_of_screen_line() 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) local y = Editor_state.top App.screen.check(y, 'madam ', 'baseline/screen:1') @@ -499,7 +485,6 @@ function test_click_on_wrapping_line_rendered_from_partway_at_top_of_screen() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=8} Editor_state.screen_top1 = {line=1, pos=7} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, "I'm ad", 'baseline/screen:2') @@ -520,7 +505,6 @@ function test_click_past_end_of_wrapping_line() 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) local y = Editor_state.top App.screen.check(y, 'madam ', 'baseline/screen:1') @@ -544,7 +528,6 @@ function test_click_past_end_of_wrapping_line_containing_non_ascii() 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) local y = Editor_state.top App.screen.check(y, 'madam ', 'baseline/screen:1') @@ -569,7 +552,6 @@ function test_click_past_end_of_word_wrapping_line() 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) local y = Editor_state.top App.screen.check(y, 'the quick brown fox ', 'baseline/screen:1') @@ -588,7 +570,6 @@ function test_select_text() 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') @@ -611,7 +592,6 @@ function test_cursor_movement_without_shift_resets_selection() 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', 'right') @@ -629,7 +609,6 @@ function test_edit_deletes_selection() 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_text_input(Editor_state, 'x') @@ -646,7 +625,6 @@ function test_edit_with_shift_key_deletes_selection() 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') @@ -668,7 +646,6 @@ function test_copy_does_not_reset_selection() 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', 'c') @@ -686,7 +663,6 @@ function test_cut() 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', 'x') @@ -704,7 +680,6 @@ function test_paste_replaces_selection() 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' @@ -723,7 +698,6 @@ function test_deleting_selection_may_scroll() 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', 'baseline/screen:1') @@ -747,7 +721,6 @@ function test_edit_wrapping_text() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=4} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) edit.run_after_text_input(Editor_state, 'g') local y = Editor_state.top @@ -766,7 +739,6 @@ function test_insert_newline() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=2} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'abc', 'baseline/screen:1') @@ -795,7 +767,6 @@ function test_insert_newline_at_start_of_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} -- hitting the enter key splits the line edit.run_after_keychord(Editor_state, 'return', 'return') check_eq(Editor_state.cursor1.line, 2, 'cursor:line') @@ -812,7 +783,6 @@ function test_insert_from_clipboard() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=2} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'abc', 'baseline/screen:1') @@ -841,7 +811,6 @@ function test_select_text_using_mouse() 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 @@ -861,7 +830,6 @@ function test_select_text_using_mouse_starting_above_text() 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 mouse above first line of text @@ -879,7 +847,6 @@ function test_select_text_using_mouse_starting_above_text_wrapping_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=5} Editor_state.screen_top1 = {line=2, pos=3} - Editor_state.screen_bottom1 = {} -- press mouse above first line of text edit.draw(Editor_state) edit.run_after_mouse_press(Editor_state, Editor_state.left+8,5, 1) @@ -902,7 +869,6 @@ function test_select_text_using_mouse_starting_below_text() 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) local y = Editor_state.top App.screen.check(y, 'ab', 'baseline:screen:1') @@ -923,7 +889,6 @@ function test_select_text_using_mouse_and_shift() 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 @@ -948,7 +913,6 @@ function test_select_text_repeatedly_using_mouse_and_shift() 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 @@ -978,7 +942,6 @@ function test_select_all_text() 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 all App.fake_key_press('lctrl') @@ -1000,7 +963,6 @@ function test_cut_without_selection() 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 @@ -1016,7 +978,6 @@ function test_pagedown() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} -- initially the first two lines are displayed edit.draw(Editor_state) local y = Editor_state.top @@ -1046,7 +1007,6 @@ function test_pagedown_skips_drawings() check_eq(Editor_state.lines[2].mode, 'drawing', 'baseline/lines') Editor_state.cursor1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} local drawing_height = Drawing_padding_height + drawing_width/2 -- default -- initially the screen displays the first line and the drawing -- 15px margin + 15px line1 + 10px margin + 25px drawing + 10px margin = 75px < screen height 80px @@ -1062,36 +1022,6 @@ function test_pagedown_skips_drawings() App.screen.check(y, 'def', 'screen:1') end -function test_pagedown_often_shows_start_of_wrapping_line() - -- draw a few lines ending in part of a wrapping line - App.screen.init{width=50, 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.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} - edit.draw(Editor_state) - local y = Editor_state.top - App.screen.check(y, 'abc', 'baseline/screen:1') - y = y + Editor_state.line_height - App.screen.check(y, 'def ', 'baseline/screen:2') - y = y + Editor_state.line_height - App.screen.check(y, 'ghi ', 'baseline/screen:3') - -- after pagedown we start drawing from the bottom _line_ (multiple screen lines) - edit.run_after_keychord(Editor_state, 'pagedown', 'pagedown') - check_eq(Editor_state.screen_top1.line, 2, 'screen_top:line') - check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos') - check_eq(Editor_state.cursor1.line, 2, 'cursor:line') - check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos') - y = Editor_state.top - App.screen.check(y, 'def ', 'screen:1') - y = y + Editor_state.line_height - App.screen.check(y, 'ghi ', 'screen:2') - y = y + Editor_state.line_height - App.screen.check(y, 'jkl', 'screen:3') -end - function test_pagedown_can_start_from_middle_of_long_wrapping_line() -- draw a few lines starting from a very long wrapping line App.screen.init{width=Editor_state.left+30, height=60} @@ -1100,7 +1030,6 @@ function test_pagedown_can_start_from_middle_of_long_wrapping_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=2} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'abc ', 'baseline/screen:1') @@ -1135,7 +1064,6 @@ function test_pagedown_never_moves_up() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=9} Editor_state.screen_top1 = {line=1, pos=9} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) -- pagedown makes no change edit.run_after_keychord(Editor_state, 'pagedown', 'pagedown') @@ -1150,7 +1078,6 @@ function test_down_arrow_moves_cursor() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} -- initially the first three lines are displayed edit.draw(Editor_state) local y = Editor_state.top @@ -1183,7 +1110,6 @@ function test_down_arrow_skips_drawing() 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) local y = Editor_state.top App.screen.check(y, 'abc', 'baseline/screen:1') @@ -1205,7 +1131,6 @@ function test_down_arrow_scrolls_down_by_one_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=3, pos=1} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'abc', 'baseline/screen:1') @@ -1233,7 +1158,6 @@ function test_down_arrow_scrolls_down_by_one_screen_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=3, pos=1} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'abc', 'baseline/screen:1') @@ -1262,7 +1186,6 @@ function test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_ Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=3, pos=1} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'abc', 'baseline/screen:1') @@ -1290,7 +1213,6 @@ function test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=3, pos=1} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'abc', 'baseline/screen:1') @@ -1324,7 +1246,6 @@ function test_up_arrow_moves_cursor() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=3, pos=1} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'abc', 'baseline/screen:1') @@ -1356,7 +1277,6 @@ function test_up_arrow_skips_drawing() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=3, pos=1} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'abc', 'baseline/screen:1') @@ -1378,7 +1298,6 @@ function test_up_arrow_scrolls_up_by_one_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=1} 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', 'baseline/screen:1') @@ -1406,7 +1325,6 @@ function test_up_arrow_scrolls_up_by_one_line_skipping_drawing() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=3, pos=1} Editor_state.screen_top1 = {line=3, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'def', 'baseline/screen:1') @@ -1428,7 +1346,6 @@ function test_up_arrow_scrolls_up_by_one_screen_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=3, pos=6} Editor_state.screen_top1 = {line=3, pos=5} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'jkl', 'baseline/screen:1') @@ -1456,7 +1373,6 @@ function test_up_arrow_scrolls_up_to_final_screen_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=1} 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, 'ghi', 'baseline/screen:1') @@ -1486,7 +1402,6 @@ function test_up_arrow_scrolls_up_to_empty_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=1} 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, 'abc', 'baseline/screen:1') @@ -1513,7 +1428,6 @@ function test_pageup() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=1} Editor_state.screen_top1 = {line=2, pos=1} - Editor_state.screen_bottom1 = {} -- initially the last two lines are displayed edit.draw(Editor_state) local y = Editor_state.top @@ -1538,7 +1452,6 @@ function test_pageup_scrolls_up_by_screen_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=1} 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, 'ghi', 'baseline/screen:1') @@ -1567,7 +1480,6 @@ function test_pageup_scrolls_up_from_middle_screen_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=5} Editor_state.screen_top1 = {line=2, pos=5} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'jkl', 'baseline/screen:2') @@ -1594,7 +1506,6 @@ function test_enter_on_bottom_line_scrolls_down() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=3, pos=2} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'abc', 'baseline/screen:1') @@ -1623,7 +1534,6 @@ function test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=4, pos=2} Editor_state.screen_top1 = {line=4, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'jkl', 'baseline/screen:1') @@ -1646,7 +1556,6 @@ function test_inserting_text_on_final_line_avoids_scrolling_down_when_not_at_bot Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=1} Editor_state.screen_top1 = {line=2, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) -- after hitting the inserting_text key the screen does not scroll down edit.run_after_text_input(Editor_state, 'a') @@ -1665,7 +1574,6 @@ function test_typing_on_bottom_line_scrolls_down() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=3, pos=4} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'abc', 'baseline/screen:1') @@ -1695,7 +1603,6 @@ function test_left_arrow_scrolls_up_in_wrapped_line() Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'} Text.redraw_all(Editor_state) Editor_state.screen_top1 = {line=3, pos=5} - Editor_state.screen_bottom1 = {} -- cursor is at top of screen Editor_state.cursor1 = {line=3, pos=5} edit.draw(Editor_state) @@ -1724,7 +1631,6 @@ function test_right_arrow_scrolls_down_in_wrapped_line() Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'} Text.redraw_all(Editor_state) Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} -- cursor is at bottom right of screen Editor_state.cursor1 = {line=3, pos=5} edit.draw(Editor_state) @@ -1754,7 +1660,6 @@ function test_home_scrolls_up_in_wrapped_line() Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'} Text.redraw_all(Editor_state) Editor_state.screen_top1 = {line=3, pos=5} - Editor_state.screen_bottom1 = {} -- cursor is at top of screen Editor_state.cursor1 = {line=3, pos=5} edit.draw(Editor_state) @@ -1783,7 +1688,6 @@ function test_end_scrolls_down_in_wrapped_line() Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'} Text.redraw_all(Editor_state) Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} -- cursor is at bottom right of screen Editor_state.cursor1 = {line=3, pos=5} edit.draw(Editor_state) @@ -1814,7 +1718,6 @@ function test_position_cursor_on_recently_edited_wrapping_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=25} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'abc def ghi ', 'baseline1/screen:1') @@ -1848,7 +1751,6 @@ function test_backspace_can_scroll_up() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=1} 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', 'baseline/screen:1') @@ -1876,7 +1778,6 @@ function test_backspace_can_scroll_up_screen_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=3, pos=5} Editor_state.screen_top1 = {line=3, pos=5} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) local y = Editor_state.top App.screen.check(y, 'jkl', 'baseline/screen:1') @@ -2011,7 +1912,6 @@ function test_undo_insert_text() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=4} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} -- insert a character edit.draw(Editor_state) edit.run_after_text_input(Editor_state, 'g') @@ -2046,7 +1946,6 @@ function test_undo_delete_text() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=5} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} -- delete a character edit.run_after_keychord(Editor_state, 'backspace', 'backspace') check_eq(Editor_state.cursor1.line, 2, 'baseline/cursor:line') @@ -2085,7 +1984,6 @@ function test_undo_restores_selection() 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_text_input(Editor_state, 'x') @@ -2106,7 +2004,6 @@ function test_search() 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) -- search for a string edit.run_after_keychord(Editor_state, 'C-f', 'f') @@ -2133,7 +2030,6 @@ function test_search_upwards() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=1} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) -- search for a string edit.run_after_keychord(Editor_state, 'C-f', 'f') @@ -2151,7 +2047,6 @@ function test_search_wrap() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=2, pos=1} Editor_state.screen_top1 = {line=1, pos=1} - Editor_state.screen_bottom1 = {} edit.draw(Editor_state) -- search for a string edit.run_after_keychord(Editor_state, 'C-f', 'f') @@ -2169,7 +2064,6 @@ function test_search_wrap_upwards() 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) -- search upwards for a string edit.run_after_keychord(Editor_state, 'C-f', 'f') From 69c88da98ca1bba3ab1c890639aa0228f125c467 Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Tue, 11 Jun 2024 10:37:58 -0700 Subject: [PATCH 5/9] stop caching starty This is quite useful because I used to have a long list of places in which to invalidate the cache. --- app.lua | 2 +- drawing.lua | 107 ++++++++++++++++++++++-------------------- drawing_tests.lua | 20 ++++---- edit.lua | 9 +--- help.lua | 12 ++--- select.lua | 2 +- source.lua | 1 - source_edit.lua | 9 +--- source_text.lua | 35 +++++++++++--- source_text_tests.lua | 10 ++-- text.lua | 38 ++++++++++++--- text_tests.lua | 10 ++-- 12 files changed, 148 insertions(+), 107 deletions(-) diff --git a/app.lua b/app.lua index 5e61d47..274f38d 100644 --- a/app.lua +++ b/app.lua @@ -131,7 +131,7 @@ function App.run_tests() end table.sort(sorted_names) --? App.initialize_for_test() -- debug: run a single test at a time like these 2 lines ---? test_pagedown_skips_drawings() +--? test_click_moves_cursor() for _,name in ipairs(sorted_names) do App.initialize_for_test() --? print('=== '..name) diff --git a/drawing.lua b/drawing.lua index 246c7ae..92e3d5f 100644 --- a/drawing.lua +++ b/drawing.lua @@ -6,16 +6,15 @@ require 'drawing_tests' -- into 256 parts. function Drawing.draw(State, line_index, y) local line = State.lines[line_index] - local line_cache = State.line_cache[line_index] - line_cache.starty = y local pmx,pmy = App.mouse_x(), App.mouse_y() - if pmx < State.right and pmy > line_cache.starty and pmy < line_cache.starty+Drawing.pixels(line.h, State.width) then + local starty = Text.starty(State, line_index) + if pmx < State.right and pmy > starty and pmy < starty+Drawing.pixels(line.h, State.width) then App.color(Icon_color) - love.graphics.rectangle('line', State.left,line_cache.starty, State.width,Drawing.pixels(line.h, State.width)) + love.graphics.rectangle('line', State.left,starty, State.width,Drawing.pixels(line.h, State.width)) if icon[State.current_drawing_mode] then - icon[State.current_drawing_mode](State.right-22, line_cache.starty+4) + icon[State.current_drawing_mode](State.right-22, starty+4) else - icon[State.previous_drawing_mode](State.right-22, line_cache.starty+4) + icon[State.previous_drawing_mode](State.right-22, starty+4) end if App.mouse_down(1) and love.keyboard.isDown('h') then @@ -30,7 +29,7 @@ function Drawing.draw(State, line_index, y) end local mx = Drawing.coord(pmx-State.left, State.width) - local my = Drawing.coord(pmy-line_cache.starty, State.width) + local my = Drawing.coord(pmy-starty, State.width) for _,shape in ipairs(line.shapes) do if geom.on_shape(mx,my, line, shape) then @@ -38,11 +37,11 @@ function Drawing.draw(State, line_index, y) else App.color(Stroke_color) end - Drawing.draw_shape(line, shape, line_cache.starty, State.left,State.right) + Drawing.draw_shape(line, shape, starty, State.left,State.right) end local function px(x) return Drawing.pixels(x, State.width)+State.left end - local function py(y) return Drawing.pixels(y, State.width)+line_cache.starty end + local function py(y) return Drawing.pixels(y, State.width)+starty end for i,p in ipairs(line.points) do if p.deleted == nil then if Drawing.near(p, mx,my, State.width) then @@ -71,7 +70,7 @@ function Drawing.draw(State, line_index, y) end end App.color(Current_stroke_color) - Drawing.draw_pending_shape(line, line_cache.starty, State.left,State.right) + Drawing.draw_pending_shape(line, starty, State.left,State.right) end function Drawing.draw_shape(drawing, shape, top, left,right) @@ -209,17 +208,24 @@ function Drawing.draw_pending_shape(drawing, top, left,right) end end -function Drawing.in_drawing(drawing, line_cache, x,y, left,right) - if line_cache.starty == nil then return false end -- outside current page +function Drawing.in_current_drawing(State, x,y, left,right) + return Drawing.in_drawing(State, State.lines.current_drawing_index, x,y, left,right) +end + +function Drawing.in_drawing(State, line_index, x,y, left,right) + assert(State.lines[line_index].mode == 'drawing') + local starty = Text.starty(State, line_index) + if starty == nil then return false end -- outside current page + local drawing = State.lines[line_index] local width = right-left - return y >= line_cache.starty and y < line_cache.starty + Drawing.pixels(drawing.h, width) and x >= left and x < right + return y >= starty and y < starty + Drawing.pixels(drawing.h, width) and x >= left and x < right end function Drawing.mouse_press(State, drawing_index, x,y, mouse_button) local drawing = State.lines[drawing_index] - local line_cache = State.line_cache[drawing_index] + local starty = Text.starty(State, drawing_index) local cx = Drawing.coord(x-State.left, State.width) - local cy = Drawing.coord(y-line_cache.starty, State.width) + local cy = Drawing.coord(y-starty, State.width) if State.current_drawing_mode == 'freehand' then drawing.pending = {mode=State.current_drawing_mode, points={{x=cx, y=cy}}} elseif State.current_drawing_mode == 'line' or State.current_drawing_mode == 'manhattan' then @@ -244,8 +250,8 @@ end function Drawing.update(State) if State.lines.current_drawing == nil then return end local drawing = State.lines.current_drawing - local line_cache = State.line_cache[State.lines.current_drawing_index] - if line_cache.starty == nil then + local starty = Text.starty(State, State.lines.current_drawing_index) + if starty == nil then -- some event cleared starty just this frame -- draw in this frame will soon set starty -- just skip this frame @@ -254,9 +260,9 @@ function Drawing.update(State) assert(drawing.mode == 'drawing', 'Drawing.update: line is not a drawing') local pmx, pmy = App.mouse_x(), App.mouse_y() local mx = Drawing.coord(pmx-State.left, State.width) - local my = Drawing.coord(pmy-line_cache.starty, State.width) + local my = Drawing.coord(pmy-starty, State.width) if App.mouse_down(1) then - if Drawing.in_drawing(drawing, line_cache, pmx,pmy, State.left,State.right) then + if Drawing.in_current_drawing(State, pmx,pmy, State.left,State.right) then if drawing.pending.mode == 'freehand' then table.insert(drawing.pending.points, {x=mx, y=my}) elseif drawing.pending.mode == 'move' then @@ -266,7 +272,7 @@ function Drawing.update(State) end end elseif State.current_drawing_mode == 'move' then - if Drawing.in_drawing(drawing, line_cache, pmx, pmy, State.left,State.right) then + if Drawing.in_current_drawing(State, pmx, pmy, State.left,State.right) then drawing.pending.target_point.x = mx drawing.pending.target_point.y = my Drawing.relax_constraints(drawing, drawing.pending.target_point_index) @@ -304,7 +310,7 @@ function Drawing.mouse_release(State, x,y, mouse_button) end elseif State.lines.current_drawing then local drawing = State.lines.current_drawing - local line_cache = State.line_cache[State.lines.current_drawing_index] + local starty = Text.starty(State, State.lines.current_drawing_index) if drawing.pending then if drawing.pending.mode == nil then -- nothing pending @@ -313,14 +319,14 @@ function Drawing.mouse_release(State, x,y, mouse_button) Drawing.smoothen(drawing.pending) table.insert(drawing.shapes, drawing.pending) elseif drawing.pending.mode == 'line' then - local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width) + local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width) if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then drawing.pending.p2 = Drawing.find_or_insert_point(drawing.points, mx,my, State.width) table.insert(drawing.shapes, drawing.pending) end elseif drawing.pending.mode == 'manhattan' then local p1 = drawing.points[drawing.pending.p1] - local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width) + local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width) if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then if math.abs(mx-p1.x) > math.abs(my-p1.y) then drawing.pending.p2 = Drawing.find_or_insert_point(drawing.points, mx, p1.y, State.width) @@ -328,11 +334,11 @@ function Drawing.mouse_release(State, x,y, mouse_button) drawing.pending.p2 = Drawing.find_or_insert_point(drawing.points, p1.x, my, State.width) end local p2 = drawing.points[drawing.pending.p2] - App.mouse_move(State.left+Drawing.pixels(p2.x, State.width), line_cache.starty+Drawing.pixels(p2.y, State.width)) + App.mouse_move(State.left+Drawing.pixels(p2.x, State.width), starty+Drawing.pixels(p2.y, State.width)) table.insert(drawing.shapes, drawing.pending) end elseif drawing.pending.mode == 'polygon' then - local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width) + local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width) if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then table.insert(drawing.pending.vertices, Drawing.find_or_insert_point(drawing.points, mx,my, State.width)) table.insert(drawing.shapes, drawing.pending) @@ -340,7 +346,7 @@ function Drawing.mouse_release(State, x,y, mouse_button) elseif drawing.pending.mode == 'rectangle' then assert(#drawing.pending.vertices <= 2, 'Drawing.mouse_release: rectangle has too many pending vertices') if #drawing.pending.vertices == 2 then - local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width) + local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width) if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then local first = drawing.points[drawing.pending.vertices[1]] local second = drawing.points[drawing.pending.vertices[2]] @@ -355,7 +361,7 @@ function Drawing.mouse_release(State, x,y, mouse_button) elseif drawing.pending.mode == 'square' then assert(#drawing.pending.vertices <= 2, 'Drawing.mouse_release: square has too many pending vertices') if #drawing.pending.vertices == 2 then - local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width) + local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width) if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then local first = drawing.points[drawing.pending.vertices[1]] local second = drawing.points[drawing.pending.vertices[2]] @@ -366,14 +372,14 @@ function Drawing.mouse_release(State, x,y, mouse_button) end end elseif drawing.pending.mode == 'circle' then - local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width) + local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width) if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then local center = drawing.points[drawing.pending.center] drawing.pending.radius = round(geom.dist(center.x,center.y, mx,my)) table.insert(drawing.shapes, drawing.pending) end elseif drawing.pending.mode == 'arc' then - local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width) + local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width) if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then local center = drawing.points[drawing.pending.center] drawing.pending.end_angle = geom.angle_with_hint(center.x,center.y, mx,my, drawing.pending.end_angle) @@ -477,13 +483,15 @@ function Drawing.keychord_press(State, chord) end drawing.pending.mode = 'square' elseif App.mouse_down(1) and chord == 'p' and State.current_drawing_mode == 'polygon' then - local _,drawing,line_cache = Drawing.current_drawing(State) - local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-line_cache.starty, State.width) + local drawing_index,drawing = Drawing.current_drawing(State) + local starty = Text.starty(State, drawing_index) + local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-starty, State.width) local j = Drawing.find_or_insert_point(drawing.points, mx,my, State.width) table.insert(drawing.pending.vertices, j) elseif App.mouse_down(1) and chord == 'p' and (State.current_drawing_mode == 'rectangle' or State.current_drawing_mode == 'square') then - local _,drawing,line_cache = Drawing.current_drawing(State) - local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-line_cache.starty, State.width) + local drawing_index,drawing = Drawing.current_drawing(State) + local starty = Text.starty(State, drawing_index) + local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-starty, State.width) local j = Drawing.find_or_insert_point(drawing.points, mx,my, State.width) while #drawing.pending.vertices >= 2 do table.remove(drawing.pending.vertices) @@ -492,9 +500,10 @@ function Drawing.keychord_press(State, chord) elseif chord == 'C-o' and not App.mouse_down(1) then State.current_drawing_mode = 'circle' elseif App.mouse_down(1) and chord == 'a' and State.current_drawing_mode == 'circle' then - local _,drawing,line_cache = Drawing.current_drawing(State) + local drawing_index,drawing = Drawing.current_drawing(State) + local starty = Text.starty(State, drawing_index) drawing.pending.mode = 'arc' - local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-line_cache.starty, State.width) + local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-starty, State.width) local center = drawing.points[drawing.pending.center] drawing.pending.radius = round(geom.dist(center.x,center.y, mx,my)) drawing.pending.start_angle = geom.angle(center.x,center.y, mx,my) @@ -510,7 +519,7 @@ function Drawing.keychord_press(State, chord) end drawing.pending.mode = 'circle' elseif chord == 'C-u' and not App.mouse_down(1) then - local drawing_index,drawing,line_cache,i,p = Drawing.select_point_at_mouse(State) + local drawing_index,drawing,_,i,p = Drawing.select_point_at_mouse(State) if drawing then if State.previous_drawing_mode == nil then State.previous_drawing_mode = State.current_drawing_mode @@ -521,7 +530,7 @@ function Drawing.keychord_press(State, chord) State.lines.current_drawing = drawing end elseif chord == 'C-n' and not App.mouse_down(1) then - local drawing_index,drawing,line_cache,point_index,p = Drawing.select_point_at_mouse(State) + local drawing_index,drawing,_,point_index,p = Drawing.select_point_at_mouse(State) if drawing then if State.previous_drawing_mode == nil then -- don't clobber @@ -619,9 +628,8 @@ function Drawing.current_drawing(State) local x, y = App.mouse_x(), App.mouse_y() for drawing_index,drawing in ipairs(State.lines) do if drawing.mode == 'drawing' then - local line_cache = State.line_cache[drawing_index] - if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) then - return drawing_index,drawing,line_cache + if Drawing.in_drawing(State, drawing_index, x,y, State.left,State.right) then + return drawing_index,drawing end end end @@ -632,12 +640,12 @@ function Drawing.select_shape_at_mouse(State) for drawing_index,drawing in ipairs(State.lines) do if drawing.mode == 'drawing' then local x, y = App.mouse_x(), App.mouse_y() - local line_cache = State.line_cache[drawing_index] - if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) then - local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width) + local starty = Text.starty(State, drawing_index) + if Drawing.in_drawing(State, drawing_index, x,y, State.left,State.right) then + local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width) for i,shape in ipairs(drawing.shapes) do if geom.on_shape(mx,my, drawing, shape) then - return drawing,line_cache,i,shape + return drawing,starty,i,shape end end end @@ -649,12 +657,12 @@ function Drawing.select_point_at_mouse(State) for drawing_index,drawing in ipairs(State.lines) do if drawing.mode == 'drawing' then local x, y = App.mouse_x(), App.mouse_y() - local line_cache = State.line_cache[drawing_index] - if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) then - local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width) + local starty = Text.starty(State, drawing_index) + if Drawing.in_drawing(State, drawing_index, x,y, State.left,State.right) then + local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width) for i,point in ipairs(drawing.points) do if Drawing.near(point, mx,my, State.width) then - return drawing_index,drawing,line_cache,i,point + return drawing_index,drawing,starty,i,point end end end @@ -666,8 +674,7 @@ function Drawing.select_drawing_at_mouse(State) for drawing_index,drawing in ipairs(State.lines) do if drawing.mode == 'drawing' then local x, y = App.mouse_x(), App.mouse_y() - local line_cache = State.line_cache[drawing_index] - if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) then + if Drawing.in_drawing(State, drawing_index, x,y, State.left,State.right) then return drawing end end diff --git a/drawing_tests.lua b/drawing_tests.lua index 64188df..3484436 100644 --- a/drawing_tests.lua +++ b/drawing_tests.lua @@ -32,7 +32,7 @@ function test_draw_line() edit.draw(Editor_state) check_eq(#Editor_state.lines, 2, 'baseline/#lines') check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode') - check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y') + check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y') check_eq(Editor_state.lines[1].h, 128, 'baseline/y') check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes') -- draw a line @@ -77,7 +77,7 @@ function test_draw_horizontal_line() edit.draw(Editor_state) check_eq(#Editor_state.lines, 2, 'baseline/#lines') check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode') - check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y') + check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y') check_eq(Editor_state.lines[1].h, 128, 'baseline/y') check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes') -- draw a line that is more horizontal than vertical @@ -105,7 +105,7 @@ function test_draw_circle() edit.draw(Editor_state) check_eq(#Editor_state.lines, 2, 'baseline/#lines') check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode') - check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y') + check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y') check_eq(Editor_state.lines[1].h, 128, 'baseline/y') check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes') -- draw a circle @@ -134,7 +134,7 @@ function test_cancel_stroke() edit.draw(Editor_state) check_eq(#Editor_state.lines, 2, 'baseline/#lines') check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode') - check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y') + check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y') check_eq(Editor_state.lines[1].h, 128, 'baseline/y') check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes') -- start drawing a line @@ -172,7 +172,7 @@ function test_draw_circle_mid_stroke() edit.draw(Editor_state) check_eq(#Editor_state.lines, 2, 'baseline/#lines') check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode') - check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y') + check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y') check_eq(Editor_state.lines[1].h, 128, 'baseline/y') check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes') -- draw a circle @@ -200,7 +200,7 @@ function test_draw_arc() edit.draw(Editor_state) check_eq(#Editor_state.lines, 2, 'baseline/#lines') check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode') - check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y') + check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y') check_eq(Editor_state.lines[1].h, 128, 'baseline/y') check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes') -- draw an arc @@ -231,7 +231,7 @@ function test_draw_polygon() check_eq(Editor_state.current_drawing_mode, 'line', 'baseline/drawing_mode') check_eq(#Editor_state.lines, 2, 'baseline/#lines') check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode') - check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y') + check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y') check_eq(Editor_state.lines[1].h, 128, 'baseline/y') check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes') -- first point @@ -269,7 +269,7 @@ function test_draw_rectangle() check_eq(Editor_state.current_drawing_mode, 'line', 'baseline/drawing_mode') check_eq(#Editor_state.lines, 2, 'baseline/#lines') check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode') - check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y') + check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y') check_eq(Editor_state.lines[1].h, 128, 'baseline/y') check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes') -- first point @@ -313,7 +313,7 @@ function test_draw_rectangle_intermediate() check_eq(Editor_state.current_drawing_mode, 'line', 'baseline/drawing_mode') check_eq(#Editor_state.lines, 2, 'baseline/#lines') check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode') - check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y') + check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y') check_eq(Editor_state.lines[1].h, 128, 'baseline/y') check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes') -- first point @@ -349,7 +349,7 @@ function test_draw_square() check_eq(Editor_state.current_drawing_mode, 'line', 'baseline/drawing_mode') check_eq(#Editor_state.lines, 2, 'baseline/#lines') check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode') - check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y') + check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y') check_eq(Editor_state.lines[1].h, 128, 'baseline/y') check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes') -- first point diff --git a/edit.lua b/edit.lua index 5a2ab28..7154a7d 100644 --- a/edit.lua +++ b/edit.lua @@ -31,7 +31,6 @@ function edit.initialize_state(top, left, right, font, font_height, line_height) -- string data, -- a drawing is a table with: -- mode = 'drawing' - -- a (y) coord in pixels (updated while painting screen), -- a (h)eight, -- an array of points, and -- an array of shapes @@ -52,7 +51,6 @@ function edit.initialize_state(top, left, right, font, font_height, line_height) -- rendering wrapped text lines needs some additional short-lived data per line: -- startpos, the index of data the line starts rendering from, can only be >1 for topmost line on screen - -- starty, the y coord in pixels the line starts rendering from -- fragments: snippets of the line guaranteed to not straddle screen lines -- screen_line_starting_pos: optional array of grapheme indices if it wraps over more than one screen line line_cache = {}, @@ -184,7 +182,6 @@ function edit.draw(State) Drawing.before = snapshot(State, line_index-1, line_index) table.insert(State.lines, line_index, {mode='drawing', y=y, h=256/2, points={}, shapes={}, pending={}}) table.insert(State.line_cache, line_index, {}) - for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end if State.cursor1.line >= line_index then State.cursor1.line = State.cursor1.line+1 end @@ -276,8 +273,7 @@ function edit.mouse_press(State, x,y, mouse_button) return end elseif line.mode == 'drawing' then - local line_cache = State.line_cache[line_index] - if Drawing.in_drawing(line, line_cache, x, y, State.left,State.right) then + if Drawing.in_drawing(State, line_index, x, y, State.left,State.right) then State.lines.current_drawing_index = line_index State.lines.current_drawing = line Drawing.before = snapshot(State, line_index) @@ -395,7 +391,6 @@ function edit.keychord_press(State, chord, key) Text.delete_selection(State, State.left, State.right) end if State.search_term then - for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll if chord == 'escape' then State.search_term = nil State.cursor1 = State.search_backup.cursor @@ -500,7 +495,6 @@ function edit.keychord_press(State, chord, key) record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)}) -- dispatch to drawing or text elseif App.mouse_down(1) or chord:sub(1,2) == 'C-' then - -- DON'T reset line_cache.starty here local drawing_index, drawing = Drawing.current_drawing(State) if drawing_index then local before = snapshot(State, drawing_index) @@ -537,7 +531,6 @@ function edit.keychord_press(State, chord, key) end schedule_save(State) else - for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll Text.keychord_press(State, chord) end end diff --git a/help.lua b/help.lua index 6f8633b..cc7a0c9 100644 --- a/help.lua +++ b/help.lua @@ -1,8 +1,8 @@ function draw_help_without_mouse_pressed(State, drawing_index) local drawing = State.lines[drawing_index] - local line_cache = State.line_cache[drawing_index] + local starty = Text.starty(State, drawing_index) App.color(Help_color) - local y = line_cache.starty+10 + local y = starty+10 love.graphics.print("Things you can do:", State.left+30,y) y = y + State.line_height love.graphics.print("* Press the mouse button to start drawing a "..current_shape(State), State.left+30,y) @@ -48,14 +48,14 @@ function draw_help_without_mouse_pressed(State, drawing_index) love.graphics.print("Press 'esc' now to hide this message", State.left+30,y) y = y + State.line_height App.color(Help_background_color) - love.graphics.rectangle('fill', State.left,line_cache.starty, State.width, math.max(Drawing.pixels(drawing.h, State.width),y-line_cache.starty)) + love.graphics.rectangle('fill', State.left,starty, State.width, math.max(Drawing.pixels(drawing.h, State.width),y-starty)) end function draw_help_with_mouse_pressed(State, drawing_index) local drawing = State.lines[drawing_index] - local line_cache = State.line_cache[drawing_index] + local starty = Text.starty(State, drawing_index) App.color(Help_color) - local y = line_cache.starty+10 + local y = starty+10 love.graphics.print("You're currently drawing a "..current_shape(State, drawing.pending), State.left+30,y) y = y + State.line_height love.graphics.print('Things you can do now:', State.left+30,y) @@ -129,7 +129,7 @@ function draw_help_with_mouse_pressed(State, drawing_index) y = y + State.line_height end App.color(Help_background_color) - love.graphics.rectangle('fill', State.left,line_cache.starty, State.width, math.max(Drawing.pixels(drawing.h, State.width),y-line_cache.starty)) + love.graphics.rectangle('fill', State.left,starty, State.width, math.max(Drawing.pixels(drawing.h, State.width),y-starty)) end function current_shape(State, shape) diff --git a/select.lua b/select.lua index 78d18db..b67dd16 100644 --- a/select.lua +++ b/select.lua @@ -69,7 +69,7 @@ end function Text.mouse_pos(State) local x,y = App.mouse_x(), App.mouse_y() - if y < State.line_cache[State.screen_top1.line].starty then + if y < State.top then return State.screen_top1.line, State.screen_top1.pos end for line_index,line in ipairs(State.lines) do diff --git a/source.lua b/source.lua index 439efa3..d24957c 100644 --- a/source.lua +++ b/source.lua @@ -307,7 +307,6 @@ function source.mouse_press(x,y, mouse_button) return end log_browser.mouse_press(Log_browser_state, x,y, mouse_button) - for _,line_cache in ipairs(Editor_state.line_cache) do line_cache.starty = nil end -- just in case we scroll end end diff --git a/source_edit.lua b/source_edit.lua index 64c8980..5351857 100644 --- a/source_edit.lua +++ b/source_edit.lua @@ -33,7 +33,6 @@ function edit.initialize_state(top, left, right, font, font_height, line_height) -- string data, -- a drawing is a table with: -- mode = 'drawing' - -- a (y) coord in pixels (updated while painting screen), -- a (h)eight, -- an array of points, and -- an array of shapes @@ -54,7 +53,6 @@ function edit.initialize_state(top, left, right, font, font_height, line_height) -- rendering wrapped text lines needs some additional short-lived data per line: -- startpos, the index of data the line starts rendering from, can only be >1 for topmost line on screen - -- starty, the y coord in pixels the line starts rendering from -- fragments: snippets of the line guaranteed to not straddle screen lines -- screen_line_starting_pos: optional array of grapheme indices if it wraps over more than one screen line line_cache = {}, @@ -189,7 +187,6 @@ function edit.draw(State, hide_cursor, show_line_numbers) Drawing.before = snapshot(State, line_index-1, line_index) table.insert(State.lines, line_index, {mode='drawing', y=y, h=256/2, points={}, shapes={}, pending={}}) table.insert(State.line_cache, line_index, {}) - for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end if State.cursor1.line >= line_index then State.cursor1.line = State.cursor1.line+1 end @@ -280,8 +277,7 @@ function edit.mouse_press(State, x,y, mouse_button) return end elseif line.mode == 'drawing' then - local line_cache = State.line_cache[line_index] - if Drawing.in_drawing(line, line_cache, x, y, State.left,State.right) then + if Drawing.in_drawing(State, line_index, x, y, State.left,State.right) then State.lines.current_drawing_index = line_index State.lines.current_drawing = line Drawing.before = snapshot(State, line_index) @@ -399,7 +395,6 @@ function edit.keychord_press(State, chord, key) Text.delete_selection(State, State.left, State.right) end if State.search_term then - for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll if chord == 'escape' then State.search_term = nil State.cursor1 = State.search_backup.cursor @@ -504,7 +499,6 @@ function edit.keychord_press(State, chord, key) record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)}) -- dispatch to drawing or text elseif App.mouse_down(1) or chord:sub(1,2) == 'C-' then - -- DON'T reset line_cache.starty here local drawing_index, drawing = Drawing.current_drawing(State) if drawing_index then local before = snapshot(State, drawing_index) @@ -541,7 +535,6 @@ function edit.keychord_press(State, chord, key) end schedule_save(State) else - for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll Text.keychord_press(State, chord) end end diff --git a/source_text.lua b/source_text.lua index 0fb1147..d2c68c2 100644 --- a/source_text.lua +++ b/source_text.lua @@ -6,7 +6,6 @@ Text = {} function Text.draw(State, line_index, y, startpos, hide_cursor, show_line_numbers) local line = State.lines[line_index] local line_cache = State.line_cache[line_index] - line_cache.starty = y line_cache.startpos = startpos -- wrap long lines Text.populate_screen_line_starting_pos(State, line_index) @@ -431,6 +430,28 @@ function Text.pageup(State) Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks end +-- return the top y coordinate of a given line_index, +-- or nil if no part of it is on screen +function Text.starty(State, line_index) + -- duplicate some logic from love.draw + -- does not modify State (except to populate line_cache) + if line_index < State.screen_top1.line then return end + local loc2 = Text.to2(State, State.screen_top1) + local y = State.top + while true do + if loc2.line == line_index then return y end + if State.lines[loc2.line].mode == 'text' then + y = y + State.line_height + elseif State.lines[loc2.line].mode == 'drawing' then + y = y + Drawing_padding_height + Drawing.pixels(State.lines[loc2.line].h, State.width) + end + if y + State.line_height > App.screen.height then break end + local next_loc2 = Text.next_screen_line(State, loc2) + if Text.eq2(next_loc2, loc2) then break end -- end of file + loc2 = next_loc2 + end +end + function Text.previous_screen_top1(State) -- duplicate some logic from love.draw -- does not modify State (except to populate line_cache) @@ -811,19 +832,21 @@ end function Text.in_line(State, line_index, x,y) local line = State.lines[line_index] local line_cache = State.line_cache[line_index] - if line_cache.starty == nil then return false end -- outside current page - if y < line_cache.starty then return false end + local starty = Text.starty(State, line_index) + if starty == nil then return false end -- outside current page + if y < starty then return false end Text.populate_screen_line_starting_pos(State, line_index) - return y < line_cache.starty + State.line_height*(#line_cache.screen_line_starting_pos - Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos) + 1) + return y < starty + State.line_height*(#line_cache.screen_line_starting_pos - Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos) + 1) end -- convert mx,my in pixels to schema-1 coordinates function Text.to_pos_on_line(State, line_index, mx, my) local line = State.lines[line_index] local line_cache = State.line_cache[line_index] - assert(my >= line_cache.starty, 'failed to map y pixel to line') + local starty = Text.starty(State, line_index) + assert(my >= starty, 'failed to map y pixel to line') -- duplicate some logic from Text.draw - local y = line_cache.starty + local y = starty local start_screen_line_index = Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos) for screen_line_index = start_screen_line_index,#line_cache.screen_line_starting_pos do local screen_line_starting_pos = line_cache.screen_line_starting_pos[screen_line_index] diff --git a/source_text_tests.lua b/source_text_tests.lua index c17842a..ce9cf3c 100644 --- a/source_text_tests.lua +++ b/source_text_tests.lua @@ -255,7 +255,7 @@ function test_click_moves_cursor() Editor_state.cursor1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1} Editor_state.selection1 = {} - edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache + edit.draw(Editor_state) -- populate line_cache.startpos for each line edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1) check_eq(Editor_state.cursor1.line, 1, 'cursor:line') check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos') @@ -812,7 +812,7 @@ function test_select_text_using_mouse() Editor_state.cursor1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1} Editor_state.selection1 = {} - edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache + edit.draw(Editor_state) -- populate line_cache.startpos for each line -- 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 @@ -831,7 +831,7 @@ function test_select_text_using_mouse_starting_above_text() Editor_state.cursor1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1} Editor_state.selection1 = {} - edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache + edit.draw(Editor_state) -- populate line_cache.startpos for each line -- press mouse above first line of text edit.run_after_mouse_press(Editor_state, Editor_state.left+8,5, 1) check(Editor_state.selection1.line ~= nil, 'selection:line-not-nil') @@ -890,7 +890,7 @@ function test_select_text_using_mouse_and_shift() Editor_state.cursor1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1} Editor_state.selection1 = {} - edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache + edit.draw(Editor_state) -- populate line_cache.startpos for each line -- 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) @@ -914,7 +914,7 @@ function test_select_text_repeatedly_using_mouse_and_shift() Editor_state.cursor1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1} Editor_state.selection1 = {} - edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache + edit.draw(Editor_state) -- populate line_cache.startpos for each line -- 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) diff --git a/text.lua b/text.lua index f203950..c897f0b 100644 --- a/text.lua +++ b/text.lua @@ -7,7 +7,6 @@ function Text.draw(State, line_index, y, startpos) --? print('text.draw', line_index, y) local line = State.lines[line_index] local line_cache = State.line_cache[line_index] - line_cache.starty = y line_cache.startpos = startpos -- wrap long lines Text.populate_screen_line_starting_pos(State, line_index) @@ -357,6 +356,31 @@ function Text.pageup(State) Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks end +-- return the top y coordinate of a given line_index, +-- or nil if no part of it is on screen +function Text.starty(State, line_index) + -- duplicate some logic from love.draw + -- does not modify State (except to populate line_cache) + if line_index < State.screen_top1.line then return end + local loc2 = Text.to2(State, State.screen_top1) + local y = State.top + while true do + if State.lines[loc2.line].mode == 'drawing' then + y = y + Drawing_padding_top + end + if loc2.line == line_index then return y end + if State.lines[loc2.line].mode == 'text' then + y = y + State.line_height + elseif State.lines[loc2.line].mode == 'drawing' then + y = y + Drawing.pixels(State.lines[loc2.line].h, State.width) + Drawing_padding_bottom + end + if y + State.line_height > App.screen.height then break end + local next_loc2 = Text.next_screen_line(State, loc2) + if Text.eq2(next_loc2, loc2) then break end -- end of file + loc2 = next_loc2 + end +end + function Text.previous_screen_top1(State) -- duplicate some logic from love.draw -- does not modify State (except to populate line_cache) @@ -737,19 +761,21 @@ end function Text.in_line(State, line_index, x,y) local line = State.lines[line_index] local line_cache = State.line_cache[line_index] - if line_cache.starty == nil then return false end -- outside current page - if y < line_cache.starty then return false end + local starty = Text.starty(State, line_index) + if starty == nil then return false end -- outside current page + if y < starty then return false end Text.populate_screen_line_starting_pos(State, line_index) - return y < line_cache.starty + State.line_height*(#line_cache.screen_line_starting_pos - Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos) + 1) + return y < starty + State.line_height*(#line_cache.screen_line_starting_pos - Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos) + 1) end -- convert mx,my in pixels to schema-1 coordinates function Text.to_pos_on_line(State, line_index, mx, my) local line = State.lines[line_index] local line_cache = State.line_cache[line_index] - assert(my >= line_cache.starty, 'failed to map y pixel to line') + local starty = Text.starty(State, line_index) + assert(my >= starty, 'failed to map y pixel to line') -- duplicate some logic from Text.draw - local y = line_cache.starty + local y = starty local start_screen_line_index = Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos) for screen_line_index = start_screen_line_index,#line_cache.screen_line_starting_pos do local screen_line_starting_pos = line_cache.screen_line_starting_pos[screen_line_index] diff --git a/text_tests.lua b/text_tests.lua index 40320b1..9e42088 100644 --- a/text_tests.lua +++ b/text_tests.lua @@ -255,7 +255,7 @@ function test_click_moves_cursor() Editor_state.cursor1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1} Editor_state.selection1 = {} - edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache + edit.draw(Editor_state) -- populate line_cache.startpos for each line Editor_state.line_cache edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1) check_eq(Editor_state.cursor1.line, 1, 'cursor:line') check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos') @@ -812,7 +812,7 @@ function test_select_text_using_mouse() Editor_state.cursor1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1} Editor_state.selection1 = {} - edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache + edit.draw(Editor_state) -- populate line_cache.startpos 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 @@ -831,7 +831,7 @@ function test_select_text_using_mouse_starting_above_text() Editor_state.cursor1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1} Editor_state.selection1 = {} - edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache + edit.draw(Editor_state) -- populate line_cache.startpos for each line -- press mouse above first line of text edit.run_after_mouse_press(Editor_state, Editor_state.left+8,5, 1) check(Editor_state.selection1.line ~= nil, 'selection:line-not-nil') @@ -890,7 +890,7 @@ function test_select_text_using_mouse_and_shift() Editor_state.cursor1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1} Editor_state.selection1 = {} - edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache + edit.draw(Editor_state) -- populate line_cache.startpos for each line -- 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) @@ -914,7 +914,7 @@ function test_select_text_repeatedly_using_mouse_and_shift() Editor_state.cursor1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1} Editor_state.selection1 = {} - edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache + edit.draw(Editor_state) -- populate line_cache.startpos for each line -- 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) From 85b09772bae914482f1e50c214091120f5225081 Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Tue, 11 Jun 2024 10:49:34 -0700 Subject: [PATCH 6/9] whitespace --- source_text_tests.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source_text_tests.lua b/source_text_tests.lua index ce9cf3c..11cc823 100644 --- a/source_text_tests.lua +++ b/source_text_tests.lua @@ -231,7 +231,7 @@ function test_skip_multiple_spaces_to_next_word() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=4} -- at the start of second word edit.draw(Editor_state) - edit.run_after_keychord(Editor_state, 'M-right', 'right') + edit.run_after_keychord(Editor_state, 'M-right', 'right') check_eq(Editor_state.cursor1.pos, 9, 'check') end @@ -242,7 +242,7 @@ function test_move_past_end_of_word_on_next_line() Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=8} edit.draw(Editor_state) - edit.run_after_keychord(Editor_state, 'M-right', 'right') + edit.run_after_keychord(Editor_state, 'M-right', 'right') check_eq(Editor_state.cursor1.line, 2, 'line') check_eq(Editor_state.cursor1.pos, 4, 'pos') end @@ -573,7 +573,7 @@ function test_select_text() edit.draw(Editor_state) -- select a letter App.fake_key_press('lshift') - edit.run_after_keychord(Editor_state, 'S-right', 'right') + edit.run_after_keychord(Editor_state, 'S-right', 'right') App.fake_key_release('lshift') edit.key_release(Editor_state, 'lshift') -- selection persists even after shift is released @@ -594,7 +594,7 @@ function test_cursor_movement_without_shift_resets_selection() Editor_state.screen_top1 = {line=1, pos=1} edit.draw(Editor_state) -- press an arrow key without shift - edit.run_after_keychord(Editor_state, 'right', 'right') + edit.run_after_keychord(Editor_state, 'right', 'right') -- no change to data, selection is reset check_nil(Editor_state.selection1.line, 'check') check_eq(Editor_state.lines[1].data, 'abc', 'data') @@ -1641,7 +1641,7 @@ function test_right_arrow_scrolls_down_in_wrapped_line() y = y + Editor_state.line_height App.screen.check(y, 'ghi ', 'baseline/screen:3') -- line wrapping includes trailing whitespace -- after hitting the right arrow the screen scrolls down by one line - edit.run_after_keychord(Editor_state, 'right', 'right') + edit.run_after_keychord(Editor_state, 'right', 'right') check_eq(Editor_state.screen_top1.line, 2, 'screen_top') check_eq(Editor_state.cursor1.line, 3, 'cursor:line') check_eq(Editor_state.cursor1.pos, 6, 'cursor:pos') From 7aa43d1d2dd57792c447ba40cdf05791e3301123 Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Tue, 11 Jun 2024 10:49:56 -0700 Subject: [PATCH 7/9] comment --- text_tests.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text_tests.lua b/text_tests.lua index 9e42088..9b34a24 100644 --- a/text_tests.lua +++ b/text_tests.lua @@ -255,7 +255,7 @@ function test_click_moves_cursor() Editor_state.cursor1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1} Editor_state.selection1 = {} - edit.draw(Editor_state) -- populate line_cache.startpos for each line Editor_state.line_cache + edit.draw(Editor_state) -- populate line_cache.startpos for each line edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1) check_eq(Editor_state.cursor1.line, 1, 'cursor:line') check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos') @@ -812,7 +812,7 @@ function test_select_text_using_mouse() Editor_state.cursor1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1} Editor_state.selection1 = {} - edit.draw(Editor_state) -- populate line_cache.startpos for each line Editor_state.line_cache + edit.draw(Editor_state) -- populate line_cache.startpos for each line -- 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 From 55f5c2d696ffba6c5d220d8e317c15ed2335ac7b Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Tue, 11 Jun 2024 11:10:34 -0700 Subject: [PATCH 8/9] crap, fix some final changes in the source editor --- app.lua | 2 +- source_select.lua | 2 +- source_text.lua | 12 +++++------- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/app.lua b/app.lua index 274f38d..263518c 100644 --- a/app.lua +++ b/app.lua @@ -131,7 +131,7 @@ function App.run_tests() end table.sort(sorted_names) --? App.initialize_for_test() -- debug: run a single test at a time like these 2 lines ---? test_click_moves_cursor() +--? test_click_below_all_lines() for _,name in ipairs(sorted_names) do App.initialize_for_test() --? print('=== '..name) diff --git a/source_select.lua b/source_select.lua index 78d18db..b67dd16 100644 --- a/source_select.lua +++ b/source_select.lua @@ -69,7 +69,7 @@ end function Text.mouse_pos(State) local x,y = App.mouse_x(), App.mouse_y() - if y < State.line_cache[State.screen_top1.line].starty then + if y < State.top then return State.screen_top1.line, State.screen_top1.pos end for line_index,line in ipairs(State.lines) do diff --git a/source_text.lua b/source_text.lua index d2c68c2..6e0c4f9 100644 --- a/source_text.lua +++ b/source_text.lua @@ -439,11 +439,14 @@ function Text.starty(State, line_index) local loc2 = Text.to2(State, State.screen_top1) local y = State.top while true do + if State.lines[loc2.line].mode == 'drawing' then + y = y + Drawing_padding_top + end if loc2.line == line_index then return y end if State.lines[loc2.line].mode == 'text' then y = y + State.line_height elseif State.lines[loc2.line].mode == 'drawing' then - y = y + Drawing_padding_height + Drawing.pixels(State.lines[loc2.line].h, State.width) + y = y + Drawing.pixels(State.lines[loc2.line].h, State.width) + Drawing_padding_bottom end if y + State.line_height > App.screen.height then break end local next_loc2 = Text.next_screen_line(State, loc2) @@ -1102,13 +1105,8 @@ function Text.tweak_screen_top_and_cursor(State) if Text.lt1(State.cursor1, State.screen_top1) then State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos} elseif State.cursor1.line >= screen_bottom1.line then ---? print('too low') if Text.cursor_out_of_screen(State) then ---? print('tweak') - State.cursor1 = { - line=screen_bottom1.line, - pos=Text.to_pos_on_line(State, screen_bottom1.line, State.right-5, App.screen.height-5), - } + State.cursor1 = Text.final_text_loc_on_screen(State) end end end From 62d8911c9e24b0fbe4e87484a0dd2c4139749277 Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Tue, 11 Jun 2024 12:33:30 -0700 Subject: [PATCH 9/9] comment out debug prints while fixing merge conflicts --- text.lua | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/text.lua b/text.lua index 79bff98..5814693 100644 --- a/text.lua +++ b/text.lua @@ -440,45 +440,45 @@ function Text.up(State) end function Text.down(State) - print('down', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos) +--? print('down', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos) assert(State.cursor1.pos, 'cursor has no pos') if Text.cursor_at_final_screen_line(State) then -- line is done, skip to next text line - print('cursor at final screen line of its line') +--? print('cursor at final screen line of its line') if State.cursor1.line < #State.lines then local new_cursor_line = State.cursor1.line+1 State.cursor1.line = new_cursor_line State.cursor1.pos = Text.nearest_cursor_pos(State.font, State.lines[State.cursor1.line].data, State.cursor_x, State.left) - print(State.cursor1.pos) +--? print(State.cursor1.pos) end local screen_bottom1 = Text.screen_bottom1(State) - print('down 2', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, screen_bottom1.line, screen_bottom1.pos) +--? print('down 2', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, screen_bottom1.line, screen_bottom1.pos) if State.cursor1.line > screen_bottom1.line then - print('screen top before:', State.screen_top1.line, State.screen_top1.pos) - print('scroll up preserving cursor') +--? print('screen top before:', State.screen_top1.line, State.screen_top1.pos) +--? print('scroll up preserving cursor') Text.snap_cursor_to_bottom_of_screen(State) - print('screen top after:', State.screen_top1.line, State.screen_top1.pos) +--? print('screen top after:', State.screen_top1.line, State.screen_top1.pos) end else -- move down one screen line in current line local screen_bottom1 = Text.screen_bottom1(State) local scroll_down = Text.le1(screen_bottom1, State.cursor1) - print('cursor is NOT at final screen line of its line') +--? 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)) +--? 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) local s = string.sub(State.lines[State.cursor1.line].data, new_screen_line_starting_byte_offset) State.cursor1.pos = new_screen_line_starting_pos + Text.nearest_cursor_pos(State.font, s, State.cursor_x, State.left) - 1 - print('cursor pos is now', State.cursor1.line, State.cursor1.pos) +--? print('cursor pos is now', State.cursor1.line, State.cursor1.pos) if scroll_down then - print('scroll up preserving cursor') +--? print('scroll up preserving cursor') Text.snap_cursor_to_bottom_of_screen(State) - print('screen top after:', State.screen_top1.line, State.screen_top1.pos) +--? print('screen top after:', State.screen_top1.line, State.screen_top1.pos) end end - print('=>', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos) +--? print('=>', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos) end function Text.start_of_line(State)