Compare commits

...

9 Commits

Author SHA1 Message Date
Kartik K. Agaram 2eb71d7a80 Merge lines.love 2024-06-11 11:46:36 -07:00
Kartik K. Agaram 55f5c2d696 crap, fix some final changes in the source editor 2024-06-11 11:10:34 -07:00
Kartik K. Agaram 7aa43d1d2d comment 2024-06-11 10:49:56 -07:00
Kartik K. Agaram 85b09772ba whitespace 2024-06-11 10:49:34 -07:00
Kartik K. Agaram 69c88da98c stop caching starty
This is quite useful because I used to have a long list of places in
which to invalidate the cache.
2024-06-11 10:37:58 -07:00
Kartik K. Agaram f2299cb422 stop caching screen_bottom1
I'm not sure this is very useful. I had an initial idea to stop using
screen_bottom1 in final_text_loc_on_screen, by starting from screen_top1
rather than screen_bottom1. But that changes the direction in which we
scan for the text line in situations where there is somehow no text on
screen (something that should never happen but I have zero confidence in
that).

Still, it doesn't seem like a bad thing to drastically reduce the
lifetime of some derived state.

Really what I need to do is throw this whole UX out and allow the cursor
to be on a drawing as a whole. So up arrow or left arrow below a drawing
would focus the whole drawing in a red border, and another up arrow and
left arrow would skip the drawing and continue upward. I think that
change to the UX will eliminate a whole class of special cases in the
code.
2024-06-11 07:02:46 -07:00
Kartik K. Agaram 19615eade0 bugfix in source editor: don't clear selection on M-arrow 2024-06-09 20:38:53 -07:00
Kartik K. Agaram 9b5a78d3c5 bugfix in source editor 2024-06-09 20:35:50 -07:00
Kartik K. Agaram 9501f01ca0 fix a crash involving mouse and drawings
Thanks Alex Schroeder for reporting this crash. The scenario:
  * Edit a file like say this repo's Readme.
  * The second line is empty and there's a '+' to insert a drawing.
    Click on that.
  * Resize the window so just the first line of text and the drawing are
    visible.
  * Close the window.
  * Reopen lines.love, it will reopen the same file.
  * Click on the left margin to the left of the drawing.

Before this commit these steps yielded the following crash:

  Error: bad argument #1 to 'len' (string expected, got nil)
  text.lua:626: in function 'pos_at_end_of_screen_line'
  edit.lua:298: in function 'mouse_press'

There were two distinct problems here:

1. State.screen_bottom1 is not required to point to a text line, it
   could just as well be a drawing. I have been sloppy in handling that.
2. The bug was partially masked (the need to close and reopen the
   window) by a second bug: inserting a drawing was not invalidating the
   cache I save of starty coordinates for each line. (I've inserted and
   deleted starty invalidations a few times in the past, but it looks
   like I'd never had one in this particular location edit.draw before.)

How did these issues get missed for years?
  - Even though I use lines.love on a daily basis, it turns out I don't
    actually create line drawings all that often.
  - When I do, I'm still living in files that are mostly text with only
    an occasional drawing.
  - I keep my windows fairly large.

Between these 3 patterns, the odds of running into a drawing as the
first or bottom-most line on the screen were fairly small. And then I
had to interact with it. I suspect I tend to interact with drawings
after centering them vertically.

---

Bug #1 in particular has some interesting past history.

* Near the start of the project, when I implemented line-wrapping I
  started saving screen_bottom, the bottom-most line displayed on
  screen. I did this so I could scroll down easily just by assigning
  `screen_top = screen_bottom`. (On the other hand, scrolling up still
  required some work. I should perhaps get rid of it and just compute
  scrolls from scratch each time.)

* Also near the start of the project, I supported selecting text by a
  complex state machine spanning keypress, mouse press and mouse
  release:
    mouse click (press and immediate release) moves cursor
    mouse drag (press and much later release) creates selection
    shift-click selects from current cursor to click location
    shift-movement creates/grows a selection

* On 2023-06-01, inscript reported a bug. Opening a window with just a
  little bit of text (lots of unused space in the window), selecting all
  the text and then clicking below all the text would crash the editor.

  To fix this I added code at the bottom of edit.mouse_press which
  computed the final visible line+pos location and used that in the
  cursor-move/text-selection state machine. It did this computation
  based on.. screen_bottom. But I didn't notice that screen_bottom could
  be a drawing (which has no pos). This commit's bug/regression was
  created.

* On 2023-09-20, Matt Wynne encountered a crash which got me to realize
  I need code at the bottom of edit.mouse_release symmetric to the code
  at the bottom of edit.mouse_press. I still didn't notice that
  screen_bottom could be a drawing.

So in fixing inscript's bug report, I introduced (at least) 2
regressions, because I either had no idea or quickly forgot that
screen_bottom could point at a drawing.

While I created regressions, the underlying mental bug feels new. I just
never focused on the fact that screen_bottom could point at a drawing.

This past history makes me suspicious of my mouse_press/mouse_release
code. I think I'm going to get rid of screen_bottom entirely as a
concept. I'll still have to be careful though about the remaining
locations and which of them are allowed to point at drawings:

  - cursor and selection are not allowed to point at drawings
  - screen_top and screen_bottom are allowed to point at drawings

I sometimes copy between these 4 location variables. Auditing shows no
gaps where cursor could ever end up pointing at a drawing. It's just
when I started using screen_bottom for a whole new purpose (in
the mouse_press/release state machine) that I went wrong.

I should also try getting rid of starty entirely. Is it _really_ needed
for a responsive editor? I think I introduced it back when I didn't know
what I was doing with LÖVE and was profligately creating text objects
willy-nilly just to compute widths.

Getting rid of these two fairly global bits of mutable state will
hopefully make lines much more robust when the next person tries it out
in 6 months :-/ X-(

Thanks everyone for the conversation around this bug:
  https://merveilles.town/@akkartik/112567862542495637

---

Bug #2 has some complexity as well, and might lead to some follow-on
cleanup.

When I click on the button to insert a new drawing, the mouse_release
hook triggers and moves the cursor below the new drawing. This is
desirable, but I'd never noticed this happy accident. It stops working
when I invalidate starty for all lines (which gets recomputed and cached
for all visible lines on every frame).

Fixing this caused a couple of unit tests start crashing for 2 reasons
that required their own minor fixes:

  - My emulated mouse press and release didn't have an intervening
    frame and so mouse_release no longer receives starty. Now I've added
    a call to edit.draw() between press and release.

    This might actually bite someone for real someday, if they're
    running on a slow computer or something like that. I've tried to
    click really fast but I can't seem to put mouse_press and release in
    the same frame (assuming 30 frames per second)

  - My tests' window dimensions often violate my constraint that the
    screen always have one line of text for showing the cursor. They're
    unrealistically small or have a really wide aspect ratio (width 2x
    of height). I suspect lines.love will itself crash in those
    situations, but hopefully they're unrealistic. Hmm, I wonder what
    would happen if someone maximized in a 16:9 screen, that's almost
    2x.. Anyways, I've cleaned a couple of tests up, but might need to
    fix up others at some point. I'd have to rejigger all my brittle
    line-wrapping tests if I modify the screen width :-/ X-(
2024-06-09 13:17:55 -07:00
15 changed files with 387 additions and 433 deletions

View File

@ -73,6 +73,11 @@ found anything amiss: http://akkartik.name/contact
* No clipping yet for drawings. In particular, circles/squares/rectangles and * No clipping yet for drawings. In particular, circles/squares/rectangles and
point labels can overflow a drawing. point labels can overflow a drawing.
* If you ever see a crash when clicking on the mouse, it might be because a
mouse press and release need to happen in separate frames. Try pressing and
releasing more slowly and let me know if that helps or not. This is klunky,
sorry.
* Touchpads can drag the mouse pointer using a light touch or a heavy click. * Touchpads can drag the mouse pointer using a light touch or a heavy click.
On Linux, drags using the light touch get interrupted when a key is pressed. On Linux, drags using the light touch get interrupted when a key is pressed.
You'll have to press down to drag. You'll have to press down to drag.

View File

@ -130,6 +130,8 @@ function App.run_tests()
end end
end end
table.sort(sorted_names) table.sort(sorted_names)
--? App.initialize_for_test() -- debug: run a single test at a time like these 2 lines
--? test_click_below_all_lines()
for _,name in ipairs(sorted_names) do for _,name in ipairs(sorted_names) do
App.initialize_for_test() App.initialize_for_test()
--? print('=== '..name) --? print('=== '..name)
@ -404,9 +406,10 @@ end
-- prepend file/line/test -- prepend file/line/test
function prepend_debug_info_to_test_failure(test_name, err) function prepend_debug_info_to_test_failure(test_name, err)
local err_without_line_number = err:gsub('^[^:]*:[^:]*: ', '') local err_without_line_number = err:gsub('^[^:]*:[^:]*: ', '')
local stack_trace = debug.traceback('', --[[stack frame]]5) local stack_trace = debug.traceback('', --[[stack frame]]5) -- most likely to be useful, but set to 0 for a complete stack trace
local file_and_line_number = stack_trace:gsub('stack traceback:\n', ''):gsub(': .*', '') local file_and_line_number = stack_trace:gsub('stack traceback:\n', ''):gsub(': .*', '')
local full_error = file_and_line_number..':'..test_name..' -- '..err_without_line_number local full_error = file_and_line_number..':'..test_name..' -- '..err_without_line_number
-- uncomment this line for a complete stack trace
--? local full_error = file_and_line_number..':'..test_name..' -- '..err_without_line_number..'\t\t'..stack_trace:gsub('\n', '\n\t\t') --? local full_error = file_and_line_number..':'..test_name..' -- '..err_without_line_number..'\t\t'..stack_trace:gsub('\n', '\n\t\t')
table.insert(Test_errors, full_error) table.insert(Test_errors, full_error)
end end

View File

@ -6,16 +6,15 @@ require 'drawing_tests'
-- into 256 parts. -- into 256 parts.
function Drawing.draw(State, line_index, y) function Drawing.draw(State, line_index, y)
local line = State.lines[line_index] local line = State.lines[line_index]
local line_cache = State.line_cache[line_index]
line_cache.starty = y
local pmx,pmy = App.mouse_x(), App.mouse_y() local pmx,pmy = App.mouse_x(), App.mouse_y()
if pmx < State.right and pmy > line_cache.starty and pmy < line_cache.starty+Drawing.pixels(line.h, State.width) then local starty = Text.starty(State, line_index)
if pmx < State.right and pmy > starty and pmy < starty+Drawing.pixels(line.h, State.width) then
App.color(Icon_color) App.color(Icon_color)
love.graphics.rectangle('line', State.left,line_cache.starty, State.width,Drawing.pixels(line.h, State.width)) love.graphics.rectangle('line', State.left,starty, State.width,Drawing.pixels(line.h, State.width))
if icon[State.current_drawing_mode] then if icon[State.current_drawing_mode] then
icon[State.current_drawing_mode](State.right-22, line_cache.starty+4) icon[State.current_drawing_mode](State.right-22, starty+4)
else else
icon[State.previous_drawing_mode](State.right-22, line_cache.starty+4) icon[State.previous_drawing_mode](State.right-22, starty+4)
end end
if App.mouse_down(1) and love.keyboard.isDown('h') then if App.mouse_down(1) and love.keyboard.isDown('h') then
@ -30,7 +29,7 @@ function Drawing.draw(State, line_index, y)
end end
local mx = Drawing.coord(pmx-State.left, State.width) local mx = Drawing.coord(pmx-State.left, State.width)
local my = Drawing.coord(pmy-line_cache.starty, State.width) local my = Drawing.coord(pmy-starty, State.width)
for _,shape in ipairs(line.shapes) do for _,shape in ipairs(line.shapes) do
if geom.on_shape(mx,my, line, shape) then if geom.on_shape(mx,my, line, shape) then
@ -38,11 +37,11 @@ function Drawing.draw(State, line_index, y)
else else
App.color(Stroke_color) App.color(Stroke_color)
end end
Drawing.draw_shape(line, shape, line_cache.starty, State.left,State.right) Drawing.draw_shape(line, shape, starty, State.left,State.right)
end end
local function px(x) return Drawing.pixels(x, State.width)+State.left end local function px(x) return Drawing.pixels(x, State.width)+State.left end
local function py(y) return Drawing.pixels(y, State.width)+line_cache.starty end local function py(y) return Drawing.pixels(y, State.width)+starty end
for i,p in ipairs(line.points) do for i,p in ipairs(line.points) do
if p.deleted == nil then if p.deleted == nil then
if Drawing.near(p, mx,my, State.width) then if Drawing.near(p, mx,my, State.width) then
@ -71,7 +70,7 @@ function Drawing.draw(State, line_index, y)
end end
end end
App.color(Current_stroke_color) App.color(Current_stroke_color)
Drawing.draw_pending_shape(line, line_cache.starty, State.left,State.right) Drawing.draw_pending_shape(line, starty, State.left,State.right)
end end
function Drawing.draw_shape(drawing, shape, top, left,right) function Drawing.draw_shape(drawing, shape, top, left,right)
@ -209,17 +208,24 @@ function Drawing.draw_pending_shape(drawing, top, left,right)
end end
end end
function Drawing.in_drawing(drawing, line_cache, x,y, left,right) function Drawing.in_current_drawing(State, x,y, left,right)
if line_cache.starty == nil then return false end -- outside current page return Drawing.in_drawing(State, State.lines.current_drawing_index, x,y, left,right)
end
function Drawing.in_drawing(State, line_index, x,y, left,right)
assert(State.lines[line_index].mode == 'drawing')
local starty = Text.starty(State, line_index)
if starty == nil then return false end -- outside current page
local drawing = State.lines[line_index]
local width = right-left local width = right-left
return y >= line_cache.starty and y < line_cache.starty + Drawing.pixels(drawing.h, width) and x >= left and x < right return y >= starty and y < starty + Drawing.pixels(drawing.h, width) and x >= left and x < right
end end
function Drawing.mouse_press(State, drawing_index, x,y, mouse_button) function Drawing.mouse_press(State, drawing_index, x,y, mouse_button)
local drawing = State.lines[drawing_index] local drawing = State.lines[drawing_index]
local line_cache = State.line_cache[drawing_index] local starty = Text.starty(State, drawing_index)
local cx = Drawing.coord(x-State.left, State.width) local cx = Drawing.coord(x-State.left, State.width)
local cy = Drawing.coord(y-line_cache.starty, State.width) local cy = Drawing.coord(y-starty, State.width)
if State.current_drawing_mode == 'freehand' then if State.current_drawing_mode == 'freehand' then
drawing.pending = {mode=State.current_drawing_mode, points={{x=cx, y=cy}}} drawing.pending = {mode=State.current_drawing_mode, points={{x=cx, y=cy}}}
elseif State.current_drawing_mode == 'line' or State.current_drawing_mode == 'manhattan' then elseif State.current_drawing_mode == 'line' or State.current_drawing_mode == 'manhattan' then
@ -244,8 +250,8 @@ end
function Drawing.update(State) function Drawing.update(State)
if State.lines.current_drawing == nil then return end if State.lines.current_drawing == nil then return end
local drawing = State.lines.current_drawing local drawing = State.lines.current_drawing
local line_cache = State.line_cache[State.lines.current_drawing_index] local starty = Text.starty(State, State.lines.current_drawing_index)
if line_cache.starty == nil then if starty == nil then
-- some event cleared starty just this frame -- some event cleared starty just this frame
-- draw in this frame will soon set starty -- draw in this frame will soon set starty
-- just skip this frame -- just skip this frame
@ -254,9 +260,9 @@ function Drawing.update(State)
assert(drawing.mode == 'drawing', 'Drawing.update: line is not a drawing') assert(drawing.mode == 'drawing', 'Drawing.update: line is not a drawing')
local pmx, pmy = App.mouse_x(), App.mouse_y() local pmx, pmy = App.mouse_x(), App.mouse_y()
local mx = Drawing.coord(pmx-State.left, State.width) local mx = Drawing.coord(pmx-State.left, State.width)
local my = Drawing.coord(pmy-line_cache.starty, State.width) local my = Drawing.coord(pmy-starty, State.width)
if App.mouse_down(1) then if App.mouse_down(1) then
if Drawing.in_drawing(drawing, line_cache, pmx,pmy, State.left,State.right) then if Drawing.in_current_drawing(State, pmx,pmy, State.left,State.right) then
if drawing.pending.mode == 'freehand' then if drawing.pending.mode == 'freehand' then
table.insert(drawing.pending.points, {x=mx, y=my}) table.insert(drawing.pending.points, {x=mx, y=my})
elseif drawing.pending.mode == 'move' then elseif drawing.pending.mode == 'move' then
@ -266,7 +272,7 @@ function Drawing.update(State)
end end
end end
elseif State.current_drawing_mode == 'move' then elseif State.current_drawing_mode == 'move' then
if Drawing.in_drawing(drawing, line_cache, pmx, pmy, State.left,State.right) then if Drawing.in_current_drawing(State, pmx, pmy, State.left,State.right) then
drawing.pending.target_point.x = mx drawing.pending.target_point.x = mx
drawing.pending.target_point.y = my drawing.pending.target_point.y = my
Drawing.relax_constraints(drawing, drawing.pending.target_point_index) Drawing.relax_constraints(drawing, drawing.pending.target_point_index)
@ -304,7 +310,7 @@ function Drawing.mouse_release(State, x,y, mouse_button)
end end
elseif State.lines.current_drawing then elseif State.lines.current_drawing then
local drawing = State.lines.current_drawing local drawing = State.lines.current_drawing
local line_cache = State.line_cache[State.lines.current_drawing_index] local starty = Text.starty(State, State.lines.current_drawing_index)
if drawing.pending then if drawing.pending then
if drawing.pending.mode == nil then if drawing.pending.mode == nil then
-- nothing pending -- nothing pending
@ -313,14 +319,14 @@ function Drawing.mouse_release(State, x,y, mouse_button)
Drawing.smoothen(drawing.pending) Drawing.smoothen(drawing.pending)
table.insert(drawing.shapes, drawing.pending) table.insert(drawing.shapes, drawing.pending)
elseif drawing.pending.mode == 'line' then elseif drawing.pending.mode == 'line' then
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width) local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)
if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then
drawing.pending.p2 = Drawing.find_or_insert_point(drawing.points, mx,my, State.width) drawing.pending.p2 = Drawing.find_or_insert_point(drawing.points, mx,my, State.width)
table.insert(drawing.shapes, drawing.pending) table.insert(drawing.shapes, drawing.pending)
end end
elseif drawing.pending.mode == 'manhattan' then elseif drawing.pending.mode == 'manhattan' then
local p1 = drawing.points[drawing.pending.p1] local p1 = drawing.points[drawing.pending.p1]
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width) local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)
if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then
if math.abs(mx-p1.x) > math.abs(my-p1.y) then if math.abs(mx-p1.x) > math.abs(my-p1.y) then
drawing.pending.p2 = Drawing.find_or_insert_point(drawing.points, mx, p1.y, State.width) drawing.pending.p2 = Drawing.find_or_insert_point(drawing.points, mx, p1.y, State.width)
@ -328,11 +334,11 @@ function Drawing.mouse_release(State, x,y, mouse_button)
drawing.pending.p2 = Drawing.find_or_insert_point(drawing.points, p1.x, my, State.width) drawing.pending.p2 = Drawing.find_or_insert_point(drawing.points, p1.x, my, State.width)
end end
local p2 = drawing.points[drawing.pending.p2] local p2 = drawing.points[drawing.pending.p2]
App.mouse_move(State.left+Drawing.pixels(p2.x, State.width), line_cache.starty+Drawing.pixels(p2.y, State.width)) App.mouse_move(State.left+Drawing.pixels(p2.x, State.width), starty+Drawing.pixels(p2.y, State.width))
table.insert(drawing.shapes, drawing.pending) table.insert(drawing.shapes, drawing.pending)
end end
elseif drawing.pending.mode == 'polygon' then elseif drawing.pending.mode == 'polygon' then
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width) local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)
if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then
table.insert(drawing.pending.vertices, Drawing.find_or_insert_point(drawing.points, mx,my, State.width)) table.insert(drawing.pending.vertices, Drawing.find_or_insert_point(drawing.points, mx,my, State.width))
table.insert(drawing.shapes, drawing.pending) table.insert(drawing.shapes, drawing.pending)
@ -340,7 +346,7 @@ function Drawing.mouse_release(State, x,y, mouse_button)
elseif drawing.pending.mode == 'rectangle' then elseif drawing.pending.mode == 'rectangle' then
assert(#drawing.pending.vertices <= 2, 'Drawing.mouse_release: rectangle has too many pending vertices') assert(#drawing.pending.vertices <= 2, 'Drawing.mouse_release: rectangle has too many pending vertices')
if #drawing.pending.vertices == 2 then if #drawing.pending.vertices == 2 then
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width) local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)
if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then
local first = drawing.points[drawing.pending.vertices[1]] local first = drawing.points[drawing.pending.vertices[1]]
local second = drawing.points[drawing.pending.vertices[2]] local second = drawing.points[drawing.pending.vertices[2]]
@ -355,7 +361,7 @@ function Drawing.mouse_release(State, x,y, mouse_button)
elseif drawing.pending.mode == 'square' then elseif drawing.pending.mode == 'square' then
assert(#drawing.pending.vertices <= 2, 'Drawing.mouse_release: square has too many pending vertices') assert(#drawing.pending.vertices <= 2, 'Drawing.mouse_release: square has too many pending vertices')
if #drawing.pending.vertices == 2 then if #drawing.pending.vertices == 2 then
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width) local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)
if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then
local first = drawing.points[drawing.pending.vertices[1]] local first = drawing.points[drawing.pending.vertices[1]]
local second = drawing.points[drawing.pending.vertices[2]] local second = drawing.points[drawing.pending.vertices[2]]
@ -366,14 +372,14 @@ function Drawing.mouse_release(State, x,y, mouse_button)
end end
end end
elseif drawing.pending.mode == 'circle' then elseif drawing.pending.mode == 'circle' then
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width) local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)
if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then
local center = drawing.points[drawing.pending.center] local center = drawing.points[drawing.pending.center]
drawing.pending.radius = round(geom.dist(center.x,center.y, mx,my)) drawing.pending.radius = round(geom.dist(center.x,center.y, mx,my))
table.insert(drawing.shapes, drawing.pending) table.insert(drawing.shapes, drawing.pending)
end end
elseif drawing.pending.mode == 'arc' then elseif drawing.pending.mode == 'arc' then
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width) local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)
if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h then
local center = drawing.points[drawing.pending.center] local center = drawing.points[drawing.pending.center]
drawing.pending.end_angle = geom.angle_with_hint(center.x,center.y, mx,my, drawing.pending.end_angle) drawing.pending.end_angle = geom.angle_with_hint(center.x,center.y, mx,my, drawing.pending.end_angle)
@ -477,13 +483,15 @@ function Drawing.keychord_press(State, chord)
end end
drawing.pending.mode = 'square' drawing.pending.mode = 'square'
elseif App.mouse_down(1) and chord == 'p' and State.current_drawing_mode == 'polygon' then elseif App.mouse_down(1) and chord == 'p' and State.current_drawing_mode == 'polygon' then
local _,drawing,line_cache = Drawing.current_drawing(State) local drawing_index,drawing = Drawing.current_drawing(State)
local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-line_cache.starty, State.width) local starty = Text.starty(State, drawing_index)
local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-starty, State.width)
local j = Drawing.find_or_insert_point(drawing.points, mx,my, State.width) local j = Drawing.find_or_insert_point(drawing.points, mx,my, State.width)
table.insert(drawing.pending.vertices, j) table.insert(drawing.pending.vertices, j)
elseif App.mouse_down(1) and chord == 'p' and (State.current_drawing_mode == 'rectangle' or State.current_drawing_mode == 'square') then elseif App.mouse_down(1) and chord == 'p' and (State.current_drawing_mode == 'rectangle' or State.current_drawing_mode == 'square') then
local _,drawing,line_cache = Drawing.current_drawing(State) local drawing_index,drawing = Drawing.current_drawing(State)
local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-line_cache.starty, State.width) local starty = Text.starty(State, drawing_index)
local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-starty, State.width)
local j = Drawing.find_or_insert_point(drawing.points, mx,my, State.width) local j = Drawing.find_or_insert_point(drawing.points, mx,my, State.width)
while #drawing.pending.vertices >= 2 do while #drawing.pending.vertices >= 2 do
table.remove(drawing.pending.vertices) table.remove(drawing.pending.vertices)
@ -492,9 +500,10 @@ function Drawing.keychord_press(State, chord)
elseif chord == 'C-o' and not App.mouse_down(1) then elseif chord == 'C-o' and not App.mouse_down(1) then
State.current_drawing_mode = 'circle' State.current_drawing_mode = 'circle'
elseif App.mouse_down(1) and chord == 'a' and State.current_drawing_mode == 'circle' then elseif App.mouse_down(1) and chord == 'a' and State.current_drawing_mode == 'circle' then
local _,drawing,line_cache = Drawing.current_drawing(State) local drawing_index,drawing = Drawing.current_drawing(State)
local starty = Text.starty(State, drawing_index)
drawing.pending.mode = 'arc' drawing.pending.mode = 'arc'
local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-line_cache.starty, State.width) local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-starty, State.width)
local center = drawing.points[drawing.pending.center] local center = drawing.points[drawing.pending.center]
drawing.pending.radius = round(geom.dist(center.x,center.y, mx,my)) drawing.pending.radius = round(geom.dist(center.x,center.y, mx,my))
drawing.pending.start_angle = geom.angle(center.x,center.y, mx,my) drawing.pending.start_angle = geom.angle(center.x,center.y, mx,my)
@ -510,7 +519,7 @@ function Drawing.keychord_press(State, chord)
end end
drawing.pending.mode = 'circle' drawing.pending.mode = 'circle'
elseif chord == 'C-u' and not App.mouse_down(1) then elseif chord == 'C-u' and not App.mouse_down(1) then
local drawing_index,drawing,line_cache,i,p = Drawing.select_point_at_mouse(State) local drawing_index,drawing,_,i,p = Drawing.select_point_at_mouse(State)
if drawing then if drawing then
if State.previous_drawing_mode == nil then if State.previous_drawing_mode == nil then
State.previous_drawing_mode = State.current_drawing_mode State.previous_drawing_mode = State.current_drawing_mode
@ -521,7 +530,7 @@ function Drawing.keychord_press(State, chord)
State.lines.current_drawing = drawing State.lines.current_drawing = drawing
end end
elseif chord == 'C-n' and not App.mouse_down(1) then elseif chord == 'C-n' and not App.mouse_down(1) then
local drawing_index,drawing,line_cache,point_index,p = Drawing.select_point_at_mouse(State) local drawing_index,drawing,_,point_index,p = Drawing.select_point_at_mouse(State)
if drawing then if drawing then
if State.previous_drawing_mode == nil then if State.previous_drawing_mode == nil then
-- don't clobber -- don't clobber
@ -619,9 +628,8 @@ function Drawing.current_drawing(State)
local x, y = App.mouse_x(), App.mouse_y() local x, y = App.mouse_x(), App.mouse_y()
for drawing_index,drawing in ipairs(State.lines) do for drawing_index,drawing in ipairs(State.lines) do
if drawing.mode == 'drawing' then if drawing.mode == 'drawing' then
local line_cache = State.line_cache[drawing_index] if Drawing.in_drawing(State, drawing_index, x,y, State.left,State.right) then
if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) then return drawing_index,drawing
return drawing_index,drawing,line_cache
end end
end end
end end
@ -632,12 +640,12 @@ function Drawing.select_shape_at_mouse(State)
for drawing_index,drawing in ipairs(State.lines) do for drawing_index,drawing in ipairs(State.lines) do
if drawing.mode == 'drawing' then if drawing.mode == 'drawing' then
local x, y = App.mouse_x(), App.mouse_y() local x, y = App.mouse_x(), App.mouse_y()
local line_cache = State.line_cache[drawing_index] local starty = Text.starty(State, drawing_index)
if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) then if Drawing.in_drawing(State, drawing_index, x,y, State.left,State.right) then
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width) local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)
for i,shape in ipairs(drawing.shapes) do for i,shape in ipairs(drawing.shapes) do
if geom.on_shape(mx,my, drawing, shape) then if geom.on_shape(mx,my, drawing, shape) then
return drawing,line_cache,i,shape return drawing,starty,i,shape
end end
end end
end end
@ -649,12 +657,12 @@ function Drawing.select_point_at_mouse(State)
for drawing_index,drawing in ipairs(State.lines) do for drawing_index,drawing in ipairs(State.lines) do
if drawing.mode == 'drawing' then if drawing.mode == 'drawing' then
local x, y = App.mouse_x(), App.mouse_y() local x, y = App.mouse_x(), App.mouse_y()
local line_cache = State.line_cache[drawing_index] local starty = Text.starty(State, drawing_index)
if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) then if Drawing.in_drawing(State, drawing_index, x,y, State.left,State.right) then
local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width) local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-starty, State.width)
for i,point in ipairs(drawing.points) do for i,point in ipairs(drawing.points) do
if Drawing.near(point, mx,my, State.width) then if Drawing.near(point, mx,my, State.width) then
return drawing_index,drawing,line_cache,i,point return drawing_index,drawing,starty,i,point
end end
end end
end end
@ -666,8 +674,7 @@ function Drawing.select_drawing_at_mouse(State)
for drawing_index,drawing in ipairs(State.lines) do for drawing_index,drawing in ipairs(State.lines) do
if drawing.mode == 'drawing' then if drawing.mode == 'drawing' then
local x, y = App.mouse_x(), App.mouse_y() local x, y = App.mouse_x(), App.mouse_y()
local line_cache = State.line_cache[drawing_index] if Drawing.in_drawing(State, drawing_index, x,y, State.left,State.right) then
if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) then
return drawing return drawing
end end
end end

