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