Skip to content
Snippets Groups Projects
Commit 3473c6be authored by George Nachman's avatar George Nachman
Browse files

Save and restore tab colors in tmux integration windows. tab colors are stored...

Save and restore tab colors in tmux integration windows. tab colors are stored in a session option (since window panes can move from window to window but cannot leave a session). Sure would be nice if tmux has window pane options. The tmux controller keeps a map from window pane to tab color. It passes it to the tmux window opener which decorates the layout parse tree with each window pane's tab color. PTYSession copies it into the arrangement (changing it from a #hexstring into a dictionary), and when restoring from a tmux arrangement the profile gets divorced and updated. This implementation is imperfect: if two clients are attached they'll have divergent pictures of the tab colors. The correct solution to this is to add window pane options to tmux and for tmux to post notifications when a window pane option changes. Issue 3531
parent 5389ad12
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -69,4 +69,7 @@ CGFloat PerceivedBrightness(CGFloat r, CGFloat g, CGFloat b);
// Return the color you'd get by rendering self over background.
- (NSColor *)colorByPremultiplyingAlphaWithColor:(NSColor *)background;
 
- (NSString *)hexString;
+ (instancetype)colorFromHexString:(NSString *)hexString;
@end
Loading
Loading
@@ -292,4 +292,28 @@ CGFloat PerceivedBrightness(CGFloat r, CGFloat g, CGFloat b) {
return [NSColor colorWithColorSpace:self.colorSpace components:x count:4];
}
 
- (NSString *)hexString {
NSDictionary *dict = [self dictionaryValue];
int red = [dict[kEncodedColorDictionaryRedComponent] doubleValue] * 255;
int green = [dict[kEncodedColorDictionaryGreenComponent] doubleValue] * 255;
int blue = [dict[kEncodedColorDictionaryBlueComponent] doubleValue] * 255;
return [NSString stringWithFormat:@"#%02x%02x%02x", red, green, blue];
}
+ (instancetype)colorFromHexString:(NSString *)hexString {
if (![hexString hasPrefix:@"#"] || hexString.length != 7) {
return nil;
}
NSScanner *scanner = [NSScanner scannerWithString:[hexString substringFromIndex:1]];
unsigned long long ll;
if (![scanner scanHexLongLong:&ll]) {
return nil;
}
CGFloat red = (ll >> 16) & 0xff;
CGFloat green = (ll >> 8) & 0xff;
CGFloat blue = (ll >> 0) & 0xff;
return [NSColor colorWithSRGBRed:red/255.0 green:green/255.0 blue:blue/255.0 alpha:1];
}
@end
Loading
Loading
@@ -122,6 +122,7 @@ static NSString *const SESSION_ARRANGEMENT_TMUX_PANE = @"Tmux Pane";
static NSString *const SESSION_ARRANGEMENT_TMUX_HISTORY = @"Tmux History";
static NSString *const SESSION_ARRANGEMENT_TMUX_ALT_HISTORY = @"Tmux AltHistory";
static NSString *const SESSION_ARRANGEMENT_TMUX_STATE = @"Tmux State";
static NSString *const SESSION_ARRANGEMENT_TMUX_TAB_COLOR = @"Tmux Tab Color";
static NSString *const SESSION_ARRANGEMENT_IS_TMUX_GATEWAY = @"Is Tmux Gateway";
static NSString *const SESSION_ARRANGEMENT_TMUX_GATEWAY_SESSION_NAME = @"Tmux Gateway Session Name";
static NSString *const SESSION_ARRANGEMENT_TMUX_GATEWAY_SESSION_ID = @"Tmux Gateway Session ID";
Loading
Loading
@@ -720,10 +721,10 @@ ITERM_WEAKLY_REFERENCEABLE
[[arrangement objectForKey:SESSION_ARRANGEMENT_ROWS] intValue])];
}
 
