Skip to content

Commit

Permalink
Added new Search Filter menu with case sensitivity and regex options
Browse files Browse the repository at this point in the history
  • Loading branch information
sveinbjornt committed Mar 28, 2018
1 parent 4962b9e commit 0047f3e
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 45 deletions.
9 changes: 5 additions & 4 deletions TODO.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
TODO list for Sloth 2.3

DONE * Now supports access mode filtering (e.g. read, write, read/write)
DONE * Search filter now also filters by PID
DONE * DNS and port name lookup for IP Sockets in Info Window
DONE * Now identifies standard I/O stream character devices
DONE * Search string filter now also filters by PID
DONE * DNS and port name lookup for IP Sockets in Info Panel
DONE * Info Panel now identifies standard I/O stream character devices
DONE * Fixed bug where Volumes filter wouldn't work
DONE * Minor interface improvements
DONE * Fixed bug where Volumes filter didn't work
DONE * New Search Filter menu with case sensitivity and regex options
* New application icon
* New screenshots

41 changes: 36 additions & 5 deletions resources/MainMenu.xib
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ CA
<connections>
<outlet property="delegate" destination="212" id="796"/>
</connections>
<point key="canvasLocation" x="621" y="-459"/>
<point key="canvasLocation" x="463" y="-545"/>
</menu>
<customObject id="212" userLabel="SlothController" customClass="SlothController">
<connections>
Expand Down Expand Up @@ -489,7 +489,7 @@ CA
</treeController>
<userDefaultsController representsSharedInstance="YES" id="560" userLabel="Shared User Defaults Controller"/>
<customObject id="Nds-SX-rlr" customClass="SUUpdater"/>
<menu id="3w8-wK-AmU" userLabel="Contextual Menu">
<menu id="3w8-wK-AmU" userLabel="Item Contextual Menu">
<items>
<menuItem title="Copy" id="aIV-bf-ZRN" userLabel="Copy">
<modifierMask key="keyEquivalentModifierMask"/>
Expand Down Expand Up @@ -631,17 +631,18 @@ DQ
<binding destination="560" name="value" keyPath="values.showApplicationsOnly" id="kNZ-Wh-Rrx"/>
</connections>
</button>
<searchField toolTip="Search filter (with full support for regular expressions)" wantsLayer="YES" verticalHuggingPriority="750" id="252">
<searchField toolTip="Search Filter (Supports Regular Expressions)" wantsLayer="YES" verticalHuggingPriority="750" id="252">
<rect key="frame" x="721" y="565" width="151" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
<searchFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" alignment="left" placeholderString="Search Filter" bezelStyle="round" id="735">
<searchFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" alignment="left" placeholderString="Search Filter" usesSingleLineMode="YES" bezelStyle="round" id="735">
<font key="font" metaFont="cellTitle"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</searchFieldCell>
<connections>
<binding destination="560" name="value" keyPath="values.filterString" id="ydo-dA-IIK"/>
<outlet property="delegate" destination="212" id="552"/>
<outlet property="searchMenuTemplate" destination="6L5-jB-614" id="7x1-pU-Ybs"/>
</connections>
</searchField>
<button toolTip="Kill selected process" verticalHuggingPriority="750" misplaced="YES" id="210">
Expand Down Expand Up @@ -904,7 +905,7 @@ DQ
<outlet property="delegate" destination="212" id="3KT-G0-6pk"/>
<outlet property="initialFirstResponder" destination="252" id="414"/>
</connections>
<point key="canvasLocation" x="809" y="170.5"/>
<point key="canvasLocation" x="333" y="161"/>
</window>
<window title="Sloth Preferences" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" tabbingMode="disallowed" id="OsR-k1-sul">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
Expand Down Expand Up @@ -976,6 +977,36 @@ DQ
</view>
<point key="canvasLocation" x="401" y="-327"/>
</window>
<menu title="Search Filter Contextual Menu" autoenablesItems="NO" id="6L5-jB-614" userLabel="Search Filter Menu">
<items>
<menuItem title="Case Sensitive" id="ghU-WC-RvG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="searchFieldOptionChanged:" target="212" id="oZz-yp-5XX"/>
<binding destination="560" name="value" keyPath="values.searchFilterCaseSensitive" id="35l-BQ-g2d">
<dictionary key="options">
<bool key="NSConditionallySetsEnabled" value="NO"/>
</dictionary>
</binding>
</connections>
</menuItem>
<menuItem title="Regular Expressions" tag="1" id="bYP-KH-Im4">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="searchFieldOptionChanged:" target="212" id="6HU-41-l2N"/>
<binding destination="560" name="value" keyPath="values.searchFilterSupportsRegex" id="qXX-FC-Qka">
<dictionary key="options">
<bool key="NSConditionallySetsEnabled" value="NO"/>
</dictionary>
</binding>
</connections>
</menuItem>
</items>
<connections>
<outlet property="delegate" destination="212" id="FZb-Aa-Ayc"/>
</connections>
<point key="canvasLocation" x="478" y="-270"/>
</menu>
</objects>
<resources>
<image name="NSActionTemplate" width="14" height="14"/>
Expand Down
4 changes: 4 additions & 0 deletions resources/RegistrationDefaults.plist
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
<false/>
<key>showCurrentWorkingDirectories</key>
<false/>
<key>searchFilterCaseSensitive</key>
<false/>
<key>searchFilterRegex</key>
<true/>
<key>accessMode</key>
<string>Any</string>
</dict>
Expand Down
4 changes: 2 additions & 2 deletions source/Common.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@
#define GENERIC_NETWORK_ICON_PATH \
@"/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericNetworkIcon.icns"

