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 if Text
.lt1(State
.cursor1
, State
.screen_top1
) or Text
.lt1(State
.screen_bottom1
, State
.cursor1
) then
66 State
.screen_top1
.line
= State
.cursor1
.line
67 local pos
= Text
.pos_at_start_of_screen_line(State
, State
.cursor1
)
68 State
.screen_top1
.pos
= pos
72 function Text
.search_previous(State
)
73 -- search current line before cursor
74 local curr_pos
= State
.cursor1
.pos
75 local curr_line
= State
.lines
[State
.cursor1
.line
].data
76 local curr_offset
= Text
.offset(curr_line
, curr_pos
)
77 local offset
= rfind(curr_line
, State
.search_term
, curr_offset
-1, --[[literal]] true)
79 State
.cursor1
.pos
= utf8
.len(curr_line
, 1, offset
)
82 -- search lines above cursor
83 for i
=State
.cursor1
.line
-1,1,-1 do
84 local curr_line
= State
.lines
[i
].data
85 offset
= rfind(curr_line
, State
.search_term
, --[[from end]] nil, --[[literal]] true)
87 State
.cursor1
= {line
=i
, pos
=utf8
.len(curr_line
, 1, offset
)}
94 for i
=#State
.lines
,State
.cursor1
.line
+1,-1 do
95 local curr_line
= State
.lines
[i
].data
96 offset
= rfind(curr_line
, State
.search_term
, --[[from end]] nil, --[[literal]] true)
98 State
.cursor1
= {line
=i
, pos
=utf8
.len(curr_line
, 1, offset
)}
103 if offset
== nil then
104 -- search current line after cursor
105 local curr_line
= State
.lines
[State
.cursor1
.line
].data
106 offset
= rfind(curr_line
, State
.search_term
, --[[from end]] nil, --[[literal]] true)
107 local pos
= utf8
.len(curr_line
, 1, offset
)
108 if pos
and pos
> State
.cursor1
.pos
then
109 State
.cursor1
.pos
= pos
112 if offset
== nil then
113 State
.cursor1
.line
= State
.search_backup
.cursor
.line
114 State
.cursor1
.pos
= State
.search_backup
.cursor
.pos
115 State
.screen_top1
.line
= State
.search_backup
.screen_top
.line
116 State
.screen_top1
.pos
= State
.search_backup
.screen_top
.pos
118 if Text
.lt1(State
.cursor1
, State
.screen_top1
) or Text
.lt1(State
.screen_bottom1
, State
.cursor1
) then
119 State
.screen_top1
.line
= State
.cursor1
.line
120 local pos
= Text
.pos_at_start_of_screen_line(State
, State
.cursor1
)
121 State
.screen_top1
.pos
= pos
125 function find(s
, pat
, i
, plain
)
126 if s
== nil then return end
127 return s
:find(pat
, i
, plain
)
130 -- TODO: avoid the expensive reverse() operations
131 -- Particularly if we only care about literal matches, we don't need all of string.find
132 function rfind(s
, pat
, i
, plain
)
133 if s
== nil then return end
134 if #pat
== 0 then return #s
end
135 local rs
= s
:reverse()
136 local rpat
= pat
:reverse()
137 if i
== nil then i
= #s
end
138 local ri
= #s
- i
+ 1
139 local rendpos
= rs
:find(rpat
, ri
, plain
)
140 if rendpos
== nil then return nil end
141 local endpos
= #s
- rendpos
+ 1
142 assert (endpos
>= #pat
, ('rfind: endpos %d should be >= #pat %d at this point'):format(endpos
, #pat
))
146 function test_rfind()
147 check_eq(rfind('abc', ''), 3, 'empty pattern')
148 check_eq(rfind('abc', 'c'), 3, 'final char')
149 check_eq(rfind('acbc', 'c', 3), 2, 'previous char')
150 check_nil(rfind('abc', 'd'), 'missing char')
151 check_nil(rfind('abc', 'c', 2), 'no more char')