+ (PTYSession*)sessionFromArrangement:(NSDictionary *)arrangement
inView:(SessionView *)sessionView
withDelegate:(id<PTYSessionDelegate>)delegate
forObjectType:(iTermObjectType)objectType {
+ (PTYSession *)sessionFromArrangement:(NSDictionary *)arrangement
inView:(SessionView *)sessionView
withDelegate:(id<PTYSessionDelegate>)delegate
forObjectType:(iTermObjectType)objectType {
DLog(@"Restoring session from arrangement");
PTYSession* aSession = [[[PTYSession alloc] init] autorelease];
aSession.view = sessionView;
Loading
Loading
@@ -734,12 +735,26 @@ ITERM_WEAKLY_REFERENCEABLE
objectForKey:KEY_GUID]];
BOOL needDivorce = NO;
if (!theBookmark) {
NSMutableDictionary *temp = [NSMutableDictionary dictionaryWithDictionary:[arrangement objectForKey:SESSION_ARRANGEMENT_BOOKMARK]];
// Keep it from stepping on an existing sesion with the same guid.
temp[KEY_GUID] = [ProfileModel freshGuid];
theBookmark = temp;
theBookmark = [arrangement objectForKey:SESSION_ARRANGEMENT_BOOKMARK];
needDivorce = YES;
}
NSDictionary *tabColorDict = [ITAddressBookMgr encodeColor:[NSColor colorFromHexString:arrangement[SESSION_ARRANGEMENT_TMUX_TAB_COLOR]]];
if (tabColorDict) {
if (![iTermProfilePreferences boolForKey:KEY_USE_TAB_COLOR inProfile:theBookmark] ||
![[ITAddressBookMgr decodeColor:[iTermProfilePreferences objectForKey:KEY_TAB_COLOR inProfile:theBookmark]] isEqual:tabColorDict]) {
theBookmark = [theBookmark dictionaryBySettingObject:tabColorDict forKey:KEY_TAB_COLOR];
theBookmark = [theBookmark dictionaryBySettingObject:@YES forKey:KEY_USE_TAB_COLOR];
needDivorce = YES;
}
} else if ([iTermProfilePreferences boolForKey:KEY_USE_TAB_COLOR inProfile:theBookmark]) {
theBookmark = [theBookmark dictionaryBySettingObject:@NO forKey:KEY_USE_TAB_COLOR];
needDivorce = YES;
}
if (needDivorce) {
// Keep it from stepping on an existing sesion with the same guid.
theBookmark = [theBookmark dictionaryBySettingObject:[ProfileModel freshGuid] forKey:KEY_GUID];
}
[[aSession screen] setUnlimitedScrollback:[[theBookmark objectForKey:KEY_UNLIMITED_SCROLLBACK] boolValue]];
[[aSession screen] setMaxScrollbackLines:[[theBookmark objectForKey:KEY_SCROLLBACK_LINES] intValue]];
 
Loading
Loading
@@ -2802,8 +2817,7 @@ ITERM_WEAKLY_REFERENCEABLE
return didChange;
}
 