View File

@ -3,7 +3,7 @@
-- of specific shapes. In particular, no tests of freehand drawings. -- of specific shapes. In particular, no tests of freehand drawings.
function test_creating_drawing_saves() function test_creating_drawing_saves()
App.screen.init{width=120, height=60} App.screen.init{width=800, height=600}
Editor_state = edit.initialize_test_state() Editor_state = edit.initialize_test_state()
Editor_state.filename = 'foo' Editor_state.filename = 'foo'
Editor_state.lines = load_array{} Editor_state.lines = load_array{}
@ -32,7 +32,7 @@ function test_draw_line()
edit.draw(Editor_state) edit.draw(Editor_state)
check_eq(#Editor_state.lines, 2, 'baseline/#lines') check_eq(#Editor_state.lines, 2, 'baseline/#lines')
check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode') check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode')
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y') check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Editor_state.lines[1].h, 128, 'baseline/y') check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes') check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
-- draw a line -- draw a line
@ -77,7 +77,7 @@ function test_draw_horizontal_line()
edit.draw(Editor_state) edit.draw(Editor_state)
check_eq(#Editor_state.lines, 2, 'baseline/#lines') check_eq(#Editor_state.lines, 2, 'baseline/#lines')
check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode') check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode')
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y') check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Editor_state.lines[1].h, 128, 'baseline/y') check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes') check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
-- draw a line that is more horizontal than vertical -- draw a line that is more horizontal than vertical
@ -105,7 +105,7 @@ function test_draw_circle()
edit.draw(Editor_state) edit.draw(Editor_state)
check_eq(#Editor_state.lines, 2, 'baseline/#lines') check_eq(#Editor_state.lines, 2, 'baseline/#lines')
check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode') check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode')
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y') check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Editor_state.lines[1].h, 128, 'baseline/y') check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes') check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
-- draw a circle -- draw a circle
@ -134,7 +134,7 @@ function test_cancel_stroke()
edit.draw(Editor_state) edit.draw(Editor_state)
check_eq(#Editor_state.lines, 2, 'baseline/#lines') check_eq(#Editor_state.lines, 2, 'baseline/#lines')
check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode') check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode')
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y') check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Editor_state.lines[1].h, 128, 'baseline/y') check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes') check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
-- start drawing a line -- start drawing a line
@ -172,7 +172,7 @@ function test_draw_circle_mid_stroke()
edit.draw(Editor_state) edit.draw(Editor_state)
check_eq(#Editor_state.lines, 2, 'baseline/#lines') check_eq(#Editor_state.lines, 2, 'baseline/#lines')
check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode') check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode')
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y') check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Editor_state.lines[1].h, 128, 'baseline/y') check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes') check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
-- draw a circle -- draw a circle
@ -200,7 +200,7 @@ function test_draw_arc()
edit.draw(Editor_state) edit.draw(Editor_state)
check_eq(#Editor_state.lines, 2, 'baseline/#lines') check_eq(#Editor_state.lines, 2, 'baseline/#lines')
check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode') check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode')
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y') check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Editor_state.lines[1].h, 128, 'baseline/y') check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes') check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
-- draw an arc -- draw an arc
@ -231,7 +231,7 @@ function test_draw_polygon()
check_eq(Editor_state.current_drawing_mode, 'line', 'baseline/drawing_mode') check_eq(Editor_state.current_drawing_mode, 'line', 'baseline/drawing_mode')
check_eq(#Editor_state.lines, 2, 'baseline/#lines') check_eq(#Editor_state.lines, 2, 'baseline/#lines')
check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode') check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode')
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y') check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Editor_state.lines[1].h, 128, 'baseline/y') check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes') check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
-- first point -- first point
@ -269,7 +269,7 @@ function test_draw_rectangle()
check_eq(Editor_state.current_drawing_mode, 'line', 'baseline/drawing_mode') check_eq(Editor_state.current_drawing_mode, 'line', 'baseline/drawing_mode')
check_eq(#Editor_state.lines, 2, 'baseline/#lines') check_eq(#Editor_state.lines, 2, 'baseline/#lines')
check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode') check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode')
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y') check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Editor_state.lines[1].h, 128, 'baseline/y') check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes') check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
-- first point -- first point
@ -313,7 +313,7 @@ function test_draw_rectangle_intermediate()
check_eq(Editor_state.current_drawing_mode, 'line', 'baseline/drawing_mode') check_eq(Editor_state.current_drawing_mode, 'line', 'baseline/drawing_mode')
check_eq(#Editor_state.lines, 2, 'baseline/#lines') check_eq(#Editor_state.lines, 2, 'baseline/#lines')
check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode') check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode')
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y') check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Editor_state.lines[1].h, 128, 'baseline/y') check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes') check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
-- first point -- first point
@ -349,7 +349,7 @@ function test_draw_square()
check_eq(Editor_state.current_drawing_mode, 'line', 'baseline/drawing_mode') check_eq(Editor_state.current_drawing_mode, 'line', 'baseline/drawing_mode')
check_eq(#Editor_state.lines, 2, 'baseline/#lines') check_eq(#Editor_state.lines, 2, 'baseline/#lines')
check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode') check_eq(Editor_state.lines[1].mode, 'drawing', 'baseline/mode')
check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'baseline/y') check_eq(Text.starty(Editor_state, 1), Editor_state.top+Drawing_padding_top, 'baseline/y')
check_eq(Editor_state.lines[1].h, 128, 'baseline/y') check_eq(Editor_state.lines[1].h, 128, 'baseline/y')
check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes') check_eq(#Editor_state.lines[1].shapes, 0, 'baseline/#shapes')
-- first point -- first point

