2
0

Fuzzy site name search and highlight fuzzy results.

This commit is contained in:
Maarten Billemont 2014-10-29 21:24:35 -04:00
parent d642cb1aee
commit 8615f6af5d
7 changed files with 81 additions and 87 deletions

@ -1 +1 @@
Subproject commit e1955221bf52d53736e7d3e7d38465c509e02562 Subproject commit 1b8f8b79ad12b70976c7a417ff1a9d29e8c0ed73

2
External/Pearl vendored

@ -1 +1 @@
Subproject commit ef628f388e70459725de21e3528831c705f88795 Subproject commit 2237aaf4295f74627b63040c4c246cddff339958

View File

@ -29,7 +29,6 @@
@property(nonatomic) BOOL alternatePressed; @property(nonatomic) BOOL alternatePressed;
@property(nonatomic) BOOL locked; @property(nonatomic) BOOL locked;
@property(nonatomic) BOOL newUser; @property(nonatomic) BOOL newUser;
@property(nonatomic) BOOL alwaysYes;
@property(nonatomic, weak) IBOutlet NSArrayController *sitesController; @property(nonatomic, weak) IBOutlet NSArrayController *sitesController;
@property(nonatomic, weak) IBOutlet NSImageView *blurView; @property(nonatomic, weak) IBOutlet NSImageView *blurView;

View File

