Commits
Zero King authored 4a4f7ab7a22
1 + | From 538d570ea54614d3a2b5724f820953d717fbeb0c Mon Sep 17 00:00:00 2001 |
2 + | From: George Nachman <gln@whatsapp.com> |
3 + | Date: Wed, 25 Sep 2019 23:13:00 -0700 |
4 + | Subject: [PATCH] Do not send server-controlled values in tmux integration |
5 + | mode. |
6 + | |
7 + | CVE-2019-9535 |
8 + | |
9 + | - Use session number everywhere rather than session name |
10 + | - Do not poll tmux for the set-titles-string, status-left, and status-right and |
11 + | then request the values of the returned format strings. Use ${T:} eval |
12 + | instead. These features are now only available for tmux 2.9 and later. |
13 + | - Hex-encode options saved in the tmux server to make them unexploitable (e.g., |
14 + | hotkeys, window affinities, window origins, etc.). The old values are |
15 + | accepted as inputs but will never be produced as output. |
16 + | --- |
17 + | iTerm2.xcodeproj/project.pbxproj | 8 + |
18 + | sources/PTYSession.m | 28 ++- |
19 + | sources/PTYTab.m | 13 +- |
20 + | sources/PseudoTerminal.m | 9 +- |
21 + | sources/TmuxController.h | 47 ++-- |
22 + | sources/TmuxController.m | 305 +++++++++++++++----------- |
23 + | sources/TmuxDashboardController.m | 163 +++++++------- |
24 + | sources/TmuxSessionsTable.h | 30 +-- |
25 + | sources/TmuxSessionsTable.m | 106 ++++----- |
26 + | sources/TmuxWindowsTable.h | 2 +- |
27 + | sources/TmuxWindowsTable.m | 21 +- |
28 + | sources/iTermInitialDirectory+Tmux.h | 2 +- |
29 + | sources/iTermInitialDirectory+Tmux.m | 20 +- |
30 + | sources/iTermTmuxOptionMonitor.h | 4 + |
31 + | sources/iTermTmuxOptionMonitor.m | 16 +- |
32 + | sources/iTermTmuxSessionObject.h | 17 ++ |
33 + | sources/iTermTmuxSessionObject.m | 12 + |
34 + | sources/iTermTmuxStatusBarMonitor.h | 1 + |
35 + | sources/iTermTmuxStatusBarMonitor.m | 28 +-- |
36 + | sources/iTermWorkingDirectoryPoller.m | 1 + |
37 + | 20 files changed, 471 insertions(+), 362 deletions(-) |
38 + | create mode 100644 sources/iTermTmuxSessionObject.h |
39 + | create mode 100644 sources/iTermTmuxSessionObject.m |
40 + | |
41 + | diff --git iTerm2.xcodeproj/project.pbxproj iTerm2.xcodeproj/project.pbxproj |
42 + | index 7e2f86a..32e1df2 100644 |
43 + | --- iTerm2.xcodeproj/project.pbxproj |
44 + | +++ iTerm2.xcodeproj/project.pbxproj |
45 + | |
46 + | 53E184F21FE32F2800DB78F3 /* iTermMetalBufferPool.m in Sources */ = {isa = PBXBuildFile; fileRef = 53E184F01FE32F2800DB78F3 /* iTermMetalBufferPool.m */; }; |
47 + | 53E8F36F2244A58800F3770F /* iTermActionsModel.h in Headers */ = {isa = PBXBuildFile; fileRef = 53E8F36D2244A58800F3770F /* iTermActionsModel.h */; }; |
48 + | 53E8F3702244A58800F3770F /* iTermActionsModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 53E8F36E2244A58800F3770F /* iTermActionsModel.m */; }; |
49 + | + 53E98E7B233C6B760094D8A9 /* iTermTmuxSessionObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 53E98E79233C6B760094D8A9 /* iTermTmuxSessionObject.h */; }; |
50 + | + 53E98E7C233C6B760094D8A9 /* iTermTmuxSessionObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 53E98E7A233C6B760094D8A9 /* iTermTmuxSessionObject.m */; }; |
51 + | 53E9DFE0220D518E0070C9C0 /* AlertTrigger.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D9DCC15142D7FC10016228A /* AlertTrigger.m */; }; |
52 + | 53E9DFE1220D51D50070C9C0 /* CoprocessTrigger.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D9DDD91142E5AC600275650 /* CoprocessTrigger.m */; }; |
53 + | 53E9DFE2220D52730070C9C0 /* PasswordTrigger.m in Sources */ = {isa = PBXBuildFile; fileRef = 1DABA03219253FEA00A228D8 /* PasswordTrigger.m */; }; |
54 + | |
55 + | 53E184F01FE32F2800DB78F3 /* iTermMetalBufferPool.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = iTermMetalBufferPool.m; sourceTree = "<group>"; }; |
56 + | 53E8F36D2244A58800F3770F /* iTermActionsModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = iTermActionsModel.h; sourceTree = "<group>"; }; |
57 + | 53E8F36E2244A58800F3770F /* iTermActionsModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = iTermActionsModel.m; sourceTree = "<group>"; }; |
58 + | + 53E98E79233C6B760094D8A9 /* iTermTmuxSessionObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = iTermTmuxSessionObject.h; sourceTree = "<group>"; }; |
59 + | + 53E98E7A233C6B760094D8A9 /* iTermTmuxSessionObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = iTermTmuxSessionObject.m; sourceTree = "<group>"; }; |
60 + | 53EBF29B1DCBFF7C00766613 /* iTermAPIServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = iTermAPIServer.h; sourceTree = "<group>"; }; |
61 + | 53EBF29C1DCBFF7C00766613 /* iTermAPIServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = iTermAPIServer.m; sourceTree = "<group>"; }; |
62 + | 53EBF29F1DCCF4AC00766613 /* iTermIPV4Address.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = iTermIPV4Address.h; sourceTree = "<group>"; }; |
63 + | |
64 + | A6F718B4226438FC0053488E /* iTermInitialDirectory+Tmux.m */, |
65 + | 53D68F7E2283EE7C0018710D /* iTermTmuxLayoutBuilder.h */, |
66 + | 53D68F7F2283EE7C0018710D /* iTermTmuxLayoutBuilder.m */, |
67 + | + 53E98E79233C6B760094D8A9 /* iTermTmuxSessionObject.h */, |
68 + | + 53E98E7A233C6B760094D8A9 /* iTermTmuxSessionObject.m */, |
69 + | ); |
70 + | name = tmux; |
71 + | sourceTree = "<group>"; |
72 + | |
73 + | A6153D4C21F30A9C002976FC /* iTermJobTreeViewController.h in Headers */, |
74 + | 5370678F21C9D2780088D0F3 /* SIGSHA2VerificationAlgorithm.h in Headers */, |
75 + | A66719161DCE36C3000CE608 /* NSURL+iTerm.h in Headers */, |
76 + | + 53E98E7B233C6B760094D8A9 /* iTermTmuxSessionObject.h in Headers */, |
77 + | A66719171DCE36C3000CE608 /* NSDate+iTerm.h in Headers */, |
78 + | A6BCAAD821F6F53E0000CD29 /* iTermTextPopoverViewController.h in Headers */, |
79 + | A66719181DCE36C3000CE608 /* iTermBaseHotKey.h in Headers */, |
80 + | |
81 + | A6AC5D241E9036D70097C0A7 /* iTermURLMark.m in Sources */, |
82 + | 535EA51420D8C1EB00FC81E0 /* iTermProfilesWindowController.m in Sources */, |
83 + | A6A4866F20B67A1600493302 /* iTermSemanticHistoryPrefsController.m in Sources */, |
84 + | + 53E98E7C233C6B760094D8A9 /* iTermTmuxSessionObject.m in Sources */, |
85 + | A66719621DCE3772000CE608 /* iTermSocketAddress.m in Sources */, |
86 + | A6A4867E20B67FD100493302 /* SmartSelectionController.m in Sources */, |
87 + | 53AFFC8E1DD2A04100E6CEC6 /* iTermLSOF.m in Sources */, |
88 + | diff --git sources/PTYSession.m sources/PTYSession.m |
89 + | index 9b64e03..0332d8c 100644 |
90 + | --- sources/PTYSession.m |
91 + | +++ sources/PTYSession.m |
92 + | |
93 + | } else { |
94 + | // Legacy code path for pre tmux 2.6 |
95 + | [_tmuxController renameWindowWithId:_delegate.tmuxWindow |
96 + | - inSession:nil |
97 + | + inSessionNumber:nil |
98 + | toName:profile[KEY_NAME]]; |
99 + | } |
100 + | _tmuxTitleOutOfSync = NO; |
101 + | |
102 + | |
103 + | - (void)installTmuxStatusBarMonitor { |
104 + | assert(!_tmuxStatusBarMonitor); |
105 + | - _tmuxStatusBarMonitor = [[iTermTmuxStatusBarMonitor alloc] initWithGateway:_tmuxController.gateway |
106 + | - scope:self.variablesScope]; |
107 + | - _tmuxStatusBarMonitor.active = [iTermProfilePreferences boolForKey:KEY_SHOW_STATUS_BAR inProfile:self.profile]; |
108 + | - if ([iTermPreferences boolForKey:kPreferenceKeyUseTmuxStatusBar] || |
109 + | - [iTermStatusBarLayout shouldOverrideLayout:self.profile[KEY_STATUS_BAR_LAYOUT]]) { |
110 + | - [self setSessionSpecificProfileValues:@{ KEY_STATUS_BAR_LAYOUT: [[iTermStatusBarLayout tmuxLayoutWithController:_tmuxController |
111 + | - scope:nil |
112 + | - window:self.delegate.tmuxWindow] dictionaryValue] }]; |
113 + | + |
114 + | + if (_tmuxController.gateway.minimumServerVersion.doubleValue >= 2.9) { |
115 + | + // Just use the built-in status bar for older versions of tmux because they don't support ${T:xxx} or ${E:xxx} |
116 + | + _tmuxStatusBarMonitor = [[iTermTmuxStatusBarMonitor alloc] initWithGateway:_tmuxController.gateway |
117 + | + scope:self.variablesScope]; |
118 + | + _tmuxStatusBarMonitor.active = [iTermProfilePreferences boolForKey:KEY_SHOW_STATUS_BAR inProfile:self.profile]; |
119 + | + if ([iTermPreferences boolForKey:kPreferenceKeyUseTmuxStatusBar] || |
120 + | + [iTermStatusBarLayout shouldOverrideLayout:self.profile[KEY_STATUS_BAR_LAYOUT]]) { |
121 + | + [self setSessionSpecificProfileValues:@{ KEY_STATUS_BAR_LAYOUT: [[iTermStatusBarLayout tmuxLayoutWithController:_tmuxController |
122 + | + scope:nil |
123 + | + window:self.delegate.tmuxWindow] dictionaryValue] }]; |
124 + | + } |
125 + | } |
126 + | } |
127 + | |
128 + | |
129 + | } |
130 + | __weak __typeof(self) weakSelf = self; |
131 + | _tmuxTitleMonitor = [[iTermTmuxOptionMonitor alloc] initWithGateway:_tmuxController.gateway |
132 + | - scope:self.variablesScope |
133 + | + scope:self.variablesScope |
134 + | + fallbackVariableName:nil |
135 + | format:@"#{pane_title}" |
136 + | target:[NSString stringWithFormat:@"%%%@", @(self.tmuxPane)] |
137 + | variableName:iTermVariableKeySessionTmuxPaneTitle |
138 + | |
139 + | [_tmuxController ping]; |
140 + | [_tmuxController validateOptions]; |
141 + | [_tmuxController checkForUTF8]; |
142 + | - [_tmuxController guessVersion]; |
143 + | - [_tmuxController loadTitleFormat]; |
144 + | + [_tmuxController guessVersion]; // NOTE: This kicks off more stuff that depends on knowing the version number. |
145 + | } |
146 + | |
147 + | - (void)tmuxInitialCommandDidFailWithError:(NSString *)error { |
148 + | diff --git sources/PTYTab.m sources/PTYTab.m |
149 + | index a8f8b48..6c4766e 100644 |
150 + | --- sources/PTYTab.m |
151 + | +++ sources/PTYTab.m |
152 + | |
153 + | return; |
154 + | } |
155 + | _tmuxTitleMonitor = [[iTermTmuxOptionMonitor alloc] initWithGateway:tmuxController_.gateway |
156 + | - scope:self.variablesScope |
157 + | - format:tmuxController_.setTitlesString |
158 + | - target:[NSString stringWithFormat:@"@%@", @(self.tmuxWindow)] |
159 + | - variableName:iTermVariableKeyTabTmuxWindowTitle |
160 + | - block:nil]; |
161 + | + scope:self.variablesScope |
162 + | + fallbackVariableName:iTermVariableKeySessionWindowName |
163 + | + format:@"#{T:set-titles-string}" |
164 + | + target:[NSString stringWithFormat:@"@%@", @(self.tmuxWindow)] |
165 + | + variableName:iTermVariableKeyTabTmuxWindowTitle |
166 + | + block:nil]; |
167 + | [_tmuxTitleMonitor updateOnce]; |
168 + | if (self.titleOverride.length == 0) { |
169 + | // Show the tmux window title if both the tmux option set-titles is on and the user hasn't |
170 + | |
171 + | if (self.tmuxTab) { |
172 + | if (titleOverride) { |
173 + | [self.tmuxController renameWindowWithId:self.tmuxWindow |
174 + | - inSession:nil |
175 + | + inSessionNumber:nil |
176 + | toName:titleOverride]; |
177 + | } |
178 + | return; |
179 + | diff --git sources/PseudoTerminal.m sources/PseudoTerminal.m |
180 + | index b90e733..24f041a 100644 |
181 + | --- sources/PseudoTerminal.m |
182 + | +++ sources/PseudoTerminal.m |
183 + | |
184 + | if ([arrangement objectForKey:TERMINAL_GUID] && |
185 + | [[arrangement objectForKey:TERMINAL_GUID] isKindOfClass:[NSString class]]) { |
186 + | NSString *savedGUID = [arrangement objectForKey:TERMINAL_GUID]; |
187 + | - if ([[iTermController sharedInstance] terminalWithGuid:savedGUID]) { |
188 + | - // Refuse to create a window with an already-used guid. |
189 + | + if ([[iTermController sharedInstance] terminalWithGuid:savedGUID] || ![self stringIsValidTerminalGuid:savedGUID]) { |
190 + | + // Refuse to create a window with an already-used or invalid guid. |
191 + | self.terminalGuid = [NSString stringWithFormat:@"pty-%@", [NSString uuid]]; |
192 + | } else { |
193 + | self.terminalGuid = savedGUID; |
194 + | |
195 + | return YES; |
196 + | } |
197 + | |
198 + | +- (BOOL)stringIsValidTerminalGuid:(NSString *)string { |
199 + | + NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-"]; |
200 + | + return [string rangeOfCharacterFromSet:characterSet.invertedSet].location == NSNotFound; |
201 + | +} |
202 + | + |
203 + | - (BOOL)restoreTabsFromArrangement:(NSDictionary *)arrangement sessions:(NSArray<PTYSession *> *)sessions { |
204 + | for (NSDictionary *tabArrangement in arrangement[TERMINAL_ARRANGEMENT_TABS]) { |
205 + | NSDictionary<NSString *, PTYSession *> *sessionMap = nil; |
206 + | diff --git sources/TmuxController.h sources/TmuxController.h |
207 + | index f27bacc..f1e5ad3 100644 |
208 + | --- sources/TmuxController.h |
209 + | +++ sources/TmuxController.h |
210 + | |
211 + | #import <Cocoa/Cocoa.h> |
212 + | #import "ProfileModel.h" |
213 + | #import "iTermInitialDirectory.h" |
214 + | +#import "iTermTmuxSessionObject.h" |
215 + | #import "TmuxGateway.h" |
216 + | #import "WindowControllerInterface.h" |
217 + | |
218 + | |
219 + | extern NSString *const kTmuxControllerAttachedSessionDidChange; |
220 + | // Posted when a session changes name |
221 + | extern NSString *const kTmuxControllerSessionWasRenamed; |
222 + | -// Posted when set-titles-string option changes. Object is tmux controller. |
223 + | +// Posted when set-titles option changes. Object is tmux controller. |
224 + | extern NSString *const kTmuxControllerDidFetchSetTitlesStringOption; |
225 + | |
226 + | @interface TmuxController : NSObject |
227 + | |
228 + | @property(nonatomic, readonly) TmuxGateway *gateway; |
229 + | @property(nonatomic, retain) NSMutableDictionary *windowPositions; |
230 + | @property(nonatomic, copy) NSString *sessionName; |
231 + | -@property(nonatomic, retain) NSArray *sessions; |
232 + | +@property(nonatomic, copy) NSArray<iTermTmuxSessionObject *> *sessionObjects; |
233 + | @property(nonatomic, assign) BOOL ambiguousIsDoubleWidth; |
234 + | @property(nonatomic, assign) NSInteger unicodeVersion; |
235 + | @property(nonatomic, readonly) NSString *clientName; |
236 + | |
237 + | @property(nonatomic, readonly) NSDictionary *sharedFontOverrides; |
238 + | @property(nonatomic, readonly) NSString *sessionGuid; |
239 + | @property(nonatomic, readonly) BOOL variableWindowSize; |
240 + | -@property(nonatomic, readonly) NSString *setTitlesString; |
241 + | @property(nonatomic, readonly) BOOL shouldSetTitles; |
242 + | @property(nonatomic, readonly) BOOL serverIsLocal; |
243 + | |
244 + | |
245 + | scope:(iTermVariableScope *)scope |
246 + | initialDirectory:(iTermInitialDirectory *)initialDirectory; |
247 + | |
248 + | -- (void)newWindowInSession:(NSString *)targetSession |
249 + | - scope:(iTermVariableScope *)scope |
250 + | - initialDirectory:(iTermInitialDirectory *)initialDirectory; |
251 + | +- (void)newWindowInSessionNumber:(NSNumber *)sessionNumber |
252 + | + scope:(iTermVariableScope *)scope |
253 + | + initialDirectory:(iTermInitialDirectory *)initialDirectory; |
254 + | |
255 + | - (void)selectPane:(int)windowPane; |
256 + | |
257 + | |
258 + | |
259 + | - (void)killWindowPane:(int)windowPane; |
260 + | - (void)killWindow:(int)window; |
261 + | -- (void)unlinkWindowWithId:(int)windowId inSession:(NSString *)sessionName; |
262 + | +- (void)unlinkWindowWithId:(int)windowId; |
263 + | - (void)requestDetach; |
264 + | -- (void)renameWindowWithId:(int)windowId inSession:(NSString *)sessionName toName:(NSString *)newName; |
265 + | +- (void)renameWindowWithId:(int)windowId |
266 + | + inSessionNumber:(NSNumber *)sessionNumber |
267 + | + toName:(NSString *)newName; |
268 + | - (BOOL)canRenamePane; |
269 + | - (void)renamePane:(int)windowPane toTitle:(NSString *)newTitle; |
270 + | - (void)setHotkeyForWindowPane:(int)windowPane to:(NSDictionary *)hotkey; |
271 + | |
272 + | - (NSString *)tabColorStringForWindowPane:(int)windowPane; |
273 + | |
274 + | - (void)linkWindowId:(int)windowId |
275 + | - inSession:(NSString *)sessionName |
276 + | - toSession:(NSString *)targetSession; |
277 + | + inSessionNumber:(int)sessionNumber |
278 + | + toSessionNumber:(int)targetSession; |
279 + | + |
280 + | - (void)moveWindowId:(int)windowId |
281 + | - inSession:(NSString *)sessionName |
282 + | - toSession:(NSString *)targetSession; |
283 + | + inSessionNumber:(int)sessionNumber |
284 + | + toSessionNumber:(int)targetSessionNumber; |
285 + | + |
286 + | +- (void)renameSessionNumber:(int)sessionNumber |
287 + | + to:(NSString *)newName; |
288 + | |
289 + | -- (void)renameSession:(NSString *)oldName to:(NSString *)newName; |
290 + | -- (void)killSession:(NSString *)sessionName; |
291 + | -- (void)attachToSession:(NSString *)sessionName; |
292 + | +- (void)killSessionNumber:(int)sessionNumber; |
293 + | +- (void)attachToSessionWithNumber:(int)sessionNumber; |
294 + | - (void)addSessionWithName:(NSString *)sessionName; |
295 + | -// NOTE: If the session name is bogus (or any other error occurs) the selector will not be called. |
296 + | -- (void)listWindowsInSession:(NSString *)sessionName |
297 + | - target:(id)target |
298 + | - selector:(SEL)selector |
299 + | - object:(id)object; |
300 + | +// NOTE: If anything goes wrong the selector will not be called. |
301 + | +- (void)listWindowsInSessionNumber:(int)sessionNumber |
302 + | + target:(id)target |
303 + | + selector:(SEL)selector |
304 + | + object:(id)object; |
305 + | + |
306 + | - (void)listSessions; |
307 + | - (void)saveAffinities; |
308 + | - (void)saveWindowOrigins; |
309 + | |
310 + | - (void)setLayoutInWindow:(int)window toLayout:(NSString *)layout; |
311 + | - (NSArray<PTYSession *> *)clientSessions; |
312 + | |
313 + | -- (void)setSize:(NSSize)size windows:(NSArray<NSString *> *)windows; |
314 + | - (void)setSize:(NSSize)size window:(int)window; |
315 + | |
316 + | @end |
317 + | diff --git sources/TmuxController.m sources/TmuxController.m |
318 + | index 08cbd91..77b0424 100644 |
319 + | --- sources/TmuxController.m |
320 + | +++ sources/TmuxController.m |
321 + | |
322 + | #import "iTermShortcut.h" |
323 + | #import "iTermTuple.h" |
324 + | #import "NSArray+iTerm.h" |
325 + | +#import "NSData+iTerm.h" |
326 + | #import "NSFont+iTerm.h" |
327 + | #import "NSStringITerm.h" |
328 + | #import "PreferencePanel.h" |
329 + | |
330 + | NSString *const kTmuxControllerSessionWasRenamed = @"kTmuxControllerSessionWasRenamed"; |
331 + | NSString *const kTmuxControllerDidFetchSetTitlesStringOption = @"kTmuxControllerDidFetchSetTitlesStringOption"; |
332 + | |
333 + | +static NSString *const iTermTmuxControllerEncodingPrefixHotkeys = @"h_"; |
334 + | +static NSString *const iTermTmuxControllerEncodingPrefixTabColors = @"t_"; |
335 + | +static NSString *const iTermTmuxControllerEncodingPrefixAffinities = @"a_"; |
336 + | +static NSString *const iTermTmuxControllerEncodingPrefixOrigins = @"o_"; |
337 + | +static NSString *const iTermTmuxControllerEncodingPrefixHidden = @"i_"; |
338 + | + |
339 + | // Unsupported global options: |
340 + | static NSString *const kAggressiveResize = @"aggressive-resize"; |
341 + | |
342 + | |
343 + | TmuxGateway *gateway_; |
344 + | NSMutableDictionary *windowPanes_; // paneId -> PTYSession * |
345 + | NSMutableDictionary<NSNumber *, iTermTmuxWindowState *> *_windowStates; // Key is window number |
346 + | - NSArray *sessions_; |
347 + | + NSArray<iTermTmuxSessionObject *> *sessionObjects_; |
348 + | int numOutstandingWindowResizes_; |
349 + | NSMutableDictionary *windowPositions_; |
350 + | NSSize lastSize_; // last size for windowDidChange: |
351 + | |
352 + | BOOL windowOriginsDirty_; |
353 + | BOOL haveOutstandingSaveWindowOrigins_; |
354 + | NSMutableDictionary *origins_; // window id -> NSValue(Point) window origin |
355 + | - NSMutableSet *hiddenWindows_; |
356 + | + NSMutableSet<NSNumber *> *hiddenWindows_; |
357 + | NSTimer *listSessionsTimer_; // Used to do a cancelable delayed perform of listSessions. |
358 + | NSTimer *listWindowsTimer_; // Used to do a cancelable delayed perform of listWindows. |
359 + | BOOL ambiguousIsDoubleWidth_; |
360 + | |
361 + | @synthesize gateway = gateway_; |
362 + | @synthesize windowPositions = windowPositions_; |
363 + | @synthesize sessionName = sessionName_; |
364 + | -@synthesize sessions = sessions_; |
365 + | +@synthesize sessionObjects = sessionObjects_; |
366 + | @synthesize ambiguousIsDoubleWidth = ambiguousIsDoubleWidth_; |
367 + | @synthesize sessionId = sessionId_; |
368 + | |
369 + | |
370 + | [_sharedFontOverrides release]; |
371 + | [_pendingWindows release]; |
372 + | [sessionName_ release]; |
373 + | - [sessions_ release]; |
374 + | - [_setTitlesString release]; |
375 + | + [sessionObjects_ release]; |
376 + | |
377 + | [super dealloc]; |
378 + | } |
379 + | |
380 + | } |
381 + | [windowsToOpen addObject:record]; |
382 + | } |
383 + | + BOOL tooMany = NO; |
384 + | if (windowsToOpen.count > [iTermPreferences intForKey:kPreferenceKeyTmuxDashboardLimit]) { |
385 + | DLog(@"There are too many windows to open so just show the dashboard"); |
386 + | haveHidden = YES; |
387 + | + tooMany = YES; |
388 + | [windowsToOpen removeAllObjects]; |
389 + | } |
390 + | if (haveHidden) { |
391 + | DLog(@"Hidden windows existing, showing dashboard"); |
392 + | [[TmuxDashboardController sharedInstance] showWindow:nil]; |
393 + | [[[TmuxDashboardController sharedInstance] window] makeKeyAndOrderFront:nil]; |
394 + | - [[iTermNotificationController sharedInstance] notify:@"Too many tmux windows!" withDescription:@"Use the tmux dashboard to select which to open."]; |
395 + | + if (tooMany) { |
396 + | + [[iTermNotificationController sharedInstance] notify:@"Too many tmux windows!" withDescription:@"Use the tmux dashboard to select which to open."]; |
397 + | + } else { |
398 + | + [[iTermNotificationController sharedInstance] notify:@"Some tmux windows were hidden." withDescription:@"Use the tmux dashboard to select which to open."]; |
399 + | + } |
400 + | } |
401 + | for (NSArray *record in windowsToOpen) { |
402 + | DLog(@"Open window %@", record); |
403 + | |
404 + | NSString *setSizeCommand = [NSString stringWithFormat:@"refresh-client -C %d,%d", |
405 + | size.width, [self adjustHeightForStatusBar:size.height]]; |
406 + | NSString *listWindowsCommand = [NSString stringWithFormat:@"list-windows -F %@", kListWindowsFormat]; |
407 + | - NSString *listSessionsCommand = @"list-sessions -F \"#{session_name}\""; |
408 + | + NSString *listSessionsCommand = @"list-sessions -F \"#{session_id} #{session_name}\""; |
409 + | NSString *getAffinitiesCommand = [NSString stringWithFormat:@"show -v -q -t $%d @affinities", sessionId_]; |
410 + | NSString *getOriginsCommand = [NSString stringWithFormat:@"show -v -q -t $%d @origins", sessionId_]; |
411 + | NSString *getHotkeysCommand = [NSString stringWithFormat:@"show -v -q -t $%d @hotkeys", sessionId_]; |
412 + | |
413 + | } |
414 + | } |
415 + | |
416 + | -- (void)setSize:(NSSize)size windows:(NSArray<NSString *> *)windows { |
417 + | - [gateway_ sendCommandList:[self commandListToSetSize:size ofWindows:windows]]; |
418 + | -} |
419 + | - |
420 + | - (void)setWindowSizes:(NSArray<iTermTuple<NSString *, NSValue *> *> *)windowSizes { |
421 + | [gateway_ sendCommandList:[self commandListToSetWindowSizes:windowSizes]]; |
422 + | } |
423 + | |
424 + | return [windowSizes mapWithBlock:^NSDictionary *(iTermTuple<NSString *,NSValue *> *tuple) { |
425 + | NSString *window = tuple.firstObject; |
426 + | NSSize size = tuple.secondObject.sizeValue; |
427 + | - if ([window hasPrefix:@"pty"]) { |
428 + | + if ([window hasPrefix:@"pty"] || [window hasSuffix:@"_ph"]) { |
429 + | return nil; |
430 + | } |
431 + | // 10000 comes from WINDOW_MAXIMUM in tmux.h |
432 + | if (size.width < 1 || size.height < 1 || size.width >= 10000 || size.height >= 10000) { |
433 + | return nil; |
434 + | } |
435 + | - NSString *command = [NSString stringWithFormat:@"resize-window -x %@ -y %@ -t @%@", @((int)size.width), @((int)size.height), window]; |
436 + | + NSString *command = [NSString stringWithFormat:@"resize-window -x %@ -y %@ -t @%d", @((int)size.width), @((int)size.height), window.intValue]; |
437 + | NSDictionary *dict = [gateway_ dictionaryForCommand:command |
438 + | responseTarget:self |
439 + | responseSelector:@selector(handleResizeWindowResponse:) |
440 + | |
441 + | [gateway_ sendCommandList:@[ [gateway_ dictionaryForCommand:@"show-options -v -g set-titles" |
442 + | responseTarget:self |
443 + | responseSelector:@selector(handleShowSetTitles:) |
444 + | - responseObject:nil |
445 + | - flags:0], |
446 + | - [gateway_ dictionaryForCommand:@"show-options -v -g set-titles-string" |
447 + | - responseTarget:self |
448 + | - responseSelector:@selector(handleShowSetTitlesString:) |
449 + | responseObject:nil |
450 + | flags:0] ]]; |
451 + | } |
452 + | |
453 + | object:self]; |
454 + | } |
455 + | |
456 + | -- (void)handleShowSetTitlesString:(NSString *)setTitlesString { |
457 + | - _setTitlesString = [setTitlesString copy]; |
458 + | -} |
459 + | - |
460 + | - (void)guessVersion { |
461 + | // Run commands that will fail in successively older versions. |
462 + | // show-window-options pane-border-format will succeed in 2.3 and later (presumably. 2.3 isn't out yet) |
463 + | |
464 + | } |
465 + | |
466 + | // This is the oldest version supported. By the time you get here you know the version. |
467 + | + [self didGuessVersion]; |
468 + | +} |
469 + | + |
470 + | +// Actions to perform after the version number is known. |
471 + | +- (void)didGuessVersion { |
472 + | [self loadServerPID]; |
473 + | + [self loadTitleFormat]; |
474 + | } |
475 + | |
476 + | -- (BOOL)recyclingSupported { |
477 + | - NSDecimalNumber *version1_9 = [NSDecimalNumber decimalNumberWithString:@"1.9"]; |
478 + | - if (gateway_.minimumServerVersion != nil) { |
479 + | - return ([gateway_.minimumServerVersion compare:version1_9] != NSOrderedAscending); |
480 + | - } else { |
481 + | - // Assume 1.8 |
482 + | +- (BOOL)versionAtLeastDecimalNumberWithString:(NSString *)string { |
483 + | + NSDecimalNumber *version = [NSDecimalNumber decimalNumberWithString:string]; |
484 + | + if (gateway_.minimumServerVersion == nil) { |
485 + | return NO; |
486 + | } |
487 + | + return ([gateway_.minimumServerVersion compare:version] != NSOrderedAscending); |
488 + | +} |
489 + | + |
490 + | +- (BOOL)recyclingSupported { |
491 + | + return [self versionAtLeastDecimalNumberWithString:@"1.9"]; |
492 + | } |
493 + | |
494 + | // Show an error and terminate the connection because tmux has an unsupported option turned on. |
495 + | |
496 + | responseSelector:nil]; |
497 + | } |
498 + | |
499 + | -- (void)newWindowInSession:(NSString *)targetSession |
500 + | - scope:(iTermVariableScope *)scope |
501 + | - initialDirectory:(iTermInitialDirectory *)initialDirectory { |
502 + | - [initialDirectory tmuxNewWindowCommandInSession:targetSession |
503 + | +- (void)newWindowInSessionNumber:(NSNumber *)sessionNumber |
504 + | + scope:(iTermVariableScope *)scope |
505 + | + initialDirectory:(iTermInitialDirectory *)initialDirectory { |
506 + | + [initialDirectory tmuxNewWindowCommandInSessionNumber:sessionNumber |
507 + | recyclingSupported:self.recyclingSupported |
508 + | scope:scope |
509 + | completion: |
510 + | |
511 + | responseObject:nil |
512 + | flags:0]]; |
513 + | } |
514 + | - [commands addObject:[gateway_ dictionaryForCommand:command responseTarget:nil responseSelector:nil responseObject:nil flags:0]]; |
515 + | + [commands addObject:[gateway_ dictionaryForCommand:command |
516 + | + responseTarget:nil |
517 + | + responseSelector:nil |
518 + | + responseObject:nil |
519 + | + flags:0]]; |
520 + | [gateway_ sendCommandList:commands]; |
521 + | }]; |
522 + | } |
523 + | |
524 + | responseSelector:nil]; |
525 + | } |
526 + | |
527 + | -- (void)unlinkWindowWithId:(int)windowId inSession:(NSString *)sessionName |
528 + | -{ |
529 + | +- (void)unlinkWindowWithId:(int)windowId { |
530 + | [gateway_ sendCommand:[NSString stringWithFormat:@"unlink-window -k -t @%d", windowId] |
531 + | responseTarget:nil |
532 + | responseSelector:nil |
533 + | |
534 + | } |
535 + | |
536 + | - (void)renameWindowWithId:(int)windowId |
537 + | - inSession:(NSString *)sessionName |
538 + | + inSessionNumber:(NSNumber *)sessionNumber |
539 + | toName:(NSString *)newName { |
540 + | NSString *theCommand; |
541 + | - if (sessionName) { |
542 + | - theCommand = [NSString stringWithFormat:@"rename-window -t \"%@:@%d\" \"%@\"", sessionName, windowId, [self stringByEscapingBackslashesAndRemovingNewlines:newName]]; |
543 + | + if (sessionNumber) { |
544 + | + theCommand = [NSString stringWithFormat:@"rename-window -t \"$%d:@%d\" \"%@\"", |
545 + | + sessionNumber.intValue, |
546 + | + windowId, |
547 + | + [self stringByEscapingBackslashesAndRemovingNewlines:newName]]; |
548 + | } else { |
549 + | theCommand = [NSString stringWithFormat:@"rename-window -t @%d \"%@\"", windowId, [self stringByEscapingBackslashesAndRemovingNewlines:newName]]; |
550 + | } |
551 + | |
552 + | [self sendCommandToSetTabColors]; |
553 + | } |
554 + | |
555 + | +- (NSString *)encodedString:(NSString *)string prefix:(NSString *)prefix { |
556 + | + return [prefix stringByAppendingString:[[string dataUsingEncoding:NSUTF8StringEncoding] it_hexEncoded]]; |
557 + | +} |
558 + | + |
559 + | +- (NSString *)decodedString:(NSString *)string optionalPrefix:(NSString *)prefix { |
560 + | + if (![string hasPrefix:prefix]) { |
561 + | + return string; |
562 + | + } |
563 + | + return [[[NSString alloc] initWithData:[[string substringFromIndex:prefix.length] dataFromHexValues] |
564 + | + encoding:NSUTF8StringEncoding] autorelease]; |
565 + | +} |
566 + | + |
567 + | - (void)sendCommandToSetHotkeys { |
568 + | + NSString *hexEncoded = [self encodedString:[self.hotkeysString stringByEscapingQuotes] |
569 + | + prefix:iTermTmuxControllerEncodingPrefixHotkeys]; |
570 + | NSString *command = [NSString stringWithFormat:@"set -t $%d @hotkeys \"%@\"", |
571 + | - sessionId_, [self.hotkeysString stringByEscapingQuotes]]; |
572 + | + sessionId_, hexEncoded]; |
573 + | [gateway_ sendCommand:command |
574 + | responseTarget:nil |
575 + | responseSelector:nil |
576 + | |
577 + | } |
578 + | |
579 + | - (void)sendCommandToSetTabColors { |
580 + | + |
581 + | NSString *command = [NSString stringWithFormat:@"set -t $%d @tab_colors \"%@\"", |
582 + | - sessionId_, [self.tabColorsString stringByEscapingQuotes]]; |
583 + | + sessionId_, [self encodedString:[self.tabColorsString stringByEscapingQuotes] |
584 + | + prefix:iTermTmuxControllerEncodingPrefixTabColors]]; |
585 + | [gateway_ sendCommand:command |
586 + | responseTarget:nil |
587 + | responseSelector:nil |
588 + | |
589 + | |
590 + | - (void)breakOutWindowPane:(int)windowPane toTabAside:(NSString *)sibling |
591 + | { |
592 + | - [gateway_ sendCommand:[NSString stringWithFormat:@"break-pane -P -F \"#{window_id}\" %@ \"%%%d\"", [self breakPaneWindowPaneFlag], windowPane] |
593 + | + [gateway_ sendCommand:[NSString stringWithFormat:@"break-pane -P -F \"#{window_id}\" %@ \"%%%d\"", |
594 + | + [self breakPaneWindowPaneFlag], windowPane] |
595 + | responseTarget:self |
596 + | responseSelector:@selector(windowPaneBrokeOutWithWindowId:setAffinityTo:) |
597 + | responseObject:sibling |
598 + | |
599 + | } |
600 + | |
601 + | - (void)linkWindowId:(int)windowId |
602 + | - inSession:(NSString *)sessionName |
603 + | - toSession:(NSString *)targetSession { |
604 + | - [gateway_ sendCommand:[NSString stringWithFormat:@"link-window -s \"%@:@%d\" -t \"%@:+\"", |
605 + | - sessionName, windowId, targetSession] |
606 + | + inSessionNumber:(int)sessionNumber |
607 + | + toSessionNumber:(int)targetSessionNumber { |
608 + | + [gateway_ sendCommand:[NSString stringWithFormat:@"link-window -s \"$%d:@%d\" -t \"$%d:+\"", |
609 + | + sessionNumber, windowId, targetSessionNumber] |
610 + | responseTarget:nil |
611 + | responseSelector:nil]; |
612 + | } |
613 + | |
614 + | - (void)moveWindowId:(int)windowId |
615 + | - inSession:(NSString *)sessionName |
616 + | - toSession:(NSString *)targetSession { |
617 + | - [gateway_ sendCommand:[NSString stringWithFormat:@"move-window -s \"%@:@%d\" -t \"%@:+\"", |
618 + | - sessionName, windowId, targetSession] |
619 + | + inSessionNumber:(int)sessionNumber |
620 + | + toSessionNumber:(int)targetSessionNumber { |
621 + | + [gateway_ sendCommand:[NSString stringWithFormat:@"move-window -s \"$%d:@%d\" -t \"$%d:+\"", |
622 + | + sessionNumber, windowId, targetSessionNumber] |
623 + | responseTarget:nil |
624 + | responseSelector:nil]; |
625 + | } |
626 + | |
627 + | return pos; |
628 + | } |
629 + | |
630 + | -- (void)renameSession:(NSString *)oldName to:(NSString *)newName |
631 + | -{ |
632 + | - NSString *renameCommand = [NSString stringWithFormat:@"rename-session -t \"%@\" \"%@\"", |
633 + | - [oldName stringByEscapingQuotes], |
634 + | +- (void)renameSessionNumber:(int)sessionNumber |
635 + | + to:(NSString *)newName { |
636 + | + NSString *renameCommand = [NSString stringWithFormat:@"rename-session -t \"$%d\" \"%@\"", |
637 + | + sessionNumber, |
638 + | [newName stringByEscapingQuotes]]; |
639 + | [gateway_ sendCommand:renameCommand responseTarget:nil responseSelector:nil]; |
640 + | } |
641 + | |
642 + | -- (void)killSession:(NSString *)sessionName |
643 + | -{ |
644 + | - NSString *killCommand = [NSString stringWithFormat:@"kill-session -t \"%@\"", |
645 + | - [sessionName stringByEscapingQuotes]]; |
646 + | - [gateway_ sendCommand:killCommand responseTarget:nil responseSelector:nil]; |
647 + | +- (void)killSessionNumber:(int)sessionNumber { |
648 + | + NSString *killCommand = [NSString stringWithFormat:@"kill-session -t \"$%d\"", sessionNumber]; |
649 + | + [gateway_ sendCommand:killCommand |
650 + | + responseTarget:nil |
651 + | + responseSelector:nil]; |
652 + | [self listSessions]; |
653 + | } |
654 + | |
655 + | |
656 + | [self listSessions]; |
657 + | } |
658 + | |
659 + | -- (void)attachToSession:(NSString *)sessionName |
660 + | -{ |
661 + | - NSString *attachCommand = [NSString stringWithFormat:@"attach-session -t \"%@\"", |
662 + | - [sessionName stringByEscapingQuotes]]; |
663 + | +- (void)attachToSessionWithNumber:(int)sessionNumber { |
664 + | + NSString *attachCommand = [NSString stringWithFormat:@"attach-session -t \"$%d\"", sessionNumber]; |
665 + | [gateway_ sendCommand:attachCommand |
666 + | responseTarget:nil |
667 + | responseSelector:nil]; |
668 + | } |
669 + | |
670 + | -- (void)listWindowsInSession:(NSString *)sessionName |
671 + | - target:(id)target |
672 + | - selector:(SEL)selector |
673 + | - object:(id)object { |
674 + | +- (void)listWindowsInSessionNumber:(int)sessionNumber |
675 + | + target:(id)target |
676 + | + selector:(SEL)selector |
677 + | + object:(id)object { |
678 + | if (detached_ || !object) { |
679 + | // This can happen if you're not attached to a session. |
680 + | return; |
681 + | } |
682 + | - NSString *listWindowsCommand = [NSString stringWithFormat:@"list-windows -F %@ -t \"%@\"", |
683 + | - kListWindowsFormat, sessionName]; |
684 + | + NSString *listWindowsCommand = [NSString stringWithFormat:@"list-windows -F %@ -t \"$%d\"", |
685 + | + kListWindowsFormat, sessionNumber]; |
686 + | // Wait a few seconds. We always get a windows-close notification when the last window in |
687 + | // a window closes. To avoid spamming the command line with list-windows, we wait a bit to see |
688 + | // if there is an exit notification coming down the pipe. |
689 + | const CGFloat kListWindowsDelay = 1.5; |
690 + | [listWindowsTimer_ invalidate]; |
691 + | - listWindowsTimer_ = [NSTimer scheduledTimerWithTimeInterval:kListWindowsDelay |
692 + | - target:self |
693 + | - selector:@selector(listWindowsTimerFired:) |
694 + | - userInfo:[NSArray arrayWithObjects:listWindowsCommand, object, target, NSStringFromSelector(selector), nil] |
695 + | - repeats:NO]; |
696 + | + listWindowsTimer_ = |
697 + | + [NSTimer scheduledTimerWithTimeInterval:kListWindowsDelay |
698 + | + target:self |
699 + | + selector:@selector(listWindowsTimerFired:) |
700 + | + userInfo:@[listWindowsCommand, |
701 + | + object, |
702 + | + target, |
703 + | + NSStringFromSelector(selector) ] |
704 + | + repeats:NO]; |
705 + | } |
706 + | |
707 + | - (void)listWindowsTimerFired:(NSTimer *)timer |
708 + | { |
709 + | NSArray *array = [timer userInfo]; |
710 + | - NSString *command = [array objectAtIndex:0]; |
711 + | - id object = [array objectAtIndex:1]; |
712 + | - id target = [array objectAtIndex:2]; |
713 + | - NSString *selector = [array objectAtIndex:3]; |
714 + | + NSString *command = array[0]; |
715 + | + id object = array[1]; |
716 + | + id target = array[2]; |
717 + | + NSString *selector = array[3]; |
718 + | |
719 + | [listWindowsTimer_ invalidate]; |
720 + | listWindowsTimer_ = nil; |
721 + | |
722 + | [gateway_ sendCommand:command |
723 + | responseTarget:self |
724 + | responseSelector:@selector(didListWindows:userData:) |
725 + | - responseObject:[NSArray arrayWithObjects:object, |
726 + | - selector, |
727 + | - target, |
728 + | - nil] |
729 + | + responseObject:@[object, selector, target] |
730 + | flags:kTmuxGatewayCommandShouldTolerateErrors]; // Tolerates errors because the session may have been detached by the time we get the notification or the timer fires. |
731 + | } |
732 + | |
733 + | |
734 + | { |
735 + | NSString *hidden = [[hiddenWindows_ allObjects] componentsJoinedByString:@","]; |
736 + | NSString *command = [NSString stringWithFormat: |
737 + | - @"set -t $%d @hidden \"%@\"", |
738 + | - sessionId_, hidden]; |
739 + | + @"set -t $%d @hidden \"%@\"", |
740 + | + sessionId_, |
741 + | + [self encodedString:hidden |
742 + | + prefix:iTermTmuxControllerEncodingPrefixHidden]]; |
743 + | [gateway_ sendCommand:command |
744 + | responseTarget:nil |
745 + | responseSelector:nil |
746 + | |
747 + | NSString *enc = [maps componentsJoinedByString:@" "]; |
748 + | DLog(@"Save window origins to %@ called from %@", enc, [NSThread callStackSymbols]); |
749 + | NSString *command = [NSString stringWithFormat:@"set -t $%d @origins \"%@\"", |
750 + | - sessionId_, [enc stringByEscapingQuotes]]; |
751 + | + sessionId_, |
752 + | + [self encodedString:[enc stringByEscapingQuotes] |
753 + | + prefix:iTermTmuxControllerEncodingPrefixOrigins]]; |
754 + | if (!lastOrigins_ || ![command isEqualToString:lastOrigins_]) { |
755 + | [lastOrigins_ release]; |
756 + | lastOrigins_ = [command copy]; |
757 + | |
758 + | responseTarget:self |
759 + | responseSelector:@selector(saveWindowOriginsResponse:)]; |
760 + | } |
761 + | - [self getOriginsResponse:enc]; |
762 + | + [self getOriginsResponse:[self encodedString:[enc stringByEscapingQuotes] |
763 + | + prefix:iTermTmuxControllerEncodingPrefixOrigins]]; |
764 + | } |
765 + | |
766 + | - (void)saveWindowOriginsResponse:(NSString *)response |
767 + | |
768 + | // Update affinities if any have changed. |
769 + | NSString *arg = [affinities componentsJoinedByString:@" "]; |
770 + | NSString *command = [NSString stringWithFormat:@"set -t $%d @affinities \"%@\"", |
771 + | - sessionId_, [arg stringByEscapingQuotes]]; |
772 + | + sessionId_, [self encodedString:[arg stringByEscapingQuotes] |
773 + | + prefix:iTermTmuxControllerEncodingPrefixAffinities]]; |
774 + | if ([command isEqualToString:lastSaveAffinityCommand_]) { |
775 + | return; |
776 + | } |
777 + | |
778 + | responseSelector:@selector(didSetLayout:) |
779 + | responseObject:nil |
780 + | flags:0], |
781 + | - [gateway_ dictionaryForCommand:[NSString stringWithFormat:@"list-windows -F \"#{window_id} #{window_layout} #{window_flags}\" -t \"%@\"", sessionName_] |
782 + | + [gateway_ dictionaryForCommand:[NSString stringWithFormat:@"list-windows -F \"#{window_id} #{window_layout} #{window_flags}\" -t \"$%d\"", sessionId_] |
783 + | responseTarget:self |
784 + | responseSelector:@selector(didListWindowsSubsequentToSettingLayout:) |
785 + | responseObject:nil |
786 + | |
787 + | responseSelector:@selector(didSetLayout:) |
788 + | responseObject:nil |
789 + | flags:0], |
790 + | - [gateway_ dictionaryForCommand:[NSString stringWithFormat:@"list-windows -F \"#{window_id} #{window_layout} #{window_flags}\" -t \"%@\"", sessionName_] |
791 + | + [gateway_ dictionaryForCommand:[NSString stringWithFormat:@"list-windows -F \"#{window_id} #{window_layout} #{window_flags}\" -t \"$%d\"", sessionId_] |
792 + | responseTarget:self |
793 + | responseSelector:@selector(didListWindowsSubsequentToSettingLayout:) |
794 + | responseObject:nil |
795 + | |
796 + | |
797 + | #pragma mark - Private |
798 + | |
799 + | -- (void)getOriginsResponse:(NSString *)result |
800 + | -{ |
801 + | - [origins_ removeAllObjects]; |
802 + | - if ([result length] > 0) { |
803 + | - NSArray *windows = [result componentsSeparatedByString:@" "]; |
804 + | - for (NSString *wstr in windows) { |
805 + | - NSArray *tuple = [wstr componentsSeparatedByString:@":"]; |
806 + | - if (tuple.count != 2) { |
807 + | - continue; |
808 + | - } |
809 + | - NSString *windowsStr = [tuple objectAtIndex:0]; |
810 + | - NSString *coords = [tuple objectAtIndex:1]; |
811 + | - NSArray *windowIds = [windowsStr componentsSeparatedByString:@","]; |
812 + | - NSArray *xy = [coords componentsSeparatedByString:@","]; |
813 + | - if (xy.count != 2) { |
814 + | - continue; |
815 + | - } |
816 + | - NSPoint origin = NSMakePoint([[xy objectAtIndex:0] intValue], |
817 + | - [[xy objectAtIndex:1] intValue]); |
818 + | - for (NSString *wid in windowIds) { |
819 + | - [origins_ setObject:[NSValue valueWithPoint:origin] |
820 + | - forKey:[NSNumber numberWithInt:[wid intValue]]]; |
821 + | - } |
822 + | - } |
823 + | - } |
824 + | +- (void)getOriginsResponse:(NSString *)encodedResult { |
825 + | + NSString *result = [self decodedString:encodedResult |
826 + | + optionalPrefix:iTermTmuxControllerEncodingPrefixOrigins]; |
827 + | + [origins_ removeAllObjects]; |
828 + | + if ([result length] > 0) { |
829 + | + NSArray *windows = [result componentsSeparatedByString:@" "]; |
830 + | + for (NSString *wstr in windows) { |
831 + | + NSArray *tuple = [wstr componentsSeparatedByString:@":"]; |
832 + | + if (tuple.count != 2) { |
833 + | + continue; |
834 + | + } |
835 + | + NSString *windowsStr = [tuple objectAtIndex:0]; |
836 + | + NSString *coords = [tuple objectAtIndex:1]; |
837 + | + NSArray *windowIds = [windowsStr componentsSeparatedByString:@","]; |
838 + | + NSArray *xy = [coords componentsSeparatedByString:@","]; |
839 + | + if (xy.count != 2) { |
840 + | + continue; |
841 + | + } |
842 + | + NSPoint origin = NSMakePoint([[xy objectAtIndex:0] intValue], |
843 + | + [[xy objectAtIndex:1] intValue]); |
844 + | + for (NSString *wid in windowIds) { |
845 + | + [origins_ setObject:[NSValue valueWithPoint:origin] |
846 + | + forKey:[NSNumber numberWithInt:[wid intValue]]]; |
847 + | + } |
848 + | + } |
849 + | + } |
850 + | } |
851 + | |
852 + | - (NSString *)shortStringForHotkeyDictionary:(NSDictionary *)dict paneID:(int)wp { |
853 + | |
854 + | return [parts componentsJoinedByString:@" "]; |
855 + | } |
856 + | |
857 + | -- (void)getHotkeysResponse:(NSString *)result { |
858 + | +- (void)getHotkeysResponse:(NSString *)encodedResult { |
859 + | + NSString *result = [self decodedString:encodedResult optionalPrefix:iTermTmuxControllerEncodingPrefixHotkeys]; |
860 + | [_hotkeys removeAllObjects]; |
861 + | if (result.length > 0) { |
862 + | [_hotkeys removeAllObjects]; |
863 + | |
864 + | } |
865 + | } |
866 + | |
867 + | -- (void)getTabColorsResponse:(NSString *)result { |
868 + | +- (void)getTabColorsResponse:(NSString *)encodedResult { |
869 + | + NSString *result = [self decodedString:encodedResult |
870 + | + optionalPrefix:iTermTmuxControllerEncodingPrefixTabColors]; |
871 + | [_tabColors removeAllObjects]; |
872 + | if (result.length > 0) { |
873 + | [_tabColors removeAllObjects]; |
874 + | |
875 + | response = @""; |
876 + | } |
877 + | TSVDocument *doc = [response tsvDocumentWithFields:[self listWindowFields]]; |
878 + | - id object = [userData objectAtIndex:0]; |
879 + | - SEL selector = NSSelectorFromString([userData objectAtIndex:1]); |
880 + | - id target = [userData objectAtIndex:2]; |
881 + | + id object = userData[0]; |
882 + | + SEL selector = NSSelectorFromString(userData[1]); |
883 + | + id target = userData[2]; |
884 + | [target performSelector:selector withObject:doc withObject:object]; |
885 + | } |
886 + | |
887 + | -- (void)getHiddenWindowsResponse:(NSString *)response |
888 + | -{ |
889 + | +- (void)getHiddenWindowsResponse:(NSString *)encodedResponse { |
890 + | + NSString *response = [self decodedString:encodedResponse |
891 + | + optionalPrefix:iTermTmuxControllerEncodingPrefixHidden]; |
892 + | [hiddenWindows_ removeAllObjects]; |
893 + | if ([response length] > 0) { |
894 + | NSArray *windowIds = [response componentsSeparatedByString:@","]; |
895 + | |
896 + | } |
897 + | |
898 + | - (void)getAffinitiesResponse:(NSString *)result { |
899 + | - [self setAffinitiesFromString:result]; |
900 + | + [self setAffinitiesFromString:[self decodedString:result optionalPrefix:iTermTmuxControllerEncodingPrefixAffinities]]; |
901 + | } |
902 + | |
903 + | - (NSArray *)componentsOfAffinities:(NSString *)affinities { |
904 + | |
905 + | |
906 + | - (void)listSessionsResponse:(NSString *)result |
907 + | { |
908 + | - self.sessions = [result componentsSeparatedByRegex:@"\n"]; |
909 + | + self.sessionObjects = [[result componentsSeparatedByRegex:@"\n"] mapWithBlock:^iTermTmuxSessionObject *(NSString *line) { |
910 + | + const NSInteger space = [line rangeOfString:@" "].location; |
911 + | + if (space == NSNotFound) { |
912 + | + return nil; |
913 + | + } |
914 + | + NSString *sessionID = [line substringToIndex:space]; |
915 + | + NSString *sessionName = [line substringFromIndex:space + 1]; |
916 + | + if (![sessionID hasPrefix:@"$"]) { |
917 + | + return nil; |
918 + | + } |
919 + | + NSScanner *scanner = [NSScanner scannerWithString:[sessionID substringFromIndex:1]]; |
920 + | + int sessionNumber = -1; |
921 + | + if (![scanner scanInt:&sessionNumber] || sessionNumber < 0) { |
922 + | + return nil; |
923 + | + } |
924 + | + iTermTmuxSessionObject *obj = [[[iTermTmuxSessionObject alloc] init] autorelease]; |
925 + | + obj.name = sessionName; |
926 + | + obj.number = sessionNumber; |
927 + | + return obj; |
928 + | + }]; |
929 + | [[NSNotificationCenter defaultCenter] postNotificationName:kTmuxControllerSessionsDidChange |
930 + | - object:self.sessions]; |
931 + | + object:nil]; |
932 + | } |
933 + | |
934 + | - (void)listedWindowsToOpenOne:(NSString *)response |
935 + | diff --git sources/TmuxDashboardController.m sources/TmuxDashboardController.m |
936 + | index b83bfe6..b2ed7b7 100644 |
937 + | --- sources/TmuxDashboardController.m |
938 + | +++ sources/TmuxDashboardController.m |
939 + | |
940 + | if (self) { |
941 + | [self window]; |
942 + | |
943 + | - [sessionsTable_ selectSessionWithName:[[self tmuxController] sessionName]]; |
944 + | + [sessionsTable_ selectSessionNumber:[[self tmuxController] sessionId]]; |
945 + | [self reloadWindows]; |
946 + | [[NSNotificationCenter defaultCenter] addObserver:self |
947 + | selector:@selector(tmuxControllerDetached:) |
948 + | |
949 + | |
950 + | #pragma mark TmuxSessionsTableProtocol |
951 + | |
952 + | -- (void)renameSessionWithName:(NSString *)oldName toName:(NSString *)newName |
953 + | -{ |
954 + | - [[self tmuxController] renameSession:oldName to:newName]; |
955 + | +- (void)renameSessionWithNumber:(int)sessionNumber toName:(NSString *)newName { |
956 + | + [[self tmuxController] renameSessionNumber:sessionNumber |
957 + | + to:newName]; |
958 + | } |
959 + | |
960 + | -- (void)removeSessionWithName:(NSString *)sessionName |
961 + | -{ |
962 + | - [[self tmuxController] killSession:sessionName]; |
963 + | +- (void)removeSessionWithNumber:(int)sessionNumber { |
964 + | + [[self tmuxController] killSessionNumber:sessionNumber]; |
965 + | } |
966 + | |
967 + | -- (void)addSessionWithName:(NSString *)sessionName |
968 + | -{ |
969 + | +- (void)addSessionWithName:(NSString *)sessionName { |
970 + | [[self tmuxController] addSessionWithName:sessionName]; |
971 + | } |
972 + | |
973 + | -- (void)attachToSessionWithName:(NSString *)sessionName |
974 + | -{ |
975 + | - [[self tmuxController] attachToSession:sessionName]; |
976 + | +- (void)attachToSessionWithNumber:(int)sessionNumber { |
977 + | + [[self tmuxController] attachToSessionWithNumber:sessionNumber]; |
978 + | } |
979 + | |
980 + | -- (void)detach |
981 + | -{ |
982 + | +- (void)detach { |
983 + | [[self tmuxController] requestDetach]; |
984 + | } |
985 + | |
986 + | -- (NSString *)nameOfAttachedSession |
987 + | -{ |
988 + | - return [[self tmuxController] sessionName]; |
989 + | +- (NSNumber *)numberOfAttachedSession { |
990 + | + TmuxController *controller = [self tmuxController]; |
991 + | + if (!controller) { |
992 + | + return nil; |
993 + | + } |
994 + | + return @([controller sessionId]); |
995 + | } |
996 + | |
997 + | -- (NSArray *)sessions |
998 + | -{ |
999 + | - return [[self tmuxController] sessions]; |
1000 + | +- (NSArray<iTermTmuxSessionObject *> *)sessionsTableModelValues:(id)sender { |
1001 + | + return [self.tmuxController sessionObjects]; |
1002 + | } |
1003 + | |
1004 + | -- (void)selectedSessionChangedTo:(NSString *)newSessionName |
1005 + | -{ |
1006 + | +- (NSArray<iTermTmuxSessionObject *> *)sessionsTableObjects:(TmuxSessionsTable *)sender { |
1007 + | + return [[self tmuxController] sessionObjects]; |
1008 + | +} |
1009 + | + |
1010 + | +- (void)selectedSessionDidChange { |
1011 + | [windowsTable_ setWindows:[NSArray array]]; |
1012 + | [self reloadWindows]; |
1013 + | } |
1014 + | |
1015 + | - (void)linkWindowId:(int)windowId |
1016 + | - inSession:(NSString *)sessionName |
1017 + | - toSession:(NSString *)targetSession |
1018 + | -{ |
1019 + | + inSessionNumber:(int)sourceSessionNumber |
1020 + | + toSessionNumber:(int)targetSessionNumber { |
1021 + | [[self tmuxController] linkWindowId:windowId |
1022 + | - inSession:sessionName |
1023 + | - toSession:targetSession]; |
1024 + | + inSessionNumber:sourceSessionNumber |
1025 + | + toSessionNumber:targetSessionNumber]; |
1026 + | } |
1027 + | |
1028 + | - (void)moveWindowId:(int)windowId |
1029 + | - inSession:(NSString *)sessionName |
1030 + | - toSession:(NSString *)targetSession |
1031 + | -{ |
1032 + | + inSessionNumber:(int)sessionNumber |
1033 + | + toSessionNumber:(int)targetSessionNumber { |
1034 + | [[self tmuxController] moveWindowId:windowId |
1035 + | - inSession:sessionName |
1036 + | - toSession:targetSession]; |
1037 + | + inSessionNumber:sessionNumber |
1038 + | + toSessionNumber:targetSessionNumber]; |
1039 + | } |
1040 + | |
1041 + | #pragma mark TmuxWindowsTableProtocol |
1042 + | |
1043 + | -- (void)reloadWindows |
1044 + | -{ |
1045 + | - [[self tmuxController] listWindowsInSession:[sessionsTable_ selectedSessionName] |
1046 + | - target:self |
1047 + | - selector:@selector(setWindows:forSession:) |
1048 + | - object:[sessionsTable_ selectedSessionName]]; |
1049 + | +- (void)reloadWindows { |
1050 + | + NSNumber *sessionNumber = [sessionsTable_ selectedSessionNumber]; |
1051 + | + if (!sessionNumber) { |
1052 + | + return; |
1053 + | + } |
1054 + | + [[self tmuxController] listWindowsInSessionNumber:sessionNumber.intValue |
1055 + | + target:self |
1056 + | + selector:@selector(setWindows:forSession:) |
1057 + | + object:[sessionsTable_ selectedSessionNumber]]; |
1058 + | } |
1059 + | |
1060 + | -- (void)setWindows:(TSVDocument *)doc forSession:(NSString *)sessionName |
1061 + | -{ |
1062 + | - if ([sessionName isEqualToString:[sessionsTable_ selectedSessionName]]) { |
1063 + | +- (void)setWindows:(TSVDocument *)doc forSession:(NSNumber *)sessionNumber { |
1064 + | + if ([sessionNumber isEqual:[sessionsTable_ selectedSessionNumber]]) { |
1065 + | NSMutableArray *windows = [NSMutableArray array]; |
1066 + | for (NSArray *record in doc.records) { |
1067 + | [windows addObject:[NSMutableArray arrayWithObjects: |
1068 + | |
1069 + | } |
1070 + | } |
1071 + | |
1072 + | -- (void)renameWindowWithId:(int)windowId toName:(NSString *)newName |
1073 + | -{ |
1074 + | +- (void)renameWindowWithId:(int)windowId toName:(NSString *)newName { |
1075 + | + NSNumber *sessionNumber = [sessionsTable_ selectedSessionNumber]; |
1076 + | + if (!sessionNumber) { |
1077 + | + return; |
1078 + | + } |
1079 + | [[self tmuxController] renameWindowWithId:windowId |
1080 + | - inSession:[sessionsTable_ selectedSessionName] |
1081 + | + inSessionNumber:sessionNumber |
1082 + | toName:newName]; |
1083 + | [self reloadWindows]; |
1084 + | } |
1085 + | |
1086 + | -- (void)unlinkWindowWithId:(int)windowId |
1087 + | -{ |
1088 + | - [[self tmuxController] unlinkWindowWithId:windowId |
1089 + | - inSession:[sessionsTable_ selectedSessionName]]; |
1090 + | +- (void)unlinkWindowWithId:(int)windowId { |
1091 + | + [[self tmuxController] unlinkWindowWithId:windowId]; |
1092 + | [self reloadWindows]; |
1093 + | } |
1094 + | |
1095 + | |
1096 + | NSString *lastName = [[windowsTable_ names] lastObject]; |
1097 + | if (lastName) { |
1098 + | TmuxController *tmuxController = self.tmuxController; |
1099 + | - [tmuxController newWindowInSession:[sessionsTable_ selectedSessionName] |
1100 + | - scope:[iTermVariableScope globalsScope] |
1101 + | - initialDirectory:[iTermInitialDirectory initialDirectoryFromProfile:tmuxController.sharedProfile |
1102 + | - objectType:iTermWindowObject]]; |
1103 + | + [tmuxController newWindowInSessionNumber:[sessionsTable_ selectedSessionNumber] |
1104 + | + scope:[iTermVariableScope globalsScope] |
1105 + | + initialDirectory:[iTermInitialDirectory initialDirectoryFromProfile:tmuxController.sharedProfile |
1106 + | + objectType:iTermWindowObject]]; |
1107 + | } |
1108 + | } |
1109 + | |
1110 + | -- (void)showWindowsWithIds:(NSArray *)windowIds inTabs:(BOOL)inTabs |
1111 + | -{ |
1112 + | +- (void)showWindowsWithIds:(NSArray *)windowIds inTabs:(BOOL)inTabs { |
1113 + | if (inTabs) { |
1114 + | for (NSNumber *wid in windowIds) { |
1115 + | [[self tmuxController] openWindowWithId:[wid intValue] |
1116 + | |
1117 + | profile:self.tmuxController.sharedProfile]; |
1118 + | } |
1119 + | } |
1120 + | - [[self tmuxController] saveHiddenWindows]; |
1121 + | + [[self tmuxController] saveHiddenWindows]; |
1122 + | } |
1123 + | |
1124 + | - (void)hideWindowWithId:(int)windowId |
1125 + | { |
1126 + | - [[self tmuxController] hideWindow:windowId]; |
1127 + | + [[self tmuxController] hideWindow:windowId]; |
1128 + | [windowsTable_ updateEnabledStateOfButtons]; |
1129 + | } |
1130 + | |
1131 + | -- (BOOL)haveSelectedSession |
1132 + | -{ |
1133 + | - return [sessionsTable_ selectedSessionName] != nil; |
1134 + | +- (BOOL)haveSelectedSession { |
1135 + | + return [sessionsTable_ selectedSessionNumber] != nil; |
1136 + | } |
1137 + | |
1138 + | -- (BOOL)currentSessionSelected |
1139 + | -{ |
1140 + | - return [[sessionsTable_ selectedSessionName] isEqualToString:[[self tmuxController] sessionName]]; |
1141 + | +- (BOOL)currentSessionSelected { |
1142 + | + return [[sessionsTable_ selectedSessionNumber] isEqual:@([[self tmuxController] sessionId])]; |
1143 + | } |
1144 + | |
1145 + | -- (BOOL)haveOpenWindowWithId:(int)windowId |
1146 + | -{ |
1147 + | +- (BOOL)haveOpenWindowWithId:(int)windowId { |
1148 + | return [[self tmuxController] window:windowId] != nil; |
1149 + | } |
1150 + | |
1151 + | |
1152 + | [tab.activeSession reveal]; |
1153 + | } |
1154 + | |
1155 + | -- (NSString *)selectedSessionName |
1156 + | -{ |
1157 + | - return [sessionsTable_ selectedSessionName]; |
1158 + | +- (NSNumber *)selectedSessionNumber { |
1159 + | + return [sessionsTable_ selectedSessionNumber]; |
1160 + | } |
1161 + | |
1162 + | #pragma mark - Private |
1163 + | |
1164 + | -- (void)tmuxControllerDetached:(NSNotification *)notification |
1165 + | -{ |
1166 + | - [sessionsTable_ setSessions:[NSArray array]]; |
1167 + | +- (void)tmuxControllerDetached:(NSNotification *)notification { |
1168 + | + [sessionsTable_ setSessionObjects:@[]]; |
1169 + | } |
1170 + | |
1171 + | -- (void)tmuxControllerSessionsDidChange:(NSNotification *)notification |
1172 + | -{ |
1173 + | - [sessionsTable_ setSessions:[[self tmuxController] sessions]]; |
1174 + | +- (void)tmuxControllerSessionsDidChange:(NSNotification *)notification { |
1175 + | + [sessionsTable_ setSessionObjects:[[self tmuxController] sessionObjects]]; |
1176 + | } |
1177 + | |
1178 + | -- (void)tmuxControllerWindowsDidChange:(NSNotification *)notification |
1179 + | -{ |
1180 + | +- (void)tmuxControllerWindowsDidChange:(NSNotification *)notification { |
1181 + | if ([[self window] isVisible]) { |
1182 + | [self reloadWindows]; |
1183 + | } |
1184 + | } |
1185 + | |
1186 + | -- (void)tmuxControllerAttachedSessionChanged:(NSNotification *)notification |
1187 + | -{ |
1188 + | +- (void)tmuxControllerAttachedSessionChanged:(NSNotification *)notification { |
1189 + | if ([[self window] isVisible]) { |
1190 + | - [sessionsTable_ selectSessionWithName:[[self tmuxController] sessionName]]; |
1191 + | + [sessionsTable_ selectSessionNumber:[[self tmuxController] sessionId]]; |
1192 + | [windowsTable_ updateEnabledStateOfButtons]; |
1193 + | } |
1194 + | } |
1195 + | |
1196 + | -- (void)tmuxControllerWindowOpenedOrClosed:(NSNotification *)notification |
1197 + | -{ |
1198 + | +- (void)tmuxControllerWindowOpenedOrClosed:(NSNotification *)notification { |
1199 + | if ([[self window] isVisible]) { |
1200 + | [windowsTable_ updateEnabledStateOfButtons]; |
1201 + | [windowsTable_ reloadData]; |
1202 + | } |
1203 + | } |
1204 + | |
1205 + | -- (void)tmuxControllerWindowWasRenamed:(NSNotification *)notification |
1206 + | -{ |
1207 + | +- (void)tmuxControllerWindowWasRenamed:(NSNotification *)notification { |
1208 + | if ([[self window] isVisible]) { |
1209 + | NSArray *objects = [notification object]; |
1210 + | int wid = [[objects objectAtIndex:0] intValue]; |
1211 + | |
1212 + | } |
1213 + | } |
1214 + | |
1215 + | -- (void)tmuxControllerSessionWasRenamed:(NSNotification *)notification |
1216 + | -{ |
1217 + | +- (void)tmuxControllerSessionWasRenamed:(NSNotification *)notification { |
1218 + | // This is a bit of extra work but the sessions table wasn't built knowing about session IDs. |
1219 + | [[self tmuxController] listSessions]; |
1220 + | } |
1221 + | |
1222 + | [self connectionSelectionDidChange:nil]; |
1223 + | } |
1224 + | |
1225 + | -- (TmuxController *)tmuxController |
1226 + | -{ |
1227 + | +- (TmuxController *)tmuxController { |
1228 + | return [[TmuxControllerRegistry sharedInstance] controllerForClient:[self currentClient]]; // TODO: track the current client when multiples are supported |
1229 + | } |
1230 + | |
1231 + | |
1232 + | |
1233 + | |
1234 + | - (IBAction)connectionSelectionDidChange:(id)sender { |
1235 + | - [sessionsTable_ setSessions:[[self tmuxController] sessions]]; |
1236 + | + [sessionsTable_ setSessionObjects:[[self tmuxController] sessionObjects]]; |
1237 + | [self reloadWindows]; |
1238 + | } |
1239 + | |
1240 + | diff --git sources/TmuxSessionsTable.h sources/TmuxSessionsTable.h |
1241 + | index 856f0f5..a3e5823 100644 |
1242 + | --- sources/TmuxSessionsTable.h |
1243 + | +++ sources/TmuxSessionsTable.h |
1244 + | |
1245 + | #import <Cocoa/Cocoa.h> |
1246 + | #import "FutureMethods.h" |
1247 + | |
1248 + | +@class TmuxSessionsTable; |
1249 + | +@class iTermTmuxSessionObject; |
1250 + | + |
1251 + | @protocol TmuxSessionsTableProtocol <NSObject> |
1252 + | |
1253 + | -- (NSArray *)sessions; |
1254 + | -- (void)renameSessionWithName:(NSString *)oldName toName:(NSString *)newName; |
1255 + | -- (void)removeSessionWithName:(NSString *)sessionName; |
1256 + | +- (NSArray<iTermTmuxSessionObject *> *)sessionsTableObjects:(TmuxSessionsTable *)sender; |
1257 + | +- (void)renameSessionWithNumber:(int)sessionNumber |
1258 + | + toName:(NSString *)newName; |
1259 + | +- (void)removeSessionWithNumber:(int)sessionNumber; |
1260 + | - (void)addSessionWithName:(NSString *)sessionName; |
1261 + | -- (void)attachToSessionWithName:(NSString *)sessionName; |
1262 + | -- (NSString *)nameOfAttachedSession; |
1263 + | -- (void)selectedSessionChangedTo:(NSString *)newName; |
1264 + | +- (void)attachToSessionWithNumber:(int)sessionNumber; |
1265 + | +- (NSNumber *)numberOfAttachedSession; |
1266 + | +- (void)selectedSessionDidChange; |
1267 + | - (void)linkWindowId:(int)windowId |
1268 + | - inSession:(NSString *)sessionName |
1269 + | - toSession:(NSString *)targetSession; |
1270 + | + inSessionNumber:(int)sourceSessionNumber |
1271 + | + toSessionNumber:(int)targetSessionNumber; |
1272 + | - (void)moveWindowId:(int)windowId |
1273 + | - inSession:(NSString *)sessionName |
1274 + | - toSession:(NSString *)targetSession; |
1275 + | + inSessionNumber:(int)sessionNumber |
1276 + | + toSessionNumber:(int)targetSessionNumber; |
1277 + | - (void)detach; |
1278 + | |
1279 + | @end |
1280 + | |
1281 + | @interface TmuxSessionsTable : NSObject <NSTableViewDelegate, NSTableViewDataSource> |
1282 + | |
1283 + | @property(nonatomic, assign) id<TmuxSessionsTableProtocol> delegate; |
1284 + | -@property(nonatomic, readonly) NSString *selectedSessionName; |
1285 + | +@property(nonatomic, readonly) NSNumber *selectedSessionNumber; |
1286 + | |
1287 + | -- (void)setSessions:(NSArray *)names; |
1288 + | -- (void)selectSessionWithName:(NSString *)name; |
1289 + | +- (void)setSessionObjects:(NSArray<iTermTmuxSessionObject *> *)names; |
1290 + | +- (void)selectSessionNumber:(int)number; |
1291 + | |
1292 + | @end |
1293 + | diff --git sources/TmuxSessionsTable.m sources/TmuxSessionsTable.m |
1294 + | index f9a086f..88e2f72 100644 |
1295 + | --- sources/TmuxSessionsTable.m |
1296 + | +++ sources/TmuxSessionsTable.m |
1297 + | |
1298 + | // |
1299 + | |
1300 + | #import "TmuxSessionsTable.h" |
1301 + | + |
1302 + | #import "FutureMethods.h" |
1303 + | +#import "iTermTmuxSessionObject.h" |
1304 + | +#import "NSArray+iTerm.h" |
1305 + | |
1306 + | extern NSString *kWindowPasteboardType; |
1307 + | |
1308 + | @implementation TmuxSessionsTable { |
1309 + | - NSMutableArray *model_; |
1310 + | + NSMutableArray<iTermTmuxSessionObject *> *_model; |
1311 + | BOOL canAttachToSelectedSession_; |
1312 + | |
1313 + | IBOutlet NSTableColumn *checkColumn_; |
1314 + | |
1315 + | - (instancetype)init { |
1316 + | self = [super init]; |
1317 + | if (self) { |
1318 + | - model_ = [[NSMutableArray alloc] init]; |
1319 + | + _model = [[NSMutableArray alloc] init]; |
1320 + | } |
1321 + | return self; |
1322 + | } |
1323 + | |
1324 + | [tableView_ setDraggingDestinationFeedbackStyle:NSTableViewDraggingDestinationFeedbackStyleRegular]; |
1325 + | } |
1326 + | |
1327 + | -- (void)dealloc |
1328 + | -{ |
1329 + | - [model_ release]; |
1330 + | +- (void)dealloc { |
1331 + | + [_model release]; |
1332 + | [super dealloc]; |
1333 + | } |
1334 + | |
1335 + | - (void)setDelegate:(id<TmuxSessionsTableProtocol>)delegate { |
1336 + | delegate_ = delegate; |
1337 + | - [self setSessions:[delegate_ sessions]]; |
1338 + | + [self setSessionObjects:[delegate_ sessionsTableObjects:self]]; |
1339 + | } |
1340 + | |
1341 + | -- (void)setSessions:(NSArray *)names |
1342 + | +- (void)setSessionObjects:(NSArray<iTermTmuxSessionObject *> *)sessions |
1343 + | { |
1344 + | - [model_ removeAllObjects]; |
1345 + | - [model_ addObjectsFromArray:names]; |
1346 + | + [_model removeAllObjects]; |
1347 + | + [_model addObjectsFromArray:sessions]; |
1348 + | [tableView_ reloadData]; |
1349 + | } |
1350 + | |
1351 + | -- (void)selectSessionWithName:(NSString *)name |
1352 + | -{ |
1353 + | - NSUInteger i = [model_ indexOfObject:name]; |
1354 + | +- (void)selectSessionNumber:(int)number { |
1355 + | + NSInteger i = [_model indexOfObjectPassingTest:^BOOL(iTermTmuxSessionObject * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { |
1356 + | + return obj.number == number; |
1357 + | + }]; |
1358 + | if (i != NSNotFound) { |
1359 + | [tableView_ selectRowIndexes:[NSIndexSet indexSetWithIndex:i] |
1360 + | byExtendingSelection:NO]; |
1361 + | |
1362 + | |
1363 + | - (IBAction)removeSession:(id)sender |
1364 + | { |
1365 + | - NSString *name = [self selectedSessionName]; |
1366 + | - if (name) { |
1367 + | - [delegate_ removeSessionWithName:name]; |
1368 + | + NSNumber *number = [self selectedSessionNumber]; |
1369 + | + if (number) { |
1370 + | + [delegate_ removeSessionWithNumber:number.intValue]; |
1371 + | } |
1372 + | } |
1373 + | |
1374 + | - (IBAction)attach:(id)sender { |
1375 + | - NSString *name = [self selectedSessionName]; |
1376 + | - if (name) { |
1377 + | - [delegate_ attachToSessionWithName:name]; |
1378 + | + NSNumber *number = [self selectedSessionNumber]; |
1379 + | + if (number) { |
1380 + | + [delegate_ attachToSessionWithNumber:number.intValue]; |
1381 + | } |
1382 + | } |
1383 + | |
1384 + | - (IBAction)detach:(id)sender { |
1385 + | - NSString *name = [self selectedSessionName]; |
1386 + | - if (name) { |
1387 + | + NSNumber *number = [self selectedSessionNumber]; |
1388 + | + if (number) { |
1389 + | [delegate_ detach]; |
1390 + | } |
1391 + | } |
1392 + | |
1393 + | #pragma mark NSTableViewDataSource |
1394 + | |
1395 + | -- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView |
1396 + | -{ |
1397 + | - return model_.count; |
1398 + | +- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView { |
1399 + | + return _model.count; |
1400 + | } |
1401 + | |
1402 + | - (id)tableView:(NSTableView *)aTableView |
1403 + | objectValueForTableColumn:(NSTableColumn *)aTableColumn |
1404 + | - row:(NSInteger)rowIndex |
1405 + | -{ |
1406 + | - NSString *name = [model_ objectAtIndex:rowIndex]; |
1407 + | + row:(NSInteger)rowIndex { |
1408 + | + iTermTmuxSessionObject *sessionObject = _model[rowIndex]; |
1409 + | if (aTableColumn == checkColumn_) { |
1410 + | - if ([[delegate_ nameOfAttachedSession] isEqualToString:name]) { |
1411 + | + if ([[delegate_ numberOfAttachedSession] isEqual:@(sessionObject.number)]) { |
1412 + | return @"✓"; |
1413 + | } else { |
1414 + | return @""; |
1415 + | } |
1416 + | } else { |
1417 + | - if (rowIndex < model_.count) { |
1418 + | - return name; |
1419 + | + if (rowIndex < _model.count) { |
1420 + | + return sessionObject.name; |
1421 + | } else { |
1422 + | return nil; |
1423 + | } |
1424 + | |
1425 + | - (void)tableView:(NSTableView *)aTableView |
1426 + | setObjectValue:(id)anObject |
1427 + | forTableColumn:(NSTableColumn *)aTableColumn |
1428 + | - row:(NSInteger)rowIndex |
1429 + | -{ |
1430 + | - [delegate_ renameSessionWithName:[model_ objectAtIndex:rowIndex] |
1431 + | - toName:(NSString *)anObject]; |
1432 + | + row:(NSInteger)rowIndex { |
1433 + | + [delegate_ renameSessionWithNumber:_model[rowIndex].number |
1434 + | + toName:(NSString *)anObject]; |
1435 + | } |
1436 + | |
1437 + | #pragma mark NSTableViewDataSource |
1438 + | |
1439 + | return YES; |
1440 + | } |
1441 + | |
1442 + | -- (void)tableViewSelectionDidChange:(NSNotification *)aNotification |
1443 + | -{ |
1444 + | +- (void)tableViewSelectionDidChange:(NSNotification *)aNotification { |
1445 + | [self updateEnabledStateOfButtons]; |
1446 + | - [delegate_ selectedSessionChangedTo:[self selectedSessionName]]; |
1447 + | + [delegate_ selectedSessionDidChange]; |
1448 + | } |
1449 + | |
1450 + | -- (NSString *)selectedSessionName { |
1451 + | +- (NSNumber *)selectedSessionNumber { |
1452 + | int i = [tableView_ selectedRow]; |
1453 + | - if (i >= 0 && i < model_.count) { |
1454 + | - return [model_ objectAtIndex:i]; |
1455 + | + if (i >= 0 && i < _model.count) { |
1456 + | + return @(_model[i].number); |
1457 + | } else { |
1458 + | return nil; |
1459 + | } |
1460 + | |
1461 + | dropOperation:(NSTableViewDropOperation)operation { |
1462 + | NSPasteboard *pb = [info draggingPasteboard]; |
1463 + | NSArray* pair = [pb propertyListForType:kWindowPasteboardType]; |
1464 + | - NSString *sessionName = [pair objectAtIndex:0]; |
1465 + | - NSArray *draggedItems = [pair objectAtIndex:1]; |
1466 + | - NSString *targetSession = [model_ objectAtIndex:row]; |
1467 + | + NSNumber *sessionNumber = pair[0]; |
1468 + | + NSArray *draggedItems = pair[1]; |
1469 + | + iTermTmuxSessionObject *targetSessionObject = _model[row]; |
1470 + | for (NSArray *tuple in draggedItems) { |
1471 + | NSNumber *windowId = [tuple objectAtIndex:1]; |
1472 + | if (info.draggingSourceOperationMask & NSDragOperationLink) { |
1473 + | [delegate_ linkWindowId:[windowId intValue] |
1474 + | - inSession:sessionName |
1475 + | - toSession:targetSession]; |
1476 + | + inSessionNumber:sessionNumber.intValue |
1477 + | + toSessionNumber:targetSessionObject.number]; |
1478 + | } else { |
1479 + | [delegate_ moveWindowId:[windowId intValue] |
1480 + | - inSession:sessionName |
1481 + | - toSession:targetSession]; |
1482 + | + inSessionNumber:sessionNumber.intValue |
1483 + | + toSessionNumber:targetSessionObject.number]; |
1484 + | } |
1485 + | } |
1486 + | return YES; |
1487 + | |
1488 + | } |
1489 + | } |
1490 + | |
1491 + | -- (NSString *)nameForNewSession |
1492 + | -{ |
1493 + | +- (BOOL)haveSessionWithName:(NSString *)name { |
1494 + | + return [_model anyWithBlock:^BOOL(iTermTmuxSessionObject *anObject) { |
1495 + | + return [anObject.name isEqualToString:name]; |
1496 + | + }]; |
1497 + | +} |
1498 + | + |
1499 + | +- (NSString *)nameForNewSession { |
1500 + | int n = 0; |
1501 + | NSString *candidate = [self nameForNewSessionWithNumber:n]; |
1502 + | - while ([model_ indexOfObject:candidate] != NSNotFound) { |
1503 + | + while ([self haveSessionWithName:candidate]) { |
1504 + | n++; |
1505 + | candidate = [self nameForNewSessionWithNumber:n]; |
1506 + | } |
1507 + | |
1508 + | [detachButton_ setEnabled:NO]; |
1509 + | [removeButton_ setEnabled:NO]; |
1510 + | } else { |
1511 + | - BOOL isAttachedSession = [[delegate_ nameOfAttachedSession] isEqualToString:[self selectedSessionName]]; |
1512 + | + NSNumber *selected = [self selectedSessionNumber]; |
1513 + | + BOOL isAttachedSession = (selected != nil && |
1514 + | + [[delegate_ numberOfAttachedSession] isEqual:@(selected.intValue)]); |
1515 + | [attachButton_ setEnabled:!isAttachedSession]; |
1516 + | [detachButton_ setEnabled:isAttachedSession]; |
1517 + | [removeButton_ setEnabled:YES]; |
1518 + | diff --git sources/TmuxWindowsTable.h sources/TmuxWindowsTable.h |
1519 + | index 7c3e16c..6e9604e 100644 |
1520 + | --- sources/TmuxWindowsTable.h |
1521 + | +++ sources/TmuxWindowsTable.h |
1522 + | |
1523 + | - (BOOL)haveSelectedSession; |
1524 + | - (BOOL)currentSessionSelected; |
1525 + | - (BOOL)haveOpenWindowWithId:(int)windowId; |
1526 + | -- (NSString *)selectedSessionName; |
1527 + | +- (NSNumber *)selectedSessionNumber; |
1528 + | - (void)tmuxWindowsTableDidSelectWindowWithId:(int)windowId; |
1529 + | |
1530 + | @end |
1531 + | diff --git sources/TmuxWindowsTable.m sources/TmuxWindowsTable.m |
1532 + | index 56f4954..8f92993 100644 |
1533 + | --- sources/TmuxWindowsTable.m |
1534 + | +++ sources/TmuxWindowsTable.m |
1535 + | |
1536 + | - (void)tableView:(NSTableView *)aTableView |
1537 + | setObjectValue:(id)anObject |
1538 + | forTableColumn:(NSTableColumn *)aTableColumn |
1539 + | - row:(NSInteger)rowIndex |
1540 + | -{ |
1541 + | - [delegate_ renameWindowWithId:[[[[self filteredModel] objectAtIndex:rowIndex] objectAtIndex:1] intValue] |
1542 + | + row:(NSInteger)rowIndex { |
1543 + | + const int windowID = [[[[self filteredModel] objectAtIndex:rowIndex] objectAtIndex:1] intValue]; |
1544 + | + [delegate_ renameWindowWithId:windowID |
1545 + | toName:anObject]; |
1546 + | } |
1547 + | |
1548 + | |
1549 + | NSArray* selectedItems = [[self filteredModel] objectsAtIndexes:rowIndexes]; |
1550 + | [pboard declareTypes:[NSArray arrayWithObject:kWindowPasteboardType] owner:self]; |
1551 + | [pboard setPropertyList:[NSArray arrayWithObjects: |
1552 + | - [delegate_ selectedSessionName], |
1553 + | + [delegate_ selectedSessionNumber], |
1554 + | selectedItems, |
1555 + | nil] |
1556 + | forType:kWindowPasteboardType]; |
1557 + | |
1558 + | |
1559 + | #pragma mark - Private |
1560 + | |
1561 + | -- (NSArray *)selectedWindowIdsAsStrings |
1562 + | -{ |
1563 + | - NSArray *ids = [self selectedWindowIds]; |
1564 + | +- (NSArray *)selectedWindowIdsAsStrings { |
1565 + | + NSArray *ids = [self selectedWindowIds]; |
1566 + | NSMutableArray *result = [NSMutableArray array]; |
1567 + | - for (NSString *n in ids) { |
1568 + | - [result addObject:n]; |
1569 + | - } |
1570 + | - return result; |
1571 + | + for (NSString *n in ids) { |
1572 + | + [result addObject:n]; |
1573 + | + } |
1574 + | + return result; |
1575 + | } |
1576 + | |
1577 + | - (NSArray *)selectedWindowIds |
1578 + | diff --git sources/iTermInitialDirectory+Tmux.h sources/iTermInitialDirectory+Tmux.h |
1579 + | index a470a56..586ade6 100644 |
1580 + | --- sources/iTermInitialDirectory+Tmux.h |
1581 + | +++ sources/iTermInitialDirectory+Tmux.h |
1582 + | |
1583 + | |
1584 + | @interface iTermInitialDirectory (Tmux) |
1585 + | |
1586 + | -- (void)tmuxNewWindowCommandInSession:(nullable NSString *)session |
1587 + | +- (void)tmuxNewWindowCommandInSessionNumber:(nullable NSNumber *)sessionNumber |
1588 + | recyclingSupported:(BOOL)recyclingSupported |
1589 + | scope:(iTermVariableScope *)scope |
1590 + | completion:(void (^)(NSString *))completion; |
1591 + | diff --git sources/iTermInitialDirectory+Tmux.m sources/iTermInitialDirectory+Tmux.m |
1592 + | index a67bea4..3259eda 100644 |
1593 + | --- sources/iTermInitialDirectory+Tmux.m |
1594 + | +++ sources/iTermInitialDirectory+Tmux.m |
1595 + | |
1596 + | |
1597 + | @implementation iTermInitialDirectory(Tmux) |
1598 + | |
1599 + | -- (void)tmuxNewWindowCommandInSession:(NSString *)session |
1600 + | - recyclingSupported:(BOOL)recyclingSupported |
1601 + | - scope:(iTermVariableScope *)scope |
1602 + | - completion:(void (^)(NSString *))completion { |
1603 + | +- (void)tmuxNewWindowCommandInSessionNumber:(nullable NSNumber *)sessionNumber |
1604 + | + recyclingSupported:(BOOL)recyclingSupported |
1605 + | + scope:(iTermVariableScope *)scope |
1606 + | + completion:(void (^)(NSString *))completion { |
1607 + | NSArray *args = @[ @"new-window", @"-PF '#{window_id}'" ]; |
1608 + | |
1609 + | - if (session) { |
1610 + | - NSString *targetSessionArg = [NSString stringWithFormat:@"\"%@:+\"", [session stringByEscapingQuotes]]; |
1611 + | + if (sessionNumber) { |
1612 + | + NSString *targetSessionArg = [NSString stringWithFormat:@"\"$%d:+\"", sessionNumber.intValue]; |
1613 + | NSArray *insertionArguments = @[ @"-a", |
1614 + | @"-t", |
1615 + | targetSessionArg ]; |
1616 + | |
1617 + | - (void)tmuxNewWindowCommandRecyclingSupported:(BOOL)recyclingSupported |
1618 + | scope:(iTermVariableScope *)scope |
1619 + | completion:(void (^)(NSString *))completion { |
1620 + | - [self tmuxNewWindowCommandInSession:nil |
1621 + | - recyclingSupported:recyclingSupported |
1622 + | - scope:scope |
1623 + | - completion:completion]; |
1624 + | + [self tmuxNewWindowCommandInSessionNumber:nil |
1625 + | + recyclingSupported:recyclingSupported |
1626 + | + scope:scope |
1627 + | + completion:completion]; |
1628 + | } |
1629 + | |
1630 + | - (void)tmuxSplitWindowCommand:(int)wp |
1631 + | diff --git sources/iTermTmuxOptionMonitor.h sources/iTermTmuxOptionMonitor.h |
1632 + | index 147fcd6..8dffe52 100644 |
1633 + | --- sources/iTermTmuxOptionMonitor.h |
1634 + | +++ sources/iTermTmuxOptionMonitor.h |
1635 + | |
1636 + | @property (nullable, nonatomic, strong) iTermVariableScope *scope; |
1637 + | |
1638 + | - (instancetype)init NS_UNAVAILABLE; |
1639 + | + |
1640 + | +// If `fallbackVariableName` is nonnil, the value of the variable named |
1641 + | +// `fallbackVariableName` will be used for tmux 2.8 and earlier. |
1642 + | - (instancetype)initWithGateway:(TmuxGateway *)gateway |
1643 + | scope:(iTermVariableScope *)scope |
1644 + | + fallbackVariableName:(nullable NSString *)fallbackVariableName |
1645 + | format:(NSString *)format |
1646 + | target:(NSString *)tmuxTarget |
1647 + | variableName:(nullable NSString *)variableName |
1648 + | diff --git sources/iTermTmuxOptionMonitor.m sources/iTermTmuxOptionMonitor.m |
1649 + | index 4f72d02..fd6120f 100644 |
1650 + | --- sources/iTermTmuxOptionMonitor.m |
1651 + | +++ sources/iTermTmuxOptionMonitor.m |
1652 + | |
1653 + | #import "DebugLogging.h" |
1654 + | #import "iTermVariables.h" |
1655 + | #import "iTermVariableScope+Session.h" |
1656 + | +#import "NSStringITerm.h" |
1657 + | #import "NSTimer+iTerm.h" |
1658 + | #import "TmuxGateway.h" |
1659 + | |
1660 + | |
1661 + | BOOL _haveOutstandingRequest; |
1662 + | NSString *_target; |
1663 + | NSString *_variableName; |
1664 + | + NSString *_fallbackVariableName; |
1665 + | void (^_block)(NSString *); |
1666 + | } |
1667 + | |
1668 + | - (instancetype)initWithGateway:(TmuxGateway *)gateway |
1669 + | scope:(iTermVariableScope *)scope |
1670 + | + fallbackVariableName:(NSString *)fallbackVariableName |
1671 + | format:(NSString *)format |
1672 + | target:(NSString *)target |
1673 + | variableName:(NSString *)variableName |
1674 + | |
1675 + | _target = [target copy]; |
1676 + | _variableName = [variableName copy]; |
1677 + | _block = [block copy]; |
1678 + | + _fallbackVariableName = [fallbackVariableName copy]; |
1679 + | } |
1680 + | return self; |
1681 + | } |
1682 + | |
1683 + | [self updateOnce]; |
1684 + | } |
1685 + | |
1686 + | +- (NSString *)command { |
1687 + | + return [NSString stringWithFormat:@"display-message -t '%@' -p '%@'", _target, self.escapedFormat]; |
1688 + | +} |
1689 + | + |
1690 + | - (void)updateOnce { |
1691 + | if (_haveOutstandingRequest) { |
1692 + | DLog(@"Not making a request because one is outstanding"); |
1693 + | return; |
1694 + | } |
1695 + | + if (_fallbackVariableName && self.gateway.minimumServerVersion.doubleValue <= 2.9) { |
1696 + | + [self didFetch:[self.scope valueForVariableName:_fallbackVariableName]]; |
1697 + | + return; |
1698 + | + } |
1699 + | _haveOutstandingRequest = YES; |
1700 + | - NSString *command = [NSString stringWithFormat:@"display-message -t '%@' -p '%@'", _target, self.escapedFormat]; |
1701 + | + NSString *command = [self command]; |
1702 + | DLog(@"Request option with command %@", command); |
1703 + | [self.gateway sendCommand:command |
1704 + | responseTarget:self |
1705 + | |
1706 + | } |
1707 + | |
1708 + | - (void)didFetch:(NSString *)value { |
1709 + | - DLog(@"Did fetch %@", value); |
1710 + | + DLog(@"%@ -> %@", self.command, value); |
1711 + | if (!value) { |
1712 + | // Probably the pane went away and we'll be dealloced soon. |
1713 + | return; |
1714 + | diff --git sources/iTermTmuxSessionObject.h sources/iTermTmuxSessionObject.h |
1715 + | new file mode 100644 |
1716 + | index 0000000..6407d5f |
1717 + | --- /dev/null |
1718 + | +++ sources/iTermTmuxSessionObject.h |
1719 + | |
1720 + | +// |
1721 + | +// iTermTmuxSessionObject.h |
1722 + | +// iTerm2SharedARC |
1723 + | +// |
1724 + | +// Created by George Nachman on 9/25/19. |
1725 + | +// |
1726 + | + |
1727 + | +#import <Foundation/Foundation.h> |
1728 + | + |
1729 + | +NS_ASSUME_NONNULL_BEGIN |
1730 + | + |
1731 + | +@interface iTermTmuxSessionObject : NSObject |
1732 + | +@property (nonatomic, copy) NSString *name; |
1733 + | +@property (nonatomic) int number; |
1734 + | +@end |
1735 + | + |
1736 + | +NS_ASSUME_NONNULL_END |
1737 + | diff --git sources/iTermTmuxSessionObject.m sources/iTermTmuxSessionObject.m |
1738 + | new file mode 100644 |
1739 + | index 0000000..5ac9130 |
1740 + | --- /dev/null |
1741 + | +++ sources/iTermTmuxSessionObject.m |
1742 + | |
1743 + | +// |
1744 + | +// iTermTmuxSessionObject.m |
1745 + | +// iTerm2SharedARC |
1746 + | +// |
1747 + | +// Created by George Nachman on 9/25/19. |
1748 + | +// |
1749 + | + |
1750 + | +#import "iTermTmuxSessionObject.h" |
1751 + | + |
1752 + | +@implementation iTermTmuxSessionObject |
1753 + | + |
1754 + | +@end |
1755 + | diff --git sources/iTermTmuxStatusBarMonitor.h sources/iTermTmuxStatusBarMonitor.h |
1756 + | index 8172779..96338ec 100644 |
1757 + | --- sources/iTermTmuxStatusBarMonitor.h |
1758 + | +++ sources/iTermTmuxStatusBarMonitor.h |
1759 + | |
1760 + | |
1761 + | NS_ASSUME_NONNULL_BEGIN |
1762 + | |
1763 + | +// NOTE: Requires tmux 2.9 |
1764 + | @interface iTermTmuxStatusBarMonitor : NSObject |
1765 + | |
1766 + | @property (nonatomic) BOOL active; |
1767 + | diff --git sources/iTermTmuxStatusBarMonitor.m sources/iTermTmuxStatusBarMonitor.m |
1768 + | index 50504da..0d3daf3 100644 |
1769 + | --- sources/iTermTmuxStatusBarMonitor.m |
1770 + | +++ sources/iTermTmuxStatusBarMonitor.m |
1771 + | |
1772 + | |
1773 + | #import "DebugLogging.h" |
1774 + | #import "iTermVariableScope.h" |
1775 + | +#import "NSStringITerm.h" |
1776 + | #import "NSTimer+iTerm.h" |
1777 + | #import "TmuxGateway.h" |
1778 + | #import "RegexKitLite.h" |
1779 + | |
1780 + | |
1781 + | - (void)requestUpdates { |
1782 + | _accelerated = NO; |
1783 + | - [_gateway sendCommand:@"display-message -p \"#{status-left}\"" responseTarget:self responseSelector:@selector(handleStatusLeftResponse:)]; |
1784 + | - [_gateway sendCommand:@"display-message -p \"#{status-right}\"" responseTarget:self responseSelector:@selector(handleStatusRightResponse:)]; |
1785 + | -} |
1786 + | - |
1787 + | -- (NSString *)escapedString:(NSString *)string { |
1788 + | - return [[string stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"] |
1789 + | - stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""]; |
1790 + | + [_gateway sendCommand:@"display-message -p \"#{T:status-left}\"" responseTarget:self responseSelector:@selector(handleStatusLeftValueExpansionResponse:)]; |
1791 + | + [_gateway sendCommand:@"display-message -p \"#{T:status-right}\"" responseTarget:self responseSelector:@selector(handleStatusRightValueExpansionResponse:)]; |
1792 + | } |
1793 + | |
1794 + | - (void)handleStatusIntervalResponse:(NSString *)response { |
1795 + | |
1796 + | _timer = [NSTimer scheduledWeakTimerWithTimeInterval:interval target:self selector:@selector(timerDidFire:) userInfo:nil repeats:YES]; |
1797 + | } |
1798 + | |
1799 + | -- (void)handleStatusLeftResponse:(NSString *)response { |
1800 + | - if (!response) { |
1801 + | - return; |
1802 + | - } |
1803 + | - NSString *command = [NSString stringWithFormat:@"display-message -p \"%@\"", [self escapedString:response]]; |
1804 + | - [_gateway sendCommand:command responseTarget:self responseSelector:@selector(handleStatusLeftValueExpansionResponse:)]; |
1805 + | -} |
1806 + | - |
1807 + | -- (void)handleStatusRightResponse:(NSString *)response { |
1808 + | - if (!response) { |
1809 + | - return; |
1810 + | - } |
1811 + | - NSString *command = [NSString stringWithFormat:@"display-message -p \"%@\"", [self escapedString:response]]; |
1812 + | - [_gateway sendCommand:command responseTarget:self responseSelector:@selector(handleStatusRightValueExpansionResponse:)]; |
1813 + | -} |
1814 + | - |
1815 + | - (void)handleStatusLeftValueExpansionResponse:(NSString *)string { |
1816 + | + DLog(@"Left status bar is: %@", string); |
1817 + | [self.scope setValue:[self sanitizedString:string] ?: @"" forVariableNamed:iTermVariableKeySessionTmuxStatusLeft]; |
1818 + | [self accelerateUpdateIfStringContainsNotReady:string]; |
1819 + | } |
1820 + | |
1821 + | - (void)handleStatusRightValueExpansionResponse:(NSString *)string { |
1822 + | + DLog(@"Right status bar is: %@", string); |
1823 + | [self.scope setValue:[self sanitizedString:string] ?: @"" forVariableNamed:iTermVariableKeySessionTmuxStatusRight]; |
1824 + | [self accelerateUpdateIfStringContainsNotReady:string]; |
1825 + | } |
1826 + | diff --git sources/iTermWorkingDirectoryPoller.m sources/iTermWorkingDirectoryPoller.m |
1827 + | index 5271227..f4d8602 100644 |
1828 + | --- sources/iTermWorkingDirectoryPoller.m |
1829 + | +++ sources/iTermWorkingDirectoryPoller.m |
1830 + | |
1831 + | __weak __typeof(self) weakSelf = self; |
1832 + | _tmuxOptionMonitor = [[iTermTmuxOptionMonitor alloc] initWithGateway:gateway |
1833 + | scope:scope |
1834 + | + fallbackVariableName:nil |
1835 + | format:@"#{pane_current_path}" |
1836 + | target:[NSString stringWithFormat:@"%%%@", @(windowPane)] |
1837 + | variableName:nil |
1838 + | -- |
1839 + | 2.23.0 |
1840 + | |