speculatively recommend new LÖVE v11.5 in all forks
[view.love.git] / search.lua
blob67b82b371de1c4b0300a0801a3d403b64ee1e4dc
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)
14 App.color(Text_color)
15 App.screen.print(State.search_term, 25,y-5)
16 Text.draw_cursor(State, 25+App.width(State.search_term),y-5)
17 end
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)
25 if offset then
26 State.cursor1.pos = utf8.len(curr_line, 1, offset)
27 end
28 if offset == nil then
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)
33 if offset then
34 State.cursor1 = {line=i, pos=utf8.len(curr_line, 1, offset)}
35 break
36 end
37 end
38 end
39 if offset == nil then
40 -- wrap around
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)
44 if offset then
45 State.cursor1 = {line=i, pos=utf8.len(curr_line, 1, offset)}
46 break
47 end
48 end
49 end
50 if offset == nil then
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
57 end
58 end
59 if offset == nil then
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
64 end
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
69 end
70 end
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)
78 if offset then
79 State.cursor1.pos = utf8.len(curr_line, 1, offset)
80 end
81 if offset == nil then
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)
86 if offset then
87 State.cursor1 = {line=i, pos=utf8.len(curr_line, 1, offset)}
88 break
89 end
90 end
91 end
92 if offset == nil then
93 -- wrap around
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)
97 if offset then
98 State.cursor1 = {line=i, pos=utf8.len(curr_line, 1, offset)}
99 break
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))
143 return endpos-#pat+1
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')