-- text editor, particularly text drawing, horizontal wrap, vertical scrolling Text = {} -- 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 function Text.draw(State, line_index, y, startpos) --? print('text.draw', line_index, y) local line = State.lines[line_index] local line_cache = State.line_cache[line_index] line_cache.starty = y line_cache.startpos = startpos -- wrap long lines local final_screen_line_starting_pos = startpos -- track value to return Text.populate_screen_line_starting_pos(State, line_index) assert(#line_cache.screen_line_starting_pos >= 1) for i=1,#line_cache.screen_line_starting_pos do local pos = line_cache.screen_line_starting_pos[i] if pos < startpos then -- render nothing else final_screen_line_starting_pos = pos local screen_line = Text.screen_line(line, line_cache, i) --? print('text.draw:', screen_line, 'at', line_index,pos, 'after', x,y) local frag_len = utf8.len(screen_line) -- render fragment if State.selection1.line then local lo, hi = Text.clip_selection(State, line_index, pos, pos+frag_len) Text.draw_highlight(State, line, State.left,y, pos, lo,hi) end App.color(Text_color) App.screen.print(screen_line, State.left,y) -- render cursor if necessary if line_index == State.cursor1.line then if pos <= State.cursor1.pos and pos + frag_len >= State.cursor1.pos then if State.search_term then local data = State.lines[State.cursor1.line].data local cursor_offset = Text.offset(data, State.cursor1.pos) if data:sub(cursor_offset, cursor_offset+#State.search_term-1) == State.search_term then local lo_px = Text.draw_highlight(State, line, State.left,y, pos, State.cursor1.pos, State.cursor1.pos+utf8.len(State.search_term)) App.color(Text_color) love.graphics.print(State.search_term, State.left+lo_px,y) end else Text.draw_cursor(State, State.left+Text.x(screen_line, State.cursor1.pos-pos+1), y) end end end y = y + State.line_height if y >= App.screen.height then break end end end return y, final_screen_line_starting_pos end function Text.screen_line(line, line_cache, i) local pos = line_cache.screen_line_starting_pos[i] local offset = Text.offset(line.data, pos) 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 end_offset = Text.offset(line.data, endpos) return line.data:sub(offset, end_offset) end function Text.draw_cursor(State, x, y) -- blink every 0.5s if math.floor(Cursor_time*2)%2 == 0 then App.color(Cursor_color) love.graphics.rectangle('fill', x,y, 3,State.line_height) end State.cursor_x = x State.cursor_y = y+State.line_height 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 end line_cache.screen_line_starting_pos = {1} local x = 0 local pos = 1 -- try to wrap at word boundaries for frag in line.data:gmatch('%S*%s*') do local frag_width = App.width(frag) --? print('-- frag:', frag, pos, x, frag_width, State.width) while x + frag_width > State.width do --? print('frag:', frag, pos, x, frag_width, State.width) if x < 0.8 * State.width then -- long word; chop it at some letter -- We're not going to reimplement TeX here. local bpos = Text.nearest_pos_less_than(frag, State.width - x) -- everything works if bpos == 0, but is a little inefficient pos = pos + bpos local boffset = Text.offset(frag, bpos+1) -- byte _after_ bpos frag = string.sub(frag, boffset) --? if bpos > 0 then --? print('after chop:', frag) --? end frag_width = App.width(frag) end --? print('screen line:', pos) table.insert(line_cache.screen_line_starting_pos, pos) x = 0 -- new screen line end x = x + frag_width pos = pos + utf8.len(frag) end end function Text.text_input(State, t) if App.mouse_down(1) then return end if App.ctrl_down() or App.alt_down() or App.cmd_down() then return end 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) Text.insert_at_cursor(State, t) if State.cursor_y > App.screen.height - State.line_height then Text.populate_screen_line_starting_pos(State, State.cursor1.line) Text.snap_cursor_to_bottom_of_screen(State, State.left, State.right) end record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)}) end function Text.insert_at_cursor(State, t) assert(State.lines[State.cursor1.line].mode == '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) State.cursor1.pos = State.cursor1.pos+1 end -- Don't handle any keys here that would trigger text_input above. function Text.keychord_press(State, chord) --? print('chord', chord, State.selection1.line, State.selection1.pos) --== shortcuts that mu
c{0: 0 (((1 integer)) <- ((copy)) ((2 literal))) -- nil
c{0: 1 (((2 integer)) <- ((copy)) ((23 literal))) -- nil
c{0: 2 (((3 boolean)) <- ((copy)) ((nil literal))) -- nil
c{0: 3 (((4 integer)) <- ((copy)) ((24 literal))) -- nil
c{0: 4 (((5 boolean)) <- ((copy)) ((t literal))) -- nil
c{0: 5 (((6 integer-address)) <- ((copy)) ((1 literal))) -- nil
c{0: 6 (((7 integer)) <- ((length)) ((6 integer-boolean-pair-array-address) (deref))) -- nil
c{1: 0 ✓ (((1 integer)) <- ((copy)) ((2 literal)))
c{1: 1 ✓ (((2 integer)) <- ((copy)) ((23 literal)))
c{1: 2 ✓ (((3 boolean)) <- ((copy)) ((nil literal)))
c{1: 3 ✓ (((4 integer)) <- ((copy)) ((24 literal)))
c{1: 4 ✓ (((5 boolean)) <- ((copy)) ((t literal)))
c{1: 5 ✓ (((6 integer-address)) <- ((copy)) ((1 literal)))
c{1: 6 ✓ (((7 integer)) <- ((length)) ((6 integer-boolean-pair-array-address) (deref)))
cn0: convert-names in main
cn0: (((1 integer)) <- ((copy)) ((2 literal))) nil nil
cn0: checking arg ((2 literal))
cn0: checking oarg ((1 integer))
maybe-add: ((1 integer))
cn0: (((2 integer)) <- ((copy)) ((23 literal))) nil nil
cn0: checking arg ((23 literal))
cn0: checking oarg ((2 integer))
maybe-add: ((2 integer))
cn0: (((3 boolean)) <- ((copy)) ((nil literal))) nil nil
cn0: checking arg ((nil literal))
cn0: checking oarg ((3 boolean))
maybe-add: ((3 boolean))
cn0: (((4 integer)) <- ((copy)) ((24 literal))) nil nil
cn0: checking arg ((24 literal))
cn0: checking oarg ((4 integer))
maybe-add: ((4 integer))
cn0: (((5 boolean)) <- ((copy)) ((t literal))) nil nil
cn0: checking arg ((t literal))
cn0: checking oarg ((5 boolean))
maybe-add: ((5 boolean))
cn0: (((6 integer-address)) <- ((copy)) ((1 literal))) nil nil
cn0: checking arg ((1 literal))
cn0: checking oarg ((6 integer-address))
maybe-add: ((6 integer-address))
cn0: (((7 integer)) <- ((length)) ((6 integer-boolean-pair-array-address) (deref))) nil nil
cn0: checking arg ((6 integer-boolean-pair-array-address) (deref))
maybe-add: ((6 integer-boolean-pair-array-address) (deref))
cn0: checking oarg ((7 integer))
maybe-add: ((7 integer))
cn1: (((1 integer)) <- ((copy)) ((2 literal)))
cn1: (((2 integer)) <- ((copy)) ((23 literal)))
cn1: (((3 boolean)) <- ((copy)) ((nil literal)))
cn1: (((4 integer)) <- ((copy)) ((24 literal)))
cn1: (((5 boolean)) <- ((copy)) ((t literal)))
cn1: (((6 integer-address)) <- ((copy)) ((1 literal)))
cn1: (((7 integer)) <- ((length)) ((6 integer-boolean-pair-array-address) (deref)))
schedule: main
run: main 0: (((1 integer)) <- ((copy)) ((2 literal)))
run: main 0: 2 => ((1 integer))
mem: ((1 integer)): 1 <= 2
run: main 1: (((2 integer)) <- ((copy)) ((23 literal)))
run: main 1: 23 => ((2 integer))
mem: ((2 integer)): 2 <= 23
run: main 2: (((3 boolean)) <- ((copy)) ((nil literal)))
run: main 2: nil => ((3 boolean))
mem: ((3 boolean)): 3 <= nil
run: main 3: (((4 integer)) <- ((copy)) ((24 literal)))
run: main 3: 24 => ((4 integer))
mem: ((4 integer)): 4 <= 24
run: main 4: (((5 boolean)) <- ((copy)) ((t literal)))
run: main 4: t => ((5 boolean))
mem: ((5 boolean)): 5 <= t
run: main 5: (((6 integer-address)) <- ((copy)) ((1 literal)))
run: main 5: 1 => ((6 integer-address))
mem: ((6 integer-address)): 6 <= 1
run: main 6: (((7 integer)) <- ((length)) ((6 integer-boolean-pair-array-address) (deref)))
array-len: ((6 integer-boolean-pair-array-address) (deref))
mem: ((1 integer)) => 2
run: main 6: 2 => ((7 integer))
mem: ((7 integer)): 7 <= 2
schedule: done with routine nil