From 37e98655ec5381f970dcc13c2dc9fdb3416b1739 Mon Sep 17 00:00:00 2001 From: Jacob Bandes-Storch Date: Tue, 19 Mar 2013 16:50:36 -0700 Subject: [PATCH] Implement filter through command Closes #131 --- .../src/OakRunCommandWindowController.mm | 51 +++++++++++---- Frameworks/OakTextView/src/OakTextView.h | 2 + Frameworks/OakTextView/src/OakTextView.mm | 65 +++++++++++++++++++ 3 files changed, 106 insertions(+), 12 deletions(-) diff --git a/Frameworks/DocumentWindow/src/OakRunCommandWindowController.mm b/Frameworks/DocumentWindow/src/OakRunCommandWindowController.mm index aefbf509..a9d5a246 100644 --- a/Frameworks/DocumentWindow/src/OakRunCommandWindowController.mm +++ b/Frameworks/DocumentWindow/src/OakRunCommandWindowController.mm @@ -2,6 +2,7 @@ #import #import #import +#import @interface OakRunCommandWindowController () @property (nonatomic) NSTextField* commandLabel; @@ -13,6 +14,7 @@ @property (nonatomic) NSObjectController* objectController; @property (nonatomic) OakHistoryList* commandHistoryList; @property (nonatomic) NSMutableArray* myConstraints; +@property (nonatomic) output::type outputType; @end #ifndef CONSTRAINT @@ -30,6 +32,7 @@ { if((self = [super initWithWindow:[[NSPanel alloc] initWithContentRect:NSZeroRect styleMask:(NSTitledWindowMask|NSClosableWindowMask|NSResizableWindowMask|NSMiniaturizableWindowMask) backing:NSBackingStoreBuffered defer:NO]])) { + self.outputType = output::replace_input; self.myConstraints = [NSMutableArray new]; self.commandLabel = OakCreateLabel(@"Command:"); @@ -39,8 +42,21 @@ self.executeButton = OakCreateButton(@"Execute"); self.cancelButton = OakCreateButton(@"Cancel"); - for(NSString* title in @[ @"Replace Input", @"Insert After Input" ]) - [self.resultPopUpButton addItemWithTitle:title]; + NSDictionary* outputOptions = @{ + @(output::replace_input) : @"Replace Input", + @(output::after_input) : @"Insert After Input", + // @(output::new_window) : @"New Window", + @(output::tool_tip) : @"Tool Tip", + }; + + NSMenu* menu = [self.resultPopUpButton menu]; + [menu removeAllItems]; + char key = '0'; + for(NSNumber* type in outputOptions) + [[menu addItemWithTitle:outputOptions[type] action:@selector(takeOutputTypeFrom:) keyEquivalent:[NSString stringWithFormat:@"%c", ++key]] setTag:[type intValue]]; + + [self.resultLabel setAlignment:NSRightTextAlignment]; + [self.commandLabel setAlignment:NSRightTextAlignment]; self.executeButton.action = @selector(execute:); self.cancelButton.action = @selector(cancel:); @@ -54,6 +70,9 @@ [self.commandComboBox bind:NSValueBinding toObject:_objectController withKeyPath:@"content.commandHistoryList.head" options:nil]; [self.commandComboBox bind:NSContentValuesBinding toObject:_objectController withKeyPath:@"content.commandHistoryList.list" options:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(commandChanged:) name:NSControlTextDidChangeNotification object:self.commandComboBox]; + [self commandChanged:nil]; + NSDictionary* views = @{ @"commandLabel" : self.commandLabel, @"command" : self.commandComboBox, @@ -70,8 +89,8 @@ [contentView addSubview:view]; } - CONSTRAINT(@"H:|-(>=20,==20@75)-[commandLabel]-[command(>=250)]-|", NSLayoutFormatAlignAllBaseline); - CONSTRAINT(@"H:|-(>=20,==20@75)-[resultLabel]-[result]-(>=20)-|", NSLayoutFormatAlignAllBaseline); + CONSTRAINT(@"H:|-[commandLabel]-[command(>=250)]-|", NSLayoutFormatAlignAllBaseline); + CONSTRAINT(@"H:|-[resultLabel(==commandLabel)]-[result]-(>=20)-|", NSLayoutFormatAlignAllBaseline); CONSTRAINT(@"H:|-(>=20)-[cancel]-[execute]-|", NSLayoutFormatAlignAllBaseline); CONSTRAINT(@"V:|-[command]-[result]", NSLayoutFormatAlignAllLeft); CONSTRAINT(@"V:[result]-[execute]-|", 0); @@ -88,22 +107,30 @@ return self; } +- (void)takeOutputTypeFrom:(id)sender +{ + self.outputType = (output::type)[sender tag]; +} + +- (void)commandChanged:(NSNotification*)notification +{ + self.executeButton.enabled = NSNotEmptyString([self.commandComboBox.stringValue stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]); +} + - (IBAction)execute:(id)sender { - NSLog(@"%s", sel_getName(_cmd)); + if(![self.objectController commitEditing]) + return; + + NSString* command = self.commandComboBox.stringValue; + if(id textView = [NSApp targetForAction:@selector(filterDocumentThroughCommand:input:output:)]) + [textView filterDocumentThroughCommand:command input:input::selection output:self.outputType]; - if([self.objectController commitEditing]) - { - NSString* command = self.commandComboBox.stringValue; - if(NSNotEmptyString(command)) - NSLog(@"%s run ‘%@’", sel_getName(_cmd), command); - } [self close]; } - (IBAction)cancel:(id)sender { - NSLog(@"%s", sel_getName(_cmd)); [self close]; } @end diff --git a/Frameworks/OakTextView/src/OakTextView.h b/Frameworks/OakTextView/src/OakTextView.h index 20725fa4..5c766da4 100644 --- a/Frameworks/OakTextView/src/OakTextView.h +++ b/Frameworks/OakTextView/src/OakTextView.h @@ -120,6 +120,8 @@ PUBLIC @interface OakTextView : OakView - (GVLineRecord const&)lineRecordForPosition:(CGFloat)yPos; - (GVLineRecord const&)lineFragmentForLine:(NSUInteger)aLine column:(NSUInteger)aColumn; +- (BOOL)filterDocumentThroughCommand:(NSString*)commandString input:(input::type)inputUnit output:(output::type)outputUnit; + - (NSPoint)positionForWindowUnderCaret; - (scope::context_t const&)scopeContext; - (folding_state_t)foldingStateForLine:(NSUInteger)lineNumber; diff --git a/Frameworks/OakTextView/src/OakTextView.mm b/Frameworks/OakTextView/src/OakTextView.mm index 404b4661..a019fb02 100644 --- a/Frameworks/OakTextView/src/OakTextView.mm +++ b/Frameworks/OakTextView/src/OakTextView.mm @@ -27,9 +27,12 @@ #import #import #import +#import #import #import #import +#import +#import OAK_DEBUG_VAR(OakTextView_TextInput); OAK_DEBUG_VAR(OakTextView_Accessibility); @@ -2275,6 +2278,68 @@ static char const* kOakMenuItemTitle = "OakMenuItemTitle"; return res = GVLineRecord(record.line, record.softline, record.top, record.bottom, record.baseline); } +- (BOOL)filterDocumentThroughCommand:(NSString*)commandString input:(input::type)inputUnit output:(output::type)outputUnit +{ + auto environment = editor->variables(std::map(), to_s([self scopeAttributes])); + if(io::process_t process = io::spawn(std::vector{ "/bin/sh", "-c", to_s(commandString) }, environment)) + { + bool inputWasSelection = false; + text::range_t inputRange = ng::write_unit_to_fd(document->buffer(), editor->ranges().last(), document->buffer().indent().tab_size(), process.in, inputUnit, input::entire_document, input_format::text, scope::selector_t(), environment, &inputWasSelection); + close(process.in); + + __block std::string output, error; + __block bool success = false; + + dispatch_group_t group = dispatch_group_create(); + + dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + io::exhaust_fd(process.out, &output); + }); + + dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + io::exhaust_fd(process.err, &error); + }); + + dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + int status = 0; + if(waitpid(process.pid, &status, 0) != process.pid) + perror("waitpid"); + else if(!WIFEXITED(status)) + NSLog(@"*** abnormal exit (%d) from ‘%@’\n", status, commandString); + else if(WEXITSTATUS(status) != 0) + NSLog(@"*** exit code %d from ‘%@’\n", WEXITSTATUS(status), commandString); + else + success = true; + }); + + dispatch_group_wait(group, DISPATCH_TIME_FOREVER); + dispatch_release(group); + + error = text::trim(error); + if(!error.empty()) + { + OakShowToolTip([NSString stringWithCxxString:error], [self positionForWindowUnderCaret]); + if(success && output.empty()) + return YES; + } + + if(success) + { + if(outputUnit == output::tool_tip) + { + OakShowToolTip([NSString stringWithCxxString:text::trim(output)], [self positionForWindowUnderCaret]); + } + else + { + AUTO_REFRESH; + editor->handle_result(output, outputUnit, output_format::text, output_caret::after_output, inputRange, environment); + } + return YES; + } + } + return NO; +} + // =================== // = Macro Recording = // ===================