1 -- helpers for selecting portions of text
3 -- Return any intersection of the region from State.selection1 to
4 -- State.cursor1 (or current mouse, if mouse is pressed) with the region
5 -- between {line=line_index, pos=apos} and {line=line_index, pos=bpos}.
6 -- apos must be less than bpos. However State.selection1 and State.cursor1 can be in any order.
7 -- Result: positions spos,epos between apos,bpos.
8 function Text
.clip_selection(State
, line_index
, apos
, bpos
)
9 if State
.selection1
.line
== nil then return nil,nil end
10 -- min,max = sorted(State.selection1,State.cursor1)
11 local minl
,minp
= State
.selection1
.line
,State
.selection1
.pos
13 if State
.mouse_down
then
14 maxl
,maxp
= Text
.mouse_pos(State
)
16 maxl
,maxp
= State
.cursor1
.line
,State
.cursor1
.pos
18 if Text
.lt1({line
=maxl
, pos
=maxp
},
19 {line
=minl
, pos
=minp
}) then
23 -- check if intervals are disjoint
24 if line_index
< minl
then return nil,nil end
25 if line_index
> maxl
then return nil,nil end
26 if line_index
== minl
and bpos
<= minp
then return nil,nil end
27 if line_index
== maxl
and apos
>= maxp
then return nil,nil end
28 -- compare bounds more carefully (start inclusive, end exclusive)
29 local a_ge
= Text
.le1({line
=minl
, pos
=minp
}, {line
=line_index
, pos
=apos
})
30 local b_lt
= Text
.lt1({line
=line_index
, pos
=bpos
}, {line
=maxl
, pos
=maxp
})
35 assert(maxl
== line_index
, ('maxl %d not equal to line_index %d'):format(maxl
, line_index
))
38 assert(minl
== line_index
, ('minl %d not equal to line_index %d'):format(minl
, line_index
))
41 assert(minl
== maxl
and minl
== line_index
, ('minl %d, maxl %d and line_index %d are not all equal'):format(minl
, maxl
, line_index
))
46 -- draw highlight for line corresponding to (lo,hi) given an approximate x,y and pos on the same screen line
47 -- Returns some intermediate computation useful elsewhere.
48 function Text
.draw_highlight(State
, line
, x
,y
, pos
, lo
,hi
)
50 local lo_offset
= Text
.offset(line
.data
, lo
)
51 local hi_offset
= Text
.offset(line
.data
, hi
)
52 local pos_offset
= Text
.offset(line
.data
, pos
)
57 local before
= line
.data
:sub(pos_offset
, lo_offset
-1)
58 lo_px
= State
.font
:getWidth(before
)
60 local s
= line
.data
:sub(lo_offset
, hi_offset
-1)
61 App
.color(Highlight_color
)
62 love
.graphics
.rectangle('fill', x
+lo_px
,y
, State
.font
:getWidth(s
),State
.line_height
)
68 function Text
.mouse_pos(State
)
69 local x
,y
= App
.mouse_x(), App
.mouse_y()
71 return State
.screen_top1
.line
, State
.screen_top1
.pos
73 for line_index
,line
in ipairs(State
.lines
) do
74 if line
.mode
== 'text' then
75 if Text
.in_line(State
, line_index
, x
,y
) then
76 return line_index
, Text
.to_pos_on_line(State
, line_index
, x
,y
)
80 local screen_bottom1
= Text
.screen_bottom1(State
)
81 return screen_bottom1
.line
, Text
.pos_at_end_of_screen_line(State
, screen_bottom1
)
84 function Text
.cut_selection_and_record_undo_event(State
)
85 if State
.selection1
.line
== nil then return end
86 local result
= Text
.selection(State
)
87 Text
.delete_selection_and_record_undo_event(State
)
91 function Text
.delete_selection_and_record_undo_event(State
)
92 if State
.selection1
.line
== nil then return end
93 local minl
,maxl
= minmax(State
.selection1
.line
, State
.cursor1
.line
)
94 local before
= snapshot(State
, minl
, maxl
)
95 Text
.delete_selection_without_undo(State
)
96 record_undo_event(State
, {before
=before
, after
=snapshot(State
, State
.cursor1
.line
)})
99 function Text
.delete_selection_without_undo(State
)
100 if State
.selection1
.line
== nil then return end
101 -- min,max = sorted(State.selection1,State.cursor1)
102 local minl
,minp
= State
.selection1
.line
,State
.selection1
.pos
103 local maxl
,maxp
= State
.cursor1
.line
,State
.cursor1
.pos
105 minl
,maxl
= maxl
,minl
106 minp
,maxp
= maxp
,minp
107 elseif minl
== maxl
then
109 minp
,maxp
= maxp
,minp
112 -- update State.cursor1 and State.selection1
113 State
.cursor1
.line
= minl
114 State
.cursor1
.pos
= minp
115 if Text
.lt1(State
.cursor1
, State
.screen_top1
) then
116 State
.screen_top1
.line
= State
.cursor1
.line
117 State
.screen_top1
.pos
= Text
.pos_at_start_of_screen_line(State
, State
.cursor1
)
119 State
.selection1
= {}
120 -- delete everything between min (inclusive) and max (exclusive)
121 Text
.clear_screen_line_cache(State
, minl
)
122 local min_offset
= Text
.offset(State
.lines
[minl
].data
, minp
)
123 local max_offset
= Text
.offset(State
.lines
[maxl
].data
, maxp
)
125 --? print('minl == maxl')
126 State
.lines
[minl
].data
= State
.lines
[minl
].data
:sub(1, min_offset
-1)..State
.lines
[minl
].data
:sub(max_offset
)
129 assert(minl
< maxl
, ('minl %d not < maxl %d'):format(minl
, maxl
))
130 local rhs
= State
.lines
[maxl
].data
:sub(max_offset
)
131 for i
=maxl
,minl
+1,-1 do
132 table.remove(State
.lines
, i
)
133 table.remove(State
.line_cache
, i
)
135 State
.lines
[minl
].data
= State
.lines
[minl
].data
:sub(1, min_offset
-1)..rhs
138 function Text
.selection(State
)
139 if State
.selection1
.line
== nil then return end
140 -- min,max = sorted(State.selection1,State.cursor1)
141 local minl
,minp
= State
.selection1
.line
,State
.selection1
.pos
142 local maxl
,maxp
= State
.cursor1
.line
,State
.cursor1
.pos
144 minl
,maxl
= maxl
,minl
145 minp
,maxp
= maxp
,minp
146 elseif minl
== maxl
then
148 minp
,maxp
= maxp
,minp
151 local min_offset
= Text
.offset(State
.lines
[minl
].data
, minp
)
152 local max_offset
= Text
.offset(State
.lines
[maxl
].data
, maxp
)
154 return State
.lines
[minl
].data
:sub(min_offset
, max_offset
-1)
156 assert(minl
< maxl
, ('minl %d not < maxl %d'):format(minl
, maxl
))
157 local result
= {State
.lines
[minl
].data
:sub(min_offset
)}
158 for i
=minl
+1,maxl
-1 do
159 if State
.lines
[i
].mode
== 'text' then
160 table.insert(result
, State
.lines
[i
].data
)
163 table.insert(result
, State
.lines
[maxl
].data
:sub(1, max_offset
-1))
164 return table.concat(result
, '\n')