View File

@ -31,7 +31,6 @@ function edit.initialize_state(top, left, right, font, font_height, line_height)
-- string data, -- string data,
-- a drawing is a table with: -- a drawing is a table with:
-- mode = 'drawing' -- mode = 'drawing'
-- a (y) coord in pixels (updated while painting screen),
-- a (h)eight, -- a (h)eight,
-- an array of points, and -- an array of points, and
-- an array of shapes -- an array of shapes
@ -52,7 +51,6 @@ function edit.initialize_state(top, left, right, font, font_height, line_height)
-- rendering wrapped text lines needs some additional short-lived data per line: -- rendering wrapped text lines needs some additional short-lived data per line:
-- startpos, the index of data the line starts rendering from, can only be >1 for topmost line on screen -- startpos, the index of data the line starts rendering from, can only be >1 for topmost line on screen
-- starty, the y coord in pixels the line starts rendering from
-- fragments: snippets of the line guaranteed to not straddle screen lines -- fragments: snippets of the line guaranteed to not straddle screen lines
-- screen_line_starting_pos: optional array of grapheme indices if it wraps over more than one screen line -- screen_line_starting_pos: optional array of grapheme indices if it wraps over more than one screen line
line_cache = {}, line_cache = {},
@ -66,9 +64,10 @@ function edit.initialize_state(top, left, right, font, font_height, line_height)
-- --
-- Make sure these coordinates are never aliased, so that changing one causes -- Make sure these coordinates are never aliased, so that changing one causes
-- action at a distance. -- action at a distance.
--
-- On lines that are drawings, pos will be nil.
screen_top1 = {line=1, pos=1}, -- position of start of screen line at top of screen screen_top1 = {line=1, pos=1}, -- position of start of screen line at top of screen
cursor1 = {line=1, pos=1}, -- position of cursor cursor1 = {line=1, pos=1}, -- position of cursor; must be on a text line
screen_bottom1 = {line=1, pos=1}, -- position of start of screen line at bottom of screen
selection1 = {}, selection1 = {},
-- some extra state to compute selection between mouse press and release -- some extra state to compute selection between mouse press and release
@ -166,13 +165,11 @@ function edit.draw(State)
State.cursor_x = nil State.cursor_x = nil
State.cursor_y = nil State.cursor_y = nil
local y = State.top local y = State.top
local screen_bottom1 = {line=nil, pos=nil}
--? print('== draw') --? print('== draw')
for line_index = State.screen_top1.line,#State.lines do for line_index = State.screen_top1.line,#State.lines do
local line = State.lines[line_index] local line = State.lines[line_index]
--? print('draw:', y, line_index, line) --? print('draw:', y, line_index, line)
if y + State.line_height > App.screen.height then break end if y + State.line_height > App.screen.height then break end
screen_bottom1.line = line_index
if line.mode == 'text' then if line.mode == 'text' then
--? print('text.draw', y, line_index) --? print('text.draw', y, line_index)
local startpos = 1 local startpos = 1
@ -195,7 +192,7 @@ function edit.draw(State)
end, end,
}) })
end end
y, screen_bottom1.pos = Text.draw(State, line_index, y, startpos) y = Text.draw(State, line_index, y, startpos)
--? print('=> y', y) --? print('=> y', y)
elseif line.mode == 'drawing' then elseif line.mode == 'drawing' then
y = y+Drawing_padding_top y = y+Drawing_padding_top
@ -205,7 +202,6 @@ function edit.draw(State)
assert(false, ('unknown line mode %s'):format(line.mode)) assert(false, ('unknown line mode %s'):format(line.mode))
end end
end end
State.screen_bottom1 = screen_bottom1
if State.search_term then if State.search_term then
Text.draw_search_bar(State) Text.draw_search_bar(State)
end end
@ -279,8 +275,7 @@ function edit.mouse_press(State, x,y, mouse_button)
return return
end end
elseif line.mode == 'drawing' then elseif line.mode == 'drawing' then
local line_cache = State.line_cache[line_index] if Drawing.in_drawing(State, line_index, x, y, State.left,State.right) then
if Drawing.in_drawing(line, line_cache, x, y, State.left,State.right) then
State.lines.current_drawing_index = line_index State.lines.current_drawing_index = line_index
State.lines.current_drawing = line State.lines.current_drawing = line
Drawing.before = snapshot(State, line_index) Drawing.before = snapshot(State, line_index)
@ -294,10 +289,7 @@ function edit.mouse_press(State, x,y, mouse_button)
State.old_cursor1 = State.cursor1 State.old_cursor1 = State.cursor1
State.old_selection1 = State.selection1 State.old_selection1 = State.selection1
State.mousepress_shift = App.shift_down() State.mousepress_shift = App.shift_down()
State.selection1 = { State.selection1 = Text.final_text_loc_on_screen(State)
line=State.screen_bottom1.line,
pos=Text.pos_at_end_of_screen_line(State, State.screen_bottom1),
}
end end
function edit.mouse_release(State, x,y, mouse_button) function edit.mouse_release(State, x,y, mouse_button)
@ -335,7 +327,7 @@ function edit.mouse_release(State, x,y, mouse_button)
end end
-- still here? mouse release is below all screen lines -- still here? mouse release is below all screen lines
State.cursor1.line, State.cursor1.pos = State.screen_bottom1.line, Text.pos_at_end_of_screen_line(State, State.screen_bottom1) State.cursor1 = Text.final_text_loc_on_screen(State)
edit.clean_up_mouse_press(State) edit.clean_up_mouse_press(State)
--? print_and_log(('edit.mouse_release: finally selection %s,%s cursor %d,%d'):format(tostring(State.selection1.line), tostring(State.selection1.pos), State.cursor1.line, State.cursor1.pos)) --? print_and_log(('edit.mouse_release: finally selection %s,%s cursor %d,%d'):format(tostring(State.selection1.line), tostring(State.selection1.pos), State.cursor1.line, State.cursor1.pos))
end end
@ -363,7 +355,7 @@ function edit.mouse_wheel_move(State, dx,dy)
Text.up(State) Text.up(State)
end end
elseif dy < 0 then elseif dy < 0 then
State.cursor1 = {line=State.screen_bottom1.line, pos=State.screen_bottom1.pos} State.cursor1 = Text.screen_bottom1(State)
edit.put_cursor_on_next_text_line(State) edit.put_cursor_on_next_text_line(State)
for i=1,math.floor(-dy) do for i=1,math.floor(-dy) do
Text.down(State) Text.down(State)
@ -401,7 +393,6 @@ function edit.keychord_press(State, chord, key)
Text.delete_selection(State, State.left, State.right) Text.delete_selection(State, State.left, State.right)
end end
if State.search_term then if State.search_term then
for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll
if chord == 'escape' then if chord == 'escape' then
State.search_term = nil State.search_term = nil
State.cursor1 = State.search_backup.cursor State.cursor1 = State.search_backup.cursor
@ -506,7 +497,6 @@ function edit.keychord_press(State, chord, key)
record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)}) record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)})
-- dispatch to drawing or text -- dispatch to drawing or text
elseif App.mouse_down(1) or chord:sub(1,2) == 'C-' then elseif App.mouse_down(1) or chord:sub(1,2) == 'C-' then
-- DON'T reset line_cache.starty here
local drawing_index, drawing = Drawing.current_drawing(State) local drawing_index, drawing = Drawing.current_drawing(State)
if drawing_index then if drawing_index then
local before = snapshot(State, drawing_index) local before = snapshot(State, drawing_index)
@ -543,7 +533,6 @@ function edit.keychord_press(State, chord, key)
end end
schedule_save(State) schedule_save(State)
else else
for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll
Text.keychord_press(State, chord) Text.keychord_press(State, chord)
end end
end end
@ -598,6 +587,7 @@ end
function edit.run_after_mouse_click(State, x,y, mouse_button) function edit.run_after_mouse_click(State, x,y, mouse_button)
App.fake_mouse_press(x,y, mouse_button) App.fake_mouse_press(x,y, mouse_button)
edit.mouse_press(State, x,y, mouse_button) edit.mouse_press(State, x,y, mouse_button)
edit.draw(State)
App.fake_mouse_release(x,y, mouse_button) App.fake_mouse_release(x,y, mouse_button)
edit.mouse_release(State, x,y, mouse_button) edit.mouse_release(State, x,y, mouse_button)
App.screen.contents = {} App.screen.contents = {}

View File

@ -1,8 +1,8 @@
function draw_help_without_mouse_pressed(State, drawing_index) function draw_help_without_mouse_pressed(State, drawing_index)
local drawing = State.lines[drawing_index] local drawing = State.lines[drawing_index]
local line_cache = State.line_cache[drawing_index] local starty = Text.starty(State, drawing_index)
App.color(Help_color) App.color(Help_color)
local y = line_cache.starty+10 local y = starty+10
love.graphics.print("Things you can do:", State.left+30,y) love.graphics.print("Things you can do:", State.left+30,y)
y = y + State.line_height y = y + State.line_height
love.graphics.print("* Press the mouse button to start drawing a "..current_shape(State), State.left+30,y) love.graphics.print("* Press the mouse button to start drawing a "..current_shape(State), State.left+30,y)
@ -48,14 +48,14 @@ function draw_help_without_mouse_pressed(State, drawing_index)
love.graphics.print("Press 'esc' now to hide this message", State.left+30,y) love.graphics.print("Press 'esc' now to hide this message", State.left+30,y)
y = y + State.line_height y = y + State.line_height
App.color(Help_background_color) App.color(Help_background_color)
love.graphics.rectangle('fill', State.left,line_cache.starty, State.width, math.max(Drawing.pixels(drawing.h, State.width),y-line_cache.starty)) love.graphics.rectangle('fill', State.left,starty, State.width, math.max(Drawing.pixels(drawing.h, State.width),y-starty))
end end
function draw_help_with_mouse_pressed(State, drawing_index) function draw_help_with_mouse_pressed(State, drawing_index)
local drawing = State.lines[drawing_index] local drawing = State.lines[drawing_index]
local line_cache = State.line_cache[drawing_index] local starty = Text.starty(State, drawing_index)
App.color(Help_color) App.color(Help_color)
local y = line_cache.starty+10 local y = starty+10
love.graphics.print("You're currently drawing a "..current_shape(State, drawing.pending), State.left+30,y) love.graphics.print("You're currently drawing a "..current_shape(State, drawing.pending), State.left+30,y)
y = y + State.line_height y = y + State.line_height
love.graphics.print('Things you can do now:', State.left+30,y) love.graphics.print('Things you can do now:', State.left+30,y)
@ -129,7 +129,7 @@ function draw_help_with_mouse_pressed(State, drawing_index)
y = y + State.line_height y = y + State.line_height
end end
App.color(Help_background_color) App.color(Help_background_color)
love.graphics.rectangle('fill', State.left,line_cache.starty, State.width, math.max(Drawing.pixels(drawing.h, State.width),y-line_cache.starty)) love.graphics.rectangle('fill', State.left,starty, State.width, math.max(Drawing.pixels(drawing.h, State.width),y-starty))
end end
function current_shape(State, shape) function current_shape(State, shape)

View File

@ -62,7 +62,8 @@ function Text.search_next(State)
State.screen_top1.line = State.search_backup.screen_top.line State.screen_top1.line = State.search_backup.screen_top.line
State.screen_top1.pos = State.search_backup.screen_top.pos State.screen_top1.pos = State.search_backup.screen_top.pos
end end
if Text.lt1(State.cursor1, State.screen_top1) or Text.lt1(State.screen_bottom1, State.cursor1) then local screen_bottom1 = Text.screen_bottom1(State)
if Text.lt1(State.cursor1, State.screen_top1) or Text.lt1(screen_bottom1, State.cursor1) then
State.screen_top1.line = State.cursor1.line State.screen_top1.line = State.cursor1.line
local pos = Text.pos_at_start_of_screen_line(State, State.cursor1) local pos = Text.pos_at_start_of_screen_line(State, State.cursor1)
State.screen_top1.pos = pos State.screen_top1.pos = pos
@ -115,7 +116,8 @@ function Text.search_previous(State)
State.screen_top1.line = State.search_backup.screen_top.line State.screen_top1.line = State.search_backup.screen_top.line
State.screen_top1.pos = State.search_backup.screen_top.pos State.screen_top1.pos = State.search_backup.screen_top.pos
end end
if Text.lt1(State.cursor1, State.screen_top1) or Text.lt1(State.screen_bottom1, State.cursor1) then local screen_bottom1 = Text.screen_bottom1(State)
if Text.lt1(State.cursor1, State.screen_top1) or Text.lt1(screen_bottom1, State.cursor1) then
State.screen_top1.line = State.cursor1.line State.screen_top1.line = State.cursor1.line
local pos = Text.pos_at_start_of_screen_line(State, State.cursor1) local pos = Text.pos_at_start_of_screen_line(State, State.cursor1)
State.screen_top1.pos = pos State.screen_top1.pos = pos

View File

@ -69,7 +69,7 @@ end
function Text.mouse_pos(State) function Text.mouse_pos(State)
local x,y = App.mouse_x(), App.mouse_y() local x,y = App.mouse_x(), App.mouse_y()
if y < State.line_cache[State.screen_top1.line].starty then if y < State.top then
return State.screen_top1.line, State.screen_top1.pos return State.screen_top1.line, State.screen_top1.pos
end end
for line_index,line in ipairs(State.lines) do for line_index,line in ipairs(State.lines) do
@ -79,7 +79,8 @@ function Text.mouse_pos(State)
end end
end end
end end
return State.screen_bottom1.line, Text.pos_at_end_of_screen_line(State, State.screen_bottom1) local screen_bottom1 = Text.screen_bottom1(State)
return screen_bottom1.line, Text.pos_at_end_of_screen_line(State, screen_bottom1)
end end
function Text.cut_selection(State) function Text.cut_selection(State)

View File

@ -307,7 +307,6 @@ function source.mouse_press(x,y, mouse_button)
return return
end end
log_browser.mouse_press(Log_browser_state, x,y, mouse_button) log_browser.mouse_press(Log_browser_state, x,y, mouse_button)
for _,line_cache in ipairs(Editor_state.line_cache) do line_cache.starty = nil end -- just in case we scroll
end end
end end

View File

