diff options
-rw-r--r-- | Manual_tests.md | 2 | ||||
-rw-r--r-- | README.md | 109 | ||||
-rw-r--r-- | edit.lua | 254 | ||||
-rw-r--r-- | file.lua | 136 | ||||
-rw-r--r-- | main.lua | 12 | ||||
-rw-r--r-- | reference.md | 17 | ||||
-rw-r--r-- | run.lua | 4 | ||||
-rw-r--r-- | select.lua | 10 | ||||
-rw-r--r-- | source.lua | 4 | ||||
-rw-r--r-- | text.lua | 193 | ||||
-rw-r--r-- | text_tests.lua | 125 | ||||
-rw-r--r-- | undo.lua | 10 |
12 files changed, 153 insertions, 723 deletions
diff --git a/Manual_tests.md b/Manual_tests.md index 650bc76..3ee22c7 100644 --- a/Manual_tests.md +++ b/Manual_tests.md @@ -12,7 +12,7 @@ Initializing settings: - run with a filename on commandline, scroll around, quit; restart with new filename; window opens new filename with cursor up top - run editor, scroll around, move cursor to end of some line, quit; restart with new filename; window opens running the text editor in same position+dimensions - quit while running the text editor, restart; window opens running the text editor in same position+dimensions - - quit while editing source (color; no drawings; no selection), restart; window opens editing source in same position+dimensions + - quit while editing source (color; no selection), restart; window opens editing source in same position+dimensions - start out running the text editor, move window, press ctrl+e twice; window is running text editor in same position+dimensions - start out editing source, move window, press ctrl+e twice; window is editing source in same position+dimensions - no log file; switching to source works diff --git a/README.md b/README.md index 690d94c..27ce06d 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ -# Plain text with lines +# An editor for plain text. + +Not very useful by itself, but it's a fork of [lines.love](http://akkartik.name/lines.html) +that you can take in other directions besides line drawings, while easily +sharing patches between forks. -An editor for plain text where you can also seamlessly insert line drawings. Designed above all to be easy to modify and give you early warning if your modifications break something. -http://akkartik.name/lines.html - ## Getting started Install [LÖVE](https://love2d.org). It's just a 5MB download, open-source and @@ -17,13 +18,13 @@ optionally with a file path to edit. Alternatively, turn it into a .love file you can double-click on: ``` -$ zip -r /tmp/lines.love *.lua +$ zip -r /tmp/text.love *.lua ``` -By default, lines.love reads/writes the file `lines.txt` in +By default, it reads/writes the file `lines.txt` in [a directory relative to this app](https://love2d.org/wiki/love.filesystem.getSourceBaseDirectory). -To open a different file, drop it on the lines.love window. +To open a different file, drop it on the app window. ## Keyboard shortcuts @@ -36,12 +37,7 @@ While editing text: * mouse drag or `shift` + movement to select text, `ctrl+a` to select all * `ctrl+e` to modify the sources -For shortcuts while editing drawings, consult the online help. Either: -* hover on a drawing and hit `ctrl+h`, or -* click on a drawing to start a stroke and then press and hold `h` to see your - options at any point during a stroke. - -lines.love has been exclusively tested so far with a US keyboard layout. If +Exclusively tested so far with a US keyboard layout. If you use a different layout, please let me know if things worked, or if you found anything amiss: http://akkartik.name/contact @@ -52,74 +48,45 @@ found anything amiss: http://akkartik.name/contact * No support yet for right-to-left languages. * Undo/redo may be sluggish in large files. Large files may grow sluggish in - other ways. lines.love works well in all circumstances with files under - 50KB. + other ways. Works well in all circumstances with files under 50KB. * If you kill the process, say by force-quitting because things things get sluggish, you can lose data. -* The text cursor will always stay on the screen. This can have some strange - implications: - - * A long series of drawings will get silently skipped when you hit - page-down, until a line of text can be showed on screen. - * If there's no line of text at the top of the file, you may not be able - to scroll back up to the top with page-up. - - So far this app isn't really designed for drawing-heavy files. For now I'm - targeting mostly-text files with a few drawings mixed in. - -* No clipping yet for drawings. In particular, circles/squares/rectangles and - point labels can overflow a drawing. - -* If you ever see a crash when clicking on the mouse, it might be because a - mouse press and release need to happen in separate frames. Try pressing and - releasing more slowly and let me know if that helps or not. This is klunky, - sorry. - -* Touchpads can drag the mouse pointer using a light touch or a heavy click. - On Linux, drags using the light touch get interrupted when a key is pressed. - You'll have to press down to drag. - * Can't scroll while selecting text with mouse. * No scrollbars yet. That stuff is hard. ## Mirrors and Forks -Updates to lines.love can be downloaded from the following mirrors in addition -to the website above: -* https://git.sr.ht/~akkartik/lines.love -* https://repo.or.cz/lines.love.git -* https://tildegit.org/akkartik/lines.love -* https://git.merveilles.town/akkartik/lines.love -* https://git.tilde.institute/akkartik/lines.love -* https://codeberg.org/akkartik/lines.love -* https://github.com/akkartik/lines.love -* https://notabug.org/akkartik/lines.love -* https://pagure.io/lines.love -* https://nest.pijul.com/akkartik/lines.love (using the Pijul version control system) - -Forks of lines.love are encouraged. If you show me your fork, I'll link to it -here. - -* https://github.com/akkartik/lines-polygon-experiment -- an experiment that - uses separate shortcuts for regular polygons. `ctrl+3` for triangles, - `ctrl+4` for squares, etc. -* https://git.sr.ht/~akkartik/text.love -- a stripped down version without - drawings; useful starting point for some forks -* https://git.sr.ht/~akkartik/pensieve.love -- a note-taking app on an - infinite 2D surface. Still in development. -* https://git.sr.ht/~akkartik/capture.love -- a blank-slate mode for the - note-taking app, so all the stuff pensieve.love puts on screen doesn't cause - you to forget what you came to write down. - -## Associated tools - -* https://codeberg.org/akkartik/lines2md exports lines.love files to Markdown - and (non-editable) SVG. -* https://git.sr.ht/~akkartik/lines2html.love exports lines.love files to html - and inline SVG. +This repo is a fork of [lines.love](http://akkartik.name/lines.html), an +editor for plain text where you can also seamlessly insert line drawings. +Updates to it can be downloaded from the following mirrors: + +* https://repo.or.cz/text.love.git +* https://tildegit.org/akkartik/text.love +* https://git.tilde.institute/akkartik/text.love +* https://git.merveilles.town/akkartik/text.love +* https://git.sr.ht/~akkartik/text.love +* https://github.com/akkartik/text.love +* https://codeberg.org/akkartik/text.love +* https://notabug.org/akkartik/text.love +* https://pagure.io/text.love +* https://nest.pijul.com/akkartik/text.love (using the Pijul version control system) + +Further forks are encouraged. If you show me your fork, I'll link to it here. + +* https://git.sr.ht/~akkartik/view.love -- a stripped down version without + support for modifying files; useful starting point for some forks. +* https://git.sr.ht/~akkartik/pong.love -- a fairly minimal example app that + can edit and debug its own source code. +* https://git.sr.ht/~akkartik/template-live-editor -- a template for + building "free-wheeling" live programs (easy to fork, can be modified as + they run), with a text editor primitive. +* https://git.sr.ht/~akkartik/luaML.love -- a free-wheeling 'browser' for a + Lua-based markup language built as a live program. +* https://git.sr.ht/~akkartik/driver.love -- a programming environment for + modifying free-wheeling programs while they run. ## Feedback diff --git a/edit.lua b/edit.lua index 1fbe682..aa62ad3 100644 --- a/edit.lua +++ b/edit.lua @@ -1,50 +1,18 @@ -- some constants people might like to tweak Text_color = {r=0, g=0, b=0} Cursor_color = {r=1, g=0, b=0} -Stroke_color = {r=0, g=0, b=0} -Current_stroke_color = {r=0.7, g=0.7, b=0.7} -- in process of being drawn -Current_name_background_color = {r=1, g=0, b=0, a=0.1} -- name currently being edited -Focus_stroke_color = {r=1, g=0, b=0} -- what mouse is hovering over Highlight_color = {r=0.7, g=0.7, b=0.9} -- selected text -Icon_color = {r=0.7, g=0.7, b=0.7} -- color of current mode icon in drawings -Help_color = {r=0, g=0.5, b=0} -Help_background_color = {r=0, g=0.5, b=0, a=0.1} Margin_top = 15 Margin_left = 25 Margin_right = 25 -Drawing_padding_top = 10 -Drawing_padding_bottom = 10 -Drawing_padding_height = Drawing_padding_top + Drawing_padding_bottom - -Same_point_distance = 4 -- pixel distance at which two points are considered the same - edit = {} -- run in both tests and a real run function edit.initialize_state(top, left, right, font, font_height, line_height) -- currently always draws to bottom of screen local result = { - -- a line is either text or a drawing - -- a text is a table with: - -- mode = 'text', - -- string data, - -- a drawing is a table with: - -- mode = 'drawing' - -- a (h)eight, - -- an array of points, and - -- an array of shapes - -- a shape is a table containing: - -- a mode - -- an array points for mode 'freehand' (raw x,y coords; freehand drawings don't pollute the points array of a drawing) - -- an array vertices for mode 'polygon', 'rectangle', 'square' - -- p1, p2 for mode 'line' - -- center, radius for mode 'circle' - -- center, radius, start_angle, end_angle for mode 'arc' - -- Unless otherwise specified, coord fields are normalized; a drawing is always 256 units wide - -- The field names are carefully chosen so that switching modes in midstream - -- remembers previously entered points where that makes sense. - lines = {{mode='text', data=''}}, -- array of lines + lines = {{data=''}}, -- array of strings -- Lines can be too long to fit on screen, in which case they _wrap_ into -- multiple _screen lines_. @@ -78,9 +46,6 @@ function edit.initialize_state(top, left, right, font, font_height, line_height) cursor_x = 0, cursor_y = 0, - current_drawing_mode = 'line', -- one of the available shape modes - previous_drawing_mode = nil, -- extra state for some ephemeral modes like moving/deleting/naming points - font = font, font_height = font_height, line_height = line_height, @@ -109,11 +74,9 @@ function edit.check_locs(State) -- throw away all cursor state entirely if edit.invalid1(State, State.screen_top1) or edit.invalid_cursor1(State) - or not edit.cursor_on_text(State) or not Text.le1(State.screen_top1, State.cursor1) then State.screen_top1 = {line=1, pos=1} State.cursor1 = {line=1, pos=1} - edit.put_cursor_on_next_text_line(State) end end @@ -134,27 +97,8 @@ function edit.invalid_cursor1(State) return cursor1.pos > #State.lines[cursor1.line].data + 1 end -function edit.cursor_on_text(State) - return State.cursor1.line <= #State.lines - and State.lines[State.cursor1.line].mode == 'text' -end - -function edit.put_cursor_on_next_text_line(State) - while true do - if State.cursor1.line >= #State.lines then - break - end - if State.lines[State.cursor1.line].mode == 'text' then - break - end - State.cursor1.line = State.cursor1.line+1 - State.cursor1.pos = 1 - end -end - -- return y drawn until function edit.draw(State) - State.button_handlers = {} love.graphics.setFont(State.font) App.color(Text_color) assert(#State.lines == #State.line_cache, ('line_cache is out of date; %d elements when it should be %d'):format(#State.line_cache, #State.lines)) @@ -167,37 +111,13 @@ function edit.draw(State) local line = State.lines[line_index] --? print('draw:', y, line_index, line) if y + State.line_height > App.screen.height then break end - if line.mode == 'text' then ---? print('text.draw', y, line_index) - local startpos = 1 - if line_index == State.screen_top1.line then - startpos = State.screen_top1.pos - end - if line.data == '' then - -- button to insert new drawing - button(State, 'draw', {x=State.left-Margin_left+4, y=y+4, w=12,h=12, bg={r=1,g=1,b=0}, - icon = icon.insert_drawing, - onpress1 = function() - Drawing.before = snapshot(State, line_index-1, line_index) - table.insert(State.lines, line_index, {mode='drawing', y=y, h=256/2, points={}, shapes={}, pending={}}) - table.insert(State.line_cache, line_index, {}) - if State.cursor1.line >= line_index then - State.cursor1.line = State.cursor1.line+1 - end - schedule_save(State) - record_undo_event(State, {before=Drawing.before, after=snapshot(State, line_index-1, line_index+1)}) - end, - }) - end - y = Text.draw(State, line_index, y, startpos) ---? print('=> y', y) - elseif line.mode == 'drawing' then - y = y+Drawing_padding_top - Drawing.draw(State, line_index, y) - y = y + Drawing.pixels(line.h, State.width) + Drawing_padding_bottom - else - assert(false, ('unknown line mode %s'):format(line.mode)) +--? print('text.draw', y, line_index) + local startpos = 1 + if line_index == State.screen_top1.line then + startpos = State.screen_top1.pos end + y = Text.draw(State, line_index, y, startpos) +--? print('=> y', y) end if State.search_term then Text.draw_search_bar(State) @@ -206,7 +126,6 @@ function edit.draw(State) end function edit.update(State, dt) - Drawing.update(State, dt) if State.next_save and State.next_save < Current_time then save_to_disk(State) State.next_save = nil @@ -233,11 +152,6 @@ function edit.mouse_press(State, x,y, mouse_button) if State.search_term then return end State.mouse_down = mouse_button --? print_and_log(('edit.mouse_press: cursor at %d,%d'):format(State.cursor1.line, State.cursor1.pos)) - if mouse_press_consumed_by_any_button(State, x,y, mouse_button) then - -- press on a button and it returned 'true' to short-circuit - return - end - if y < State.top then State.old_cursor1 = State.cursor1 State.old_selection1 = State.selection1 @@ -250,35 +164,25 @@ function edit.mouse_press(State, x,y, mouse_button) end for line_index,line in ipairs(State.lines) do - if line.mode == 'text' then - if Text.in_line(State, line_index, x,y) then - -- delicate dance between cursor, selection and old cursor/selection - -- scenarios: - -- regular press+release: sets cursor, clears selection - -- shift press+release: - -- sets selection to old cursor if not set otherwise leaves it untouched - -- sets cursor - -- press and hold to start a selection: sets selection on press, cursor on release - -- press and hold, then press shift: ignore shift - -- i.e. mouse_release should never look at shift state ---? print_and_log(('edit.mouse_press: in line %d'):format(line_index)) - State.old_cursor1 = State.cursor1 - State.old_selection1 = State.selection1 - State.mousepress_shift = App.shift_down() - State.selection1 = { - line=line_index, - pos=Text.to_pos_on_line(State, line_index, x, y), - } - return - end - elseif line.mode == 'drawing' then - if Drawing.in_drawing(State, line_index, x, y, State.left,State.right) then - State.lines.current_drawing_index = line_index - State.lines.current_drawing = line - Drawing.before = snapshot(State, line_index) - Drawing.mouse_press(State, line_index, x,y, mouse_button) - return - end + if Text.in_line(State, line_index, x,y) then + -- delicate dance between cursor, selection and old cursor/selection + -- scenarios: + -- regular press+release: sets cursor, clears selection + -- shift press+release: + -- sets selection to old cursor if not set otherwise leaves it untouched + -- sets cursor + -- press and hold to start a selection: sets selection on press, cursor on release + -- press and hold, then press shift: ignore shift + -- i.e. mouse_release should never look at shift state +--? print_and_log(('edit.mouse_press: in line %d'):format(line_index)) + State.old_cursor1 = State.cursor1 + State.old_selection1 = State.selection1 + State.mousepress_shift = App.shift_down() + State.selection1 = { + line=line_index, + pos=Text.to_pos_on_line(State, line_index, x, y), + } + return end end @@ -291,43 +195,30 @@ end function edit.mouse_release(State, x,y, mouse_button) if State.search_term then return end ---? print_and_log(('edit.mouse_release: cursor at %d,%d'):format(State.cursor1.line, State.cursor1.pos)) +--? print_and_log(('edit.mouse_release(%d,%d): cursor at %d,%d'):format(x,y, State.cursor1.line, State.cursor1.pos)) State.mouse_down = nil - if State.lines.current_drawing then - Drawing.mouse_release(State, x,y, mouse_button) - schedule_save(State) - if Drawing.before then - record_undo_event(State, {before=Drawing.before, after=snapshot(State, State.lines.current_drawing_index)}) - Drawing.before = nil - end - else ---? print_and_log('edit.mouse_release: no current drawing') - if y < State.top then - State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos} + if y < State.top then + State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos} + edit.clean_up_mouse_press(State) + return + end + for line_index,line in ipairs(State.lines) do + if Text.in_line(State, line_index, x,y) then +--? print_and_log(('edit.mouse_release: in line %d'):format(line_index)) + State.cursor1 = { + line=line_index, + pos=Text.to_pos_on_line(State, line_index, x, y), + } +--? print_and_log(('edit.mouse_release: cursor now %d,%d'):format(State.cursor1.line, State.cursor1.pos)) edit.clean_up_mouse_press(State) return end - - for line_index,line in ipairs(State.lines) do - if line.mode == 'text' then - if Text.in_line(State, line_index, x,y) then ---? print_and_log(('edit.mouse_release: in line %d'):format(line_index)) - State.cursor1 = { - line=line_index, - pos=Text.to_pos_on_line(State, line_index, x, y), - } ---? print_and_log(('edit.mouse_release: cursor now %d,%d'):format(State.cursor1.line, State.cursor1.pos)) - edit.clean_up_mouse_press(State) - return - end - end - end - - -- still here? mouse release is below all screen lines - State.cursor1 = Text.final_text_loc_on_screen(State) - edit.clean_up_mouse_press(State) ---? print_and_log(('edit.mouse_release: finally selection %s,%s cursor %d,%d'):format(tostring(State.selection1.line), tostring(State.selection1.pos), State.cursor1.line, State.cursor1.pos)) end + + -- still here? mouse release is below all screen lines + State.cursor1 = Text.final_text_loc_on_screen(State) + edit.clean_up_mouse_press(State) +--? print_and_log(('edit.mouse_release: finally selection %s,%s cursor %d,%d'):format(tostring(State.selection1.line), tostring(State.selection1.pos), State.cursor1.line, State.cursor1.pos)) end function edit.clean_up_mouse_press(State) @@ -347,7 +238,6 @@ end function edit.mouse_wheel_move(State, dx,dy) if dy > 0 then State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos} - edit.put_cursor_on_next_text_line(State) for i=1,math.floor(dy) do Text.up(State) end @@ -365,24 +255,14 @@ function edit.text_input(State, t) if State.search_term then State.search_term = State.search_term..t Text.search_next(State) - elseif State.lines.current_drawing and State.current_drawing_mode == 'name' then - local before = snapshot(State, State.lines.current_drawing_index) - local drawing = State.lines.current_drawing - local p = drawing.points[drawing.pending.target_point] - p.name = p.name..t - record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)}) else - local drawing_index, drawing = Drawing.current_drawing(State) - if drawing_index == nil then - Text.text_input(State, t) - end + Text.text_input(State, t) end schedule_save(State) end function edit.keychord_press(State, chord, key) if State.selection1.line and - not State.lines.current_drawing and -- printable character created using shift key => delete selection -- (we're not creating any ctrl-shift- or alt-shift- combinations using regular/printable keys) (not App.shift_down() or utf8.len(key) == 1) and @@ -438,8 +318,6 @@ function edit.keychord_press(State, chord, key) State.selection1 = deepcopy(src.selection) patch(State.lines, event.after, event.before) patch_placeholders(State.line_cache, event.after, event.before) - -- invalidate various cached bits of lines - State.lines.current_drawing = nil -- if we're scrolling, reclaim all fragments to avoid memory leaks Text.redraw_all(State) schedule_save(State) @@ -452,8 +330,6 @@ function edit.keychord_press(State, chord, key) State.cursor1 = deepcopy(src.cursor) State.selection1 = deepcopy(src.selection) patch(State.lines, event.before, event.after) - -- invalidate various cached bits of lines - State.lines.current_drawing = nil -- if we're scrolling, reclaim all fragments to avoid memory leaks Text.redraw_all(State) schedule_save(State) @@ -492,43 +368,7 @@ function edit.keychord_press(State, chord, key) end schedule_save(State) record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)}) - -- dispatch to drawing or text - elseif App.mouse_down(1) or chord:sub(1,2) == 'C-' then - local drawing_index, drawing = Drawing.current_drawing(State) - if drawing_index then - local before = snapshot(State, drawing_index) - Drawing.keychord_press(State, chord) - record_undo_event(State, {before=before, after=snapshot(State, drawing_index)}) - schedule_save(State) - end - elseif chord == 'escape' and not App.mouse_down(1) then - for _,line in ipairs(State.lines) do - if line.mode == 'drawing' then - line.show_help = false - end - end - elseif State.lines.current_drawing and State.current_drawing_mode == 'name' then - if chord == 'return' then - State.current_drawing_mode = State.previous_drawing_mode - State.previous_drawing_mode = nil - else - local before = snapshot(State, State.lines.current_drawing_index) - local drawing = State.lines.current_drawing - local p = drawing.points[drawing.pending.target_point] - if chord == 'escape' then - p.name = nil - record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)}) - elseif chord == 'backspace' then - local len = utf8.len(p.name) - if len > 0 then - local byte_offset = Text.offset(p.name, len-1) - if len == 1 then byte_offset = 0 end - p.name = string.sub(p.name, 1, byte_offset) - record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)}) - end - end - end - schedule_save(State) + -- dispatch to text else Text.keychord_press(State, chord) end diff --git a/file.lua b/file.lua index 34be015..f7f832b 100644 --- a/file.lua +++ b/file.lua @@ -22,15 +22,11 @@ function load_from_file(infile) while true do local line = infile_next_line() if line == nil then break end - if line == '```lines' then -- inflexible with whitespace since these files are always autogenerated - table.insert(result, load_drawing(infile_next_line)) - else - table.insert(result, {mode='text', data=line}) - end + table.insert(result, {data=line}) end end if #result == 0 then - table.insert(result, {mode='text', data=''}) + table.insert(result, {data=''}) end return result end @@ -41,85 +37,12 @@ function save_to_disk(State) error('failed to write to "'..State.filename..'"') end for _,line in ipairs(State.lines) do - if line.mode == 'drawing' then - store_drawing(outfile, line) - else - outfile:write(line.data) - outfile:write('\n') - end + outfile:write(line.data) + outfile:write('\n') end outfile:close() end -function load_drawing(infile_next_line) - local drawing = {mode='drawing', h=256/2, points={}, shapes={}, pending={}} - while true do - local line = infile_next_line() - assert(line, 'drawing in file is incomplete') - if line == '```' then break end - local shape = json.decode(line) - if shape.mode == 'freehand' then - -- no changes needed - elseif shape.mode == 'line' or shape.mode == 'manhattan' then - local name = shape.p1.name - shape.p1 = Drawing.find_or_insert_point(drawing.points, shape.p1.x, shape.p1.y, --[[large width to minimize overlap]] 1600) - drawing.points[shape.p1].name = name - name = shape.p2.name - shape.p2 = Drawing.find_or_insert_point(drawing.points, shape.p2.x, shape.p2.y, --[[large width to minimize overlap]] 1600) - drawing.points[shape.p2].name = name - elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then - for i,p in ipairs(shape.vertices) do - local name = p.name - shape.vertices[i] = Drawing.find_or_insert_point(drawing.points, p.x,p.y, --[[large width to minimize overlap]] 1600) - drawing.points[shape.vertices[i]].name = name - end - elseif shape.mode == 'circle' or shape.mode == 'arc' then - local name = shape.center.name - shape.center = Drawing.find_or_insert_point(drawing.points, shape.center.x,shape.center.y, --[[large width to minimize overlap]] 1600) - drawing.points[shape.center].name = name - elseif shape.mode == 'deleted' then - -- ignore - else - assert(false, ('unknown drawing mode %s'):format(shape.mode)) - end - table.insert(drawing.shapes, shape) - end - return drawing -end - -function store_drawing(outfile, drawing) - outfile:write('```lines\n') - for _,shape in ipairs(drawing.shapes) do - if shape.mode == 'freehand' then - outfile:write(json.encode(shape)) - outfile:write('\n') - elseif shape.mode == 'line' or shape.mode == 'manhattan' then - local line = json.encode({mode=shape.mode, p1=drawing.points[shape.p1], p2=drawing.points[shape.p2]}) - outfile:write(line) - outfile:write('\n') - elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then - local obj = {mode=shape.mode, vertices={}} - for _,p in ipairs(shape.vertices) do - table.insert(obj.vertices, drawing.points[p]) - end - local line = json.encode(obj) - outfile:write(line) - outfile:write('\n') - elseif shape.mode == 'circle' then - outfile:write(json.encode({mode=shape.mode, center=drawing.points[shape.center], radius=shape.radius})) - outfile:write('\n') - elseif shape.mode == 'arc' then - outfile:write(json.encode({mode=shape.mode, center=drawing.points[shape.center], radius=shape.radius, start_angle=shape.start_angle, end_angle=shape.end_angle})) - outfile:write('\n') - elseif shape.mode == 'deleted' then - -- ignore - else - assert(false, ('unknown drawing mode %s'):format(shape.mode)) - end - end - outfile:write('```\n') -end - -- for tests function load_array(a) local result = {} @@ -128,61 +51,14 @@ function load_array(a) while true do i,line = next_line(a, i) if i == nil then break end ---? print(line) - if line == '```lines' then -- inflexible with whitespace since these files are always autogenerated ---? print('inserting drawing') - i, drawing = load_drawing_from_array(next_line, a, i) ---? print('i now', i) - table.insert(result, drawing) - else ---? print('inserting text') - table.insert(result, {mode='text', data=line}) - end + table.insert(result, {data=line}) end if #result == 0 then - table.insert(result, {mode='text', data=''}) + table.insert(result, {data=''}) end return result end -function load_drawing_from_array(iter, a, i) - local drawing = {mode='drawing', h=256/2, points={}, shapes={}, pending={}} - local line - while true do - i, line = iter(a, i) - assert(i, 'drawing in array is incomplete') ---? print(i) - if line == '```' then break end - local shape = json.decode(line) - if shape.mode == 'freehand' then - -- no changes needed - elseif shape.mode == 'line' or shape.mode == 'manhattan' then - local name = shape.p1.name - shape.p1 = Drawing.find_or_insert_point(drawing.points, shape.p1.x, shape.p1.y, --[[large width to minimize overlap]] 1600) - drawing.points[shape.p1].name = name - name = shape.p2.name - shape.p2 = Drawing.find_or_insert_point(drawing.points, shape.p2.x, shape.p2.y, --[[large width to minimize overlap]] 1600) - drawing.points[shape.p2].name = name - elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then - for i,p in ipairs(shape.vertices) do - local name = p.name - shape.vertices[i] = Drawing.find_or_insert_point(drawing.points, p.x,p.y, --[[large width to minimize overlap]] 1600) - drawing.points[shape.vertices[i]].name = name - end - elseif shape.mode == 'circle' or shape.mode == 'arc' then - local name = shape.center.name - shape.center = Drawing.find_or_insert_point(drawing.points, shape.center.x,shape.center.y, --[[large width to minimize overlap]] 1600) - drawing.points[shape.center].name = name - elseif shape.mode == 'deleted' then - -- ignore - else - assert(false, ('unknown drawing mode %s'):format(shape.mode)) - end - table.insert(drawing.shapes, shape) - end - return i, drawing -end - function is_absolute_path(path) local os_path_separator = package.config:sub(1,1) if os_path_separator == '/' then diff --git a/main.lua b/main.lua index 232308e..582f05a 100644 --- a/main.lua +++ b/main.lua @@ -24,13 +24,6 @@ load_file_from_source_or_save_directory('button.lua') -- both sides require (different parts of) the logging framework load_file_from_source_or_save_directory('log.lua') --- both sides use drawings -load_file_from_source_or_save_directory('icons.lua') -load_file_from_source_or_save_directory('drawing.lua') - load_file_from_source_or_save_directory('geom.lua') - load_file_from_source_or_save_directory('help.lua') -load_file_from_source_or_save_directory('drawing_tests.lua') - -- but some files we want to only load sometimes function App.load() log_new('session') @@ -70,6 +63,11 @@ function App.load() load_file_from_source_or_save_directory('source_undo.lua') load_file_from_source_or_save_directory('colorize.lua') load_file_from_source_or_save_directory('source_text_tests.lua') + load_file_from_source_or_save_directory('icons.lua') + load_file_from_source_or_save_directory('drawing.lua') + load_file_from_source_or_save_directory('geom.lua') + load_file_from_source_or_save_directory('help.lua') + load_file_from_source_or_save_directory('drawing_tests.lua') load_file_from_source_or_save_directory('source_tests.lua') elseif current_app_is_warning() then else diff --git a/reference.md b/reference.md index 1aadc41..972ab1d 100644 --- a/reference.md +++ b/reference.md @@ -199,9 +199,9 @@ early warning if you break something. * `state = edit.initialize_state(top, left, right, font, font_height, line_height)` -- returns an object that can be used to render an interactive editor widget - for text and line drawings starting at `y=top` on the app window, between - `x=left` and `x=right`. Wraps long lines at word boundaries where possible, - or in the middle of words (no hyphenation yet) when it must. + for text starting at `y=top` on the app window, between `x=left` and + `x=right`. Wraps long lines at word boundaries where possible, or in the + middle of words (no hyphenation yet) when it must. * `edit.quit()` -- calling this ensures any final edits are flushed to disk before the app exits. @@ -248,20 +248,9 @@ Some constants that affect editor behavior: * `Margin_top`, `Margin_left`, `Margin_right` are integers in pixel units that affect where the editor is drawn on window (it always extends to bottom of window as needed) -* `Drawing_padding_top` and `Drawing_padding_bottom` affect spacing around - drawings. * Various color constants are represented as tables with r/g/b keys: * `Text_color`, `Cursor_color`, `Highlight_color` for drawing text. - * `Stroke_color`, `Current_stroke_color` for line drawings. - * `Icon_color` affects the color of the little mode icon on the top right of - a drawing. - * `Current_name_background_color` manages the color when naming points using - `ctrl+n`. - * `Focus_stroke_color` affects the color of a point or line when you hover - over it. - * `Help_color` and `Help_background_color` affect the color of online help - within line drawings. ### clickable buttons diff --git a/run.lua b/run.lua index 2cdd892..307505d 100644 --- a/run.lua +++ b/run.lua @@ -34,7 +34,7 @@ function run.initialize(arg) -- keep a few blank lines around: https://merveilles.town/@akkartik/110084833821965708 - love.window.setTitle('lines.love - '..Editor_state.filename) + love.window.setTitle('text.love - '..Editor_state.filename) @@ -128,7 +128,7 @@ function run.file_drop(file) -- keep a few blank lines around: https://merveilles.town/@akkartik/110084833821965708 - love.window.setTitle('lines.love - '..Editor_state.filename) + love.window.setTitle('text.love - '..Editor_state.filename) diff --git a/select.lua b/select.lua index b67dd16..e8df6f9 100644 --- a/select.lua +++ b/select.lua @@ -73,10 +73,8 @@ function Text.mouse_pos(State) return State.screen_top1.line, State.screen_top1.pos end for line_index,line in ipairs(State.lines) do - if line.mode == 'text' then - if Text.in_line(State, line_index, x,y) then - return line_index, Text.to_pos_on_line(State, line_index, x,y) - end + if Text.in_line(State, line_index, x,y) then + return line_index, Text.to_pos_on_line(State, line_index, x,y) end end local screen_bottom1 = Text.screen_bottom1(State) @@ -158,9 +156,7 @@ function Text.selection(State) assert(minl < maxl, ('minl %d not < maxl %d'):format(minl, maxl)) local result = {State.lines[minl].data:sub(min_offset)} for i=minl+1,maxl-1 do - if State.lines[i].mode == 'text' then - table.insert(result, State.lines[i].data) - end + table.insert(result, State.lines[i].data) end table.insert(result, State.lines[maxl].data:sub(1, max_offset-1)) return table.concat(result, '\n') diff --git a/source.lua b/source.lua index d24957c..c85517d 100644 --- a/source.lua +++ b/source.lua @@ -74,7 +74,7 @@ function source.initialize() -- keep a few blank lines around: https://merveilles.town/@akkartik/110084833821965708 - love.window.setTitle('lines.love - source - '..Editor_state.filename) + love.window.setTitle('text.love - source - '..Editor_state.filename) @@ -207,7 +207,7 @@ function source.file_drop(file) -- keep a few blank lines around: https://merveilles.town/@akkartik/110084833821965708 - love.window.setTitle('lines.love - source') + love.window.setTitle('text.love - source') diff --git a/text.lua b/text.lua index c897f0b..5814693 100644 --- a/text.lua +++ b/text.lua @@ -82,7 +82,6 @@ end function Text.populate_screen_line_starting_pos(State, line_index) local line = State.lines[line_index] - if line.mode ~= 'text' then return end local line_cache = State.line_cache[line_index] if line_cache.screen_line_starting_pos then return @@ -141,7 +140,6 @@ function Text.text_input(State, t) end function Text.insert_at_cursor(State, t) - assert(State.lines[State.cursor1.line].mode == 'text', 'line is not text') local byte_offset = Text.offset(State.lines[State.cursor1.line].data, State.cursor1.pos) State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_offset-1)..t..string.sub(State.lines[State.cursor1.line].data, byte_offset) Text.clear_screen_line_cache(State, State.cursor1.line) @@ -194,16 +192,11 @@ function Text.keychord_press(State, chord) end elseif State.cursor1.line > 1 then before = snapshot(State, State.cursor1.line-1, State.cursor1.line) - if State.lines[State.cursor1.line-1].mode == 'drawing' then - table.remove(State.lines, State.cursor1.line-1) - table.remove(State.line_cache, State.cursor1.line-1) - else - -- join lines - State.cursor1.pos = utf8.len(State.lines[State.cursor1.line-1].data)+1 - State.lines[State.cursor1.line-1].data = State.lines[State.cursor1.line-1].data..State.lines[State.cursor1.line].data - table.remove(State.lines, State.cursor1.line) - table.remove(State.line_cache, State.cursor1.line) - end + -- join lines + State.cursor1.pos = utf8.len(State.lines[State.cursor1.line-1].data)+1 + State.lines[State.cursor1.line-1].data = State.lines[State.cursor1.line-1].data..State.lines[State.cursor1.line].data + table.remove(State.lines, State.cursor1.line) + table.remove(State.line_cache, State.cursor1.line) State.cursor1.line = State.cursor1.line-1 end if State.screen_top1.line > #State.lines then @@ -245,10 +238,8 @@ function Text.keychord_press(State, chord) -- no change to State.cursor1.pos end elseif State.cursor1.line < #State.lines then - if State.lines[State.cursor1.line+1].mode == 'text' then - -- join lines - State.lines[State.cursor1.line].data = State.lines[State.cursor1.line].data..State.lines[State.cursor1.line+1].data - end + -- join lines + State.lines[State.cursor1.line].data = State.lines[State.cursor1.line].data..State.lines[State.cursor1.line+1].data table.remove(State.lines, State.cursor1.line+1) table.remove(State.line_cache, State.cursor1.line+1) end @@ -342,7 +333,7 @@ end function Text.insert_return(State) local byte_offset = Text.offset(State.lines[State.cursor1.line].data, State.cursor1.pos) - table.insert(State.lines, State.cursor1.line+1, {mode='text', data=string.sub(State.lines[State.cursor1.line].data, byte_offset)}) + table.insert(State.lines, State.cursor1.line+1, {data=string.sub(State.lines[State.cursor1.line].data, byte_offset)}) table.insert(State.line_cache, State.cursor1.line+1, {}) State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_offset-1) Text.clear_screen_line_cache(State, State.cursor1.line) @@ -365,15 +356,8 @@ function Text.starty(State, line_index) local loc2 = Text.to2(State, State.screen_top1) local y = State.top while true do - if State.lines[loc2.line].mode == 'drawing' then - y = y + Drawing_padding_top - end if loc2.line == line_index then return y end - if State.lines[loc2.line].mode == 'text' then - y = y + State.line_height - elseif State.lines[loc2.line].mode == 'drawing' then - y = y + Drawing.pixels(State.lines[loc2.line].h, State.width) + Drawing_padding_bottom - end + y = y + State.line_height 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 @@ -388,11 +372,7 @@ function Text.previous_screen_top1(State) 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 + y = y - State.line_height loc2 = Text.previous_screen_line(State, loc2) end return Text.to1(State, loc2) @@ -412,11 +392,7 @@ function Text.screen_bottom1(State) 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 + y = y + State.line_height 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 @@ -426,29 +402,24 @@ function Text.screen_bottom1(State) end function Text.up(State) - assert(State.lines[State.cursor1.line].mode == 'text', 'line is not text') --? print('up', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos) local screen_line_starting_pos, screen_line_index = Text.pos_at_start_of_screen_line(State, State.cursor1) if screen_line_starting_pos == 1 then --? print('cursor is at first screen line of its line') -- line is done; skip to previous text line - local new_cursor_line = State.cursor1.line - while new_cursor_line > 1 do - new_cursor_line = new_cursor_line-1 - if State.lines[new_cursor_line].mode == 'text' then ---? print('found previous text line') - State.cursor1 = {line=new_cursor_line, pos=nil} - Text.populate_screen_line_starting_pos(State, State.cursor1.line) - -- previous text line found, pick its final screen line ---? print('has multiple screen lines') - local screen_line_starting_pos = State.line_cache[State.cursor1.line].screen_line_starting_pos ---? print(#screen_line_starting_pos) - screen_line_starting_pos = screen_line_starting_pos[#screen_line_starting_pos] - local screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line].data, screen_line_starting_pos) - local s = string.sub(State.lines[State.cursor1.line].data, screen_line_starting_byte_offset) - State.cursor1.pos = screen_line_starting_pos + Text.nearest_cursor_pos(State.font, s, State.cursor_x, State.left) - 1 - break - end + if State.cursor1.line > 1 then + local new_cursor_line = State.cursor1.line-1 +--? print('found previous text line') + State.cursor1 = {line=new_cursor_line, pos=nil} + Text.populate_screen_line_starting_pos(State, State.cursor1.line) + -- previous text line found, pick its final screen line +--? print('has multiple screen lines') + local screen_line_starting_pos = State.line_cache[State.cursor1.line].screen_line_starting_pos +--? print(#screen_line_starting_pos) + screen_line_starting_pos = screen_line_starting_pos[#screen_line_starting_pos] + local screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line].data, screen_line_starting_pos) + local s = string.sub(State.lines[State.cursor1.line].data, screen_line_starting_byte_offset) + State.cursor1.pos = screen_line_starting_pos + Text.nearest_cursor_pos(State.font, s, State.cursor_x, State.left) - 1 end else -- move up one screen line in current line @@ -469,23 +440,16 @@ function Text.up(State) end function Text.down(State) - assert(State.lines[State.cursor1.line].mode == 'text', 'line is not text') --? print('down', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos) assert(State.cursor1.pos, 'cursor has no pos') if Text.cursor_at_final_screen_line(State) then -- line is done, skip to next text line --? print('cursor at final screen line of its line') - local new_cursor_line = State.cursor1.line - while new_cursor_line < #State.lines do - new_cursor_line = new_cursor_line+1 - if State.lines[new_cursor_line].mode == 'text' then - State.cursor1 = { - line = new_cursor_line, - pos = Text.nearest_cursor_pos(State.font, State.lines[new_cursor_line].data, State.cursor_x, State.left), - } ---? print(State.cursor1.pos) - break - end + if State.cursor1.line < #State.lines then + local new_cursor_line = State.cursor1.line+1 + State.cursor1.line = new_cursor_line + State.cursor1.pos = Text.nearest_cursor_pos(State.font, State.lines[State.cursor1.line].data, State.cursor_x, State.left) +--? print(State.cursor1.pos) end local screen_bottom1 = Text.screen_bottom1(State) --? print('down 2', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, screen_bottom1.line, screen_bottom1.pos) @@ -589,21 +553,11 @@ function Text.match(s, pos, pat) end function Text.left(State) - assert(State.lines[State.cursor1.line].mode == 'text', 'line is not text') if State.cursor1.pos > 1 then State.cursor1.pos = State.cursor1.pos-1 - else - local new_cursor_line = State.cursor1.line - while new_cursor_line > 1 do - new_cursor_line = new_cursor_line-1 - if State.lines[new_cursor_line].mode == 'text' then - State.cursor1 = { - line = new_cursor_line, - pos = utf8.len(State.lines[new_cursor_line].data) + 1, - } - break - end - end + elseif State.cursor1.line > 1 then + State.cursor1.line = State.cursor1.line-1 + State.cursor1.pos = utf8.len(State.lines[State.cursor1.line].data) + 1 end if Text.lt1(State.cursor1, State.screen_top1) then State.screen_top1 = { @@ -622,18 +576,11 @@ function Text.right(State) end function Text.right_without_scroll(State) - assert(State.lines[State.cursor1.line].mode == 'text', 'line is not text') if State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) then State.cursor1.pos = State.cursor1.pos+1 - else - local new_cursor_line = State.cursor1.line - while new_cursor_line <= #State.lines-1 do - new_cursor_line = new_cursor_line+1 - if State.lines[new_cursor_line].mode == 'text' then - State.cursor1 = {line=new_cursor_line, pos=1} - break - end - end + elseif State.cursor1.line <= #State.lines-1 then + State.cursor1.line = State.cursor1.line+1 + State.cursor1.pos = 1 end end @@ -651,7 +598,6 @@ function Text.pos_at_start_of_screen_line(State, loc1) end function Text.pos_at_end_of_screen_line(State, loc1) - assert(State.lines[loc1.line].mode == 'text') Text.populate_screen_line_starting_pos(State, loc1.line) local line_cache = State.line_cache[loc1.line] local most_recent_final_pos = utf8.len(State.lines[loc1.line].data)+1 @@ -667,21 +613,10 @@ 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 + return { + line=screen_bottom1.line, + pos=Text.pos_at_end_of_screen_line(State, screen_bottom1), + } end function Text.cursor_at_final_screen_line(State) @@ -692,26 +627,7 @@ function Text.cursor_at_final_screen_line(State) end function Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State) - local y = State.top - while State.cursor1.line <= #State.lines do - if State.lines[State.cursor1.line].mode == 'text' then - break - end ---? print('cursor skips', State.cursor1.line) - y = y + Drawing_padding_height + Drawing.pixels(State.lines[State.cursor1.line].h, State.width) - State.cursor1.line = State.cursor1.line + 1 - end - if State.cursor1.pos == nil then - State.cursor1.pos = 1 - end - -- hack: insert a text line at bottom of file if necessary - if State.cursor1.line > #State.lines then - assert(State.cursor1.line == #State.lines+1, 'tried to ensure bottom line of file is text, but failed') - table.insert(State.lines, {mode='text', data=''}) - table.insert(State.line_cache, {}) - end ---? print(y, App.screen.height, App.screen.height-State.line_height) - if y > App.screen.height - State.line_height then + if State.top > App.screen.height - State.line_height then --? print('scroll up') Text.snap_cursor_to_bottom_of_screen(State) end @@ -731,24 +647,11 @@ function Text.snap_cursor_to_bottom_of_screen(State) while true do --? print(y, 'top2:', top2.line, top2.screen_line, top2.screen_pos) if top2.line == 1 and top2.screen_line == 1 then break end - if top2.screen_line > 1 or State.lines[top2.line-1].mode == 'text' then - local h = State.line_height - if y - h < State.top then - break - end - y = y - h - else - assert(top2.line > 1, 'tried to snap cursor to buttom of screen but failed') - assert(State.lines[top2.line-1].mode == 'drawing', "expected a drawing but it's not") - -- We currently can't draw partial drawings, so either skip it entirely - -- or not at all. - local h = Drawing_padding_height + Drawing.pixels(State.lines[top2.line-1].h, State.width) - if y - h < State.top then - break - end ---? print('skipping drawing of height', h) - y = y - h + local h = State.line_height + if y - h < State.top then + break end + y = y - h top2 = Text.previous_screen_line(State, top2) end --? print('top2 finally:', top2.line, top2.screen_line, top2.screen_pos) @@ -911,9 +814,6 @@ function Text.x(font, s, pos) end function Text.to2(State, loc1) - if State.lines[loc1.line].mode == 'drawing' then - return {line=loc1.line, screen_line=1, screen_pos=1} - end local result = {line=loc1.line} local line_cache = State.line_cache[loc1.line] Text.populate_screen_line_starting_pos(State, loc1.line) @@ -979,8 +879,6 @@ function Text.previous_screen_line(State, loc2) return {line=loc2.line, screen_line=loc2.screen_line-1, screen_pos=1} elseif loc2.line == 1 then return loc2 - elseif State.lines[loc2.line-1].mode == 'drawing' then - return {line=loc2.line-1, screen_line=1, screen_pos=1} else local l = State.lines[loc2.line-1] Text.populate_screen_line_starting_pos(State, loc2.line-1) @@ -989,9 +887,6 @@ function Text.previous_screen_line(State, loc2) 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 diff --git a/text_tests.lua b/text_tests.lua index 9b34a24..9b3b60c 100644 --- a/text_tests.lua +++ b/text_tests.lua @@ -15,32 +15,6 @@ function test_initial_state() check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos') end -function test_click_to_create_drawing() - App.screen.init{width=800, height=600} - Editor_state = edit.initialize_test_state() - Editor_state.lines = load_array{} - Text.redraw_all(Editor_state) - edit.draw(Editor_state) - edit.run_after_mouse_click(Editor_state, 8,Editor_state.top+8, 1) - -- cursor skips drawing to always remain on text - check_eq(#Editor_state.lines, 2, '#lines') - check_eq(Editor_state.cursor1.line, 2, 'cursor') -end - -function test_backspace_to_delete_drawing() - -- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end) - App.screen.init{width=120, height=60} - Editor_state = edit.initialize_test_state() - Editor_state.lines = load_array{'```lines', '```', ''} - Text.redraw_all(Editor_state) - -- cursor is on text as always (outside tests this will get initialized correctly) - Editor_state.cursor1.line = 2 - -- backspacing deletes the drawing - edit.run_after_keychord(Editor_state, 'backspace', 'backspace') - check_eq(#Editor_state.lines, 1, '#lines') - check_eq(Editor_state.cursor1.line, 1, 'cursor') -end - function test_backspace_from_start_of_final_line() -- display final line of text with cursor at start of it App.screen.init{width=120, height=60} @@ -994,34 +968,6 @@ function test_pagedown() App.screen.check(y, 'ghi', 'screen:2') end -function test_pagedown_skips_drawings() - -- some lines of text with a drawing intermixed - local drawing_width = 50 - App.screen.init{width=Editor_state.left+drawing_width, height=80} - Editor_state = edit.initialize_test_state() - Editor_state.lines = load_array{'abc', -- height 15 - '```lines', '```', -- height 25 - 'def', -- height 15 - 'ghi'} -- height 15 - Text.redraw_all(Editor_state) - check_eq(Editor_state.lines[2].mode, 'drawing', 'baseline/lines') - Editor_state.cursor1 = {line=1, pos=1} - Editor_state.screen_top1 = {line=1, pos=1} - local drawing_height = Drawing_padding_height + drawing_width/2 -- default - -- initially the screen displays the first line and the drawing - -- 15px margin + 15px line1 + 10px margin + 25px drawing + 10px margin = 75px < screen height 80px - edit.draw(Editor_state) - local y = Editor_state.top - App.screen.check(y, 'abc', 'baseline/screen:1') - -- after pagedown the screen draws the drawing up top - -- 15px margin + 10px margin + 25px drawing + 10px margin + 15px line3 = 75px < screen height 80px - edit.run_after_keychord(Editor_state, 'pagedown', 'pagedown') - check_eq(Editor_state.screen_top1.line, 2, 'screen_top') - check_eq(Editor_state.cursor1.line, 3, 'cursor') - y = Editor_state.top + drawing_height - App.screen.check(y, 'def', 'screen:1') -end - function test_pagedown_can_start_from_middle_of_long_wrapping_line() -- draw a few lines starting from a very long wrapping line App.screen.init{width=Editor_state.left+30, height=60} @@ -1099,30 +1045,6 @@ function test_down_arrow_moves_cursor() App.screen.check(y, 'ghi', 'screen:3') end -function test_down_arrow_skips_drawing() - -- some lines of text with a drawing intermixed - local drawing_width = 50 - App.screen.init{width=Editor_state.left+drawing_width, height=100} - Editor_state = edit.initialize_test_state() - Editor_state.lines = load_array{'abc', -- height 15 - '```lines', '```', -- height 25 - 'ghi'} - Text.redraw_all(Editor_state) - Editor_state.cursor1 = {line=1, pos=1} - Editor_state.screen_top1 = {line=1, pos=1} - edit.draw(Editor_state) - local y = Editor_state.top - App.screen.check(y, 'abc', 'baseline/screen:1') - y = y + Editor_state.line_height - local drawing_height = Drawing_padding_height + drawing_width/2 -- default - y = y + drawing_height - App.screen.check(y, 'ghi', 'baseline/screen:3') - check(Editor_state.cursor_x, 'baseline/cursor_x') - -- after hitting the down arrow the cursor moves down by 2 lines, skipping the drawing - edit.run_after_keychord(Editor_state, 'down', 'down') - check_eq(Editor_state.cursor1.line, 3, 'cursor') -end - function test_down_arrow_scrolls_down_by_one_line() -- display the first three lines with the cursor on the bottom line App.screen.init{width=120, height=60} @@ -1266,30 +1188,6 @@ function test_up_arrow_moves_cursor() App.screen.check(y, 'ghi', 'screen:3') end -function test_up_arrow_skips_drawing() - -- some lines of text with a drawing intermixed - local drawing_width = 50 - App.screen.init{width=Editor_state.left+drawing_width, height=100} - Editor_state = edit.initialize_test_state() - Editor_state.lines = load_array{'abc', -- height 15 - '```lines', '```', -- height 25 - 'ghi'} - Text.redraw_all(Editor_state) - Editor_state.cursor1 = {line=3, pos=1} - Editor_state.screen_top1 = {line=1, pos=1} - edit.draw(Editor_state) - local y = Editor_state.top - App.screen.check(y, 'abc', 'baseline/screen:1') - y = y + Editor_state.line_height - local drawing_height = Drawing_padding_height + drawing_width/2 -- default - y = y + drawing_height - App.screen.check(y, 'ghi', 'baseline/screen:3') - check(Editor_state.cursor_x, 'baseline/cursor_x') - -- after hitting the up arrow the cursor moves up by 2 lines, skipping the drawing - edit.run_after_keychord(Editor_state, 'up', 'up') - check_eq(Editor_state.cursor1.line, 1, 'cursor') -end - function test_up_arrow_scrolls_up_by_one_line() -- display the lines 2/3/4 with the cursor on line 2 App.screen.init{width=120, height=60} @@ -1317,27 +1215,6 @@ function test_up_arrow_scrolls_up_by_one_line() App.screen.check(y, 'ghi', 'screen:3') end -function test_up_arrow_scrolls_up_by_one_line_skipping_drawing() - -- display lines 3/4/5 with a drawing just off screen at line 2 - App.screen.init{width=120, height=60} - Editor_state = edit.initialize_test_state() - Editor_state.lines = load_array{'abc', '```lines', '```', 'def', 'ghi', 'jkl'} - Text.redraw_all(Editor_state) - Editor_state.cursor1 = {line=3, pos=1} - Editor_state.screen_top1 = {line=3, pos=1} - edit.draw(Editor_state) - local y = Editor_state.top - App.screen.check(y, 'def', 'baseline/screen:1') - y = y + Editor_state.line_height - App.screen.check(y, 'ghi', 'baseline/screen:2') - y = y + Editor_state.line_height - App.screen.check(y, 'jkl', 'baseline/screen:3') - -- after hitting the up arrow the screen scrolls up to previous text line - edit.run_after_keychord(Editor_state, 'up', 'up') - check_eq(Editor_state.screen_top1.line, 1, 'screen_top') - check_eq(Editor_state.cursor1.line, 1, 'cursor') -end - function test_up_arrow_scrolls_up_by_one_screen_line() -- display lines starting from second screen line of a line App.screen.init{width=Editor_state.left+30, height=60} @@ -2000,7 +1877,7 @@ end function test_search() App.screen.init{width=120, height=60} Editor_state = edit.initialize_test_state() - Editor_state.lines = load_array{'```lines', '```', 'def', 'ghi', '’deg'} -- contains unicode quote in final line + Editor_state.lines = load_array{'abc', 'def', 'ghi', '’deg'} -- contains unicode quote in final line Text.redraw_all(Editor_state) Editor_state.cursor1 = {line=1, pos=1} Editor_state.screen_top1 = {line=1, pos=1} diff --git a/undo.lua b/undo.lua index e5dea93..a41ba38 100644 --- a/undo.lua +++ b/undo.lua @@ -50,8 +50,6 @@ function snapshot(State, s,e) screen_top=deepcopy(State.screen_top1), selection=deepcopy(State.selection1), cursor=deepcopy(State.cursor1), - current_drawing_mode=Drawing_mode, - previous_drawing_mode=State.previous_drawing_mode, lines={}, start_line=s, end_line=e, @@ -60,13 +58,7 @@ function snapshot(State, s,e) -- deep copy lines without cached stuff like text fragments for i=s,e do local line = State.lines[i] - if line.mode == 'text' then - table.insert(event.lines, {mode='text', data=line.data}) -- I've forgotten: should we deepcopy(line.data)? - elseif line.mode == 'drawing' then - table.insert(event.lines, {mode='drawing', h=line.h, points=deepcopy(line.points), shapes=deepcopy(line.shapes), pending={}}) - else - assert(false, ('unknown line mode %s'):format(line.mode)) - end + table.insert(event.lines, {data=line.data}) -- I've forgotten: should we deepcopy(line.data)? end return event end |