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

Rewrite the code for finding the current foreground job. It now builds a tree...

Rewrite the code for finding the current foreground job. It now builds a tree of processes and finds the foreground job with the deepest path from the root. It is possible for a foreground job to have a child foreground job. Issue 5730.
parent 50f949ab
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -1242,6 +1242,10 @@
A60BB3891EB54BD700D76C09 /* CopyMode@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = A60BB3831EB54B6700D76C09 /* CopyMode@2x.png */; };
A60BB38A1EB54BD800D76C09 /* CopyMode@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = A60BB3831EB54B6700D76C09 /* CopyMode@2x.png */; };
A60BB38B1EB54BD900D76C09 /* CopyMode@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = A60BB3831EB54B6700D76C09 /* CopyMode@2x.png */; };
A60BB38E1EB6A08A00D76C09 /* iTermProcessCollection.h in Headers */ = {isa = PBXBuildFile; fileRef = A60BB38C1EB6A08A00D76C09 /* iTermProcessCollection.h */; };
A60BB38F1EB6A08A00D76C09 /* iTermProcessCollection.m in Sources */ = {isa = PBXBuildFile; fileRef = A60BB38D1EB6A08A00D76C09 /* iTermProcessCollection.m */; };
A60BB3911EB6A56800D76C09 /* iTermProcessCollectionTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A60BB3901EB6A56800D76C09 /* iTermProcessCollectionTest.m */; };
A60BB3921EB6A56800D76C09 /* iTermProcessCollectionTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A60BB3901EB6A56800D76C09 /* iTermProcessCollectionTest.m */; };
A60BD9131B3913F6007D7F11 /* iTermTextViewAccessibilityHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = A60BD9111B3913F6007D7F11 /* iTermTextViewAccessibilityHelper.h */; };
A60BD9141B3913F6007D7F11 /* iTermTextViewAccessibilityHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = A60BD9111B3913F6007D7F11 /* iTermTextViewAccessibilityHelper.h */; };
A60BD9191B3F5D76007D7F11 /* OpenDirectory.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A60BD9181B3F5D76007D7F11 /* OpenDirectory.framework */; };
Loading
Loading
@@ -3210,6 +3214,9 @@
A60BB37D1EB5149100D76C09 /* iTermCopyModeState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = iTermCopyModeState.m; sourceTree = "<group>"; };
A60BB3821EB54B6700D76C09 /* CopyMode.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = CopyMode.png; path = images/CopyMode.png; sourceTree = "<group>"; };
A60BB3831EB54B6700D76C09 /* CopyMode@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "CopyMode@2x.png"; path = "images/CopyMode@2x.png"; sourceTree = "<group>"; };
A60BB38C1EB6A08A00D76C09 /* iTermProcessCollection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = iTermProcessCollection.h; sourceTree = "<group>"; };
A60BB38D1EB6A08A00D76C09 /* iTermProcessCollection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = iTermProcessCollection.m; sourceTree = "<group>"; };
A60BB3901EB6A56800D76C09 /* iTermProcessCollectionTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = iTermProcessCollectionTest.m; sourceTree = "<group>"; };
A60BD9111B3913F6007D7F11 /* iTermTextViewAccessibilityHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = iTermTextViewAccessibilityHelper.h; sourceTree = "<group>"; };
A60BD9121B3913F6007D7F11 /* iTermTextViewAccessibilityHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.objc; path = iTermTextViewAccessibilityHelper.m; sourceTree = "<group>"; };
A60BD9181B3F5D76007D7F11 /* OpenDirectory.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenDirectory.framework; path = System/Library/Frameworks/OpenDirectory.framework; sourceTree = SDKROOT; };
Loading
Loading
@@ -5695,6 +5702,8 @@
A668E8A11E7EFEA5005F8758 /* iTermURLStore.m */,
A60BB37C1EB5149100D76C09 /* iTermCopyModeState.h */,
A60BB37D1EB5149100D76C09 /* iTermCopyModeState.m */,
A60BB38C1EB6A08A00D76C09 /* iTermProcessCollection.h */,
A60BB38D1EB6A08A00D76C09 /* iTermProcessCollection.m */,
);
name = Helpers;
sourceTree = "<group>";
Loading
Loading
@@ -5790,6 +5799,7 @@
A6C7641D1B45CB2800E3C992 /* iTerm2XCTests */ = {
isa = PBXGroup;
children = (
A60BB3901EB6A56800D76C09 /* iTermProcessCollectionTest.m */,
C6675EB91C4FE95E0041173B /* Utilities */,
A6D22B431BC9D368004084E0 /* iTermShellHistoryTest.m */,
A6BDB0401B45E8BA00F511E6 /* iTermEquivalenceClassSetTest.m */,
Loading
Loading
@@ -6843,6 +6853,7 @@
A66719411DCE36C3000CE608 /* iTermSystemVersion.h in Headers */,
A66719421DCE36C3000CE608 /* iTermTip.h in Headers */,
A66719431DCE36C3000CE608 /* iTermScriptingWindow.h in Headers */,
A60BB38E1EB6A08A00D76C09 /* iTermProcessCollection.h in Headers */,
A668E8A21E7EFEA5005F8758 /* iTermURLStore.h in Headers */,
A66719441DCE36C3000CE608 /* iTermHotKeyProfileBindingController.h in Headers */,
A66719451DCE36C3000CE608 /* iTermSavePanel.h in Headers */,
Loading
Loading
@@ -8331,6 +8342,7 @@
90A1E13B186F9EA4003EC3E8 /* AppleScriptTest.m in Sources */,
A6C763FE1B45C72F00E3C992 /* iTermTests.m in Sources */,
A6D22A441BC8BE6B004084E0 /* Model.xcdatamodeld in Sources */,
A60BB3911EB6A56800D76C09 /* iTermProcessCollectionTest.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Loading
Loading
@@ -8368,6 +8380,7 @@
A62A1F721E6E724B00363EE9 /* iTermMenuOpener.m in Sources */,
A66719661DCE3772000CE608 /* iTermWebSocketFrame.m in Sources */,
A66719601DCE3772000CE608 /* iTermAPIServer.m in Sources */,
A60BB38F1EB6A08A00D76C09 /* iTermProcessCollection.m in Sources */,
A66719631DCE3772000CE608 /* iTermSocketIPV4Address.m in Sources */,
A66719611DCE3772000CE608 /* iTermIPV4Address.m in Sources */,
);
Loading
Loading
@@ -8817,6 +8830,7 @@
A61CEAA71C72EA4C00939E97 /* iTermWeakReferenceTest.m in Sources */,
A6BDB0421B45E8BA00F511E6 /* iTermEquivalenceClassSetTest.m in Sources */,
A6BDB04E1B45EC8A00F511E6 /* PTYSessionTest.m in Sources */,
A60BB3921EB6A56800D76C09 /* iTermProcessCollectionTest.m in Sources */,
A6BDB04C1B45EC3A00F511E6 /* iTermNSStringCategoryTest.m in Sources */,
A6BDB0501B45FBCB00F511E6 /* PTYTextViewTest.m in Sources */,
A667F3871B48615900705186 /* iTermToolbeltTest.m in Sources */,
//
// iTermProcessCollectionTest.m
// iTerm2
//
// Created by George Nachman on 4/30/17.
//
//
#import <XCTest/XCTest.h>
#import "iTermProcessCollection.h"
@interface iTermProcessCollectionTest : XCTestCase
@end
@implementation iTermProcessCollectionTest
- (void)testBasic {
iTermProcessCollection *collection = [[iTermProcessCollection alloc] init];
// ? -> a -> b+
[collection addProcessWithName:@"a" processID:2 parentProcessID:1 isForegroundJob:NO];
[collection addProcessWithName:@"b" processID:3 parentProcessID:2 isForegroundJob:YES];
// ? -> x -> y+
[collection addProcessWithName:@"x" processID:10 parentProcessID:9 isForegroundJob:NO];
[collection addProcessWithName:@"y" processID:11 parentProcessID:10 isForegroundJob:YES];
[collection commit];
int actual;
actual = [[[collection infoForProcessID:2] deepestForegroundJob] processID];
XCTAssertEqual(actual, 3);
actual = [[[collection infoForProcessID:11] deepestForegroundJob] processID];
XCTAssertEqual(actual, 11);
}
- (void)testMultipleChildren {
// ? -> a -> b
// -> c -> d+
// -> e -> f -> g+
iTermProcessCollection *collection = [[iTermProcessCollection alloc] init];
const int a=1, b=2, c=3, d=4, e=5, f=6, g=8;
[collection addProcessWithName:@"a" processID:a parentProcessID:0 isForegroundJob:NO];
[collection addProcessWithName:@"b" processID:b parentProcessID:a isForegroundJob:NO];
[collection addProcessWithName:@"c" processID:c parentProcessID:a isForegroundJob:NO];
[collection addProcessWithName:@"d" processID:d parentProcessID:c isForegroundJob:YES];
[collection addProcessWithName:@"e" processID:e parentProcessID:a isForegroundJob:NO];
[collection addProcessWithName:@"f" processID:f parentProcessID:e isForegroundJob:NO];
[collection addProcessWithName:@"g" processID:g parentProcessID:f isForegroundJob:YES];
[collection commit];
int actual;
actual = [[[collection infoForProcessID:a] deepestForegroundJob] processID];
XCTAssertEqual(actual, g);
actual = [[[collection infoForProcessID:b] deepestForegroundJob] processID];
XCTAssertEqual(actual, 0);
actual = [[[collection infoForProcessID:c] deepestForegroundJob] processID];
XCTAssertEqual(actual, d);
actual = [[[collection infoForProcessID:d] deepestForegroundJob] processID];
XCTAssertEqual(actual, d);
actual = [[[collection infoForProcessID:e] deepestForegroundJob] processID];
XCTAssertEqual(actual, g);
actual = [[[collection infoForProcessID:f] deepestForegroundJob] processID];
XCTAssertEqual(actual, g);
actual = [[[collection infoForProcessID:g] deepestForegroundJob] processID];
XCTAssertEqual(actual, g);
}
- (void)testNoForegroundJob {
iTermProcessCollection *collection = [[iTermProcessCollection alloc] init];
const int a=1, b=2, c=3, d=4, e=5, f=6, g=8;
[collection addProcessWithName:@"a" processID:a parentProcessID:0 isForegroundJob:NO];
[collection addProcessWithName:@"b" processID:b parentProcessID:a isForegroundJob:NO];
[collection addProcessWithName:@"c" processID:c parentProcessID:a isForegroundJob:NO];
[collection addProcessWithName:@"d" processID:d parentProcessID:c isForegroundJob:NO];
[collection addProcessWithName:@"e" processID:e parentProcessID:a isForegroundJob:NO];
[collection addProcessWithName:@"f" processID:f parentProcessID:e isForegroundJob:NO];
[collection addProcessWithName:@"g" processID:g parentProcessID:f isForegroundJob:NO];
[collection commit];
for (int i = a; i <= g; i++) {
id actual = [[collection infoForProcessID:i] deepestForegroundJob];
XCTAssertNil(actual);
}
}
- (void)testCycle {
iTermProcessCollection *collection = [[iTermProcessCollection alloc] init];
// +-> a -> b
// | -> c -> d+
// | -> e -> f -> g+ -+
// | |
// +----------------------+
const int a=1, b=2, c=3, d=4, e=5, f=6, g=8;
[collection addProcessWithName:@"a" processID:a parentProcessID:g isForegroundJob:NO];
[collection addProcessWithName:@"b" processID:b parentProcessID:a isForegroundJob:NO];
[collection addProcessWithName:@"c" processID:c parentProcessID:a isForegroundJob:NO];
[collection addProcessWithName:@"d" processID:d parentProcessID:c isForegroundJob:YES];
[collection addProcessWithName:@"e" processID:e parentProcessID:a isForegroundJob:NO];
[collection addProcessWithName:@"f" processID:f parentProcessID:e isForegroundJob:NO];
[collection addProcessWithName:@"g" processID:g parentProcessID:f isForegroundJob:YES];
[collection commit];
iTermProcessInfo *actual;
actual = [[collection infoForProcessID:a] deepestForegroundJob];
XCTAssertNil(actual);
actual = [[collection infoForProcessID:b] deepestForegroundJob];
XCTAssertNil(actual);
actual = [[collection infoForProcessID:c] deepestForegroundJob];
XCTAssertEqual(actual.processID, d);
actual = [[collection infoForProcessID:d] deepestForegroundJob];
XCTAssertEqual(actual.processID, d);
actual = [[collection infoForProcessID:e] deepestForegroundJob];
XCTAssertNil(actual);
actual = [[collection infoForProcessID:f] deepestForegroundJob];
XCTAssertNil(actual);
actual = [[collection infoForProcessID:g] deepestForegroundJob];
XCTAssertNil(actual);
}
- (void)testMultipleForegroundJobs {
// a -> b+ -> c+
const int a = 1, b = 2, c = 3;
iTermProcessCollection *collection = [[iTermProcessCollection alloc] init];
[collection addProcessWithName:@"a" processID:a parentProcessID:0 isForegroundJob:NO];
[collection addProcessWithName:@"b" processID:b parentProcessID:a isForegroundJob:YES];
[collection addProcessWithName:@"c" processID:c parentProcessID:b isForegroundJob:YES];
[collection commit];
int actual;
actual = [[[collection infoForProcessID:a] deepestForegroundJob] processID];
XCTAssertEqual(actual, c);
}
@end
Loading
Loading
@@ -66,6 +66,7 @@
 
#import "ProcessCache.h"
#import "iTerm.h"
#import "iTermProcessCollection.h"
#include <libproc.h>
#include <sys/sysctl.h>
 
Loading
Loading
@@ -75,7 +76,7 @@ NSString *PID_INFO_IS_FOREGROUND = @"foreground";
NSString *PID_INFO_NAME = @"name";
 
@implementation ProcessCache {
NSMutableDictionary* pidInfoCache_; // guraded by _cacheLock
NSDictionary* pidInfoCache_; // guarded by _cacheLock
NSLock *_cacheLock;
 
BOOL newOutput_;
Loading
Loading
@@ -95,7 +96,7 @@ NSString *PID_INFO_NAME = @"name";
- (instancetype)init {
self = [super init];
if (self) {
pidInfoCache_ = [[NSMutableDictionary alloc] init];
pidInfoCache_ = [[NSDictionary alloc] init];
_lock = [[NSLock alloc] init];
_cacheLock = [[NSLock alloc] init];
}
Loading
Loading
@@ -250,74 +251,39 @@ NSString *PID_INFO_NAME = @"name";
return closure;
}
 
// Constructs a map of pid -> name of tty controller where pid is the tty
// controller or any ancestor of the tty controller.
- (void)_refreshProcessCache:(NSMutableDictionary*)cache
{
[cache removeAllObjects];
// Add a mapping to 'temp' of pid->job name for all foreground jobs.
// Add a mapping to 'ancestry' of of pid->ppid for all pid's.
- (NSDictionary<NSNumber *, NSString *> *)pidToForegroundJobName {
NSArray *allPids = [ProcessCache allPids];
int numPids = [allPids count];
NSMutableDictionary* temp = [NSMutableDictionary dictionaryWithCapacity:numPids];
NSMutableDictionary* ancestry = [NSMutableDictionary dictionaryWithCapacity:numPids];
for (NSNumber *n in allPids) {
pid_t thePid = [n intValue];
iTermProcessCollection *collection = [[[iTermProcessCollection alloc] init] autorelease];
for (NSNumber *pidNumber in allPids) {
pid_t pid = pidNumber.intValue;
 
pid_t ppid = [ProcessCache ppidForPid:thePid];
pid_t ppid = [ProcessCache ppidForPid:pid];
if (!ppid) {
continue;
}
 
BOOL isForeground;
NSString* name = [self getNameOfPid:thePid isForeground:&isForeground];
NSString* name = [self getNameOfPid:pid isForeground:&isForeground];
if (name) {
if (isForeground) {
[temp setObject:name forKey:[NSNumber numberWithInt:thePid]];
}
[ancestry setObject:[NSNumber numberWithInt:ppid] forKey:n];
[collection addProcessWithName:name processID:pid parentProcessID:ppid isForegroundJob:isForeground];
}
}
 
// For each pid in 'temp', follow the parent pid chain in 'ancestry' and add a map of
// ancestorPid->job name to 'cache' for all ancestors of the job with that name.
for (NSNumber* tempPid in temp) {
NSString* value = [temp objectForKey:tempPid];
[cache setObject:value forKey:tempPid];
NSNumber* parent = [ancestry objectForKey:tempPid];
NSNumber* cycleFinder = parent;
while (parent != nil) {
[cache setObject:value forKey:parent];
// cycleFinder moves through the chain of ancestry at twice the
// rate of parent. If it ever catches up to parent then there is a cycle.
// A cycle can occur because there's a race in getting each process's
// ppid. See bug 771 for details.
if (cycleFinder) {
cycleFinder = [ancestry objectForKey:cycleFinder];
if (cycleFinder && [cycleFinder isEqualToNumber:parent]) {
break;
}
}
if (cycleFinder) {
cycleFinder = [ancestry objectForKey:cycleFinder];
if (cycleFinder && [cycleFinder isEqualToNumber:parent]) {
break;
}
}
parent = [ancestry objectForKey:parent];
[collection commit];
NSMutableDictionary *pidToForegroundJobName = [NSMutableDictionary dictionary];
for (NSNumber *pidNumber in allPids) {
NSString *name = [[[collection infoForProcessID:pidNumber.intValue] deepestForegroundJob] name];
if (name != nil) {
pidToForegroundJobName[pidNumber] = name;
}
}
return pidToForegroundJobName;
}
 
- (void)_update {
// Calculate a new ancestorPid->jobName dict.
NSMutableDictionary* temp = [NSMutableDictionary dictionaryWithCapacity:100];
[self _refreshProcessCache:temp];
NSDictionary* temp = [self pidToForegroundJobName];
 
// Quickly swap the pointer to minimize lock time, and then free the old cache.
[_cacheLock lock];
Loading
Loading
@@ -363,10 +329,10 @@ NSString *PID_INFO_NAME = @"name";
 
- (NSString*)jobNameWithPid:(int)pid {
[_cacheLock lock];
NSString *jobName = [[[pidInfoCache_ objectForKey:@(pid)] retain] autorelease];
NSString *jobName = [pidInfoCache_[@(pid)] retain];
[_cacheLock unlock];
 
return jobName;
return [jobName autorelease];
}
 
 
Loading
Loading
//
// iTermProcessCollection.h
// iTerm2
//
// Created by George Nachman on 4/30/17.
//
//
#import <Foundation/Foundation.h>
@interface iTermProcessInfo : NSObject
@property(nonatomic, retain) NSString *name;
@property(nonatomic, assign) pid_t processID;
@property(nonatomic, assign) pid_t parentProcessID;
@property(nonatomic, readonly) NSMutableArray<iTermProcessInfo *> *children;
@property(nonatomic, weak) iTermProcessInfo *parent;
@property(nonatomic, assign) BOOL isForegroundJob;
@property(nonatomic, weak, readonly) iTermProcessInfo *deepestForegroundJob;
@end
@interface iTermProcessCollection : NSObject
@property (nonatomic, readonly) NSString *treeString;
- (void)addProcessWithName:(NSString *)name
processID:(pid_t)processID
parentProcessID:(pid_t)parentProcessID
isForegroundJob:(BOOL)isForegroundJob;
- (void)commit;
- (iTermProcessInfo *)infoForProcessID:(pid_t)processID;
@end
//
// iTermProcessCollection.m
// iTerm2
//
// Created by George Nachman on 4/30/17.
//
//
#import "iTermProcessCollection.h"
#import "NSArray+iTerm.h"
@implementation iTermProcessInfo {
NSMutableArray *_children;
__weak iTermProcessInfo *_deepestForegroundJob;
BOOL _haveDeepestForegroundJob;
}
- (NSString *)treeStringWithIndent:(NSString *)indent {
NSString *children = [[_children mapWithBlock:^id(id anObject) {
return [anObject treeStringWithIndent:[indent stringByAppendingString:@" "]];
}] componentsJoinedByString:@"\n"];
if (_children.count > 0) {
children = [@"\n" stringByAppendingString:children];
}
return [NSString stringWithFormat:@"%@pid=%@ name=%@ fg=%@%@", indent, @(self.processID), self.name, @(self.isForegroundJob), children];
}
- (NSMutableArray<iTermProcessInfo *> *)children {
if (!_children) {
_children = [NSMutableArray array];
}
return _children;
}
- (iTermProcessInfo *)deepestForegroundJob {
if (!_haveDeepestForegroundJob) {
NSInteger level = 0;
NSMutableSet<NSNumber *> *visitedPids = [NSMutableSet set];
BOOL cycle = NO;
return [self deepestForegroundJob:&level visited:visitedPids cycle:&cycle];
}
return _deepestForegroundJob;
}
- (iTermProcessInfo *)deepestForegroundJob:(NSInteger *)levelInOut visited:(NSMutableSet *)visited cycle:(BOOL *)cycle {
if ([visited containsObject:@(self.processID)]) {
_haveDeepestForegroundJob = YES;
_deepestForegroundJob = nil;
*cycle = YES;
return nil;
} else {
[visited addObject:@(self.processID)];
}
if (_children.count == 0 && _isForegroundJob) {
_haveDeepestForegroundJob = YES;
_deepestForegroundJob = self;
return self;
}
NSInteger bestLevel = *levelInOut;
iTermProcessInfo *bestProcessInfo = nil;
for (iTermProcessInfo *child in _children) {
NSInteger level = *levelInOut + 1;
iTermProcessInfo *candidate = [child deepestForegroundJob:&level visited:visited cycle:cycle];
if (*cycle) {
_haveDeepestForegroundJob = YES;
_deepestForegroundJob = nil;
return nil;
}
if (level > bestLevel || bestProcessInfo == nil) {
bestLevel = level;
bestProcessInfo = candidate;
}
}
_haveDeepestForegroundJob = YES;
_deepestForegroundJob = bestProcessInfo;
*levelInOut = bestLevel;
return bestProcessInfo;
}
@end
@implementation iTermProcessCollection {
NSMutableDictionary<NSNumber *, iTermProcessInfo *> *_processes;
}
- (instancetype)init {
self = [super init];
if (self) {
_processes = [[NSMutableDictionary alloc] init];
}
return self;
}
- (NSString *)treeString {
return [[_processes.allValues mapWithBlock:^id(iTermProcessInfo *anObject) {
return [anObject treeStringWithIndent:@""];
}] componentsJoinedByString:@"\n"];
}
- (void)addProcessWithName:(NSString *)name
processID:(pid_t)processID
parentProcessID:(pid_t)parentProcessID
isForegroundJob:(BOOL)isForegroundJob {
iTermProcessInfo *info = [[iTermProcessInfo alloc] init];
info.name = name;
info.processID = processID;
info.parentProcessID = parentProcessID;
info.isForegroundJob = isForegroundJob;
_processes[@(processID)] = info;
}
- (void)commit {
[_processes enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull processID, iTermProcessInfo * _Nonnull info, BOOL * _Nonnull stop) {
info.parent = _processes[@(info.parentProcessID)];
[info.parent.children addObject:info];
}];
}
- (iTermProcessInfo *)infoForProcessID:(pid_t)processID {
return _processes[@(processID)];
}
@end
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