1 -- helpers for selecting portions of text
2 -- To keep things simple, we'll ignore the B side when selections start on the
3 -- A side, and stick to within a single B side selections start in.
5 -- Return any intersection of the region from State.selection1 to State.cursor1 (or
6 -- current mouse, if mouse is pressed; or recent mouse if mouse is pressed and
7 -- currently over a drawing) with the region between {line=line_index, pos=apos}
8 -- and {line=line_index, pos=bpos}.
9 -- apos must be less than bpos. However State.selection1 and State.cursor1 can be in any order.
10 -- Result: positions spos,epos between apos,bpos.
11 function Text
.clip_selection(State
, line_index
, apos
, bpos
)
12 if State
.selection1
.line
== nil then return nil,nil end
13 -- min,max = sorted(State.selection1,State.cursor1)
14 local minl
,minp
= State
.selection1
.line
,State
.selection1
.pos
16 if App
.mouse_down(1) then
17 maxl
,maxp
= Text
.mouse_pos(State
)
19 maxl
,maxp
= State
.cursor1
.line
,State
.cursor1
.pos
21 if Text
.lt1({line
=maxl
, pos
=maxp
},
22 {line
=minl
, pos
=minp
}) then
26 -- check if intervals are disjoint
27 if line_index
< minl
then return nil,nil end
28 if line_index
> maxl
then return nil,nil end
29 if line_index
== minl
and bpos
<= minp
then return nil,nil end
30 if line_index
== maxl
and apos
>= maxp
then return nil,nil end
31 -- compare bounds more carefully (start inclusive, end exclusive)
32 local a_ge
= Text
.le1({line
=minl
, pos
=minp
}, {line
=line_index
, pos
=apos
})
33 local b_lt
= Text
.lt1({line
=line_index
, pos
=bpos
}, {line
=maxl
, pos
=maxp
})
34 --? print(minl,line_index,maxl, '--', minp,apos,bpos,maxp, '--', a_ge,b_lt)
39 assert(maxl
== line_index
)
42 assert(minl
== line_index
)
45 assert(minl
== maxl
and minl
== line_index
)
50 -- draw highlight for line corresponding to (lo,hi) given an approximate x,y and pos on the same screen line
51 -- Creates text objects every time, so use this sparingly.
52 -- Returns some intermediate computation useful elsewhere.
53 function Text
.draw_highlight(State
, line
, x
,y
, pos
, lo
,hi
)
55 local lo_offset
= Text
.offset(line
.data
, lo
)
56 local hi_offset
= Text
.offset(line
.data
, hi
)
57 local pos_offset
= Text
.offset(line
.data
, pos
)
62 local before
= line
.data
:sub(pos_offset
, lo_offset
-1)
63 local before_text
= App
.newText(love
.graphics
.getFont(), before
)
64 lo_px
= App
.width(before_text
)
66 --? print(lo,pos,hi, '--', lo_offset,pos_offset,hi_offset, '--', lo_px)
67 local s
= line
.data
:sub(lo_offset
, hi_offset
-1)
68 local text
= App
.newText(love
.graphics
.getFont(), s
)
69 local text_width
= App
.width(text
)
70 App
.color(Highlight_color
)
71 love
.graphics
.rectangle('fill', x
+lo_px
,y
, text_width
,State
.line_height
)
77 -- inefficient for some reason, so don't do it on every frame
78 function Text
.mouse_pos(State
)
79 local time
= love
.timer
.getTime()
80 if State
.recent_mouse
.time
and State
.recent_mouse
.time
> time
-0.1 then
81 return State
.recent_mouse
.line
, State
.recent_mouse
.pos
83 State
.recent_mouse
.time
= time
84 local line
,pos
= Text
.to_pos(State
, App
.mouse_x(), App
.mouse_y())
86 State
.recent_mouse
.line
= line
87 State
.recent_mouse
.pos
= pos
89 return State
.recent_mouse
.line
, State
.recent_mouse
.pos
92 function Text
.to_pos(State
, x
,y
)
93 for line_index
,line
in ipairs(State
.lines
) do
94 if line
.mode
== 'text' then
95 if Text
.in_line(State
, line_index
, x
,y
) then
96 return line_index
, Text
.to_pos_on_line(State
, line_index
, x
,y
)
102 function Text
.cut_selection(State
)
103 if State
.selection1
.line
== nil then return end
104 local result
= Text
.selection(State
)
105 Text
.delete_selection(State
)
109 function Text
.delete_selection(State
)
110 if State
.selection1
.line
== nil then return end
111 local minl
,maxl
= minmax(State
.selection1
.line
, State
.cursor1
.line
)
112 local before
= snapshot(State
, minl
, maxl
)
113 Text
.delete_selection_without_undo(State
)
114 record_undo_event(State
, {before
=before
, after
=snapshot(State
, State
.cursor1
.line
)})
117 function Text
.delete_selection_without_undo(State
)
118 if State
.selection1
.line
== nil then return end
119 -- min,max = sorted(State.selection1,State.cursor1)
120 local minl
,minp
= State
.selection1
.line
,State
.selection1
.pos
121 local maxl
,maxp
= State
.cursor1
.line
,State
.cursor1
.pos
123 minl
,maxl
= maxl
,minl
124 minp
,maxp
= maxp
,minp
125 elseif minl
== maxl
then
127 minp
,maxp
= maxp
,minp
130 -- update State.cursor1 and State.selection1
131 State
.cursor1
.line
= minl
132 State
.cursor1
.pos
= minp
133 if Text
.lt1(State
.cursor1
, State
.screen_top1
) then
134 State
.screen_top1
.line
= State
.cursor1
.line
135 State
.screen_top1
.pos
= Text
.pos_at_start_of_screen_line(State
, State
.cursor1
)
137 State
.selection1
= {}
138 -- delete everything between min (inclusive) and max (exclusive)
139 Text
.clear_screen_line_cache(State
, minl
)
140 local min_offset
= Text
.offset(State
.lines
[minl
].data
, minp
)
141 local max_offset
= Text
.offset(State
.lines
[maxl
].data
, maxp
)
143 --? print('minl == maxl')
144 State
.lines
[minl
].data
= State
.lines
[minl
].data
:sub(1, min_offset
-1)..State
.lines
[minl
].data
:sub(max_offset
)
148 local rhs
= State
.lines
[maxl
].data
:sub(max_offset
)
149 for i
=maxl
,minl
+1,-1 do
150 table.remove(State
.lines
, i
)
151 table.remove(State
.line_cache
, i
)
153 State
.lines
[minl
].data
= State
.lines
[minl
].data
:sub(1, min_offset
-1)..rhs
156 function Text
.selection(State
)
157 if State
.selection1
.line
== nil then return end
158 -- min,max = sorted(State.selection1,State.cursor1)
159 local minl
,minp
= State
.selection1
.line
,State
.selection1
.pos
160 local maxl
,maxp
= State
.cursor1
.line
,State
.cursor1
.pos
162 minl
,maxl
= maxl
,minl
163 minp
,maxp
= maxp
,minp
164 elseif minl
== maxl
then
166 minp
,maxp
= maxp
,minp
169 local min_offset
= Text
.offset(State
.lines
[minl
].data
, minp
)
170 local max_offset
= Text
.offset(State
.lines
[maxl
].data
, maxp
)
172 return State
.lines
[minl
].data
:sub(min_offset
, max_offset
-1)
175 local result
= {State
.lines
[minl
].data
:sub(min_offset
)}
176 for i
=minl
+1,maxl
-1 do
177 if State
.lines
[i
].mode
== 'text' then
178 table.insert(result
, State
.lines
[i
].data
)
181 table.insert(result
, State
.lines
[maxl
].data
:sub(1, max_offset
-1))
182 return table.concat(result
, '\n')