From 81ebc6a55998f59c34a725bd47d77e88fb0eb341 Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Fri, 17 Mar 2023 19:58:49 -0700 Subject: bugfix: disallow font size of 0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks Mikoláš Štrajt. --- edit.lua | 6 ++++-- source_edit.lua | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/edit.lua b/edit.lua index 1d8e0b3..a939bc4 100644 --- a/edit.lua +++ b/edit.lua @@ -368,8 +368,10 @@ function edit.keychord_press(State, chord, key) edit.update_font_settings(State, State.font_height+2) Text.redraw_all(State) elseif chord == 'C--' then - edit.update_font_settings(State, State.font_height-2) - Text.redraw_all(State) + if State.font_height > 2 then + edit.update_font_settings(State, State.font_height-2) + Text.redraw_all(State) + end elseif chord == 'C-0' then edit.update_font_settings(State, 20) Text.redraw_all(State) diff --git a/source_edit.lua b/source_edit.lua index f340ab3..5c90075 100644 --- a/source_edit.lua +++ b/source_edit.lua @@ -405,8 +405,10 @@ function edit.keychord_press(State, chord, key) edit.update_font_settings(State, State.font_height+2) Text.redraw_all(State) elseif chord == 'C--' then - edit.update_font_settings(State, State.font_height-2) - Text.redraw_all(State) + if State.font_height > 2 then + edit.update_font_settings(State, State.font_height-2) + Text.redraw_all(State) + end elseif chord == 'C-0' then edit.update_font_settings(State, 20) Text.redraw_all(State) -- cgit 1.4.1-2-gfad0 From ae429cd78a1f52272d5f51991ebb9b04b542ce01 Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Fri, 17 Mar 2023 21:18:17 -0700 Subject: bring a few things in sync between run and source --- edit.lua | 5 ++++- source_edit.lua | 8 +++++--- source_text.lua | 24 ++++++++++++++++++++---- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/edit.lua b/edit.lua index a939bc4..715fd1a 100644 --- a/edit.lua +++ b/edit.lua @@ -143,7 +143,10 @@ end function edit.draw(State) State.button_handlers = {} App.color(Text_color) - assert(#State.lines == #State.line_cache) + if #State.lines ~= #State.line_cache then + print(('line_cache is out of date; %d when it should be %d'):format(#State.line_cache, #State.lines)) + assert(false) + end if not Text.le1(State.screen_top1, State.cursor1) then print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos) assert(false) diff --git a/source_edit.lua b/source_edit.lua index 5c90075..a296c53 100644 --- a/source_edit.lua +++ b/source_edit.lua @@ -96,8 +96,8 @@ function edit.initialize_state(top, left, right, font_height, line_height) -- c em = App.newText(love.graphics.getFont(), 'm'), -- widest possible character width top = top, - left = left, - right = right, + left = math.floor(left), + right = math.floor(right), width = right-left, filename = love.filesystem.getUserDirectory()..'/lines.txt', -- '/' should work even on Windows @@ -227,12 +227,14 @@ function edit.quit(State) -- make sure to save before quitting if State.next_save then save_to_disk(State) + -- give some time for the OS to flush everything to disk + love.timer.sleep(0.1) end end function edit.mouse_press(State, x,y, mouse_button) if State.search_term then return end ---? print('press') +--? print('press', State.selection1.line, State.selection1.pos) if mouse_press_consumed_by_any_button_handler(State, x,y, mouse_button) then -- press on a button and it returned 'true' to short-circuit return diff --git a/source_text.lua b/source_text.lua index 7c00191..1a43842 100644 --- a/source_text.lua +++ b/source_text.lua @@ -1721,10 +1721,26 @@ function rtrim(s) return s:gsub('%s+$', '') end -function starts_with(s, sub) - return s:find(sub, 1, --[[no escapes]] true) == 1 +function starts_with(s, prefix) + if #s < #prefix then + return false + end + for i=1,#prefix do + if s:sub(i,i) ~= prefix:sub(i,i) then + return false + end + end + return true end -function ends_with(s, sub) - return s:reverse():find(sub:reverse(), 1, --[[no escapes]] true) == 1 +function ends_with(s, suffix) + if #s < #suffix then + return false + end + for i=0,#suffix-1 do + if s:sub(#s-i,#s-i) ~= suffix:sub(#suffix-i,#suffix-i) then + return false + end + end + return true end -- cgit 1.4.1-2-gfad0 From 8c373fdb60620747c7997819538037df190335c0 Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Fri, 17 Mar 2023 21:46:42 -0700 Subject: get rid of all bifold text It's just uneconomic to maintain given how little I've used it. I have a bug right now and no time to port the bugfix to all the complexities of the B side. I briefly considered tossing out the entire source editor. But I _have_ been using it to browse logs across sessions. The live editor doesn't quite cover all my use cases just yet. We now have duplication in the source editor only for: * syntax highlighting * hyperlinking [[WikiWords]] * ability to hide cursor (when showing file browser or Focus is in log browser) --- source.lua | 14 +- source_edit.lua | 103 ++----- source_text.lua | 876 ++++++-------------------------------------------------- text.lua | 1 - 4 files changed, 120 insertions(+), 874 deletions(-) diff --git a/source.lua b/source.lua index d0836cb..f224459 100644 --- a/source.lua +++ b/source.lua @@ -75,7 +75,7 @@ function source.initialize() love.window.setTitle('lines.love - source') end --- environment for a mutable file of bifolded text +-- environment for a mutable file -- TODO: some initialization is also happening in load_settings/initialize_default_settings. Clean that up. function source.initialize_edit_side() load_from_disk(Editor_state) @@ -89,17 +89,12 @@ function source.initialize_edit_side() end edit.check_locs(Editor_state) - -- We currently start out with side B collapsed. - -- Other options: - -- * save all expanded state by line - -- * expand all if any location is in side B if Editor_state.cursor1.line > #Editor_state.lines then Editor_state.cursor1 = {line=1, pos=1} end if Editor_state.screen_top1.line > #Editor_state.lines then Editor_state.screen_top1 = {line=1, pos=1} end - edit.eradicate_locations_after_the_fold(Editor_state) if rawget(_G, 'jit') then jit.off() @@ -253,13 +248,6 @@ end function source.quit() edit.quit(Editor_state) log_browser.quit(Log_browser_state) - -- convert any bifold files here -end - -function source.convert_bifold_text(infilename, outfilename) - local contents = love.filesystem.read(infilename) - contents = contents:gsub('\u{1e}', ';') - love.filesystem.write(outfilename, contents) end function source.settings() diff --git a/source_edit.lua b/source_edit.lua index a296c53..964f6ff 100644 --- a/source_edit.lua +++ b/source_edit.lua @@ -10,8 +10,6 @@ Highlight_color = {r=0.7, g=0.7, b=0.9} -- selected text Icon_color = {r=0.7, g=0.7, b=0.7} -- color of current mode icon in drawings Help_color = {r=0, g=0.5, b=0} Help_background_color = {r=0, g=0.5, b=0, a=0.1} -Fold_color = {r=0, g=0.6, b=0} -Fold_background_color = {r=0, g=0.7, b=0} Margin_top = 15 Margin_left = 25 @@ -28,12 +26,10 @@ edit = {} -- run in both tests and a real run function edit.initialize_state(top, left, right, font_height, line_height) -- currently always draws to bottom of screen local result = { - -- a line is either bifold text or a drawing - -- a line of bifold text consists of an A side and an optional B side + -- a line is either text or a drawing + -- a text is a table with: -- mode = 'text', -- string data, - -- string dataB, - -- expanded: whether to show B side -- a drawing is a table with: -- mode = 'drawing' -- a (y) coord in pixels (updated while painting screen), @@ -50,7 +46,7 @@ function edit.initialize_state(top, left, right, font_height, line_height) -- c -- Unless otherwise specified, coord fields are normalized; a drawing is always 256 units wide -- The field names are carefully chosen so that switching modes in midstream -- remembers previously entered points where that makes sense. - lines = {{mode='text', data='', dataB=nil, expanded=nil}}, -- array of lines + lines = {{mode='text', data=''}}, -- array of lines -- Lines can be too long to fit on screen, in which case they _wrap_ into -- multiple _screen lines_. @@ -65,16 +61,15 @@ function edit.initialize_state(top, left, right, font_height, line_height) -- c -- Given wrapping, any potential location for the text cursor can be described in two ways: -- * schema 1: As a combination of line index and position within a line (in utf8 codepoint units) -- * schema 2: As a combination of line index, screen line index within the line, and a position within the screen line. - -- Positions (and screen line indexes) can be in either the A or the B side. -- -- Most of the time we'll only persist positions in schema 1, translating to -- schema 2 when that's convenient. -- -- Make sure these coordinates are never aliased, so that changing one causes -- action at a distance. - screen_top1 = {line=1, pos=1, posB=nil}, -- position of start of screen line at top of screen - cursor1 = {line=1, pos=1, posB=nil}, -- position of cursor - screen_bottom1 = {line=1, pos=1, posB=nil}, -- position of start of screen line at bottom of screen + screen_top1 = {line=1, pos=1}, -- position of start of screen line at top of screen + cursor1 = {line=1, pos=1}, -- position of cursor + 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 @@ -154,7 +149,7 @@ function edit.draw(State, hide_cursor) assert(false) end if not Text.le1(State.screen_top1, State.cursor1) then - print(State.screen_top1.line, State.screen_top1.pos, State.screen_top1.posB, State.cursor1.line, State.cursor1.pos, State.cursor1.posB) + print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos) assert(false) end State.cursor_x = nil @@ -163,18 +158,14 @@ function edit.draw(State, hide_cursor) --? print('== draw') for line_index = State.screen_top1.line,#State.lines do local line = State.lines[line_index] ---? print('draw:', y, line_index, line, line.mode) +--? print('draw:', y, line_index, line) if y + State.line_height > App.screen.height then break end - State.screen_bottom1 = {line=line_index, pos=nil, posB=nil} + State.screen_bottom1 = {line=line_index, pos=nil} if line.mode == 'text' then ---? print('text.draw', y, line_index) - local startpos, startposB = 1, nil +--? print('text.draw', y, line_index) + local startpos = 1 if line_index == State.screen_top1.line then - if State.screen_top1.pos then - startpos = State.screen_top1.pos - else - startpos, startposB = nil, State.screen_top1.posB - end + startpos = State.screen_top1.pos end if line.data == '' then -- button to insert new drawing @@ -192,7 +183,7 @@ function edit.draw(State, hide_cursor) end, }) end - y, State.screen_bottom1.pos, State.screen_bottom1.posB = Text.draw(State, line_index, y, startpos, startposB, hide_cursor) + y, State.screen_bottom1.pos = Text.draw(State, line_index, y, startpos, hide_cursor) y = y + State.line_height --? print('=> y', y) elseif line.mode == 'drawing' then @@ -255,10 +246,11 @@ 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() - local pos,posB = Text.to_pos_on_line(State, line_index, x, y) - --? print(x,y, 'setting cursor:', line_index, pos, posB) - State.selection1 = {line=line_index, pos=pos, posB=posB} ---? print('selection', State.selection1.line, State.selection1.pos, State.selection1.posB) + State.selection1 = { + line=line_index, + pos=Text.to_pos_on_line(State, line_index, x, y), + } +--? print('selection', State.selection1.line, State.selection1.pos) break end elseif line.mode == 'drawing' then @@ -289,9 +281,11 @@ function edit.mouse_release(State, x,y, mouse_button) if line.mode == 'text' then if Text.in_line(State, line_index, x,y) then --? print('reset selection') - local pos,posB = Text.to_pos_on_line(State, line_index, x, y) - State.cursor1 = {line=line_index, pos=pos, posB=posB} ---? print('cursor', State.cursor1.line, State.cursor1.pos, State.cursor1.posB) + State.cursor1 = { + line=line_index, + pos=Text.to_pos_on_line(State, line_index, x, y), + } +--? print('cursor', State.cursor1.line, State.cursor1.pos) if State.mousepress_shift then if State.old_selection1.line == nil then State.selection1 = State.old_cursor1 @@ -360,11 +354,7 @@ function edit.keychord_press(State, chord, key) State.search_term = string.sub(State.search_term, 1, byte_offset-1) State.search_text = nil elseif chord == 'down' then - if State.cursor1.pos then - State.cursor1.pos = State.cursor1.pos+1 - else - State.cursor1.posB = State.cursor1.posB+1 - end + State.cursor1.pos = State.cursor1.pos+1 Text.search_next(State) elseif chord == 'up' then Text.search_previous(State) @@ -373,35 +363,10 @@ function edit.keychord_press(State, chord, key) elseif chord == 'C-f' then State.search_term = '' State.search_backup = { - cursor={line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB}, - screen_top={line=State.screen_top1.line, pos=State.screen_top1.pos, posB=State.screen_top1.posB}, + cursor={line=State.cursor1.line, pos=State.cursor1.pos}, + screen_top={line=State.screen_top1.line, pos=State.screen_top1.pos}, } assert(State.search_text == nil) - -- bifold text - elseif chord == 'M-b' then - State.expanded = not State.expanded - Text.redraw_all(State) - if not State.expanded then - for _,line in ipairs(State.lines) do - line.expanded = nil - end - edit.eradicate_locations_after_the_fold(State) - end - elseif chord == 'M-d' then - if State.cursor1.posB == nil then - local before = snapshot(State, State.cursor1.line) - if State.lines[State.cursor1.line].dataB == nil then - State.lines[State.cursor1.line].dataB = '' - end - State.lines[State.cursor1.line].expanded = true - State.cursor1.pos = nil - State.cursor1.posB = 1 - if Text.cursor_out_of_screen(State) then - Text.snap_cursor_to_bottom_of_screen(State, State.left, State.right) - end - schedule_save(State) - record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)}) - end -- zoom elseif chord == 'C-=' then edit.update_font_settings(State, State.font_height+2) @@ -449,7 +414,7 @@ function edit.keychord_press(State, chord, key) -- clipboard elseif chord == 'C-a' then State.selection1 = {line=1, pos=1} - State.cursor1 = {line=#State.lines, pos=utf8.len(State.lines[#State.lines].data)+1, posB=nil} + State.cursor1 = {line=#State.lines, pos=utf8.len(State.lines[#State.lines].data)+1} elseif chord == 'C-c' then local s = Text.selection(State) if s then @@ -524,20 +489,6 @@ function edit.keychord_press(State, chord, key) end end -function edit.eradicate_locations_after_the_fold(State) - -- eradicate side B from any locations we track - if State.cursor1.posB then - State.cursor1.posB = nil - State.cursor1.pos = utf8.len(State.lines[State.cursor1.line].data) - State.cursor1.pos = Text.pos_at_start_of_screen_line(State, State.cursor1) - end - if State.screen_top1.posB then - State.screen_top1.posB = nil - State.screen_top1.pos = utf8.len(State.lines[State.screen_top1.line].data) - State.screen_top1.pos = Text.pos_at_start_of_screen_line(State, State.screen_top1) - end -end - function edit.key_release(State, key, scancode) end diff --git a/source_text.lua b/source_text.lua index 1a43842..4cf3edd 100644 --- a/source_text.lua +++ b/source_text.lua @@ -1,90 +1,16 @@ -- text editor, particularly text drawing, horizontal wrap, vertical scrolling Text = {} -AB_padding = 20 -- space in pixels between A side and B side -- draw a line starting from startpos to screen at y between State.left and State.right --- return the final y, and pos,posB of start of final screen line drawn -function Text.draw(State, line_index, y, startpos, startposB, hide_cursor) +-- return the final y, and position of start of final screen line drawn +function Text.draw(State, line_index, y, startpos, hide_cursor) local line = State.lines[line_index] local line_cache = State.line_cache[line_index] line_cache.starty = y line_cache.startpos = startpos - line_cache.startposB = startposB - -- draw A side - local overflows_screen, x, pos, screen_line_starting_pos - if startpos then - overflows_screen, x, y, pos, screen_line_starting_pos = Text.draw_wrapping_line(State, line_index, State.left, y, startpos) - if overflows_screen then - return y, screen_line_starting_pos - end - if Focus == 'edit' and State.cursor1.pos then - if not hide_cursor and not State.search_term then - if line_index == State.cursor1.line and State.cursor1.pos == pos then - Text.draw_cursor(State, x, y) - end - end - end - else - x = State.left - end - -- check for B side ---? if line_index == 8 then print('checking for B side') end - if line.dataB == nil then - assert(y) - assert(screen_line_starting_pos) ---? if line_index == 8 then print('return 1') end - return y, screen_line_starting_pos - end - if not State.expanded and not line.expanded then - assert(y) - assert(screen_line_starting_pos) ---? if line_index == 8 then print('return 2') end - button(State, 'expand', {x=x+AB_padding, y=y+2, w=App.width(State.em), h=State.line_height-4, color={1,1,1}, - icon = function(button_params) - App.color(Fold_background_color) - love.graphics.rectangle('fill', button_params.x, button_params.y, App.width(State.em), State.line_height-4, 2,2) - end, - onpress1 = function() - line.expanded = true - end, - }) - return y, screen_line_starting_pos - end - -- draw B side ---? if line_index == 8 then print('drawing B side') end - App.color(Fold_color) - if startposB then - overflows_screen, x, y, pos, screen_line_starting_pos = Text.draw_wrapping_lineB(State, line_index, x,y, startposB) - else - overflows_screen, x, y, pos, screen_line_starting_pos = Text.draw_wrapping_lineB(State, line_index, x+AB_padding,y, 1) - end - if overflows_screen then - return y, nil, screen_line_starting_pos - end ---? if line_index == 8 then print('a') end - if Focus == 'edit' and State.cursor1.posB then ---? if line_index == 8 then print('b') end - if not hide_cursor and not State.search_term then ---? if line_index == 8 then print('c', State.cursor1.line, State.cursor1.posB, line_index, pos) end - if line_index == State.cursor1.line and State.cursor1.posB == pos then - Text.draw_cursor(State, x, y) - end - end - end - return y, nil, screen_line_starting_pos -end - --- Given an array of fragments, draw the subset starting from pos to screen --- starting from (x,y). --- Return: --- - whether we got to bottom of screen before end of line --- - the final (x,y) --- - the final pos --- - starting pos of the final screen line drawn -function Text.draw_wrapping_line(State, line_index, x,y, startpos) - local line = State.lines[line_index] - local line_cache = State.line_cache[line_index] ---? print('== line', line_index, '^'..line.data..'$') + -- wrap long lines + local x = State.left + local pos = 1 local screen_line_starting_pos = startpos Text.compute_fragments(State, line_index) local pos = 1 @@ -105,7 +31,7 @@ function Text.draw_wrapping_line(State, line_index, x,y, startpos) assert(x > State.left) -- no overfull lines y = y + State.line_height if y + State.line_height > App.screen.height then - return --[[screen filled]] true, x,y, pos, screen_line_starting_pos + return y, screen_line_starting_pos end screen_line_starting_pos = pos x = State.left @@ -130,7 +56,7 @@ function Text.draw_wrapping_line(State, line_index, x,y, startpos) end App.screen.draw(frag_text, x,y) -- render cursor if necessary - if State.cursor1.pos and line_index == State.cursor1.line then + if line_index == State.cursor1.line then if pos <= State.cursor1.pos and pos + frag_len > State.cursor1.pos then if State.search_term then if State.lines[State.cursor1.line].data:sub(State.cursor1.pos, State.cursor1.pos+utf8.len(State.search_term)-1) == State.search_term then @@ -148,59 +74,12 @@ function Text.draw_wrapping_line(State, line_index, x,y, startpos) end pos = pos + frag_len end - return false, x,y, pos, screen_line_starting_pos -end - -function Text.draw_wrapping_lineB(State, line_index, x,y, startpos) - local line = State.lines[line_index] - local line_cache = State.line_cache[line_index] - local screen_line_starting_pos = startpos - Text.compute_fragmentsB(State, line_index, x) - local pos = 1 - for _, f in ipairs(line_cache.fragmentsB) do - local frag, frag_text = f.data, f.text - local frag_len = utf8.len(frag) ---? print('text.draw:', frag, 'at', line_index,pos, 'after', x,y) - if pos < startpos then - -- render nothing ---? print('skipping', frag) - else - -- render fragment - local frag_width = App.width(frag_text) - if x + frag_width > State.right then - assert(x > State.left) -- no overfull lines - y = y + State.line_height - if y + State.line_height > App.screen.height then - return --[[screen filled]] true, x,y, pos, screen_line_starting_pos - end - screen_line_starting_pos = pos - x = State.left - end - if State.selection1.line then - local lo, hi = Text.clip_selection(State, line_index, pos, pos+frag_len) - Text.draw_highlight(State, line, x,y, pos, lo,hi) - end - App.screen.draw(frag_text, x,y) - -- render cursor if necessary - if State.cursor1.posB and line_index == State.cursor1.line then - if pos <= State.cursor1.posB and pos + frag_len > State.cursor1.posB then - if State.search_term then - if State.lines[State.cursor1.line].dataB:sub(State.cursor1.posB, State.cursor1.posB+utf8.len(State.search_term)-1) == State.search_term then - local lo_px = Text.draw_highlight(State, line, x,y, pos, State.cursor1.posB, State.cursor1.posB+utf8.len(State.search_term)) - App.color(Fold_color) - love.graphics.print(State.search_term, x+lo_px,y) - end - elseif Focus == 'edit' then - Text.draw_cursor(State, x+Text.x(frag, State.cursor1.posB-pos+1), y) - App.color(Fold_color) - end - end - end - x = x + frag_width + if Focus == 'edit' and not hide_cursor and State.search_term == nil then + if line_index == State.cursor1.line and State.cursor1.pos == pos then + Text.draw_cursor(State, x, y) end - pos = pos + frag_len end - return false, x,y, pos, screen_line_starting_pos + return y, screen_line_starting_pos end function Text.draw_cursor(State, x, y) @@ -285,74 +164,6 @@ function Text.compute_fragments(State, line_index) end end -function Text.populate_screen_line_starting_posB(State, line_index, x) - local line = State.lines[line_index] - local line_cache = State.line_cache[line_index] - if line_cache.screen_line_starting_posB then - return - end - -- duplicate some logic from Text.draw - Text.compute_fragmentsB(State, line_index, x) - line_cache.screen_line_starting_posB = {1} - local pos = 1 - for _, f in ipairs(line_cache.fragmentsB) do - local frag, frag_text = f.data, f.text - -- render fragment - local frag_width = App.width(frag_text) - if x + frag_width > State.right then - x = State.left - table.insert(line_cache.screen_line_starting_posB, pos) - end - x = x + frag_width - local frag_len = utf8.len(frag) - pos = pos + frag_len - end -end - -function Text.compute_fragmentsB(State, line_index, x) ---? print('compute_fragmentsB', line_index, 'between', x, State.right) - local line = State.lines[line_index] - local line_cache = State.line_cache[line_index] - if line_cache.fragmentsB then - return - end - line_cache.fragmentsB = {} - -- try to wrap at word boundaries - for frag in line.dataB:gmatch('%S*%s*') do - local frag_text = App.newText(love.graphics.getFont(), frag) - local frag_width = App.width(frag_text) ---? print('x: '..tostring(x)..'; '..tostring(State.right-x)..'px to go') - while x + frag_width > State.right do ---? print(('checking whether to split fragment ^%s$ of width %d when rendering from %d'):format(frag, frag_width, x)) - if (x-State.left) < 0.8 * (State.right-State.left) then ---? print('splitting') - -- long word; chop it at some letter - -- We're not going to reimplement TeX here. - local bpos = Text.nearest_pos_less_than(frag, State.right - x) ---? print('bpos', bpos) - if bpos == 0 then break end -- avoid infinite loop when window is too narrow - local boffset = Text.offset(frag, bpos+1) -- byte _after_ bpos ---? print('space for '..tostring(bpos)..' graphemes, '..tostring(boffset-1)..' bytes') - local frag1 = string.sub(frag, 1, boffset-1) - local frag1_text = App.newText(love.graphics.getFont(), frag1) - local frag1_width = App.width(frag1_text) ---? print('extracting ^'..frag1..'$ of width '..tostring(frag1_width)..'px') - assert(x + frag1_width <= State.right) - table.insert(line_cache.fragmentsB, {data=frag1, text=frag1_text}) - frag = string.sub(frag, boffset) - frag_text = App.newText(love.graphics.getFont(), frag) - frag_width = App.width(frag_text) - end - x = State.left -- new line - end - if #frag > 0 then ---? print('inserting ^'..frag..'$ of width '..tostring(frag_width)..'px') - table.insert(line_cache.fragmentsB, {data=frag, text=frag_text}) - end - x = x + frag_width - end -end - function Text.text_input(State, t) if App.mouse_down(1) then return end if App.ctrl_down() or App.alt_down() or App.cmd_down() then return end @@ -367,18 +178,10 @@ function Text.text_input(State, t) end function Text.insert_at_cursor(State, t) - if State.cursor1.pos then - local byte_offset = Text.offset(State.lines[State.cursor1.line].data, State.cursor1.pos) - State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_offset-1)..t..string.sub(State.lines[State.cursor1.line].data, byte_offset) - Text.clear_screen_line_cache(State, State.cursor1.line) - State.cursor1.pos = State.cursor1.pos+1 - else - assert(State.cursor1.posB) - local byte_offset = Text.offset(State.lines[State.cursor1.line].dataB, State.cursor1.posB) - State.lines[State.cursor1.line].dataB = string.sub(State.lines[State.cursor1.line].dataB, 1, byte_offset-1)..t..string.sub(State.lines[State.cursor1.line].dataB, byte_offset) - Text.clear_screen_line_cache(State, State.cursor1.line) - State.cursor1.posB = State.cursor1.posB+1 - end + local byte_offset = Text.offset(State.lines[State.cursor1.line].data, State.cursor1.pos) + State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_offset-1)..t..string.sub(State.lines[State.cursor1.line].data, byte_offset) + Text.clear_screen_line_cache(State, State.cursor1.line) + State.cursor1.pos = State.cursor1.pos+1 end -- Don't handle any keys here that would trigger text_input above. @@ -413,7 +216,7 @@ function Text.keychord_press(State, chord) return end local before - if State.cursor1.pos and State.cursor1.pos > 1 then + if State.cursor1.pos > 1 then before = snapshot(State, State.cursor1.line) local byte_start = utf8.offset(State.lines[State.cursor1.line].data, State.cursor1.pos-1) local byte_end = utf8.offset(State.lines[State.cursor1.line].data, State.cursor1.pos) @@ -425,22 +228,6 @@ function Text.keychord_press(State, chord) end State.cursor1.pos = State.cursor1.pos-1 end - elseif State.cursor1.posB then - if State.cursor1.posB > 1 then - before = snapshot(State, State.cursor1.line) - local byte_start = utf8.offset(State.lines[State.cursor1.line].dataB, State.cursor1.posB-1) - local byte_end = utf8.offset(State.lines[State.cursor1.line].dataB, State.cursor1.posB) - if byte_start then - if byte_end then - State.lines[State.cursor1.line].dataB = string.sub(State.lines[State.cursor1.line].dataB, 1, byte_start-1)..string.sub(State.lines[State.cursor1.line].dataB, byte_end) - else - State.lines[State.cursor1.line].dataB = string.sub(State.lines[State.cursor1.line].dataB, 1, byte_start-1) - end - State.cursor1.posB = State.cursor1.posB-1 - end - else - -- refuse to delete past beginning of side B - end elseif State.cursor1.line > 1 then before = snapshot(State, State.cursor1.line-1, State.cursor1.line) if State.lines[State.cursor1.line-1].mode == 'drawing' then @@ -476,12 +263,12 @@ function Text.keychord_press(State, chord) return end local before - if State.cursor1.posB or State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) then + if State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) then before = snapshot(State, State.cursor1.line) else before = snapshot(State, State.cursor1.line, State.cursor1.line+1) end - if State.cursor1.pos and State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) then + if State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) then local byte_start = utf8.offset(State.lines[State.cursor1.line].data, State.cursor1.pos) local byte_end = utf8.offset(State.lines[State.cursor1.line].data, State.cursor1.pos+1) if byte_start then @@ -492,27 +279,10 @@ function Text.keychord_press(State, chord) end -- no change to State.cursor1.pos end - elseif State.cursor1.posB then - if State.cursor1.posB <= utf8.len(State.lines[State.cursor1.line].dataB) then - local byte_start = utf8.offset(State.lines[State.cursor1.line].dataB, State.cursor1.posB) - local byte_end = utf8.offset(State.lines[State.cursor1.line].dataB, State.cursor1.posB+1) - if byte_start then - if byte_end then - State.lines[State.cursor1.line].dataB = string.sub(State.lines[State.cursor1.line].dataB, 1, byte_start-1)..string.sub(State.lines[State.cursor1.line].dataB, byte_end) - else - State.lines[State.cursor1.line].dataB = string.sub(State.lines[State.cursor1.line].dataB, 1, byte_start-1) - end - -- no change to State.cursor1.pos - end - else - -- refuse to delete past end of side B - end elseif State.cursor1.line < #State.lines then if State.lines[State.cursor1.line+1].mode == 'text' then -- join lines State.lines[State.cursor1.line].data = State.lines[State.cursor1.line].data..State.lines[State.cursor1.line+1].data - -- delete side B on first line - State.lines[State.cursor1.line].dataB = State.lines[State.cursor1.line+1].dataB end table.remove(State.lines, State.cursor1.line+1) table.remove(State.line_cache, State.cursor1.line+1) @@ -529,12 +299,12 @@ function Text.keychord_press(State, chord) State.selection1 = {} elseif chord == 'S-left' then if State.selection1.line == nil then - State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB} + State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} end Text.left(State) elseif chord == 'S-right' then if State.selection1.line == nil then - State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB} + State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} end Text.right(State) -- C- hotkeys reserved for drawings, so we'll use M- @@ -546,12 +316,12 @@ function Text.keychord_press(State, chord) State.selection1 = {} elseif chord == 'M-S-left' then if State.selection1.line == nil then - State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB} + State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} end Text.word_left(State) elseif chord == 'M-S-right' then if State.selection1.line == nil then - State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB} + State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} end Text.word_right(State) elseif chord == 'home' then @@ -562,12 +332,12 @@ function Text.keychord_press(State, chord) State.selection1 = {} elseif chord == 'S-home' then if State.selection1.line == nil then - State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB} + State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} end Text.start_of_line(State) elseif chord == 'S-end' then if State.selection1.line == nil then - State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB} + State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} end Text.end_of_line(State) elseif chord == 'up' then @@ -578,12 +348,12 @@ function Text.keychord_press(State, chord) State.selection1 = {} elseif chord == 'S-up' then if State.selection1.line == nil then - State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB} + State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} end Text.up(State) elseif chord == 'S-down' then if State.selection1.line == nil then - State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB} + State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} end Text.down(State) elseif chord == 'pageup' then @@ -594,30 +364,24 @@ function Text.keychord_press(State, chord) State.selection1 = {} elseif chord == 'S-pageup' then if State.selection1.line == nil then - State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB} + State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} end Text.pageup(State) elseif chord == 'S-pagedown' then if State.selection1.line == nil then - State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB} + State.selection1 = {line=State.cursor1.line, pos=State.cursor1.pos} end Text.pagedown(State) end end function Text.insert_return(State) - if State.cursor1.pos then - -- when inserting a newline, move any B side to the new line - local byte_offset = Text.offset(State.lines[State.cursor1.line].data, State.cursor1.pos) - table.insert(State.lines, State.cursor1.line+1, {mode='text', data=string.sub(State.lines[State.cursor1.line].data, byte_offset), dataB=State.lines[State.cursor1.line].dataB}) - table.insert(State.line_cache, State.cursor1.line+1, {}) - State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_offset-1) - State.lines[State.cursor1.line].dataB = nil - Text.clear_screen_line_cache(State, State.cursor1.line) - State.cursor1 = {line=State.cursor1.line+1, pos=1} - else - -- disable enter when cursor is on the B side - end + local byte_offset = Text.offset(State.lines[State.cursor1.line].data, State.cursor1.pos) + table.insert(State.lines, State.cursor1.line+1, {mode='text', data=string.sub(State.lines[State.cursor1.line].data, byte_offset)}) + table.insert(State.line_cache, State.cursor1.line+1, {}) + State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_offset-1) + Text.clear_screen_line_cache(State, State.cursor1.line) + State.cursor1 = {line=State.cursor1.line+1, pos=1} end function Text.pageup(State) @@ -628,7 +392,7 @@ function Text.pageup(State) 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 and State.screen_top1.pos == 1 then break end + 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 @@ -637,7 +401,7 @@ function Text.pageup(State) top2 = Text.previous_screen_line(State, top2) end State.screen_top1 = Text.to1(State, top2) - State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos, posB=State.screen_top1.posB} + 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') @@ -645,15 +409,21 @@ 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, posB=State.screen_bottom1.posB} + State.screen_top1 = {line=State.screen_bottom1.line, pos=State.screen_bottom1.pos} end --? print('setting top to', State.screen_top1.line, State.screen_top1.pos) - State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos, posB=State.screen_top1.posB} + 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 @@ -662,14 +432,6 @@ end function Text.up(State) assert(State.lines[State.cursor1.line].mode == 'text') - if State.cursor1.pos then - Text.upA(State) - else - Text.upB(State) - end -end - -function Text.upA(State) --? print('up', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos) local screen_line_starting_pos, screen_line_index = Text.pos_at_start_of_screen_line(State, State.cursor1) if screen_line_starting_pos == 1 then @@ -709,62 +471,6 @@ function Text.upA(State) end end -function Text.upB(State) - local line_cache = State.line_cache[State.cursor1.line] - local screen_line_starting_posB, screen_line_indexB = Text.pos_at_start_of_screen_lineB(State, State.cursor1) - assert(screen_line_indexB >= 1) - if screen_line_indexB == 1 then - -- move to A side of previous line - local new_cursor_line = State.cursor1.line - while new_cursor_line > 1 do - new_cursor_line = new_cursor_line-1 - if State.lines[new_cursor_line].mode == 'text' then - State.cursor1 = {line=new_cursor_line, posB=nil} - Text.populate_screen_line_starting_pos(State, State.cursor1.line) - local prev_line_cache = State.line_cache[State.cursor1.line] - local prev_screen_line_starting_pos = prev_line_cache.screen_line_starting_pos[#prev_line_cache.screen_line_starting_pos] - local prev_screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line].data, prev_screen_line_starting_pos) - local s = string.sub(State.lines[State.cursor1.line].data, prev_screen_line_starting_byte_offset) - State.cursor1.pos = prev_screen_line_starting_pos + Text.nearest_cursor_pos(s, State.cursor_x, State.left) - 1 - break - end - end - elseif screen_line_indexB == 2 then - -- all-B screen-line to potentially A+B screen-line - local xA = Margin_left + Text.screen_line_width(State, State.cursor1.line, #line_cache.screen_line_starting_pos) + AB_padding - if State.cursor_x < xA then - State.cursor1.posB = nil - Text.populate_screen_line_starting_pos(State, State.cursor1.line) - local new_screen_line_starting_pos = line_cache.screen_line_starting_pos[#line_cache.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(s, State.cursor_x, State.left) - 1 - else - Text.populate_screen_line_starting_posB(State, State.cursor1.line) - local new_screen_line_starting_posB = line_cache.screen_line_starting_posB[screen_line_indexB-1] - local new_screen_line_starting_byte_offsetB = Text.offset(State.lines[State.cursor1.line].dataB, new_screen_line_starting_posB) - local s = string.sub(State.lines[State.cursor1.line].dataB, new_screen_line_starting_byte_offsetB) - State.cursor1.posB = new_screen_line_starting_posB + Text.nearest_cursor_pos(s, State.cursor_x-xA, State.left) - 1 - end - else - assert(screen_line_indexB > 2) - -- all-B screen-line to all-B screen-line - Text.populate_screen_line_starting_posB(State, State.cursor1.line) - local new_screen_line_starting_posB = line_cache.screen_line_starting_posB[screen_line_indexB-1] - local new_screen_line_starting_byte_offsetB = Text.offset(State.lines[State.cursor1.line].dataB, new_screen_line_starting_posB) - local s = string.sub(State.lines[State.cursor1.line].dataB, new_screen_line_starting_byte_offsetB) - State.cursor1.posB = new_screen_line_starting_posB + Text.nearest_cursor_pos(s, State.cursor_x, State.left) - 1 - end - if Text.lt1(State.cursor1, State.screen_top1) then - local top2 = Text.to2(State, State.screen_top1) - top2 = Text.previous_screen_line(State, top2) - State.screen_top1 = Text.to1(State, top2) - end -end - --- cursor on final screen line (A or B side) => goes to next screen line on A side --- cursor on A side => move down one screen line (A side) in current line --- cursor on B side => move down one screen line (B side) in current line function Text.down(State) assert(State.lines[State.cursor1.line].mode == '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) @@ -789,8 +495,8 @@ function Text.down(State) Text.snap_cursor_to_bottom_of_screen(State) --? print('screen top after:', State.screen_top1.line, State.screen_top1.pos) end - elseif State.cursor1.pos then - -- move down one screen line (A side) in current line + else + -- move down one screen line in current line local scroll_down = Text.le1(State.screen_bottom1, State.cursor1) --? print('cursor is NOT at final screen line of its line') local screen_line_starting_pos, screen_line_index = Text.pos_at_start_of_screen_line(State, State.cursor1) @@ -806,85 +512,26 @@ function Text.down(State) Text.snap_cursor_to_bottom_of_screen(State) --? print('screen top after:', State.screen_top1.line, State.screen_top1.pos) end - else - -- move down one screen line (B side) in current line - local scroll_down = false - if Text.le1(State.screen_bottom1, State.cursor1) then - scroll_down = true - end - local cursor_line = State.lines[State.cursor1.line] - local cursor_line_cache = State.line_cache[State.cursor1.line] - local cursor2 = Text.to2(State, State.cursor1) - assert(cursor2.screen_lineB < #cursor_line_cache.screen_line_starting_posB) - local screen_line_starting_posB, screen_line_indexB = Text.pos_at_start_of_screen_lineB(State, State.cursor1) - Text.populate_screen_line_starting_posB(State, State.cursor1.line) - local new_screen_line_starting_posB = cursor_line_cache.screen_line_starting_posB[screen_line_indexB+1] - local new_screen_line_starting_byte_offsetB = Text.offset(cursor_line.dataB, new_screen_line_starting_posB) - local s = string.sub(cursor_line.dataB, new_screen_line_starting_byte_offsetB) - State.cursor1.posB = new_screen_line_starting_posB + Text.nearest_cursor_pos(s, State.cursor_x, State.left) - 1 - if scroll_down then - Text.snap_cursor_to_bottom_of_screen(State) - 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) end function Text.start_of_line(State) - if State.cursor1.pos then - State.cursor1.pos = 1 - else - State.cursor1.posB = 1 - end + State.cursor1.pos = 1 if Text.lt1(State.cursor1, State.screen_top1) then - State.screen_top1 = {line=State.cursor1.line, pos=State.cursor1.pos, posB=State.cursor1.posB} -- copy + State.screen_top1 = {line=State.cursor1.line, pos=State.cursor1.pos} -- copy end end function Text.end_of_line(State) - if State.cursor1.pos then - State.cursor1.pos = utf8.len(State.lines[State.cursor1.line].data) + 1 - else - State.cursor1.posB = utf8.len(State.lines[State.cursor1.line].dataB) + 1 - end + State.cursor1.pos = utf8.len(State.lines[State.cursor1.line].data) + 1 if Text.cursor_out_of_screen(State) then Text.snap_cursor_to_bottom_of_screen(State) end end function Text.word_left(State) - -- we can cross the fold, so check side A/B one level down - Text.skip_whitespace_left(State) - Text.left(State) - Text.skip_non_whitespace_left(State) -end - -function Text.word_right(State) - -- we can cross the fold, so check side A/B one level down - Text.skip_whitespace_right(State) - Text.right(State) - Text.skip_non_whitespace_right(State) - if Text.cursor_out_of_screen(State) then - Text.snap_cursor_to_bottom_of_screen(State) - end -end - -function Text.skip_whitespace_left(State) - if State.cursor1.pos then - Text.skip_whitespace_leftA(State) - else - Text.skip_whitespace_leftB(State) - end -end - -function Text.skip_non_whitespace_left(State) - if State.cursor1.pos then - Text.skip_non_whitespace_leftA(State) - else - Text.skip_non_whitespace_leftB(State) - end -end - -function Text.skip_whitespace_leftA(State) + -- skip some whitespace while true do if State.cursor1.pos == 1 then break @@ -894,22 +541,9 @@ function Text.skip_whitespace_leftA(State) end Text.left(State) end -end - -function Text.skip_whitespace_leftB(State) + -- skip some non-whitespace while true do - if State.cursor1.posB == 1 then - break - end - if Text.match(State.lines[State.cursor1.line].dataB, State.cursor1.posB-1, '%S') then - break - end Text.left(State) - end -end - -function Text.skip_non_whitespace_leftA(State) - while true do if State.cursor1.pos == 1 then break end @@ -917,40 +551,11 @@ function Text.skip_non_whitespace_leftA(State) if Text.match(State.lines[State.cursor1.line].data, State.cursor1.pos-1, '%s') then break end - Text.left(State) end end -function Text.skip_non_whitespace_leftB(State) - while true do - if State.cursor1.posB == 1 then - break - end - assert(State.cursor1.posB > 1) - if Text.match(State.lines[State.cursor1.line].dataB, State.cursor1.posB-1, '%s') then - break - end - Text.left(State) - end -end - -function Text.skip_whitespace_right(State) - if State.cursor1.pos then - Text.skip_whitespace_rightA(State) - else - Text.skip_whitespace_rightB(State) - end -end - -function Text.skip_non_whitespace_right(State) - if State.cursor1.pos then - Text.skip_non_whitespace_rightA(State) - else - Text.skip_non_whitespace_rightB(State) - end -end - -function Text.skip_whitespace_rightA(State) +function Text.word_right(State) + -- skip some whitespace while true do if State.cursor1.pos > utf8.len(State.lines[State.cursor1.line].data) then break @@ -960,41 +565,17 @@ function Text.skip_whitespace_rightA(State) end Text.right_without_scroll(State) end -end - -function Text.skip_whitespace_rightB(State) while true do - if State.cursor1.posB > utf8.len(State.lines[State.cursor1.line].dataB) then - break - end - if Text.match(State.lines[State.cursor1.line].dataB, State.cursor1.posB, '%S') then - break - end Text.right_without_scroll(State) - end -end - -function Text.skip_non_whitespace_rightA(State) - while true do if State.cursor1.pos > utf8.len(State.lines[State.cursor1.line].data) then break end if Text.match(State.lines[State.cursor1.line].data, State.cursor1.pos, '%s') then break end - Text.right_without_scroll(State) end -end - -function Text.skip_non_whitespace_rightB(State) - while true do - if State.cursor1.posB > utf8.len(State.lines[State.cursor1.line].dataB) then - break - end - if Text.match(State.lines[State.cursor1.line].dataB, State.cursor1.posB, '%s') then - break - end - Text.right_without_scroll(State) + if Text.cursor_out_of_screen(State) then + Text.snap_cursor_to_bottom_of_screen(State) end end @@ -1008,14 +589,7 @@ function Text.match(s, pos, pat) end function Text.left(State) - if State.cursor1.pos then - Text.leftA(State) - else - Text.leftB(State) - end -end - -function Text.leftA(State) + assert(State.lines[State.cursor1.line].mode == 'text') if State.cursor1.pos > 1 then State.cursor1.pos = State.cursor1.pos-1 else @@ -1038,21 +612,6 @@ function Text.leftA(State) end end -function Text.leftB(State) - if State.cursor1.posB > 1 then - State.cursor1.posB = State.cursor1.posB-1 - else - -- overflow back into A side - State.cursor1.posB = nil - State.cursor1.pos = utf8.len(State.lines[State.cursor1.line].data) + 1 - end - if Text.lt1(State.cursor1, State.screen_top1) then - local top2 = Text.to2(State, State.screen_top1) - top2 = Text.previous_screen_line(State, top2) - State.screen_top1 = Text.to1(State, top2) - end -end - function Text.right(State) Text.right_without_scroll(State) if Text.cursor_out_of_screen(State) then @@ -1062,14 +621,6 @@ end function Text.right_without_scroll(State) assert(State.lines[State.cursor1.line].mode == 'text') - if State.cursor1.pos then - Text.right_without_scrollA(State) - else - Text.right_without_scrollB(State) - end -end - -function Text.right_without_scrollA(State) if State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) then State.cursor1.pos = State.cursor1.pos+1 else @@ -1084,22 +635,6 @@ function Text.right_without_scrollA(State) end end -function Text.right_without_scrollB(State) - if State.cursor1.posB <= utf8.len(State.lines[State.cursor1.line].dataB) then - State.cursor1.posB = State.cursor1.posB+1 - else - -- overflow back into A side - local new_cursor_line = State.cursor1.line - while new_cursor_line <= #State.lines-1 do - new_cursor_line = new_cursor_line+1 - if State.lines[new_cursor_line].mode == 'text' then - State.cursor1 = {line=new_cursor_line, pos=1} - break - end - end - end -end - function Text.pos_at_start_of_screen_line(State, loc1) Text.populate_screen_line_starting_pos(State, loc1.line) local line_cache = State.line_cache[loc1.line] @@ -1112,39 +647,11 @@ function Text.pos_at_start_of_screen_line(State, loc1) assert(false) end -function Text.pos_at_start_of_screen_lineB(State, loc1) - Text.populate_screen_line_starting_pos(State, loc1.line) - local line_cache = State.line_cache[loc1.line] - local x = Margin_left + Text.screen_line_width(State, loc1.line, #line_cache.screen_line_starting_pos) + AB_padding - Text.populate_screen_line_starting_posB(State, loc1.line, x) - for i=#line_cache.screen_line_starting_posB,1,-1 do - local sposB = line_cache.screen_line_starting_posB[i] - if sposB <= loc1.posB then - return sposB,i - end - end - assert(false) -end - function Text.cursor_at_final_screen_line(State) Text.populate_screen_line_starting_pos(State, State.cursor1.line) - local line = State.lines[State.cursor1.line] local screen_lines = State.line_cache[State.cursor1.line].screen_line_starting_pos --? print(screen_lines[#screen_lines], State.cursor1.pos) - if (not State.expanded and not line.expanded) or - line.dataB == nil then - return screen_lines[#screen_lines] <= State.cursor1.pos - end - if State.cursor1.pos then - -- ignore B side - return screen_lines[#screen_lines] <= State.cursor1.pos - end - assert(State.cursor1.posB) - local line_cache = State.line_cache[State.cursor1.line] - local x = Margin_left + Text.screen_line_width(State, State.cursor1.line, #line_cache.screen_line_starting_pos) + AB_padding - Text.populate_screen_line_starting_posB(State, State.cursor1.line, x) - local screen_lines = State.line_cache[State.cursor1.line].screen_line_starting_posB - return screen_lines[#screen_lines] <= State.cursor1.posB + return screen_lines[#screen_lines] <= State.cursor1.pos end function Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State) @@ -1172,22 +679,17 @@ end -- should never modify State.cursor1 function Text.snap_cursor_to_bottom_of_screen(State) ---? print('to2:', State.cursor1.line, State.cursor1.pos, State.cursor1.posB) +--? print('to2:', State.cursor1.line, State.cursor1.pos) local top2 = Text.to2(State, State.cursor1) ---? print('to2: =>', top2.line, top2.screen_line, top2.screen_pos, top2.screen_lineB, top2.screen_posB) +--? print('to2: =>', top2.line, top2.screen_line, top2.screen_pos) -- slide to start of screen line - if top2.screen_pos then - top2.screen_pos = 1 - else - assert(top2.screen_posB) - top2.screen_posB = 1 - end ---? print('snap', State.screen_top1.line, State.screen_top1.pos, State.screen_top1.posB, State.cursor1.line, State.cursor1.pos, State.cursor1.posB, State.screen_bottom1.line, State.screen_bottom1.pos, State.screen_bottom1.posB) + top2.screen_pos = 1 -- start of screen line +--? print('snap', State.screen_top1.line, State.screen_top1.pos, State.screen_top1.posB, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) --? print('cursor pos '..tostring(State.cursor1.pos)..' is on the #'..tostring(top2.screen_line)..' screen line down') local y = App.screen.height - State.line_height -- duplicate some logic from love.draw while true do ---? print(y, 'top2:', State.lines[top2.line].data, top2.line, top2.screen_line, top2.screen_pos, top2.screen_lineB, top2.screen_posB) +--? print(y, 'top2:', top2.line, top2.screen_line, top2.screen_pos) if top2.line == 1 and top2.screen_line == 1 then break end if top2.screen_line > 1 or State.lines[top2.line-1].mode == 'text' then local h = State.line_height @@ -1212,7 +714,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.screen_top1.posB, State.cursor1.line, State.cursor1.pos, State.cursor1.posB, State.screen_bottom1.line, State.screen_bottom1.pos, State.screen_bottom1.posB) +--? print('snap =>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks end @@ -1221,116 +723,34 @@ function Text.in_line(State, line_index, x,y) 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 num_screen_lines = 0 - if line_cache.startpos then - Text.populate_screen_line_starting_pos(State, line_index) - num_screen_lines = num_screen_lines + #line_cache.screen_line_starting_pos - Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos) + 1 - end ---? print('#screenlines after A', num_screen_lines) - if line.dataB and (State.expanded or line.expanded) then - local x = Margin_left + Text.screen_line_width(State, line_index, #line_cache.screen_line_starting_pos) + AB_padding - Text.populate_screen_line_starting_posB(State, line_index, x) ---? print('B:', x, #line_cache.screen_line_starting_posB) - if line_cache.startposB then - num_screen_lines = num_screen_lines + #line_cache.screen_line_starting_posB - Text.screen_line_indexB(line_cache.screen_line_starting_posB, line_cache.startposB) -- no +1; first screen line of B side overlaps with A side - else - num_screen_lines = num_screen_lines + #line_cache.screen_line_starting_posB - Text.screen_line_indexB(line_cache.screen_line_starting_posB, 1) -- no +1; first screen line of B side overlaps with A side - end - end ---? print('#screenlines after B', num_screen_lines) - return y < line_cache.starty + State.line_height*num_screen_lines + 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) end -- convert mx,my in pixels to schema-1 coordinates --- returns: pos, posB --- scenarios: --- line without B side --- line with B side collapsed --- line with B side expanded --- line starting rendering in A side (startpos ~= nil) --- line starting rendering in B side (startposB ~= nil) --- my on final screen line of A side --- mx to right of A side with no B side --- mx to right of A side but left of B side --- mx to right of B side --- preconditions: --- startpos xor startposB --- expanded -> dataB 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) -- duplicate some logic from Text.draw local y = line_cache.starty ---? print('click', line_index, my, 'with line starting at', y, #line_cache.screen_line_starting_pos) -- , #line_cache.screen_line_starting_posB) - if line_cache.startpos then - 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] - local screen_line_starting_byte_offset = Text.offset(line.data, screen_line_starting_pos) ---? print('iter', y, screen_line_index, screen_line_starting_pos, string.sub(line.data, screen_line_starting_byte_offset)) - local nexty = y + State.line_height - if my < nexty then - -- On all wrapped screen lines but the final one, clicks past end of - -- line position cursor on final character of screen line. - -- (The final screen line positions past end of screen line as always.) - if screen_line_index < #line_cache.screen_line_starting_pos and mx > State.left + Text.screen_line_width(State, line_index, screen_line_index) then ---? print('past end of non-final line; return') - return line_cache.screen_line_starting_pos[screen_line_index+1]-1 - end - local s = string.sub(line.data, screen_line_starting_byte_offset) ---? print('return', mx, Text.nearest_cursor_pos(s, mx, State.left), '=>', screen_line_starting_pos + Text.nearest_cursor_pos(s, mx, State.left) - 1) - local screen_line_posA = Text.nearest_cursor_pos(s, mx, State.left) - if line.dataB == nil then - -- no B side - return screen_line_starting_pos + screen_line_posA - 1 - end - if not State.expanded and not line.expanded then - -- B side is not expanded - return screen_line_starting_pos + screen_line_posA - 1 - end - local lenA = utf8.len(s) - if screen_line_posA < lenA then - -- mx is within A side - return screen_line_starting_pos + screen_line_posA - 1 - end - local max_xA = State.left+Text.x(s, lenA+1) - if mx < max_xA + AB_padding then - -- mx is in the space between A and B side - return screen_line_starting_pos + screen_line_posA - 1 - end - mx = mx - max_xA - AB_padding - local screen_line_posB = Text.nearest_cursor_pos(line.dataB, mx, --[[no left margin]] 0) - return nil, screen_line_posB - end - y = nexty - end - end - -- look in screen lines composed entirely of the B side - assert(State.expanded or line.expanded) - local start_screen_line_indexB - if line_cache.startposB then - start_screen_line_indexB = Text.screen_line_indexB(line_cache.screen_line_starting_posB, line_cache.startposB) - else - start_screen_line_indexB = 2 -- skip the first line of side B, which we checked above - end - for screen_line_indexB = start_screen_line_indexB,#line_cache.screen_line_starting_posB do - local screen_line_starting_posB = line_cache.screen_line_starting_posB[screen_line_indexB] - local screen_line_starting_byte_offsetB = Text.offset(line.dataB, screen_line_starting_posB) ---? print('iter2', y, screen_line_indexB, screen_line_starting_posB, string.sub(line.dataB, screen_line_starting_byte_offsetB)) + 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] + local screen_line_starting_byte_offset = Text.offset(line.data, screen_line_starting_pos) +--? print('iter', y, screen_line_index, screen_line_starting_pos, string.sub(line.data, screen_line_starting_byte_offset)) local nexty = y + State.line_height if my < nexty then -- On all wrapped screen lines but the final one, clicks past end of -- line position cursor on final character of screen line. -- (The final screen line positions past end of screen line as always.) ---? print('aa', mx, State.left, Text.screen_line_widthB(State, line_index, screen_line_indexB)) - if screen_line_indexB < #line_cache.screen_line_starting_posB and mx > State.left + Text.screen_line_widthB(State, line_index, screen_line_indexB) then + if screen_line_index < #line_cache.screen_line_starting_pos and mx > State.left + Text.screen_line_width(State, line_index, screen_line_index) then --? print('past end of non-final line; return') - return nil, line_cache.screen_line_starting_posB[screen_line_indexB+1]-1 + return line_cache.screen_line_starting_pos[screen_line_index+1]-1 end - local s = string.sub(line.dataB, screen_line_starting_byte_offsetB) ---? print('return', mx, Text.nearest_cursor_pos(s, mx, State.left), '=>', screen_line_starting_posB + Text.nearest_cursor_pos(s, mx, State.left) - 1) - return nil, screen_line_starting_posB + Text.nearest_cursor_pos(s, mx, State.left) - 1 + local s = string.sub(line.data, screen_line_starting_byte_offset) +--? print('return', mx, Text.nearest_cursor_pos(s, mx, State.left), '=>', screen_line_starting_pos + Text.nearest_cursor_pos(s, mx, State.left) - 1) + return screen_line_starting_pos + Text.nearest_cursor_pos(s, mx, State.left) - 1 end y = nexty end @@ -1354,30 +774,6 @@ function Text.screen_line_width(State, line_index, i) return App.width(screen_line_text) end -function Text.screen_line_widthB(State, line_index, i) - local line = State.lines[line_index] - local line_cache = State.line_cache[line_index] - local start_posB = line_cache.screen_line_starting_posB[i] - local start_offsetB = Text.offset(line.dataB, start_posB) - local screen_line - if i < #line_cache.screen_line_starting_posB then ---? print('non-final', i) - local past_end_posB = line_cache.screen_line_starting_posB[i+1] - local past_end_offsetB = Text.offset(line.dataB, past_end_posB) ---? print('between', start_offsetB, past_end_offsetB) - screen_line = string.sub(line.dataB, start_offsetB, past_end_offsetB-1) - else ---? print('final', i) ---? print('after', start_offsetB) - screen_line = string.sub(line.dataB, start_offsetB) - end - local screen_line_text = App.newText(love.graphics.getFont(), screen_line) ---? local result = App.width(screen_line_text) ---? print('=>', result) ---? return result - return App.width(screen_line_text) -end - function Text.screen_line_index(screen_line_starting_pos, pos) for i = #screen_line_starting_pos,1,-1 do if screen_line_starting_pos[i] <= pos then @@ -1386,18 +782,6 @@ function Text.screen_line_index(screen_line_starting_pos, pos) end end -function Text.screen_line_indexB(screen_line_starting_posB, posB) - if posB == nil then - return 0 - end - assert(screen_line_starting_posB) - for i = #screen_line_starting_posB,1,-1 do - if screen_line_starting_posB[i] <= posB then - return i - end - end -end - -- convert x pixel coordinate to pos -- oblivious to wrapping -- result: 1 to len+1 @@ -1490,14 +874,6 @@ function Text.to2(State, loc1) if State.lines[loc1.line].mode == 'drawing' then return {line=loc1.line, screen_line=1, screen_pos=1} end - if loc1.pos then - return Text.to2A(State, loc1) - else - return Text.to2B(State, loc1) - end -end - -function Text.to2A(State, loc1) local result = {line=loc1.line} local line_cache = State.line_cache[loc1.line] Text.populate_screen_line_starting_pos(State, loc1.line) @@ -1513,33 +889,7 @@ function Text.to2A(State, loc1) return result end -function Text.to2B(State, loc1) - local result = {line=loc1.line} - local line_cache = State.line_cache[loc1.line] - Text.populate_screen_line_starting_pos(State, loc1.line) - local x = Margin_left + Text.screen_line_width(State, loc1.line, #line_cache.screen_line_starting_pos) + AB_padding - Text.populate_screen_line_starting_posB(State, loc1.line, x) - for i=#line_cache.screen_line_starting_posB,1,-1 do - local sposB = line_cache.screen_line_starting_posB[i] - if sposB <= loc1.posB then - result.screen_lineB = i - result.screen_posB = loc1.posB - sposB + 1 - break - end - end - assert(result.screen_posB) - return result -end - function Text.to1(State, loc2) - if loc2.screen_pos then - return Text.to1A(State, loc2) - else - return Text.to1B(State, loc2) - end -end - -function Text.to1A(State, loc2) local result = {line=loc2.line, pos=loc2.screen_pos} if loc2.screen_line > 1 then result.pos = State.line_cache[loc2.line].screen_line_starting_pos[loc2.screen_line] + loc2.screen_pos - 1 @@ -1547,12 +897,8 @@ function Text.to1A(State, loc2) return result end -function Text.to1B(State, loc2) - local result = {line=loc2.line, posB=loc2.screen_posB} - if loc2.screen_lineB > 1 then - result.posB = State.line_cache[loc2.line].screen_line_starting_posB[loc2.screen_lineB] + loc2.screen_posB - 1 - end - return result +function Text.eq1(a, b) + return a.line == b.line and a.pos == b.pos end function Text.lt1(a, b) @@ -1562,22 +908,17 @@ function Text.lt1(a, b) if a.line > b.line then return false end - -- A side < B side - if a.pos and not b.pos then + return a.pos < b.pos +end + +function Text.le1(a, b) + if a.line < b.line then return true end - if not a.pos and b.pos then + if a.line > b.line then return false end - if a.pos then - return a.pos < b.pos - else - return a.posB < b.posB - end -end - -function Text.le1(a, b) - return eq(a, b) or Text.lt1(a, b) + return a.pos <= b.pos end function Text.offset(s, pos1) @@ -1591,49 +932,16 @@ function Text.offset(s, pos1) end function Text.previous_screen_line(State, loc2) - if loc2.screen_pos then - return Text.previous_screen_lineA(State, loc2) - else - return Text.previous_screen_lineB(State, loc2) - end -end - -function Text.previous_screen_lineA(State, loc2) if loc2.screen_line > 1 then return {line=loc2.line, screen_line=loc2.screen_line-1, screen_pos=1} elseif loc2.line == 1 then return loc2 + elseif State.lines[loc2.line-1].mode == 'drawing' then + return {line=loc2.line-1, screen_line=1, screen_pos=1} else + local l = State.lines[loc2.line-1] Text.populate_screen_line_starting_pos(State, loc2.line-1) - if State.lines[loc2.line-1].dataB == nil or - (not State.expanded and not State.lines[loc2.line-1].expanded) then ---? print('c1', loc2.line-1, State.lines[loc2.line-1].data, '==', State.lines[loc2.line-1].dataB, State.line_cache[loc2.line-1].fragmentsB) - return {line=loc2.line-1, screen_line=#State.line_cache[loc2.line-1].screen_line_starting_pos, screen_pos=1} - end - -- try to switch to B - local prev_line_cache = State.line_cache[loc2.line-1] - local x = Margin_left + Text.screen_line_width(State, loc2.line-1, #prev_line_cache.screen_line_starting_pos) + AB_padding - Text.populate_screen_line_starting_posB(State, loc2.line-1, x) - local screen_line_starting_posB = State.line_cache[loc2.line-1].screen_line_starting_posB ---? print('c', loc2.line-1, State.lines[loc2.line-1].data, '==', State.lines[loc2.line-1].dataB, '==', #screen_line_starting_posB, 'starting from x', x) - if #screen_line_starting_posB > 1 then ---? print('c2') - return {line=loc2.line-1, screen_lineB=#State.line_cache[loc2.line-1].screen_line_starting_posB, screen_posB=1} - else ---? print('c3') - -- if there's only one screen line, assume it overlaps with A, so remain in A - return {line=loc2.line-1, screen_line=#State.line_cache[loc2.line-1].screen_line_starting_pos, screen_pos=1} - end - end -end - -function Text.previous_screen_lineB(State, loc2) - if loc2.screen_lineB > 2 then -- first screen line of B side overlaps with A side - return {line=loc2.line, screen_lineB=loc2.screen_lineB-1, screen_posB=1} - else - -- switch to A side - -- TODO: handle case where fold lands precisely at end of a new screen-line - return {line=loc2.line, screen_line=#State.line_cache[loc2.line].screen_line_starting_pos, screen_pos=1} + return {line=loc2.line-1, screen_line=#State.line_cache[loc2.line-1].screen_line_starting_pos, screen_pos=1} end end @@ -1666,8 +974,10 @@ function Text.tweak_screen_top_and_cursor(State) --? print('too low') if Text.cursor_out_of_screen(State) then --? print('tweak') - local pos,posB = Text.to_pos_on_line(State, State.screen_bottom1.line, State.right-5, App.screen.height-5) - State.cursor1 = {line=State.screen_bottom1.line, pos=pos, posB=posB} + 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), + } end end end @@ -1704,9 +1014,7 @@ end function Text.clear_screen_line_cache(State, line_index) State.line_cache[line_index].fragments = nil - State.line_cache[line_index].fragmentsB = nil State.line_cache[line_index].screen_line_starting_pos = nil - State.line_cache[line_index].screen_line_starting_posB = nil end function trim(s) diff --git a/text.lua b/text.lua index 13ef07d..1267699 100644 --- a/text.lua +++ b/text.lua @@ -156,7 +156,6 @@ function Text.text_input(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) end record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)}) end -- cgit 1.4.1-2-gfad0 From 675d1cbbdf026313d682086e1652a69540f18334 Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Fri, 17 Mar 2023 21:52:35 -0700 Subject: bugfix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks Mikoláš Štrajt. --- source_text.lua | 23 ++++++++++++++--------- source_text_tests.lua | 22 ++++++++++++++++++++++ text.lua | 23 ++++++++++++++--------- text_tests.lua | 22 ++++++++++++++++++++++ 4 files changed, 72 insertions(+), 18 deletions(-) diff --git a/source_text.lua b/source_text.lua index 4cf3edd..1c91f28 100644 --- a/source_text.lua +++ b/source_text.lua @@ -247,9 +247,10 @@ function Text.keychord_press(State, chord) local line_cache = State.line_cache[#State.line_cache] State.screen_top1 = {line=#State.lines, pos=line_cache.screen_line_starting_pos[#line_cache.screen_line_starting_pos]} elseif Text.lt1(State.cursor1, State.screen_top1) then - local top2 = Text.to2(State, State.screen_top1) - top2 = Text.previous_screen_line(State, top2, State.left, State.right) - State.screen_top1 = Text.to1(State, top2) + State.screen_top1 = { + line=State.cursor1.line, + pos=Text.pos_at_start_of_screen_line(State, State.cursor1), + } Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks end Text.clear_screen_line_cache(State, State.cursor1.line) @@ -465,9 +466,11 @@ function Text.up(State) --? print('cursor pos is now '..tostring(State.cursor1.pos)) end if Text.lt1(State.cursor1, State.screen_top1) then - local top2 = Text.to2(State, State.screen_top1) - top2 = Text.previous_screen_line(State, top2) - State.screen_top1 = Text.to1(State, top2) + State.screen_top1 = { + line=State.cursor1.line, + pos=Text.pos_at_start_of_screen_line(State, State.cursor1), + } + Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks end end @@ -606,9 +609,11 @@ function Text.left(State) end end if Text.lt1(State.cursor1, State.screen_top1) then - local top2 = Text.to2(State, State.screen_top1) - top2 = Text.previous_screen_line(State, top2) - State.screen_top1 = Text.to1(State, top2) + State.screen_top1 = { + line=State.cursor1.line, + pos=Text.pos_at_start_of_screen_line(State, State.cursor1), + } + Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks end end diff --git a/source_text_tests.lua b/source_text_tests.lua index 2385325..7a7d71c 100644 --- a/source_text_tests.lua +++ b/source_text_tests.lua @@ -1251,6 +1251,28 @@ function test_up_arrow_scrolls_up_by_one_line() App.screen.check(y, 'ghi', 'screen:3') end +function test_up_arrow_scrolls_up_by_one_line_skipping_drawing() + -- display lines 3/4/5 with a drawing just off screen at line 2 + App.screen.init{width=120, height=60} + Editor_state = edit.initialize_test_state() + Editor_state.lines = load_array{'abc', '```lines', '```', 'def', 'ghi', 'jkl'} + 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') + y = y + Editor_state.line_height + App.screen.check(y, 'ghi', 'baseline/screen:2') + y = y + Editor_state.line_height + App.screen.check(y, 'jkl', 'baseline/screen:3') + -- after hitting the up arrow the screen scrolls up to previous text line + edit.run_after_keychord(Editor_state, 'up') + check_eq(Editor_state.screen_top1.line, 1, 'screen_top') + check_eq(Editor_state.cursor1.line, 1, 'cursor') +end + function test_up_arrow_scrolls_up_by_one_screen_line() -- display lines starting from second screen line of a line App.screen.init{width=Editor_state.left+30, height=60} diff --git a/text.lua b/text.lua index 1267699..6a1ef2d 100644 --- a/text.lua +++ b/text.lua @@ -230,9 +230,10 @@ function Text.keychord_press(State, chord) local line_cache = State.line_cache[#State.line_cache] State.screen_top1 = {line=#State.lines, pos=line_cache.screen_line_starting_pos[#line_cache.screen_line_starting_pos]} elseif Text.lt1(State.cursor1, State.screen_top1) then - local top2 = Text.to2(State, State.screen_top1) - top2 = Text.previous_screen_line(State, top2, State.left, State.right) - State.screen_top1 = Text.to1(State, top2) + State.screen_top1 = { + line=State.cursor1.line, + pos=Text.pos_at_start_of_screen_line(State, State.cursor1), + } Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks end Text.clear_screen_line_cache(State, State.cursor1.line) @@ -448,9 +449,11 @@ function Text.up(State) --? print('cursor pos is now '..tostring(State.cursor1.pos)) end if Text.lt1(State.cursor1, State.screen_top1) then - local top2 = Text.to2(State, State.screen_top1) - top2 = Text.previous_screen_line(State, top2) - State.screen_top1 = Text.to1(State, top2) + State.screen_top1 = { + line=State.cursor1.line, + pos=Text.pos_at_start_of_screen_line(State, State.cursor1), + } + Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks end end @@ -589,9 +592,11 @@ function Text.left(State) end end if Text.lt1(State.cursor1, State.screen_top1) then - local top2 = Text.to2(State, State.screen_top1) - top2 = Text.previous_screen_line(State, top2) - State.screen_top1 = Text.to1(State, top2) + State.screen_top1 = { + line=State.cursor1.line, + pos=Text.pos_at_start_of_screen_line(State, State.cursor1), + } + Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks end end diff --git a/text_tests.lua b/text_tests.lua index d08848a..7cf980b 100644 --- a/text_tests.lua +++ b/text_tests.lua @@ -1281,6 +1281,28 @@ function test_up_arrow_scrolls_up_by_one_line() App.screen.check(y, 'ghi', 'screen:3') end +function test_up_arrow_scrolls_up_by_one_line_skipping_drawing() + -- display lines 3/4/5 with a drawing just off screen at line 2 + App.screen.init{width=120, height=60} + Editor_state = edit.initialize_test_state() + Editor_state.lines = load_array{'abc', '```lines', '```', 'def', 'ghi', 'jkl'} + 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') + y = y + Editor_state.line_height + App.screen.check(y, 'ghi', 'baseline/screen:2') + y = y + Editor_state.line_height + App.screen.check(y, 'jkl', 'baseline/screen:3') + -- after hitting the up arrow the screen scrolls up to previous text line + edit.run_after_keychord(Editor_state, 'up') + check_eq(Editor_state.screen_top1.line, 1, 'screen_top') + check_eq(Editor_state.cursor1.line, 1, 'cursor') +end + function test_up_arrow_scrolls_up_by_one_screen_line() -- display lines starting from second screen line of a line App.screen.init{width=Editor_state.left+30, height=60} -- cgit 1.4.1-2-gfad0 From 6709b394fba8dbc8cdfaaf3a9461e79ce7b0c2e0 Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Fri, 17 Mar 2023 22:17:23 -0700 Subject: more bugfix Don't crash on showing the log browser. --- log_browser.lua | 6 ++---- source_text.lua | 2 +- text.lua | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/log_browser.lua b/log_browser.lua index 76596f2..5946e3e 100644 --- a/log_browser.lua +++ b/log_browser.lua @@ -12,7 +12,7 @@ function source.initialize_log_browser_side() log_browser.parse(Log_browser_state) Text.redraw_all(Log_browser_state) Log_browser_state.screen_top1 = {line=1, pos=1} - Log_browser_state.cursor1 = {line=1, pos=nil} + Log_browser_state.cursor1 = {line=1, pos=1} end Section_stack = {} @@ -237,14 +237,12 @@ function log_browser.mouse_press(State, x,y, mouse_button) source.switch_to_file(line.filename) end -- set cursor - Editor_state.cursor1 = {line=line.line_number, pos=1, posB=nil} + Editor_state.cursor1 = {line=line.line_number, pos=1} -- make sure it's visible -- TODO: handle extremely long lines Editor_state.screen_top1.line = math.max(0, Editor_state.cursor1.line-5) -- show cursor Focus = 'edit' - -- expand B side - Editor_state.expanded = true end function log_browser.line_index(State, mx,my) diff --git a/source_text.lua b/source_text.lua index 1c91f28..fb4c389 100644 --- a/source_text.lua +++ b/source_text.lua @@ -689,7 +689,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.screen_top1.posB, 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, State.screen_bottom1.line, State.screen_bottom1.pos) --? print('cursor pos '..tostring(State.cursor1.pos)..' is on the #'..tostring(top2.screen_line)..' screen line down') local y = App.screen.height - State.line_height -- duplicate some logic from love.draw diff --git a/text.lua b/text.lua index 6a1ef2d..35b30c4 100644 --- a/text.lua +++ b/text.lua @@ -672,7 +672,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.screen_top1.posB, 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, State.screen_bottom1.line, State.screen_bottom1.pos) --? print('cursor pos '..tostring(State.cursor1.pos)..' is on the #'..tostring(top2.screen_line)..' screen line down') local y = App.screen.height - State.line_height -- duplicate some logic from love.draw -- cgit 1.4.1-2-gfad0