@ -33,7 +33,6 @@ function edit.initialize_state(top, left, right, font, font_height, line_height)
-- string data, -- string data,
-- a drawing is a table with: -- a drawing is a table with:
-- mode = 'drawing' -- mode = 'drawing'
-- a (y) coord in pixels (updated while painting screen),
-- a (h)eight, -- a (h)eight,
-- an array of points, and -- an array of points, and
-- an array of shapes -- an array of shapes
@ -54,7 +53,6 @@ function edit.initialize_state(top, left, right, font, font_height, line_height)
-- rendering wrapped text lines needs some additional short-lived data per line: -- rendering wrapped text lines needs some additional short-lived data per line:
-- startpos, the index of data the line starts rendering from, can only be >1 for topmost line on screen -- startpos, the index of data the line starts rendering from, can only be >1 for topmost line on screen
-- starty, the y coord in pixels the line starts rendering from
-- fragments: snippets of the line guaranteed to not straddle screen lines -- fragments: snippets of the line guaranteed to not straddle screen lines
-- screen_line_starting_pos: optional array of grapheme indices if it wraps over more than one screen line -- screen_line_starting_pos: optional array of grapheme indices if it wraps over more than one screen line
line_cache = {}, line_cache = {},
@ -68,9 +66,10 @@ function edit.initialize_state(top, left, right, font, font_height, line_height)
-- --
-- Make sure these coordinates are never aliased, so that changing one causes -- Make sure these coordinates are never aliased, so that changing one causes
-- action at a distance. -- action at a distance.
--
-- On lines that are drawings, pos will be nil.
screen_top1 = {line=1, pos=1}, -- position of start of screen line at top of screen screen_top1 = {line=1, pos=1}, -- position of start of screen line at top of screen
cursor1 = {line=1, pos=1}, -- position of cursor cursor1 = {line=1, pos=1}, -- position of cursor; must be on a text line
screen_bottom1 = {line=1, pos=1}, -- position of start of screen line at bottom of screen
selection1 = {}, selection1 = {},
-- some extra state to compute selection between mouse press and release -- some extra state to compute selection between mouse press and release
@ -165,13 +164,11 @@ function edit.draw(State, hide_cursor, show_line_numbers)
State.cursor_x = nil State.cursor_x = nil
State.cursor_y = nil State.cursor_y = nil
local y = State.top local y = State.top
local screen_bottom1 = {line=nil, pos=nil}
--? print('== draw') --? print('== draw')
for line_index = State.screen_top1.line,#State.lines do for line_index = State.screen_top1.line,#State.lines do
local line = State.lines[line_index] local line = State.lines[line_index]
--? print('draw:', y, line_index, line) --? print('draw:', y, line_index, line)
if y + State.line_height > App.screen.height then break end if y + State.line_height > App.screen.height then break end
screen_bottom1.line = line_index
if line.mode == 'text' then if line.mode == 'text' then
--? print('text.draw', y, line_index) --? print('text.draw', y, line_index)
local startpos = 1 local startpos = 1
@ -198,7 +195,7 @@ function edit.draw(State, hide_cursor, show_line_numbers)
end, end,
}) })
end end
y, screen_bottom1.pos = Text.draw(State, line_index, y, startpos, hide_cursor, show_line_numbers) y = Text.draw(State, line_index, y, startpos, hide_cursor, show_line_numbers)
--? print('=> y', y) --? print('=> y', y)
elseif line.mode == 'drawing' then elseif line.mode == 'drawing' then
y = y+Drawing_padding_top y = y+Drawing_padding_top
@ -208,7 +205,6 @@ function edit.draw(State, hide_cursor, show_line_numbers)
assert(false, ('unknown line mode %s'):format(line.mode)) assert(false, ('unknown line mode %s'):format(line.mode))
end end
end end
State.screen_bottom1 = screen_bottom1
if State.search_term then if State.search_term then
Text.draw_search_bar(State) Text.draw_search_bar(State)
end end
@ -281,8 +277,7 @@ function edit.mouse_press(State, x,y, mouse_button)
return return
end end
elseif line.mode == 'drawing' then elseif line.mode == 'drawing' then
local line_cache = State.line_cache[line_index] if Drawing.in_drawing(State, line_index, x, y, State.left,State.right) then
if Drawing.in_drawing(line, line_cache, x, y, State.left,State.right) then
State.lines.current_drawing_index = line_index State.lines.current_drawing_index = line_index
State.lines.current_drawing = line State.lines.current_drawing = line
Drawing.before = snapshot(State, line_index) Drawing.before = snapshot(State, line_index)
@ -296,10 +291,7 @@ function edit.mouse_press(State, x,y, mouse_button)
State.old_cursor1 = State.cursor1 State.old_cursor1 = State.cursor1
State.old_selection1 = State.selection1 State.old_selection1 = State.selection1
State.mousepress_shift = App.shift_down() State.mousepress_shift = App.shift_down()
State.selection1 = { State.selection1 = Text.final_text_loc_on_screen(State)
line=State.screen_bottom1.line,
pos=Text.pos_at_end_of_screen_line(State, State.screen_bottom1),
}
end end
function edit.mouse_release(State, x,y, mouse_button) function edit.mouse_release(State, x,y, mouse_button)
@ -337,7 +329,7 @@ function edit.mouse_release(State, x,y, mouse_button)
end end
-- still here? mouse release is below all screen lines -- still here? mouse release is below all screen lines
State.cursor1.line, State.cursor1.pos = State.screen_bottom1.line, Text.pos_at_end_of_screen_line(State, State.screen_bottom1) State.cursor1 = Text.final_text_loc_on_screen(State)
edit.clean_up_mouse_press(State) edit.clean_up_mouse_press(State)
--? print_and_log(('edit.mouse_release: finally selection %s,%s cursor %d,%d'):format(tostring(State.selection1.line), tostring(State.selection1.pos), State.cursor1.line, State.cursor1.pos)) --? print_and_log(('edit.mouse_release: finally selection %s,%s cursor %d,%d'):format(tostring(State.selection1.line), tostring(State.selection1.pos), State.cursor1.line, State.cursor1.pos))
end end
@ -365,7 +357,7 @@ function edit.mouse_wheel_move(State, dx,dy)
Text.up(State) Text.up(State)
end end
elseif dy < 0 then elseif dy < 0 then
State.cursor1 = {line=State.screen_bottom1.line, pos=State.screen_bottom1.pos} State.cursor1 = Text.screen_bottom1(State)
edit.put_cursor_on_next_text_line(State) edit.put_cursor_on_next_text_line(State)
for i=1,math.floor(-dy) do for i=1,math.floor(-dy) do
Text.down(State) Text.down(State)
@ -399,11 +391,10 @@ function edit.keychord_press(State, chord, key)
-- printable character created using shift key => delete selection -- printable character created using shift key => delete selection
-- (we're not creating any ctrl-shift- or alt-shift- combinations using regular/printable keys) -- (we're not creating any ctrl-shift- or alt-shift- combinations using regular/printable keys)
(not App.shift_down() or utf8.len(key) == 1) and (not App.shift_down() or utf8.len(key) == 1) and
chord ~= 'C-a' and chord ~= 'C-c' and chord ~= 'C-x' and chord ~= 'backspace' and chord ~= 'delete' and chord ~= 'C-z' and chord ~= 'C-y' and not App.is_cursor_movement(chord) then chord ~= 'C-a' and chord ~= 'C-c' and chord ~= 'C-x' and chord ~= 'backspace' and chord ~= 'delete' and chord ~= 'C-z' and chord ~= 'C-y' and not App.is_cursor_movement(key) then
Text.delete_selection(State, State.left, State.right) Text.delete_selection(State, State.left, State.right)
end end
if State.search_term then if State.search_term then
for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll
if chord == 'escape' then if chord == 'escape' then
State.search_term = nil State.search_term = nil
State.cursor1 = State.search_backup.cursor State.cursor1 = State.search_backup.cursor
@ -508,7 +499,6 @@ function edit.keychord_press(State, chord, key)
record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)}) record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)})
-- dispatch to drawing or text -- dispatch to drawing or text
elseif App.mouse_down(1) or chord:sub(1,2) == 'C-' then elseif App.mouse_down(1) or chord:sub(1,2) == 'C-' then
-- DON'T reset line_cache.starty here
local drawing_index, drawing = Drawing.current_drawing(State) local drawing_index, drawing = Drawing.current_drawing(State)
if drawing_index then if drawing_index then
local before = snapshot(State, drawing_index) local before = snapshot(State, drawing_index)
@ -545,7 +535,6 @@ function edit.keychord_press(State, chord, key)
end end
schedule_save(State) schedule_save(State)
else else
for _,line_cache in ipairs(State.line_cache) do line_cache.starty = nil end -- just in case we scroll
Text.keychord_press(State, chord) Text.keychord_press(State, chord)
end end
end end
@ -600,6 +589,7 @@ end
function edit.run_after_mouse_click(State, x,y, mouse_button) function edit.run_after_mouse_click(State, x,y, mouse_button)
App.fake_mouse_press(x,y, mouse_button) App.fake_mouse_press(x,y, mouse_button)
edit.mouse_press(State, x,y, mouse_button) edit.mouse_press(State, x,y, mouse_button)
edit.draw(State)
App.fake_mouse_release(x,y, mouse_button) App.fake_mouse_release(x,y, mouse_button)
edit.mouse_release(State, x,y, mouse_button) edit.mouse_release(State, x,y, mouse_button)
App.screen.contents = {} App.screen.contents = {}

View File

@ -69,7 +69,7 @@ end
function Text.mouse_pos(State) function Text.mouse_pos(State)
local x,y = App.mouse_x(), App.mouse_y() local x,y = App.mouse_x(), App.mouse_y()
if y < State.line_cache[State.screen_top1.line].starty then if y < State.top then
return State.screen_top1.line, State.screen_top1.pos return State.screen_top1.line, State.screen_top1.pos
end end
for line_index,line in ipairs(State.lines) do for line_index,line in ipairs(State.lines) do
@ -79,7 +79,8 @@ function Text.mouse_pos(State)
end end
end end
end end
return State.screen_bottom1.line, Text.pos_at_end_of_screen_line(State, State.screen_bottom1) local screen_bottom1 = Text.screen_bottom1(State)
return screen_bottom1.line, Text.pos_at_end_of_screen_line(State, screen_bottom1)
end end
function Text.cut_selection(State) function Text.cut_selection(State)

View File

@ -2,14 +2,12 @@
Text = {} Text = {}
-- draw a line starting from startpos to screen at y between State.left and State.right -- draw a line starting from startpos to screen at y between State.left and State.right
-- return y for the next line, and position of start of final screen line drawn -- return y for the next line
function Text.draw(State, line_index, y, startpos, hide_cursor, show_line_numbers) function Text.draw(State, line_index, y, startpos, hide_cursor, show_line_numbers)
local line = State.lines[line_index] local line = State.lines[line_index]
local line_cache = State.line_cache[line_index] local line_cache = State.line_cache[line_index]
line_cache.starty = y
line_cache.startpos = startpos line_cache.startpos = startpos
-- wrap long lines -- wrap long lines
local final_screen_line_starting_pos = startpos -- track value to return
Text.populate_screen_line_starting_pos(State, line_index) Text.populate_screen_line_starting_pos(State, line_index)
Text.populate_link_offsets(State, line_index) Text.populate_link_offsets(State, line_index)
if show_line_numbers then if show_line_numbers then
@ -24,7 +22,6 @@ function Text.draw(State, line_index, y, startpos, hide_cursor, show_line_number
-- render nothing -- render nothing
--? print('skipping', screen_line) --? print('skipping', screen_line)
else else
final_screen_line_starting_pos = pos
local screen_line = Text.screen_line(line, line_cache, i) local screen_line = Text.screen_line(line, line_cache, i)
--? print('text.draw:', screen_line, 'at', line_index,pos, 'after', x,y) --? print('text.draw:', screen_line, 'at', line_index,pos, 'after', x,y)
local frag_len = utf8.len(screen_line) local frag_len = utf8.len(screen_line)
@ -84,7 +81,7 @@ function Text.draw(State, line_index, y, startpos, hide_cursor, show_line_number
end end
end end
end end
return y, final_screen_line_starting_pos return y
end end
function Text.screen_line(line, line_cache, i) function Text.screen_line(line, line_cache, i)
@ -208,7 +205,7 @@ function Text.text_input(State, t)
end end
end end
local before = snapshot(State, State.cursor1.line) local before = snapshot(State, State.cursor1.line)
--? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) --? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
Text.insert_at_cursor(State, t) Text.insert_at_cursor(State, t)
if State.cursor_y > App.screen.height - State.line_height then if State.cursor_y > App.screen.height - State.line_height then
Text.populate_screen_line_starting_pos(State, State.cursor1.line) Text.populate_screen_line_starting_pos(State, State.cursor1.line)
@ -241,12 +238,12 @@ function Text.keychord_press(State, chord)
record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)}) record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)})
elseif chord == 'tab' then elseif chord == 'tab' then
local before = snapshot(State, State.cursor1.line) local before = snapshot(State, State.cursor1.line)
--? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) --? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
Text.insert_at_cursor(State, '\t') Text.insert_at_cursor(State, '\t')
if State.cursor_y > App.screen.height - State.line_height then if State.cursor_y > App.screen.height - State.line_height then
Text.populate_screen_line_starting_pos(State, State.cursor1.line) Text.populate_screen_line_starting_pos(State, State.cursor1.line)
Text.snap_cursor_to_bottom_of_screen(State, State.left, State.right) Text.snap_cursor_to_bottom_of_screen(State, State.left, State.right)
--? print('=>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) --? print('=>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
end end
schedule_save(State) schedule_save(State)
record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)}) record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})
@ -427,37 +424,79 @@ function Text.insert_return(State)
end end
function Text.pageup(State) function Text.pageup(State)
--? print('pageup') State.screen_top1 = Text.previous_screen_top1(State)
-- duplicate some logic from love.draw
local top2 = Text.to2(State, State.screen_top1)
--? print(App.screen.height)
local y = App.screen.height - State.line_height
while y >= State.top do
--? print(y, top2.line, top2.screen_line, top2.screen_pos)
if State.screen_top1.line == 1 and State.screen_top1.pos == 1 then break end
if State.lines[State.screen_top1.line].mode == 'text' then
y = y - State.line_height
elseif State.lines[State.screen_top1.line].mode == 'drawing' then
y = y - Drawing_padding_height - Drawing.pixels(State.lines[State.screen_top1.line].h, State.width)
end
top2 = Text.previous_screen_line(State, top2)
end
State.screen_top1 = Text.to1(State, top2)
State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos} State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}
Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State) 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) Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks
--? print('pageup end') end
-- return the top y coordinate of a given line_index,
-- or nil if no part of it is on screen
function Text.starty(State, line_index)
-- duplicate some logic from love.draw
-- does not modify State (except to populate line_cache)
if line_index < State.screen_top1.line then return end
local loc2 = Text.to2(State, State.screen_top1)
local y = State.top
while true do
if State.lines[loc2.line].mode == 'drawing' then
y = y + Drawing_padding_top
end
if loc2.line == line_index then return y end
if State.lines[loc2.line].mode == 'text' then
y = y + State.line_height
elseif State.lines[loc2.line].mode == 'drawing' then
y = y + Drawing.pixels(State.lines[loc2.line].h, State.width) + Drawing_padding_bottom
end
if y + State.line_height > App.screen.height then break end
local next_loc2 = Text.next_screen_line(State, loc2)
if Text.eq2(next_loc2, loc2) then break end -- end of file
loc2 = next_loc2
end
end
function Text.previous_screen_top1(State)
-- duplicate some logic from love.draw
-- does not modify State (except to populate line_cache)
local loc2 = Text.to2(State, State.screen_top1)
local y = App.screen.height - State.line_height
while y >= State.top do
if loc2.line == 1 and loc2.screen_line == 1 and loc2.screen_pos == 1 then break end
if State.lines[loc2.line].mode == 'text' then
y = y - State.line_height
elseif State.lines[loc2.line].mode == 'drawing' then
y = y - Drawing_padding_height - Drawing.pixels(State.lines[loc2.line].h, State.width)
end
loc2 = Text.previous_screen_line(State, loc2)
end
return Text.to1(State, loc2)
end end
function Text.pagedown(State) function Text.pagedown(State)
--? print('pagedown') State.screen_top1 = Text.screen_bottom1(State)
State.screen_top1 = {line=State.screen_bottom1.line, pos=State.screen_bottom1.pos}
--? print('setting top to', State.screen_top1.line, State.screen_top1.pos)
State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos} State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}
Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State) 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 Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks
--? print('pagedown end') end
-- return the location of the start of the bottom-most line on screen
function Text.screen_bottom1(State)
-- duplicate some logic from love.draw
-- does not modify State (except to populate line_cache)
local loc2 = Text.to2(State, State.screen_top1)
local y = State.top
while true do
if State.lines[loc2.line].mode == 'text' then
y = y + State.line_height
elseif State.lines[loc2.line].mode == 'drawing' then
y = y + Drawing_padding_height + Drawing.pixels(State.lines[loc2.line].h, State.width)
end
if y + State.line_height > App.screen.height then break end
local next_loc2 = Text.next_screen_line(State, loc2)
if Text.eq2(next_loc2, loc2) then break end
loc2 = next_loc2
end
return Text.to1(State, loc2)
end end
function Text.up(State) function Text.up(State)
@ -505,7 +544,7 @@ end
function Text.down(State) function Text.down(State)
assert(State.lines[State.cursor1.line].mode == 'text', 'line is not text') assert(State.lines[State.cursor1.line].mode == 'text', 'line is not text')
--? print('down', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) --? print('down', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos)
assert(State.cursor1.pos, 'cursor has no pos') assert(State.cursor1.pos, 'cursor has no pos')
if Text.cursor_at_final_screen_line(State) then if Text.cursor_at_final_screen_line(State) then
-- line is done, skip to next text line -- line is done, skip to next text line
@ -522,7 +561,9 @@ function Text.down(State)
break break
end end
end end
if State.cursor1.line > State.screen_bottom1.line then local screen_bottom1 = Text.screen_bottom1(State)
--? print('down 2', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, screen_bottom1.line, screen_bottom1.pos)
if State.cursor1.line > screen_bottom1.line then
--? print('screen top before:', State.screen_top1.line, State.screen_top1.pos) --? print('screen top before:', State.screen_top1.line, State.screen_top1.pos)
--? print('scroll up preserving cursor') --? print('scroll up preserving cursor')
Text.snap_cursor_to_bottom_of_screen(State) Text.snap_cursor_to_bottom_of_screen(State)
@ -530,7 +571,8 @@ function Text.down(State)
end end
else else
-- move down one screen line in current line -- move down one screen line in current line
local scroll_down = Text.le1(State.screen_bottom1, State.cursor1) local screen_bottom1 = Text.screen_bottom1(State)
local scroll_down = Text.le1(screen_bottom1, State.cursor1)
--? print('cursor is NOT at final screen line of its line') --? 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) local screen_line_starting_pos, screen_line_index = Text.pos_at_start_of_screen_line(State, State.cursor1)
Text.populate_screen_line_starting_pos(State, State.cursor1.line) Text.populate_screen_line_starting_pos(State, State.cursor1.line)
@ -546,7 +588,7 @@ function Text.down(State)
--? print('screen top after:', State.screen_top1.line, State.screen_top1.pos) --? print('screen top after:', State.screen_top1.line, State.screen_top1.pos)
end end
end end
--? print('=>', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) --? print('=>', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos)
end end
function Text.start_of_line(State) function Text.start_of_line(State)
@ -683,6 +725,7 @@ function Text.pos_at_start_of_screen_line(State, loc1)
end end
function Text.pos_at_end_of_screen_line(State, loc1) function Text.pos_at_end_of_screen_line(State, loc1)
assert(State.lines[loc1.line].mode == 'text')
Text.populate_screen_line_starting_pos(State, loc1.line) Text.populate_screen_line_starting_pos(State, loc1.line)
local line_cache = State.line_cache[loc1.line] local line_cache = State.line_cache[loc1.line]
local most_recent_final_pos = utf8.len(State.lines[loc1.line].data)+1 local most_recent_final_pos = utf8.len(State.lines[loc1.line].data)+1
@ -696,6 +739,25 @@ function Text.pos_at_end_of_screen_line(State, loc1)
assert(false, ('invalid pos %d'):format(loc1.pos)) assert(false, ('invalid pos %d'):format(loc1.pos))
end end
function Text.final_text_loc_on_screen(State)
local screen_bottom1 = Text.screen_bottom1(State)
if State.lines[screen_bottom1.line].mode == 'text' then
return {
line=screen_bottom1.line,
pos=Text.pos_at_end_of_screen_line(State, screen_bottom1),
}
end
local loc2 = Text.to2(State, screen_bottom1)
while true do
if State.lines[loc2.line].mode == 'text' then break end
assert(loc2.line > 1 or loc2.screen_line > 1 and loc2.screen_pos > 1) -- elsewhere we're making sure there's always at least one text line on screen
loc2 = Text.previous_screen_line(State, loc2)
end
local result = Text.to1(State, loc2)
result.pos = Text.pos_at_end_of_screen_line(State, result)
return result
end
function Text.cursor_at_final_screen_line(State) function Text.cursor_at_final_screen_line(State)
Text.populate_screen_line_starting_pos(State, State.cursor1.line) Text.populate_screen_line_starting_pos(State, State.cursor1.line)
local screen_lines = State.line_cache[State.cursor1.line].screen_line_starting_pos local screen_lines = State.line_cache[State.cursor1.line].screen_line_starting_pos
@ -736,7 +798,7 @@ function Text.snap_cursor_to_bottom_of_screen(State)
--? print('to2: =>', top2.line, top2.screen_line, top2.screen_pos) --? print('to2: =>', top2.line, top2.screen_line, top2.screen_pos)
-- slide to start of screen line -- slide to start of screen line
top2.screen_pos = 1 -- start of screen line top2.screen_pos = 1 -- start of screen line
--? print('snap', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) --? print('snap', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
--? print('cursor pos '..tostring(State.cursor1.pos)..' is on the #'..tostring(top2.screen_line)..' screen line down') --? 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 local y = App.screen.height - State.line_height
-- duplicate some logic from love.draw -- duplicate some logic from love.draw
@ -766,26 +828,28 @@ function Text.snap_cursor_to_bottom_of_screen(State)
--? print('top2 finally:', top2.line, top2.screen_line, top2.screen_pos) --? print('top2 finally:', top2.line, top2.screen_line, top2.screen_pos)
State.screen_top1 = Text.to1(State, top2) State.screen_top1 = Text.to1(State, top2)
--? print('top1 finally:', State.screen_top1.line, State.screen_top1.pos) --? print('top1 finally:', State.screen_top1.line, State.screen_top1.pos)
--? print('snap =>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) --? print('snap =>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks
end end
function Text.in_line(State, line_index, x,y) function Text.in_line(State, line_index, x,y)
local line = State.lines[line_index] local line = State.lines[line_index]
local line_cache = State.line_cache[line_index] local line_cache = State.line_cache[line_index]
if line_cache.starty == nil then return false end -- outside current page local starty = Text.starty(State, line_index)
if y < line_cache.starty then return false end if starty == nil then return false end -- outside current page
if y < starty then return false end
Text.populate_screen_line_starting_pos(State, line_index) Text.populate_screen_line_starting_pos(State, line_index)
return y < line_cache.starty + State.line_height*(#line_cache.screen_line_starting_pos - Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos) + 1) return y < starty + State.line_height*(#line_cache.screen_line_starting_pos - Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos) + 1)
end end
-- convert mx,my in pixels to schema-1 coordinates -- convert mx,my in pixels to schema-1 coordinates
function Text.to_pos_on_line(State, line_index, mx, my) function Text.to_pos_on_line(State, line_index, mx, my)
local line = State.lines[line_index] local line = State.lines[line_index]
local line_cache = State.line_cache[line_index] local line_cache = State.line_cache[line_index]
assert(my >= line_cache.starty, 'failed to map y pixel to line') local starty = Text.starty(State, line_index)
assert(my >= starty, 'failed to map y pixel to line')
-- duplicate some logic from Text.draw -- duplicate some logic from Text.draw
local y = line_cache.starty local y = starty
local start_screen_line_index = Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos) 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 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_pos = line_cache.screen_line_starting_pos[screen_line_index]
@ -971,6 +1035,10 @@ function Text.le1(a, b)
return a.pos <= b.pos return a.pos <= b.pos
end end
function Text.eq2(a, b)
return a.line == b.line and a.screen_line == b.screen_line and a.screen_pos == b.screen_pos
end
function Text.offset(s, pos1) function Text.offset(s, pos1)
if pos1 == 1 then return 1 end if pos1 == 1 then return 1 end
local result = utf8.offset(s, pos1) local result = utf8.offset(s, pos1)
@ -994,6 +1062,22 @@ function Text.previous_screen_line(State, loc2)
end end
end end
function Text.next_screen_line(State, loc2)
if State.lines[loc2.line].mode == 'drawing' then
return {line=loc2.line+1, screen_line=1, screen_pos=1}
end
Text.populate_screen_line_starting_pos(State, loc2.line)
if loc2.screen_line >= #State.line_cache[loc2.line].screen_line_starting_pos then
if loc2.line < #State.lines then
return {line=loc2.line+1, screen_line=1, screen_pos=1}
else
return loc2
end
else
return {line=loc2.line, screen_line=loc2.screen_line+1, screen_pos=1}
end
end
-- resize helper -- resize helper
function Text.tweak_screen_top_and_cursor(State) function Text.tweak_screen_top_and_cursor(State)
if State.screen_top1.pos == 1 then return end if State.screen_top1.pos == 1 then return end
@ -1017,16 +1101,12 @@ function Text.tweak_screen_top_and_cursor(State)
end end
end end
-- make sure cursor is on screen -- make sure cursor is on screen
local screen_bottom1 = Text.screen_bottom1(State)
if Text.lt1(State.cursor1, State.screen_top1) then if Text.lt1(State.cursor1, State.screen_top1) then
State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos} State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}
elseif State.cursor1.line >= State.screen_bottom1.line then elseif State.cursor1.line >= screen_bottom1.line then
--? print('too low')
if Text.cursor_out_of_screen(State) then if Text.cursor_out_of_screen(State) then
--? print('tweak') State.cursor1 = Text.final_text_loc_on_screen(State)
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 end
end end
@ -1035,11 +1115,6 @@ end
function Text.cursor_out_of_screen(State) function Text.cursor_out_of_screen(State)
edit.draw(State) edit.draw(State)
return State.cursor_y == nil return State.cursor_y == nil
-- this approach is cheaper and almost works, except on the final screen
-- where file ends above bottom of screen
--? local botpos = Text.pos_at_start_of_screen_line(State, State.cursor1)
--? local botline1 = {line=State.cursor1.line, pos=botpos}
--? return Text.lt1(State.screen_bottom1, botline1)
end end
function Text.redraw_all(State) function Text.redraw_all(State)

