diff --git a/NeoVimServer/NeoVimTab.h b/NeoVimServer/NeoVimTab.h index 9b4f45632..294dc777d 100644 --- a/NeoVimServer/NeoVimTab.h +++ b/NeoVimServer/NeoVimTab.h @@ -15,8 +15,12 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) NSInteger handle; @property (nonatomic, readonly) NSArray *windows; +@property (nonatomic, readonly) bool isCurrent; -- (instancetype)initWithHandle:(NSInteger)handle windows:(NSArray *)windows; +- (instancetype)initWithHandle:(NSInteger)handle windows:(NSArray *)windows current:(bool)current; + +/// @returns The most recently selected window in *this* tab. +- (NeoVimWindow * _Nullable )currentWindow; - (NSString *)description; diff --git a/NeoVimServer/NeoVimTab.m b/NeoVimServer/NeoVimTab.m index 6c0adbfc1..76218b89a 100644 --- a/NeoVimServer/NeoVimTab.m +++ b/NeoVimServer/NeoVimTab.m @@ -9,7 +9,7 @@ @implementation NeoVimTab -- (instancetype)initWithHandle:(NSInteger)handle windows:(NSArray *)windows { +- (instancetype)initWithHandle:(NSInteger)handle windows:(NSArray *)windows current:(bool)current { self = [super init]; if (self == nil) { return nil; @@ -17,14 +17,21 @@ - (instancetype)initWithHandle:(NSInteger)handle windows:(NSArray "]; return description; } @@ -32,6 +39,7 @@ - (NSString *)description { - (void)encodeWithCoder:(NSCoder *)coder { [coder encodeObject:@(self.handle) forKey:@"handle"]; [coder encodeObject:self.windows forKey:@"windows"]; + [coder encodeBool:self.isCurrent forKey:@"current"]; } - (instancetype)initWithCoder:(NSCoder *)coder { @@ -40,6 +48,7 @@ - (instancetype)initWithCoder:(NSCoder *)coder { NSNumber *objHandle = [coder decodeObjectForKey:@"handle"]; _handle = objHandle.integerValue; _windows = [coder decodeObjectForKey:@"windows"]; + _isCurrent = [coder decodeBoolForKey:@"current"]; } return self; diff --git a/NeoVimServer/NeoVimWindow.h b/NeoVimServer/NeoVimWindow.h index a34ce8046..394e72f42 100644 --- a/NeoVimServer/NeoVimWindow.h +++ b/NeoVimServer/NeoVimWindow.h @@ -15,8 +15,9 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) NSInteger handle; @property (nonatomic, readonly) NeoVimBuffer *buffer; +@property (nonatomic, readonly) bool isCurrentInTab; -- (instancetype)initWithHandle:(NSInteger)handle buffer:(NeoVimBuffer *)buffer; +- (instancetype)initWithHandle:(NSInteger)handle buffer:(NeoVimBuffer *)buffer currentInTab:(bool)current; - (instancetype)initWithCoder:(NSCoder *)coder; - (void)encodeWithCoder:(NSCoder *)coder; diff --git a/NeoVimServer/NeoVimWindow.m b/NeoVimServer/NeoVimWindow.m index d8e874de9..561c4ae44 100644 --- a/NeoVimServer/NeoVimWindow.m +++ b/NeoVimServer/NeoVimWindow.m @@ -9,7 +9,7 @@ @implementation NeoVimWindow -- (instancetype)initWithHandle:(NSInteger)handle buffer:(NeoVimBuffer *)buffer { +- (instancetype)initWithHandle:(NSInteger)handle buffer:(NeoVimBuffer *)buffer currentInTab:(bool)current { self = [super init]; if (self == nil) { return nil; @@ -17,6 +17,7 @@ - (instancetype)initWithHandle:(NSInteger)handle buffer:(NeoVimBuffer *)buffer { _handle = handle; _buffer = buffer; + _isCurrentInTab = current; return self; } @@ -25,6 +26,7 @@ - (NSString *)description { NSMutableString *description = [NSMutableString stringWithFormat:@"<%@: ", NSStringFromClass([self class])]; [description appendFormat:@"self.handle=%li", self.handle]; [description appendFormat:@", self.buffer=%@", self.buffer]; + [description appendFormat:@", self.currentInTab=%d", self.isCurrentInTab]; [description appendString:@">"]; return description; } @@ -32,6 +34,7 @@ - (NSString *)description { - (void)encodeWithCoder:(NSCoder *)coder { [coder encodeObject:@(self.handle) forKey:@"handle"]; [coder encodeObject:self.buffer forKey:@"buffer"]; + [coder encodeBool:self.isCurrentInTab forKey:@"currentInTab"]; } - (instancetype)initWithCoder:(NSCoder *)coder { @@ -40,6 +43,7 @@ - (instancetype)initWithCoder:(NSCoder *)coder { NSNumber *objHandle = [coder decodeObjectForKey:@"handle"]; _handle = objHandle.integerValue; _buffer = [coder decodeObjectForKey:@"buffer"]; + _isCurrentInTab = [coder decodeBoolForKey:@"currentInTab"]; } return self; diff --git a/NeoVimServer/server_ui.m b/NeoVimServer/server_ui.m index baa722194..d6224b645 100644 --- a/NeoVimServer/server_ui.m +++ b/NeoVimServer/server_ui.m @@ -780,18 +780,24 @@ void neovim_tabs(void **argv) { FOR_ALL_TABS(t) { NSMutableArray *windows = [NSMutableArray new]; + bool currentTab = curtab ? t->handle == curtab->handle : false; + FOR_ALL_WINDOWS_IN_TAB(win, t) { NeoVimBuffer *buffer = buffer_for(win->w_buffer); if (buffer == nil) { continue; } - NeoVimWindow *window = [[NeoVimWindow alloc] initWithHandle:win->handle buffer:buffer]; + bool current = false; + // tp_curwin is only valid for tabs that aren't the current one + if (currentTab) current = curwin ? win->handle == curwin->handle : false; + else if (t->tp_curwin) current = win->handle == t->tp_curwin->handle; + NeoVimWindow *window = [[NeoVimWindow alloc] initWithHandle:win->handle buffer:buffer currentInTab:current]; [windows addObject:window]; [window release]; } - NeoVimTab *tab = [[NeoVimTab alloc] initWithHandle:t->handle windows:windows]; + NeoVimTab *tab = [[NeoVimTab alloc] initWithHandle:t->handle windows:windows current:currentTab]; [windows release]; [tabs addObject:tab]; diff --git a/SwiftNeoVim/NeoVimView+Api.swift b/SwiftNeoVim/NeoVimView+Api.swift index 14c6ea35c..79da1f646 100644 --- a/SwiftNeoVim/NeoVimView+Api.swift +++ b/SwiftNeoVim/NeoVimView+Api.swift @@ -90,6 +90,7 @@ extension NeoVimView { } } + /// Closes the current window. public func closeCurrentTab() { // We don't have to wait here even when neovim quits since we wait in gui.async() block in neoVimStopped(). self.exec(command: "q") diff --git a/SwiftNeoVim/NeoVimView+TouchBar.swift b/SwiftNeoVim/NeoVimView+TouchBar.swift new file mode 100644 index 000000000..fa905f821 --- /dev/null +++ b/SwiftNeoVim/NeoVimView+TouchBar.swift @@ -0,0 +1,91 @@ +/** + * Greg Omelaenko - http://omelaen.co + * See LICENSE + */ + +import Cocoa + +@available(OSX 10.12.2, *) +extension NeoVimView : NSTouchBarDelegate, NSScrubberDataSource, NSScrubberDelegate { + + private static let touchBarIdentifier = NSTouchBarCustomizationIdentifier("com.qvacua.VimR.SwiftNeoVim.touchBar") + private static let touchBarTabSwitcherIdentifier = NSTouchBarItemIdentifier("com.qvacua.VimR.SwiftNeoVim.touchBar.tabSwitcher") + private static let touchBarTabSwitcherItem = "com.qvacua.VimR.SwiftNeoVim.touchBar.tabSwitcher.item" + + override public func makeTouchBar() -> NSTouchBar? { + let bar = NSTouchBar() + bar.delegate = self + bar.customizationIdentifier = NeoVimView.touchBarIdentifier + bar.defaultItemIdentifiers = [NeoVimView.touchBarTabSwitcherIdentifier] + bar.customizationRequiredItemIdentifiers = [NeoVimView.touchBarTabSwitcherIdentifier] + return bar + } + + public func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItemIdentifier) -> NSTouchBarItem? { + switch identifier { + case NeoVimView.touchBarTabSwitcherIdentifier: + let item = NSCustomTouchBarItem(identifier: identifier) + item.customizationLabel = "Tab Switcher" + let tabsControl = NSScrubber() + tabsControl.register(NSScrubberTextItemView.self, forItemIdentifier: NeoVimView.touchBarTabSwitcherItem) + tabsControl.mode = .fixed + tabsControl.dataSource = self + tabsControl.delegate = self + tabsControl.selectionOverlayStyle = .outlineOverlay + tabsControl.selectedIndex = selectedTabIndex() + let layout = NSScrubberProportionalLayout() + layout.numberOfVisibleItems = 1 + tabsControl.scrubberLayout = layout + item.view = tabsControl + return item + default: + return nil + } + } + + private func selectedTabIndex() -> Int { + return tabsCache.index(where: { $0.isCurrent }) ?? -1 + } + + private func getTabsControl() -> NSScrubber? { + return (self.touchBar?.item(forIdentifier: NeoVimView.touchBarTabSwitcherIdentifier) as? NSCustomTouchBarItem)?.view as? NSScrubber + } + + func updateTouchBarCurrentBuffer() { + guard let tabsControl = getTabsControl() else { return } + tabsCache = self.agent.tabs() + tabsControl.reloadData() + (tabsControl.scrubberLayout as! NSScrubberProportionalLayout).numberOfVisibleItems = tabsControl.numberOfItems > 0 ? tabsControl.numberOfItems : 1 + tabsControl.selectedIndex = selectedTabIndex() + } + + func updateTouchBarTab() { + guard let tabsControl = getTabsControl() else { return } + tabsCache = self.agent.tabs() + if tabsControl.numberOfItems != tabsCache.count { + tabsControl.reloadData() + } + tabsControl.selectedIndex = selectedTabIndex() + tabsControl.reloadItems(at: [tabsControl.selectedIndex]) + } + + public func numberOfItems(for scrubber: NSScrubber) -> Int { + return tabsCache.count + } + + public func scrubber(_ scrubber: NSScrubber, viewForItemAt index: Int) -> NSScrubberItemView { + let itemView = scrubber.makeItem(withIdentifier: type(of: self).touchBarTabSwitcherItem, owner: nil) as! NSScrubberTextItemView + guard tabsCache.count > index else { return itemView } + let tab = tabsCache[index] + itemView.title = tab.currentWindow()?.buffer.name ?? "[No Name]" + + return itemView + } + + public func scrubber(_ scrubber: NSScrubber, didSelectItemAt selectedIndex: Int) { + let tab = tabsCache[selectedIndex] + guard tab.windows.count > 0 else { return } + self.agent.select(tab.currentWindow() ?? tab.windows[0]) + } + +} diff --git a/SwiftNeoVim/NeoVimView+UiBridge.swift b/SwiftNeoVim/NeoVimView+UiBridge.swift index b1407287a..b365e43b3 100644 --- a/SwiftNeoVim/NeoVimView+UiBridge.swift +++ b/SwiftNeoVim/NeoVimView+UiBridge.swift @@ -200,7 +200,7 @@ extension NeoVimView { self.tabChanged() } - if event == .BUFREADPOST || event == .BUFWRITEPOST { + if event == .BUFREADPOST || event == .BUFWRITEPOST || event == .BUFENTER { self.currentBufferChanged(bufferHandle) } } @@ -358,6 +358,9 @@ extension NeoVimView { } self.delegate?.currentBufferChanged(currentBuffer) + if #available(OSX 10.12.2, *) { + self.updateTouchBarTab() + } } fileprivate func tabChanged() { @@ -370,6 +373,9 @@ extension NeoVimView { fileprivate func bufferListChanged() { self.delegate?.bufferListChanged() + if #available(OSX 10.12.2, *) { + self.updateTouchBarCurrentBuffer() + } } } diff --git a/SwiftNeoVim/NeoVimView.swift b/SwiftNeoVim/NeoVimView.swift index a40e0f040..56ef96df2 100644 --- a/SwiftNeoVim/NeoVimView.swift +++ b/SwiftNeoVim/NeoVimView.swift @@ -235,6 +235,9 @@ public class NeoVimView: NSView, var _cwd = URL(fileURLWithPath: NSHomeDirectory()) var shouldDrawCursor = false var isInitialResize = true + + // cache the tabs for Touch Bar use + var tabsCache = [NeoVimTab]() // MARK: - Private fileprivate var _linespacing = NeoVimView.defaultLinespacing diff --git a/SwiftNeoVim/NeoVimViewDelegate.swift b/SwiftNeoVim/NeoVimViewDelegate.swift index c55805c45..f6414f4fe 100644 --- a/SwiftNeoVim/NeoVimViewDelegate.swift +++ b/SwiftNeoVim/NeoVimViewDelegate.swift @@ -14,6 +14,7 @@ public protocol NeoVimViewDelegate: class { func cwdChanged() func bufferListChanged() func tabChanged() + /// Called when the current buffer changes, including when a new one is selected. func currentBufferChanged(_ currentBuffer: NeoVimBuffer) func colorschemeChanged(to: NeoVimView.Theme) diff --git a/VimR.xcodeproj/project.pbxproj b/VimR.xcodeproj/project.pbxproj index 0864fddd6..f95f60f03 100644 --- a/VimR.xcodeproj/project.pbxproj +++ b/VimR.xcodeproj/project.pbxproj @@ -94,6 +94,7 @@ 1929BFC70581084B5CE04A5B /* MatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BFE179BCA3C75A13D71B /* MatcherTests.swift */; }; 1929BFDE22D155F7C4B19E96 /* HtmlPreviewTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B85023B042C485409CE1 /* HtmlPreviewTool.swift */; }; 1F1000F81F0ABC0000CA3195 /* NeoVimView+Dragging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F1000F71F0ABC0000CA3195 /* NeoVimView+Dragging.swift */; }; + 373416BD1F71879300A87A92 /* NeoVimView+TouchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373416BC1F71879300A87A92 /* NeoVimView+TouchBar.swift */; }; 4B029F1A1D45E349004EE0D3 /* PrefWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B029F1C1D45E349004EE0D3 /* PrefWindow.xib */; }; 4B0BCC941D70320C00D3CE65 /* Logger.h in Headers */ = {isa = PBXBuildFile; fileRef = 4B0BCC931D70320C00D3CE65 /* Logger.h */; settings = {ATTRIBUTES = (Private, ); }; }; 4B12CD891F5A985600167D59 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B12CD881F5A985600167D59 /* AppDelegate.swift */; }; @@ -454,6 +455,7 @@ 1929BFC0A5A9C6DB09BE1368 /* Types.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Types.swift; sourceTree = ""; }; 1929BFE179BCA3C75A13D71B /* MatcherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MatcherTests.swift; sourceTree = ""; }; 1F1000F71F0ABC0000CA3195 /* NeoVimView+Dragging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NeoVimView+Dragging.swift"; sourceTree = ""; }; + 373416BC1F71879300A87A92 /* NeoVimView+TouchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NeoVimView+TouchBar.swift"; sourceTree = ""; }; 4B029F1B1D45E349004EE0D3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/PrefWindow.xib; sourceTree = ""; }; 4B0BCC931D70320C00D3CE65 /* Logger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Logger.h; path = VimR/Logger.h; sourceTree = SOURCE_ROOT; }; 4B12CD861F5A985600167D59 /* ThemedWindow.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ThemedWindow.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -976,7 +978,9 @@ 4B5012001EBA791000F76C46 /* Frameworks */, 4BEBA5061CFF374B00673FDF /* Products */, ); + indentWidth = 2; sourceTree = ""; + tabWidth = 2; }; 4BEBA5061CFF374B00673FDF /* Products */ = { isa = PBXGroup; @@ -1041,6 +1045,7 @@ 1929BDF3167E15E7F3349798 /* NeoVimView+Key.swift */, 1929B4F65149D3C3E326DA65 /* NeoVimView+MenuItems.swift */, 1F1000F71F0ABC0000CA3195 /* NeoVimView+Dragging.swift */, + 373416BC1F71879300A87A92 /* NeoVimView+TouchBar.swift */, ); name = NeoVimView; sourceTree = ""; @@ -1442,6 +1447,7 @@ 4BDD05891DBBC50000D1B405 /* NeoVimTab.m in Sources */, 4BEE79171D16D3800012EDAA /* CellAttributes.swift in Sources */, 4BDD05861DBBC50000D1B405 /* NeoVimBuffer.m in Sources */, + 373416BD1F71879300A87A92 /* NeoVimView+TouchBar.swift in Sources */, 4BF6E29C1D34153C0053FA76 /* KeyUtils.swift in Sources */, 4BCADE081D11ED12004DAD0F /* CocoaExtensions.swift in Sources */, 4B401B1A1D046E0600D99EDC /* NeoVimViewDelegate.swift in Sources */,