From 57fb2d4b57db3fef5245d40e16f5ea811c3806a4 Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Fri, 12 Jan 2024 03:03:31 -0800 Subject: clean up test mocks before aborting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scenario: modify a test to fail in the source editor delete any settings in the 'config' file in the save dir start lines.love press C-e to switch to source editor Before this commit, this scenario led to the following events: the C-e keypress invokes App.run_tests_and_initialize() the failing test results in a call to error() the call to error() is trapped by the xpcall around the event handler in love.run handle_error runs Current_app is 'source', so love.event.quit() is triggered love.quit() is invoked source.settings() is invoked App.screen.position() is invoked, which calls the test mock Since App.screen.move was never invoked, App.screen.position() returns nil The 'config' file is written without values for source.x and source.y As a result, future runs fail to open. This is likely a corner case only I will ever run into, since I'm careful to never commit failing unit tests. Still, I spent some time trying to figure out the best place to fix this. Options: * don't write config if Error_message is set but we do want config written in this scenario: * we hit an error, source editor opens * we spend some time debugging and don't immediately fix the issue * we quit, with some new files opened in various places * hardcode source.settings() to call love.window.getPosition() rather than App.screen.position(). drawback: weird special case * clean up test mocks before aborting this seems like something we always want I'm not very sure of my choice. This bug doesn't leave me feeling very great about my whole app. Arguably everything I've done is bullshit hacks piled on hacks. Perhaps the issue is: - naked error() in LÖVE apps never invokes love.quit(), but - an unhandled error within my handle_error invokes love.quit() (via love.event.quit) Perhaps LÖVE should provide a way to abort without invoking the quit handler. There's literally no other way in LÖVE to request a quit. --- app.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/app.lua b/app.lua index 725d380..ac8e709 100644 --- a/app.lua +++ b/app.lua @@ -53,6 +53,7 @@ function handle_error(err) App.undo_initialize() App.run_tests_and_initialize() else + if App.disable_tests then App.disable_tests() end love.event.quit() end end -- cgit 1.4.1-2-gfad0 From 5d4fd4aa93918ae2139a581bae4d9f9579359281 Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Fri, 12 Jan 2024 03:33:13 -0800 Subject: fix still more issues with the previous scenario - source editor always expects relative paths - refresh mocked data There's still one issue after this: the font size saved in the config file is the one we use in tests. More broadly, Editor_state is completely wrong. Ideally I'd just not save any settings for the source editor if the tests fail. --- source.lua | 1 + source_edit.lua | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/source.lua b/source.lua index 1aa1f74..a4b3603 100644 --- a/source.lua +++ b/source.lua @@ -271,6 +271,7 @@ function source.settings() if Settings == nil then Settings = {} end if Settings.source == nil then Settings.source = {} end Settings.source.x, Settings.source.y, Settings.source.displayindex = App.screen.position() + App.screen.width, App.screen.height = App.screen.size() File_navigation.cursors[Editor_state.filename] = {cursor1=Editor_state.cursor1, screen_top1=Editor_state.screen_top1} return { x=Settings.source.x, y=Settings.source.y, displayindex=Settings.source.displayindex, diff --git a/source_edit.lua b/source_edit.lua index f58d7e3..e2f9bd0 100644 --- a/source_edit.lua +++ b/source_edit.lua @@ -94,7 +94,7 @@ function edit.initialize_state(top, left, right, font, font_height, line_height) right = math.floor(right), width = right-left, - filename = love.filesystem.getSourceBaseDirectory()..'/lines.txt', -- '/' should work even on Windows + filename = 'run.lua', next_save = nil, -- undo -- cgit 1.4.1-2-gfad0 From 4121613fc678d71acaa6213300dac3c321530589 Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Fri, 12 Jan 2024 03:51:34 -0800 Subject: don't save settings on error in source editor --- app.lua | 3 ++- main.lua | 1 + source.lua | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app.lua b/app.lua index ac8e709..f59897c 100644 --- a/app.lua +++ b/app.lua @@ -53,7 +53,8 @@ function handle_error(err) App.undo_initialize() App.run_tests_and_initialize() else - if App.disable_tests then App.disable_tests() end + -- abort without running love.quit handler + Disable_all_quit_handlers = true love.event.quit() end end diff --git a/main.lua b/main.lua index 82a8f2d..b40ddf2 100644 --- a/main.lua +++ b/main.lua @@ -331,6 +331,7 @@ function App.wheelmoved(dx,dy) end function love.quit() + if Disable_all_quit_handlers then return end if current_app_is_warning() then return end if Current_app == 'run' then local source_settings = Settings.source diff --git a/source.lua b/source.lua index a4b3603..1aa1f74 100644 --- a/source.lua +++ b/source.lua @@ -271,7 +271,6 @@ function source.settings() if Settings == nil then Settings = {} end if Settings.source == nil then Settings.source = {} end Settings.source.x, Settings.source.y, Settings.source.displayindex = App.screen.position() - App.screen.width, App.screen.height = App.screen.size() File_navigation.cursors[Editor_state.filename] = {cursor1=Editor_state.cursor1, screen_top1=Editor_state.screen_top1} return { x=Settings.source.x, y=Settings.source.y, displayindex=Settings.source.displayindex, -- cgit 1.4.1-2-gfad0 From 95d88a8298142a996bf828734f7e986d2f99f9ab Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Fri, 12 Jan 2024 05:23:06 -0800 Subject: use editor state font for width calculations --- drawing.lua | 4 ++-- help.lua | 6 +----- log_browser.lua | 8 ++++---- search.lua | 2 +- select.lua | 4 ++-- source.lua | 4 ++-- source_select.lua | 4 ++-- source_text.lua | 58 +++++++++++++++++++++++++++---------------------------- text.lua | 48 ++++++++++++++++++++++----------------------- 9 files changed, 67 insertions(+), 71 deletions(-) diff --git a/drawing.lua b/drawing.lua index 615ea62..246c7ae 100644 --- a/drawing.lua +++ b/drawing.lua @@ -61,9 +61,9 @@ function Drawing.draw(State, line_index, y) App.color(Current_name_background_color) local name_width if p.name == '' then - name_width = App.width('m') + name_width = State.font:getWidth('m') else - name_width = App.width(p.name) + name_width = State.font:getWidth(p.name) end love.graphics.rectangle('fill', x,y, name_width, State.line_height) end diff --git a/help.lua b/help.lua index d91be3c..6f8633b 100644 --- a/help.lua +++ b/help.lua @@ -9,7 +9,7 @@ function draw_help_without_mouse_pressed(State, drawing_index) y = y + State.line_height love.graphics.print("* Hover on a point and press 'ctrl+u' to pick it up and start moving it,", State.left+30,y) y = y + State.line_height - love.graphics.print("then press the mouse button to drop it", State.left+30+bullet_indent(),y) + love.graphics.print("then press the mouse button to drop it", State.left+30+State.font:getWidth('* '),y) y = y + State.line_height love.graphics.print("* Hover on a point and press 'ctrl+n', type a name, then press 'enter'", State.left+30,y) y = y + State.line_height @@ -145,7 +145,3 @@ function current_shape(State, shape) return State.current_drawing_mode end end - -function bullet_indent() - return App.width('* ') -end diff --git a/log_browser.lua b/log_browser.lua index dabec2b..6e7e6db 100644 --- a/log_browser.lua +++ b/log_browser.lua @@ -94,14 +94,14 @@ function log_browser.draw(State, hide_cursor) love.graphics.line(xright,sectiony, xright,y+State.line_height) love.graphics.line(xleft,sectiony, xleft+50-2,sectiony) love.graphics.print(line.section_name, xleft+50,y) - love.graphics.line(xleft+50+App.width(line.section_name)+2,sectiony, xright,sectiony) + love.graphics.line(xleft+50+State.font:getWidth(line.section_name)+2,sectiony, xright,sectiony) else assert(line.section_end, "log line has a section name, but it's neither the start nor end of a section") local sectiony = y+State.line_height-Section_border_padding_vertical love.graphics.line(xleft,y, xleft,sectiony) love.graphics.line(xright,y, xright,sectiony) love.graphics.line(xleft,sectiony, xleft+50-2,sectiony) love.graphics.print(line.section_name, xleft+50,y) - love.graphics.line(xleft+50+App.width(line.section_name)+2,sectiony, xright,sectiony) + love.graphics.line(xleft+50+State.font:getWidth(line.section_name)+2,sectiony, xright,sectiony) end else if type(line.data) == 'string' then @@ -137,7 +137,7 @@ function render_stack_left_margin(State, line_index, line, y) love.graphics.print(line.section_stack[i].name, x+State.font_height+5, y+5, --[[vertically]] math.pi/2) end if y > App.screen.height-log_browser.height(State, line_index) then - love.graphics.print(line.section_stack[i].name, x+State.font_height+5, App.screen.height-App.width(line.section_stack[i].name)-5, --[[vertically]] math.pi/2) + love.graphics.print(line.section_stack[i].name, x+State.font_height+5, App.screen.height-State.font:getWidth(line.section_stack[i].name)-5, --[[vertically]] math.pi/2) end end return log_browser.left_margin(State, line) @@ -152,7 +152,7 @@ function render_stack_right_margin(State, line_index, line, y) love.graphics.print(line.section_stack[i].name, x, y+5, --[[vertically]] math.pi/2) end if y > App.screen.height-log_browser.height(State, line_index) then - love.graphics.print(line.section_stack[i].name, x, App.screen.height-App.width(line.section_stack[i].name)-5, --[[vertically]] math.pi/2) + love.graphics.print(line.section_stack[i].name, x, App.screen.height-State.font:getWidth(line.section_stack[i].name)-5, --[[vertically]] math.pi/2) end end return log_browser.right_margin(State, line) diff --git a/search.lua b/search.lua index 67b82b3..54bab14 100644 --- a/search.lua +++ b/search.lua @@ -13,7 +13,7 @@ function Text.draw_search_bar(State) love.graphics.rectangle('line', 20, y-6, App.screen.width-40, h+2, 2,2) App.color(Text_color) App.screen.print(State.search_term, 25,y-5) - Text.draw_cursor(State, 25+App.width(State.search_term),y-5) + Text.draw_cursor(State, 25+State.font:getWidth(State.search_term),y-5) end function Text.search_next(State) diff --git a/select.lua b/select.lua index 5434988..6e21c7b 100644 --- a/select.lua +++ b/select.lua @@ -57,11 +57,11 @@ function Text.draw_highlight(State, line, x,y, pos, lo,hi) lo_px = 0 else local before = line.data:sub(pos_offset, lo_offset-1) - lo_px = App.width(before) + lo_px = State.font:getWidth(before) end local s = line.data:sub(lo_offset, hi_offset-1) App.color(Highlight_color) - love.graphics.rectangle('fill', x+lo_px,y, App.width(s),State.line_height) + love.graphics.rectangle('fill', x+lo_px,y, State.font:getWidth(s),State.line_height) App.color(Text_color) return lo_px end diff --git a/source.lua b/source.lua index 1aa1f74..439efa3 100644 --- a/source.lua +++ b/source.lua @@ -126,7 +126,7 @@ function source.load_settings() if Show_log_browser_side then right = App.screen.width/2 - Margin_right end - Editor_state = edit.initialize_state(Margin_top, Margin_left + Line_number_width*App.width('m'), right, font, settings.font_height, math.floor(settings.font_height*1.3)) + Editor_state = edit.initialize_state(Margin_top, Margin_left + Line_number_width*font:getWidth('m'), right, font, settings.font_height, math.floor(settings.font_height*1.3)) Editor_state.filename = settings.filename Editor_state.filename = basename(Editor_state.filename) -- migrate settings that used full paths; we now support only relative paths within the app if settings.cursors then @@ -154,7 +154,7 @@ function source.initialize_default_settings() local font_height = 20 local font = love.graphics.newFont(font_height) source.initialize_window_geometry() - Editor_state = edit.initialize_state(Margin_top, Margin_left + Line_number_width*App.width('m'), App.screen.width-Margin_right, font, font_height, math.floor(font_height*1.3)) + Editor_state = edit.initialize_state(Margin_top, Margin_left + Line_number_width*font:getWidth('m'), App.screen.width-Margin_right, font, font_height, math.floor(font_height*1.3)) Editor_state.filename = 'run.lua' end diff --git a/source_select.lua b/source_select.lua index 5434988..6e21c7b 100644 --- a/source_select.lua +++ b/source_select.lua @@ -57,11 +57,11 @@ function Text.draw_highlight(State, line, x,y, pos, lo,hi) lo_px = 0 else local before = line.data:sub(pos_offset, lo_offset-1) - lo_px = App.width(before) + lo_px = State.font:getWidth(before) end local s = line.data:sub(lo_offset, hi_offset-1) App.color(Highlight_color) - love.graphics.rectangle('fill', x+lo_px,y, App.width(s),State.line_height) + love.graphics.rectangle('fill', x+lo_px,y, State.font:getWidth(s),State.line_height) App.color(Text_color) return lo_px end diff --git a/source_text.lua b/source_text.lua index a4175c3..b22d358 100644 --- a/source_text.lua +++ b/source_text.lua @@ -14,7 +14,7 @@ function Text.draw(State, line_index, y, startpos, hide_cursor, show_line_number Text.populate_link_offsets(State, line_index) if show_line_numbers then App.color(Line_number_color) - love.graphics.print(line_index, State.left-Line_number_width*App.width('m')+10,y) + love.graphics.print(line_index, State.left-Line_number_width*State.font:getWidth('m')+10,y) end initialize_color() assert(#line_cache.screen_line_starting_pos >= 1, 'line cache missing screen line info') @@ -32,7 +32,7 @@ function Text.draw(State, line_index, y, startpos, hide_cursor, show_line_number for _,link_offsets in ipairs(line_cache.link_offsets) do -- render link decorations local s,e,filename = unpack(link_offsets) - local lo, hi = Text.clip_wikiword_with_screen_line(line, line_cache, i, s, e) + local lo, hi = Text.clip_wikiword_with_screen_line(State.font, line, line_cache, i, s, e) if lo then button(State, 'link', {x=State.left+lo, y=y, w=hi-lo, h=State.line_height, icon = icon.hyperlink_decoration, @@ -62,12 +62,12 @@ function Text.draw(State, line_index, y, startpos, hide_cursor, show_line_number end elseif Focus == 'edit' then if pos <= State.cursor1.pos and pos + frag_len > State.cursor1.pos then - Text.draw_cursor(State, State.left+Text.x(screen_line, State.cursor1.pos-pos+1), y) + Text.draw_cursor(State, State.left+Text.x(State.font, screen_line, State.cursor1.pos-pos+1), y) elseif pos + frag_len == State.cursor1.pos then -- Show cursor at end of line. -- This place also catches end of wrapping screen lines. That doesn't seem worth distinguishing. -- It seems useful to see a cursor whether your eye is on the left or right margin. - Text.draw_cursor(State, State.left+Text.x(screen_line, State.cursor1.pos-pos+1), y) + Text.draw_cursor(State, State.left+Text.x(State.font, screen_line, State.cursor1.pos-pos+1), y) end end end @@ -76,7 +76,7 @@ function Text.draw(State, line_index, y, startpos, hide_cursor, show_line_number for frag in screen_line:gmatch('%S*%s*') do select_color(frag) App.screen.print(frag, x,y) - x = x+App.width(frag) + x = x+State.font:getWidth(frag) end y = y + State.line_height if y >= App.screen.height then @@ -120,14 +120,14 @@ function Text.populate_screen_line_starting_pos(State, line_index) local pos = 1 -- try to wrap at word boundaries for frag in line.data:gmatch('%S*%s*') do - local frag_width = App.width(frag) + local frag_width = State.font:getWidth(frag) --? print('-- frag:', frag, pos, x, frag_width, State.width) while x + frag_width > State.width do --? print('frag:', frag, pos, x, frag_width, State.width) if x < 0.8 * State.width then -- long word; chop it at some letter -- We're not going to reimplement TeX here. - local bpos = Text.nearest_pos_less_than(frag, State.width - x) + local bpos = Text.nearest_pos_less_than(State.font, frag, State.width - x) if x == 0 and bpos == 0 then assert(false, ("Infinite loop while line-wrapping. Editor is %dpx wide; window is %dpx wide"):format(State.width, App.screen.width)) end @@ -137,7 +137,7 @@ function Text.populate_screen_line_starting_pos(State, line_index) --? if bpos > 0 then --? print('after chop:', frag) --? end - frag_width = App.width(frag) + frag_width = State.font:getWidth(frag) end --? print('screen line:', pos) table.insert(line_cache.screen_line_starting_pos, pos) @@ -172,7 +172,7 @@ end -- Intersect the filename between byte offsets s,e with the bounds of screen line i. -- Return the left/right pixel coordinates of of the intersection, -- or nil if it doesn't intersect with screen line i. -function Text.clip_wikiword_with_screen_line(line, line_cache, i, s, e) +function Text.clip_wikiword_with_screen_line(font, line, line_cache, i, s, e) local spos = line_cache.screen_line_starting_pos[i] local soff = Text.offset(line.data, spos) if e < soff then @@ -194,7 +194,7 @@ function Text.clip_wikiword_with_screen_line(line, line_cache, i, s, e) hoff = e end --? print(s, e, soff, eoff, loff, hoff) - return App.width(line.data:sub(soff, loff-1)), App.width(line.data:sub(soff, hoff)) + return font:getWidth(line.data:sub(soff, loff-1)), font:getWidth(line.data:sub(soff, hoff)) end function Text.text_input(State, t) @@ -481,7 +481,7 @@ function Text.up(State) screen_line_starting_pos = screen_line_starting_pos[#screen_line_starting_pos] local screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line].data, screen_line_starting_pos) local s = string.sub(State.lines[State.cursor1.line].data, screen_line_starting_byte_offset) - State.cursor1.pos = screen_line_starting_pos + Text.nearest_cursor_pos(s, State.cursor_x, State.left) - 1 + State.cursor1.pos = screen_line_starting_pos + Text.nearest_cursor_pos(State.font, s, State.cursor_x, State.left) - 1 break end end @@ -491,7 +491,7 @@ function Text.up(State) local new_screen_line_starting_pos = State.line_cache[State.cursor1.line].screen_line_starting_pos[screen_line_index-1] 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 + 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 '..tostring(State.cursor1.pos)) end if Text.lt1(State.cursor1, State.screen_top1) then @@ -516,7 +516,7 @@ function Text.down(State) if State.lines[new_cursor_line].mode == 'text' then State.cursor1 = { line = new_cursor_line, - pos = Text.nearest_cursor_pos(State.lines[new_cursor_line].data, State.cursor_x, State.left), + pos = Text.nearest_cursor_pos(State.font, State.lines[new_cursor_line].data, State.cursor_x, State.left), } --? print(State.cursor1.pos) break @@ -538,7 +538,7 @@ function Text.down(State) --? 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(s, State.cursor_x, State.left) - 1 + 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) if scroll_down then --? print('scroll up preserving cursor') @@ -801,8 +801,8 @@ function Text.to_pos_on_line(State, line_index, mx, my) 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) - return screen_line_starting_pos + Text.nearest_cursor_pos(s, mx, State.left) - 1 +--? print('return', mx, Text.nearest_cursor_pos(State.font, s, mx, State.left), '=>', screen_line_starting_pos + Text.nearest_cursor_pos(State.font, s, mx, State.left) - 1) + return screen_line_starting_pos + Text.nearest_cursor_pos(State.font, s, mx, State.left) - 1 end y = nexty end @@ -822,7 +822,7 @@ function Text.screen_line_width(State, line_index, i) else screen_line = string.sub(line.data, start_pos) end - return App.width(screen_line) + return State.font:getWidth(screen_line) end function Text.screen_line_index(screen_line_starting_pos, pos) @@ -836,12 +836,12 @@ end -- convert x pixel coordinate to pos -- oblivious to wrapping -- result: 1 to len+1 -function Text.nearest_cursor_pos(line, x, left) +function Text.nearest_cursor_pos(font, line, x, left) if x < left then return 1 end local len = utf8.len(line) - local max_x = left+Text.x(line, len+1) + local max_x = left+Text.x(font, line, len+1) if x > max_x then return len+1 end @@ -853,8 +853,8 @@ function Text.nearest_cursor_pos(line, x, left) return leftpos end local curr = math.floor((leftpos+rightpos)/2) - local currxmin = left+Text.x(line, curr) - local currxmax = left+Text.x(line, curr+1) + local currxmin = left+Text.x(font, line, curr) + local currxmax = left+Text.x(font, line, curr+1) --? print('nearest', x, leftpos, rightpos, curr, currxmin, currxmax) if currxmin <= x and x < currxmax then if x-currxmin < currxmax-x then @@ -878,18 +878,18 @@ end -- return the nearest index of line (in utf8 code points) which lies entirely -- within x pixels of the left margin -- result: 0 to len+1 -function Text.nearest_pos_less_than(line, x) +function Text.nearest_pos_less_than(font, line, x) --? print('', '-- nearest_pos_less_than', line, x) local len = utf8.len(line) - local max_x = Text.x_after(line, len) + local max_x = Text.x_after(font, line, len) if x > max_x then return len+1 end local left, right = 0, len+1 while true do local curr = math.floor((left+right)/2) - local currxmin = Text.x_after(line, curr+1) - local currxmax = Text.x_after(line, curr+2) + local currxmin = Text.x_after(font, line, curr+1) + local currxmax = Text.x_after(font, line, curr+2) --? print('', x, left, right, curr, currxmin, currxmax) if currxmin <= x and x < currxmax then return curr @@ -906,18 +906,18 @@ function Text.nearest_pos_less_than(line, x) assert(false, 'failed to map x pixel to pos') end -function Text.x_after(s, pos) +function Text.x_after(font, s, pos) local len = utf8.len(s) local offset = Text.offset(s, math.min(pos+1, len+1)) local s_before = s:sub(1, offset-1) --? print('^'..s_before..'$') - return App.width(s_before) + return font:getWidth(s_before) end -function Text.x(s, pos) +function Text.x(font, s, pos) local offset = Text.offset(s, pos) local s_before = s:sub(1, offset-1) - return App.width(s_before) + return font:getWidth(s_before) end function Text.to2(State, loc1) diff --git a/text.lua b/text.lua index 6696fe5..3b67710 100644 --- a/text.lua +++ b/text.lua @@ -41,12 +41,12 @@ function Text.draw(State, line_index, y, startpos) end else if pos <= State.cursor1.pos and pos + frag_len > State.cursor1.pos then - Text.draw_cursor(State, State.left+Text.x(screen_line, State.cursor1.pos-pos+1), y) + Text.draw_cursor(State, State.left+Text.x(State.font, screen_line, State.cursor1.pos-pos+1), y) elseif pos + frag_len == State.cursor1.pos then -- Show cursor at end of line. -- This place also catches end of wrapping screen lines. That doesn't seem worth distinguishing. -- It seems useful to see a cursor whether your eye is on the left or right margin. - Text.draw_cursor(State, State.left+Text.x(screen_line, State.cursor1.pos-pos+1), y) + Text.draw_cursor(State, State.left+Text.x(State.font, screen_line, State.cursor1.pos-pos+1), y) end end end @@ -95,14 +95,14 @@ function Text.populate_screen_line_starting_pos(State, line_index) local pos = 1 -- try to wrap at word boundaries for frag in line.data:gmatch('%S*%s*') do - local frag_width = App.width(frag) + local frag_width = State.font:getWidth(frag) --? print('-- frag:', frag, pos, x, frag_width, State.width) while x + frag_width > State.width do --? print('frag:', frag, pos, x, frag_width, State.width) if x < 0.8 * State.width then -- long word; chop it at some letter -- We're not going to reimplement TeX here. - local bpos = Text.nearest_pos_less_than(frag, State.width - x) + local bpos = Text.nearest_pos_less_than(State.font, frag, State.width - x) if x == 0 and bpos == 0 then assert(false, ("Infinite loop while line-wrapping. Editor is %dpx wide; window is %dpx wide"):format(State.width, App.screen.width)) end @@ -112,7 +112,7 @@ function Text.populate_screen_line_starting_pos(State, line_index) --? if bpos > 0 then --? print('after chop:', frag) --? end - frag_width = App.width(frag) + frag_width = State.font:getWidth(frag) end --? print('screen line:', pos) table.insert(line_cache.screen_line_starting_pos, pos) @@ -419,7 +419,7 @@ function Text.up(State) screen_line_starting_pos = screen_line_starting_pos[#screen_line_starting_pos] local screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line].data, screen_line_starting_pos) local s = string.sub(State.lines[State.cursor1.line].data, screen_line_starting_byte_offset) - State.cursor1.pos = screen_line_starting_pos + Text.nearest_cursor_pos(s, State.cursor_x, State.left) - 1 + State.cursor1.pos = screen_line_starting_pos + Text.nearest_cursor_pos(State.font, s, State.cursor_x, State.left) - 1 break end end @@ -429,7 +429,7 @@ function Text.up(State) local new_screen_line_starting_pos = State.line_cache[State.cursor1.line].screen_line_starting_pos[screen_line_index-1] 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 + 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 '..tostring(State.cursor1.pos)) end if Text.lt1(State.cursor1, State.screen_top1) then @@ -454,7 +454,7 @@ function Text.down(State) if State.lines[new_cursor_line].mode == 'text' then State.cursor1 = { line = new_cursor_line, - pos = Text.nearest_cursor_pos(State.lines[new_cursor_line].data, State.cursor_x, State.left), + pos = Text.nearest_cursor_pos(State.font, State.lines[new_cursor_line].data, State.cursor_x, State.left), } --? print(State.cursor1.pos) break @@ -476,7 +476,7 @@ function Text.down(State) --? 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(s, State.cursor_x, State.left) - 1 + 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) if scroll_down then --? print('scroll up preserving cursor') @@ -739,8 +739,8 @@ function Text.to_pos_on_line(State, line_index, mx, my) 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) - return screen_line_starting_pos + Text.nearest_cursor_pos(s, mx, State.left) - 1 +--? print('return', mx, Text.nearest_cursor_pos(State.font, s, mx, State.left), '=>', screen_line_starting_pos + Text.nearest_cursor_pos(State.font, s, mx, State.left) - 1) + return screen_line_starting_pos + Text.nearest_cursor_pos(State.font, s, mx, State.left) - 1 end y = nexty end @@ -760,7 +760,7 @@ function Text.screen_line_width(State, line_index, i) else screen_line = string.sub(line.data, start_pos) end - return App.width(screen_line) + return State.font:getWidth(screen_line) end function Text.screen_line_index(screen_line_starting_pos, pos) @@ -774,12 +774,12 @@ end -- convert x pixel coordinate to pos -- oblivious to wrapping -- result: 1 to len+1 -function Text.nearest_cursor_pos(line, x, left) +function Text.nearest_cursor_pos(font, line, x, left) if x < left then return 1 end local len = utf8.len(line) - local max_x = left+Text.x(line, len+1) + local max_x = left+Text.x(font, line, len+1) if x > max_x then return len+1 end @@ -791,8 +791,8 @@ function Text.nearest_cursor_pos(line, x, left) return leftpos end local curr = math.floor((leftpos+rightpos)/2) - local currxmin = left+Text.x(line, curr) - local currxmax = left+Text.x(line, curr+1) + local currxmin = left+Text.x(font, line, curr) + local currxmax = left+Text.x(font, line, curr+1) --? print('nearest', x, leftpos, rightpos, curr, currxmin, currxmax) if currxmin <= x and x < currxmax then if x-currxmin < currxmax-x then @@ -816,18 +816,18 @@ end -- return the nearest index of line (in utf8 code points) which lies entirely -- within x pixels of the left margin -- result: 0 to len+1 -function Text.nearest_pos_less_than(line, x) +function Text.nearest_pos_less_than(font, line, x) --? print('', '-- nearest_pos_less_than', line, x) local len = utf8.len(line) - local max_x = Text.x_after(line, len) + local max_x = Text.x_after(font, line, len) if x > max_x then return len+1 end local left, right = 0, len+1 while true do local curr = math.floor((left+right)/2) - local currxmin = Text.x_after(line, curr+1) - local currxmax = Text.x_after(line, curr+2) + local currxmin = Text.x_after(font, line, curr+1) + local currxmax = Text.x_after(font, line, curr+2) --? print('', x, left, right, curr, currxmin, currxmax) if currxmin <= x and x < currxmax then return curr @@ -844,18 +844,18 @@ function Text.nearest_pos_less_than(line, x) assert(false, 'failed to map x pixel to pos') end -function Text.x_after(s, pos) +function Text.x_after(font, s, pos) local len = utf8.len(s) local offset = Text.offset(s, math.min(pos+1, len+1)) local s_before = s:sub(1, offset-1) --? print('^'..s_before..'$') - return App.width(s_before) + return font:getWidth(s_before) end -function Text.x(s, pos) +function Text.x(font, s, pos) local offset = Text.offset(s, pos) local s_before = s:sub(1, offset-1) - return App.width(s_before) + return font:getWidth(s_before) end function Text.to2(State, loc1) -- cgit 1.4.1-2-gfad0