source: trunk/ICeCoffEE/ICeCoffEE/ICeCoffEETerminal.m@ 139

Last change on this file since 139 was 139, checked in by Nicholas Riley, 21 years ago
File size: 16.1 KB
Line 
1//
2// ICeCoffEETerminal.m
3// ICeCoffEE
4//
5// Created by Nicholas Riley on Mon Jan 28 2002.
6// Copyright (c) 2002 Nicholas Riley. All rights reserved.
7//
8
9#import "ICeCoffEETerminal.h"
10#import "ICeCoffEEScanner.h"
11#import "ICeCoffEE.h"
12#import <Carbon/Carbon.h>
13#include <unistd.h>
14
15static NSRange ICCF_zeroRange = { NSNotFound, 5 };
16
17#define min(a,b) (a < b ? a : b)
18#define max(a,b) (a > b ? a : b)
19
20@interface TermStorage:NSObject
21- (struct _FSelPt)selPt0;
22- (struct _FSelPt)selPt1;
23- (BOOL)hasSelection;
24- (void)startSelectionAtLine:(unsigned int)row column:(unsigned short)column;
25- (void)startSelectionAtLine:(unsigned int)row offset:(unsigned short)offset;
26- (void)endSelectionAtLine:(unsigned int)row offset:(unsigned short)offset;
27- (void)selectWhitespaceDelimitedTextAtLine:(unsigned int)line offset:(unsigned short)offset;
28- (void)selectWordAtLine:(unsigned int)line offset:(unsigned short)offset;
29- (NSString *)selectedString;
30- (unsigned int)numberOfLines;
31- (unsigned int)effectiveColumnsForLine:(unsigned int)line;
32- (void)clearSelection;
33- (BOOL)isSelected:(unsigned int)line :(unsigned int)column;
34@end
35
36@interface TermController:NSObject
37- (TermStorage *)storage;
38@end
39
40@implementation ICECoffEETermSubviewSuper
41// NSTextInput implementation
42- (void)insertText:(id)aString {}
43- (void)doCommandBySelector:(SEL)aSelector {}
44- (void)setMarkedText:(id)aString selectedRange:(NSRange)selRange {}
45- (void)unmarkText {}
46- (BOOL)hasMarkedText { return NO; }
47- (long)conversationIdentifier { return 0; }
48- (NSAttributedString *)attributedSubstringFromRange:(NSRange)theRange { return nil; }
49- (NSRange)markedRange { return ICCF_zeroRange; }
50- (NSRange)selectedRange { return ICCF_zeroRange; }
51- (NSRect)firstRectForCharacterRange:(NSRange)theRange { return NSZeroRect; }
52- (unsigned int)characterIndexForPoint:(NSPoint)thePoint { return 0; }
53- (NSArray*)validAttributesForMarkedText { return nil; }
54
55// NSDraggingDestination
56- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender { return NSDragOperationNone; }
57- (void)draggingExited:(id <NSDraggingInfo>)sender {}
58
59 // misc. other stuff
60- (void)_optionClickEvent:(NSEvent *)event:(unsigned int)row:(unsigned short)column {}
61- (void)setNeedsDisplay; {}
62
63@end
64
65@interface ICeCoffEETerminalRange : NSObject
66{
67 TermStorage *storage;
68 struct _FSelPt pt0;
69 struct _FSelPt pt1;
70 unsigned int width;
71 unsigned int height;
72}
73
74+ (ICeCoffEETerminalRange *)rangeWithTerminal:(ICeCoffEETerminal *)terminal;
75+ (ICeCoffEETerminalRange *)rangeWithTerminal:(ICeCoffEETerminal *)terminal pt0:(struct _FSelPt)selPt0 pt1:(struct _FSelPt)selPt1;
76
77- (id)initWithTerminal:(ICeCoffEETerminal *)terminal;
78- (id)initWithTerminal:(ICeCoffEETerminal *)terminal pt0:(struct _FSelPt)selPt0 pt1:(struct _FSelPt)selPt1;
79
80- (struct _FSelPt)pt0;
81- (struct _FSelPt)pt1;
82
83- (NSString *)stringFromRange;
84
85- (void)growBackwardByLength:(unsigned long)length;
86- (void)growForwardByLength:(unsigned long)length;
87
88- (void)shrinkBackByLength:(unsigned long)length;
89- (void)shrinkFrontByLength:(unsigned long)length;
90
91- (BOOL)rangeIsEmpty;
92
93- (void)select;
94
95@end
96
97@implementation ICeCoffEETerminalRange
98
99+ (ICeCoffEETerminalRange *)rangeWithTerminal:(ICeCoffEETerminal *)terminal;
100{
101 return [[[self alloc] initWithTerminal: terminal] autorelease];
102}
103
104+ (ICeCoffEETerminalRange *)rangeWithTerminal:(ICeCoffEETerminal *)terminal pt0:(struct _FSelPt)selPt0 pt1:(struct _FSelPt)selPt1;
105{
106 return [[[self alloc] initWithTerminal: terminal pt0: selPt0 pt1: selPt1] autorelease];
107}
108
109- (id)initWithTerminal:(ICeCoffEETerminal *)terminal;
110{
111 if ( (self = [self init]) != nil) {
112 storage = [(TermController *)[terminal valueForKey: @"controller"] storage];
113 pt0 = [storage selPt0];
114 pt1 = [storage selPt1];
115 width = [storage effectiveColumnsForLine: pt0.line];
116 height = [storage numberOfLines];
117 }
118 return self;
119}
120
121- (id)initWithTerminal:(ICeCoffEETerminal *)terminal pt0:(struct _FSelPt)selPt0 pt1:(struct _FSelPt)selPt1;
122{
123 if ( (self = [self initWithTerminal: terminal]) != nil) {
124 pt0 = selPt0;
125 pt1 = selPt1;
126 }
127 return self;
128}
129
130- (struct _FSelPt)pt0;
131{
132 return pt0;
133}
134
135- (struct _FSelPt)pt1;
136{
137 return pt1;
138}
139
140- (NSString *)stringFromRange;
141{
142 struct _FSelPt oldPt0 = [storage selPt0], oldPt1 = [storage selPt1];
143 NSString *str;
144
145 [storage startSelectionAtLine: pt0.line offset: pt0.col];
146 [storage endSelectionAtLine: pt1.line offset: pt1.col];
147
148 str = [storage selectedString];
149
150 [storage startSelectionAtLine: oldPt0.line offset: oldPt0.col];
151 [storage endSelectionAtLine: oldPt1.line offset: oldPt1.col];
152
153 return str;
154}
155
156- (struct _FSelPt)_moveBack:(struct _FSelPt)pt byLength:(unsigned long)length;
157{
158 unsigned int extraLines = length / width;
159 if ((long)(pt.line - extraLines) < 0) {
160 pt.line = 0;
161 pt.col = 0;
162 } else if (pt.line - extraLines == 0) {
163 pt.line = 0;
164 pt.col += width - (length % height);
165 if (pt.col < width) pt.col = 0;
166 else pt.col -= width;
167 } else {
168 pt.line -= extraLines;
169 pt.col += width - (length % height);
170 if (pt.col < width) pt.line--;
171 else pt.col -= width;
172 }
173 return pt;
174}
175
176- (struct _FSelPt)_moveForward:(struct _FSelPt)pt byLength:(unsigned long)length;
177{
178 unsigned int extraLines = length / width;
179 if (pt.line + extraLines >= height) {
180 pt.line = height - 1;
181 pt.col = width - 1;
182 } else if (pt.line + extraLines == height - 1) {
183 pt.line = height - 1;
184 pt.col += length % height;
185 if (pt.col >= width) pt.col = width - 1;
186 } else {
187 pt.line += extraLines;
188 pt.col = pt.col + (length % height);
189 if (pt.col > width) {
190 pt.line++;
191 pt.col -= width;
192 }
193 }
194 return pt;
195}
196
197- (BOOL)rangeIsEmpty;
198{
199 return (pt1.line < pt0.line) || (pt1.line == pt0.line && pt1.col <= pt0.col);
200}
201
202- (void)growBackwardByLength:(unsigned long)length;
203{
204 pt0 = [self _moveBack: pt0 byLength: length];
205}
206
207- (void)growForwardByLength:(unsigned long)length;
208{
209 pt1 = [self _moveForward: pt1 byLength: length];
210}
211
212- (void)shrinkFrontByLength:(unsigned long)length;
213{
214 pt0 = [self _moveForward: pt0 byLength: length];
215}
216
217- (void)shrinkBackByLength:(unsigned long)length;
218{
219 pt1 = [self _moveBack: pt1 byLength: length];
220}
221
222- (void)select;
223{
224 [storage startSelectionAtLine: pt0.line offset: pt0.col];
225 [storage endSelectionAtLine: pt1.line offset: pt1.col];
226}
227
228- (NSString *)description;
229{
230 return [NSString stringWithFormat: @"L%uC%hu - L%uC%hu: |%@|", pt0.line, pt0.col, pt1.line, pt1.col, [[self stringFromRange] stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]]];
231}
232
233@end
234
235// TermSubview's NSTextInput implementation is essentially a noop: make one that does something
236@implementation ICeCoffEETerminal // XXX normally in category (NSTextInput), but APE doesnÕt work if I do that
237
238- (NSRange)selectedRange {
239 TermStorage *storage = [(TermController *)[self valueForKey: @"controller"] storage];
240 struct _FSelPt selPt0 = [storage selPt0], selPt1 = [storage selPt1];
241 unsigned width = [storage effectiveColumnsForLine: selPt0.line];
242 unsigned pt0 = selPt0.line * width + selPt0.col;
243 ICLog(@"selPt0 %d x %d selPt1 %d x %d", selPt0.line, selPt0.col, selPt1.line, selPt1.col);
244 return NSMakeRange(pt0, selPt1.line * width + selPt1.col - pt0);
245}
246
247// XXX string isn't padded: what we need is something like the ICeCoffEE 1.2.x method, adapted to Terminal 1.2, but we've chosen another way
248- (NSAttributedString *)attributedSubstringFromRange:(NSRange)theRange;
249{
250 TermStorage *storage = [(TermController *)[self valueForKey: @"controller"] storage];
251 struct _FSelPt oldPt0 = [storage selPt0], oldPt1 = [storage selPt1], realPt0, realPt1;
252 unsigned pt1 = theRange.location + theRange.length;
253 unsigned width = [storage effectiveColumnsForLine: 0];
254 NSAttributedString *str;
255
256 realPt0.line = theRange.location / width;
257 realPt0.col = theRange.location % width;
258 realPt1.line = pt1 / width;
259 realPt1.col = pt1 % width;
260
261 [storage startSelectionAtLine: realPt0.line offset: realPt0.col];
262 [storage endSelectionAtLine: realPt1.line offset: realPt1.col];
263
264 str = [[NSAttributedString alloc] initWithString: [storage selectedString]];
265
266 NSAssert2([str length] == theRange.length, @"Substring has length %lu when we expected %lu", [str length], theRange.length);
267
268 [storage startSelectionAtLine: oldPt0.line offset: oldPt0.col];
269 [storage endSelectionAtLine: oldPt1.line offset: oldPt1.col];
270
271 return [str autorelease];
272}
273
274static NSEvent *ICCF_downEvent;
275static unsigned int ICCF_line;
276static unsigned short ICCF_col;
277static BOOL ICCF_optionClickRegistered;
278
279- (void)setSelectedRange:(NSRange)charRange affinity:(NSSelectionAffinity)affinity stillSelecting:(BOOL)stillSelectingFlag;
280{
281 TermStorage *storage = [(TermController *)[self valueForKey: @"controller"] storage];
282 unsigned width = [storage effectiveColumnsForLine: 0];
283 unsigned pt1 = charRange.location + charRange.length;
284 [storage startSelectionAtLine: charRange.location / width offset: charRange.location % width];
285 [storage endSelectionAtLine: pt1 / width offset: pt1 % width];
286 // [self refresh];
287}
288
289void ICCF_LaunchURLFromTerminal(ICeCoffEETerminal *self) {
290 NSCharacterSet *urlLeftDelimiters = nil, *urlRightDelimiters = nil;
291
292 ICeCoffEETerminalRange *termRange = nil, *selRange = nil;
293 NSString *s;
294 NSRange range, delimiterRange;
295
296 NS_DURING
297
298 TermStorage *storage = [(TermController *)[self valueForKey: @"controller"] storage];
299
300 if ([storage hasSelection] && [storage isSelected: ICCF_line : ICCF_col]) {
301 selRange = [ICeCoffEETerminalRange rangeWithTerminal: self];
302 } else { // select something
303 [storage selectWordAtLine: ICCF_line offset: ICCF_col];
304 selRange = [ICeCoffEETerminalRange rangeWithTerminal: self];
305 NSCAssert(![selRange rangeIsEmpty], ICCF_LocalizedString(@"Sorry, ICeCoffEE was unable to find anything to select"));
306 }
307
308 // However, word selection does not capture even the approximate boundaries of a URL
309 // (text to a space/line ending character); it'll stop at a period in the middle of a hostname.
310 // So, we expand it as follows:
311
312 ICCF_Delimiters(&urlLeftDelimiters, &urlRightDelimiters);
313
314 termRange = [ICeCoffEETerminalRange rangeWithTerminal: self pt0: [selRange pt0] pt1: [selRange pt0]];
315 // add 1 to range to trap delimiters that are on the edge of the selection (i.e., <...)
316 [termRange growForwardByLength: 1];
317 [termRange growBackwardByLength: ICCF_MAX_URL_LEN]; // potentially too big
318 s = [termRange stringFromRange];
319 ICLog(@"front %@", termRange);
320 delimiterRange = [s rangeOfCharacterFromSet: urlLeftDelimiters
321 options: NSLiteralSearch | NSBackwardsSearch];
322 if (delimiterRange.location == NSNotFound) {
323 // extend to beginning of string (as much as possible)
324 [selRange growBackwardByLength: [s length] - 1];
325 } else {
326 NSCAssert(delimiterRange.length == 1, @"Internal error: delimiter matched range is not of length 1");
327 [selRange growBackwardByLength: [s length] - delimiterRange.location - 2];
328 }
329
330 ICLog(@"parsed front %@", selRange);
331
332 termRange = [ICeCoffEETerminalRange rangeWithTerminal: self pt0: [selRange pt1] pt1: [selRange pt1]];
333 // subtract 1 from range to trap delimiters that are on the edge of the selection (i.e., ...>)
334 [termRange growBackwardByLength: 1];
335 [termRange growForwardByLength: ICCF_MAX_URL_LEN]; // potentially too big
336 s = [termRange stringFromRange];
337 ICLog(@"back %@", termRange);
338 delimiterRange = [s rangeOfCharacterFromSet: urlRightDelimiters
339 options: NSLiteralSearch];
340 if (delimiterRange.location == NSNotFound) {
341 // extend to end of string
342 [selRange growForwardByLength: [s length] - 1];
343 } else {
344 NSCAssert(delimiterRange.length == 1, @"Internal error: delimiter matched range is not of length 1");
345 [selRange growForwardByLength: delimiterRange.location - 1];
346 }
347
348 ICCF_StartIC();
349
350 s = [selRange stringFromRange];
351
352 range = NSMakeRange(0, [s length]);
353
354 ICCF_CheckRange(range);
355
356 ICLog(@"parsed back %@", selRange);
357 ICLog(@"range of string %@", NSStringFromRange(range));
358 ICCF_ParseURL(s, &range);
359 ICLog(@"parsed range %@ |%@|", NSStringFromRange(range), [s substringWithRange: range]);
360
361 [selRange shrinkFrontByLength: range.location];
362 [selRange shrinkBackByLength: [s length] - range.length - range.location];
363
364 s = [selRange stringFromRange];
365 ICLog(@"reconstituted URL %@", selRange);
366
367 [selRange select];
368 [self setNeedsDisplay];
369 [[self superview] display];
370
371 ICCF_LaunchURL(s, ICCF_KeyboardAction());
372
373 if (ICCF_prefs.textBlinkEnabled) {
374 int i;
375 // Terminal flashes the selection one more time, so blink one fewer
376 for (i = 1 ; i < ICCF_prefs.textBlinkCount ; i++) {
377 [storage clearSelection];
378 [self setNeedsDisplay];
379 [[self superview] display];
380 usleep(kICBlinkDelayUsecs);
381 [selRange select];
382 [self setNeedsDisplay];
383 [[self superview] display];
384 usleep(kICBlinkDelayUsecs);
385 }
386 }
387 NS_HANDLER
388 ICCF_HandleException(localException);
389 NS_ENDHANDLER
390
391 ICCF_StopIC();
392}
393
394- (void)_optionClickEvent:(NSEvent *)event:(unsigned int)row:(unsigned short)column;
395{
396 if (ICCF_downEvent != nil) {
397 ICCF_line = row; // XXX are these lines or rows? check with wrapping
398 ICCF_col = column;
399 ICCF_optionClickRegistered = YES;
400 } else {
401 [super _optionClickEvent: event :row :column];
402 }
403}
404
405- (void)mouseUp:(NSEvent *)upEvent;
406{
407 ICLog(@"ICeCoffEE Terminal up: %@", upEvent);
408 [super mouseUp: upEvent];
409 if (ICCF_downEvent != nil) {
410 NSPoint downPt = [ICCF_downEvent locationInWindow];
411 NSPoint upPt = [upEvent locationInWindow];
412 [ICCF_downEvent release];
413 ICCF_downEvent = nil;
414 if (abs(downPt.x - upPt.x) <= kICHysteresisPixels && abs(downPt.y - upPt.y) <= kICHysteresisPixels) {
415 if (ICCF_optionClickRegistered) {
416 ICCF_optionClickRegistered = NO;
417 ICLog(@"========= launching... %d x %d", ICCF_line, ICCF_col);
418 ICCF_LaunchURLFromTerminal(self);
419 }
420 }
421 }
422}
423
424- (void)mouseDown:(NSEvent *)downEvent;
425{
426 if (ICCF_enabled && ICCF_prefs.commandClickEnabled && ICCF_EventIsCommandMouseDown(downEvent)) {
427 NSEvent *optionClickEvent = [NSEvent mouseEventWithType: NSLeftMouseDown location: [downEvent locationInWindow] modifierFlags: NSAlternateKeyMask timestamp: [downEvent timestamp] windowNumber: [downEvent windowNumber] context: [downEvent context] eventNumber: [downEvent eventNumber] clickCount: 1 pressure: 0];
428 [ICCF_downEvent release];
429 ICCF_downEvent = [downEvent retain];
430 ICLog(@"ICeCoffEE Terminal constructed: %@", optionClickEvent);
431 ICCF_optionClickRegistered = NO;
432 [super mouseDown: optionClickEvent];
433 } else {
434 [super mouseDown: downEvent];
435 }
436}
437
438// NSDraggingDestination
439// -[TermSubview draggingUpdated:] just invokes draggingEntered...
440// XXX Crashing on repeated drag snap-back can happen even without ICeCoffEE installed; don't bother to try to fix
441- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender;
442{
443 if ([sender draggingSource] != self || ([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask)) {
444 [super draggingEntered: sender];
445 // When doing non-self drags, this works around one bug in Terminal wherein the option key acts as a toggle, and it shouldn't (see Aqua HIG). Unfortunately, this messes up drag feedback for self drags, but I don't know of any way to fix it. Not that most Cocoa apps get it remotely right, anyway.
446 return NSDragOperationCopy;
447 }
448 return NSDragOperationNone;
449}
450
451@end
Note: See TracBrowser for help on using the repository browser.