Skip to content

Commit b589c84

Browse files
committed
Make GUI tabs track Vim tabs in updates and animate correctly
MMTabline was introduced in #1120, which replaced the ancient PSMTabBarControl for representing Vim tabs. It uses animation to handle tab layouts, but it only worked in some situations, due to the Vim IPC API only sending a tabline update with all the tab labels with no way to track individual tabs. Update the API so that Vim now sends individual unique IDs in the update message as well to allow the GUI to track tabs over time and animate them. Vim does not interally have a concept of unique tab IDs, but we can use the pointer to the structure as such because they are allocated as a linked list and will never change. Extend MMTabline to have a new `updateTabsByTags` API to batch update all tabs in one go, which will diff the new tags with existing tabs and create/remove/move tabs as necessary. The scrolling logic has also been moved inside it and it now only scrolls to selected tab when it has changed or moved. When deleting tabs we won't scroll as usually the user doesn't expect it. Dragging tabs also now work correctly when it is removed during a drag, or another tab has been selected. This does mean the add/close tab APIs are currently unused, as the only entrypoint for modifying tabs from MacVim is currently only via `updateTabsByTags`. After this, different Vim operations will now animate correctly, including `:tabmove`, `:bdelete` (which could remove multiple tabs at once), `:tabnew`, `:tabclose`.
1 parent 8a68481 commit b589c84

File tree

9 files changed

+294
-149
lines changed

9 files changed

+294
-149
lines changed

src/MacVim/MMBackend.m

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -764,32 +764,36 @@ - (void)selectTab:(int)index
764764

765765
- (void)updateTabBar
766766
{
767+
// Update the tab bar with the most up-to-date info, including number of
768+
// tabs and titles/tooltips. MacVim would also like to know which specific
769+
// tabs were moved/added/deleted in order for animation to work, but Vim
770+
// does not have specific callbacks to listen to that. Instead, since the
771+
// tabpage_T memory address is constant per tab, we use that as a permanent
772+
// identifier for each GUI tab so MacVim can do the association.
767773
NSMutableData *data = [NSMutableData data];
768774

775+
// 1. Current selected tab index
769776
int idx = tabpage_index(curtab) - 1;
770777
[data appendBytes:&idx length:sizeof(int)];
771778

772779
tabpage_T *tp;
780+
// 2. Unique id for all the tabs
781+
// Do these first so they appear as a consecutive memory block.
773782
for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
774-
// Count the number of windows in the tabpage.
775-
//win_T *wp = tp->tp_firstwin;
776-
//int wincount;
777-
//for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
778-
//[data appendBytes:&wincount length:sizeof(int)];
779-
780-
int tabProp = MMTabInfoCount;
781-
[data appendBytes:&tabProp length:sizeof(int)];
782-
for (tabProp = MMTabLabel; tabProp < MMTabInfoCount; ++tabProp) {
783+
[data appendBytes:&tp length:sizeof(void*)];
784+
}
785+
// Null terminate the unique IDs.
786+
tp = 0;
787+
[data appendBytes:&tp length:sizeof(void*)];
788+
// 3. Labels and tooltips of each tab
789+
for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
790+
for (int tabProp = MMTabLabel; tabProp < MMTabInfoCount; ++tabProp) {
783791
// This function puts the label of the tab in the global 'NameBuff'.
784792
get_tabline_label(tp, (tabProp == MMTabToolTip));
785-
NSString *s = [NSString stringWithVimString:NameBuff];
786-
int len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
787-
if (len < 0)
788-
len = 0;
789-
790-
[data appendBytes:&len length:sizeof(int)];
793+
size_t len = STRLEN(NameBuff);
794+
[data appendBytes:&len length:sizeof(size_t)];
791795
if (len > 0)
792-
[data appendBytes:[s UTF8String] length:len];
796+
[data appendBytes:NameBuff length:len];
793797
}
794798
}
795799

src/MacVim/MMTabline/MMTab.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ typedef enum : NSInteger {
1414

1515
@interface MMTab : NSView
1616

17+
@property (nonatomic, readwrite) NSInteger tag; ///< Unique identifier that caller can set for the tab
1718
@property (nonatomic, copy) NSString *title;
1819
@property (nonatomic, getter=isCloseButtonHidden) BOOL closeButtonHidden;
1920
@property (nonatomic) MMTabState state;

src/MacVim/MMTabline/MMTab.m

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ @implementation MMTab
2020
NSTextField *_titleLabel;
2121
}
2222

23+
@synthesize tag = _tag;
24+
2325
+ (id)defaultAnimationForKey:(NSAnimatablePropertyKey)key
2426
{
2527
if ([key isEqualToString:@"fillColor"]) {

src/MacVim/MMTabline/MMTabline.h

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010

1111
@interface MMTabline : NSView
1212

13-
@property (nonatomic) NSInteger selectedTabIndex;
13+
/// The index of the selected tab. Can be -1 if nothing is selected.
14+
@property (nonatomic, readonly) NSInteger selectedTabIndex;
1415
@property (nonatomic) NSInteger optimumTabWidth;
1516
@property (nonatomic) NSInteger minimumTabWidth;
1617
@property (nonatomic) BOOL showsAddTabButton;
@@ -24,10 +25,31 @@
2425
@property (nonatomic, retain) NSColor *tablineFillFgColor;
2526
@property (nonatomic, weak) id <MMTablineDelegate> delegate;
2627

28+
/// Add a tab at the end. It's not selected automatically.
2729
- (NSInteger)addTabAtEnd;
30+
/// Add a tab after the selected one. It's not selected automatically.
2831
- (NSInteger)addTabAfterSelectedTab;
32+
/// Add a tab at specified index. It's not selected automatically.
2933
- (NSInteger)addTabAtIndex:(NSInteger)index;
34+
3035
- (void)closeTab:(MMTab *)tab force:(BOOL)force layoutImmediately:(BOOL)layoutImmediately;
36+
37+
/// Batch update all the tabs using tab tags as unique IDs. Tab line will handle
38+
/// creating / removing tabs as necessary, and moving tabs to their new
39+
/// positions.
40+
///
41+
/// The tags array has to have unique items only, and each existing MMTab also
42+
/// has to have unique tags.
43+
///
44+
/// @param tags The list of unique tags that are cross-referenced with each
45+
/// MMTab's tag. Order within the array indicates the desired tab order.
46+
/// @param len The length of the tags array.
47+
/// @param delayTabResize If true, do not resize tab widths until the the tab
48+
/// line loses focus. This helps preserve the relative tab positions and
49+
/// lines up the close buttons to the previous tab. This will also
50+
/// prevent scrolling to the new selected tab.
51+
- (void)updateTabsByTags:(NSInteger *)tags len:(NSUInteger)len delayTabResize:(BOOL)delayTabResize;
52+
3153
- (void)selectTabAtIndex:(NSInteger)index;
3254
- (MMTab *)tabAtIndex:(NSInteger)index;
3355
- (void)scrollTabToVisibleAtIndex:(NSInteger)index;

0 commit comments

Comments
 (0)