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 locked;
@property(nonatomic) BOOL newUser;
@property(nonatomic) BOOL alwaysYes;
@property(nonatomic, weak) IBOutlet NSArrayController *sitesController;
@property(nonatomic, weak) IBOutlet NSImageView *blurView;

View File

@ -36,7 +36,7 @@
@end
@implementation MPPasswordWindowController { BOOL _skipTextChange; }
@implementation MPPasswordWindowController
#pragma mark - Life
@ -54,31 +54,33 @@
// }];
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowDidBecomeKeyNotification object:self.window
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[self fadeIn];
[self updateUser];
}];
[self fadeIn];
[self updateUser];
}];
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowWillCloseNotification object:self.window
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
NSWindow *sheet = [self.window attachedSheet];
if (sheet)
[NSApp endSheet:sheet];
}];
NSWindow *sheet = [self.window attachedSheet];
if (sheet)
[NSApp endSheet:sheet];
}];
[[NSNotificationCenter defaultCenter] addObserverForName:NSApplicationWillResignActiveNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
#ifndef DEBUG
[self fadeOut];
}];
#endif
}];
[[NSNotificationCenter defaultCenter] addObserverForName:MPSignedInNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[self updateUser];
}];
[self updateUser];
}];
[[NSNotificationCenter defaultCenter] addObserverForName:MPSignedOutNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[self updateUser];
}];
[self updateUser];
}];
[self observeKeyPath:@"sitesController.selection"
withBlock:^(id from, id to, NSKeyValueChange cause, id _self) {
[_self updateSelection];
}];
[_self updateSelection];
}];
NSSearchFieldCell *siteFieldCell = self.siteField.cell;
siteFieldCell.searchButtonCell = nil;
@ -124,17 +126,15 @@
- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector {
if (control == self.siteField) {
if ([NSStringFromSelector( commandSelector ) rangeOfString:@"delete"].location == 0) {
_skipTextChange = YES;
dbg_return_tr( NO, @, control, NSStringFromSelector( commandSelector ) );
}
if ([NSStringFromSelector( commandSelector ) rangeOfString:@"delete"].location == 0)
return NO;
}
if (control == self.securePasswordField || control == self.revealPasswordField) {
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 {
@ -174,7 +174,6 @@
- (BOOL)textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector {
dbg( @"textView:%@doCommandBySelector:%@", textView, NSStringFromSelector( commandSelector ) );
return [self handleCommand:commandSelector];
}
@ -227,9 +226,9 @@
// "Create" button.
[[MPMacAppDelegate get] addSiteNamed:[self.siteField stringValue] completion:
^(MPSiteEntity *site, NSManagedObjectContext *context) {
if (site)
PearlMainQueue( ^{ [self updateSites]; } );
}];
if (site)
PearlMainQueue( ^{ [self updateSites]; } );
}];
break;
}
default:
@ -305,19 +304,6 @@
#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 {
[self.sites insertObject:model atIndex:index];
@ -347,7 +333,7 @@
[alert addButtonWithTitle:@"Delete"];
[alert addButtonWithTitle:@"Cancel"];
[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
didEndSelector:@selector( alertDidEnd:returnCode:contextInfo: ) contextInfo:MPAlertDeleteSite];
}
@ -358,7 +344,7 @@
[alert addButtonWithTitle:@"Save"];
[alert addButtonWithTitle:@"Cancel"];
[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 )];
loginField.stringValue = self.selectedSite.loginName?: @"";
[loginField selectText:self];
@ -392,7 +378,7 @@
[alert addButtonWithTitle:@"Save"];
[alert addButtonWithTitle:@"Cancel"];
[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 layout];
[alert beginSheetModalForWindow:self.window modalDelegate:self
@ -408,8 +394,9 @@
MPSiteType type = [types[t] unsignedIntegerValue];
NSString *title = [site.algorithm nameOfType:type];
if (type & MPSiteTypeClassGenerated)
title = [site.algorithm generatePasswordForSiteNamed:site.siteName ofType:type
withCounter:site.counter usingKey:[MPMacAppDelegate get].key];
title = [site.algorithm generatePasswordForSiteNamed:site.name ofType:type
withCounter:site.counter
usingKey:[MPMacAppDelegate get].key];
NSButtonCell *cell = [self.passwordTypesMatrix cellAtRow:(NSInteger)t column:0];
cell.tag = type;
@ -421,7 +408,7 @@
[alert addButtonWithTitle:@"Save"];
[alert addButtonWithTitle:@"Cancel"];
[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 layout];
[alert beginSheetModalForWindow:self.window modalDelegate:self
@ -464,9 +451,9 @@
NSUserNotification *notification = [NSUserNotification new];
notification.title = @"Password Copied";
if (selectedSite.loginName.length)
notification.subtitle = strf( @"%@ at %@", selectedSite.loginName, selectedSite.siteName );
notification.subtitle = strf( @"%@ at %@", selectedSite.loginName, selectedSite.name );
else
notification.subtitle = selectedSite.siteName;
notification.subtitle = selectedSite.name;
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
}
else {
@ -515,12 +502,24 @@
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) {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
fetchRequest.sortDescriptors = @[ [[NSSortDescriptor alloc] initWithKey:@"lastUsed" ascending:NO] ];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name BEGINSWITH[cd] %@) AND user == %@",
query, query, [[MPMacAppDelegate get] activeUserInContext:context]];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"(%@ == '' OR name LIKE[cd] %@) AND user == %@",
queryPattern, queryPattern, [[MPMacAppDelegate get] activeUserInContext:context]];
NSError *error = nil;
NSArray *siteResults = [context executeFetchRequest:fetchRequest error:&error];
@ -531,36 +530,14 @@
NSMutableArray *newSites = [NSMutableArray arrayWithCapacity:[siteResults count]];
for (MPSiteEntity *site in siteResults)
[newSites addObject:[[MPSiteModel alloc] initWithEntity:site]];
[newSites addObject:[[MPSiteModel alloc] initWithEntity:site fuzzyGroups:fuzzyGroups]];
self.sites = newSites;
}];
}
- (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 updateGradient];
}
- (void)updateGradient {
NSView *siteScrollView = self.siteTable.superview.superview;
NSRect selectedCellFrame = [self.siteTable frameOfCellAtColumn:0 row:((NSInteger)self.sitesController.selectionIndex)];

View File

@ -48,7 +48,7 @@
</ciFilter>
<ciFilter name="CIExposureAdjust">
<configuration>
<real key="inputEV" value="-1"/>
<real key="inputEV" value="0.0"/>
<null key="inputImage"/>
</configuration>
</ciFilter>
@ -181,11 +181,11 @@
</shadow>
<textFieldCell key="cell" lineBreakMode="truncatingTail" alignment="center" title="apple.com" id="o0g-Zv-pH4">
<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"/>
</textFieldCell>
<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">
<bool key="NSContinuouslyUpdatesValue" value="YES"/>
</dictionary>
@ -438,7 +438,7 @@
<string key="NSValueTransformerName">NSNegateBoolean</string>
</dictionary>
</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>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Ia6-7b-dFr">

View File

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

View File

@ -28,25 +28,41 @@
BOOL _initialized;
}
- (id)initWithEntity:(MPSiteEntity *)entity {
- (id)initWithEntity:(MPSiteEntity *)entity fuzzyGroups:(NSArray *)fuzzyGroups {
if (!(self = [super init]))
return nil;
[self setEntity:entity];
[self setEntity:entity fuzzyGroups:fuzzyGroups];
_initialized = YES;
return self;
}
- (void)setEntity:(MPSiteEntity *)entity {
- (void)setEntity:(MPSiteEntity *)entity fuzzyGroups:(NSArray *)fuzzyGroups {
if ([_entityOID isEqual:entity.objectID])
return;
_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.siteName = entity.name;
self.lastUsed = entity.lastUsed;
self.type = entity.type;
self.typeName = entity.typeName;
@ -123,7 +139,7 @@
PearlMainQueue( ^{
self.content = result;
self.contentDisplay = displayResult;
self.displayedContent = displayResult;
} );
}];
[entity resolveLoginUsingKey:[MPAppDelegate_Shared get].key result:^(NSString *result) {