- (void)setPreferencesFromAddressBookEntry:(NSDictionary *)aePrefs
{
- (void)setPreferencesFromAddressBookEntry:(NSDictionary *)aePrefs {
int i;
NSDictionary *aDict = aePrefs;
 
Loading
Loading
@@ -2971,6 +2985,15 @@ ITERM_WEAKLY_REFERENCEABLE
[[iTermSessionHotkeyController sharedInstance] setShortcut:shortcut
forSession:self];
[[_delegate realParentWindow] invalidateRestorableState];
if (self.isTmuxClient) {
NSDictionary *tabColorDict = [iTermProfilePreferences objectForKey:KEY_TAB_COLOR inProfile:aDict];
if (![iTermProfilePreferences boolForKey:KEY_USE_TAB_COLOR inProfile:aDict]) {
tabColorDict = nil;
}
NSColor *tabColor = [ITAddressBookMgr decodeColor:tabColorDict];
[self.tmuxController setTabColorString:[tabColor hexString] forWindowPane:_tmuxPane];
}
}
 
- (NSString *)badgeLabel {
Loading
Loading
@@ -3624,6 +3647,10 @@ ITERM_WEAKLY_REFERENCEABLE
if (value) {
[result setObject:value forKey:SESSION_ARRANGEMENT_TMUX_STATE];
}
value = parseNode[kLayoutDictTabColorKey];
if (value) {
result[SESSION_ARRANGEMENT_TMUX_TAB_COLOR] = value;
}
 
return result;
}
Loading
Loading
Loading
Loading
@@ -118,6 +118,9 @@ extern NSString *const kTmuxControllerSessionWasRenamed;
- (void)setHotkeyForWindowPane:(int)windowPane to:(NSDictionary *)hotkey;
- (NSDictionary *)hotkeyForWindowPane:(int)windowPane;
 
- (void)setTabColorString:(NSString *)colorString forWindowPane:(int)windowPane;
- (NSString *)tabColorStringForWindowPane:(int)windowPane;
- (void)linkWindowId:(int)windowId
inSession:(NSString *)sessionName
toSession:(NSString *)targetSession;
Loading
Loading
Loading
Loading
@@ -129,6 +129,7 @@ static NSString *kListWindowsFormat = @"\"#{session_name}\t#{window_id}\t"
BOOL ambiguousIsDoubleWidth_;
NSMutableDictionary<NSNumber *, NSDictionary *> *_hotkeys;
NSMutableSet<NSNumber *> *_paneIDs; // existing pane IDs
NSMutableDictionary<NSNumber *, NSString *> *_tabColors;
 
// Maps a window id string to a dictionary of window flags defined by TmuxWindowOpener (see the
// top of its header file)
Loading
Loading
@@ -155,6 +156,7 @@ static NSString *kListWindowsFormat = @"\"#{session_name}\t#{window_id}\t"
pendingWindowOpens_ = [[NSMutableSet alloc] init];
hiddenWindows_ = [[NSMutableSet alloc] init];
_hotkeys = [[NSMutableDictionary alloc] init];
_tabColors = [[NSMutableDictionary alloc] init];
self.clientName = [[TmuxControllerRegistry sharedInstance] uniqueClientNameBasedOn:clientName];
_windowOpenerOptions = [[NSMutableDictionary alloc] init];
[[TmuxControllerRegistry sharedInstance] setController:self forClient:_clientName];
Loading
Loading
@@ -178,6 +180,7 @@ static NSString *kListWindowsFormat = @"\"#{session_name}\t#{window_id}\t"
[_sessionGuid release];
[_windowOpenerOptions release];
[_hotkeys release];
[_tabColors release];
[super dealloc];
}
 
Loading
Loading
@@ -215,6 +218,7 @@ static NSString *kListWindowsFormat = @"\"#{session_name}\t#{window_id}\t"
windowOpener.windowOptions = _windowOpenerOptions;
windowOpener.zoomed = windowFlags ? @([windowFlags containsString:@"Z"]) : nil;
windowOpener.manuallyOpened = _manualOpenRequested;
windowOpener.tabColors = _tabColors;
_manualOpenRequested = NO;
if (![windowOpener openWindows:YES]) {
[pendingWindowOpens_ removeObject:n];
Loading
Loading
@@ -239,6 +243,7 @@ static NSString *kListWindowsFormat = @"\"#{session_name}\t#{window_id}\t"
windowOpener.selector = @selector(windowDidOpen:);
windowOpener.windowOptions = _windowOpenerOptions;
windowOpener.zoomed = zoomed;
windowOpener.tabColors = _tabColors;
[windowOpener updateLayoutInTab:tab];
}
 
