diff options
-rw-r--r-- | README.md | 3 | ||||
-rw-r--r-- | app.lua | 6 | ||||
-rw-r--r-- | conf.lua | 3 | ||||
-rw-r--r-- | drawing.lua | 6 | ||||
-rw-r--r-- | edit.lua | 21 | ||||
-rw-r--r-- | file.lua | 2 | ||||
-rw-r--r-- | keychord.lua | 4 | ||||
-rw-r--r-- | log.lua | 50 | ||||
-rw-r--r-- | log_browser.lua | 6 | ||||
-rw-r--r-- | main.lua | 29 | ||||
-rw-r--r-- | reference.md | 3 | ||||
-rw-r--r-- | run.lua | 17 | ||||
-rw-r--r-- | search.lua | 2 | ||||
-rw-r--r-- | source.lua | 27 | ||||
-rw-r--r-- | source_edit.lua | 39 | ||||
-rw-r--r-- | source_text.lua | 6 | ||||
-rw-r--r-- | source_text_tests.lua | 35 | ||||
-rw-r--r-- | source_undo.lua | 8 | ||||
-rw-r--r-- | text.lua | 10 | ||||
-rw-r--r-- | text_tests | 2 | ||||
-rw-r--r-- | text_tests.lua | 35 | ||||
-rw-r--r-- | undo.lua | 8 |
22 files changed, 193 insertions, 129 deletions
diff --git a/README.md b/README.md index d0893bb..0887f4a 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,7 @@ modifications break something. ## Getting started Install [LÖVE](https://love2d.org). It's just a 5MB download, open-source and -extremely well-behaved. I'll assume below that you can invoke it using the -`love` command, but that might vary depending on your OS. +extremely well-behaved. To run from the terminal, [pass this directory to LÖVE](https://love2d.org/wiki/Getting_Started#Running_Games), optionally with a file path to edit. diff --git a/app.lua b/app.lua index 263518c..2375ff9 100644 --- a/app.lua +++ b/app.lua @@ -131,7 +131,7 @@ function App.run_tests() end table.sort(sorted_names) --? App.initialize_for_test() -- debug: run a single test at a time like these 2 lines ---? test_click_below_all_lines() +--? test_search() for _,name in ipairs(sorted_names) do App.initialize_for_test() --? print('=== '..name) @@ -428,9 +428,9 @@ function App.disable_tests() -- love.keyboard.isDown doesn't work on Android, so emulate it using -- keypressed and keyreleased events if name == 'keypressed' then - love.handlers[name] = function(key, scancode, isrepeat) + love.handlers[name] = function(key, scancode, is_repeat) Keys_down[key] = true - return App.keypressed(key, scancode, isrepeat) + return App.keypressed(key, scancode, is_repeat) end elseif name == 'keyreleased' then love.handlers[name] = function(key, scancode) diff --git a/conf.lua b/conf.lua new file mode 100644 index 0000000..d9878da --- /dev/null +++ b/conf.lua @@ -0,0 +1,3 @@ +function love.conf(t) + t.identity = 'text' +end diff --git a/drawing.lua b/drawing.lua index 92e3d5f..b74855a 100644 --- a/drawing.lua +++ b/drawing.lua @@ -221,7 +221,7 @@ function Drawing.in_drawing(State, line_index, x,y, left,right) return y >= starty and y < starty + Drawing.pixels(drawing.h, width) and x >= left and x < right end -function Drawing.mouse_press(State, drawing_index, x,y, mouse_button) +function Drawing.mouse_press(State, drawing_index, x,y, mouse_button, is_touch, presses) local drawing = State.lines[drawing_index] local starty = Text.starty(State, drawing_index) local cx = Drawing.coord(x-State.left, State.width) @@ -300,7 +300,7 @@ function Drawing.relax_constraints(drawing, p) end end -function Drawing.mouse_release(State, x,y, mouse_button) +function Drawing.mouse_release(State, x,y, mouse_button, is_touch, presses) if State.current_drawing_mode == 'move' then State.current_drawing_mode = State.previous_drawing_mode State.previous_drawing_mode = nil @@ -396,7 +396,7 @@ function Drawing.mouse_release(State, x,y, mouse_button) end end -function Drawing.keychord_press(State, chord) +function Drawing.keychord_press(State, chord, key, scancode, is_repeat) if chord == 'C-p' and not App.mouse_down(1) then State.current_drawing_mode = 'freehand' elseif App.mouse_down(1) and chord == 'l' then diff --git a/edit.lua b/edit.lua index 99a71a8..edfcb20 100644 --- a/edit.lua +++ b/edit.lua @@ -1,7 +1,7 @@ -- some constants people might like to tweak Text_color = {r=0, g=0, b=0} Cursor_color = {r=1, g=0, b=0} -Highlight_color = {r=0.7, g=0.7, b=0.9} -- selected text +Highlight_color = {r=0.7, g=0.7, b=0.9, a=0.4} -- selected text Margin_top = 15 Margin_left = 25 @@ -145,8 +145,7 @@ function edit.quit(State) end end -function edit.mouse_press(State, x,y, mouse_button) - love.keyboard.setTextInput(true) -- bring up keyboard on touch screen +function edit.mouse_press(State, x,y, mouse_button, is_touch, presses) 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)) @@ -188,10 +187,10 @@ function edit.mouse_press(State, x,y, mouse_button) State.old_cursor1 = State.cursor1 State.old_selection1 = State.selection1 State.mousepress_shift = App.shift_down() - State.selection1 = Text.final_text_loc_on_screen(State) + State.selection1 = Text.final_loc_on_screen(State) end -function edit.mouse_release(State, x,y, mouse_button) +function edit.mouse_release(State, x,y, mouse_button, is_touch, presses) if State.search_term then return end --? 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 @@ -214,7 +213,7 @@ function edit.mouse_release(State, x,y, mouse_button) end -- still here? mouse release is below all screen lines - State.cursor1 = Text.final_text_loc_on_screen(State) + State.cursor1 = Text.final_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 @@ -258,7 +257,7 @@ function edit.text_input(State, t) schedule_save(State) end -function edit.keychord_press(State, chord, key) +function edit.keychord_press(State, chord, key, scancode, is_repeat) if State.selection1.line and -- printable character created using shift key => delete selection -- (we're not creating any ctrl-shift- or alt-shift- combinations using regular/printable keys) @@ -284,8 +283,10 @@ function edit.keychord_press(State, chord, key) State.screen_top = deepcopy(State.search_backup.screen_top) Text.search_next(State) elseif chord == 'down' then - State.cursor1.pos = State.cursor1.pos+1 - Text.search_next(State) + if #State.search_term > 0 then + Text.right(State) + Text.search_next(State) + end elseif chord == 'up' then Text.search_previous(State) end @@ -366,7 +367,7 @@ function edit.keychord_press(State, chord, key) record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)}) schedule_save(State) else - Text.keychord_press(State, chord) + Text.keychord_press(State, chord, key, scancode, is_repeat) end end diff --git a/file.lua b/file.lua index f7f832b..028ffb4 100644 --- a/file.lua +++ b/file.lua @@ -47,7 +47,7 @@ end function load_array(a) local result = {} local next_line = ipairs(a) - local i,line,drawing = 0, '' + local i,line = 0, '' while true do i,line = next_line(a, i) if i == nil then break end diff --git a/keychord.lua b/keychord.lua index f1b6a59..5de899d 100644 --- a/keychord.lua +++ b/keychord.lua @@ -2,13 +2,13 @@ Modifiers = {'lctrl', 'rctrl', 'lalt', 'ralt', 'lshift', 'rshift', 'lgui', 'rgui'} -function App.keypressed(key, scancode, isrepeat) +function App.keypressed(key, scancode, is_repeat) if array.find(Modifiers, key) then -- do nothing when the modifier is pressed return end -- include the modifier(s) when the non-modifer is pressed - App.keychord_press(App.combine_modifiers(key), key) + App.keychord_press(App.combine_modifiers(key), key, scancode, is_repeat) end function App.combine_modifiers(key) diff --git a/log.lua b/log.lua index 8903e08..4e428d8 100644 --- a/log.lua +++ b/log.lua @@ -1,38 +1,38 @@ function log(stack_frame_index, obj) - local info = debug.getinfo(stack_frame_index, 'Sl') - local msg - if type(obj) == 'string' then - msg = obj - else - msg = json.encode(obj) - end - love.filesystem.append('log', info.short_src..':'..info.currentline..': '..msg..'\n') + local info = debug.getinfo(stack_frame_index, 'Sl') + local msg + if type(obj) == 'string' then + msg = obj + else + msg = json.encode(obj) + end + love.filesystem.append('log', info.short_src..':'..info.currentline..': '..msg..'\n') end -- for section delimiters we'll use specific Unicode box characters function log_start(name, stack_frame_index) - if stack_frame_index == nil then - stack_frame_index = 3 - end - -- I'd like to use the unicode character \u{250c} here, but it doesn't work - -- in OpenBSD. - log(stack_frame_index, '[ u250c ' .. name) + if stack_frame_index == nil then + stack_frame_index = 3 + end + -- I'd like to use the unicode character \u{250c} here, but it doesn't work + -- in OpenBSD. + log(stack_frame_index, '[ u250c ' .. name) end function log_end(name, stack_frame_index) - if stack_frame_index == nil then - stack_frame_index = 3 - end - -- I'd like to use the unicode character \u{2518} here, but it doesn't work - -- in OpenBSD. - log(stack_frame_index, '] u2518 ' .. name) + if stack_frame_index == nil then + stack_frame_index = 3 + end + -- I'd like to use the unicode character \u{2518} here, but it doesn't work + -- in OpenBSD. + log(stack_frame_index, '] u2518 ' .. name) end function log_new(name, stack_frame_index) - if stack_frame_index == nil then - stack_frame_index = 4 - end - log_end(name, stack_frame_index) - log_start(name, stack_frame_index) + if stack_frame_index == nil then + stack_frame_index = 4 + end + log_end(name, stack_frame_index) + log_start(name, stack_frame_index) end -- rendering graphical objects within sections/boxes diff --git a/log_browser.lua b/log_browser.lua index 6e7e6db..fae9d6d 100644 --- a/log_browser.lua +++ b/log_browser.lua @@ -183,7 +183,7 @@ end function log_browser.quit(State) end -function log_browser.mouse_press(State, x,y, mouse_button) +function log_browser.mouse_press(State, x,y, mouse_button, is_touch, presses) local line_index = log_browser.line_index(State, x,y) if line_index == nil then -- below lower margin @@ -249,7 +249,7 @@ function log_browser.line_index(State, mx,my) end end -function log_browser.mouse_release(State, x,y, mouse_button) +function log_browser.mouse_release(State, x,y, mouse_button, is_touch, presses) end function log_browser.mouse_wheel_move(State, dx,dy) @@ -267,7 +267,7 @@ end function log_browser.text_input(State, t) end -function log_browser.keychord_press(State, chord, key) +function log_browser.keychord_press(State, chord, key, scancode, is_repeat) -- move if chord == 'up' then log_browser.up(State) diff --git a/main.lua b/main.lua index 582f05a..83049c2 100644 --- a/main.lua +++ b/main.lua @@ -113,16 +113,15 @@ function check_love_version_for_tests() end end -function App.initialize(arg) - love.keyboard.setTextInput(true) -- bring up keyboard on touch screen +function App.initialize(arg, unfiltered_arg) love.keyboard.setKeyRepeat(true) love.graphics.setBackgroundColor(1,1,1) if Current_app == 'run' then - run.initialize(arg) + run.initialize(arg, unfiltered_arg) elseif Current_app == 'source' then - source.initialize(arg) + source.initialize(arg, unfiltered_arg) elseif current_app_is_warning() then else assert(false, 'unknown app "'..Current_app..'"') @@ -208,7 +207,7 @@ function App.update(dt) end end -function App.keychord_press(chord, key) +function App.keychord_press(chord, key, scancode, is_repeat) -- ignore events for some time after window in focus (mostly alt-tab) if Current_time < Last_focus_time + 0.01 then return @@ -252,9 +251,9 @@ function App.keychord_press(chord, key) return end if Current_app == 'run' then - if run.keychord_press then run.keychord_press(chord, key) end + if run.keychord_press then run.keychord_press(chord, key, scancode, is_repeat) end elseif Current_app == 'source' then - if source.keychord_press then source.keychord_press(chord, key) end + if source.keychord_press then source.keychord_press(chord, key, scancode, is_repeat) end else assert(false, 'unknown app "'..Current_app..'"') end @@ -294,24 +293,24 @@ function App.keyreleased(key, scancode) end end -function App.mousepressed(x,y, mouse_button) +function App.mousepressed(x,y, mouse_button, is_touch, presses) if current_app_is_warning() then return end --? print('mouse press', x,y) if Current_app == 'run' then - if run.mouse_press then run.mouse_press(x,y, mouse_button) end + if run.mouse_press then run.mouse_press(x,y, mouse_button, is_touch, presses) end elseif Current_app == 'source' then - if source.mouse_press then source.mouse_press(x,y, mouse_button) end + if source.mouse_press then source.mouse_press(x,y, mouse_button, is_touch, presses) end else assert(false, 'unknown app "'..Current_app..'"') end end -function App.mousereleased(x,y, mouse_button) +function App.mousereleased(x,y, mouse_button, is_touch, presses) if current_app_is_warning() then return end if Current_app == 'run' then - if run.mouse_release then run.mouse_release(x,y, mouse_button) end + if run.mouse_release then run.mouse_release(x,y, mouse_button, is_touch, presses) end elseif Current_app == 'source' then - if source.mouse_release then source.mouse_release(x,y, mouse_button) end + if source.mouse_release then source.mouse_release(x,y, mouse_button, is_touch, presses) end else assert(false, 'unknown app "'..Current_app..'"') end @@ -320,9 +319,9 @@ end function App.mousemoved(x,y, dx,dy, is_touch) if current_app_is_warning() then return end if Current_app == 'run' then - if run.mouse_move then run.mouse_move(dx,dy) end + if run.mouse_move then run.mouse_move(x,y, dx,dy, is_touch) end elseif Current_app == 'source' then - if source.mouse_move then source.mouse_move(dx,dy) end + if source.mouse_move then source.mouse_move(x,y, dx,dy, is_touch) end else assert(false, 'unknown app "'..Current_app..'"') end diff --git a/reference.md b/reference.md index 972ab1d..80309c3 100644 --- a/reference.md +++ b/reference.md @@ -203,9 +203,6 @@ early warning if you break something. `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. - * `edit.draw(state)` -- call this from `App.draw` to display the current editor state on the app window as requested in the call to `edit.initialize_state` that created `state`. diff --git a/run.lua b/run.lua index 307505d..9511726 100644 --- a/run.lua +++ b/run.lua @@ -11,7 +11,7 @@ function run.initialize_globals() end -- called only for real run -function run.initialize(arg) +function run.initialize(arg, unfiltered_arg) log_new('run') if Settings then run.load_settings() @@ -100,7 +100,7 @@ function run.initialize_window_geometry() App.screen.resize(App.screen.width, App.screen.height, App.screen.flags) end -function run.resize(w, h) +function run.resize(w,h) --? print(("Window resized to width: %d and height: %d."):format(w, h)) App.screen.width, App.screen.height = w, h Text.redraw_all(Editor_state) @@ -166,14 +166,15 @@ function absolutize(path) return path end -function run.mouse_press(x,y, mouse_button) +function run.mouse_press(x,y, mouse_button, is_touch, presses) Cursor_time = 0 -- ensure cursor is visible immediately after it moves - return edit.mouse_press(Editor_state, x,y, mouse_button) + love.keyboard.setTextInput(true) -- bring up keyboard on touch screen + return edit.mouse_press(Editor_state, x,y, mouse_button, is_touch, presses) end -function run.mouse_release(x,y, mouse_button) +function run.mouse_release(x,y, mouse_button, is_touch, presses) Cursor_time = 0 -- ensure cursor is visible immediately after it moves - return edit.mouse_release(Editor_state, x,y, mouse_button) + return edit.mouse_release(Editor_state, x,y, mouse_button, is_touch, presses) end function run.mouse_wheel_move(dx,dy) @@ -186,9 +187,9 @@ function run.text_input(t) return edit.text_input(Editor_state, t) end -function run.keychord_press(chord, key) +function run.keychord_press(chord, key, scancode, is_repeat) Cursor_time = 0 -- ensure cursor is visible immediately after it moves - return edit.keychord_press(Editor_state, chord, key) + return edit.keychord_press(Editor_state, chord, key, scancode, is_repeat) end function run.key_release(key, scancode) diff --git a/search.lua b/search.lua index d3a5fea..4aa1f02 100644 --- a/search.lua +++ b/search.lua @@ -17,6 +17,7 @@ function Text.draw_search_bar(State) end function Text.search_next(State) + if #State.search_term == 0 then return end -- search current line from cursor local curr_pos = State.cursor1.pos local curr_line = State.lines[State.cursor1.line].data @@ -71,6 +72,7 @@ function Text.search_next(State) end function Text.search_previous(State) + if #State.search_term == 0 then return end -- search current line before cursor local curr_pos = State.cursor1.pos local curr_line = State.lines[State.cursor1.line].data diff --git a/source.lua b/source.lua index c85517d..bb42440 100644 --- a/source.lua +++ b/source.lua @@ -56,7 +56,7 @@ function source.initialize_globals() end -- called only for real run -function source.initialize() +function source.initialize(arg, unfiltered_arg) log_new('source') if Settings and Settings.source then source.load_settings() @@ -173,7 +173,7 @@ function source.initialize_window_geometry() App.screen.resize(App.screen.width, App.screen.height, App.screen.flags) end -function source.resize(w, h) +function source.resize(w,h) --? print(("Window resized to width: %d and height: %d."):format(w, h)) App.screen.width, App.screen.height = w, h Text.redraw_all(Editor_state) @@ -283,14 +283,15 @@ function source.settings() } end -function source.mouse_press(x,y, mouse_button) +function source.mouse_press(x,y, mouse_button, is_touch, presses) Cursor_time = 0 -- ensure cursor is visible immediately after it moves + love.keyboard.setTextInput(true) -- bring up keyboard on touch screen --? print('mouse click', x, y) --? print(Editor_state.left, Editor_state.right) --? print(Log_browser_state.left, Log_browser_state.right) if Show_file_navigator and y < Menu_status_bar_height + File_navigation.num_lines * Editor_state.line_height then -- send click to buttons - edit.mouse_press(Editor_state, x,y, mouse_button) + edit.mouse_press(Editor_state, x,y, mouse_button, is_touch, presses) return end if x < Editor_state.right + Margin_right then @@ -299,23 +300,23 @@ function source.mouse_press(x,y, mouse_button) Focus = 'edit' return end - edit.mouse_press(Editor_state, x,y, mouse_button) + edit.mouse_press(Editor_state, x,y, mouse_button, is_touch, presses) elseif Show_log_browser_side and Log_browser_state.left <= x and x < Log_browser_state.right then --? print('click on log_browser side') if Focus ~= 'log_browser' then Focus = 'log_browser' return end - log_browser.mouse_press(Log_browser_state, x,y, mouse_button) + log_browser.mouse_press(Log_browser_state, x,y, mouse_button, is_touch, presses) end end -function source.mouse_release(x,y, mouse_button) +function source.mouse_release(x,y, mouse_button, is_touch, presses) Cursor_time = 0 -- ensure cursor is visible immediately after it moves if Focus == 'edit' then - return edit.mouse_release(Editor_state, x,y, mouse_button) + return edit.mouse_release(Editor_state, x,y, mouse_button, is_touch, presses) else - return log_browser.mouse_release(Log_browser_state, x,y, mouse_button) + return log_browser.mouse_release(Log_browser_state, x,y, mouse_button, is_touch, presses) end end @@ -341,7 +342,7 @@ function source.text_input(t) end end -function source.keychord_press(chord, key) +function source.keychord_press(chord, key, scancode, is_repeat) Cursor_time = 0 -- ensure cursor is visible immediately after it moves --? print('source keychord') if Show_file_navigator then @@ -381,9 +382,9 @@ function source.keychord_press(chord, key) return end if Focus == 'edit' then - return edit.keychord_press(Editor_state, chord, key) + return edit.keychord_press(Editor_state, chord, key, scancode, is_repeat) else - return log_browser.keychord_press(Log_browser_state, chord, key) + return log_browser.keychord_press(Log_browser_state, chord, key, scancode, is_repeat) end end @@ -392,6 +393,6 @@ function source.key_release(key, scancode) if Focus == 'edit' then return edit.key_release(Editor_state, key, scancode) else - return log_browser.keychord_press(Log_browser_state, chordkey, scancode) + return log_browser.key_release(Log_browser_state, key, scancode) end end diff --git a/source_edit.lua b/source_edit.lua index 2dca05d..77487ea 100644 --- a/source_edit.lua +++ b/source_edit.lua @@ -142,14 +142,13 @@ function edit.cursor_on_text(State) 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 + local line = State.cursor1.line + if State.lines[line].mode == 'text' then return end + while line <= #State.lines and State.lines[line].mode ~= 'text' do + line = line+1 + end + if line <= #State.lines and State.lines[line].mode == 'text' then + State.cursor1.line = line State.cursor1.pos = 1 end end @@ -233,8 +232,7 @@ function edit.quit(State) end end -function edit.mouse_press(State, x,y, mouse_button) - love.keyboard.setTextInput(true) -- bring up keyboard on touch screen +function edit.mouse_press(State, x,y, mouse_button, is_touch, presses) 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)) @@ -281,7 +279,7 @@ function edit.mouse_press(State, x,y, mouse_button) 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) + Drawing.mouse_press(State, line_index, x,y, mouse_button, is_touch, presses) return end end @@ -294,12 +292,12 @@ function edit.mouse_press(State, x,y, mouse_button) State.selection1 = Text.final_text_loc_on_screen(State) end -function edit.mouse_release(State, x,y, mouse_button) +function edit.mouse_release(State, x,y, mouse_button, is_touch, presses) if State.search_term then return end --? print_and_log(('edit.mouse_release: cursor at %d,%d'):format(State.cursor1.line, State.cursor1.pos)) State.mouse_down = nil if State.lines.current_drawing then - Drawing.mouse_release(State, x,y, mouse_button) + Drawing.mouse_release(State, x,y, mouse_button, is_touch, presses) if Drawing.before then record_undo_event(State, {before=Drawing.before, after=snapshot(State, State.lines.current_drawing_index)}) Drawing.before = nil @@ -385,7 +383,7 @@ function edit.text_input(State, t) schedule_save(State) end -function edit.keychord_press(State, chord, key) +function edit.keychord_press(State, chord, key, scancode, is_repeat) if State.selection1.line and not State.lines.current_drawing and -- printable character created using shift key => delete selection @@ -408,9 +406,14 @@ function edit.keychord_press(State, chord, key) local len = utf8.len(State.search_term) local byte_offset = Text.offset(State.search_term, len) State.search_term = string.sub(State.search_term, 1, byte_offset-1) - elseif chord == 'down' then - State.cursor1.pos = State.cursor1.pos+1 + State.cursor = deepcopy(State.search_backup.cursor) + State.screen_top = deepcopy(State.search_backup.screen_top) Text.search_next(State) + elseif chord == 'down' then + if #State.search_term > 0 then + Text.right(State) + Text.search_next(State) + end elseif chord == 'up' then Text.search_previous(State) end @@ -499,7 +502,7 @@ function edit.keychord_press(State, chord, key) local drawing_index, drawing = Drawing.current_drawing(State) if drawing_index then local before = snapshot(State, drawing_index) - Drawing.keychord_press(State, chord) + Drawing.keychord_press(State, chord, key, scancode, is_repeat) record_undo_event(State, {before=before, after=snapshot(State, drawing_index)}) schedule_save(State) end @@ -532,7 +535,7 @@ function edit.keychord_press(State, chord, key) end schedule_save(State) else - Text.keychord_press(State, chord) + Text.keychord_press(State, chord, key, scancode, is_repeat) end end diff --git a/source_text.lua b/source_text.lua index 9125cb0..c281acf 100644 --- a/source_text.lua +++ b/source_text.lua @@ -90,9 +90,9 @@ function Text.screen_line(line, line_cache, i) if i >= #line_cache.screen_line_starting_pos then return line.data:sub(offset) end - local endpos = line_cache.screen_line_starting_pos[i+1]-1 + local endpos = line_cache.screen_line_starting_pos[i+1] local end_offset = Text.offset(line.data, endpos) - return line.data:sub(offset, end_offset) + return line.data:sub(offset, end_offset-1) end function Text.draw_cursor(State, x, y) @@ -223,7 +223,7 @@ function Text.insert_at_cursor(State, t) end -- Don't handle any keys here that would trigger text_input above. -function Text.keychord_press(State, chord) +function Text.keychord_press(State, chord, key, scancode, is_repeat) --? print('chord', chord, State.selection1.line, State.selection1.pos) --== shortcuts that mutate text if chord == 'return' then diff --git a/source_text_tests.lua b/source_text_tests.lua index 11cc823..12fb9c6 100644 --- a/source_text_tests.lua +++ b/source_text_tests.lua @@ -319,7 +319,7 @@ function test_click_on_empty_line() check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits') end -function test_click_below_all_lines() +function test_click_below_final_line_of_file() -- display one line App.screen.init{width=50, height=80} Editor_state = edit.initialize_test_state() @@ -331,8 +331,9 @@ function test_click_below_all_lines() -- click below first line edit.draw(Editor_state) edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+50, 1) - -- cursor doesn't move - check_eq(Editor_state.cursor1.line, 1, 'cursor') + -- cursor goes to bottom + check_eq(Editor_state.cursor1.line, 1, 'cursor:line') + check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos') -- selection remains empty check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits') end @@ -2073,3 +2074,31 @@ function test_search_wrap_upwards() check_eq(Editor_state.cursor1.line, 1, '1/cursor:line') check_eq(Editor_state.cursor1.pos, 6, '1/cursor:pos') end + +function test_search_downwards_from_end_of_line() + App.screen.init{width=120, height=60} + Editor_state = edit.initialize_test_state() + Editor_state.lines = load_array{'abc', 'def', 'ghi'} + Text.redraw_all(Editor_state) + Editor_state.cursor1 = {line=1, pos=4} + Editor_state.screen_top1 = {line=1, pos=1} + edit.draw(Editor_state) + -- search for empty string + edit.run_after_keychord(Editor_state, 'C-f', 'f') + edit.run_after_keychord(Editor_state, 'down', 'down') + -- no crash +end + +function test_search_downwards_from_final_pos_of_line() + App.screen.init{width=120, height=60} + Editor_state = edit.initialize_test_state() + Editor_state.lines = load_array{'abc', 'def', 'ghi'} + Text.redraw_all(Editor_state) + Editor_state.cursor1 = {line=1, pos=3} + Editor_state.screen_top1 = {line=1, pos=1} + edit.draw(Editor_state) + -- search for empty string + edit.run_after_keychord(Editor_state, 'C-f', 'f') + edit.run_after_keychord(Editor_state, 'down', 'down') + -- no crash +end diff --git a/source_undo.lua b/source_undo.lua index d91fecd..772e5da 100644 --- a/source_undo.lua +++ b/source_undo.lua @@ -84,12 +84,12 @@ end -- https://stackoverflow.com/questions/640642/how-do-you-copy-a-lua-table-by-value/26367080#26367080 function deepcopy(obj, seen) if type(obj) ~= 'table' then return obj end - if seen and seen[obj] then return seen[obj] end - local s = seen or {} + seen = seen or {} + if seen[obj] then return seen[obj] end local result = setmetatable({}, getmetatable(obj)) - s[obj] = result + seen[obj] = result for k,v in pairs(obj) do - result[deepcopy(k, s)] = deepcopy(v, s) + result[deepcopy(k, seen)] = deepcopy(v, seen) end return result end diff --git a/text.lua b/text.lua index 82b4ab8..9db8b16 100644 --- a/text.lua +++ b/text.lua @@ -65,9 +65,9 @@ function Text.screen_line(line, line_cache, i) if i >= #line_cache.screen_line_starting_pos then return line.data:sub(offset) end - local endpos = line_cache.screen_line_starting_pos[i+1]-1 + local endpos = line_cache.screen_line_starting_pos[i+1] local end_offset = Text.offset(line.data, endpos) - return line.data:sub(offset, end_offset) + return line.data:sub(offset, end_offset-1) end function Text.draw_cursor(State, x, y) @@ -147,7 +147,7 @@ function Text.insert_at_cursor(State, t) end -- Don't handle any keys here that would trigger text_input above. -function Text.keychord_press(State, chord) +function Text.keychord_press(State, chord, key, scancode, is_repeat) --? print('chord', chord, State.selection1.line, State.selection1.pos) --== shortcuts that mutate text (must schedule_save) if chord == 'return' then @@ -610,7 +610,7 @@ function Text.pos_at_end_of_screen_line(State, loc1) assert(false, ('invalid pos %d'):format(loc1.pos)) end -function Text.final_text_loc_on_screen(State) +function Text.final_loc_on_screen(State) local screen_bottom1 = Text.screen_bottom1(State) return { line=screen_bottom1.line, @@ -926,7 +926,7 @@ function Text.tweak_screen_top_and_cursor(State) State.cursor1 = deepcopy(State.screen_top1) elseif State.cursor1.line >= screen_bottom1.line then if Text.cursor_out_of_screen(State) then - State.cursor1 = Text.final_text_loc_on_screen(State) + State.cursor1 = Text.final_loc_on_screen(State) end end end diff --git a/text_tests b/text_tests index 2a31131..85d9de9 100644 --- a/text_tests +++ b/text_tests @@ -23,7 +23,7 @@ click on wrapping line rendered from partway at top of screen click past end of wrapping line click past end of wrapping line containing non ascii click past end of word wrapping line -click below final line does nothing +click below final line of file # cursor movement move left diff --git a/text_tests.lua b/text_tests.lua index 9b3b60c..962de7d 100644 --- a/text_tests.lua +++ b/text_tests.lua @@ -293,7 +293,7 @@ function test_click_on_empty_line() check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits') end -function test_click_below_all_lines() +function test_click_below_final_line_of_file() -- display one line App.screen.init{width=50, height=80} Editor_state = edit.initialize_test_state() @@ -305,8 +305,9 @@ function test_click_below_all_lines() -- click below first line edit.draw(Editor_state) edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+50, 1) - -- cursor doesn't move - check_eq(Editor_state.cursor1.line, 1, 'cursor') + -- cursor goes to bottom + check_eq(Editor_state.cursor1.line, 1, 'cursor:line') + check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos') -- selection remains empty check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits') end @@ -1950,3 +1951,31 @@ function test_search_wrap_upwards() check_eq(Editor_state.cursor1.line, 1, '1/cursor:line') check_eq(Editor_state.cursor1.pos, 6, '1/cursor:pos') end + +function test_search_downwards_from_end_of_line() + App.screen.init{width=120, height=60} + Editor_state = edit.initialize_test_state() + Editor_state.lines = load_array{'abc', 'def', 'ghi'} + Text.redraw_all(Editor_state) + Editor_state.cursor1 = {line=1, pos=4} + Editor_state.screen_top1 = {line=1, pos=1} + edit.draw(Editor_state) + -- search for empty string + edit.run_after_keychord(Editor_state, 'C-f', 'f') + edit.run_after_keychord(Editor_state, 'down', 'down') + -- no crash +end + +function test_search_downwards_from_final_pos_of_line() + App.screen.init{width=120, height=60} + Editor_state = edit.initialize_test_state() + Editor_state.lines = load_array{'abc', 'def', 'ghi'} + Text.redraw_all(Editor_state) + Editor_state.cursor1 = {line=1, pos=3} + Editor_state.screen_top1 = {line=1, pos=1} + edit.draw(Editor_state) + -- search for empty string + edit.run_after_keychord(Editor_state, 'C-f', 'f') + edit.run_after_keychord(Editor_state, 'down', 'down') + -- no crash +end diff --git a/undo.lua b/undo.lua index e0cd730..69f7c31 100644 --- a/undo.lua +++ b/undo.lua @@ -81,12 +81,12 @@ end -- https://stackoverflow.com/questions/640642/how-do-you-copy-a-lua-table-by-value/26367080#26367080 function deepcopy(obj, seen) if type(obj) ~= 'table' then return obj end - if seen and seen[obj] then return seen[obj] end - local s = seen or {} + seen = seen or {} + if seen[obj] then return seen[obj] end local result = setmetatable({}, getmetatable(obj)) - s[obj] = result + seen[obj] = result for k,v in pairs(obj) do - result[deepcopy(k, s)] = deepcopy(v, s) + result[deepcopy(k, seen)] = deepcopy(v, seen) end return result end |