@ -36,7 +36,7 @@
@end @end
@implementation MPPasswordWindowController { BOOL _skipTextChange; } @implementation MPPasswordWindowController
#pragma mark - Life #pragma mark - Life
@ -65,7 +65,9 @@
}]; }];
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillResignActiveNotification object:nil [[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillResignActiveNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
#ifndef DEBUG
[self fadeOut]; [self fadeOut];
#endif
}]; }];
[[NSNotificationCenter defaultCenter] addObserverForName:MPSignedInNotification object:nil [[NSNotificationCenter defaultCenter] addObserverForName:MPSignedInNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
@ -124,17 +126,15 @@
- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector { - (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector {
if (control == self.siteField) { if (control == self.siteField) {
if ([NSStringFromSelector( commandSelector ) rangeOfString:@"delete"].location == 0) { if ([NSStringFromSelector( commandSelector ) rangeOfString:@"delete"].location == 0)
_skipTextChange = YES; return NO;
dbg_return_tr( NO, @, control, NSStringFromSelector( commandSelector ) );
}
} }
if (control == self.securePasswordField || control == self.revealPasswordField) { if (control == self.securePasswordField || control == self.revealPasswordField) {
if (commandSelector == @selector( insertNewline: )) if (commandSelector == @selector( insertNewline: ))
dbg_return_tr( NO, @, control, NSStringFromSelector( commandSelector ) ); return NO;
} }
dbg_return_tr( [self handleCommand:commandSelector], @, control, NSStringFromSelector( commandSelector ) ); return [self handleCommand:commandSelector];
} }
- (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor { - (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor {
@ -174,7 +174,6 @@
- (BOOL)textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector { - (BOOL)textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector {
dbg( @"textView:%@doCommandBySelector:%@", textView, NSStringFromSelector( commandSelector ) );
return [self handleCommand:commandSelector]; return [self handleCommand:commandSelector];
} }
@ -305,19 +304,6 @@
#pragma mark - State #pragma mark - State
- (NSString *)query {
return [self.siteField.stringValue stringByReplacingCharactersInRange:self.siteField.currentEditor.selectedRange withString:@""]?: @"";
}
- (BOOL)alwaysYes {
return YES;
}
- (void)setAlwaysYes:(BOOL)alwaysYes {
}
- (void)insertObject:(MPSiteModel *)model inSitesAtIndex:(NSUInteger)index { - (void)insertObject:(MPSiteModel *)model inSitesAtIndex:(NSUInteger)index {
[self.sites insertObject:model atIndex:index]; [self.sites insertObject:model atIndex:index];
@ -347,7 +333,7 @@
[alert addButtonWithTitle:@"Delete"]; [alert addButtonWithTitle:@"Delete"];
[alert addButtonWithTitle:@"Cancel"]; [alert addButtonWithTitle:@"Cancel"];
[alert setMessageText:@"Delete Site?"]; [alert setMessageText:@"Delete Site?"];
[alert setInformativeText:strf( @"Do you want to delete the site named:\n\n%@", self.selectedSite.siteName )]; [alert setInformativeText:strf( @"Do you want to delete the site named:\n\n%@", self.selectedSite.name )];
[alert beginSheetModalForWindow:self.window modalDelegate:self [alert beginSheetModalForWindow:self.window modalDelegate:self
didEndSelector:@selector( alertDidEnd:returnCode:contextInfo: ) contextInfo:MPAlertDeleteSite]; didEndSelector:@selector( alertDidEnd:returnCode:contextInfo: ) contextInfo:MPAlertDeleteSite];
} }
@ -358,7 +344,7 @@
[alert addButtonWithTitle:@"Save"]; [alert addButtonWithTitle:@"Save"];
[alert addButtonWithTitle:@"Cancel"]; [alert addButtonWithTitle:@"Cancel"];
[alert setMessageText:@"Change Login Name"]; [alert setMessageText:@"Change Login Name"];
[alert setInformativeText:strf( @"Enter the login name for: %@", self.selectedSite.siteName )]; [alert setInformativeText:strf( @"Enter the login name for: %@", self.selectedSite.name )];
NSTextField *loginField = [[NSTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )]; NSTextField *loginField = [[NSTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
loginField.stringValue = self.selectedSite.loginName?: @""; loginField.stringValue = self.selectedSite.loginName?: @"";
[loginField selectText:self]; [loginField selectText:self];
@ -392,7 +378,7 @@
[alert addButtonWithTitle:@"Save"]; [alert addButtonWithTitle:@"Save"];
[alert addButtonWithTitle:@"Cancel"]; [alert addButtonWithTitle:@"Cancel"];
[alert setMessageText:@"Change Password"]; [alert setMessageText:@"Change Password"];
[alert setInformativeText:strf( @"Enter the new password for: %@", self.selectedSite.siteName )]; [alert setInformativeText:strf( @"Enter the new password for: %@", self.selectedSite.name )];
[alert setAccessoryView:[[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )]]; [alert setAccessoryView:[[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )]];
[alert layout]; [alert layout];
[alert beginSheetModalForWindow:self.window modalDelegate:self [alert beginSheetModalForWindow:self.window modalDelegate:self
@ -408,8 +394,9 @@
MPSiteType type = [types[t] unsignedIntegerValue]; MPSiteType type = [types[t] unsignedIntegerValue];
NSString *title = [site.algorithm nameOfType:type]; NSString *title = [site.algorithm nameOfType:type];
if (type & MPSiteTypeClassGenerated) if (type & MPSiteTypeClassGenerated)
title = [site.algorithm generatePasswordForSiteNamed:site.siteName ofType:type title = [site.algorithm generatePasswordForSiteNamed:site.name ofType:type
withCounter:site.counter usingKey:[MPMacAppDelegate get].key]; withCounter:site.counter
usingKey:[MPMacAppDelegate get].key];
NSButtonCell *cell = [self.passwordTypesMatrix cellAtRow:(NSInteger)t column:0]; NSButtonCell *cell = [self.passwordTypesMatrix cellAtRow:(NSInteger)t column:0];
cell.tag = type; cell.tag = type;
@ -421,7 +408,7 @@
[alert addButtonWithTitle:@"Save"]; [alert addButtonWithTitle:@"Save"];
[alert addButtonWithTitle:@"Cancel"]; [alert addButtonWithTitle:@"Cancel"];
[alert setMessageText:@"Change Password Type"]; [alert setMessageText:@"Change Password Type"];
[alert setInformativeText:strf( @"Choose a new password type for: %@", site.siteName )]; [alert setInformativeText:strf( @"Choose a new password type for: %@", site.name )];
[alert setAccessoryView:self.passwordTypesBox]; [alert setAccessoryView:self.passwordTypesBox];
[alert layout]; [alert layout];
[alert beginSheetModalForWindow:self.window modalDelegate:self [alert beginSheetModalForWindow:self.window modalDelegate:self
@ -464,9 +451,9 @@
NSUserNotification *notification = [NSUserNotification new]; NSUserNotification *notification = [NSUserNotification new];
notification.title = @"Password Copied"; notification.title = @"Password Copied";
if (selectedSite.loginName.length) if (selectedSite.loginName.length)
notification.subtitle = strf( @"%@ at %@", selectedSite.loginName, selectedSite.siteName ); notification.subtitle = strf( @"%@ at %@", selectedSite.loginName, selectedSite.name );
else else
notification.subtitle = selectedSite.siteName; notification.subtitle = selectedSite.name;
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification]; [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
} }
else { else {
@ -515,12 +502,24 @@
return; return;
} }
NSString *query = [self query]; static NSRegularExpression *fuzzyRE;
static dispatch_once_t once = 0;
dispatch_once( &once, ^{
fuzzyRE = [NSRegularExpression regularExpressionWithPattern:@"(.)" options:0 error:nil];
} );
NSString *queryString = self.siteField.stringValue;
NSString *queryPattern = [queryString stringByReplacingMatchesOfExpression:fuzzyRE withTemplate:@"*$1*"];
NSMutableArray *fuzzyGroups = [NSMutableArray new];
[fuzzyRE enumerateMatchesInString:queryString options:0 range:NSMakeRange( 0, queryString.length )
usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
[fuzzyGroups addObject:[queryString substringWithRange:result.range] ];
}];
[MPMacAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) { [MPMacAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )]; NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
fetchRequest.sortDescriptors = @[ [[NSSortDescriptor alloc] initWithKey:@"lastUsed" ascending:NO] ]; fetchRequest.sortDescriptors = @[ [[NSSortDescriptor alloc] initWithKey:@"lastUsed" ascending:NO] ];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND user == %@", fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name LIKE[cd] %@) AND user == %@",
query, query, [[MPMacAppDelegate get] activeUserInContext:context]]; queryPattern, queryPattern, [[MPMacAppDelegate get] activeUserInContext:context]];
NSError *error = nil; NSError *error = nil;
NSArray *siteResults = [context executeFetchRequest:fetchRequest error:&error]; NSArray *siteResults = [context executeFetchRequest:fetchRequest error:&error];
@ -531,36 +530,14 @@
NSMutableArray *newSites = [NSMutableArray arrayWithCapacity:[siteResults count]]; NSMutableArray *newSites = [NSMutableArray arrayWithCapacity:[siteResults count]];
for (MPSiteEntity *site in siteResults) for (MPSiteEntity *site in siteResults)
[newSites addObject:[[MPSiteModel alloc] initWithEntity:site]]; [newSites addObject:[[MPSiteModel alloc] initWithEntity:site fuzzyGroups:fuzzyGroups]];
self.sites = newSites; self.sites = newSites;
}]; }];
} }
- (void)updateSelection { - (void)updateSelection {
if (_skipTextChange) {
_skipTextChange = NO;
return;
}
NSString *siteName = self.selectedSite.siteName;
if (!siteName)
return;
if ([self.window isKeyWindow] && [self.siteField isEqual:[self.window firstResponder]]) {
NSRange siteNameQueryRange = [siteName rangeOfString:[self query]];
self.siteField.stringValue = siteName;
if (siteNameQueryRange.location == 0)
self.siteField.currentEditor.selectedRange =
NSMakeRange( siteNameQueryRange.length, siteName.length - siteNameQueryRange.length );
}
[self.siteTable scrollRowToVisible:(NSInteger)self.sitesController.selectionIndex]; [self.siteTable scrollRowToVisible:(NSInteger)self.sitesController.selectionIndex];
[self updateGradient];
}
- (void)updateGradient {
NSView *siteScrollView = self.siteTable.superview.superview; NSView *siteScrollView = self.siteTable.superview.superview;
NSRect selectedCellFrame = [self.siteTable frameOfCellAtColumn:0 row:((NSInteger)self.sitesController.selectionIndex)]; NSRect selectedCellFrame = [self.siteTable frameOfCellAtColumn:0 row:((NSInteger)self.sitesController.selectionIndex)];

View File

@ -48,7 +48,7 @@
</ciFilter> </ciFilter>
<ciFilter name="CIExposureAdjust"> <ciFilter name="CIExposureAdjust">
<configuration> <configuration>
<real key="inputEV" value="-1"/> <real key="inputEV" value="0.0"/>
<null key="inputImage"/> <null key="inputImage"/>
</configuration> </configuration>
</ciFilter> </ciFilter>
@ -181,11 +181,11 @@
</shadow> </shadow>
<textFieldCell key="cell" lineBreakMode="truncatingTail" alignment="center" title="apple.com" id="o0g-Zv-pH4"> <textFieldCell key="cell" lineBreakMode="truncatingTail" alignment="center" title="apple.com" id="o0g-Zv-pH4">
<font key="font" size="24" name="HelveticaNeue-Thin"/> <font key="font" size="24" name="HelveticaNeue-Thin"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/> <color key="textColor" name="alternateSelectedControlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
<connections> <connections>
<binding destination="xQb-o5-M5U" name="value" keyPath="objectValue.siteName" id="CZi-gD-5Ec"> <binding destination="xQb-o5-M5U" name="value" keyPath="objectValue.displayedName" id="CZi-gD-5Ec">
<dictionary key="options"> <dictionary key="options">
<bool key="NSContinuouslyUpdatesValue" value="YES"/> <bool key="NSContinuouslyUpdatesValue" value="YES"/>
</dictionary> </dictionary>
@ -438,7 +438,7 @@
<string key="NSValueTransformerName">NSNegateBoolean</string> <string key="NSValueTransformerName">NSNegateBoolean</string>
</dictionary> </dictionary>
</binding> </binding>
<binding destination="mcS-ik-b0n" name="value" keyPath="selection.contentDisplay" id="djg-i5-pwt"/> <binding destination="mcS-ik-b0n" name="value" keyPath="selection.displayedContent" id="djg-i5-pwt"/>
</connections> </connections>
</textField> </textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Ia6-7b-dFr"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Ia6-7b-dFr">

View File

@ -17,15 +17,17 @@
// //
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "MPSiteEntity.h"
@class MPSiteEntity; @class MPSiteEntity;
@interface MPSiteModel : NSObject @interface MPSiteModel : NSObject
@property (nonatomic) NSString *siteName; @property (nonatomic) NSString *name;
@property (nonatomic) NSAttributedString *displayedName;
@property (nonatomic) MPSiteType type; @property (nonatomic) MPSiteType type;
@property (nonatomic) NSString *typeName; @property (nonatomic) NSString *typeName;
@property (nonatomic) NSString *content; @property (nonatomic) NSString *content;
@property (nonatomic) NSString *contentDisplay; @property (nonatomic) NSString *displayedContent;
@property (nonatomic) NSString *loginName; @property (nonatomic) NSString *loginName;
@property (nonatomic) NSNumber *uses; @property (nonatomic) NSNumber *uses;
@property (nonatomic) NSUInteger counter; @property (nonatomic) NSUInteger counter;
@ -34,7 +36,7 @@
@property (nonatomic) BOOL generated; @property (nonatomic) BOOL generated;
@property (nonatomic) BOOL stored; @property (nonatomic) BOOL stored;
- (id)initWithEntity:(MPSiteEntity *)entity; - (instancetype)initWithEntity:(MPSiteEntity *)entity fuzzyGroups:(NSArray *)fuzzyGroups;
- (MPSiteEntity *)entityInContext:(NSManagedObjectContext *)moc; - (MPSiteEntity *)entityInContext:(NSManagedObjectContext *)moc;
- (void)updateContent; - (void)updateContent;

View File

@ -28,25 +28,41 @@
BOOL _initialized; BOOL _initialized;
} }
- (id)initWithEntity:(MPSiteEntity *)entity { - (id)initWithEntity:(MPSiteEntity *)entity fuzzyGroups:(NSArray *)fuzzyGroups {
if (!(self = [super init])) if (!(self = [super init]))
return nil; return nil;
[self setEntity:entity]; [self setEntity:entity fuzzyGroups:fuzzyGroups];
_initialized = YES; _initialized = YES;
return self; return self;
} }
- (void)setEntity:(MPSiteEntity *)entity { - (void)setEntity:(MPSiteEntity *)entity fuzzyGroups:(NSArray *)fuzzyGroups {
if ([_entityOID isEqual:entity.objectID]) if ([_entityOID isEqual:entity.objectID])
return; return;
_entityOID = entity.objectID; _entityOID = entity.objectID;
NSString *siteName = entity.name;
NSMutableAttributedString *attributedSiteName = [[NSMutableAttributedString alloc] initWithString:siteName];
for (NSUInteger f = 0, s = (NSUInteger)-1; f < [fuzzyGroups count]; ++f) {
s = [siteName rangeOfString:fuzzyGroups[f] options:NSDiacriticInsensitiveSearch | NSCaseInsensitiveSearch
range:NSMakeRange( s + 1, [siteName length] - (s + 1) )].location;
if (s == NSNotFound)
break;
[attributedSiteName addAttribute:NSBackgroundColorAttributeName value:[NSColor alternateSelectedControlColor]
range:NSMakeRange( s, [fuzzyGroups[f] length] )];
}
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.alignment = NSCenterTextAlignment;
[attributedSiteName addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange( 0, [siteName length] )];
self.displayedName = attributedSiteName;
self.name = siteName;
self.algorithm = entity.algorithm; self.algorithm = entity.algorithm;
self.siteName = entity.name;
self.lastUsed = entity.lastUsed; self.lastUsed = entity.lastUsed;
self.type = entity.type; self.type = entity.type;
self.typeName = entity.typeName; self.typeName = entity.typeName;
@ -123,7 +139,7 @@
PearlMainQueue( ^{ PearlMainQueue( ^{
self.content = result; self.content = result;
self.contentDisplay = displayResult; self.displayedContent = displayResult;
} ); } );
}]; }];
[entity resolveLoginUsingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) { [entity resolveLoginUsingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) {