| 1 |
// |
| 2 |
// RubyConsoleWindowController.m |
| 3 |
// Alchemusica |
| 4 |
// |
| 5 |
// Created by Toshi Nagata on 09/03/01. |
| 6 |
// Copyright 2009-2016 Toshi Nagata. All rights reserved. |
| 7 |
// |
| 8 |
/* |
| 9 |
This program is free software; you can redistribute it and/or modify |
| 10 |
it under the terms of the GNU General Public License as published by |
| 11 |
the Free Software Foundation version 2 of the License. |
| 12 |
|
| 13 |
This program is distributed in the hope that it will be useful, |
| 14 |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 15 |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 16 |
GNU General Public License for more details. |
| 17 |
*/ |
| 18 |
|
| 19 |
#import "RubyConsoleWindowController.h" |
| 20 |
#import "MyAppController.h" |
| 21 |
#include "MDRubyExtern.h" |
| 22 |
#include "MDHeaders.h" |
| 23 |
|
| 24 |
@implementation RubyConsoleWindowController |
| 25 |
|
| 26 |
#pragma mark ====== Ruby Console ====== |
| 27 |
|
| 28 |
static RubyConsoleWindowController *shared; |
| 29 |
|
| 30 |
+ (RubyConsoleWindowController *)sharedRubyConsoleWindowController |
| 31 |
{ |
| 32 |
if (shared == nil) |
| 33 |
shared = [[RubyConsoleWindowController alloc] initWithWindowNibName: @"RubyConsoleWindow"]; |
| 34 |
return shared; |
| 35 |
} |
| 36 |
|
| 37 |
- (void)windowDidLoad |
| 38 |
{ |
| 39 |
NSFont *font; |
| 40 |
[super windowDidLoad]; |
| 41 |
font = [NSFont userFixedPitchFontOfSize:11.0f]; |
| 42 |
[consoleView setFont:font]; |
| 43 |
[consoleView setEnabledTextCheckingTypes:0]; // Disable "smart=***" |
| 44 |
} |
| 45 |
|
| 46 |
- (void)flushMessage |
| 47 |
{ |
| 48 |
[consoleView display]; |
| 49 |
} |
| 50 |
|
| 51 |
- (void)setConsoleColor: (int)colorID |
| 52 |
{ |
| 53 |
NSColor *col; |
| 54 |
switch (colorID) { |
| 55 |
case 1: col = [NSColor blueColor]; break; |
| 56 |
case 2: col = [NSColor greenColor]; break; |
| 57 |
case 4: col = [NSColor redColor]; break; |
| 58 |
default: col = nil; break; |
| 59 |
} |
| 60 |
[defaultColor release]; |
| 61 |
defaultColor = col; |
| 62 |
if (col != nil) |
| 63 |
[col retain]; |
| 64 |
} |
| 65 |
|
| 66 |
- (int)appendMessage: (NSString *)string withColor: (NSColor *)color |
| 67 |
{ |
| 68 |
NSRange range; |
| 69 |
int len; |
| 70 |
range.location = [[consoleView textStorage] length]; |
| 71 |
range.length = 0; |
| 72 |
[consoleView replaceCharactersInRange: range withString: string]; |
| 73 |
range.length = len = (int)[string length]; |
| 74 |
if (color == nil) |
| 75 |
color = defaultColor; |
| 76 |
[consoleView setTextColor: color range: range]; |
| 77 |
range.location += range.length; |
| 78 |
range.length = 0; |
| 79 |
[consoleView scrollRangeToVisible: range]; |
| 80 |
return (int)[string length]; |
| 81 |
} |
| 82 |
|
| 83 |
- (int)appendMessage: (NSString *)string |
| 84 |
{ |
| 85 |
return [self appendMessage: string withColor: nil]; |
| 86 |
} |
| 87 |
|
| 88 |
- (void)showRubyPrompt |
| 89 |
{ |
| 90 |
NSString *str = [[consoleView textStorage] string]; |
| 91 |
int len = (int)[str length]; |
| 92 |
if (len > 0 && [str characterAtIndex: len - 1] != '\n') |
| 93 |
[self appendMessage: @"\n% "]; |
| 94 |
else |
| 95 |
[self appendMessage: @"% "]; |
| 96 |
[self flushMessage]; |
| 97 |
} |
| 98 |
|
| 99 |
- (void)onEnterPressed: (id)sender |
| 100 |
{ |
| 101 |
// printf("OnEnterPressed invoked\n"); |
| 102 |
|
| 103 |
// if (::wxGetKeyState(WXK_ALT)) { |
| 104 |
// textCtrl->WriteText(wxT("\n> ")); |
| 105 |
// return; |
| 106 |
// } |
| 107 |
NSString *str = [[consoleView textStorage] string]; |
| 108 |
NSMutableString *script = [NSMutableString string]; |
| 109 |
NSRange range, selectedLineRange; |
| 110 |
int start, end; |
| 111 |
int strLen = (int)[str length]; |
| 112 |
|
| 113 |
// Get the block of script to be executed |
| 114 |
range = [str lineRangeForRange: [[[consoleView selectedRanges] objectAtIndex: 0] rangeValue]]; |
| 115 |
// Look forwards |
| 116 |
end = (int)(range.location + range.length); |
| 117 |
while (end < strLen && [str characterAtIndex: end] == '>') { |
| 118 |
NSRange range1 = [str lineRangeForRange: NSMakeRange(end + 1, 0)]; |
| 119 |
end = (int)(range1.location + range1.length); |
| 120 |
} |
| 121 |
// Look backwards |
| 122 |
start = (int)range.location; |
| 123 |
while (start > 0 && start < strLen && [str characterAtIndex: start] != '%') { |
| 124 |
NSRange range2 = [str lineRangeForRange: NSMakeRange(start - 1, 0)]; |
| 125 |
start = (int)range2.location; |
| 126 |
} |
| 127 |
if (start < strLen && [str characterAtIndex: start] == '%') |
| 128 |
start++; |
| 129 |
if (start < strLen && [str characterAtIndex: start] == ' ') |
| 130 |
start++; |
| 131 |
|
| 132 |
// Get script (prompt characters are still in) |
| 133 |
selectedLineRange = NSMakeRange(start, end - start); |
| 134 |
script = [NSMutableString stringWithString: [str substringWithRange: selectedLineRange]]; |
| 135 |
// Remove prompt characters |
| 136 |
[script replaceOccurrencesOfString: @"\n>" withString: @"\n" options: 0 range: NSMakeRange(0, [script length])]; |
| 137 |
[script replaceOccurrencesOfString: @"\n%" withString: @"\n" options: 0 range: NSMakeRange(0, [script length])]; |
| 138 |
|
| 139 |
if ([script length] == 0) { |
| 140 |
// Input is empty |
| 141 |
[self showRubyPrompt]; |
| 142 |
return; |
| 143 |
} |
| 144 |
|
| 145 |
// Append newline to avoid choking Ruby lexical analyzer |
| 146 |
if ([script characterAtIndex: [script length] - 1] != '\n') |
| 147 |
[script appendString: @"\n"]; |
| 148 |
|
| 149 |
if (end < strLen) { |
| 150 |
// Enter is pressed in the block not at the end |
| 151 |
// -> Insert the text at the end |
| 152 |
[self showRubyPrompt]; |
| 153 |
[self appendMessage: script withColor: [NSColor blueColor]]; |
| 154 |
} else { |
| 155 |
[consoleView setTextColor: [NSColor blueColor] range: selectedLineRange]; |
| 156 |
[self appendMessage: @"\n"]; |
| 157 |
} |
| 158 |
|
| 159 |
// Invoke ruby interpreter |
| 160 |
int status; |
| 161 |
char *cscript = strdup([script UTF8String]); |
| 162 |
RubyValue val = Ruby_evalRubyScriptOnDocument(cscript, [(MyAppController *)[NSApp delegate] documentAtIndex: 0], &status); |
| 163 |
cscript[strlen(cscript) - 1] = 0; /* Remove the last newline */ |
| 164 |
AssignArray(&commandHistory, &nCommandHistory, sizeof(char *), nCommandHistory, &cscript); |
| 165 |
if (nCommandHistory >= MAX_HISTORY_LINES) |
| 166 |
DeleteArray(&commandHistory, &nCommandHistory, sizeof(char *), 0, 1, NULL); |
| 167 |
defaultColor = [[NSColor redColor] retain]; |
| 168 |
if (status != 0) |
| 169 |
Ruby_showError(status); |
| 170 |
else { |
| 171 |
char *valueString; |
| 172 |
[self appendMessage: @"-->"]; |
| 173 |
status = Ruby_showValue(val, &valueString); |
| 174 |
if (status != 0) { |
| 175 |
Ruby_showError(status); |
| 176 |
} else { |
| 177 |
AssignArray(&valueHistory, &nValueHistory, sizeof(char *), nValueHistory, &valueString); |
| 178 |
if (nValueHistory >= MAX_HISTORY_LINES) |
| 179 |
DeleteArray(&valueHistory, &nValueHistory, sizeof(char *), 0, 1, NULL); |
| 180 |
} |
| 181 |
} |
| 182 |
[defaultColor release]; |
| 183 |
defaultColor = nil; |
| 184 |
[self showRubyPrompt]; |
| 185 |
[consoleView setSelectedRange: NSMakeRange([[consoleView string] length], 0)]; |
| 186 |
commandHistoryIndex = valueHistoryIndex = -1; |
| 187 |
} |
| 188 |
|
| 189 |
- (BOOL)showHistory:(int)updown |
| 190 |
{ |
| 191 |
BOOL up = (updown < 0); |
| 192 |
BOOL option = ([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask) != 0; |
| 193 |
NSTextStorage *storage = [consoleView textStorage]; |
| 194 |
char *p; |
| 195 |
if (commandHistoryIndex == -1 && valueHistoryIndex == -1) { |
| 196 |
if (!up) |
| 197 |
return NO; |
| 198 |
historyPos = (int)[storage length]; |
| 199 |
} |
| 200 |
if (option) { |
| 201 |
if (up) { |
| 202 |
if (valueHistoryIndex == -1) { |
| 203 |
if (nValueHistory == 0) |
| 204 |
return NO; |
| 205 |
valueHistoryIndex = nValueHistory; |
| 206 |
} |
| 207 |
if (valueHistoryIndex <= 0) |
| 208 |
return YES; /* Key is processed but do nothing */ |
| 209 |
valueHistoryIndex--; |
| 210 |
p = valueHistory[valueHistoryIndex]; |
| 211 |
} else { |
| 212 |
if (valueHistoryIndex == -1) |
| 213 |
return YES; /* Do nothing */ |
| 214 |
if (valueHistoryIndex == nValueHistory - 1) { |
| 215 |
valueHistoryIndex = -1; |
| 216 |
p = ""; |
| 217 |
} else { |
| 218 |
valueHistoryIndex++; |
| 219 |
p = valueHistory[valueHistoryIndex]; |
| 220 |
} |
| 221 |
} |
| 222 |
} else { |
| 223 |
if (up) { |
| 224 |
if (commandHistoryIndex == -1) { |
| 225 |
if (nCommandHistory == 0) |
| 226 |
return NO; |
| 227 |
commandHistoryIndex = nCommandHistory; |
| 228 |
} |
| 229 |
if (commandHistoryIndex <= 0) |
| 230 |
return YES; /* Do nothing */ |
| 231 |
commandHistoryIndex--; |
| 232 |
p = commandHistory[commandHistoryIndex]; |
| 233 |
} else { |
| 234 |
if (commandHistoryIndex == -1) |
| 235 |
return YES; /* Do nothing */ |
| 236 |
if (commandHistoryIndex == nCommandHistory - 1) { |
| 237 |
commandHistoryIndex = -1; |
| 238 |
p = ""; |
| 239 |
} else { |
| 240 |
commandHistoryIndex++; |
| 241 |
p = commandHistory[commandHistoryIndex]; |
| 242 |
} |
| 243 |
} |
| 244 |
} |
| 245 |
if (p == NULL) |
| 246 |
p = ""; |
| 247 |
[storage deleteCharactersInRange:NSMakeRange(historyPos, [storage length] - historyPos)]; |
| 248 |
while (isspace(*p)) |
| 249 |
p++; |
| 250 |
[self setConsoleColor:(option ? 4 : 1)]; |
| 251 |
[self appendMessage:[NSString stringWithUTF8String:p]]; |
| 252 |
[self setConsoleColor:0]; |
| 253 |
return YES; |
| 254 |
} |
| 255 |
|
| 256 |
- (BOOL)textView:(NSTextView *)aTextView doCommandBySelector:(SEL)aSelector |
| 257 |
{ |
| 258 |
if (aSelector == @selector(insertNewline:)) { |
| 259 |
[self onEnterPressed: self]; |
| 260 |
return YES; |
| 261 |
} else if (aSelector == @selector(moveUp:) || aSelector == @selector(moveDown:)) { |
| 262 |
NSArray *a = [aTextView selectedRanges]; |
| 263 |
NSRange r; |
| 264 |
if ([a count] == 1 && (r = [[a objectAtIndex:0] rangeValue]).length == 0 && r.location == [[aTextView textStorage] length]) |
| 265 |
return [self showHistory:(aSelector == @selector(moveDown:) ? 1 : -1)]; |
| 266 |
else return NO; |
| 267 |
} |
| 268 |
return NO; |
| 269 |
} |
| 270 |
|
| 271 |
@end |
| 272 |
|
| 273 |
#pragma mark ====== Plain-C Interface ====== |
| 274 |
|
| 275 |
int |
| 276 |
MyAppCallback_showScriptMessage(const char *fmt, ...) |
| 277 |
{ |
| 278 |
RubyConsoleWindowController *cont = [RubyConsoleWindowController sharedRubyConsoleWindowController]; |
| 279 |
if (fmt != NULL) { |
| 280 |
char *p; |
| 281 |
va_list ap; |
| 282 |
int retval; |
| 283 |
va_start(ap, fmt); |
| 284 |
if (strchr(fmt, '%') == NULL) { |
| 285 |
/* No format characters */ |
| 286 |
return [cont appendMessage: [NSString stringWithUTF8String: fmt]]; |
| 287 |
} else if (strcmp(fmt, "%s") == 0) { |
| 288 |
/* Direct output of one string */ |
| 289 |
p = va_arg(ap, char *); |
| 290 |
return [cont appendMessage: [NSString stringWithUTF8String: p]]; |
| 291 |
} |
| 292 |
vasprintf(&p, fmt, ap); |
| 293 |
if (p != NULL) { |
| 294 |
retval = [cont appendMessage: [NSString stringWithUTF8String: p]]; |
| 295 |
free(p); |
| 296 |
return retval; |
| 297 |
} else return 0; |
| 298 |
} else { |
| 299 |
[cont flushMessage]; |
| 300 |
return 0; |
| 301 |
} |
| 302 |
} |
| 303 |
|
| 304 |
void |
| 305 |
MyAppCallback_setConsoleColor(int color) |
| 306 |
{ |
| 307 |
RubyConsoleWindowController *cont = [RubyConsoleWindowController sharedRubyConsoleWindowController]; |
| 308 |
[cont setConsoleColor: color]; |
| 309 |
} |
| 310 |
|
| 311 |
void |
| 312 |
MyAppCallback_showRubyPrompt(void) |
| 313 |
{ |
| 314 |
RubyConsoleWindowController *cont = [RubyConsoleWindowController sharedRubyConsoleWindowController]; |
| 315 |
[cont setConsoleColor: 0]; |
| 316 |
[cont showRubyPrompt]; |
| 317 |
} |
| 318 |
|