View File

@ -16,7 +16,7 @@ function test_initial_state()
end end
function test_click_to_create_drawing() function test_click_to_create_drawing()
App.screen.init{width=120, height=60} App.screen.init{width=800, height=600}
Editor_state = edit.initialize_test_state() Editor_state = edit.initialize_test_state()
Editor_state.lines = load_array{} Editor_state.lines = load_array{}
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
@ -75,7 +75,6 @@ function test_press_ctrl()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.run_after_keychord(Editor_state, 'C-m', 'm') edit.run_after_keychord(Editor_state, 'C-m', 'm')
end end
@ -232,7 +231,7 @@ function test_skip_multiple_spaces_to_next_word()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=4} -- at the start of second word Editor_state.cursor1 = {line=1, pos=4} -- at the start of second word
edit.draw(Editor_state) edit.draw(Editor_state)
edit.run_after_keychord(Editor_state, 'M-right', 'right') edit.run_after_keychord(Editor_state, 'M-right', 'right')
check_eq(Editor_state.cursor1.pos, 9, 'check') check_eq(Editor_state.cursor1.pos, 9, 'check')
end end
@ -243,7 +242,7 @@ function test_move_past_end_of_word_on_next_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=8} Editor_state.cursor1 = {line=1, pos=8}
edit.draw(Editor_state) edit.draw(Editor_state)
edit.run_after_keychord(Editor_state, 'M-right', 'right') edit.run_after_keychord(Editor_state, 'M-right', 'right')
check_eq(Editor_state.cursor1.line, 2, 'line') check_eq(Editor_state.cursor1.line, 2, 'line')
check_eq(Editor_state.cursor1.pos, 4, 'pos') check_eq(Editor_state.cursor1.pos, 4, 'pos')
end end
@ -255,9 +254,8 @@ function test_click_moves_cursor()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {} Editor_state.selection1 = {}
edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache edit.draw(Editor_state) -- populate line_cache.startpos for each line
edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1) edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
check_eq(Editor_state.cursor1.line, 1, 'cursor:line') check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos') check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
@ -274,7 +272,6 @@ function test_click_to_left_of_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=3} Editor_state.cursor1 = {line=1, pos=3}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {} Editor_state.selection1 = {}
-- click to the left of the line -- click to the left of the line
edit.draw(Editor_state) edit.draw(Editor_state)
@ -294,7 +291,6 @@ function test_click_takes_margins_into_account()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1} Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {} Editor_state.selection1 = {}
-- click on the other line -- click on the other line
edit.draw(Editor_state) edit.draw(Editor_state)
@ -313,7 +309,6 @@ function test_click_on_empty_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1} Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {} Editor_state.selection1 = {}
-- click on the empty line -- click on the empty line
edit.draw(Editor_state) edit.draw(Editor_state)
@ -332,7 +327,6 @@ function test_click_below_all_lines()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {} Editor_state.selection1 = {}
-- click below first line -- click below first line
edit.draw(Editor_state) edit.draw(Editor_state)
@ -350,7 +344,6 @@ function test_draw_text()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc', 'screen:1') App.screen.check(y, 'abc', 'screen:1')
@ -367,7 +360,6 @@ function test_draw_wrapping_text()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc', 'screen:1') App.screen.check(y, 'abc', 'screen:1')
@ -384,7 +376,6 @@ function test_draw_word_wrapping_text()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc ', 'screen:1') App.screen.check(y, 'abc ', 'screen:1')
@ -402,7 +393,6 @@ function test_click_on_wrapping_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=20} Editor_state.cursor1 = {line=1, pos=20}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- click on the other line -- click on the other line
edit.draw(Editor_state) edit.draw(Editor_state)
edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1) edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
@ -421,7 +411,6 @@ function test_click_on_wrapping_line_takes_margins_into_account()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=20} Editor_state.cursor1 = {line=1, pos=20}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- click on the other line -- click on the other line
edit.draw(Editor_state) edit.draw(Editor_state)
edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1) edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
@ -439,7 +428,6 @@ function test_draw_text_wrapping_within_word()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abcd ', 'screen:1') App.screen.check(y, 'abcd ', 'screen:1')
@ -457,7 +445,6 @@ function test_draw_wrapping_text_containing_non_ascii()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'mad', 'screen:1') App.screen.check(y, 'mad', 'screen:1')
@ -476,7 +463,6 @@ function test_click_past_end_of_screen_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'madam ', 'baseline/screen:1') App.screen.check(y, 'madam ', 'baseline/screen:1')
@ -499,7 +485,6 @@ function test_click_on_wrapping_line_rendered_from_partway_at_top_of_screen()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=8} Editor_state.cursor1 = {line=1, pos=8}
Editor_state.screen_top1 = {line=1, pos=7} Editor_state.screen_top1 = {line=1, pos=7}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, "I'm ad", 'baseline/screen:2') App.screen.check(y, "I'm ad", 'baseline/screen:2')
@ -520,7 +505,6 @@ function test_click_past_end_of_wrapping_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'madam ', 'baseline/screen:1') App.screen.check(y, 'madam ', 'baseline/screen:1')
@ -544,7 +528,6 @@ function test_click_past_end_of_wrapping_line_containing_non_ascii()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'madam ', 'baseline/screen:1') App.screen.check(y, 'madam ', 'baseline/screen:1')
@ -569,7 +552,6 @@ function test_click_past_end_of_word_wrapping_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'the quick brown fox ', 'baseline/screen:1') App.screen.check(y, 'the quick brown fox ', 'baseline/screen:1')
@ -588,11 +570,10 @@ function test_select_text()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
-- select a letter -- select a letter
App.fake_key_press('lshift') App.fake_key_press('lshift')
edit.run_after_keychord(Editor_state, 'S-right', 'right') edit.run_after_keychord(Editor_state, 'S-right', 'right')
App.fake_key_release('lshift') App.fake_key_release('lshift')
edit.key_release(Editor_state, 'lshift') edit.key_release(Editor_state, 'lshift')
-- selection persists even after shift is released -- selection persists even after shift is released
@ -611,10 +592,9 @@ function test_cursor_movement_without_shift_resets_selection()
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.selection1 = {line=1, pos=2} Editor_state.selection1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
-- press an arrow key without shift -- press an arrow key without shift
edit.run_after_keychord(Editor_state, 'right', 'right') edit.run_after_keychord(Editor_state, 'right', 'right')
-- no change to data, selection is reset -- no change to data, selection is reset
check_nil(Editor_state.selection1.line, 'check') check_nil(Editor_state.selection1.line, 'check')
check_eq(Editor_state.lines[1].data, 'abc', 'data') check_eq(Editor_state.lines[1].data, 'abc', 'data')
@ -629,7 +609,6 @@ function test_edit_deletes_selection()
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.selection1 = {line=1, pos=2} Editor_state.selection1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
-- press a key -- press a key
edit.run_after_text_input(Editor_state, 'x') edit.run_after_text_input(Editor_state, 'x')
@ -646,7 +625,6 @@ function test_edit_with_shift_key_deletes_selection()
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.selection1 = {line=1, pos=2} Editor_state.selection1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
-- mimic precise keypresses for a capital letter -- mimic precise keypresses for a capital letter
App.fake_key_press('lshift') App.fake_key_press('lshift')
@ -668,7 +646,6 @@ function test_copy_does_not_reset_selection()
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.selection1 = {line=1, pos=2} Editor_state.selection1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
-- copy selection -- copy selection
edit.run_after_keychord(Editor_state, 'C-c', 'c') edit.run_after_keychord(Editor_state, 'C-c', 'c')
@ -686,7 +663,6 @@ function test_cut()
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.selection1 = {line=1, pos=2} Editor_state.selection1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
-- press a key -- press a key
edit.run_after_keychord(Editor_state, 'C-x', 'x') edit.run_after_keychord(Editor_state, 'C-x', 'x')
@ -704,7 +680,6 @@ function test_paste_replaces_selection()
Editor_state.cursor1 = {line=2, pos=1} Editor_state.cursor1 = {line=2, pos=1}
Editor_state.selection1 = {line=1, pos=1} Editor_state.selection1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
-- set clipboard -- set clipboard
App.clipboard = 'xyz' App.clipboard = 'xyz'
@ -723,7 +698,6 @@ function test_deleting_selection_may_scroll()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=2} Editor_state.cursor1 = {line=3, pos=2}
Editor_state.screen_top1 = {line=2, pos=1} Editor_state.screen_top1 = {line=2, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'def', 'baseline/screen:1') App.screen.check(y, 'def', 'baseline/screen:1')
@ -747,7 +721,6 @@ function test_edit_wrapping_text()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=4} Editor_state.cursor1 = {line=2, pos=4}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
edit.run_after_text_input(Editor_state, 'g') edit.run_after_text_input(Editor_state, 'g')
local y = Editor_state.top local y = Editor_state.top
@ -766,7 +739,6 @@ function test_insert_newline()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=2} Editor_state.cursor1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1') App.screen.check(y, 'abc', 'baseline/screen:1')
@ -795,7 +767,6 @@ function test_insert_newline_at_start_of_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- hitting the enter key splits the line -- hitting the enter key splits the line
edit.run_after_keychord(Editor_state, 'return', 'return') edit.run_after_keychord(Editor_state, 'return', 'return')
check_eq(Editor_state.cursor1.line, 2, 'cursor:line') check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
@ -812,7 +783,6 @@ function test_insert_from_clipboard()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=2} Editor_state.cursor1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1') App.screen.check(y, 'abc', 'baseline/screen:1')
@ -841,9 +811,8 @@ function test_select_text_using_mouse()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {} Editor_state.selection1 = {}
edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache edit.draw(Editor_state) -- populate line_cache.startpos for each line
-- press and hold on first location -- press and hold on first location
edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1) edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
-- drag and release somewhere else -- drag and release somewhere else
@ -861,9 +830,8 @@ function test_select_text_using_mouse_starting_above_text()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {} Editor_state.selection1 = {}
edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache edit.draw(Editor_state) -- populate line_cache.startpos for each line
-- press mouse above first line of text -- press mouse above first line of text
edit.run_after_mouse_press(Editor_state, Editor_state.left+8,5, 1) edit.run_after_mouse_press(Editor_state, Editor_state.left+8,5, 1)
check(Editor_state.selection1.line ~= nil, 'selection:line-not-nil') check(Editor_state.selection1.line ~= nil, 'selection:line-not-nil')
@ -879,7 +847,6 @@ function test_select_text_using_mouse_starting_above_text_wrapping_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=5} Editor_state.cursor1 = {line=2, pos=5}
Editor_state.screen_top1 = {line=2, pos=3} Editor_state.screen_top1 = {line=2, pos=3}
Editor_state.screen_bottom1 = {}
-- press mouse above first line of text -- press mouse above first line of text
edit.draw(Editor_state) edit.draw(Editor_state)
edit.run_after_mouse_press(Editor_state, Editor_state.left+8,5, 1) edit.run_after_mouse_press(Editor_state, Editor_state.left+8,5, 1)
@ -902,7 +869,6 @@ function test_select_text_using_mouse_starting_below_text()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'ab', 'baseline:screen:1') App.screen.check(y, 'ab', 'baseline:screen:1')
@ -923,9 +889,8 @@ function test_select_text_using_mouse_and_shift()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {} Editor_state.selection1 = {}
edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache edit.draw(Editor_state) -- populate line_cache.startpos for each line
-- click on first location -- click on first location
edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1) edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1) edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
@ -948,9 +913,8 @@ function test_select_text_repeatedly_using_mouse_and_shift()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {} Editor_state.selection1 = {}
edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache edit.draw(Editor_state) -- populate line_cache.startpos for each line
-- click on first location -- click on first location
edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1) edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1) edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
@ -978,7 +942,6 @@ function test_select_all_text()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
-- select all -- select all
App.fake_key_press('lctrl') App.fake_key_press('lctrl')
@ -1000,7 +963,6 @@ function test_cut_without_selection()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=2} Editor_state.cursor1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {} Editor_state.selection1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
-- try to cut without selecting text -- try to cut without selecting text
@ -1016,7 +978,6 @@ function test_pagedown()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- initially the first two lines are displayed -- initially the first two lines are displayed
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
@ -1046,7 +1007,6 @@ function test_pagedown_skips_drawings()
check_eq(Editor_state.lines[2].mode, 'drawing', 'baseline/lines') check_eq(Editor_state.lines[2].mode, 'drawing', 'baseline/lines')
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
local drawing_height = Drawing_padding_height + drawing_width/2 -- default local drawing_height = Drawing_padding_height + drawing_width/2 -- default
-- initially the screen displays the first line and the drawing -- initially the screen displays the first line and the drawing
-- 15px margin + 15px line1 + 10px margin + 25px drawing + 10px margin = 75px < screen height 80px -- 15px margin + 15px line1 + 10px margin + 25px drawing + 10px margin = 75px < screen height 80px
@ -1070,7 +1030,6 @@ function test_pagedown_can_start_from_middle_of_long_wrapping_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=2} Editor_state.cursor1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc ', 'baseline/screen:1') App.screen.check(y, 'abc ', 'baseline/screen:1')
@ -1105,7 +1064,6 @@ function test_pagedown_never_moves_up()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=9} Editor_state.cursor1 = {line=1, pos=9}
Editor_state.screen_top1 = {line=1, pos=9} Editor_state.screen_top1 = {line=1, pos=9}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
-- pagedown makes no change -- pagedown makes no change
edit.run_after_keychord(Editor_state, 'pagedown', 'pagedown') edit.run_after_keychord(Editor_state, 'pagedown', 'pagedown')
@ -1120,7 +1078,6 @@ function test_down_arrow_moves_cursor()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- initially the first three lines are displayed -- initially the first three lines are displayed
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
@ -1153,7 +1110,6 @@ function test_down_arrow_skips_drawing()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1') App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1175,7 +1131,6 @@ function test_down_arrow_scrolls_down_by_one_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=1} Editor_state.cursor1 = {line=3, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1') App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1203,7 +1158,6 @@ function test_down_arrow_scrolls_down_by_one_screen_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=1} Editor_state.cursor1 = {line=3, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1') App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1232,7 +1186,6 @@ function test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=1} Editor_state.cursor1 = {line=3, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1') App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1260,7 +1213,6 @@ function test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=1} Editor_state.cursor1 = {line=3, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1') App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1294,7 +1246,6 @@ function test_up_arrow_moves_cursor()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=1} Editor_state.cursor1 = {line=3, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1') App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1326,7 +1277,6 @@ function test_up_arrow_skips_drawing()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=1} Editor_state.cursor1 = {line=3, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1') App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1348,7 +1298,6 @@ function test_up_arrow_scrolls_up_by_one_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1} Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=2, pos=1} Editor_state.screen_top1 = {line=2, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'def', 'baseline/screen:1') App.screen.check(y, 'def', 'baseline/screen:1')
@ -1376,7 +1325,6 @@ function test_up_arrow_scrolls_up_by_one_line_skipping_drawing()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=1} Editor_state.cursor1 = {line=3, pos=1}
Editor_state.screen_top1 = {line=3, pos=1} Editor_state.screen_top1 = {line=3, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'def', 'baseline/screen:1') App.screen.check(y, 'def', 'baseline/screen:1')
@ -1398,7 +1346,6 @@ function test_up_arrow_scrolls_up_by_one_screen_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=6} Editor_state.cursor1 = {line=3, pos=6}
Editor_state.screen_top1 = {line=3, pos=5} Editor_state.screen_top1 = {line=3, pos=5}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'jkl', 'baseline/screen:1') App.screen.check(y, 'jkl', 'baseline/screen:1')
@ -1426,7 +1373,6 @@ function test_up_arrow_scrolls_up_to_final_screen_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1} Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=2, pos=1} Editor_state.screen_top1 = {line=2, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'ghi', 'baseline/screen:1') App.screen.check(y, 'ghi', 'baseline/screen:1')
@ -1456,7 +1402,6 @@ function test_up_arrow_scrolls_up_to_empty_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1} Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=2, pos=1} Editor_state.screen_top1 = {line=2, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1') App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1483,7 +1428,6 @@ function test_pageup()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1} Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=2, pos=1} Editor_state.screen_top1 = {line=2, pos=1}
Editor_state.screen_bottom1 = {}
-- initially the last two lines are displayed -- initially the last two lines are displayed
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
@ -1508,7 +1452,6 @@ function test_pageup_scrolls_up_by_screen_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1} Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=2, pos=1} Editor_state.screen_top1 = {line=2, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'ghi', 'baseline/screen:1') App.screen.check(y, 'ghi', 'baseline/screen:1')
@ -1537,7 +1480,6 @@ function test_pageup_scrolls_up_from_middle_screen_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=5} Editor_state.cursor1 = {line=2, pos=5}
Editor_state.screen_top1 = {line=2, pos=5} Editor_state.screen_top1 = {line=2, pos=5}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'jkl', 'baseline/screen:2') App.screen.check(y, 'jkl', 'baseline/screen:2')
@ -1564,7 +1506,6 @@ function test_enter_on_bottom_line_scrolls_down()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=2} Editor_state.cursor1 = {line=3, pos=2}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1') App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1593,7 +1534,6 @@ function test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=4, pos=2} Editor_state.cursor1 = {line=4, pos=2}
Editor_state.screen_top1 = {line=4, pos=1} Editor_state.screen_top1 = {line=4, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'jkl', 'baseline/screen:1') App.screen.check(y, 'jkl', 'baseline/screen:1')
@ -1616,7 +1556,6 @@ function test_inserting_text_on_final_line_avoids_scrolling_down_when_not_at_bot
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1} Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=2, pos=1} Editor_state.screen_top1 = {line=2, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
-- after hitting the inserting_text key the screen does not scroll down -- after hitting the inserting_text key the screen does not scroll down
edit.run_after_text_input(Editor_state, 'a') edit.run_after_text_input(Editor_state, 'a')
@ -1635,7 +1574,6 @@ function test_typing_on_bottom_line_scrolls_down()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=4} Editor_state.cursor1 = {line=3, pos=4}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1') App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1665,7 +1603,6 @@ function test_left_arrow_scrolls_up_in_wrapped_line()
Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'} Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.screen_top1 = {line=3, pos=5} Editor_state.screen_top1 = {line=3, pos=5}
Editor_state.screen_bottom1 = {}
-- cursor is at top of screen -- cursor is at top of screen
Editor_state.cursor1 = {line=3, pos=5} Editor_state.cursor1 = {line=3, pos=5}
edit.draw(Editor_state) edit.draw(Editor_state)
@ -1694,7 +1631,6 @@ function test_right_arrow_scrolls_down_in_wrapped_line()
Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'} Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- cursor is at bottom right of screen -- cursor is at bottom right of screen
Editor_state.cursor1 = {line=3, pos=5} Editor_state.cursor1 = {line=3, pos=5}
edit.draw(Editor_state) edit.draw(Editor_state)
@ -1705,7 +1641,7 @@ function test_right_arrow_scrolls_down_in_wrapped_line()
y = y + Editor_state.line_height y = y + Editor_state.line_height
App.screen.check(y, 'ghi ', 'baseline/screen:3') -- line wrapping includes trailing whitespace App.screen.check(y, 'ghi ', 'baseline/screen:3') -- line wrapping includes trailing whitespace
-- after hitting the right arrow the screen scrolls down by one line -- after hitting the right arrow the screen scrolls down by one line
edit.run_after_keychord(Editor_state, 'right', 'right') edit.run_after_keychord(Editor_state, 'right', 'right')
check_eq(Editor_state.screen_top1.line, 2, 'screen_top') check_eq(Editor_state.screen_top1.line, 2, 'screen_top')
check_eq(Editor_state.cursor1.line, 3, 'cursor:line') check_eq(Editor_state.cursor1.line, 3, 'cursor:line')
check_eq(Editor_state.cursor1.pos, 6, 'cursor:pos') check_eq(Editor_state.cursor1.pos, 6, 'cursor:pos')
@ -1724,7 +1660,6 @@ function test_home_scrolls_up_in_wrapped_line()
Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'} Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.screen_top1 = {line=3, pos=5} Editor_state.screen_top1 = {line=3, pos=5}
Editor_state.screen_bottom1 = {}
-- cursor is at top of screen -- cursor is at top of screen
Editor_state.cursor1 = {line=3, pos=5} Editor_state.cursor1 = {line=3, pos=5}
edit.draw(Editor_state) edit.draw(Editor_state)
@ -1753,7 +1688,6 @@ function test_end_scrolls_down_in_wrapped_line()
Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'} Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- cursor is at bottom right of screen -- cursor is at bottom right of screen
Editor_state.cursor1 = {line=3, pos=5} Editor_state.cursor1 = {line=3, pos=5}
edit.draw(Editor_state) edit.draw(Editor_state)
@ -1784,7 +1718,6 @@ function test_position_cursor_on_recently_edited_wrapping_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=25} Editor_state.cursor1 = {line=1, pos=25}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc def ghi ', 'baseline1/screen:1') App.screen.check(y, 'abc def ghi ', 'baseline1/screen:1')
@ -1818,7 +1751,6 @@ function test_backspace_can_scroll_up()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1} Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=2, pos=1} Editor_state.screen_top1 = {line=2, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'def', 'baseline/screen:1') App.screen.check(y, 'def', 'baseline/screen:1')
@ -1846,7 +1778,6 @@ function test_backspace_can_scroll_up_screen_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=5} Editor_state.cursor1 = {line=3, pos=5}
Editor_state.screen_top1 = {line=3, pos=5} Editor_state.screen_top1 = {line=3, pos=5}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'jkl', 'baseline/screen:1') App.screen.check(y, 'jkl', 'baseline/screen:1')
@ -1981,7 +1912,6 @@ function test_undo_insert_text()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=4} Editor_state.cursor1 = {line=2, pos=4}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- insert a character -- insert a character
edit.draw(Editor_state) edit.draw(Editor_state)
edit.run_after_text_input(Editor_state, 'g') edit.run_after_text_input(Editor_state, 'g')
@ -2016,7 +1946,6 @@ function test_undo_delete_text()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=5} Editor_state.cursor1 = {line=2, pos=5}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- delete a character -- delete a character
edit.run_after_keychord(Editor_state, 'backspace', 'backspace') edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
check_eq(Editor_state.cursor1.line, 2, 'baseline/cursor:line') check_eq(Editor_state.cursor1.line, 2, 'baseline/cursor:line')
@ -2055,7 +1984,6 @@ function test_undo_restores_selection()
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.selection1 = {line=1, pos=2} Editor_state.selection1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
-- delete selected text -- delete selected text
edit.run_after_text_input(Editor_state, 'x') edit.run_after_text_input(Editor_state, 'x')
@ -2076,7 +2004,6 @@ function test_search()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
-- search for a string -- search for a string
edit.run_after_keychord(Editor_state, 'C-f', 'f') edit.run_after_keychord(Editor_state, 'C-f', 'f')
@ -2103,7 +2030,6 @@ function test_search_upwards()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1} Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
-- search for a string -- search for a string
edit.run_after_keychord(Editor_state, 'C-f', 'f') edit.run_after_keychord(Editor_state, 'C-f', 'f')
@ -2121,7 +2047,6 @@ function test_search_wrap()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1} Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
-- search for a string -- search for a string
edit.run_after_keychord(Editor_state, 'C-f', 'f') edit.run_after_keychord(Editor_state, 'C-f', 'f')
@ -2139,7 +2064,6 @@ function test_search_wrap_upwards()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
-- search upwards for a string -- search upwards for a string
edit.run_after_keychord(Editor_state, 'C-f', 'f') edit.run_after_keychord(Editor_state, 'C-f', 'f')

