1 /* vifm
2  * Copyright (C) 2018 xaizek.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18 
19 #include "tabs.h"
20 
21 #include <assert.h> /* assert() */
22 #include <stdlib.h> /* free() */
23 #include <string.h> /* memmove() */
24 
25 #include "../cfg/config.h"
26 #include "../engine/autocmds.h"
27 #include "../modes/view.h"
28 #include "../utils/darray.h"
29 #include "../utils/filter.h"
30 #include "../utils/matcher.h"
31 #include "../utils/path.h"
32 #include "../utils/str.h"
33 #include "../utils/utils.h"
34 #include "../filelist.h"
35 #include "../flist_hist.h"
36 #include "../opt_handlers.h"
37 #include "../status.h"
38 #include "fileview.h"
39 #include "ui.h"
40 
41 /* Pane-specific tab (contains information about only one view). */
42 typedef struct
43 {
44 	view_t view;            /* Buffer holding state of the view when it's
45 	                           hidden. */
46 	preview_t preview;      /* Information about state of the quickview. */
47 	char *name;             /* Name of the tab.  Might be NULL. */
48 	unsigned int init_mark; /* Which initialization this tab has seen. */
49 }
50 pane_tab_t;
51 
52 /* Collection of pane tabs. */
53 typedef struct
54 {
55 	pane_tab_t *tabs;        /* List of tabs. */
56 	DA_INSTANCE_FIELD(tabs); /* Declarations to enable use of DA_* on tabs. */
57 	int current;             /* Index of the current tab. */
58 }
59 pane_tabs_t;
60 
61 /* Global tab (contains both views as well as layout). */
62 typedef struct
63 {
64 	pane_tabs_t left;  /* Collection of tabs for the left pane. */
65 	pane_tabs_t right; /* Collection of tabs for the right pane. */
66 
67 	int active_pane;        /* 0 -- left, 1 -- right. */
68 	int only_mode;          /* Whether in single-pane mode. */
69 	SPLIT split;            /* State of window split. */
70 	int splitter_pos;       /* Splitter position. */
71 	double splitter_ratio;  /* Relative position of the splitter. */
72 	preview_t preview;      /* Information about state of the quickview. */
73 	char *name;             /* Name of the tab.  Might be NULL. */
74 	unsigned int init_mark; /* Which initialization this tab has seen. */
75 }
76 global_tab_t;
77 
78 static int tabs_new_global(const char name[], const char path[], int at,
79 		int clean);
80 static pane_tab_t * tabs_new_pane(pane_tabs_t *ptabs, view_t *side,
81 		const char name[], const char path[], int at, int clean);
82 static void clone_view(view_t *dst, view_t *side, const char path[], int clean);
83 static void clone_viewport(view_t *dst, const view_t *src);
84 static void tabs_goto_pane(int idx);
85 static void tabs_goto_global(int idx);
86 static void capture_global_state(global_tab_t *gtab);
87 static void assign_preview(preview_t *dst, const preview_t *src);
88 static void assign_view(view_t *dst, const view_t *src);
89 static void free_global_tab(global_tab_t *gtab);
90 static void free_pane_tabs(pane_tabs_t *ptabs);
91 static void free_pane_tab(pane_tab_t *ptab);
92 static void free_preview(preview_t *preview);
93 static pane_tabs_t * get_pane_tabs(const view_t *side);
94 static int get_pane_tab(view_t *side, int idx, tab_info_t *tab_info);
95 static int get_global_tab(view_t *side, int idx, tab_info_t *tab_info,
96 		int return_active);
97 static int count_pane_visitors(const pane_tabs_t *ptabs, const char path[],
98 		const view_t *view);
99 static void normalize_pane_tabs(const pane_tabs_t *ptabs, view_t *side);
100 static void apply_layout(global_tab_t *gtab, const tab_layout_t *layout);
101 
102 /* Number of time this unit was (re-)initialized. */
103 static unsigned int init_counter;
104 /* List of global tabs. */
105 static global_tab_t *gtabs;
106 /* Declarations to enable use of DA_* on gtabs. */
107 static DA_INSTANCE(gtabs);
108 /* Index of current global tab. */
109 static int current_gtab;
110 
111 void
tabs_init(void)112 tabs_init(void)
113 {
114 	assert(init_counter == 0 && "Mustn't initialize more than once.");
115 	const int result = tabs_new_global(NULL, NULL, 0, 0);
116 	assert(result == 0 && "Failed to initialize first tab.");
117 	(void)result;
118 
119 	init_counter = 1;
120 }
121 
122 void
tabs_reinit(void)123 tabs_reinit(void)
124 {
125 	tabs_only(&lwin);
126 	tabs_only(&rwin);
127 
128 	++init_counter;
129 }
130 
131 int
tabs_new(const char name[],const char path[])132 tabs_new(const char name[], const char path[])
133 {
134 	if(cfg.pane_tabs)
135 	{
136 		pane_tabs_t *const ptabs = get_pane_tabs(curr_view);
137 		int idx = DA_SIZE(ptabs->tabs);
138 		pane_tab_t *ptab = tabs_new_pane(ptabs, curr_view, name, path, idx, 0);
139 		if(ptab == NULL)
140 		{
141 			return 1;
142 		}
143 
144 		ptab->preview.on = curr_stats.preview.on;
145 		tabs_goto_pane(idx);
146 		return 0;
147 	}
148 
149 	if(tabs_new_global(name, path, current_gtab + 1, 0) == 0)
150 	{
151 		tabs_goto_global(current_gtab + 1);
152 		return 0;
153 	}
154 	return 1;
155 }
156 
157 /* Creates new global tab at position with the specified name, which might be
158  * NULL.  Path specifies location of active pane and can be NULL.  Non-zero
159  * clean parameter requests clean cloning.  Returns zero on success, otherwise
160  * non-zero is returned. */
161 static int
tabs_new_global(const char name[],const char path[],int at,int clean)162 tabs_new_global(const char name[], const char path[], int at, int clean)
163 {
164 	assert(at >= 0 && "Global tab position is too small.");
165 	assert(at <= (int)DA_SIZE(gtabs) && "Global tab position is too big.");
166 
167 	global_tab_t new_tab = {};
168 
169 	if(DA_EXTEND(gtabs) == NULL)
170 	{
171 		return 1;
172 	}
173 
174 	const char *leftPath = (curr_view == &lwin ? path : NULL);
175 	const char *rightPath = (curr_view == &rwin ? path : NULL);
176 	if(tabs_new_pane(&new_tab.left, &lwin, NULL, leftPath, 0, clean) == NULL ||
177 			tabs_new_pane(&new_tab.right, &rwin, NULL, rightPath, 0, clean) == NULL)
178 	{
179 		free_global_tab(&new_tab);
180 		return 1;
181 	}
182 	update_string(&new_tab.name, name);
183 	capture_global_state(&new_tab);
184 
185 	DA_COMMIT(gtabs);
186 
187 	memmove(gtabs + at + 1, gtabs + at,
188 			sizeof(*gtabs)*(DA_SIZE(gtabs) - (at + 1)));
189 	gtabs[at] = new_tab;
190 	return 0;
191 }
192 
193 /* Creates new tab at position with the specified name, which might be NULL.
194  * Path specifies location of active pane and can be NULL.  Non-zero clean
195  * parameter requests clean cloning.  Returns newly created tab on success or
196  * NULL on error. */
197 static pane_tab_t *
tabs_new_pane(pane_tabs_t * ptabs,view_t * side,const char name[],const char path[],int at,int clean)198 tabs_new_pane(pane_tabs_t *ptabs, view_t *side, const char name[],
199 		const char path[], int at, int clean)
200 {
201 	assert(at >= 0 && "Pane tab position is too small.");
202 	assert(at <= (int)DA_SIZE(ptabs->tabs) && "Pane tab position is too big.");
203 
204 	pane_tab_t new_tab = {};
205 
206 	if(DA_EXTEND(ptabs->tabs) == NULL)
207 	{
208 		return NULL;
209 	}
210 
211 	DA_COMMIT(ptabs->tabs);
212 
213 	/* We're called from tabs_init() and just need to create internal structures
214 	 * without cloning data (or it will leak). */
215 	if(DA_SIZE(gtabs) == 0U)
216 	{
217 		ptabs->tabs[0] = new_tab;
218 		return &ptabs->tabs[0];
219 	}
220 
221 	clone_view(&new_tab.view, side, path, clean);
222 	update_string(&new_tab.name, name);
223 
224 	if(DA_SIZE(ptabs->tabs) == 1U)
225 	{
226 		ptabs->tabs[0] = new_tab;
227 		return &ptabs->tabs[0];
228 	}
229 
230 	memmove(ptabs->tabs + at + 1, ptabs->tabs + at,
231 			sizeof(*ptabs->tabs)*(DA_SIZE(ptabs->tabs) - (at + 1)));
232 	ptabs->tabs[at] = new_tab;
233 
234 	return &ptabs->tabs[at];
235 }
236 
237 /* Clones one view into another.  Path specifies new location and can be NULL.
238  * The destination view is assumed to not own any resources.  Clean cloning
239  * produces a copy without populating file list and with empty history. */
240 static void
clone_view(view_t * dst,view_t * side,const char path[],int clean)241 clone_view(view_t *dst, view_t *side, const char path[], int clean)
242 {
243 	strcpy(dst->curr_dir, path == NULL ? flist_get_dir(side) : path);
244 	dst->timestamps_mutex = side->timestamps_mutex;
245 
246 	flist_init_view(dst);
247 	/* This is for replace_dir_entries() below due to check in fentry_free(),
248 	 * should adjust the check instead? */
249 	dst->dir_entry[0].origin = side->curr_dir;
250 
251 	clone_local_options(side, dst, 1);
252 	reset_local_options(dst);
253 
254 	matcher_free(dst->manual_filter);
255 	dst->manual_filter = matcher_clone(side->manual_filter);
256 	filter_assign(&dst->auto_filter, &side->auto_filter);
257 	dst->prev_invert = side->prev_invert;
258 	dst->invert = side->invert;
259 
260 	/* Clone current entry even though we populate file list later to give
261 	 * reloading reference point for cursor. */
262 	replace_dir_entries(dst, &dst->dir_entry, &dst->list_rows,
263 			get_current_entry(side), 1);
264 	dst->list_pos = 0;
265 	clone_viewport(dst, side);
266 
267 	flist_hist_resize(dst, cfg.history_len);
268 
269 	if(!clean)
270 	{
271 		flist_hist_clone(dst, side);
272 		if(path != NULL && !flist_custom_active(side))
273 		{
274 			/* Record location we're leaving. */
275 			flist_hist_setup(dst, side->curr_dir, get_current_file_name(side),
276 					side->list_pos - side->top_line, -1);
277 		}
278 
279 		(void)populate_dir_list(dst, path == NULL);
280 		/* Redirect origins from tab's view to lwin or rwin, which is how they
281 		 * should end up after loading a tab into lwin or rwin. */
282 		flist_update_origins(dst, &dst->curr_dir[0], &side->curr_dir[0]);
283 
284 		/* Record new location. */
285 		flist_hist_save(dst);
286 	}
287 }
288 
289 /* Clones viewport configuration. */
290 static void
clone_viewport(view_t * dst,const view_t * src)291 clone_viewport(view_t *dst, const view_t *src)
292 {
293 	dst->curr_line = src->curr_line;
294 	dst->top_line = src->top_line;
295 	dst->window_rows = src->window_rows;
296 	dst->window_cols = src->window_cols;
297 	dst->window_cells = src->window_cells;
298 }
299 
300 void
tabs_rename(view_t * side,const char name[])301 tabs_rename(view_t *side, const char name[])
302 {
303 	if(cfg.pane_tabs)
304 	{
305 		pane_tabs_t *const ptabs = get_pane_tabs(side);
306 		update_string(&ptabs->tabs[ptabs->current].name, name);;
307 	}
308 	else
309 	{
310 		global_tab_t *const gtab = &gtabs[current_gtab];
311 		update_string(&gtab->name, name);
312 	}
313 }
314 
315 void
tabs_goto(int idx)316 tabs_goto(int idx)
317 {
318 	if(cfg.pane_tabs)
319 	{
320 		tabs_goto_pane(idx);
321 	}
322 	else
323 	{
324 		tabs_goto_global(idx);
325 	}
326 }
327 
328 /* Switches to pane tab specified by its index if the index is valid. */
329 static void
tabs_goto_pane(int idx)330 tabs_goto_pane(int idx)
331 {
332 	pane_tabs_t *const ptabs = get_pane_tabs(curr_view);
333 
334 	if(ptabs->current == idx)
335 	{
336 		return;
337 	}
338 
339 	if(idx < 0 || idx >= (int)DA_SIZE(ptabs->tabs))
340 	{
341 		return;
342 	}
343 
344 	ui_qv_cleanup_if_needed();
345 
346 	const int prev = ptabs->current;
347 
348 	if(curr_stats.load_stage >= 3 && !curr_stats.restart_in_progress)
349 	{
350 		/* Mark the tab we started at as visited. */
351 		ptabs->tabs[ptabs->current].init_mark = init_counter;
352 	}
353 
354 	ptabs->tabs[ptabs->current].view = *curr_view;
355 	assign_preview(&ptabs->tabs[ptabs->current].preview, &curr_stats.preview);
356 	assign_view(curr_view, &ptabs->tabs[idx].view);
357 	assign_preview(&curr_stats.preview, &ptabs->tabs[idx].preview);
358 	ptabs->current = idx;
359 
360 	stats_set_quickview(curr_stats.preview.on);
361 	ui_view_schedule_redraw(curr_view);
362 
363 	load_view_options(curr_view);
364 
365 	if(ptabs->tabs[ptabs->current].init_mark != init_counter &&
366 			(curr_stats.load_stage >= 3 || curr_stats.load_stage < 0))
367 	{
368 		if(ptabs->tabs[ptabs->current].init_mark == 0)
369 		{
370 			clone_viewport(curr_view, &ptabs->tabs[prev].view);
371 			populate_dir_list(curr_view, 0);
372 			fview_dir_updated(curr_view);
373 		}
374 		vle_aucmd_execute("DirEnter", flist_get_dir(curr_view), curr_view);
375 		ptabs->tabs[ptabs->current].init_mark = init_counter;
376 	}
377 
378 	(void)vifm_chdir(flist_get_dir(curr_view));
379 }
380 
381 /* Switches to global tab specified by its index if the index is valid. */
382 static void
tabs_goto_global(int idx)383 tabs_goto_global(int idx)
384 {
385 	if(current_gtab == idx)
386 	{
387 		return;
388 	}
389 
390 	if(idx < 0 || idx >= (int)DA_SIZE(gtabs))
391 	{
392 		return;
393 	}
394 
395 	ui_qv_cleanup_if_needed();
396 	modview_hide_graphics();
397 
398 	if(curr_stats.load_stage >= 3 && !curr_stats.restart_in_progress)
399 	{
400 		/* Mark the tab we started at as visited. */
401 		gtabs[current_gtab].init_mark = init_counter;
402 	}
403 
404 	gtabs[current_gtab].left.tabs[gtabs[current_gtab].left.current].view = lwin;
405 	gtabs[current_gtab].right.tabs[gtabs[current_gtab].right.current].view = rwin;
406 	capture_global_state(&gtabs[current_gtab]);
407 	assign_preview(&gtabs[current_gtab].preview, &curr_stats.preview);
408 
409 	assign_view(&lwin, &gtabs[idx].left.tabs[gtabs[idx].left.current].view);
410 	assign_view(&rwin, &gtabs[idx].right.tabs[gtabs[idx].right.current].view);
411 	if(gtabs[idx].active_pane != (curr_view == &rwin))
412 	{
413 		swap_view_roles();
414 	}
415 	curr_stats.number_of_windows = (gtabs[idx].only_mode ? 1 : 2);
416 	curr_stats.split = gtabs[idx].split;
417 	if(gtabs[idx].splitter_ratio == -1 || gtabs[idx].splitter_pos < 0)
418 	{
419 		stats_set_splitter_pos(gtabs[idx].splitter_pos);
420 	}
421 	else
422 	{
423 		stats_set_splitter_ratio(gtabs[idx].splitter_ratio);
424 	}
425 	assign_preview(&curr_stats.preview, &gtabs[idx].preview);
426 
427 	current_gtab = idx;
428 
429 	stats_set_quickview(curr_stats.preview.on);
430 	ui_view_schedule_redraw(&lwin);
431 	ui_view_schedule_redraw(&rwin);
432 
433 	load_view_options(curr_view);
434 
435 	if(gtabs[current_gtab].init_mark != init_counter &&
436 			(curr_stats.load_stage >= 3 || curr_stats.load_stage < 0))
437 	{
438 		if(curr_stats.load_stage >= 3)
439 		{
440 			ui_resize_all();
441 		}
442 		if(gtabs[current_gtab].init_mark == 0)
443 		{
444 			populate_dir_list(&lwin, 0);
445 			populate_dir_list(&rwin, 0);
446 			fview_dir_updated(other_view);
447 			fview_dir_updated(curr_view);
448 		}
449 		vle_aucmd_execute("DirEnter", flist_get_dir(&lwin), &lwin);
450 		vle_aucmd_execute("DirEnter", flist_get_dir(&rwin), &rwin);
451 		gtabs[current_gtab].init_mark = init_counter;
452 	}
453 
454 	(void)vifm_chdir(flist_get_dir(curr_view));
455 }
456 
457 /* Records global state into a global tab structure. */
458 static void
capture_global_state(global_tab_t * gtab)459 capture_global_state(global_tab_t *gtab)
460 {
461 	tab_layout_t layout;
462 	tabs_layout_fill(&layout);
463 	apply_layout(gtab, &layout);
464 }
465 
466 /* Assigns one instance of preview_t to another managing dynamic resources on
467  * the way. */
468 static void
assign_preview(preview_t * dst,const preview_t * src)469 assign_preview(preview_t *dst, const preview_t *src)
470 {
471 	*dst = *src;
472 
473 	/* Memory allocation can fail here, but this will just mess up terminal a
474 	 * bit, which isn't really a problem given that we're probably out of
475 	 * memory. */
476 	update_string(&dst->cleanup_cmd, src->cleanup_cmd);
477 }
478 
479 /* Assigns a view preserving UI-related data. */
480 static void
assign_view(view_t * dst,const view_t * src)481 assign_view(view_t *dst, const view_t *src)
482 {
483 	WINDOW *win = dst->win;
484 	WINDOW *title = dst->title;
485 
486 	*dst = *src;
487 
488 	dst->win = win;
489 	dst->title = title;
490 }
491 
492 int
tabs_quit_on_close(void)493 tabs_quit_on_close(void)
494 {
495 	return (tabs_count(curr_view) == 1);
496 }
497 
498 void
tabs_close(void)499 tabs_close(void)
500 {
501 	/* XXX: FUSE filesystems aren't exited this way, but this might be OK because
502 	 *      usually we exit from them on explicit ".." by a user. */
503 
504 	if(cfg.pane_tabs)
505 	{
506 		pane_tabs_t *const ptabs = get_pane_tabs(curr_view);
507 		pane_tab_t *const ptab = &ptabs->tabs[ptabs->current];
508 		const int n = (int)DA_SIZE(ptabs->tabs);
509 		if(n != 1)
510 		{
511 			tabs_goto_pane(ptabs->current + (ptabs->current == n - 1 ? -1 : +1));
512 			if(ptabs->current > ptab - ptabs->tabs)
513 			{
514 				--ptabs->current;
515 			}
516 			free_pane_tab(ptab);
517 			DA_REMOVE(ptabs->tabs, ptab);
518 		}
519 	}
520 	else
521 	{
522 		global_tab_t *const gtab = &gtabs[current_gtab];
523 		const int n = (int)DA_SIZE(gtabs);
524 		if(n != 1)
525 		{
526 			int idx = (current_gtab == n - 1 ? current_gtab - 1 : current_gtab + 1);
527 			tabs_goto_global(idx);
528 			if(current_gtab > gtab - gtabs)
529 			{
530 				--current_gtab;
531 			}
532 			free_global_tab(gtab);
533 			DA_REMOVE(gtabs, gtab);
534 		}
535 	}
536 }
537 
538 /* Frees resources owned by a global tab. */
539 static void
free_global_tab(global_tab_t * gtab)540 free_global_tab(global_tab_t *gtab)
541 {
542 	free_pane_tabs(&gtab->left);
543 	free_pane_tabs(&gtab->right);
544 	free_preview(&gtab->preview);
545 	free(gtab->name);
546 }
547 
548 /* Frees resources owned by a collection of pane tabs. */
549 static void
free_pane_tabs(pane_tabs_t * ptabs)550 free_pane_tabs(pane_tabs_t *ptabs)
551 {
552 	size_t i;
553 	for(i = 0U; i < DA_SIZE(ptabs->tabs); ++i)
554 	{
555 		free_pane_tab(&ptabs->tabs[i]);
556 	}
557 	DA_REMOVE_ALL(ptabs->tabs);
558 }
559 
560 /* Frees resources owned by a pane tab. */
561 static void
free_pane_tab(pane_tab_t * ptab)562 free_pane_tab(pane_tab_t *ptab)
563 {
564 	flist_free_view(&ptab->view);
565 	free_preview(&ptab->preview);
566 	free(ptab->name);
567 }
568 
569 /* Frees resources owned by a preview state structure. */
570 static void
free_preview(preview_t * preview)571 free_preview(preview_t *preview)
572 {
573 	update_string(&preview->cleanup_cmd, NULL);
574 	modview_info_free(preview->explore);
575 }
576 
577 void
tabs_next(int n)578 tabs_next(int n)
579 {
580 	if(cfg.pane_tabs)
581 	{
582 		pane_tabs_t *const ptabs = get_pane_tabs(curr_view);
583 		const int count = (int)DA_SIZE(ptabs->tabs);
584 		tabs_goto_pane((ptabs->current + n)%count);
585 	}
586 	else
587 	{
588 		tabs_goto_global((current_gtab + 1)%(int)DA_SIZE(gtabs));
589 	}
590 }
591 
592 void
tabs_previous(int n)593 tabs_previous(int n)
594 {
595 	if(cfg.pane_tabs)
596 	{
597 		pane_tabs_t *const ptabs = get_pane_tabs(curr_view);
598 		const int count = (int)DA_SIZE(ptabs->tabs);
599 		tabs_goto_pane((ptabs->current + count - n)%count);
600 	}
601 	else
602 	{
603 		const int count = DA_SIZE(gtabs);
604 		tabs_goto_global((current_gtab + count - n)%count);
605 	}
606 }
607 
608 int
tabs_get(view_t * side,int idx,tab_info_t * tab_info)609 tabs_get(view_t *side, int idx, tab_info_t *tab_info)
610 {
611 	if(cfg.pane_tabs)
612 	{
613 		return get_pane_tab(side, idx, tab_info);
614 	}
615 
616 	return get_global_tab(side, idx, tab_info, 1);
617 }
618 
619 /* Fills *tab_info for the pane tab specified by its index and side.  Returns
620  * non-zero on success, otherwise zero is returned. */
621 static int
get_pane_tab(view_t * side,int idx,tab_info_t * tab_info)622 get_pane_tab(view_t *side, int idx, tab_info_t *tab_info)
623 {
624 	pane_tabs_t *const ptabs = get_pane_tabs(side);
625 	const int n = (int)DA_SIZE(ptabs->tabs);
626 	if(idx < 0 || idx >= n)
627 	{
628 		return 0;
629 	}
630 
631 	tabs_layout_fill(&tab_info->layout);
632 
633 	if(idx == ptabs->current)
634 	{
635 		tab_info->view = side;
636 	}
637 	else
638 	{
639 		tab_info->view = &ptabs->tabs[idx].view;
640 		tab_info->layout.preview = ptabs->tabs[idx].preview.on;
641 	}
642 
643 	tab_info->name = ptabs->tabs[idx].name;
644 	tab_info->last = (idx == n - 1);
645 	return 1;
646 }
647 
648 /* Fills *tab_info for the global tab specified by its index and side when
649  * return_active is zero, otherwise active one is used.  Returns non-zero on
650  * success, otherwise zero is returned. */
651 static int
get_global_tab(view_t * side,int idx,tab_info_t * tab_info,int return_active)652 get_global_tab(view_t *side, int idx, tab_info_t *tab_info, int return_active)
653 {
654 	const int n = (int)DA_SIZE(gtabs);
655 	if(idx < 0 || idx >= n)
656 	{
657 		return 0;
658 	}
659 
660 	global_tab_t *gtab = &gtabs[idx];
661 	tab_info->name = gtab->name;
662 	tab_info->last = (idx == n - 1);
663 
664 	if(idx == current_gtab)
665 	{
666 		tab_info->view = side;
667 		tabs_layout_fill(&tab_info->layout);
668 		return 1;
669 	}
670 	tab_info->view = (return_active && !gtab->active_pane)
671 	              || (!return_active && side == &lwin)
672 	               ? &gtab->left.tabs[gtab->left.current].view
673 	               : &gtab->right.tabs[gtab->right.current].view;
674 	tab_info->layout.active_pane = gtab->active_pane;
675 	tab_info->layout.only_mode = gtab->only_mode;
676 	tab_info->layout.split = gtab->split;
677 	tab_info->layout.splitter_pos = gtab->splitter_pos;
678 	tab_info->layout.splitter_ratio = gtab->splitter_ratio;
679 	tab_info->layout.preview = gtab->preview.on;
680 	return 1;
681 }
682 
683 int
tabs_current(const view_t * side)684 tabs_current(const view_t *side)
685 {
686 	return (cfg.pane_tabs ? get_pane_tabs(side)->current : current_gtab);
687 }
688 
689 int
tabs_count(const view_t * side)690 tabs_count(const view_t *side)
691 {
692 	if(cfg.pane_tabs)
693 	{
694 		pane_tabs_t *const ptabs = get_pane_tabs(side);
695 		return (int)DA_SIZE(ptabs->tabs);
696 	}
697 	return (int)DA_SIZE(gtabs);
698 }
699 
700 void
tabs_only(view_t * side)701 tabs_only(view_t *side)
702 {
703 	if(cfg.pane_tabs)
704 	{
705 		pane_tabs_t *const ptabs = get_pane_tabs(side);
706 		while(DA_SIZE(ptabs->tabs) != 1U)
707 		{
708 			pane_tab_t *const ptab = &ptabs->tabs[ptabs->current == 0 ? 1 : 0];
709 			ptabs->current -= (ptabs->current == 0 ? 0 : 1);
710 			free_pane_tab(ptab);
711 			DA_REMOVE(ptabs->tabs, ptab);
712 		}
713 	}
714 	else
715 	{
716 		while(DA_SIZE(gtabs) != 1U)
717 		{
718 			global_tab_t *const gtab = &gtabs[current_gtab == 0 ? 1 : 0];
719 			current_gtab -= (current_gtab == 0 ? 0 : 1);
720 			free_global_tab(gtab);
721 			DA_REMOVE(gtabs, gtab);
722 		}
723 	}
724 }
725 
726 /* Retrieves pane tab that corresponds to the specified view (must be &lwin or
727  * &rwin).  Returns the pane tab. */
728 static pane_tabs_t *
get_pane_tabs(const view_t * side)729 get_pane_tabs(const view_t *side)
730 {
731 	global_tab_t *const gtab = &gtabs[current_gtab];
732 	return (side == &lwin ? &gtab->left : &gtab->right);
733 }
734 
735 void
tabs_move(view_t * side,int where_to)736 tabs_move(view_t *side, int where_to)
737 {
738 	int future = MAX(0, MIN(tabs_count(side) - 1, where_to));
739 	const int current = tabs_current(side);
740 	const int from = (current <= future ? current + 1 : future);
741 	const int to = (current <= future ? current : future + 1);
742 
743 	/* Second check is for the case when the value was already truncated by MIN()
744 	 * above. */
745 	if(current < future && where_to < tabs_count(side))
746 	{
747 		--future;
748 	}
749 
750 	if(cfg.pane_tabs)
751 	{
752 		pane_tab_t *const ptabs = get_pane_tabs(side)->tabs;
753 		const pane_tab_t ptab = ptabs[current];
754 		memmove(ptabs + to, ptabs + from, sizeof(*ptabs)*abs(future - current));
755 		ptabs[future] = ptab;
756 		get_pane_tabs(side)->current = future;
757 	}
758 	else
759 	{
760 		const global_tab_t gtab = gtabs[current];
761 		memmove(gtabs + to, gtabs + from, sizeof(*gtabs)*abs(future - current));
762 		gtabs[future] = gtab;
763 		current_gtab = future;
764 	}
765 }
766 
767 int
tabs_visitor_count(const char path[])768 tabs_visitor_count(const char path[])
769 {
770 	int count = 0;
771 	int i;
772 	for(i = 0; i < (int)DA_SIZE(gtabs); ++i)
773 	{
774 		global_tab_t *const gtab = &gtabs[i];
775 		view_t *const lview = (i == current_gtab ? &lwin : NULL);
776 		view_t *const rview = (i == current_gtab ? &rwin : NULL);
777 		count += count_pane_visitors(&gtab->left, path, lview);
778 		count += count_pane_visitors(&gtab->right, path, rview);
779 	}
780 	return count;
781 }
782 
783 /* Counts number of pane tabs from the collection that are inside specified
784  * path.  When view parameter isn't NULL, it's a possible replacement view to
785  * use instead of the stored one.  Returns the count. */
786 static int
count_pane_visitors(const pane_tabs_t * ptabs,const char path[],const view_t * view)787 count_pane_visitors(const pane_tabs_t *ptabs, const char path[],
788 		const view_t *view)
789 {
790 	int count = 0;
791 	int i;
792 	for(i = 0; i < (int)DA_SIZE(ptabs->tabs); ++i)
793 	{
794 		const pane_tab_t *const ptab = &ptabs->tabs[i];
795 		const view_t *const v = (view != NULL && i == ptabs->current)
796 		                      ? view
797 		                      : &ptab->view;
798 		if(path_starts_with(v->curr_dir, path))
799 		{
800 			++count;
801 		}
802 	}
803 	return count;
804 }
805 
806 void
tabs_switch_panes(void)807 tabs_switch_panes(void)
808 {
809 	if(cfg.pane_tabs)
810 	{
811 		const pane_tabs_t tmp = gtabs[current_gtab].left;
812 		gtabs[current_gtab].left = gtabs[current_gtab].right;
813 		gtabs[current_gtab].right = tmp;
814 
815 		normalize_pane_tabs(&gtabs[current_gtab].left, &lwin);
816 		normalize_pane_tabs(&gtabs[current_gtab].right, &rwin);
817 	}
818 }
819 
820 /* Fixes data fields of views after they got moved from one pane to another.
821  * The side parameter indicates destination pane. */
822 static void
normalize_pane_tabs(const pane_tabs_t * ptabs,view_t * side)823 normalize_pane_tabs(const pane_tabs_t *ptabs, view_t *side)
824 {
825 	view_t tmp = *side;
826 	view_t *const other = (side == &rwin ? &lwin : &rwin);
827 	int i;
828 	for(i = 0; i < (int)DA_SIZE(ptabs->tabs); ++i)
829 	{
830 		if(i != ptabs->current)
831 		{
832 			view_t *const v = &ptabs->tabs[i].view;
833 			ui_swap_view_data(v, &tmp);
834 			*v = tmp;
835 			flist_update_origins(v, &other->curr_dir[0], &side->curr_dir[0]);
836 		}
837 	}
838 }
839 
840 int
tabs_enum(view_t * side,int idx,tab_info_t * tab_info)841 tabs_enum(view_t *side, int idx, tab_info_t *tab_info)
842 {
843 	return cfg.pane_tabs ? get_pane_tab(side, idx, tab_info)
844 	                     : get_global_tab(side, idx, tab_info, 0);
845 }
846 
847 int
tabs_enum_all(int idx,tab_info_t * tab_info)848 tabs_enum_all(int idx, tab_info_t *tab_info)
849 {
850 	if(tabs_enum(&lwin, idx, tab_info))
851 	{
852 		return 1;
853 	}
854 
855 	int offset = cfg.pane_tabs ? DA_SIZE(get_pane_tabs(&lwin)->tabs)
856 	                           : DA_SIZE(gtabs);
857 	return tabs_enum(&rwin, idx - offset, tab_info);
858 }
859 
860 void
tabs_layout_fill(tab_layout_t * layout)861 tabs_layout_fill(tab_layout_t *layout)
862 {
863 	layout->active_pane = (curr_view == &rwin);
864 	layout->only_mode = (curr_stats.number_of_windows == 1);
865 	layout->split = curr_stats.split;
866 	layout->splitter_pos = curr_stats.splitter_pos;
867 	layout->splitter_ratio = curr_stats.splitter_ratio;
868 	layout->preview = curr_stats.preview.on;
869 }
870 
871 int
tabs_setup_gtab(const char name[],const tab_layout_t * layout,view_t ** left,view_t ** right)872 tabs_setup_gtab(const char name[], const tab_layout_t *layout, view_t **left,
873 		view_t **right)
874 {
875 	int idx = DA_SIZE(gtabs);
876 	if(tabs_new_global(name, NULL, idx, 1) != 0)
877 	{
878 		return 1;
879 	}
880 
881 	global_tab_t *gtab = &gtabs[idx];
882 	apply_layout(gtab, layout);
883 
884 	*left = &gtab->left.tabs[0].view;
885 	*right = &gtab->right.tabs[0].view;
886 	return 0;
887 }
888 
889 /* Applies layout data to a global tab. */
890 static void
apply_layout(global_tab_t * gtab,const tab_layout_t * layout)891 apply_layout(global_tab_t *gtab, const tab_layout_t *layout)
892 {
893 	gtab->active_pane = layout->active_pane;
894 	gtab->only_mode = layout->only_mode;
895 	gtab->split = layout->split;
896 	gtab->splitter_pos = layout->splitter_pos;
897 	gtab->splitter_ratio = layout->splitter_ratio;
898 	gtab->preview.on = layout->preview;
899 }
900 
901 view_t *
tabs_setup_ptab(view_t * view,const char name[],int preview)902 tabs_setup_ptab(view_t *view, const char name[], int preview)
903 {
904 	pane_tabs_t *ptabs = get_pane_tabs(view);
905 	int idx = DA_SIZE(ptabs->tabs);
906 	pane_tab_t *ptab = tabs_new_pane(ptabs, view, name, NULL, idx, 1);
907 	if(ptab == NULL)
908 	{
909 		return NULL;
910 	}
911 
912 	ptab->preview.on = preview;
913 	return &ptab->view;
914 }
915 
916 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
917 /* vim: set cinoptions+=t0 : */
918