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 = >abs[current_gtab];
311 update_string(>ab->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(>abs[current_gtab]);
407 assign_preview(>abs[current_gtab].preview, &curr_stats.preview);
408
409 assign_view(&lwin, >abs[idx].left.tabs[gtabs[idx].left.current].view);
410 assign_view(&rwin, >abs[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, >abs[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 = >abs[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(>ab->left);
543 free_pane_tabs(>ab->right);
544 free_preview(>ab->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 = >abs[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 ? >ab->left.tabs[gtab->left.current].view
673 : >ab->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 = >abs[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 = >abs[current_gtab];
732 return (side == &lwin ? >ab->left : >ab->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 = >abs[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(>ab->left, path, lview);
778 count += count_pane_visitors(>ab->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(>abs[current_gtab].left, &lwin);
816 normalize_pane_tabs(>abs[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 = >abs[idx];
882 apply_layout(gtab, layout);
883
884 *left = >ab->left.tabs[0].view;
885 *right = >ab->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