195
text.lua
View File

@ -2,15 +2,13 @@
Text = {} Text = {}
-- draw a line starting from startpos to screen at y between State.left and State.right -- draw a line starting from startpos to screen at y between State.left and State.right
-- return y for the next line, and position of start of final screen line drawn -- return y for the next line
function Text.draw(State, line_index, y, startpos) function Text.draw(State, line_index, y, startpos)
--? print('text.draw', line_index, y) --? print('text.draw', line_index, y)
local line = State.lines[line_index] local line = State.lines[line_index]
local line_cache = State.line_cache[line_index] local line_cache = State.line_cache[line_index]
line_cache.starty = y
line_cache.startpos = startpos line_cache.startpos = startpos
-- wrap long lines -- wrap long lines
local final_screen_line_starting_pos = startpos -- track value to return
Text.populate_screen_line_starting_pos(State, line_index) Text.populate_screen_line_starting_pos(State, line_index)
assert(#line_cache.screen_line_starting_pos >= 1, 'line cache missing screen line info') assert(#line_cache.screen_line_starting_pos >= 1, 'line cache missing screen line info')
for i=1,#line_cache.screen_line_starting_pos do for i=1,#line_cache.screen_line_starting_pos do
@ -18,7 +16,6 @@ function Text.draw(State, line_index, y, startpos)
if pos < startpos then if pos < startpos then
-- render nothing -- render nothing
else else
final_screen_line_starting_pos = pos
local screen_line = Text.screen_line(line, line_cache, i) local screen_line = Text.screen_line(line, line_cache, i)
--? print('text.draw:', screen_line, 'at', line_index,pos, 'after', x,y) --? print('text.draw:', screen_line, 'at', line_index,pos, 'after', x,y)
local frag_len = utf8.len(screen_line) local frag_len = utf8.len(screen_line)
@ -59,7 +56,7 @@ function Text.draw(State, line_index, y, startpos)
end end
end end
end end
return y, final_screen_line_starting_pos return y
end end
function Text.screen_line(line, line_cache, i) function Text.screen_line(line, line_cache, i)
@ -134,7 +131,7 @@ function Text.text_input(State, t)
end end
end end
local before = snapshot(State, State.cursor1.line) local before = snapshot(State, State.cursor1.line)
--? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) --? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
Text.insert_at_cursor(State, t) Text.insert_at_cursor(State, t)
if State.cursor_y > App.screen.height - State.line_height then if State.cursor_y > App.screen.height - State.line_height then
Text.populate_screen_line_starting_pos(State, State.cursor1.line) Text.populate_screen_line_starting_pos(State, State.cursor1.line)
@ -167,12 +164,12 @@ function Text.keychord_press(State, chord)
record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)}) record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)})
elseif chord == 'tab' then elseif chord == 'tab' then
local before = snapshot(State, State.cursor1.line) local before = snapshot(State, State.cursor1.line)
--? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) --? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
Text.insert_at_cursor(State, '\t') Text.insert_at_cursor(State, '\t')
if State.cursor_y > App.screen.height - State.line_height then if State.cursor_y > App.screen.height - State.line_height then
Text.populate_screen_line_starting_pos(State, State.cursor1.line) Text.populate_screen_line_starting_pos(State, State.cursor1.line)
Text.snap_cursor_to_bottom_of_screen(State, State.left, State.right) Text.snap_cursor_to_bottom_of_screen(State, State.left, State.right)
--? print('=>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) --? print('=>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
end end
schedule_save(State) schedule_save(State)
record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)}) record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})
@ -353,49 +350,79 @@ function Text.insert_return(State)
end end
function Text.pageup(State) function Text.pageup(State)
--? print('pageup') State.screen_top1 = Text.previous_screen_top1(State)
-- duplicate some logic from love.draw
local top2 = Text.to2(State, State.screen_top1)
--? print(App.screen.height)
local y = App.screen.height - State.line_height
while y >= State.top do
--? print(y, top2.line, top2.screen_line, top2.screen_pos)
if State.screen_top1.line == 1 and State.screen_top1.pos == 1 then break end
if State.lines[State.screen_top1.line].mode == 'text' then
y = y - State.line_height
elseif State.lines[State.screen_top1.line].mode == 'drawing' then
y = y - Drawing_padding_height - Drawing.pixels(State.lines[State.screen_top1.line].h, State.width)
end
top2 = Text.previous_screen_line(State, top2)
end
State.screen_top1 = Text.to1(State, top2)
State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos} State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}
Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State) 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) Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks
--? print('pageup end') end
-- return the top y coordinate of a given line_index,
-- or nil if no part of it is on screen
function Text.starty(State, line_index)
-- duplicate some logic from love.draw
-- does not modify State (except to populate line_cache)
if line_index < State.screen_top1.line then return end
local loc2 = Text.to2(State, State.screen_top1)
local y = State.top
while true do
if State.lines[loc2.line].mode == 'drawing' then
y = y + Drawing_padding_top
end
if loc2.line == line_index then return y end
if State.lines[loc2.line].mode == 'text' then
y = y + State.line_height
elseif State.lines[loc2.line].mode == 'drawing' then
y = y + Drawing.pixels(State.lines[loc2.line].h, State.width) + Drawing_padding_bottom
end
if y + State.line_height > App.screen.height then break end
local next_loc2 = Text.next_screen_line(State, loc2)
if Text.eq2(next_loc2, loc2) then break end -- end of file
loc2 = next_loc2
end
end
function Text.previous_screen_top1(State)
-- duplicate some logic from love.draw
-- does not modify State (except to populate line_cache)
local loc2 = Text.to2(State, State.screen_top1)
local y = App.screen.height - State.line_height
while y >= State.top do
if loc2.line == 1 and loc2.screen_line == 1 and loc2.screen_pos == 1 then break end
if State.lines[loc2.line].mode == 'text' then
y = y - State.line_height
elseif State.lines[loc2.line].mode == 'drawing' then
y = y - Drawing_padding_height - Drawing.pixels(State.lines[loc2.line].h, State.width)
end
loc2 = Text.previous_screen_line(State, loc2)
end
return Text.to1(State, loc2)
end end
function Text.pagedown(State) function Text.pagedown(State)
--? print('pagedown') State.screen_top1 = Text.screen_bottom1(State)
-- If a line/paragraph gets to a page boundary, I often want to scroll
-- before I get to the bottom.
-- However, only do this if it makes forward progress.
local bot2 = Text.to2(State, State.screen_bottom1)
if bot2.screen_line > 1 then
bot2.screen_line = math.max(bot2.screen_line-10, 1)
end
local new_top1 = Text.to1(State, bot2)
if Text.lt1(State.screen_top1, new_top1) then
State.screen_top1 = new_top1
else
State.screen_top1 = {line=State.screen_bottom1.line, pos=State.screen_bottom1.pos}
end
--? print('setting top to', State.screen_top1.line, State.screen_top1.pos)
State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos} State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}
Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State) 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 Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks
--? print('pagedown end') end
-- return the location of the start of the bottom-most line on screen
function Text.screen_bottom1(State)
-- duplicate some logic from love.draw
-- does not modify State (except to populate line_cache)
local loc2 = Text.to2(State, State.screen_top1)
local y = State.top
while true do
if State.lines[loc2.line].mode == 'text' then
y = y + State.line_height
elseif State.lines[loc2.line].mode == 'drawing' then
y = y + Drawing_padding_height + Drawing.pixels(State.lines[loc2.line].h, State.width)
end
if y + State.line_height > App.screen.height then break end
local next_loc2 = Text.next_screen_line(State, loc2)
if Text.eq2(next_loc2, loc2) then break end
loc2 = next_loc2
end
return Text.to1(State, loc2)
end end
function Text.up(State) function Text.up(State)
@ -443,7 +470,7 @@ end
function Text.down(State) function Text.down(State)
assert(State.lines[State.cursor1.line].mode == 'text', 'line is not text') assert(State.lines[State.cursor1.line].mode == 'text', 'line is not text')
--? print('down', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) --? print('down', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos)
assert(State.cursor1.pos, 'cursor has no pos') assert(State.cursor1.pos, 'cursor has no pos')
if Text.cursor_at_final_screen_line(State) then if Text.cursor_at_final_screen_line(State) then
-- line is done, skip to next text line -- line is done, skip to next text line
@ -460,7 +487,9 @@ function Text.down(State)
break break
end end
end end
if State.cursor1.line > State.screen_bottom1.line then local screen_bottom1 = Text.screen_bottom1(State)
--? print('down 2', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, screen_bottom1.line, screen_bottom1.pos)
if State.cursor1.line > screen_bottom1.line then
--? print('screen top before:', State.screen_top1.line, State.screen_top1.pos) --? print('screen top before:', State.screen_top1.line, State.screen_top1.pos)
--? print('scroll up preserving cursor') --? print('scroll up preserving cursor')
Text.snap_cursor_to_bottom_of_screen(State) Text.snap_cursor_to_bottom_of_screen(State)
@ -468,7 +497,8 @@ function Text.down(State)
end end
else else
-- move down one screen line in current line -- move down one screen line in current line
local scroll_down = Text.le1(State.screen_bottom1, State.cursor1) local screen_bottom1 = Text.screen_bottom1(State)
local scroll_down = Text.le1(screen_bottom1, State.cursor1)
--? print('cursor is NOT at final screen line of its line') --? 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) local screen_line_starting_pos, screen_line_index = Text.pos_at_start_of_screen_line(State, State.cursor1)
Text.populate_screen_line_starting_pos(State, State.cursor1.line) Text.populate_screen_line_starting_pos(State, State.cursor1.line)
@ -484,7 +514,7 @@ function Text.down(State)
--? print('screen top after:', State.screen_top1.line, State.screen_top1.pos) --? print('screen top after:', State.screen_top1.line, State.screen_top1.pos)
end end
end end
--? print('=>', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) --? print('=>', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos)
end end
function Text.start_of_line(State) function Text.start_of_line(State)
@ -621,6 +651,7 @@ function Text.pos_at_start_of_screen_line(State, loc1)
end end
function Text.pos_at_end_of_screen_line(State, loc1) function Text.pos_at_end_of_screen_line(State, loc1)
assert(State.lines[loc1.line].mode == 'text')
Text.populate_screen_line_starting_pos(State, loc1.line) Text.populate_screen_line_starting_pos(State, loc1.line)
local line_cache = State.line_cache[loc1.line] local line_cache = State.line_cache[loc1.line]
local most_recent_final_pos = utf8.len(State.lines[loc1.line].data)+1 local most_recent_final_pos = utf8.len(State.lines[loc1.line].data)+1
@ -634,6 +665,25 @@ function Text.pos_at_end_of_screen_line(State, loc1)
assert(false, ('invalid pos %d'):format(loc1.pos)) assert(false, ('invalid pos %d'):format(loc1.pos))
end end
function Text.final_text_loc_on_screen(State)
local screen_bottom1 = Text.screen_bottom1(State)
if State.lines[screen_bottom1.line].mode == 'text' then
return {
line=screen_bottom1.line,
pos=Text.pos_at_end_of_screen_line(State, screen_bottom1),
}
end
local loc2 = Text.to2(State, screen_bottom1)
while true do
if State.lines[loc2.line].mode == 'text' then break end
assert(loc2.line > 1 or loc2.screen_line > 1 and loc2.screen_pos > 1) -- elsewhere we're making sure there's always at least one text line on screen
loc2 = Text.previous_screen_line(State, loc2)
end
local result = Text.to1(State, loc2)
result.pos = Text.pos_at_end_of_screen_line(State, result)
return result
end
function Text.cursor_at_final_screen_line(State) function Text.cursor_at_final_screen_line(State)
Text.populate_screen_line_starting_pos(State, State.cursor1.line) Text.populate_screen_line_starting_pos(State, State.cursor1.line)
local screen_lines = State.line_cache[State.cursor1.line].screen_line_starting_pos local screen_lines = State.line_cache[State.cursor1.line].screen_line_starting_pos
@ -674,7 +724,7 @@ function Text.snap_cursor_to_bottom_of_screen(State)
--? print('to2: =>', top2.line, top2.screen_line, top2.screen_pos) --? print('to2: =>', top2.line, top2.screen_line, top2.screen_pos)
-- slide to start of screen line -- slide to start of screen line
top2.screen_pos = 1 -- start of screen line top2.screen_pos = 1 -- start of screen line
--? print('snap', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) --? print('snap', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
--? print('cursor pos '..tostring(State.cursor1.pos)..' is on the #'..tostring(top2.screen_line)..' screen line down') --? 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 local y = App.screen.height - State.line_height
-- duplicate some logic from love.draw -- duplicate some logic from love.draw
@ -704,26 +754,28 @@ function Text.snap_cursor_to_bottom_of_screen(State)
--? print('top2 finally:', top2.line, top2.screen_line, top2.screen_pos) --? print('top2 finally:', top2.line, top2.screen_line, top2.screen_pos)
State.screen_top1 = Text.to1(State, top2) State.screen_top1 = Text.to1(State, top2)
--? print('top1 finally:', State.screen_top1.line, State.screen_top1.pos) --? print('top1 finally:', State.screen_top1.line, State.screen_top1.pos)
--? print('snap =>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) --? print('snap =>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos)
Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks
end end
function Text.in_line(State, line_index, x,y) function Text.in_line(State, line_index, x,y)
local line = State.lines[line_index] local line = State.lines[line_index]
local line_cache = State.line_cache[line_index] local line_cache = State.line_cache[line_index]
if line_cache.starty == nil then return false end -- outside current page local starty = Text.starty(State, line_index)
if y < line_cache.starty then return false end if starty == nil then return false end -- outside current page
if y < starty then return false end
Text.populate_screen_line_starting_pos(State, line_index) Text.populate_screen_line_starting_pos(State, line_index)
return y < line_cache.starty + State.line_height*(#line_cache.screen_line_starting_pos - Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos) + 1) return y < starty + State.line_height*(#line_cache.screen_line_starting_pos - Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos) + 1)
end end
-- convert mx,my in pixels to schema-1 coordinates -- convert mx,my in pixels to schema-1 coordinates
function Text.to_pos_on_line(State, line_index, mx, my) function Text.to_pos_on_line(State, line_index, mx, my)
local line = State.lines[line_index] local line = State.lines[line_index]
local line_cache = State.line_cache[line_index] local line_cache = State.line_cache[line_index]
assert(my >= line_cache.starty, 'failed to map y pixel to line') local starty = Text.starty(State, line_index)
assert(my >= starty, 'failed to map y pixel to line')
-- duplicate some logic from Text.draw -- duplicate some logic from Text.draw
local y = line_cache.starty local y = starty
local start_screen_line_index = Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos) 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 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_pos = line_cache.screen_line_starting_pos[screen_line_index]
@ -909,6 +961,10 @@ function Text.le1(a, b)
return a.pos <= b.pos return a.pos <= b.pos
end end
function Text.eq2(a, b)
return a.line == b.line and a.screen_line == b.screen_line and a.screen_pos == b.screen_pos
end
function Text.offset(s, pos1) function Text.offset(s, pos1)
if pos1 == 1 then return 1 end if pos1 == 1 then return 1 end
local result = utf8.offset(s, pos1) local result = utf8.offset(s, pos1)
@ -932,6 +988,22 @@ function Text.previous_screen_line(State, loc2)
end end
end end
function Text.next_screen_line(State, loc2)
if State.lines[loc2.line].mode == 'drawing' then
return {line=loc2.line+1, screen_line=1, screen_pos=1}
end
Text.populate_screen_line_starting_pos(State, loc2.line)
if loc2.screen_line >= #State.line_cache[loc2.line].screen_line_starting_pos then
if loc2.line < #State.lines then
return {line=loc2.line+1, screen_line=1, screen_pos=1}
else
return loc2
end
else
return {line=loc2.line, screen_line=loc2.screen_line+1, screen_pos=1}
end
end
-- resize helper -- resize helper
function Text.tweak_screen_top_and_cursor(State) function Text.tweak_screen_top_and_cursor(State)
if State.screen_top1.pos == 1 then return end if State.screen_top1.pos == 1 then return end
@ -955,16 +1027,12 @@ function Text.tweak_screen_top_and_cursor(State)
end end
end end
-- make sure cursor is on screen -- make sure cursor is on screen
local screen_bottom1 = Text.screen_bottom1(State)
if Text.lt1(State.cursor1, State.screen_top1) then if Text.lt1(State.cursor1, State.screen_top1) then
State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos} State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos}
elseif State.cursor1.line >= State.screen_bottom1.line then elseif State.cursor1.line >= screen_bottom1.line then
--? print('too low')
if Text.cursor_out_of_screen(State) then if Text.cursor_out_of_screen(State) then
--? print('tweak') State.cursor1 = Text.final_text_loc_on_screen(State)
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 end
end end
@ -973,11 +1041,6 @@ end
function Text.cursor_out_of_screen(State) function Text.cursor_out_of_screen(State)
edit.draw(State) edit.draw(State)
return State.cursor_y == nil return State.cursor_y == nil
-- this approach is cheaper and almost works, except on the final screen
-- where file ends above bottom of screen
--? local botpos = Text.pos_at_start_of_screen_line(State, State.cursor1)
--? local botline1 = {line=State.cursor1.line, pos=botpos}
--? return Text.lt1(State.screen_bottom1, botline1)
end end
function Text.redraw_all(State) function Text.redraw_all(State)

