1 -- helpers for the search bar (C-f)
3 function Text
.draw_search_bar(State
)
4 local h
= State
.line_height
+2
5 local y
= App
.screen
.height
-h
6 love
.graphics
.setColor(0.9,0.9,0.9)
7 love
.graphics
.rectangle('fill', 0, y
-10, App
.screen
.width
-1, h
+8)
8 love
.graphics
.setColor(0.6,0.6,0.6)
9 love
.graphics
.line(0, y
-10, App
.screen
.width
-1, y
-10)
10 love
.graphics
.setColor(1,1,1)
11 love
.graphics
.rectangle('fill', 20, y
-6, App
.screen
.width
-40, h
+2, 2,2)
12 love
.graphics
.setColor(0.6,0.6,0.6)
13 love
.graphics
.rectangle('line', 20, y
-6, App
.screen
.width
-40, h
+2, 2,2)
15 App
.screen
.print(State
.search_term
, 25,y
-5)
16 Text
.draw_cursor(State
, 25+State
.font
:getWidth(State
.search_term
),y
-5)
19 function Text
.search_next(State
)
20 assert(#State
.search_term
> 0)
21 -- search current line from cursor
22 local curr_pos
= State
.cursor1
.pos
23 local curr_line
= State
.lines
[State
.cursor1
.line
].data
24 local curr_offset
= Text
.offset(curr_line
, curr_pos
)
25 local offset
= find(curr_line
, State
.search_term
, curr_offset
, --[[literal]] true)
27 State
.cursor1
.pos
= utf8
.len(curr_line
, 1, offset
)
30 -- search lines below cursor
31 for i
=State
.cursor1
.line
+1,#State
.lines
do
32 local curr_line
= State
.lines
[i
].data
33 offset
= find(curr_line
, State
.search_term
, --[[from start]] nil, --[[literal]] true)
35 State
.cursor1
= {line
=i
, pos
=utf8
.len(curr_line
, 1, offset
)}
42 for i
=1,State
.cursor1
.line
-1 do
43 local curr_line
= State
.lines
[i
].data
44 offset
= find(curr_line
, State
.search_term
, --[[from start]] nil, --[[literal]] true)
46 State
.cursor1
= {line
=i
, pos
=utf8
.len(curr_line
, 1, offset
)}
52 -- search current line until cursor
53 local curr_line
= State
.lines
[State
.cursor1
.line
].data
54 offset
= find(curr_line
, State
.search_term
, --[[from start]] nil, --[[literal]] true)
55 local pos
= utf8
.len(curr_line
, 1, offset
)
56 if pos
and pos
< State
.cursor1
.pos
then
57 State
.cursor1
.pos
= pos
61 State
.cursor1
.line
= State
.search_backup
.cursor
.line
62 State
.cursor1
.pos
= State
.search_backup
.cursor
.pos
63 State
.screen_top1
.line
= State
.search_backup
.screen_top
.line
64 State
.screen_top1
.pos
= State
.search_backup
.screen_top
.pos
66 local screen_bottom1
= Text
.screen_bottom1(State
)
67 if Text
.lt1(State
.cursor1
, State
.screen_top1
) or Text
.lt1(screen_bottom1
, State
.cursor1
) then
68 State
.screen_top1
.line
= State
.cursor1
.line
69 local pos
= Text
.pos_at_start_of_screen_line(State
, State
.cursor1
)
70 State
.screen_top1
.pos
= pos
74 function Text
.search_previous(State
)
75 assert(#State
.search_term
> 0)
76 -- search current line before cursor
77 local curr_pos
= State
.cursor1
.pos
78 local curr_line
= State
.lines
[State
.cursor1
.line
].data
79 local curr_offset
= Text
.offset(curr_line
, curr_pos
)
80 local offset
= rfind(curr_line
, State
.search_term
, curr_offset
-1, --[[literal]] true)
82 State
.cursor1
.pos
= utf8
.len(curr_line
, 1, offset
)
85 -- search lines above cursor
86 for i
=State
.cursor1
.line
-1,1,-1 do
87 local curr_line
= State
.lines
[i
].data
88 offset
= rfind(curr_line
, State
.search_term
, --[[from end]] nil, --[[literal]] true)
90 State
.cursor1
= {line
=i
, pos
=utf8
.len(curr_line
, 1, offset
)}
97 for i
=#State
.lines
,State
.cursor1
.line
+1,-1 do
98 local curr_line
= State
.lines
[i
].data
99 offset
= rfind(curr_line
, State
.search_term
, --[[from end]] nil, --[[literal]] true)
101 State
.cursor1
= {line
=i
, pos
=utf8
.len(curr_line
, 1, offset
)}
106 if offset
== nil then
107 -- search current line after cursor
108 local curr_line
= State
.lines
[State
.cursor1
.line
].data
109 offset
= rfind(curr_line
, State
.search_term
, --[[from end]] nil, --[[literal]] true)
110 local pos
= utf8
.len(curr_line
, 1, offset
)
111 if pos
and pos
> State
.cursor1
.pos
then
112 State
.cursor1
.pos
= pos
115 if offset
== nil then
116 State
.cursor1
.line
= State
.search_backup
.cursor
.line
117 State
.cursor1
.pos
= State
.search_backup
.cursor
.pos
118 State
.screen_top1
.line
= State
.search_backup
.screen_top
.line
119 State
.screen_top1
.pos
= State
.search_backup
.screen_top
.pos
121 local screen_bottom1
= Text
.screen_bottom1(State
)
122 if Text
.lt1(State
.cursor1
, State
.screen_top1
) or Text
.lt1(screen_bottom1
, State
.cursor1
) then
123 State
.screen_top1
.line
= State
.cursor1
.line
124 local pos
= Text
.pos_at_start_of_screen_line(State
, State
.cursor1
)
125 State
.screen_top1
.pos
= pos
129 function find(s
, pat
, i
, plain
)
130 if s
== nil then return end
131 return s
:find(pat
, i
, plain
)
134 -- TODO: avoid the expensive reverse() operations
135 -- Particularly if we only care about literal matches, we don't need all of string.find
136 function rfind(s
, pat
, i
, plain
)
137 if s
== nil then return end
138 if #pat
== 0 then return #s
end
139 local rs
= s
:reverse()
140 local rpat
= pat
:reverse()
141 if i
== nil then i
= #s
end
142 local ri
= #s
- i
+ 1
143 local rendpos
= rs
:find(rpat
, ri
, plain
)
144 if rendpos
== nil then return nil end
145 local endpos
= #s
- rendpos
+ 1
146 assert (endpos
>= #pat
, ('rfind: endpos %d should be >= #pat %d at this point'):format(endpos
, #pat
))
150 function test_rfind()
151 check_eq(rfind('abc', ''), 3, 'empty pattern')
152 check_eq(rfind('abc', 'c'), 3, 'final char')
153 check_eq(rfind('acbc', 'c', 3), 2, 'previous char')
154 check_nil(rfind('abc', 'd'), 'missing char')
155 check_nil(rfind('abc', 'c', 2), 'no more char')