// // TextController.m // RGUI // // Created by Simon Urbanek on Fri Mar 05 2004. // Code by Simon Urbanek and Stefano M. Iacus // Copyright (c) 2004 R Development Core Team. All rights reserved. // #import "Controller.h" #import "TextController.h" #import "CodeCompletion.h" #import "FileCompletion.h" #import #import extern BOOL endRunLoop; @implementation TextController static TextController* sharedTextController; - (id)init { self = [super init]; hist=[[History alloc] init]; promptPosition=0; BOOL WantThread = NO; if(WantThread){ // re-route the stdout to our own file descriptor and use ConnectionCache on it int pfd[2]; pipe(pfd); dup2(pfd[1], STDOUT_FILENO); close(pfd[1]); stdoutFD=pfd[0]; [NSThread detachNewThreadSelector:@selector(stdoutReadThread:) toTarget:self withObject:nil]; //ccout=[[ConnectionCache alloc] initWithDescriptor:pfd[0]]; } // we don't want to re-route stderr yet, because possible errors would crash the application since not everything is setup yet. We do it in writePrompt when we're about to write out first prompt return sharedTextController = self; } - (void)awakeFromNib { Controller *ctrl=[Controller sharedController]; [ctrl setTextController: self]; [self RisBusy:1]; [textView setFont:[NSFont fontWithName:@"Monaco" size:11.0]]; } - (int)numberOfRowsInTableView: (NSTableView *)tableView { return [[hist entries] count]; } - (id)tableView: (NSTableView *)tableView objectValueForTableColumn: (NSTableColumn *)tableColumn row: (int)row { return (NSString*) [[hist entries] objectAtIndex: row]; } - (BOOL)windowShouldClose:(id)sender //- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { NSBeginAlertSheet(@"Closing R session",@"Save",@"Don't Save",@"Cancel",[textView window],self,@selector(shouldCloseDidEnd:returnCode:contextInfo:),NULL,NULL,@"Save workspace image?"); return NO; } /* this gets called by the "wanna save?" sheet on window close */ - (void) shouldCloseDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo { if (returnCode==NSAlertDefaultReturn) [self sendInput: @"quit(\"yes\")"]; if (returnCode==NSAlertAlternateReturn) [self sendInput: @"quit(\"no\")"]; } - (BOOL)textView:(NSTextView *)textViewX shouldChangeTextInRange:(NSRange)affectedCharRange replacementString:(NSString *)replacementString { // Allow changes only for uncommitted text return affectedCharRange.location >= committedLength; } // - (BOOL)logView:(NSTextView *)logViewX shouldChangeTextInRange:(NSRange)affectedCharRange replacementString:(NSString *)replacementString { // // Allow changes only for uncommitted text // return 1;//affectedCharRange.location >= committedLog; //} - (void) commitInput { unsigned textLength = [[textView string] length]; if (textLength == committedLength) { [[Controller sharedController] userInput: @""]; } else if (textLength > committedLength) { NSRange tRange=NSMakeRange(committedLength, textLength - committedLength); [textView setSelectedRange:NSMakeRange(textLength, 0)]; // NSLog(@"commited:\"%@\"\n", [[textView attributedSubstringFromRange:tRange] string]); { NSString *cmd = [NSString stringWithString: [[textView attributedSubstringFromRange:tRange] string]]; [textView replaceCharactersInRange: tRange withString:@""]; [[Controller sharedController] userInput: cmd]; [hist commit: cmd]; [dataSource reloadData]; } endRunLoop = YES; } } - (void) commitOutput { unsigned textLength = [[textView string] length]; if (textLength > committedLength) { NSRange tRange=NSMakeRange(committedLength, textLength - committedLength); [textView setSelectedRange:NSMakeRange(textLength, 0)]; [textView insertText:@""]; [textView setTextColor:[NSColor blackColor] range:tRange]; //NSLog(@"before output commit:\"%@\"\n", [[textView attributedSubstringFromRange:tRange] string]); committedLength = textLength; } } - (void) sendInput: (NSString*) text { [textView insertText:text]; [self commitInput]; } - (BOOL)textView:(NSTextView *)textViewX doCommandBySelector:(SEL)commandSelector { BOOL retval = NO; //NSLog(@"textView commandSelector: %@\n", NSStringFromSelector(commandSelector)); if (@selector(insertNewline:) == commandSelector) { [self commitInput]; retval = YES; } if (@selector(moveUp:) == commandSelector) { unsigned textLength = [[textViewX string] length]; NSRange sr=[textViewX selectedRange]; if (sr.location==committedLength || sr.location==textLength) { NSRange rr=NSMakeRange(committedLength, textLength-committedLength); NSString *text = [[textViewX attributedSubstringFromRange:rr] string]; //NSLog(@"Text=\"%@\"", text); if ([hist isDirty]) { [hist updateDirty: text]; //NSLog(@"updated dirty with above text"); } NSString *news = [hist prev]; //NSLog(@"[hist prev]=\"%@\"", news); if (news!=nil) { [news retain]; sr.length=0; sr.location=committedLength; [textViewX setSelectedRange:sr]; [textViewX replaceCharactersInRange:rr withString:news]; [textViewX insertText:@""]; [news release]; } retval = YES; } } if (@selector(moveDown:) == commandSelector) { unsigned textLength = [[textViewX string] length]; NSRange sr=[textViewX selectedRange]; if ((sr.location==committedLength || sr.location==textLength) && ![hist isDirty]) { NSRange rr=NSMakeRange(committedLength, textLength-committedLength); NSString *news = [hist next]; //NSLog(@"[hist next]=\"%@\"", news); if (news==nil) news=@""; else [news retain]; sr.length=0; sr.location=committedLength; [textViewX setSelectedRange:sr]; [textViewX replaceCharactersInRange:rr withString:news]; [textViewX insertText:@""]; [news release]; retval = YES; } } if (@selector(moveToBeginningOfParagraph:) == commandSelector || @selector(moveToBeginningOfLine:) == commandSelector) { [textViewX setSelectedRange: NSMakeRange(committedLength,0)]; retval = YES; } if (@selector(insertTab:) == commandSelector) { NSRange sr=[textViewX selectedRange]; int bow=sr.location; if (bow>committedLength) { while (bow>committedLength) bow--; { NSString *rep=nil; NSRange er = NSMakeRange(bow,sr.location-bow); NSString *text = [[textViewX attributedSubstringFromRange:er] string]; // first we need to find out whether we're in a text part or code part unichar c; int tl = [text length], tp=0, quotes=0, dquotes=0, lastQuote=-1; while (tp0) { // if we're inside any quotes, use file completion rep=[FileCompletion complete:[text substringFromIndex:lastQuote+1]]; er.location+=lastQuote+1; er.length-=lastQuote+1; } else { // otherwise use code completion int s = [text length]-1; c = [text characterAtIndex:s]; while (((c>='a')&&(c<='z'))||((c>='A')&&(c<='Z'))||((c>='0')&&(c<='9'))||c=='.') { s--; if (s==-1) break; c = [text characterAtIndex:s]; } s++; er.location+=s; er.length-=s; rep=[CodeCompletion complete:[text substringFromIndex:s]]; } // ok, by now we should get "rep" if completion is possible and "er" modified to match the proper part if (rep!=nil) { //NSLog(@"completion result: \"%@\"", rep); [textViewX replaceCharactersInRange:er withString:rep]; } } } retval = YES; // tab is never passed further } return retval; } - (void)writePrompt: (NSString*) text { unsigned textLength = [[textView string] length]; int promptLength=[text length]; BOOL WantThread = NO; if (promptPosition==0 && WantThread) { // we initialize stderr only after the first prompt int pfd[2]; pipe(pfd); dup2(pfd[1], STDERR_FILENO); close(pfd[1]); ccerr=[[ConnectionCache alloc] initWithDescriptor:pfd[0]]; } [self commitOutput]; promptPosition=committedLength; if (promptLength>0) { [textView setSelectedRange:NSMakeRange(textLength, 0)]; [textView insertText:text]; [textView setTextColor:[NSColor blueColor] range:NSMakeRange(textLength, promptLength)]; committedLength+=promptLength; } } - (void)writeConsole: (NSString*) text { [textView insertText:text]; [self commitOutput]; } - (void)writeUserInput: (NSString*) text { [self commitOutput]; [textView insertText:text]; unsigned textLength = [[textView string] length]; if (textLength > committedLength) { NSRange tRange=NSMakeRange(committedLength, textLength - committedLength); [textView setSelectedRange:NSMakeRange(textLength, 0)]; [textView insertText:@""]; [textView setTextColor:[NSColor redColor] range:tRange]; //NSLog(@"writeUserInput:\"%@\"\n", [[textView attributedSubstringFromRange:tRange] string]); committedLength = textLength; } } - (void)RisBusy: (int) isBusy { if (isBusy) [progressWheel startAnimation:self]; else [progressWheel stopAnimation:self]; } - (void) stdoutReadThread: (id) argument { NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init]; char *buf=(char*) malloc(2048); int n, fd; fd_set readfds; fd=stdoutFD; fcntl(fd, F_SETFL, O_NONBLOCK); while (1) { FD_ZERO(&readfds); FD_SET(fd,&readfds); select(fd+1, &readfds, 0, 0, 0); if (FD_ISSET(fd, &readfds)) { while ((n=read(fd,buf,2048))>0) { [self writeExternal:[NSData dataWithBytes:buf length:n] withColor:[NSColor yellowColor]]; } } } free(buf); [pool release]; } /* We should make it asyncronous, i.e without waiting, so to have realtime feedback (see package installation) */ /* We should also make it non editable */ - (void) writeExternal: (NSData*) d withColor: (NSColor*) color { unsigned textLength = [[logView string] length]; int dlen=[d length]; [logView setSelectedRange:NSMakeRange(textLength, 0)]; [logView insertText:[NSString stringWithCString:(char*)[d bytes] length:dlen]]; [logView setTextColor:color range:NSMakeRange(textLength, dlen)]; // committedLog += dlen; } /* This old version can be removed, I've just kept it here in case Simon does not agree with the Log window */ - (void) writeExternal2: (NSData*) d withColor: (NSColor*) color { NSData *astr=nil; int dl=[d length]; int tl=[[textView string] length]; if (promptPosition0) { [self sendInput:[NSString stringWithFormat:@"help.search(\"%@\")", [helpSearch stringValue]]]; [helpSearch setStringValue:@""]; } } -(BOOL)acceptsFirstResponder { NSLog(@"Accepting First Responder"); return YES; } -(BOOL)resignFirstResponder { NSLog(@"Resigned First Responder"); return YES; } -(BOOL)becomeFirstResponder { NSLog(@"Become First Responder"); [ consoleWindow setAcceptsMouseMovedEvents: YES]; return YES; } -(void)keyDown:(NSEvent*)theEvent { NSLog(@"Key down: %@",theEvent); } @end