View File

@ -16,7 +16,7 @@ function test_initial_state()
end end
function test_click_to_create_drawing() function test_click_to_create_drawing()
App.screen.init{width=120, height=60} App.screen.init{width=800, height=600}
Editor_state = edit.initialize_test_state() Editor_state = edit.initialize_test_state()
Editor_state.lines = load_array{} Editor_state.lines = load_array{}
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
@ -75,7 +75,6 @@ function test_press_ctrl()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.run_after_keychord(Editor_state, 'C-m', 'm') edit.run_after_keychord(Editor_state, 'C-m', 'm')
end end
@ -255,9 +254,8 @@ function test_click_moves_cursor()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {} Editor_state.selection1 = {}
edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache edit.draw(Editor_state) -- populate line_cache.startpos for each line
edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1) edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
check_eq(Editor_state.cursor1.line, 1, 'cursor:line') check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos') check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
@ -274,7 +272,6 @@ function test_click_to_left_of_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=3} Editor_state.cursor1 = {line=1, pos=3}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {} Editor_state.selection1 = {}
-- click to the left of the line -- click to the left of the line
edit.draw(Editor_state) edit.draw(Editor_state)
@ -294,7 +291,6 @@ function test_click_takes_margins_into_account()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1} Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {} Editor_state.selection1 = {}
-- click on the other line -- click on the other line
edit.draw(Editor_state) edit.draw(Editor_state)
@ -313,7 +309,6 @@ function test_click_on_empty_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1} Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {} Editor_state.selection1 = {}
-- click on the empty line -- click on the empty line
edit.draw(Editor_state) edit.draw(Editor_state)
@ -332,7 +327,6 @@ function test_click_below_all_lines()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {} Editor_state.selection1 = {}
-- click below first line -- click below first line
edit.draw(Editor_state) edit.draw(Editor_state)
@ -350,7 +344,6 @@ function test_draw_text()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc', 'screen:1') App.screen.check(y, 'abc', 'screen:1')
@ -367,7 +360,6 @@ function test_draw_wrapping_text()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc', 'screen:1') App.screen.check(y, 'abc', 'screen:1')
@ -384,7 +376,6 @@ function test_draw_word_wrapping_text()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc ', 'screen:1') App.screen.check(y, 'abc ', 'screen:1')
@ -402,7 +393,6 @@ function test_click_on_wrapping_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=20} Editor_state.cursor1 = {line=1, pos=20}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- click on the other line -- click on the other line
edit.draw(Editor_state) edit.draw(Editor_state)
edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1) edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
@ -421,7 +411,6 @@ function test_click_on_wrapping_line_takes_margins_into_account()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=20} Editor_state.cursor1 = {line=1, pos=20}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- click on the other line -- click on the other line
edit.draw(Editor_state) edit.draw(Editor_state)
edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1) edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
@ -439,7 +428,6 @@ function test_draw_text_wrapping_within_word()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abcd ', 'screen:1') App.screen.check(y, 'abcd ', 'screen:1')
@ -457,7 +445,6 @@ function test_draw_wrapping_text_containing_non_ascii()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'mad', 'screen:1') App.screen.check(y, 'mad', 'screen:1')
@ -476,7 +463,6 @@ function test_click_past_end_of_screen_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'madam ', 'baseline/screen:1') App.screen.check(y, 'madam ', 'baseline/screen:1')
@ -499,7 +485,6 @@ function test_click_on_wrapping_line_rendered_from_partway_at_top_of_screen()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=8} Editor_state.cursor1 = {line=1, pos=8}
Editor_state.screen_top1 = {line=1, pos=7} Editor_state.screen_top1 = {line=1, pos=7}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, "I'm ad", 'baseline/screen:2') App.screen.check(y, "I'm ad", 'baseline/screen:2')
@ -520,7 +505,6 @@ function test_click_past_end_of_wrapping_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'madam ', 'baseline/screen:1') App.screen.check(y, 'madam ', 'baseline/screen:1')
@ -544,7 +528,6 @@ function test_click_past_end_of_wrapping_line_containing_non_ascii()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'madam ', 'baseline/screen:1') App.screen.check(y, 'madam ', 'baseline/screen:1')
@ -569,7 +552,6 @@ function test_click_past_end_of_word_wrapping_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'the quick brown fox ', 'baseline/screen:1') App.screen.check(y, 'the quick brown fox ', 'baseline/screen:1')
@ -588,7 +570,6 @@ function test_select_text()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
-- select a letter -- select a letter
App.fake_key_press('lshift') App.fake_key_press('lshift')
@ -611,7 +592,6 @@ function test_cursor_movement_without_shift_resets_selection()
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.selection1 = {line=1, pos=2} Editor_state.selection1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
-- press an arrow key without shift -- press an arrow key without shift
edit.run_after_keychord(Editor_state, 'right', 'right') edit.run_after_keychord(Editor_state, 'right', 'right')
@ -629,7 +609,6 @@ function test_edit_deletes_selection()
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.selection1 = {line=1, pos=2} Editor_state.selection1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
-- press a key -- press a key
edit.run_after_text_input(Editor_state, 'x') edit.run_after_text_input(Editor_state, 'x')
@ -646,7 +625,6 @@ function test_edit_with_shift_key_deletes_selection()
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.selection1 = {line=1, pos=2} Editor_state.selection1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
-- mimic precise keypresses for a capital letter -- mimic precise keypresses for a capital letter
App.fake_key_press('lshift') App.fake_key_press('lshift')
@ -668,7 +646,6 @@ function test_copy_does_not_reset_selection()
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.selection1 = {line=1, pos=2} Editor_state.selection1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
-- copy selection -- copy selection
edit.run_after_keychord(Editor_state, 'C-c', 'c') edit.run_after_keychord(Editor_state, 'C-c', 'c')
@ -686,7 +663,6 @@ function test_cut()
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.selection1 = {line=1, pos=2} Editor_state.selection1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
-- press a key -- press a key
edit.run_after_keychord(Editor_state, 'C-x', 'x') edit.run_after_keychord(Editor_state, 'C-x', 'x')
@ -704,7 +680,6 @@ function test_paste_replaces_selection()
Editor_state.cursor1 = {line=2, pos=1} Editor_state.cursor1 = {line=2, pos=1}
Editor_state.selection1 = {line=1, pos=1} Editor_state.selection1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
-- set clipboard -- set clipboard
App.clipboard = 'xyz' App.clipboard = 'xyz'
@ -723,7 +698,6 @@ function test_deleting_selection_may_scroll()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=2} Editor_state.cursor1 = {line=3, pos=2}
Editor_state.screen_top1 = {line=2, pos=1} Editor_state.screen_top1 = {line=2, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'def', 'baseline/screen:1') App.screen.check(y, 'def', 'baseline/screen:1')
@ -747,7 +721,6 @@ function test_edit_wrapping_text()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=4} Editor_state.cursor1 = {line=2, pos=4}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
edit.run_after_text_input(Editor_state, 'g') edit.run_after_text_input(Editor_state, 'g')
local y = Editor_state.top local y = Editor_state.top
@ -766,7 +739,6 @@ function test_insert_newline()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=2} Editor_state.cursor1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1') App.screen.check(y, 'abc', 'baseline/screen:1')
@ -795,7 +767,6 @@ function test_insert_newline_at_start_of_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- hitting the enter key splits the line -- hitting the enter key splits the line
edit.run_after_keychord(Editor_state, 'return', 'return') edit.run_after_keychord(Editor_state, 'return', 'return')
check_eq(Editor_state.cursor1.line, 2, 'cursor:line') check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
@ -812,7 +783,6 @@ function test_insert_from_clipboard()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=2} Editor_state.cursor1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1') App.screen.check(y, 'abc', 'baseline/screen:1')
@ -841,9 +811,8 @@ function test_select_text_using_mouse()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {} Editor_state.selection1 = {}
edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache edit.draw(Editor_state) -- populate line_cache.startpos for each line
-- press and hold on first location -- press and hold on first location
edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1) edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
-- drag and release somewhere else -- drag and release somewhere else
@ -861,9 +830,8 @@ function test_select_text_using_mouse_starting_above_text()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {} Editor_state.selection1 = {}
edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache edit.draw(Editor_state) -- populate line_cache.startpos for each line
-- press mouse above first line of text -- press mouse above first line of text
edit.run_after_mouse_press(Editor_state, Editor_state.left+8,5, 1) edit.run_after_mouse_press(Editor_state, Editor_state.left+8,5, 1)
check(Editor_state.selection1.line ~= nil, 'selection:line-not-nil') check(Editor_state.selection1.line ~= nil, 'selection:line-not-nil')
@ -879,7 +847,6 @@ function test_select_text_using_mouse_starting_above_text_wrapping_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=5} Editor_state.cursor1 = {line=2, pos=5}
Editor_state.screen_top1 = {line=2, pos=3} Editor_state.screen_top1 = {line=2, pos=3}
Editor_state.screen_bottom1 = {}
-- press mouse above first line of text -- press mouse above first line of text
edit.draw(Editor_state) edit.draw(Editor_state)
edit.run_after_mouse_press(Editor_state, Editor_state.left+8,5, 1) edit.run_after_mouse_press(Editor_state, Editor_state.left+8,5, 1)
@ -902,7 +869,6 @@ function test_select_text_using_mouse_starting_below_text()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'ab', 'baseline:screen:1') App.screen.check(y, 'ab', 'baseline:screen:1')
@ -923,9 +889,8 @@ function test_select_text_using_mouse_and_shift()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {} Editor_state.selection1 = {}
edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache edit.draw(Editor_state) -- populate line_cache.startpos for each line
-- click on first location -- click on first location
edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1) edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1) edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
@ -948,9 +913,8 @@ function test_select_text_repeatedly_using_mouse_and_shift()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {} Editor_state.selection1 = {}
edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cache edit.draw(Editor_state) -- populate line_cache.startpos for each line
-- click on first location -- click on first location
edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1) edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1) edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
@ -978,7 +942,6 @@ function test_select_all_text()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
-- select all -- select all
App.fake_key_press('lctrl') App.fake_key_press('lctrl')
@ -1000,7 +963,6 @@ function test_cut_without_selection()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=2} Editor_state.cursor1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
Editor_state.selection1 = {} Editor_state.selection1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
-- try to cut without selecting text -- try to cut without selecting text
@ -1016,7 +978,6 @@ function test_pagedown()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- initially the first two lines are displayed -- initially the first two lines are displayed
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
@ -1046,7 +1007,6 @@ function test_pagedown_skips_drawings()
check_eq(Editor_state.lines[2].mode, 'drawing', 'baseline/lines') check_eq(Editor_state.lines[2].mode, 'drawing', 'baseline/lines')
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
local drawing_height = Drawing_padding_height + drawing_width/2 -- default local drawing_height = Drawing_padding_height + drawing_width/2 -- default
-- initially the screen displays the first line and the drawing -- initially the screen displays the first line and the drawing
-- 15px margin + 15px line1 + 10px margin + 25px drawing + 10px margin = 75px < screen height 80px -- 15px margin + 15px line1 + 10px margin + 25px drawing + 10px margin = 75px < screen height 80px
@ -1062,36 +1022,6 @@ function test_pagedown_skips_drawings()
App.screen.check(y, 'def', 'screen:1') App.screen.check(y, 'def', 'screen:1')
end end
function test_pagedown_often_shows_start_of_wrapping_line()
-- draw a few lines ending in part of a wrapping line
App.screen.init{width=50, height=60}
Editor_state = edit.initialize_test_state()
Editor_state.lines = load_array{'abc', 'def ghi jkl', 'mno'}
Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state)
local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1')
y = y + Editor_state.line_height
App.screen.check(y, 'def ', 'baseline/screen:2')
y = y + Editor_state.line_height
App.screen.check(y, 'ghi ', 'baseline/screen:3')
-- after pagedown we start drawing from the bottom _line_ (multiple screen lines)
edit.run_after_keychord(Editor_state, 'pagedown', 'pagedown')
check_eq(Editor_state.screen_top1.line, 2, 'screen_top:line')
check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos')
check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
y = Editor_state.top
App.screen.check(y, 'def ', 'screen:1')
y = y + Editor_state.line_height
App.screen.check(y, 'ghi ', 'screen:2')
y = y + Editor_state.line_height
App.screen.check(y, 'jkl', 'screen:3')
end
function test_pagedown_can_start_from_middle_of_long_wrapping_line() function test_pagedown_can_start_from_middle_of_long_wrapping_line()
-- draw a few lines starting from a very long wrapping line -- draw a few lines starting from a very long wrapping line
App.screen.init{width=Editor_state.left+30, height=60} App.screen.init{width=Editor_state.left+30, height=60}
@ -1100,7 +1030,6 @@ function test_pagedown_can_start_from_middle_of_long_wrapping_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=2} Editor_state.cursor1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc ', 'baseline/screen:1') App.screen.check(y, 'abc ', 'baseline/screen:1')
@ -1135,7 +1064,6 @@ function test_pagedown_never_moves_up()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=9} Editor_state.cursor1 = {line=1, pos=9}
Editor_state.screen_top1 = {line=1, pos=9} Editor_state.screen_top1 = {line=1, pos=9}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
-- pagedown makes no change -- pagedown makes no change
edit.run_after_keychord(Editor_state, 'pagedown', 'pagedown') edit.run_after_keychord(Editor_state, 'pagedown', 'pagedown')
@ -1150,7 +1078,6 @@ function test_down_arrow_moves_cursor()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- initially the first three lines are displayed -- initially the first three lines are displayed
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
@ -1183,7 +1110,6 @@ function test_down_arrow_skips_drawing()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1') App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1205,7 +1131,6 @@ function test_down_arrow_scrolls_down_by_one_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=1} Editor_state.cursor1 = {line=3, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1') App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1233,7 +1158,6 @@ function test_down_arrow_scrolls_down_by_one_screen_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=1} Editor_state.cursor1 = {line=3, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1') App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1262,7 +1186,6 @@ function test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=1} Editor_state.cursor1 = {line=3, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1') App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1290,7 +1213,6 @@ function test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=1} Editor_state.cursor1 = {line=3, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1') App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1324,7 +1246,6 @@ function test_up_arrow_moves_cursor()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=1} Editor_state.cursor1 = {line=3, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1') App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1356,7 +1277,6 @@ function test_up_arrow_skips_drawing()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=1} Editor_state.cursor1 = {line=3, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1') App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1378,7 +1298,6 @@ function test_up_arrow_scrolls_up_by_one_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1} Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=2, pos=1} Editor_state.screen_top1 = {line=2, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'def', 'baseline/screen:1') App.screen.check(y, 'def', 'baseline/screen:1')
@ -1406,7 +1325,6 @@ function test_up_arrow_scrolls_up_by_one_line_skipping_drawing()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=1} Editor_state.cursor1 = {line=3, pos=1}
Editor_state.screen_top1 = {line=3, pos=1} Editor_state.screen_top1 = {line=3, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'def', 'baseline/screen:1') App.screen.check(y, 'def', 'baseline/screen:1')
@ -1428,7 +1346,6 @@ function test_up_arrow_scrolls_up_by_one_screen_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=6} Editor_state.cursor1 = {line=3, pos=6}
Editor_state.screen_top1 = {line=3, pos=5} Editor_state.screen_top1 = {line=3, pos=5}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'jkl', 'baseline/screen:1') App.screen.check(y, 'jkl', 'baseline/screen:1')
@ -1456,7 +1373,6 @@ function test_up_arrow_scrolls_up_to_final_screen_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1} Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=2, pos=1} Editor_state.screen_top1 = {line=2, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'ghi', 'baseline/screen:1') App.screen.check(y, 'ghi', 'baseline/screen:1')
@ -1486,7 +1402,6 @@ function test_up_arrow_scrolls_up_to_empty_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1} Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=2, pos=1} Editor_state.screen_top1 = {line=2, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1') App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1513,7 +1428,6 @@ function test_pageup()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1} Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=2, pos=1} Editor_state.screen_top1 = {line=2, pos=1}
Editor_state.screen_bottom1 = {}
-- initially the last two lines are displayed -- initially the last two lines are displayed
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
@ -1538,7 +1452,6 @@ function test_pageup_scrolls_up_by_screen_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1} Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=2, pos=1} Editor_state.screen_top1 = {line=2, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'ghi', 'baseline/screen:1') App.screen.check(y, 'ghi', 'baseline/screen:1')
@ -1567,7 +1480,6 @@ function test_pageup_scrolls_up_from_middle_screen_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=5} Editor_state.cursor1 = {line=2, pos=5}
Editor_state.screen_top1 = {line=2, pos=5} Editor_state.screen_top1 = {line=2, pos=5}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'jkl', 'baseline/screen:2') App.screen.check(y, 'jkl', 'baseline/screen:2')
@ -1594,7 +1506,6 @@ function test_enter_on_bottom_line_scrolls_down()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=2} Editor_state.cursor1 = {line=3, pos=2}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1') App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1623,7 +1534,6 @@ function test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=4, pos=2} Editor_state.cursor1 = {line=4, pos=2}
Editor_state.screen_top1 = {line=4, pos=1} Editor_state.screen_top1 = {line=4, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'jkl', 'baseline/screen:1') App.screen.check(y, 'jkl', 'baseline/screen:1')
@ -1646,7 +1556,6 @@ function test_inserting_text_on_final_line_avoids_scrolling_down_when_not_at_bot
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1} Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=2, pos=1} Editor_state.screen_top1 = {line=2, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
-- after hitting the inserting_text key the screen does not scroll down -- after hitting the inserting_text key the screen does not scroll down
edit.run_after_text_input(Editor_state, 'a') edit.run_after_text_input(Editor_state, 'a')
@ -1665,7 +1574,6 @@ function test_typing_on_bottom_line_scrolls_down()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=4} Editor_state.cursor1 = {line=3, pos=4}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc', 'baseline/screen:1') App.screen.check(y, 'abc', 'baseline/screen:1')
@ -1695,7 +1603,6 @@ function test_left_arrow_scrolls_up_in_wrapped_line()
Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'} Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.screen_top1 = {line=3, pos=5} Editor_state.screen_top1 = {line=3, pos=5}
Editor_state.screen_bottom1 = {}
-- cursor is at top of screen -- cursor is at top of screen
Editor_state.cursor1 = {line=3, pos=5} Editor_state.cursor1 = {line=3, pos=5}
edit.draw(Editor_state) edit.draw(Editor_state)
@ -1724,7 +1631,6 @@ function test_right_arrow_scrolls_down_in_wrapped_line()
Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'} Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- cursor is at bottom right of screen -- cursor is at bottom right of screen
Editor_state.cursor1 = {line=3, pos=5} Editor_state.cursor1 = {line=3, pos=5}
edit.draw(Editor_state) edit.draw(Editor_state)
@ -1754,7 +1660,6 @@ function test_home_scrolls_up_in_wrapped_line()
Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'} Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.screen_top1 = {line=3, pos=5} Editor_state.screen_top1 = {line=3, pos=5}
Editor_state.screen_bottom1 = {}
-- cursor is at top of screen -- cursor is at top of screen
Editor_state.cursor1 = {line=3, pos=5} Editor_state.cursor1 = {line=3, pos=5}
edit.draw(Editor_state) edit.draw(Editor_state)
@ -1783,7 +1688,6 @@ function test_end_scrolls_down_in_wrapped_line()
Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'} Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- cursor is at bottom right of screen -- cursor is at bottom right of screen
Editor_state.cursor1 = {line=3, pos=5} Editor_state.cursor1 = {line=3, pos=5}
edit.draw(Editor_state) edit.draw(Editor_state)
@ -1814,7 +1718,6 @@ function test_position_cursor_on_recently_edited_wrapping_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=25} Editor_state.cursor1 = {line=1, pos=25}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'abc def ghi ', 'baseline1/screen:1') App.screen.check(y, 'abc def ghi ', 'baseline1/screen:1')
@ -1848,7 +1751,6 @@ function test_backspace_can_scroll_up()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1} Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=2, pos=1} Editor_state.screen_top1 = {line=2, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'def', 'baseline/screen:1') App.screen.check(y, 'def', 'baseline/screen:1')
@ -1876,7 +1778,6 @@ function test_backspace_can_scroll_up_screen_line()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=3, pos=5} Editor_state.cursor1 = {line=3, pos=5}
Editor_state.screen_top1 = {line=3, pos=5} Editor_state.screen_top1 = {line=3, pos=5}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
local y = Editor_state.top local y = Editor_state.top
App.screen.check(y, 'jkl', 'baseline/screen:1') App.screen.check(y, 'jkl', 'baseline/screen:1')
@ -2011,7 +1912,6 @@ function test_undo_insert_text()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=4} Editor_state.cursor1 = {line=2, pos=4}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- insert a character -- insert a character
edit.draw(Editor_state) edit.draw(Editor_state)
edit.run_after_text_input(Editor_state, 'g') edit.run_after_text_input(Editor_state, 'g')
@ -2046,7 +1946,6 @@ function test_undo_delete_text()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=5} Editor_state.cursor1 = {line=2, pos=5}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
-- delete a character -- delete a character
edit.run_after_keychord(Editor_state, 'backspace', 'backspace') edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
check_eq(Editor_state.cursor1.line, 2, 'baseline/cursor:line') check_eq(Editor_state.cursor1.line, 2, 'baseline/cursor:line')
@ -2085,7 +1984,6 @@ function test_undo_restores_selection()
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.selection1 = {line=1, pos=2} Editor_state.selection1 = {line=1, pos=2}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
-- delete selected text -- delete selected text
edit.run_after_text_input(Editor_state, 'x') edit.run_after_text_input(Editor_state, 'x')
@ -2106,7 +2004,6 @@ function test_search()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
-- search for a string -- search for a string
edit.run_after_keychord(Editor_state, 'C-f', 'f') edit.run_after_keychord(Editor_state, 'C-f', 'f')
@ -2133,7 +2030,6 @@ function test_search_upwards()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1} Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
-- search for a string -- search for a string
edit.run_after_keychord(Editor_state, 'C-f', 'f') edit.run_after_keychord(Editor_state, 'C-f', 'f')
@ -2151,7 +2047,6 @@ function test_search_wrap()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=2, pos=1} Editor_state.cursor1 = {line=2, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
-- search for a string -- search for a string
edit.run_after_keychord(Editor_state, 'C-f', 'f') edit.run_after_keychord(Editor_state, 'C-f', 'f')
@ -2169,7 +2064,6 @@ function test_search_wrap_upwards()
Text.redraw_all(Editor_state) Text.redraw_all(Editor_state)
Editor_state.cursor1 = {line=1, pos=1} Editor_state.cursor1 = {line=1, pos=1}
Editor_state.screen_top1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1}
Editor_state.screen_bottom1 = {}
edit.draw(Editor_state) edit.draw(Editor_state)
-- search upwards for a string -- search upwards for a string
edit.run_after_keychord(Editor_state, 'C-f', 'f') edit.run_after_keychord(Editor_state, 'C-f', 'f')