xaizek / vifm (License: GPLv2+) (since 2018-12-07)
Vifm is a file manager with curses interface, which provides Vi[m]-like environment for managing objects within file systems, extended with some useful ideas from mutt.
Commit f8b6633bf9566756c6a0caf17bb16b158569b3d9

Deduplicate associations differently
Instead of dropping programs from the list individually, compare lists
of programs and skip adding duplicates.

In practice duplicated pairs of patterns and commands are rare. When
they show up, they are likely to be part of an identical list of
commands. Therefore, we shouldn't be losing anything here. The
deduplication could even be completely dropped, but probably better to
have it in some form.
Author: xaizek
Author date (UTC): 2025-07-11 08:31
Committer name: xaizek
Committer date (UTC): 2025-07-11 08:31
Parent(s): d31c0d16433989b6011196ceff8399ad36b3e909
Signing key: 99DC5E4DB05F6BE2
Tree: 77ef9834993cb98f2944c31a127d3d2003e3ac8b
File Lines added Lines deleted
src/filetype.c 40 32
File src/filetype.c changed (mode: 100644) (index 0aaba1863..968ffd009)
... ... static assoc_records_t parse_command_list(const char cmds[], int with_descr);
41 41 static assoc_records_t clone_all_matching_records(const char file[], static assoc_records_t clone_all_matching_records(const char file[],
42 42 const assoc_list_t *record_list); const assoc_list_t *record_list);
43 43 static int add_assoc(assoc_list_t *assoc_list, assoc_t assoc); static int add_assoc(assoc_list_t *assoc_list, assoc_t assoc);
44 static assoc_records_t clone_assoc_records(const assoc_records_t *records,
45 const char pattern[], const assoc_list_t *dst);
44 static int is_assoc_equal(const assoc_t *a, const assoc_t *b);
46 45 static void reset_all_lists(void); static void reset_all_lists(void);
47 46 static void add_defaults(int in_x); static void add_defaults(int in_x);
48 47 static void reset_list(assoc_list_t *assoc_list); static void reset_list(assoc_list_t *assoc_list);
 
... ... void
194 193 ft_set_programs(matchers_t *matchers, const char programs[], int for_x, ft_set_programs(matchers_t *matchers, const char programs[], int for_x,
195 194 int in_x) int in_x)
196 195 { {
197 assoc_records_t prog_records = parse_command_list(programs, 1);
198
199 196 const assoc_t assoc = { const assoc_t assoc = {
200 197 .matchers = matchers, .matchers = matchers,
201 .records = clone_assoc_records(&prog_records, matchers_get_expr(matchers),
202 for_x ? &xfiletypes : &filetypes),
198 .records = parse_command_list(programs, 1),
203 199 }; };
204 ft_assoc_records_free(&prog_records);
205 200
206 201 /* On error, add_assoc() frees assoc, so just exit then. */ /* On error, add_assoc() frees assoc, so just exit then. */
207 202 if(add_assoc(for_x ? &xfiletypes : &filetypes, assoc) == 0) if(add_assoc(for_x ? &xfiletypes : &filetypes, assoc) == 0)
 
... ... clone_all_matching_records(const char file[], const assoc_list_t *record_list)
278 273 void void
279 274 ft_set_viewers(matchers_t *matchers, const char viewers[]) ft_set_viewers(matchers_t *matchers, const char viewers[])
280 275 { {
281 assoc_records_t view_records = parse_command_list(viewers, 0);
282
283 276 const assoc_t assoc = { const assoc_t assoc = {
284 277 .matchers = matchers, .matchers = matchers,
285 .records = clone_assoc_records(&view_records, matchers_get_expr(matchers),
286 &fileviewers),
278 .records = parse_command_list(viewers, 0),
287 279 }; };
288 ft_assoc_records_free(&view_records);
289
290 280 /* On error, add_assoc() frees assoc, so just exit then. */ /* On error, add_assoc() frees assoc, so just exit then. */
291 281 (void)add_assoc(&fileviewers, assoc); (void)add_assoc(&fileviewers, assoc);
292 282 } }
293 283
294 /* Clones list of association records. Returns the clone. */
295 static assoc_records_t
296 clone_assoc_records(const assoc_records_t *records, const char pattern[],
297 const assoc_list_t *dst)
284 /* Adds association to the list of associations, takes ownership of it
285 * regardless of the outcome. Returns non-zero on out of memory error,
286 * otherwise zero is returned. */
287 static int
288 add_assoc(assoc_list_t *assoc_list, assoc_t assoc)
298 289 { {
299 290 int i; int i;
300 assoc_records_t list = {};
301
302 for(i = 0; i < records->count; ++i)
291 for(i = 0; i < assoc_list->count; ++i)
303 292 { {
304 if(!ft_assoc_exists(dst, pattern, records->list[i].command))
293 if(is_assoc_equal(&assoc_list->list[i], &assoc))
305 294 { {
306 ft_assoc_record_add(&list, records->list[i].command,
307 records->list[i].description);
295 /* Not adding duplicates. */
296 free_assoc(&assoc);
297 return 0;
308 298 } }
309 299 } }
310 300
311 return list;
312 }
313
314 /* Adds association to the list of associations. Returns non-zero on
315 * out of memory error, otherwise zero is returned. Frees resources of assoc
316 * on error. */
317 static int
318 add_assoc(assoc_list_t *assoc_list, assoc_t assoc)
319 {
320 301 void *p; void *p;
321 302 p = reallocarray(assoc_list->list, assoc_list->count + 1, sizeof(assoc_t)); p = reallocarray(assoc_list->list, assoc_list->count + 1, sizeof(assoc_t));
322 303 if(p == NULL) if(p == NULL)
 
... ... add_assoc(assoc_list_t *assoc_list, assoc_t assoc)
332 313 return 0; return 0;
333 314 } }
334 315
316 /* Compares two associations for equality. Returns non-zero if they are equal,
317 * otherwise zero is returned. */
318 static int
319 is_assoc_equal(const assoc_t *a, const assoc_t *b)
320 {
321 if(a->records.count != b->records.count)
322 {
323 return 0;
324 }
325
326 if(strcmp(matchers_get_expr(a->matchers),
327 matchers_get_expr(b->matchers)) != 0)
328 {
329 return 0;
330 }
331
332 int i;
333 for(i = 0; i < a->records.count; ++i)
334 {
335 if(strcmp(a->records.list[i].command, b->records.list[i].command) != 0)
336 {
337 return 0;
338 }
339 }
340 return 1;
341 }
342
335 343 ViewerKind ViewerKind
336 344 ft_viewer_kind(const char viewer[]) ft_viewer_kind(const char viewer[])
337 345 { {
Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/vifm

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/vifm

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master