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 -- search current line from cursor
21 local curr_pos
= State
.cursor1
.pos
22 local curr_line
= State
.lines
[State
.cursor1
.line
].data
23 local curr_offset
= Text
.offset(curr_line
, curr_pos
)
24 local offset
= find(curr_line
, State
.search_term
, curr_offset
, --[[literal]] true)
26 State
.cursor1
.pos
= utf8
.len(curr_line
, 1, offset
)
29 -- search lines below cursor
30 for i
=State
.cursor1
.line
+1,#State
.lines
do
31 local curr_line
= State
.lines
[i
].data
32 offset
= find(curr_line
, State
.search_term
, --[[from start]] nil, --[[literal]] true)
34 State
.cursor1
= {line
=i
, pos
=utf8
.len(curr_line
, 1, offset
)}
41 for i
=1,State
.cursor1
.line
-1 do
42 local curr_line
= State
.lines
[i
].data
43 offset
= find(curr_line
, State
.search_term
, --[[from start]] nil, --[[literal]] true)
45 State
.cursor1
= {line
=i
, pos
=utf8
.len(curr_line
, 1, offset
)}
51 -- search current line until cursor
52 local curr_line
= State
.lines
[State
.cursor1
.line
].data
53 offset
= find(curr_line
, State
.search_term
, --[[from start]] nil, --[[literal]] true)
54 local pos
= utf8
.len(curr_line
, 1, offset
)
55 if pos
and pos
< State
.cursor1
.pos
then
56 State
.cursor1
.pos
= pos
60 State
.cursor1
.line
= State
.search_backup
.cursor
.line
61 State
.cursor1
.pos
= State
.search_backup
.cursor
.pos
62 State
.screen_top1
.line
= State
.search_backup
.screen_top
.line
63 State
.screen_top1
.pos
= State
.search_backup
.screen_top
.pos
65 local screen_bottom1
= Text
.screen_bottom1(State
)
66 if Text
.lt1(State
.cursor1
, State
.screen_top1
) or Text
.lt1(screen_bottom1
, State
.cursor1
) then
67 State
.screen_top1
.line
= State
.cursor1
.line
68 local pos
= Text
.pos_at_start_of_screen_line(State
, State
.cursor1
)
69 State
.screen_top1
.pos
= pos
73 function Text
.search_previous(State
)
74 -- search current line before cursor
75 local curr_pos
= State
.cursor1
.pos
76 local curr_line
= State
.lines
[State
.cursor1
.line
].data
77 local curr_offset
= Text
.offset(curr_line
, curr_pos
)
78 local offset
= rfind(curr_line
, State
.search_term
, curr_offset
-1, --[[literal]] true)
80 State
.cursor1
.pos
= utf8
.len(curr_line
, 1, offset
)
83 -- search lines above cursor
84 for i
=State
.cursor1
.line
-1,1,-1 do
85 local curr_line
= State
.lines
[i
].data
86 offset
= rfind(curr_line
, State
.search_term
, --[[from end]] nil, --[[literal]] true)
88 State
.cursor1
= {line
=i
, pos
=utf8
.len(curr_line
, 1, offset
)}
95 for i
=#State
.lines
,State
.cursor1
.line
+1,-1 do
96 local curr_line
= State
.lines
[i
].data
97 offset
= rfind(curr_line
, State
.search_term
, --[[from end]] nil, --[[literal]] true)
99 State
.cursor1
= {line
=i
, pos
=utf8
.len(curr_line
, 1, offset
)}
104 if offset
== nil then
105 -- search current line after cursor
106 local curr_line
= State
.lines
[State
.cursor1
.line
].data
107 offset
= rfind(curr_line
, State
.search_term
, --[[from end]] nil, --[[literal]] true)
108 local pos
= utf8
.len(curr_line
, 1, offset
)
109 if pos
and pos
> State
.cursor1
.pos
then
110 State
.cursor1
.pos
= pos
113 if offset
== nil then
114 State
.cursor1
.line
= State
.search_backup
.cursor
.line
115 State
.cursor1
.pos
= State
.search_backup
.cursor
.pos
116 State
.screen_top1
.line
= State
.search_backup
.screen_top
.line
117 State
.screen_top1
.pos
= State
.search_backup
.screen_top
.pos
119 local screen_bottom1
= Text
.screen_bottom1(State
)
120 if Text
.lt1(State
.cursor1
, State
.screen_top1
) or Text
.lt1(screen_bottom1
, State
.cursor1
) then
121 State
.screen_top1
.line
= State
.cursor1
.line
122 local pos
= Text
.pos_at_start_of_screen_line(State
, State
.cursor1
)
123 State
.screen_top1
.pos
= pos
127 function find(s
, pat
, i
, plain
)
128 if s
== nil then return end
129 return s
:find(pat
, i
, plain
)
132 -- TODO: avoid the expensive reverse() operations
133 -- Particularly if we only care about literal matches, we don't need all of string.find
134 function rfind(s
, pat
, i
, plain
)
135 if s
== nil then return end
136 if #pat
== 0 then return #s
end
137 local rs
= s
:reverse()
138 local rpat
= pat
:reverse()
139 if i
== nil then i
= #s
end
140 local ri
= #s
- i
+ 1
141 local rendpos
= rs
:find(rpat
, ri
, plain
)
142 if rendpos
== nil then return nil end
143 local endpos
= #s
- rendpos
+ 1
144 assert (endpos
>= #pat
, ('rfind: endpos %d should be >= #pat %d at this point'):format(endpos
, #pat
))
148 function test_rfind()
149 check_eq(rfind('abc', ''), 3, 'empty pattern')
150 check_eq(rfind('abc', 'c'), 3, 'final char')
151 check_eq(rfind('acbc', 'c', 3), 2, 'previous char')
152 check_nil(rfind('abc', 'd'), 'missing char')
153 check_nil(rfind('abc', 'c', 2), 'no more char')