1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
|
#include <stdlib.h>
#include "search.h"
#include "block-iter.h"
#include "buffer.h"
#include "error.h"
#include "regexp.h"
#include "util/ascii.h"
#include "util/xmalloc.h"
static bool do_search_fwd(View *view, regex_t *regex, BlockIter *bi, bool skip)
{
int flags = block_iter_is_bol(bi) ? 0 : REG_NOTBOL;
do {
if (block_iter_is_eof(bi)) {
return false;
}
regmatch_t match;
StringView line;
fill_line_ref(bi, &line);
// NOTE: If this is the first iteration then line.data contains
// partial line (text starting from the cursor position) and
// if match.rm_so is 0 then match is at beginning of the text
// which is same as the cursor position.
if (regexp_exec(regex, line.data, line.length, 1, &match, flags)) {
if (skip && match.rm_so == 0) {
// Ignore match at current cursor position
regoff_t count = match.rm_eo;
if (count == 0) {
// It is safe to skip one byte because every line
// has one extra byte (newline) that is not in line.data
count = 1;
}
block_iter_skip_bytes(bi, (size_t)count);
return do_search_fwd(view, regex, bi, false);
}
block_iter_skip_bytes(bi, match.rm_so);
view->cursor = *bi;
view->center_on_scroll = true;
view_reset_preferred_x(view);
return true;
}
skip = false; // Not at cursor position any more
flags = 0;
} while (block_iter_next_line(bi));
return false;
}
static bool do_search_bwd(View *view, regex_t *regex, BlockIter *bi, ssize_t cx, bool skip)
{
if (block_iter_is_eof(bi)) {
goto next;
}
do {
regmatch_t match;
StringView line;
int flags = 0;
regoff_t offset = -1;
regoff_t pos = 0;
fill_line_ref(bi, &line);
while (
pos <= line.length
&& regexp_exec(regex, line.data + pos, line.length - pos, 1, &match, flags)
) {
flags = REG_NOTBOL;
if (cx >= 0) {
if (pos + match.rm_so >= cx) {
// Ignore match at or after cursor
break;
}
if (skip && pos + match.rm_eo > cx) {
// Search -rw should not find word under cursor
break;
}
}
// This might be what we want (last match before cursor)
offset = pos + match.rm_so;
pos += match.rm_eo;
if (match.rm_so == match.rm_eo) {
// Zero length match
break;
}
}
if (offset >= 0) {
block_iter_skip_bytes(bi, offset);
view->cursor = *bi;
view->center_on_scroll = true;
view_reset_preferred_x(view);
return true;
}
next:
cx = -1;
} while (block_iter_prev_line(bi));
return false;
}
bool search_tag(View *view, const char *pattern)
{
regex_t regex;
if (!regexp_compile_basic(®ex, pattern, REG_NEWLINE)) {
return false;
}
BlockIter bi = block_iter(view->buffer);
bool found = do_search_fwd(view, ®ex, &bi, false);
regfree(®ex);
if (!found) {
// Don't center view to cursor unnecessarily
view->force_center = false;
return error_msg("Tag not found");
}
view->center_on_scroll = true;
return true;
}
static void free_regex(SearchState *search)
{
if (search->re_flags) {
regfree(&search->regex);
search->re_flags = 0;
}
}
static bool has_upper(const char *str)
{
for (size_t i = 0; str[i]; i++) {
if (ascii_isupper(str[i])) {
return true;
}
}
return false;
}
static bool update_regex(SearchState *search, SearchCaseSensitivity cs)
{
int re_flags = REG_NEWLINE;
switch (cs) {
case CSS_TRUE:
break;
case CSS_FALSE:
re_flags |= REG_ICASE;
break;
case CSS_AUTO:
if (!has_upper(search->pattern)) {
re_flags |= REG_ICASE;
}
break;
default:
BUG("unhandled case sensitivity value");
}
if (re_flags == search->re_flags) {
return true;
}
free_regex(search);
search->re_flags = re_flags;
if (regexp_compile(&search->regex, search->pattern, search->re_flags)) {
return true;
}
free_regex(search);
return false;
}
void search_free_regexp(SearchState *search)
{
free_regex(search);
free(search->pattern);
}
void search_set_regexp(SearchState *search, const char *pattern)
{
search_free_regexp(search);
search->pattern = xstrdup(pattern);
}
static bool do_search_next(View *view, SearchState *search, SearchCaseSensitivity cs, bool skip)
{
if (!search->pattern) {
return error_msg("No previous search pattern");
}
if (!update_regex(search, cs)) {
return false;
}
BlockIter bi = view->cursor;
regex_t *regex = &search->regex;
if (!search->reverse) {
if (do_search_fwd(view, regex, &bi, true)) {
return true;
}
block_iter_bof(&bi);
if (do_search_fwd(view, regex, &bi, false)) {
info_msg("Continuing at top");
return true;
}
} else {
size_t cursor_x = block_iter_bol(&bi);
if (do_search_bwd(view, regex, &bi, cursor_x, skip)) {
return true;
}
block_iter_eof(&bi);
if (do_search_bwd(view, regex, &bi, -1, false)) {
info_msg("Continuing at bottom");
return true;
}
}
return error_msg("Pattern '%s' not found", search->pattern);
}
bool search_prev(View *view, SearchState *search, SearchCaseSensitivity cs)
{
toggle_search_direction(search);
bool r = search_next(view, search, cs);
toggle_search_direction(search);
return r;
}
bool search_next(View *view, SearchState *search, SearchCaseSensitivity cs)
{
return do_search_next(view, search, cs, false);
}
bool search_next_word(View *view, SearchState *search, SearchCaseSensitivity cs)
{
return do_search_next(view, search, cs, true);
}
|