Loading
Loading
@@ -412,6 +417,7 @@ static NSString *kListWindowsFormat = @"\"#{session_name}\t#{window_id}\t"
NSString *getAffinitiesCommand = [NSString stringWithFormat:@"show -v -q -t $%d @affinities", sessionId_];
NSString *getOriginsCommand = [NSString stringWithFormat:@"show -v -q -t $%d @origins", sessionId_];
NSString *getHotkeysCommand = [NSString stringWithFormat:@"show -v -q -t $%d @hotkeys", sessionId_];
NSString *getTabColorsCommand = [NSString stringWithFormat:@"show -v -q -t $%d @tab_colors", sessionId_];
NSString *getHiddenWindowsCommand = [NSString stringWithFormat:@"show -v -q -t $%d @hidden", sessionId_];
NSArray *commands = @[ [gateway_ dictionaryForCommand:getSessionGuidCommand
responseTarget:self
Loading
Loading
@@ -443,6 +449,11 @@ static NSString *kListWindowsFormat = @"\"#{session_name}\t#{window_id}\t"
responseSelector:@selector(getHotkeysResponse:)
responseObject:nil
flags:kTmuxGatewayCommandShouldTolerateErrors],
[gateway_ dictionaryForCommand:getTabColorsCommand
responseTarget:self
responseSelector:@selector(getTabColorsResponse:)
responseObject:nil
flags:kTmuxGatewayCommandShouldTolerateErrors],
[gateway_ dictionaryForCommand:listSessionsCommand
responseTarget:self
responseSelector:@selector(listSessionsResponse:)
Loading
Loading
@@ -890,6 +901,22 @@ static NSString *kListWindowsFormat = @"\"#{session_name}\t#{window_id}\t"
flags:kTmuxGatewayCommandShouldTolerateErrors];
}
 
- (void)setTabColorString:(NSString *)colorString forWindowPane:(int)windowPane {
if ([_tabColors[@(windowPane)] isEqualToString:colorString]) {
return;
}
_tabColors[@(windowPane)] = colorString;
// First get a list of existing panes so we can avoid setting tab colors for any nonexistent panes. Keeps the string from getting too long.
NSString *getPaneIDsCommand = [NSString stringWithFormat:@"list-panes -s -t $%d -F \"#{pane_id}\"", sessionId_];
[gateway_ sendCommand:getPaneIDsCommand
responseTarget:self
responseSelector:@selector(getPaneIDsResponseAndSetTabColors:)
responseObject:nil
flags:kTmuxGatewayCommandShouldTolerateErrors];
}
- (void)getPaneIDsResponseAndSetHotkeys:(NSString *)response {
[_paneIDs removeAllObjects];
for (NSString *pane in [response componentsSeparatedByString:@"\n"]) {
Loading
Loading
@@ -900,6 +927,16 @@ static NSString *kListWindowsFormat = @"\"#{session_name}\t#{window_id}\t"
[self sendCommandToSetHotkeys];
}
 
- (void)getPaneIDsResponseAndSetTabColors:(NSString *)response {
[_paneIDs removeAllObjects];
for (NSString *pane in [response componentsSeparatedByString:@"\n"]) {
if (pane.length) {
[_paneIDs addObject:@([[pane substringFromIndex:1] intValue])];
}
}
[self sendCommandToSetTabColors];
}
- (void)sendCommandToSetHotkeys {
NSString *command = [NSString stringWithFormat:@"set -t $%d @hotkeys \"%@\"",
sessionId_, [self.hotkeysString stringByEscapingQuotes]];
Loading
Loading
@@ -910,10 +947,24 @@ static NSString *kListWindowsFormat = @"\"#{session_name}\t#{window_id}\t"
flags:0];
}
 
- (void)sendCommandToSetTabColors {
NSString *command = [NSString stringWithFormat:@"set -t $%d @tab_colors \"%@\"",
sessionId_, [self.tabColorsString stringByEscapingQuotes]];
[gateway_ sendCommand:command
responseTarget:nil
responseSelector:nil
responseObject:nil
flags:0];
}
- (NSDictionary *)hotkeyForWindowPane:(int)windowPane {
return _hotkeys[@(windowPane)];
}
 
- (NSString *)tabColorStringForWindowPane:(int)windowPane {
return _tabColors[@(windowPane)];
}
- (void)killWindow:(int)window
{
[gateway_ sendCommand:[NSString stringWithFormat:@"kill-window -t @%d", window]
Loading
Loading
@@ -1165,7 +1216,7 @@ static NSString *kListWindowsFormat = @"\"#{session_name}\t#{window_id}\t"
responseTarget:self
responseSelector:@selector(saveWindowOriginsResponse:)];
}
[self getOriginsResponse:enc];
[self getOriginsResponse:enc];
}
 
