1 /* vifm
2 * Copyright (C) 2001 Ken Steen.
3 * Copyright (C) 2011 xaizek.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
18 */
19
20 #include "fileview.h"
21
22 #include <curses.h>
23
24 #include <regex.h> /* regmatch_t regcomp() regexec() */
25
26 #ifndef _WIN32
27 #include <pwd.h>
28 #include <grp.h>
29 #endif
30
31 #include <assert.h> /* assert() */
32 #include <stddef.h> /* NULL size_t */
33 #include <stdlib.h> /* abs() */
34 #include <string.h> /* memset() strcpy() strlen() */
35
36 #include "../cfg/config.h"
37 #include "../utils/fs.h"
38 #include "../utils/macros.h"
39 #include "../utils/path.h"
40 #include "../utils/regexp.h"
41 #include "../utils/str.h"
42 #include "../utils/test_helpers.h"
43 #include "../utils/utf8.h"
44 #include "../utils/utils.h"
45 #include "../filelist.h"
46 #include "../flist_hist.h"
47 #include "../flist_pos.h"
48 #include "../opt_handlers.h"
49 #include "../sort.h"
50 #include "color_manager.h"
51 #include "color_scheme.h"
52 #include "column_view.h"
53 #include "quickview.h"
54 #include "statusline.h"
55
56 /* Mark for a cursor position of inactive pane. */
57 #define INACTIVE_CURSOR_MARK "*"
58
59 /* Packet set of parameters to pass as user data for processing columns. */
60 typedef struct
61 {
62 view_t *view; /* View on which cell is being drawn. */
63 dir_entry_t *entry; /* Entry that is being displayed. */
64 int line_pos; /* File position in the file list (the view). Can be -1
65 * for filler (entry should still be supplied though). */
66 int line_hi_group; /* Line highlight (to avoid per-column calculation). */
67 int current_pos; /* Position of entry selected with the cursor. */
68 int total_width; /* Total width available for drawing. */
69 int number_width; /* Whether to draw line numbers. */
70
71 size_t current_line; /* Line of the cell within the view window. */
72 size_t column_offset; /* Offset in characters of the column. */
73
74 size_t *prefix_len; /* Data prefix length (should be drawn in neutral color).
75 * A pointer to allow changing value in const struct.
76 * Should be zero first time, then auto reset. */
77 int is_main; /* Whether this is main file list. */
78 }
79 column_data_t;
80
81 static void draw_left_column(view_t *view);
82 static void draw_right_column(view_t *view);
83 static void print_column(view_t *view, entries_t entries, const char current[],
84 const char path[], int width, int offset, int number_width);
85 static void fill_column(view_t *view, int start_line, int top, int width,
86 int offset);
87 static void calculate_table_conf(view_t *view, size_t *count, size_t *width);
88 static int calculate_number_width(const view_t *view, int list_length,
89 int width);
90 static int count_digits(int num);
91 static int calculate_top_position(view_t *view, int top);
92 static int get_line_color(const view_t *view, const dir_entry_t *entry);
93 static size_t calculate_print_width(const view_t *view, int i,
94 size_t max_width);
95 static void draw_cell(columns_t *columns, const column_data_t *cdt,
96 size_t col_width, size_t print_width);
97 static columns_t * get_view_columns(const view_t *view, int truncated);
98 static columns_t * get_name_column(int truncated);
99 static void consider_scroll_bind(view_t *view);
100 static cchar_t prepare_inactive_color(view_t *view, dir_entry_t *entry,
101 int line_color);
102 static void redraw_cell(view_t *view, int top, int cursor, int is_current);
103 static void compute_and_draw_cell(column_data_t *cdt, int cell,
104 size_t col_width);
105 static void column_line_print(const void *data, int column_id, const char buf[],
106 size_t offset, AlignType align, const char full_column[]);
107 static void draw_line_number(const column_data_t *cdt, int column);
108 static void highlight_search(view_t *view, dir_entry_t *entry,
109 const char full_column[], char buf[], size_t buf_len, AlignType align,
110 int line, int col, const cchar_t *line_attrs);
111 static cchar_t prepare_col_color(const view_t *view, int primary, int line_nr,
112 const column_data_t *cdt);
113 static void mix_in_common_colors(col_attr_t *col, const view_t *view,
114 dir_entry_t *entry, int line_color);
115 static void mix_in_file_hi(const view_t *view, dir_entry_t *entry, int type_hi,
116 col_attr_t *col);
117 static void mix_in_file_name_hi(const view_t *view, dir_entry_t *entry,
118 col_attr_t *col);
119 TSTATIC void format_name(int id, const void *data, size_t buf_len, char buf[]);
120 static void format_size(int id, const void *data, size_t buf_len, char buf[]);
121 static void format_nitems(int id, const void *data, size_t buf_len, char buf[]);
122 static void format_primary_group(int id, const void *data, size_t buf_len,
123 char buf[]);
124 static void format_type(int id, const void *data, size_t buf_len, char buf[]);
125 static void format_target(int id, const void *data, size_t buf_len, char buf[]);
126 static void format_ext(int id, const void *data, size_t buf_len, char buf[]);
127 static void format_fileext(int id, const void *data, size_t buf_len,
128 char buf[]);
129 static void format_time(int id, const void *data, size_t buf_len, char buf[]);
130 static void format_dir(int id, const void *data, size_t buf_len, char buf[]);
131 #ifndef _WIN32
132 static void format_group(int id, const void *data, size_t buf_len, char buf[]);
133 static void format_mode(int id, const void *data, size_t buf_len, char buf[]);
134 static void format_owner(int id, const void *data, size_t buf_len, char buf[]);
135 static void format_perms(int id, const void *data, size_t buf_len, char buf[]);
136 static void format_nlinks(int id, const void *data, size_t buf_len, char buf[]);
137 static void format_inode(int id, const void *data, size_t buf_len, char buf[]);
138 #endif
139 static void format_id(int id, const void *data, size_t buf_len, char buf[]);
140 static size_t calculate_column_width(view_t *view);
141 static size_t calculate_columns_count(struct view_t *view);
142 static size_t get_max_filename_width(const view_t *view);
143 static size_t get_filename_width(const view_t *view, int i);
144 static size_t get_filetype_decoration_width(const dir_entry_t *entry);
145 static int cache_cursor_pos(view_t *view);
146 static void invalidate_cursor_pos_cache(view_t *view);
147 static void position_hardware_cursor(view_t *view);
148 static int move_curr_line(view_t *view);
149 static void reset_view_columns(view_t *view);
150
151 void
fview_setup(void)152 fview_setup(void)
153 {
154 static const struct {
155 SortingKey key;
156 column_func func;
157 } sort_to_func[] = {
158 { SK_BY_NAME, &format_name },
159 { SK_BY_INAME, &format_name },
160 { SK_BY_SIZE, &format_size },
161 { SK_BY_NITEMS, &format_nitems },
162 { SK_BY_GROUPS, &format_primary_group },
163 { SK_BY_TYPE, &format_type },
164 { SK_BY_TARGET, &format_target },
165
166 { SK_BY_EXTENSION, &format_ext },
167 { SK_BY_FILEEXT, &format_fileext },
168 { SK_BY_TIME_ACCESSED, &format_time },
169 { SK_BY_TIME_CHANGED, &format_time },
170 { SK_BY_TIME_MODIFIED, &format_time },
171 { SK_BY_DIR, &format_dir },
172
173 #ifndef _WIN32
174 { SK_BY_GROUP_ID, &format_group },
175 { SK_BY_GROUP_NAME, &format_group },
176 { SK_BY_OWNER_ID, &format_owner },
177 { SK_BY_OWNER_NAME, &format_owner },
178
179 { SK_BY_MODE, &format_mode },
180
181 { SK_BY_PERMISSIONS, &format_perms },
182
183 { SK_BY_NLINKS, &format_nlinks },
184
185 { SK_BY_INODE, &format_inode },
186 #endif
187 };
188 ARRAY_GUARD(sort_to_func, SK_COUNT);
189
190 size_t i;
191
192 columns_set_line_print_func(&column_line_print);
193 for(i = 0U; i < ARRAY_LEN(sort_to_func); ++i)
194 {
195 columns_add_column_desc(sort_to_func[i].key, sort_to_func[i].func);
196 }
197 columns_add_column_desc(SK_BY_ID, &format_id);
198 columns_add_column_desc(SK_BY_ROOT, &format_name);
199 columns_add_column_desc(SK_BY_FILEROOT, &format_name);
200 }
201
202 void
fview_init(view_t * view)203 fview_init(view_t *view)
204 {
205 view->curr_line = 0;
206 view->top_line = 0;
207
208 view->local_cs = 0;
209
210 view->columns = columns_create();
211 view->view_columns = strdup("");
212 view->view_columns_g = strdup("");
213
214 view->sort_groups = strdup("");
215 view->sort_groups_g = strdup("");
216 (void)regcomp(&view->primary_group, view->sort_groups,
217 REG_EXTENDED | REG_ICASE);
218
219 view->preview_prg = strdup("");
220 view->preview_prg_g = strdup("");
221 }
222
223 void
fview_reset(view_t * view)224 fview_reset(view_t *view)
225 {
226 view->ls_view_g = view->ls_view = 0;
227 view->ls_transposed_g = view->ls_transposed = 0;
228 /* Invalidate maximum file name widths cache. */
229 view->max_filename_width = 0;;
230 view->column_count = 1;
231 view->run_size = 1;
232
233 view->miller_view_g = view->miller_view = 0;
234 view->miller_ratios_g[0] = view->miller_ratios[0] = 1;
235 view->miller_ratios_g[1] = view->miller_ratios[1] = 1;
236 view->miller_ratios_g[2] = view->miller_ratios[2] = 1;
237 view->miller_preview_files_g = view->miller_preview_files = 0;
238
239 view->num_type_g = view->num_type = NT_NONE;
240 view->num_width_g = view->num_width = 4;
241 view->real_num_width = 0;
242
243 pthread_mutex_lock(view->timestamps_mutex);
244 view->need_redraw = 0;
245 view->need_reload = 0;
246 pthread_mutex_unlock(view->timestamps_mutex);
247 }
248
249 void
fview_reset_cs(view_t * view)250 fview_reset_cs(view_t *view)
251 {
252 int i;
253 for(i = 0; i < view->list_rows; ++i)
254 {
255 view->dir_entry[i].hi_num = -1;
256 }
257 }
258
259 void
draw_dir_list(view_t * view)260 draw_dir_list(view_t *view)
261 {
262 draw_dir_list_only(view);
263
264 if(view != curr_view)
265 {
266 fview_draw_inactive_cursor(view);
267 }
268 }
269
270 void
draw_dir_list_only(view_t * view)271 draw_dir_list_only(view_t *view)
272 {
273 int x, cell;
274 size_t col_width, col_count;
275 int visible_cells;
276
277 if(curr_stats.load_stage < 2)
278 {
279 return;
280 }
281
282 calculate_table_conf(view, &col_count, &col_width);
283
284 ui_view_title_update(view);
285
286 /* This is needed for reloading a list that has had files deleted. */
287 while(view->list_rows - view->list_pos <= 0)
288 {
289 --view->list_pos;
290 --view->curr_line;
291 }
292
293 view->top_line = calculate_top_position(view, view->top_line);
294
295 ui_view_erase(view, 0);
296
297 draw_left_column(view);
298
299 visible_cells = view->window_cells;
300 if(fview_is_transposed(view) &&
301 view->column_count*(int)col_width < ui_view_available_width(view))
302 {
303 /* Add extra visual column to display more context. */
304 visible_cells += view->window_rows;
305 }
306
307 for(x = view->top_line, cell = 0;
308 x < view->list_rows && cell < visible_cells;
309 ++x, ++cell)
310 {
311 column_data_t cdt = {
312 .view = view,
313 .entry = &view->dir_entry[x],
314 .line_pos = x,
315 .current_pos = view->list_pos,
316 };
317
318 compute_and_draw_cell(&cdt, cell, col_width);
319 }
320
321 draw_right_column(view);
322
323 view->curr_line = view->list_pos - view->top_line;
324
325 if(view == curr_view)
326 {
327 consider_scroll_bind(view);
328 position_hardware_cursor(view);
329 }
330
331 ui_view_win_changed(view);
332
333 ui_view_redrawn(view);
334 }
335
336 /* Draws a column to the left of the main part of the view. */
337 static void
draw_left_column(view_t * view)338 draw_left_column(view_t *view)
339 {
340 char path[PATH_MAX + 1];
341 const char *const dir = flist_get_dir(view);
342
343 int number_width = 0;
344 int lcol_width = ui_view_left_reserved(view)
345 - (cfg.extra_padding ? 1 : 0) - 1;
346 if(lcol_width <= 0)
347 {
348 flist_free_cache(view, &view->left_column);
349 return;
350 }
351
352 copy_str(path, sizeof(path), dir);
353 remove_last_path_component(path);
354 (void)flist_update_cache(view, &view->left_column, path);
355
356 number_width = calculate_number_width(view,
357 view->left_column.entries.nentries, lcol_width);
358 lcol_width -= number_width;
359
360 if(view->left_column.entries.nentries >= 0)
361 {
362 print_column(view, view->left_column.entries, dir, path, lcol_width, 0,
363 number_width);
364 }
365 }
366
367 /* Draws a column to the right of the main part of the view. */
368 static void
draw_right_column(view_t * view)369 draw_right_column(view_t *view)
370 {
371 view->displays_graphics = 0;
372
373 const int padding = (cfg.extra_padding ? 1 : 0);
374 const int offset = ui_view_left_reserved(view) + padding
375 + ui_view_available_width(view) + padding
376 + 1;
377
378 const int rcol_width = ui_view_right_reserved(view) - padding - 1;
379 if(rcol_width <= 0)
380 {
381 flist_free_cache(view, &view->right_column);
382 return;
383 }
384
385 dir_entry_t *const entry = get_current_entry(view);
386 if(view->miller_preview_files && !fentry_is_dir(entry))
387 {
388 const col_scheme_t *const cs = ui_view_get_cs(view);
389 col_attr_t def_col = ui_get_win_color(view, cs);
390 cs_mix_colors(&def_col, &cs->color[AUX_WIN_COLOR]);
391
392 const preview_area_t parea = {
393 .source = view,
394 .view = view,
395 .def_col = def_col,
396 .x = offset,
397 .y = 0,
398 .w = ui_view_right_reserved(view) - 1,
399 .h = view->window_rows,
400 };
401 qv_draw_on(entry, &parea);
402 return;
403 }
404
405 char path[PATH_MAX + 1];
406 get_current_full_path(view, sizeof(path), path);
407 (void)flist_update_cache(view, &view->right_column, path);
408
409 if(view->right_column.entries.nentries >= 0)
410 {
411 print_column(view, view->right_column.entries, NULL, path, rcol_width,
412 offset, 0);
413 }
414 }
415
416 /* Prints column full of entry names. Current is a hint that tells which column
417 * has to be selected (otherwise position from history record is used). */
418 static void
print_column(view_t * view,entries_t entries,const char current[],const char path[],int width,int offset,int number_width)419 print_column(view_t *view, entries_t entries, const char current[],
420 const char path[], int width, int offset, int number_width)
421 {
422 columns_t *const columns = get_name_column(0);
423 const int scroll_offset = fpos_get_offset(view);
424 int top, pos;
425 int i;
426
427 sort_entries(view, entries);
428
429 pos = flist_hist_find(view, entries, path, &top);
430
431 /* Use hint if provided. */
432 if(current != NULL)
433 {
434 dir_entry_t *entry;
435 entry = entry_from_path(view, entries.entries, entries.nentries, current);
436 if(entry != NULL)
437 {
438 pos = entry - entries.entries;
439 }
440 }
441
442 /* Make sure that current element is visible on the screen. */
443 if(pos < top + scroll_offset)
444 {
445 top = pos - scroll_offset;
446 }
447 else if(pos >= top + view->window_rows - scroll_offset)
448 {
449 top = pos - (view->window_rows - scroll_offset - 1);
450 }
451 /* Ensure that top position is correct and we fill all lines for which we have
452 * files. */
453 if(top < 0)
454 {
455 top = 0;
456 }
457 if(entries.nentries - top < view->window_rows)
458 {
459 top = MAX(0, entries.nentries - view->window_rows);
460 }
461
462 if(current != NULL)
463 {
464 /* We will display cursor on this entry and want history to be aware of
465 * it. */
466 flist_hist_update(view, path, get_last_path_component(current), pos - top);
467 }
468
469 for(i = top; i < entries.nentries && i - top < view->window_rows; ++i)
470 {
471 size_t prefix_len = 0U;
472 const column_data_t cdt = {
473 .view = view,
474 .entry = &entries.entries[i],
475 .line_pos = i,
476 .line_hi_group = get_line_color(view, &entries.entries[i]),
477 .current_pos = pos,
478 .total_width = number_width + width,
479 .number_width = number_width,
480 .current_line = i - top,
481 .column_offset = offset,
482 .prefix_len = &prefix_len,
483 };
484
485 draw_cell(columns, &cdt, width, width - 1);
486 }
487
488 fill_column(view, i, top, number_width + width, offset);
489 }
490
491 /* Fills column to the bottom to clear it from previous content. */
492 static void
fill_column(view_t * view,int start_line,int top,int width,int offset)493 fill_column(view_t *view, int start_line, int top, int width, int offset)
494 {
495 char filler[width + (cfg.extra_padding ? 1 : 0) + 1];
496 memset(filler, ' ', sizeof(filler) - 1U);
497 filler[sizeof(filler) - 1U] = '\0';
498
499 char a_space[] = " ";
500 dir_entry_t non_entry = {
501 .name = a_space,
502 .origin = a_space,
503 .type = FT_UNK,
504 };
505
506 int i;
507 for(i = start_line; i - top < view->window_rows; ++i)
508 {
509 size_t prefix_len = 0U;
510 const column_data_t cdt = {
511 .view = view,
512 .entry = &non_entry,
513 .line_pos = -1,
514 .total_width = width,
515 .current_pos = -1,
516 .current_line = i - top,
517 .column_offset = offset,
518 .prefix_len = &prefix_len,
519 };
520
521 column_line_print(&cdt, FILL_COLUMN_ID, filler, cfg.extra_padding ? -1 : 0,
522 AT_LEFT, filler);
523 }
524 }
525
526 /* Calculates number of columns and maximum width of column in a view. */
527 static void
calculate_table_conf(view_t * view,size_t * count,size_t * width)528 calculate_table_conf(view_t *view, size_t *count, size_t *width)
529 {
530 view->real_num_width = calculate_number_width(view, view->list_rows,
531 ui_view_available_width(view));
532
533 if(ui_view_displays_columns(view))
534 {
535 *count = 1;
536 *width = MAX(0, ui_view_available_width(view) - view->real_num_width);
537 }
538 else
539 {
540 *count = calculate_columns_count(view);
541 *width = calculate_column_width(view);
542 }
543
544 view->column_count = *count;
545 view->run_size = fview_is_transposed(view) ? view->window_rows
546 : view->column_count;
547 view->window_cells = *count*view->window_rows;
548 }
549
550 /* Calculates real number of characters that should be allocated in view for
551 * numbers column. Returns the number. */
552 static int
calculate_number_width(const view_t * view,int list_length,int width)553 calculate_number_width(const view_t *view, int list_length, int width)
554 {
555 if(ui_view_displays_numbers(view))
556 {
557 const int digit_count = count_digits(list_length);
558 const int min = view->num_width;
559 return MIN(MAX(1 + digit_count, min), width);
560 }
561
562 return 0;
563 }
564
565 /* Counts number of digits in a number assuming that zero takes on digit.
566 * Returns the count. */
567 static int
count_digits(int num)568 count_digits(int num)
569 {
570 int count = 0;
571 do
572 {
573 count++;
574 num /= 10;
575 }
576 while(num != 0);
577 return count;
578 }
579
580 /* Calculates top position basing on window and list size and trying to show as
581 * much of the directory as possible. Can modify view->curr_line. Returns
582 * new top. */
583 static int
calculate_top_position(view_t * view,int top)584 calculate_top_position(view_t *view, int top)
585 {
586 int result = MIN(MAX(top, 0), view->list_rows - 1);
587 result = ROUND_DOWN(result, view->run_size);
588 if(view->window_cells >= view->list_rows)
589 {
590 result = 0;
591 }
592 else if(view->list_rows - top < view->window_cells)
593 {
594 if(view->window_cells - (view->list_rows - top) >= view->run_size)
595 {
596 result = view->list_rows - view->window_cells + (view->run_size - 1);
597 result = ROUND_DOWN(result, view->run_size);
598 view->curr_line++;
599 }
600 }
601 return result;
602 }
603
604 /* Calculates highlight group for the entry. Returns highlight group number. */
605 static int
get_line_color(const view_t * view,const dir_entry_t * entry)606 get_line_color(const view_t *view, const dir_entry_t *entry)
607 {
608 switch(entry->type)
609 {
610 case FT_DIR:
611 return DIRECTORY_COLOR;
612 case FT_FIFO:
613 return FIFO_COLOR;
614 case FT_LINK:
615 if(view->on_slow_fs)
616 {
617 return LINK_COLOR;
618 }
619 else
620 {
621 char full[PATH_MAX + 1];
622 get_full_path_of(entry, sizeof(full), full);
623 if(get_link_target_abs(full, entry->origin, full, sizeof(full)) != 0)
624 {
625 return BROKEN_LINK_COLOR;
626 }
627
628 /* Assume that targets on slow file system are not broken as actual
629 * check might take long time. */
630 if(is_on_slow_fs(full, cfg.slow_fs_list))
631 {
632 return LINK_COLOR;
633 }
634
635 return path_exists(full, DEREF) ? LINK_COLOR : BROKEN_LINK_COLOR;
636 }
637 #ifndef _WIN32
638 case FT_SOCK:
639 return SOCKET_COLOR;
640 #endif
641 case FT_CHAR_DEV:
642 case FT_BLOCK_DEV:
643 return DEVICE_COLOR;
644 case FT_EXEC:
645 return EXECUTABLE_COLOR;
646
647 default:
648 return (entry->nlinks > 1 ? HARD_LINK_COLOR : WIN_COLOR);
649 }
650 }
651
652 /* Calculates width of the column using entry and maximum width. */
653 static size_t
calculate_print_width(const view_t * view,int i,size_t max_width)654 calculate_print_width(const view_t *view, int i, size_t max_width)
655 {
656 if(!ui_view_displays_columns(view))
657 {
658 const size_t raw_name_width = get_filename_width(view, i);
659 return MIN(max_width - 1, raw_name_width);
660 }
661
662 return max_width;
663 }
664
665 /* Draws a full cell of the file list. print_width <= col_width. */
666 static void
draw_cell(columns_t * columns,const column_data_t * cdt,size_t col_width,size_t print_width)667 draw_cell(columns_t *columns, const column_data_t *cdt, size_t col_width,
668 size_t print_width)
669 {
670 size_t width_left = cdt->is_main
671 ? ui_view_available_width(cdt->view) - (cdt->column_offset -
672 ui_view_left_reserved(cdt->view))
673 : col_width + 1U;
674
675 if(cfg.extra_padding)
676 {
677 column_line_print(cdt, FILL_COLUMN_ID, " ", -1, AT_LEFT, " ");
678 }
679
680 columns_format_line(columns, cdt, MIN(col_width, width_left));
681
682 if(cfg.extra_padding && width_left >= col_width)
683 {
684 column_line_print(cdt, FILL_COLUMN_ID, " ", print_width, AT_LEFT, " ");
685 }
686 }
687
688 /* Retrieves active view columns handle of the view considering 'lsview' option
689 * status. Returns the handle. */
690 static columns_t *
get_view_columns(const view_t * view,int truncated)691 get_view_columns(const view_t *view, int truncated)
692 {
693 /* Note that columns_t performs some caching, so we might want to keep one
694 * handle per view rather than sharing one. */
695
696 static const column_info_t name_column = {
697 .column_id = SK_BY_NAME, .full_width = 0UL, .text_width = 0UL,
698 .align = AT_LEFT, .sizing = ST_AUTO, .cropping = CT_ELLIPSIS,
699 };
700 static const column_info_t id_column = {
701 .column_id = SK_BY_ID, .full_width = 7UL, .text_width = 7UL,
702 .align = AT_LEFT, .sizing = ST_ABSOLUTE, .cropping = CT_ELLIPSIS,
703 };
704
705 static columns_t *comparison_columns;
706
707 if(!ui_view_displays_columns(view))
708 {
709 return get_name_column(truncated);
710 }
711
712 if(cv_compare(view->custom.type))
713 {
714 if(comparison_columns == NULL)
715 {
716 comparison_columns = columns_create();
717 columns_add_column(comparison_columns, name_column);
718 columns_add_column(comparison_columns, id_column);
719 }
720 return comparison_columns;
721 }
722
723 return view->columns;
724 }
725
726 /* Retrieves columns view handle consisting of a single name column. Returns
727 * the handle. */
728 static columns_t *
get_name_column(int truncated)729 get_name_column(int truncated)
730 {
731 static const column_info_t name_column_ell = {
732 .column_id = SK_BY_NAME, .full_width = 0UL, .text_width = 0UL,
733 .align = AT_LEFT, .sizing = ST_AUTO, .cropping = CT_ELLIPSIS,
734 };
735 static columns_t *columns_ell;
736
737 if(truncated)
738 {
739 static columns_t *columns_trunc;
740 if(columns_trunc == NULL)
741 {
742 column_info_t name_column_trunc = name_column_ell;
743 name_column_trunc.cropping = CT_TRUNCATE;
744
745 columns_trunc = columns_create();
746 columns_add_column(columns_trunc, name_column_trunc);
747 }
748 return columns_trunc;
749 }
750
751 if(columns_ell == NULL)
752 {
753 columns_ell = columns_create();
754 columns_add_column(columns_ell, name_column_ell);
755 }
756 return columns_ell;
757 }
758
759 /* Corrects top of the other view to synchronize it with the current view if
760 * 'scrollbind' option is set or view is in the compare mode. */
761 static void
consider_scroll_bind(view_t * view)762 consider_scroll_bind(view_t *view)
763 {
764 if(cfg.scroll_bind || view->custom.type == CV_DIFF)
765 {
766 view_t *const other = (view == &lwin) ? &rwin : &lwin;
767 const int bind_off = cfg.scroll_bind ? curr_stats.scroll_bind_off : 0;
768 other->top_line = view->top_line/view->column_count;
769 if(view == &lwin)
770 {
771 other->top_line += bind_off;
772 }
773 else
774 {
775 other->top_line -= bind_off;
776 }
777 other->top_line *= other->column_count;
778 other->top_line = calculate_top_position(other, other->top_line);
779
780 if(can_scroll_up(other))
781 {
782 (void)fpos_scroll_down(other, 0);
783 }
784 if(can_scroll_down(other))
785 {
786 (void)fpos_scroll_up(other, 0);
787 }
788
789 other->curr_line = other->list_pos - other->top_line;
790
791 if(window_shows_dirlist(other))
792 {
793 draw_dir_list(other);
794 refresh_view_win(other);
795 }
796 }
797 }
798
799 void
redraw_view(view_t * view)800 redraw_view(view_t *view)
801 {
802 if(!stats_redraw_planned() && !curr_stats.restart_in_progress &&
803 window_shows_dirlist(view))
804 {
805 /* Make sure cursor is visible and relevant part of the view is
806 * displayed. */
807 (void)move_curr_line(view);
808 /* Update cursor position cache as it might have been moved outside this
809 * unit. */
810 (void)cache_cursor_pos(view);
811 /* And then redraw the view unconditionally as requested. */
812 draw_dir_list(view);
813 }
814 }
815
816 void
redraw_current_view(void)817 redraw_current_view(void)
818 {
819 redraw_view(curr_view);
820 }
821
822 void
fview_cursor_redraw(view_t * view)823 fview_cursor_redraw(view_t *view)
824 {
825 /* fview_cursor_redraw() is also called in situations when file list has
826 * changed, let fview_position_updated() deal with it. With a cache of last
827 * position, it should be fine. */
828 fview_position_updated(view);
829
830 /* Always redrawing the cell won't hurt and will account for the case when
831 * selection state of item under the cursor has changed. */
832 if(view == other_view)
833 {
834 fview_draw_inactive_cursor(view);
835 }
836 else
837 {
838 if(!ui_view_displays_columns(view))
839 {
840 /* Inactive cell in ls-like view usually takes less space than an active
841 * one. Need to clear the cell before drawing over it. */
842 redraw_cell(view, view->top_line, view->curr_line, 0);
843 }
844 redraw_cell(view, view->top_line, view->curr_line, 1);
845
846 /* redraw_cell() naturally moves hardware cursor after current entry (to the
847 * next line when not in ls-like view). Fix it up. */
848 position_hardware_cursor(view);
849 }
850 }
851
852 void
fview_draw_inactive_cursor(view_t * view)853 fview_draw_inactive_cursor(view_t *view)
854 {
855 size_t col_width, col_count;
856 int line, column;
857
858 /* Reset last seen position on drawing inactive cursor or an active one won't
859 * be drawn next time. */
860 invalidate_cursor_pos_cache(view);
861
862 if(!ui_view_displays_columns(view))
863 {
864 /* Inactive cell in ls-like view usually takes less space than an active
865 * one. Need to clear the cell before drawing over it. */
866 redraw_cell(view, view->top_line, view->curr_line, 0);
867 }
868 redraw_cell(view, view->top_line, view->curr_line, 1);
869
870 if(!cfg.extra_padding)
871 {
872 return;
873 }
874
875 calculate_table_conf(view, &col_count, &col_width);
876
877 cchar_t line_attrs = prepare_inactive_color(view, get_current_entry(view),
878 get_line_color(view, get_current_entry(view)));
879
880 line = fpos_get_line(view, view->curr_line);
881 column = view->real_num_width + ui_view_left_reserved(view)
882 + fpos_get_col(view, view->curr_line)*col_width;
883 checked_wmove(view->win, line, column);
884
885 wprinta(view->win, INACTIVE_CURSOR_MARK, &line_attrs, 0);
886 ui_view_win_changed(view);
887 }
888
889 /* Calculate color attributes for cursor line of inactive pane. Returns
890 * attributes that can be used for drawing on a window. */
891 static cchar_t
prepare_inactive_color(view_t * view,dir_entry_t * entry,int line_color)892 prepare_inactive_color(view_t *view, dir_entry_t *entry, int line_color)
893 {
894 const col_scheme_t *cs = ui_view_get_cs(view);
895 col_attr_t col = ui_get_win_color(view, cs);
896
897 mix_in_common_colors(&col, view, entry, line_color);
898 cs_mix_colors(&col, &cs->color[OTHER_LINE_COLOR]);
899
900 cchar_t cch;
901 setcchar(&cch, L" ", col.attr, colmgr_get_pair(col.fg, col.bg), NULL);
902 return cch;
903 }
904
905 /* Redraws single directory list entry. is_current defines whether element
906 * is under the cursor and should be highlighted as such. The function depends
907 * on passed in positions instead of view state to be independent from changes
908 * in the view. */
909 static void
redraw_cell(view_t * view,int top,int cursor,int is_current)910 redraw_cell(view_t *view, int top, int cursor, int is_current)
911 {
912 const int pos = top + cursor;
913 if(pos < 0 || pos >= view->list_rows)
914 {
915 /* The entire list is going to be redrawn so just return. */
916 return;
917 }
918
919 if(curr_stats.load_stage < 2 || cursor < 0)
920 {
921 return;
922 }
923
924 size_t col_width, col_count;
925 calculate_table_conf(view, &col_count, &col_width);
926
927 column_data_t cdt = {
928 .view = view,
929 .entry = &view->dir_entry[pos],
930 .line_pos = pos,
931 .current_pos = is_current ? view->list_pos : -1,
932 };
933 compute_and_draw_cell(&cdt, cursor, col_width);
934 }
935
936 /* Fills in fields of cdt based on passed in arguments and
937 * view/entry/line_pos/current_pos fields of cdt. Then draws the cell. */
938 static void
compute_and_draw_cell(column_data_t * cdt,int cell,size_t col_width)939 compute_and_draw_cell(column_data_t *cdt, int cell, size_t col_width)
940 {
941 size_t prefix_len = 0U;
942
943 const size_t print_width = calculate_print_width(cdt->view, cdt->line_pos,
944 col_width);
945
946 cdt->current_line = fpos_get_line(cdt->view, cell);
947 cdt->column_offset = ui_view_left_reserved(cdt->view)
948 + fpos_get_col(cdt->view, cell)*col_width;
949 cdt->line_hi_group = get_line_color(cdt->view, cdt->entry);
950 cdt->number_width = cdt->view->real_num_width;
951 cdt->total_width = ui_view_available_width(cdt->view);
952 cdt->prefix_len = &prefix_len;
953 cdt->is_main = 1;
954
955 if(cfg.extra_padding && !ui_view_displays_columns(cdt->view))
956 {
957 /* Padding in ls-like view adds additional empty single character between
958 * columns, on which we shouldn't draw anything here. */
959 --col_width;
960 }
961
962 draw_cell(get_view_columns(cdt->view, cell >= cdt->view->window_cells), cdt,
963 col_width, print_width);
964
965 cdt->prefix_len = NULL;
966 }
967
968 int
can_scroll_up(const view_t * view)969 can_scroll_up(const view_t *view)
970 {
971 return view->top_line > 0;
972 }
973
974 int
can_scroll_down(const view_t * view)975 can_scroll_down(const view_t *view)
976 {
977 return fpos_get_last_visible_cell(view) < view->list_rows - 1;
978 }
979
980 void
scroll_up(view_t * view,int by)981 scroll_up(view_t *view, int by)
982 {
983 /* Round it up, so 1 will cause one line scrolling. */
984 view->top_line -= view->run_size*DIV_ROUND_UP(by, view->run_size);
985 if(view->top_line < 0)
986 {
987 view->top_line = 0;
988 }
989 view->top_line = calculate_top_position(view, view->top_line);
990
991 view->curr_line = view->list_pos - view->top_line;
992 }
993
994 void
scroll_down(view_t * view,int by)995 scroll_down(view_t *view, int by)
996 {
997 /* Round it up, so 1 will cause one line scrolling. */
998 view->top_line += view->run_size*DIV_ROUND_UP(by, view->run_size);
999 view->top_line = calculate_top_position(view, view->top_line);
1000
1001 view->curr_line = view->list_pos - view->top_line;
1002 }
1003
1004 int
get_corrected_list_pos_down(const view_t * view,int pos_delta)1005 get_corrected_list_pos_down(const view_t *view, int pos_delta)
1006 {
1007 const int scroll_offset = fpos_get_offset(view);
1008 if(view->list_pos <= view->top_line + scroll_offset + (MAX(pos_delta, 1) - 1))
1009 {
1010 const int column_correction = view->list_pos%view->column_count;
1011 const int offset = scroll_offset + pos_delta + column_correction;
1012 return view->top_line + offset;
1013 }
1014 return view->list_pos;
1015 }
1016
1017 int
get_corrected_list_pos_up(const view_t * view,int pos_delta)1018 get_corrected_list_pos_up(const view_t *view, int pos_delta)
1019 {
1020 const int scroll_offset = fpos_get_offset(view);
1021 const int last = fpos_get_last_visible_cell(view);
1022 if(view->list_pos >= last - scroll_offset - (MAX(pos_delta, 1) - 1))
1023 {
1024 const int column_correction = (view->column_count - 1)
1025 - view->list_pos%view->column_count;
1026 const int offset = scroll_offset + pos_delta + column_correction;
1027 return last - offset;
1028 }
1029 return view->list_pos;
1030 }
1031
1032 int
consider_scroll_offset(view_t * view)1033 consider_scroll_offset(view_t *view)
1034 {
1035 int need_redraw = 0;
1036 int pos = view->list_pos;
1037 if(cfg.scroll_off > 0)
1038 {
1039 const int s = fpos_get_offset(view);
1040 /* Check scroll offset at the top. */
1041 if(can_scroll_up(view) && pos - view->top_line < s)
1042 {
1043 scroll_up(view, s - (pos - view->top_line));
1044 need_redraw = 1;
1045 }
1046 /* Check scroll offset at the bottom. */
1047 if(can_scroll_down(view))
1048 {
1049 const int last = fpos_get_last_visible_cell(view);
1050 if(pos > last - s)
1051 {
1052 scroll_down(view, s + (pos - last));
1053 need_redraw = 1;
1054 }
1055 }
1056 }
1057 return need_redraw;
1058 }
1059
1060 void
scroll_by_files(view_t * view,int by)1061 scroll_by_files(view_t *view, int by)
1062 {
1063 if(by > 0)
1064 {
1065 scroll_down(view, by);
1066 }
1067 else if(by < 0)
1068 {
1069 scroll_up(view, -by);
1070 }
1071 }
1072
1073 void
update_scroll_bind_offset(void)1074 update_scroll_bind_offset(void)
1075 {
1076 const int rwin_pos = rwin.top_line/rwin.column_count;
1077 const int lwin_pos = lwin.top_line/lwin.column_count;
1078 curr_stats.scroll_bind_off = rwin_pos - lwin_pos;
1079 }
1080
1081 /* Print callback for column_view unit. */
1082 static void
column_line_print(const void * data,int column_id,const char buf[],size_t offset,AlignType align,const char full_column[])1083 column_line_print(const void *data, int column_id, const char buf[],
1084 size_t offset, AlignType align, const char full_column[])
1085 {
1086 char print_buf[strlen(buf) + 1];
1087 size_t prefix_len, final_offset;
1088 size_t width_left, trim_pos;
1089 int reserved_width;
1090
1091 const column_data_t *const cdt = data;
1092 view_t *view = cdt->view;
1093 dir_entry_t *entry = cdt->entry;
1094
1095 const int numbers_visible = (offset == 0 && cdt->number_width > 0);
1096 const int padding = (cfg.extra_padding != 0);
1097
1098 const int primary = column_id == SK_BY_NAME
1099 || column_id == SK_BY_INAME
1100 || column_id == SK_BY_ROOT
1101 || column_id == SK_BY_FILEROOT;
1102 const cchar_t line_attrs = prepare_col_color(view, primary, 0, cdt);
1103
1104 size_t extra_prefix = primary ? *cdt->prefix_len : 0U;
1105
1106 if(extra_prefix != 0U && align == AT_RIGHT)
1107 {
1108 /* Prefix length requires correction if left hand side of file name is
1109 * trimmed. */
1110 size_t width = utf8_strsw(buf);
1111 if(utf8_strsw(full_column) > width)
1112 {
1113 /* As left side is trimmed and might contain ellipsis calculate offsets
1114 * according to the right side. */
1115 width -= utf8_strsw(full_column + extra_prefix);
1116 extra_prefix = utf8_strsnlen(buf, width);
1117 }
1118 }
1119
1120 prefix_len = padding + cdt->number_width + extra_prefix;
1121 final_offset = prefix_len + cdt->column_offset + offset;
1122
1123 if(numbers_visible)
1124 {
1125 const int column = final_offset - extra_prefix - cdt->number_width;
1126 draw_line_number(cdt, column);
1127 }
1128
1129 if(extra_prefix != 0U)
1130 {
1131 /* Copy prefix part into working buffer. */
1132 strncpy(print_buf, buf, extra_prefix);
1133 print_buf[extra_prefix] = '\0';
1134 buf += extra_prefix;
1135 full_column += extra_prefix;
1136
1137 checked_wmove(view->win, cdt->current_line, final_offset - extra_prefix);
1138 cchar_t cch = prepare_col_color(view, 0, 0, cdt);
1139 wprinta(view->win, print_buf, &cch, 0);
1140 }
1141
1142 checked_wmove(view->win, cdt->current_line, final_offset);
1143
1144 if(fentry_is_fake(entry))
1145 {
1146 memset(print_buf, '.', sizeof(print_buf) - 1U);
1147 print_buf[sizeof(print_buf) - 1U] = '\0';
1148 }
1149 else
1150 {
1151 strcpy(print_buf, buf);
1152 }
1153 reserved_width = cfg.extra_padding ? (column_id != FILL_COLUMN_ID) : 0;
1154 width_left = padding + cdt->total_width - reserved_width - offset;
1155 trim_pos = utf8_nstrsnlen(buf, width_left);
1156 if(trim_pos < sizeof(print_buf))
1157 {
1158 print_buf[trim_pos] = '\0';
1159 }
1160 wprinta(view->win, print_buf, &line_attrs, 0);
1161
1162 if(primary && view->matches != 0 && entry->search_match)
1163 {
1164 highlight_search(view, entry, full_column, print_buf, trim_pos, align,
1165 cdt->current_line, final_offset, &line_attrs);
1166 }
1167 }
1168
1169 /* Draws current line number at specified column. */
1170 static void
draw_line_number(const column_data_t * cdt,int column)1171 draw_line_number(const column_data_t *cdt, int column)
1172 {
1173 view_t *const view = cdt->view;
1174
1175 const int mixed = cdt->line_pos == cdt->current_pos
1176 && view->num_type == NT_MIX;
1177 const char *const format = mixed ? "%-*d " : "%*d ";
1178 const int num = (view->num_type & NT_REL) && !mixed
1179 ? abs(cdt->line_pos - cdt->current_pos)
1180 : cdt->line_pos + 1;
1181
1182 char num_str[cdt->number_width + 1];
1183 snprintf(num_str, sizeof(num_str), format, cdt->number_width - 1, num);
1184
1185 checked_wmove(view->win, cdt->current_line, column);
1186 cchar_t cch = prepare_col_color(view, 0, 1, cdt);
1187 wprinta(view->win, num_str, &cch, 0);
1188 }
1189
1190 /* Highlights search match for the entry (assumed to be a search hit). Modifies
1191 * the buf argument in process. */
1192 static void
highlight_search(view_t * view,dir_entry_t * entry,const char full_column[],char buf[],size_t buf_len,AlignType align,int line,int col,const cchar_t * line_attrs)1193 highlight_search(view_t *view, dir_entry_t *entry, const char full_column[],
1194 char buf[], size_t buf_len, AlignType align, int line, int col,
1195 const cchar_t *line_attrs)
1196 {
1197 size_t name_offset, lo, ro;
1198 const char *fname;
1199
1200 const size_t width = utf8_strsw(buf);
1201
1202 const char *prefix, *suffix;
1203 ui_get_decors(entry, &prefix, &suffix);
1204
1205 fname = get_last_path_component(full_column) + strlen(prefix);
1206 name_offset = fname - full_column;
1207
1208 lo = name_offset + entry->match_left;
1209 ro = name_offset + entry->match_right;
1210
1211 if((size_t)entry->match_right > strlen(fname) - strlen(suffix))
1212 {
1213 /* Don't highlight anything past the end of file name except for single
1214 * trailing slash. */
1215 ro -= entry->match_right - (strlen(fname) - strlen(suffix));
1216 if(suffix[0] == '/')
1217 {
1218 ++ro;
1219 }
1220 }
1221
1222 if(align == AT_LEFT && buf_len < ro)
1223 {
1224 /* Right end of the match isn't visible. */
1225
1226 char mark[4];
1227 const size_t mark_len = MIN(sizeof(mark) - 1, width);
1228 const int offset = width - mark_len;
1229 copy_str(mark, mark_len + 1, ">>>");
1230
1231 checked_wmove(view->win, line, col + offset);
1232 wprinta(view->win, mark, line_attrs, A_REVERSE);
1233 }
1234 else if(align == AT_RIGHT && lo < (short int)strlen(full_column) - buf_len)
1235 {
1236 /* Left end of the match isn't visible. */
1237
1238 char mark[4];
1239 const size_t mark_len = MIN(sizeof(mark) - 1, width);
1240 copy_str(mark, mark_len + 1, "<<<");
1241
1242 checked_wmove(view->win, line, col);
1243 wprinta(view->win, mark, line_attrs, A_REVERSE);
1244 }
1245 else
1246 {
1247 /* Match is completely visible (although some chars might be concealed with
1248 * ellipsis). */
1249
1250 size_t match_start;
1251 char c;
1252
1253 /* Match offsets require correction if left hand side of file name is
1254 * trimmed. */
1255 if(align == AT_RIGHT && utf8_strsw(full_column) > width)
1256 {
1257 /* As left side is trimmed and might contain ellipsis calculate offsets
1258 * according to the right side. */
1259 lo = utf8_strsnlen(buf, width - utf8_strsw(full_column + lo));
1260 ro = utf8_strsnlen(buf, width - utf8_strsw(full_column + ro));
1261 }
1262
1263 /* Calculate number of screen characters before the match. */
1264 c = buf[lo];
1265 buf[lo] = '\0';
1266 match_start = utf8_strsw(buf);
1267 buf[lo] = c;
1268
1269 checked_wmove(view->win, line, col + match_start);
1270 buf[ro] = '\0';
1271 wprinta(view->win, buf + lo, line_attrs, (A_REVERSE | A_UNDERLINE));
1272 }
1273 }
1274
1275 /* Calculate color attributes for a view column. Returns attributes that can be
1276 * used for drawing on a window. */
1277 static cchar_t
prepare_col_color(const view_t * view,int primary,int line_nr,const column_data_t * cdt)1278 prepare_col_color(const view_t *view, int primary, int line_nr,
1279 const column_data_t *cdt)
1280 {
1281 const col_scheme_t *const cs = ui_view_get_cs(view);
1282 col_attr_t col = ui_get_win_color(view, cs);
1283
1284 if(!cdt->is_main)
1285 {
1286 cs_mix_colors(&col, &cs->color[AUX_WIN_COLOR]);
1287 }
1288
1289 if(cdt->current_line%2 == 1)
1290 {
1291 cs_mix_colors(&col, &cs->color[ODD_LINE_COLOR]);
1292 }
1293
1294 if(cdt->line_pos != -1)
1295 {
1296 /* File-specific highlight affects only primary field for non-current lines
1297 * and whole line for the current line. */
1298 const int with_line_hi = (primary || cdt->line_pos == cdt->current_pos);
1299 const int line_color = with_line_hi ? cdt->line_hi_group : -1;
1300 mix_in_common_colors(&col, view, cdt->entry, line_color);
1301
1302 if(cdt->line_pos == cdt->current_pos)
1303 {
1304 int color = (view == curr_view || !cdt->is_main) ? CURR_LINE_COLOR
1305 : OTHER_LINE_COLOR;
1306 cs_mix_colors(&col, &cs->color[color]);
1307 }
1308 else if(line_nr)
1309 {
1310 /* Line number of current line is not affected by this highlight group. */
1311 cs_mix_colors(&col, &cs->color[LINE_NUM_COLOR]);
1312 }
1313 }
1314
1315 cchar_t cch;
1316 setcchar(&cch, L" ", col.attr, colmgr_get_pair(col.fg, col.bg), NULL);
1317 return cch;
1318 }
1319
1320 /* Mixes in colors of current entry, mismatch and selection. */
1321 static void
mix_in_common_colors(col_attr_t * col,const view_t * view,dir_entry_t * entry,int line_color)1322 mix_in_common_colors(col_attr_t *col, const view_t *view, dir_entry_t *entry,
1323 int line_color)
1324 {
1325 if(line_color >= 0)
1326 {
1327 mix_in_file_hi(view, entry, line_color, col);
1328 }
1329
1330 const col_scheme_t *const cs = ui_view_get_cs(view);
1331 view_t *const other = (view == &lwin) ? &rwin : &lwin;
1332
1333 /* If two files on the same line in side-by-side comparison have different
1334 * ids, that's a mismatch. */
1335 if(view->custom.type == CV_DIFF &&
1336 other->dir_entry[entry_to_pos(view, entry)].id != entry->id)
1337 {
1338 cs_mix_colors(col, &cs->color[MISMATCH_COLOR]);
1339 }
1340
1341 if(entry->selected)
1342 {
1343 cs_mix_colors(col, &cs->color[SELECTED_COLOR]);
1344 }
1345 }
1346
1347 /* Applies file name and file type specific highlights for the entry. */
1348 static void
mix_in_file_hi(const view_t * view,dir_entry_t * entry,int type_hi,col_attr_t * col)1349 mix_in_file_hi(const view_t *view, dir_entry_t *entry, int type_hi,
1350 col_attr_t *col)
1351 {
1352 if(fentry_is_fake(entry))
1353 {
1354 return;
1355 }
1356
1357 /* Apply file name specific highlights. */
1358 mix_in_file_name_hi(view, entry, col);
1359
1360 /* Apply file type specific highlights for non-regular files (regular files
1361 * are colored the same way window is). */
1362 if(type_hi != WIN_COLOR)
1363 {
1364 const col_scheme_t *cs = ui_view_get_cs(view);
1365 cs_mix_colors(col, &cs->color[type_hi]);
1366 }
1367 }
1368
1369 /* Applies file name specific highlight for the entry. */
1370 static void
mix_in_file_name_hi(const view_t * view,dir_entry_t * entry,col_attr_t * col)1371 mix_in_file_name_hi(const view_t *view, dir_entry_t *entry, col_attr_t *col)
1372 {
1373 const col_scheme_t *const cs = ui_view_get_cs(view);
1374 char *const typed_fname = get_typed_entry_fpath(entry);
1375 const col_attr_t *color = cs_get_file_hi(cs, typed_fname, &entry->hi_num);
1376 free(typed_fname);
1377 if(color != NULL)
1378 {
1379 cs_mix_colors(col, color);
1380 }
1381 }
1382
1383 /* File name format callback for column_view unit. */
1384 TSTATIC void
format_name(int id,const void * data,size_t buf_len,char buf[])1385 format_name(int id, const void *data, size_t buf_len, char buf[])
1386 {
1387 size_t len, i;
1388 dir_entry_t *child, *parent;
1389
1390 const column_data_t *cdt = data;
1391 view_t *view = cdt->view;
1392
1393 NameFormat fmt = NF_FULL;
1394 if(id == SK_BY_ROOT || (id == SK_BY_FILEROOT && !fentry_is_dir(cdt->entry)))
1395 {
1396 fmt = NF_ROOT;
1397 }
1398
1399 if(!flist_custom_active(view))
1400 {
1401 /* Just file name. */
1402 format_entry_name(cdt->entry, fmt, buf_len + 1, buf);
1403 return;
1404 }
1405
1406 if(!ui_view_displays_columns(view) || !cv_tree(view->custom.type))
1407 {
1408 /* File name possibly with path prefix. */
1409 get_short_path_of(view, cdt->entry, fmt, 0, buf_len + 1U, buf);
1410 return;
1411 }
1412
1413 /* File name possibly with path and tree prefixes. */
1414 len = 0U;
1415 child = cdt->entry;
1416 parent = child - child->child_pos;
1417 while(parent != child)
1418 {
1419 const char *prefix;
1420 /* To avoid prepending, strings are reversed here and whole tree prefix is
1421 * reversed below to compensate for it. */
1422 if(parent->child_count == child->child_pos + child->child_count)
1423 {
1424 prefix = (child == cdt->entry ? " --`" : " ");
1425 }
1426 else
1427 {
1428 prefix = (child == cdt->entry ? " --|" : " |");
1429 }
1430 (void)sstrappend(buf, &len, buf_len + 1U, prefix);
1431
1432 child = parent;
1433 parent -= parent->child_pos;
1434 }
1435
1436 for(i = 0U; i < len/2U; ++i)
1437 {
1438 const char t = buf[i];
1439 buf[i] = buf[len - 1U - i];
1440 buf[len - 1U - i] = t;
1441 }
1442
1443 get_short_path_of(view, cdt->entry, fmt, 1, buf_len + 1U - len, buf + len);
1444 *cdt->prefix_len = len;
1445 }
1446
1447 /* Primary name group format (first value of 'sortgroups' option) callback for
1448 * column_view unit. */
1449 static void
format_primary_group(int id,const void * data,size_t buf_len,char buf[])1450 format_primary_group(int id, const void *data, size_t buf_len, char buf[])
1451 {
1452 const column_data_t *cdt = data;
1453 const view_t *view = cdt->view;
1454 regmatch_t match = get_group_match(&view->primary_group, cdt->entry->name);
1455
1456 copy_str(buf, MIN(buf_len + 1U, (size_t)match.rm_eo - match.rm_so + 1U),
1457 cdt->entry->name + match.rm_so);
1458 }
1459
1460 /* File size format callback for column_view unit. */
1461 static void
format_size(int id,const void * data,size_t buf_len,char buf[])1462 format_size(int id, const void *data, size_t buf_len, char buf[])
1463 {
1464 char str[64];
1465 const column_data_t *cdt = data;
1466 const view_t *view = cdt->view;
1467 uint64_t size = DCACHE_UNKNOWN;
1468
1469 if(fentry_is_dir(cdt->entry))
1470 {
1471 uint64_t nitems;
1472 uint64_t *nitems_ptr = (cfg.view_dir_size == VDS_NITEMS ? &nitems : NULL);
1473 fentry_get_dir_info(view, cdt->entry, &size, nitems_ptr);
1474
1475 if(size == DCACHE_UNKNOWN && nitems_ptr != NULL)
1476 {
1477 snprintf(buf, buf_len + 1, " %d", (int)nitems);
1478 return;
1479 }
1480 }
1481
1482 if(size == DCACHE_UNKNOWN)
1483 {
1484 size = cdt->entry->size;
1485 }
1486
1487 str[0] = '\0';
1488 friendly_size_notation(size, sizeof(str), str);
1489 snprintf(buf, buf_len + 1, " %s", str);
1490 }
1491
1492 /* Item number format callback for column_view unit. */
1493 static void
format_nitems(int id,const void * data,size_t buf_len,char buf[])1494 format_nitems(int id, const void *data, size_t buf_len, char buf[])
1495 {
1496 const column_data_t *cdt = data;
1497 uint64_t nitems;
1498
1499 if(!fentry_is_dir(cdt->entry))
1500 {
1501 copy_str(buf, buf_len + 1, " 0");
1502 return;
1503 }
1504
1505 if(cdt->view->on_slow_fs)
1506 {
1507 copy_str(buf, buf_len + 1, " ?");
1508 return;
1509 }
1510
1511 nitems = fentry_get_nitems(cdt->view, cdt->entry);
1512 snprintf(buf, buf_len + 1, " %d", (int)nitems);
1513 }
1514
1515 /* File type (dir/reg/exe/link/...) format callback for column_view unit. */
1516 static void
format_type(int id,const void * data,size_t buf_len,char buf[])1517 format_type(int id, const void *data, size_t buf_len, char buf[])
1518 {
1519 const column_data_t *cdt = data;
1520 snprintf(buf, buf_len, " %s", get_type_str(cdt->entry->type));
1521 }
1522
1523 /* Symbolic link target format callback for column_view unit. */
1524 static void
format_target(int id,const void * data,size_t buf_len,char buf[])1525 format_target(int id, const void *data, size_t buf_len, char buf[])
1526 {
1527 const column_data_t *cdt = data;
1528 char full_path[PATH_MAX + 1];
1529
1530 buf[0] = '\0';
1531
1532 if(cdt->entry->type != FT_LINK || !symlinks_available())
1533 {
1534 return;
1535 }
1536
1537 get_full_path_of(cdt->entry, sizeof(full_path), full_path);
1538 (void)get_link_target(full_path, buf, buf_len);
1539 }
1540
1541 /* File or directory extension format callback for column_view unit. */
1542 static void
format_ext(int id,const void * data,size_t buf_len,char buf[])1543 format_ext(int id, const void *data, size_t buf_len, char buf[])
1544 {
1545 const column_data_t *cdt = data;
1546 const char *const ext = get_ext(cdt->entry->name);
1547 copy_str(buf, buf_len + 1, ext);
1548 }
1549
1550 /* File-only extension format callback for column_view unit. */
1551 static void
format_fileext(int id,const void * data,size_t buf_len,char buf[])1552 format_fileext(int id, const void *data, size_t buf_len, char buf[])
1553 {
1554 const column_data_t *cdt = data;
1555
1556 if(!fentry_is_dir(cdt->entry))
1557 {
1558 format_ext(id, data, buf_len, buf);
1559 }
1560 else
1561 {
1562 *buf = '\0';
1563 }
1564 }
1565
1566 /* File modification/access/change date format callback for column_view unit. */
1567 static void
format_time(int id,const void * data,size_t buf_len,char buf[])1568 format_time(int id, const void *data, size_t buf_len, char buf[])
1569 {
1570 struct tm *tm_ptr;
1571 const column_data_t *cdt = data;
1572
1573 switch(id)
1574 {
1575 case SK_BY_TIME_MODIFIED:
1576 tm_ptr = localtime(&cdt->entry->mtime);
1577 break;
1578 case SK_BY_TIME_ACCESSED:
1579 tm_ptr = localtime(&cdt->entry->atime);
1580 break;
1581 case SK_BY_TIME_CHANGED:
1582 tm_ptr = localtime(&cdt->entry->ctime);
1583 break;
1584
1585 default:
1586 assert(0 && "Unknown sort by time type");
1587 tm_ptr = NULL;
1588 break;
1589 }
1590
1591 if(tm_ptr != NULL)
1592 {
1593 strftime(buf, buf_len + 1, cfg.time_format, tm_ptr);
1594 }
1595 else
1596 {
1597 buf[0] = '\0';
1598 }
1599 }
1600
1601 /* Directory vs. file type format callback for column_view unit. */
1602 static void
format_dir(int id,const void * data,size_t buf_len,char buf[])1603 format_dir(int id, const void *data, size_t buf_len, char buf[])
1604 {
1605 const column_data_t *cdt = data;
1606 const char *type = fentry_is_dir(cdt->entry) ? "dir" : "file";
1607 snprintf(buf, buf_len, " %s", type);
1608 }
1609
1610 #ifndef _WIN32
1611
1612 /* File group id/name format callback for column_view unit. */
1613 static void
format_group(int id,const void * data,size_t buf_len,char buf[])1614 format_group(int id, const void *data, size_t buf_len, char buf[])
1615 {
1616 const column_data_t *cdt = data;
1617
1618 buf[0] = ' ';
1619 get_gid_string(cdt->entry, id == SK_BY_GROUP_ID, buf_len - 1, buf + 1);
1620 }
1621
1622 /* File owner id/name format callback for column_view unit. */
1623 static void
format_owner(int id,const void * data,size_t buf_len,char buf[])1624 format_owner(int id, const void *data, size_t buf_len, char buf[])
1625 {
1626 const column_data_t *cdt = data;
1627
1628 buf[0] = ' ';
1629 get_uid_string(cdt->entry, id == SK_BY_OWNER_ID, buf_len - 1, buf + 1);
1630 }
1631
1632 /* File mode format callback for column_view unit. */
1633 static void
format_mode(int id,const void * data,size_t buf_len,char buf[])1634 format_mode(int id, const void *data, size_t buf_len, char buf[])
1635 {
1636 const column_data_t *cdt = data;
1637 snprintf(buf, buf_len, " %o", cdt->entry->mode);
1638 }
1639
1640 /* File permissions mask format callback for column_view unit. */
1641 static void
format_perms(int id,const void * data,size_t buf_len,char buf[])1642 format_perms(int id, const void *data, size_t buf_len, char buf[])
1643 {
1644 const column_data_t *cdt = data;
1645 get_perm_string(buf, buf_len, cdt->entry->mode);
1646 }
1647
1648 /* Hard link count format callback for column_view unit. */
1649 static void
format_nlinks(int id,const void * data,size_t buf_len,char buf[])1650 format_nlinks(int id, const void *data, size_t buf_len, char buf[])
1651 {
1652 const column_data_t *cdt = data;
1653 snprintf(buf, buf_len, "%lu", (unsigned long)cdt->entry->nlinks);
1654 }
1655
1656 /* Inode number format callback for column_view unit. */
1657 static void
format_inode(int id,const void * data,size_t buf_len,char buf[])1658 format_inode(int id, const void *data, size_t buf_len, char buf[])
1659 {
1660 const column_data_t *cdt = data;
1661 snprintf(buf, buf_len, "%lu", (unsigned long)cdt->entry->inode);
1662 }
1663
1664 #endif
1665
1666 /* File identifier on comparisons format callback for column_view unit. */
1667 static void
format_id(int id,const void * data,size_t buf_len,char buf[])1668 format_id(int id, const void *data, size_t buf_len, char buf[])
1669 {
1670 const column_data_t *cdt = data;
1671 snprintf(buf, buf_len, "#%d", cdt->entry->id);
1672 }
1673
1674 void
fview_set_lsview(view_t * view,int enabled)1675 fview_set_lsview(view_t *view, int enabled)
1676 {
1677 if(view->ls_view != enabled)
1678 {
1679 view->ls_view = enabled;
1680 ui_view_schedule_redraw(view);
1681 }
1682 }
1683
1684 int
fview_is_transposed(const view_t * view)1685 fview_is_transposed(const view_t *view)
1686 {
1687 return (!ui_view_displays_columns(view) && view->ls_transposed);
1688 }
1689
1690 void
fview_set_millerview(view_t * view,int enabled)1691 fview_set_millerview(view_t *view, int enabled)
1692 {
1693 if(view->miller_view != enabled)
1694 {
1695 view->miller_view = enabled;
1696 ui_view_schedule_redraw(view);
1697 }
1698 }
1699
1700 /* Returns width of one column in the view. */
1701 static size_t
calculate_column_width(view_t * view)1702 calculate_column_width(view_t *view)
1703 {
1704 const int column_gap = (cfg.extra_padding ? 2 : 1);
1705 if(view->max_filename_width == 0)
1706 {
1707 view->max_filename_width = get_max_filename_width(view);
1708 }
1709 return MIN(view->max_filename_width + column_gap,
1710 (size_t)ui_view_available_width(view));
1711 }
1712
1713 void
fview_update_geometry(view_t * view)1714 fview_update_geometry(view_t *view)
1715 {
1716 view->column_count = calculate_columns_count(view);
1717 view->run_size = fview_is_transposed(view) ? view->window_rows
1718 : view->column_count;
1719 view->window_cells = view->column_count*view->window_rows;
1720 }
1721
1722 void
fview_dir_updated(view_t * view)1723 fview_dir_updated(view_t *view)
1724 {
1725 view->local_cs = cs_load_local(view == &lwin, view->curr_dir);
1726 }
1727
1728 void
fview_list_updated(view_t * view)1729 fview_list_updated(view_t *view)
1730 {
1731 /* Invalidate maximum file name widths cache. */
1732 view->max_filename_width = 0;
1733 /* Even if position will remain the same, we might need to redraw it. */
1734 invalidate_cursor_pos_cache(view);
1735 }
1736
1737 /* Evaluates number of columns in the view. Returns the number. */
1738 static size_t
calculate_columns_count(view_t * view)1739 calculate_columns_count(view_t *view)
1740 {
1741 if(!ui_view_displays_columns(view))
1742 {
1743 const size_t column_width = calculate_column_width(view);
1744 return ui_view_available_width(view)/column_width;
1745 }
1746 return 1U;
1747 }
1748
1749 /* Finds maximum filename width (length in character positions on the screen)
1750 * among all entries of the view. Returns the width. */
1751 static size_t
get_max_filename_width(const view_t * view)1752 get_max_filename_width(const view_t *view)
1753 {
1754 size_t max_len = 0UL;
1755 int i;
1756 for(i = 0; i < view->list_rows; ++i)
1757 {
1758 const size_t name_len = get_filename_width(view, i);
1759 if(name_len > max_len)
1760 {
1761 max_len = name_len;
1762 }
1763 }
1764 return max_len;
1765 }
1766
1767 /* Gets filename width (length in character positions on the screen) of ith
1768 * entry of the view. Returns the width. */
1769 static size_t
get_filename_width(const view_t * view,int i)1770 get_filename_width(const view_t *view, int i)
1771 {
1772 const dir_entry_t *const entry = &view->dir_entry[i];
1773 size_t name_len;
1774 if(flist_custom_active(view))
1775 {
1776 char name[NAME_MAX + 1];
1777 /* XXX: should this be formatted name?. */
1778 get_short_path_of(view, entry, NF_NONE, 0, sizeof(name), name);
1779 name_len = utf8_strsw(name);
1780 }
1781 else
1782 {
1783 name_len = utf8_strsw(entry->name);
1784 }
1785 return name_len + get_filetype_decoration_width(entry);
1786 }
1787
1788 /* Retrieves additional number of characters which are needed to display names
1789 * of the entry. Returns the number. */
1790 static size_t
get_filetype_decoration_width(const dir_entry_t * entry)1791 get_filetype_decoration_width(const dir_entry_t *entry)
1792 {
1793 const char *prefix, *suffix;
1794 ui_get_decors(entry, &prefix, &suffix);
1795 return utf8_strsw(prefix) + utf8_strsw(suffix);
1796 }
1797
1798 void
fview_position_updated(view_t * view)1799 fview_position_updated(view_t *view)
1800 {
1801 const int old_top = view->top_line;
1802 const int old_curr = view->curr_line;
1803
1804 if(view->curr_line > view->list_rows - 1)
1805 {
1806 view->curr_line = view->list_rows - 1;
1807 }
1808
1809 if(curr_stats.load_stage < 1 || !window_shows_dirlist(view))
1810 {
1811 return;
1812 }
1813
1814 if(view == other_view)
1815 {
1816 invalidate_cursor_pos_cache(view);
1817 if(move_curr_line(view))
1818 {
1819 draw_dir_list(view);
1820 }
1821 else
1822 {
1823 redraw_cell(view, old_top, old_curr, 0);
1824 fview_draw_inactive_cursor(view);
1825 }
1826 return;
1827 }
1828
1829 if(curr_stats.load_stage < 2)
1830 {
1831 return;
1832 }
1833
1834 int redraw = move_curr_line(view);
1835 int up_to_date = cache_cursor_pos(view);
1836 if(!redraw && up_to_date)
1837 {
1838 return;
1839 }
1840
1841 if(redraw)
1842 {
1843 draw_dir_list(view);
1844 }
1845 else
1846 {
1847 redraw_cell(view, old_top, old_curr, 0);
1848 redraw_cell(view, view->top_line, view->curr_line, 1);
1849 draw_right_column(view);
1850 }
1851
1852 refresh_view_win(view);
1853 ui_stat_update(view, 0);
1854
1855 if(view == curr_view)
1856 {
1857 /* We're updating view non-lazily above, so doing the same with the
1858 * ruler. */
1859 ui_ruler_update(view, 0);
1860
1861 position_hardware_cursor(view);
1862
1863 if(curr_stats.preview.on)
1864 {
1865 qv_draw(view);
1866 }
1867 }
1868 }
1869
1870 /* Compares current cursor position against previously cached one and updates
1871 * the cache if necessary. Returns non-zero if cache is up to date, otherwise
1872 * zero is returned. */
1873 static int
cache_cursor_pos(view_t * view)1874 cache_cursor_pos(view_t *view)
1875 {
1876 char path[PATH_MAX + 1];
1877 get_current_full_path(view, sizeof(path), path);
1878
1879 if(view->list_pos == view->last_seen_pos &&
1880 view->curr_line == view->last_curr_line &&
1881 view->last_curr_file != NULL &&
1882 strcmp(view->last_curr_file, path) == 0)
1883 {
1884 return 1;
1885 }
1886
1887 view->last_seen_pos = view->list_pos;
1888 view->last_curr_line = view->curr_line;
1889 replace_string(&view->last_curr_file, path);
1890 return 0;
1891 }
1892
1893 /* Invalidates cache of current cursor position for the specified view. */
1894 static void
invalidate_cursor_pos_cache(view_t * view)1895 invalidate_cursor_pos_cache(view_t *view)
1896 {
1897 view->last_seen_pos = -1;
1898 }
1899
1900 /* Moves hardware cursor to the beginning of the name of current entry. */
1901 static void
position_hardware_cursor(view_t * view)1902 position_hardware_cursor(view_t *view)
1903 {
1904 size_t col_width, col_count;
1905 int current_line, column_offset;
1906 char buf[view->window_cols + 1];
1907
1908 size_t prefix_len = 0U;
1909 const column_data_t cdt = {
1910 .view = view,
1911 .entry = get_current_entry(view),
1912 .prefix_len = &prefix_len,
1913 };
1914
1915 if(cdt.entry == NULL)
1916 {
1917 /* Happens in tests. */
1918 return;
1919 }
1920
1921 calculate_table_conf(view, &col_count, &col_width);
1922 current_line = view->curr_line/col_count;
1923 column_offset = ui_view_left_reserved(view)
1924 + (view->curr_line%col_count)*col_width;
1925 format_name(SK_BY_NAME, &cdt, sizeof(buf) - 1U, buf);
1926
1927 checked_wmove(view->win, current_line,
1928 (cfg.extra_padding != 0) + column_offset + prefix_len);
1929 }
1930
1931 /* Returns non-zero if redraw is needed. */
1932 static int
move_curr_line(view_t * view)1933 move_curr_line(view_t *view)
1934 {
1935 int redraw = 0;
1936 int pos = view->list_pos;
1937 int last;
1938 size_t col_width, col_count;
1939 columns_t *columns;
1940
1941 if(pos < 1)
1942 pos = 0;
1943
1944 if(pos > view->list_rows - 1)
1945 pos = view->list_rows - 1;
1946
1947 if(pos == -1)
1948 return 0;
1949
1950 view->list_pos = pos;
1951
1952 if(view->curr_line > view->list_rows - 1)
1953 view->curr_line = view->list_rows - 1;
1954
1955 view->top_line = calculate_top_position(view, view->top_line);
1956
1957 last = fpos_get_last_visible_cell(view);
1958 if(view->top_line <= pos && pos <= last)
1959 {
1960 view->curr_line = pos - view->top_line;
1961 }
1962 else if(pos > last)
1963 {
1964 scroll_down(view, pos - last);
1965 redraw++;
1966 }
1967 else if(pos < view->top_line)
1968 {
1969 scroll_up(view, view->top_line - pos);
1970 redraw++;
1971 }
1972
1973 if(consider_scroll_offset(view))
1974 {
1975 redraw++;
1976 }
1977
1978 calculate_table_conf(view, &col_count, &col_width);
1979 /* Columns might be NULL in tests. */
1980 columns = get_view_columns(view, 0);
1981 if(columns != NULL && !columns_matches_width(columns, col_width))
1982 {
1983 redraw++;
1984 }
1985
1986 return redraw != 0 || (view->num_type & NT_REL);
1987 }
1988
1989 void
fview_sorting_updated(view_t * view)1990 fview_sorting_updated(view_t *view)
1991 {
1992 reset_view_columns(view);
1993 }
1994
1995 /* Reinitializes view columns. */
1996 static void
reset_view_columns(view_t * view)1997 reset_view_columns(view_t *view)
1998 {
1999 if(!ui_view_displays_columns(view) ||
2000 (curr_stats.restart_in_progress && flist_custom_active(view) &&
2001 ui_view_unsorted(view)))
2002 {
2003 return;
2004 }
2005
2006 if(view->view_columns[0] == '\0')
2007 {
2008 column_info_t column_info = {
2009 .column_id = SK_BY_NAME, .full_width = 0UL, .text_width = 0UL,
2010 .align = AT_LEFT, .sizing = ST_AUTO, .cropping = CT_NONE,
2011 };
2012
2013 columns_clear(view->columns);
2014 columns_add_column(view->columns, column_info);
2015
2016 column_info.column_id = get_secondary_key((SortingKey)abs(view->sort[0]));
2017 column_info.align = AT_RIGHT;
2018 columns_add_column(view->columns, column_info);
2019 }
2020 else if(strstr(view->view_columns, "{}") != NULL)
2021 {
2022 load_view_columns_option(view, view->view_columns);
2023 }
2024 }
2025
2026 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
2027 /* vim: set cinoptions+=t0 filetype=c : */
2028