#define APPLICATION_UTI @"com.apple.application"
#define APPLICATION_SUFFIX @".app"
#define APP_BUNDLE_UTI @"com.apple.application"
#define APP_BUNDLE_SUFFIX @".app"

#define VALUES_KEYPATH(X) [NSString stringWithFormat:@"values.%@", (X)]

Expand Down
113 changes: 79 additions & 34 deletions source/SlothController.m
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,9 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
@"showApplicationsOnly",
@"showHomeFolderOnly",
@"accessMode",
@"interfaceSize"]) {
@"interfaceSize",
@"searchFilterCaseSensitive",
@"searchFilterRegex"]) {
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self
forKeyPath:VALUES_KEYPATH(key)
options:NSKeyValueObservingOptionNew
Expand Down Expand Up @@ -291,10 +293,10 @@ - (void)updateFiltering {
int matchingFilesCount = 0;
self.content = [self filterContent:self.unfilteredContent numberOfMatchingFiles:&matchingFilesCount];

// Update header
// Update outline view header
[self updateProcessCountHeader];

// Update label
// Update num items label
NSString *str = [NSString stringWithFormat:@"Showing %d out of %d items", matchingFilesCount, self.totalFileCount];
[numItemsTextField setStringValue:str];

Expand Down Expand Up @@ -339,6 +341,9 @@ - (NSMutableArray *)filterContent:(NSMutableArray *)unfilteredContent numberOfMa
BOOL showApplicationsOnly = [DEFAULTS boolForKey:@"showApplicationsOnly"];
BOOL showHomeFolderOnly = [DEFAULTS boolForKey:@"showHomeFolderOnly"];

BOOL searchCaseSensitive = [DEFAULTS boolForKey:@"searchFilterCaseSensitive"];
BOOL searchUsesRegex = [DEFAULTS boolForKey:@"searchFilterRegex"];

// Access mode filter
NSString *accessModeFilter = [DEFAULTS stringForKey:@"accessMode"];
BOOL hasAccessModeFilter = ([accessModeFilter isEqualToString:@"Any"] == NO);
Expand All @@ -353,44 +358,55 @@ - (NSMutableArray *)filterContent:(NSMutableArray *)unfilteredContent numberOfMa
// User home dir path prefix
NSString *homeDirPath = NSHomeDirectory();

// Regex search field filter
NSMutableArray *regexes = [NSMutableArray array];
// Search field filter
NSMutableArray *searchFilters = [NSMutableArray array];
NSString *fieldString = [[filterTextField stringValue] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
NSArray *filterStrings = [fieldString componentsSeparatedByString:@" "];

// Create regex for each whitespace-separated search filter strings

for (NSString *fs in filterStrings) {
NSString *s = [fs stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if ([s length] == 0) {
continue;
}

NSError *err;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:s
options:NSRegularExpressionCaseInsensitive
error:&err];
if (!regex) {
NSLog(@"Error creating regex: %@", [err localizedDescription]);
continue;

if (searchUsesRegex) {
NSError *err;
NSRegularExpressionOptions options = searchCaseSensitive ? 0 : NSRegularExpressionCaseInsensitive;

NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:s
options:options
error:&err];
if (!regex) {
NSLog(@"Error creating regex: %@", [err localizedDescription]);
continue;
}
[searchFilters addObject:regex];
} else {
[searchFilters addObject:s];
}
[regexes addObject:regex];
}

BOOL hasRegexFilter = ([regexes count] > 0);
BOOL hasSearchFilter = ([searchFilters count] > 0);
BOOL showAllProcessTypes = !showApplicationsOnly;
BOOL showAllFileTypes = (showRegularFiles && showDirectories && showIPSockets && showUnixSockets
&& showCharDevices && showPipes && !showHomeFolderOnly && !hasVolumesFilter);

// Minor optimization: If there is no filter, just return
BOOL showAllFileTypes = (showRegularFiles &&
showDirectories &&
showIPSockets &&
showUnixSockets &&
showCharDevices &&
showPipes &&
!showHomeFolderOnly &&
!hasVolumesFilter);

// Minor optimization: If there is no filtering, just return
// unfiltered content instead of iterating over all items
if (showAllFileTypes && showAllProcessTypes && !hasRegexFilter && !hasAccessModeFilter) {
if (showAllFileTypes && showAllProcessTypes && !hasSearchFilter && !hasAccessModeFilter) {
*matchingFilesCount = self.totalFileCount;
return unfilteredContent;
}

NSMutableArray *filteredContent = [NSMutableArray array];

// Iterate over each process, filter children
// Iterate over each process, filter the children
for (NSMutableDictionary *process in self.unfilteredContent) {

NSMutableArray *matchingFiles = [NSMutableArray array];
Expand Down Expand Up @@ -434,26 +450,47 @@ - (NSMutableArray *)filterContent:(NSMutableArray *)unfilteredContent numberOfMa
}

// See if it matches regex in search field filter
if (hasRegexFilter) {
if (hasSearchFilter) {

int matchCount = 0;
for (NSRegularExpression *regex in regexes) {
if (!([file[@"name"] isMatchedByRegex:regex] ||
[file[@"pname"] isMatchedByRegex:regex] ||
[file[@"pid"] isMatchedByRegex:regex])) {
break;

if (searchUsesRegex) {

// Regex search
for (NSRegularExpression *regex in searchFilters) {
if (!([file[@"name"] isMatchedByRegex:regex] ||
[file[@"pname"] isMatchedByRegex:regex] ||
[file[@"pid"] isMatchedByRegex:regex])) {
break;
}
matchCount += 1;
}

} else {

// Non-regex search
NSStringCompareOptions options = searchCaseSensitive ? 0 : NSCaseInsensitiveSearch;

for (NSString *searchStr in searchFilters) {
if ([file[@"name"] rangeOfString:searchStr options:options].location == NSNotFound &&
[file[@"pname"] rangeOfString:searchStr options:options].location == NSNotFound &&
[file[@"pid"] rangeOfString:searchStr options:options].location == NSNotFound) {
break;
}
matchCount += 1;
}
matchCount += 1;
}
if (matchCount != [regexes count]) {

// Skip if it doesn't match all filter strings
if (matchCount != [searchFilters count]) {
continue;
}
}

[matchingFiles addObject:file];
}

// If we have matching files for the process, and we're not filtering
// If we have matching files for the process, and it's not being excluded as a non-app
if ([matchingFiles count] && !(showApplicationsOnly && ![process[@"app"] boolValue])) {
NSMutableDictionary *p = [process mutableCopy];
p[@"children"] = matchingFiles;
Expand Down Expand Up @@ -486,7 +523,7 @@ - (IBAction)refresh:(id)sender {
[progressIndicator setUsesThreadedAnimation:TRUE];
[progressIndicator startAnimation:self];

// Update in asynchronously in the background, so interface doesn't lock up
// Update asynchronously in the background, so interface doesn't lock up
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@autoreleasepool {
NSString *output = [self runLsof:authenticated];
Expand Down Expand Up @@ -756,7 +793,7 @@ - (void)updateProcessInfo:(NSMutableDictionary *)p {

// See if it's an app bundle
NSString *fileType = [WORKSPACE typeOfFile:pInfoDict[@"BundlePath"] error:nil];
if ([WORKSPACE type:fileType conformsToType:APPLICATION_UTI]) {
if ([WORKSPACE type:fileType conformsToType:APP_BUNDLE_UTI]) {
p[@"app"] = @YES;
}

Expand Down Expand Up @@ -1210,6 +1247,14 @@ - (IBAction)find:(id)sender {
[window makeFirstResponder:filterTextField];
}

- (IBAction)searchFieldOptionChanged:(id)sender {
NSMenuItem *item = sender;
NSString *key = [sender tag] ? @"searchFilterRegex" : @"searchFilterCaseSensitive";
[DEFAULTS setBool:![DEFAULTS boolForKey:key] forKey:key]; // toggle
// We shouldn't have to do this but bindings are flaky for NSSearchField menu templates
[item setState:[DEFAULTS boolForKey:key]];
}

- (BOOL)validateMenuItem:(NSMenuItem *)item {
NSInteger selectedRow = [outlineView clickedRow] == -1 ? [outlineView selectedRow] : [outlineView clickedRow];
BOOL isAction = ([[item title] isEqualToString:@"Show in Finder"] ||
Expand Down

0 comments on commit 0047f3e

Please sign in to comment.