- (void)saveWindowOriginsResponse:(NSString *)response
Loading
Loading
@@ -1352,6 +1403,17 @@ static NSString *kListWindowsFormat = @"\"#{session_name}\t#{window_id}\t"
return [parts componentsJoinedByString:@" "];
}
 
- (NSString *)tabColorsString {
NSMutableArray *parts = [NSMutableArray array];
[_tabColors enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull key, NSString * _Nonnull obj, BOOL * _Nonnull stop) {
if ([_paneIDs containsObject:key]) {
[parts addObject:[NSString stringWithFormat:@"%@=%@", key, obj]];
}
}];
return [parts componentsJoinedByString:@" "];
}
- (void)getHotkeysResponse:(NSString *)result {
[_hotkeys removeAllObjects];
if (result.length > 0) {
Loading
Loading
@@ -1371,6 +1433,24 @@ static NSString *kListWindowsFormat = @"\"#{session_name}\t#{window_id}\t"
}
}
 
- (void)getTabColorsResponse:(NSString *)result {
[_tabColors removeAllObjects];
if (result.length > 0) {
[_tabColors removeAllObjects];
NSArray *parts = [result componentsSeparatedByString:@" "];
for (NSString *part in parts) {
NSInteger equals = [part rangeOfString:@"="].location;
if (equals != NSNotFound && equals + 1 < part.length) {
NSString *wp = [part substringToIndex:equals];
NSString *colorString = [part substringFromIndex:equals + 1];
if (colorString && wp.length) {
_tabColors[@(wp.intValue)] = colorString;
}
}
}
}
}
- (int)windowIdFromString:(NSString *)s
{
if (s.length < 2 || [s characterAtIndex:0] != '@') {
Loading
Loading
Loading
Loading
@@ -28,6 +28,7 @@ extern NSString *kLayoutDictHistoryKey; // Array of screen_char_t-filled N
extern NSString *kLayoutDictAltHistoryKey; // Alternate screen history
extern NSString *kLayoutDictStateKey; // see TmuxStateParser
extern NSString *kLayoutDictHotkeyKey; // Session hotkey dictionary
extern NSString *kLayoutDictTabColorKey; // Tab color
 
// Children of leaf:
extern NSString *kLayoutDictTabOpenedManually; // Was this tab opened by a user-initiated action?
Loading
Loading
Loading
Loading
@@ -30,6 +30,7 @@ NSString *kLayoutDictAltHistoryKey = @"alt-history";
NSString *kLayoutDictStateKey = @"state";
NSString *kLayoutDictHotkeyKey = @"hotkey";
NSString *kLayoutDictTabOpenedManually = @"manual-open";
NSString *kLayoutDictTabColorKey = @"x-tab-color";
 
@implementation TmuxLayoutParser
 
Loading
Loading
Loading
Loading
@@ -39,6 +39,7 @@ extern NSString *const kTmuxWindowOpenerWindowOptionStyleValueFullScreen;
// Maps a window ID as a string to a dictionary of window flags (see WindowFlag constants above).
@property (nonatomic, retain) NSDictionary *windowOptions;
@property (nonatomic, assign) BOOL manuallyOpened;
@property (nonatomic, copy) NSDictionary<NSNumber *, NSString *> *tabColors;
 
+ (TmuxWindowOpener *)windowOpener;
- (BOOL)openWindows:(BOOL)initial;
Loading
Loading
Loading
Loading
@@ -79,6 +79,8 @@ NSString *const kTmuxWindowOpenerWindowOptionStyleValueFullScreen = @"FullScreen
[tabToUpdate_ release];
[_windowOptions release];
[_zoomed release];
[_tabColors release];
[super dealloc];
}
 
Loading
Loading
@@ -439,6 +441,10 @@ static int OctalValue(const char *bytes) {
parseTree[kLayoutDictHotkeyKey] = hotkey;
}
 
if (self.tabColors[n]) {
parseTree[kLayoutDictTabColorKey] = self.tabColors[n];
}
return nil;
}
 
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment