local utf8 = require 'utf8' local my_utf8 = require 'my_utf8' local t = require 'utils' local wrap = require 'wrap' local Font_height = require 'font_height' local rects = {} local I = {} rects.internal = I -- Generate rects for each screen line in it and the range [pos,pos+dpos-1] -- associated with each within each screen line generate rects for each char -- (utf8 codepoint) and the pos associated with each. -- -- Each rect is a rectangle on screen, as defined by its x, y, dx (width) and -- dy (height). A rect will also usually contain some data that is -- claimed/made to live within that rectangle on screen. -- -- Example line containing 3 screen lines after word wrapping: -- {x=0, y=0, dx=100, dy=30, -- line is 100px wide and 30px tall -- screen_line_rects = { -- {x=0, y=0, dx=100, dy=10, -- first screen line of line -- ... -- see below for what's inside a screen line -- }, -- {x=0, y=10, dx=100, dy=10, -- second screen line of line starts at y=10 -- ... -- }, -- {x=0, y=20, dx=100, dy=10, -- third screen line of line starts at y=20 -- ... -- }, -- }, -- } -- -- Example screen line: -- {x=0, y=0, dx=100, dy=10, -- pos=1, dpos=10, -- will render 10 characters from the line starting at position 1 (start of line) -- char_rects = { -- {x=0, y=0, dx=10, dy=10, pos=1, data='a'}, -- the first character is drawn at 0,0; clicking anywhere in this rect focuses cursor before this character -- ... -- } -- } -- -- Some experimental features for syntax highlighting or "syntax geometry": -- -- * A char rect can only show the cursor inside it if show_cursor is set. -- This enables padding characters. You can have a range of rects on screen -- that all map to the same location (only one of them sets data). Clicking -- on any of them positions cursor in the same location. But no matter where -- you click, the cursor shows in the same place. -- -- Example: -- char_rects = { -- {x=0, y=0, dx=10, dy=10, pos=1, data='a', show_cursor=true}, -- now you need this extra verbosity by default -- ... -- } -- -- * The 'data' in a char rect won't be drawn if 'conceal' is set. The rect -- still exists and might position the cursor if you click on it. But what -- would it look like? That brings me to: -- -- * A char rect can include arbitrary 'draw' commands. For now I only support -- a couple: lines and rectangles. -- -- Example: draw an 'a' on screen with a border around it -- char_rects = { -- {x=0, y=0, dx=10, dy=10, pos=1, data='a', show_cursor=true -- draw = { -- {type='rect', mode='line', x=0, y=0, w=10, h=10}, -- }, -- }, -- ... -- } -- -- Example: draw an 'a' on screen with an underline -- char_rects = { -- {x=0, y=0, dx=10, dy=10, pos=1, data='a', show_cursor=true -- draw = { -- {type='line', x1=0, y=10, x2=10, y2=10) -- }, -- }, -- ... -- } -- -- Influencing where to show the cursor, when to hide text, or when show -- graphics in addition to or instead of the text, these seem like they might -- cover everything we need from syntax highlighting or "syntax geometry". function rects.compute(editor, loc, available_height) local line = editor.lines[loc.line].data local screen_lines = {} local curr_screen_line = {} local spos = 1 local y = 0 local indent = 0 if editor.indent_wrapped_lines then indent = editor.font:getWidth(line:match('^%s*')) if indent > editor.width*0.5 then indent = 0 end end -- keep in sync with defs/*compute_rects_for_line local debug = false if loc.line == edit.debug_wrap then debug = true edit.debug_wrap = nil end for pos, char, w, x, wrap in wrap.indented_wrap(line, loc.pos, 0.8*editor.width, editor.width, editor.font, indent, debug) do if wrap then assert(pos > 1) local filler_rect = {x=wrap.unwrapped_x, y=y, dx=editor.width-wrap.unwrapped_x, dy=editor.line_height, pos=pos, show_cursor=true} -- draw wrap indicator after previous char if wrap.word_wrap then -- draw word wrap indicator filler_rect.draw = { {type='circle', mode='line', fg={0.7,0.7,0.7}, x=wrap.unwrapped_x+5+2, y=y+editor.font_height-5, r=2}, } else -- truncating within a word; draw hyphen filler_rect.draw = { {type='line', fg={0.7,0.7,0.7}, x1=wrap.unwrapped_x+5, y1=y+editor.font_height/2, x2=wrap.unwrapped_x+5+5, y2=y+editor.font_height/2}, -- {type='circle', mode='line', fg={0.7,0.7,0.7}, x=wrap.unwrapped_x+5+2, y=y+editor.font_height-5, r=2}, } end table.insert(curr_screen_line, filler_rect) table.insert(screen_lines, {x=0, y=y, dx=editor.width, dy=editor.line_height, pos=spos, dpos=(pos-1)-spos+1, char_rects=curr_screen_line}) curr_screen_line = {} spos = pos y = y + editor.line_height if available_height and y + editor.line_height > available_height then return {x=0, y=0, dx=editor.width, dy=y, line_index=loc.line, screen_line_rects=screen_lines} end end local char_rect = {x=x, y=y, dx=w, dy=editor.line_height, pos=pos, data=char, show_cursor=true} if wrap or (loc.pos > 1 and x == 0) then -- wrapped line; draw a circle to its left char_rect.draw = { {type='circle', mode='line', fg={0.7,0.7,0.7}, x=x-5-2, y=y+editor.font_height-5, r=2}, } end table.insert(curr_screen_line, char_rect) end local x = 0 if #curr_screen_line > 0 then local ch = curr_screen_line[#curr_screen_line] x = ch.x + ch.dx end table.insert(curr_screen_line, {x=x, y=y, dx=editor.width-x, dy=editor.line_height, pos=utf8.len(line)+1, show_cursor=true}) -- filler table.insert(screen_lines, {x=0, y=y, dx=editor.width, dy=editor.line_height, pos=spos, dpos=utf8.len(line)+1-spos+1, char_rects=curr_screen_line}) y = y + editor.line_height return {x=0, y=0, dx=editor.width, dy=y, line_index=loc.line, screen_line_rects=screen_lines} end function rects.height_of_screen_line(editor, loc) return editor.line_height end return rects