File src/filetype.c changed (mode: 100644) (index 98f0661cb..13d6bf38b) |
28 |
28 |
#include "compat/fs_limits.h" |
#include "compat/fs_limits.h" |
29 |
29 |
#include "compat/reallocarray.h" |
#include "compat/reallocarray.h" |
30 |
30 |
#include "modes/dialogs/msg_dialog.h" |
#include "modes/dialogs/msg_dialog.h" |
|
31 |
|
#include "utils/darray.h" |
31 |
32 |
#include "utils/matchers.h" |
#include "utils/matchers.h" |
|
33 |
|
#include "utils/mem.h" |
32 |
34 |
#include "utils/str.h" |
#include "utils/str.h" |
33 |
35 |
#include "utils/string_array.h" |
#include "utils/string_array.h" |
34 |
36 |
#include "utils/path.h" |
#include "utils/path.h" |
35 |
37 |
#include "utils/utils.h" |
#include "utils/utils.h" |
36 |
38 |
|
|
|
39 |
|
/* |
|
40 |
|
* Temporary state used while reordering viewers. |
|
41 |
|
* |
|
42 |
|
* We're always collecting a prefix because regardless whether the pivot is |
|
43 |
|
* moved to the top or the bottom of a matching subset, we group the subset |
|
44 |
|
* around bottom of the list of viewers where catch-all viewers are likely to |
|
45 |
|
* appear. Moving items down rather than up isolates the subset more from the |
|
46 |
|
* rest of the viewers and does not mess up matching by putting a catch-all |
|
47 |
|
* viewer above entries which did not participate in a rotation. |
|
48 |
|
*/ |
|
49 |
|
typedef struct |
|
50 |
|
{ |
|
51 |
|
assoc_t *list; /* Storage for the new list. */ |
|
52 |
|
|
|
53 |
|
/* Matching associations that precede the pivot in fileviewers. */ |
|
54 |
|
assoc_t *prefix; |
|
55 |
|
DA_INSTANCE_FIELD(prefix); |
|
56 |
|
|
|
57 |
|
int i; /* Index of an association in fileviewers used as a "pivot". */ |
|
58 |
|
int j; /* Number of associations copied to .list array. */ |
|
59 |
|
int k; /* Matching record number within fileviewers.list[i].records[]. */ |
|
60 |
|
} |
|
61 |
|
reordering_data_t; |
|
62 |
|
|
37 |
63 |
static const char * find_existing_cmd(const assoc_list_t *record_list, |
static const char * find_existing_cmd(const assoc_list_t *record_list, |
38 |
64 |
const char file[]); |
const char file[]); |
39 |
65 |
static assoc_record_t find_existing_cmd_record(const assoc_records_t *records); |
static assoc_record_t find_existing_cmd_record(const assoc_records_t *records); |
|
66 |
|
static void make_pivot_first(reordering_data_t *d); |
|
67 |
|
static int pick_out_until_first(const char file[], const char viewer[], |
|
68 |
|
reordering_data_t *d); |
|
69 |
|
static int split_assoc(assoc_t *assoc, int at_record_idx, assoc_t *out_prefix); |
|
70 |
|
static int mg_clone(const matchers_group_t *from, matchers_group_t *to); |
40 |
71 |
static assoc_records_t parse_command_list(const char cmds[]); |
static assoc_records_t parse_command_list(const char cmds[]); |
41 |
72 |
static assoc_records_t clone_all_matching_records(const char file[], |
static assoc_records_t clone_all_matching_records(const char file[], |
42 |
73 |
const assoc_list_t *record_list); |
const assoc_list_t *record_list); |
|
... |
... |
find_existing_cmd_record(const assoc_records_t *records) |
185 |
216 |
return empty_record; |
return empty_record; |
186 |
217 |
} |
} |
187 |
218 |
|
|
|
219 |
|
void |
|
220 |
|
ft_move_viewer_to_top(const char file[], const char viewer[]) |
|
221 |
|
{ |
|
222 |
|
reordering_data_t d = {}; |
|
223 |
|
|
|
224 |
|
/* |
|
225 |
|
* Overall approach: |
|
226 |
|
* 1. Find an entry with a matching program record. |
|
227 |
|
* 2. Move it and all predecessors matching file after the last matching |
|
228 |
|
* entry. |
|
229 |
|
* 3. Special case: if the record is not the last one, cut the entry after |
|
230 |
|
* the record and move only this prefix. |
|
231 |
|
*/ |
|
232 |
|
if(pick_out_until_first(file, viewer, &d) == 0) |
|
233 |
|
{ |
|
234 |
|
make_pivot_first(&d); |
|
235 |
|
} |
|
236 |
|
|
|
237 |
|
free(d.list); |
|
238 |
|
DA_REMOVE_ALL(d.prefix); |
|
239 |
|
} |
|
240 |
|
|
|
241 |
|
/* Makes a "pivot" element the first one among subset of matching file viewers. |
|
242 |
|
* This is done by putting the element, the tail of the original list and all |
|
243 |
|
* subset elements in front of the pivot in the right order. */ |
|
244 |
|
static void |
|
245 |
|
make_pivot_first(reordering_data_t *d) |
|
246 |
|
{ |
|
247 |
|
assoc_t split_prefix; |
|
248 |
|
int need_split = (d->k != 0); |
|
249 |
|
if(need_split) |
|
250 |
|
{ |
|
251 |
|
/* The viewer is not the first in the list, so split the association at |
|
252 |
|
* the viewer. The code below ensures the viewer will be at the new top |
|
253 |
|
* for the subset matching this file and then the prefix will be |
|
254 |
|
* appended. */ |
|
255 |
|
if(split_assoc(&fileviewers.list[d->i], d->k, &split_prefix) != 0) |
|
256 |
|
{ |
|
257 |
|
return; |
|
258 |
|
} |
|
259 |
|
} |
|
260 |
|
|
|
261 |
|
mem_cpy(&d->list[d->j], &fileviewers.list[d->i], fileviewers.count - d->i, |
|
262 |
|
sizeof(d->list[0])); |
|
263 |
|
d->j += fileviewers.count - d->i; |
|
264 |
|
|
|
265 |
|
mem_cpy(&d->list[d->j], &d->prefix[0], DA_SIZE(d->prefix), |
|
266 |
|
sizeof(d->list[0])); |
|
267 |
|
d->j += DA_SIZE(d->prefix); |
|
268 |
|
|
|
269 |
|
assert(d->j == fileviewers.count); |
|
270 |
|
|
|
271 |
|
free(fileviewers.list); |
|
272 |
|
fileviewers.list = d->list; |
|
273 |
|
d->list = NULL; |
|
274 |
|
|
|
275 |
|
if(need_split) |
|
276 |
|
{ |
|
277 |
|
fileviewers.list[fileviewers.count++] = split_prefix; |
|
278 |
|
} |
|
279 |
|
} |
|
280 |
|
|
|
281 |
|
/* Finds a matching entry in the list of viewers either by checking for its |
|
282 |
|
* existence (viewer is NULL) or by matching it against a passed in string |
|
283 |
|
* (viewer is not NULL). Collects all entries matching file in d->prefix array |
|
284 |
|
* while walking the list. Returns zero on successfully finding a viewer. */ |
|
285 |
|
static int |
|
286 |
|
pick_out_until_first(const char file[], const char viewer[], |
|
287 |
|
reordering_data_t *d) |
|
288 |
|
{ |
|
289 |
|
/* The new list needs to have room for an extra element in case a matching |
|
290 |
|
* entry needs to be split in two. */ |
|
291 |
|
d->list = malloc((fileviewers.count + 1)*sizeof(*d->list)); |
|
292 |
|
if(d->list == NULL) |
|
293 |
|
{ |
|
294 |
|
return 1; |
|
295 |
|
} |
|
296 |
|
|
|
297 |
|
for(d->i = 0, d->j = 0; d->i < fileviewers.count; ++d->i) |
|
298 |
|
{ |
|
299 |
|
assoc_t *assoc = &fileviewers.list[d->i]; |
|
300 |
|
|
|
301 |
|
if(!mg_match(&assoc->mg, file)) |
|
302 |
|
{ |
|
303 |
|
d->list[d->j++] = *assoc; |
|
304 |
|
continue; |
|
305 |
|
} |
|
306 |
|
|
|
307 |
|
for(d->k = 0; d->k < assoc->records.count; ++d->k) |
|
308 |
|
{ |
|
309 |
|
if(viewer != NULL) |
|
310 |
|
{ |
|
311 |
|
if(strcmp(assoc->records.list[d->k].command, viewer) == 0) |
|
312 |
|
{ |
|
313 |
|
return 0; |
|
314 |
|
} |
|
315 |
|
} |
|
316 |
|
else |
|
317 |
|
{ |
|
318 |
|
if(ft_exists(assoc->records.list[d->k].command)) |
|
319 |
|
{ |
|
320 |
|
return 0; |
|
321 |
|
} |
|
322 |
|
} |
|
323 |
|
} |
|
324 |
|
|
|
325 |
|
assoc_t *another = DA_EXTEND(d->prefix); |
|
326 |
|
if(another == NULL) |
|
327 |
|
{ |
|
328 |
|
return 1; |
|
329 |
|
} |
|
330 |
|
|
|
331 |
|
*another = *assoc; |
|
332 |
|
DA_COMMIT(d->prefix); |
|
333 |
|
} |
|
334 |
|
|
|
335 |
|
/* No matching viewer was found. */ |
|
336 |
|
return 1; |
|
337 |
|
} |
|
338 |
|
|
|
339 |
|
/* Leaves assoc->records[0..at_record_idx] in *assoc and creates *out_prefix |
|
340 |
|
* with assoc->records[at_record_idx+1..]. Returns zero on success. */ |
|
341 |
|
static int |
|
342 |
|
split_assoc(assoc_t *assoc, int at_record_idx, assoc_t *out_prefix) |
|
343 |
|
{ |
|
344 |
|
assoc_t prefix; |
|
345 |
|
if(mg_clone(&assoc->mg, &prefix.mg) != 0) |
|
346 |
|
{ |
|
347 |
|
return 1; |
|
348 |
|
} |
|
349 |
|
|
|
350 |
|
prefix.records.list = |
|
351 |
|
reallocarray(NULL, at_record_idx, sizeof(*prefix.records.list)); |
|
352 |
|
if(prefix.records.list == NULL) |
|
353 |
|
{ |
|
354 |
|
mg_free(&prefix.mg); |
|
355 |
|
return 1; |
|
356 |
|
} |
|
357 |
|
|
|
358 |
|
mem_cpy(prefix.records.list, assoc->records.list, at_record_idx, |
|
359 |
|
sizeof(*prefix.records.list)); |
|
360 |
|
mem_shl(assoc->records.list, assoc->records.count, |
|
361 |
|
sizeof(*assoc->records.list), at_record_idx); |
|
362 |
|
|
|
363 |
|
prefix.records.count = at_record_idx; |
|
364 |
|
assoc->records.count -= at_record_idx; |
|
365 |
|
|
|
366 |
|
*out_prefix = prefix; |
|
367 |
|
return 0; |
|
368 |
|
} |
|
369 |
|
|
|
370 |
|
/* Clones a group of matchers. Returns zero on success. */ |
|
371 |
|
static int |
|
372 |
|
mg_clone(const matchers_group_t *from, matchers_group_t *to) |
|
373 |
|
{ |
|
374 |
|
if(from->count == 0) |
|
375 |
|
{ |
|
376 |
|
to->list = NULL; |
|
377 |
|
to->count = 0; |
|
378 |
|
return 0; |
|
379 |
|
} |
|
380 |
|
|
|
381 |
|
matchers_group_t result = { |
|
382 |
|
.list = reallocarray(NULL, from->count, sizeof(*result.list)), |
|
383 |
|
.count = 0, |
|
384 |
|
}; |
|
385 |
|
if(result.list == NULL) |
|
386 |
|
{ |
|
387 |
|
return 1; |
|
388 |
|
} |
|
389 |
|
|
|
390 |
|
int i; |
|
391 |
|
for(i = 0; i < from->count; ++i, ++result.count) |
|
392 |
|
{ |
|
393 |
|
result.list[i] = matchers_clone(from->list[i]); |
|
394 |
|
if(result.list[i] == NULL) |
|
395 |
|
{ |
|
396 |
|
mg_free(&result); |
|
397 |
|
return 1; |
|
398 |
|
} |
|
399 |
|
} |
|
400 |
|
|
|
401 |
|
*to = result; |
|
402 |
|
return 0; |
|
403 |
|
} |
|
404 |
|
|
188 |
405 |
assoc_records_t |
assoc_records_t |
189 |
406 |
ft_get_all_programs(const char file[]) |
ft_get_all_programs(const char file[]) |
190 |
407 |
{ |
{ |
File src/modes/view.c changed (mode: 100644) (index efa912162..f41ca6a9a) |
... |
... |
static void cmd_A(key_info_t key_info, keys_info_t *keys_info); |
165 |
165 |
static void cmd_F(key_info_t key_info, keys_info_t *keys_info); |
static void cmd_F(key_info_t key_info, keys_info_t *keys_info); |
166 |
166 |
static void cmd_G(key_info_t key_info, keys_info_t *keys_info); |
static void cmd_G(key_info_t key_info, keys_info_t *keys_info); |
167 |
167 |
static void cmd_N(key_info_t key_info, keys_info_t *keys_info); |
static void cmd_N(key_info_t key_info, keys_info_t *keys_info); |
|
168 |
|
static void cmd_P(key_info_t key_info, keys_info_t *keys_info); |
168 |
169 |
static void cmd_R(key_info_t key_info, keys_info_t *keys_info); |
static void cmd_R(key_info_t key_info, keys_info_t *keys_info); |
169 |
170 |
static int load_view_data(modview_info_t *vi, const char action[], |
static int load_view_data(modview_info_t *vi, const char action[], |
170 |
171 |
const char file_to_view[], int silent); |
const char file_to_view[], int silent); |
|
... |
... |
static keys_add_info_t builtin_cmds[] = { |
279 |
280 |
{WK_G, {{&cmd_G}, .descr = "scroll to the end"}}, |
{WK_G, {{&cmd_G}, .descr = "scroll to the end"}}, |
280 |
281 |
{WK_N, {{&cmd_N}, .descr = "go to previous search match"}}, |
{WK_N, {{&cmd_N}, .descr = "go to previous search match"}}, |
281 |
282 |
{WK_Q, {{&cmd_q}, .descr = "leave view mode"}}, |
{WK_Q, {{&cmd_q}, .descr = "leave view mode"}}, |
|
283 |
|
{WK_P, {{&cmd_P}, .descr = "preserve the current viewer choice"}}, |
282 |
284 |
{WK_R, {{&cmd_R}, .descr = "reload view contents"}}, |
{WK_R, {{&cmd_R}, .descr = "reload view contents"}}, |
283 |
285 |
{WK_Z WK_Q, {{&cmd_q}, .descr = "leave view mode"}}, |
{WK_Z WK_Q, {{&cmd_q}, .descr = "leave view mode"}}, |
284 |
286 |
{WK_Z WK_Z, {{&cmd_q}, .descr = "leave view mode"}}, |
{WK_Z WK_Z, {{&cmd_q}, .descr = "leave view mode"}}, |
|
... |
... |
cmd_N(key_info_t key_info, keys_info_t *keys_info) |
1063 |
1065 |
goto_search_result(key_info.count, 1); |
goto_search_result(key_info.count, 1); |
1064 |
1066 |
} |
} |
1065 |
1067 |
|
|
|
1068 |
|
/* Preserves current previewer for the duration of the session. */ |
|
1069 |
|
static void |
|
1070 |
|
cmd_P(key_info_t key_info, keys_info_t *keys_info) |
|
1071 |
|
{ |
|
1072 |
|
if(vi->curr_viewer == vi->ext_viewer) |
|
1073 |
|
{ |
|
1074 |
|
display_error("Can't persist an external viewer."); |
|
1075 |
|
return; |
|
1076 |
|
} |
|
1077 |
|
|
|
1078 |
|
if(vi->raw) |
|
1079 |
|
{ |
|
1080 |
|
display_error("No viewer to persist, raw previewing is active."); |
|
1081 |
|
return; |
|
1082 |
|
} |
|
1083 |
|
|
|
1084 |
|
ft_move_viewer_to_top(vi->filename, vi->curr_viewer); |
|
1085 |
|
} |
|
1086 |
|
|
1066 |
1087 |
/* Handles view data reloading key. */ |
/* Handles view data reloading key. */ |
1067 |
1088 |
static void |
static void |
1068 |
1089 |
cmd_R(key_info_t key_info, keys_info_t *keys_info) |
cmd_R(key_info_t key_info, keys_info_t *keys_info) |
File tests/misc/view_mode.c changed (mode: 100644) (index 7d6b62f08..4516e5877) |
... |
... |
TEST(switching_between_viewers) |
127 |
127 |
(void)vle_keys_exec_timed_out(WK_A); |
(void)vle_keys_exec_timed_out(WK_A); |
128 |
128 |
assert_string_equal("echo 3", modview_current_viewer(lwin.vi)); |
assert_string_equal("echo 3", modview_current_viewer(lwin.vi)); |
129 |
129 |
|
|
|
130 |
|
/* Switching viewers is local to the view mode. */ |
|
131 |
|
assert_string_equal("echo 1", ft_get_viewer("read")); |
|
132 |
|
|
130 |
133 |
(void)vle_keys_exec_timed_out(WK_i); |
(void)vle_keys_exec_timed_out(WK_i); |
131 |
134 |
assert_true(modview_is_raw(lwin.vi)); |
assert_true(modview_is_raw(lwin.vi)); |
132 |
135 |
(void)vle_keys_exec_timed_out(WK_a); |
(void)vle_keys_exec_timed_out(WK_a); |
|
... |
... |
TEST(switching_between_viewers) |
135 |
138 |
assert_string_equal("echo 3", modview_current_viewer(lwin.vi)); |
assert_string_equal("echo 3", modview_current_viewer(lwin.vi)); |
136 |
139 |
} |
} |
137 |
140 |
|
|
|
141 |
|
TEST(no_persisting_for_external_viewer) |
|
142 |
|
{ |
|
143 |
|
opt_handlers_setup(); |
|
144 |
|
curr_stats.number_of_windows = 2; |
|
145 |
|
make_abs_path(lwin.curr_dir, sizeof(lwin.curr_dir), TEST_DATA_PATH, "read", |
|
146 |
|
NULL); |
|
147 |
|
populate_dir_list(&lwin, 0); |
|
148 |
|
|
|
149 |
|
int save_msg; |
|
150 |
|
rn_ext(&lwin, "echo 1", "title", MF_PREVIEW_OUTPUT | MF_NO_CACHE, /*pause=*/0, |
|
151 |
|
/*bg=*/0, &save_msg); |
|
152 |
|
(void)vle_keys_exec_timed_out(WK_C_w WK_w); |
|
153 |
|
assert_true(vle_mode_is(VIEW_MODE)); |
|
154 |
|
|
|
155 |
|
ui_sb_msg(""); |
|
156 |
|
(void)vle_keys_exec_timed_out(WK_P); |
|
157 |
|
assert_string_equal("Can't persist an external viewer.", ui_sb_last()); |
|
158 |
|
|
|
159 |
|
qv_hide(); |
|
160 |
|
opt_handlers_teardown(); |
|
161 |
|
} |
|
162 |
|
|
|
163 |
|
TEST(persisting_a_viewer_choice) |
|
164 |
|
{ |
|
165 |
|
assert_true(start_view_mode("bin*", "echo 1, echo 2, echo 3", TEST_DATA_PATH, |
|
166 |
|
"read")); |
|
167 |
|
assert_string_equal("echo 1", ft_get_viewer("binary-data")); |
|
168 |
|
|
|
169 |
|
ui_sb_msg(""); |
|
170 |
|
(void)vle_keys_exec_timed_out(WK_i); |
|
171 |
|
(void)vle_keys_exec_timed_out(WK_P); |
|
172 |
|
assert_string_equal("No viewer to persist, raw previewing is active.", |
|
173 |
|
ui_sb_last()); |
|
174 |
|
(void)vle_keys_exec_timed_out(WK_i); |
|
175 |
|
|
|
176 |
|
(void)vle_keys_exec_timed_out(WK_a); |
|
177 |
|
assert_string_equal("echo 2", modview_current_viewer(lwin.vi)); |
|
178 |
|
(void)vle_keys_exec_timed_out(WK_P); |
|
179 |
|
assert_string_equal("echo 2", ft_get_viewer("binary-data")); |
|
180 |
|
} |
|
181 |
|
|
138 |
182 |
TEST(directories_are_matched_separately) |
TEST(directories_are_matched_separately) |
139 |
183 |
{ |
{ |
140 |
184 |
assert_true(start_view_mode("*[^/]", "echo 1, echo 2, echo 3", TEST_DATA_PATH, |
assert_true(start_view_mode("*[^/]", "echo 1, echo 2, echo 3", TEST_DATA_PATH, |