Initial revision
[TestXSLT.git] / XMLTextView.m
1 //
2 //  XMLTextView.m
3 //  TestXSLT
4 //
5 //  Created by Marc Liyanage on Fri Aug 02 2002.
6 //  Copyright (c) 2002 __MyCompanyName__. All rights reserved.
7 //
8
9 #import "XMLTextView.h"
10
11
12 @implementation XMLTextView
13
14
15 - (id)init {
16
17         if (self = [super init]) {
18                 [self setRichText:NO];
19         }
20
21         return self;
22
23 }
24
25
26
27 -(void)selectLineByNumber:(int)line {
28
29         NSString *data = [self string];
30         unsigned int i, startIndex, lineEndIndex;
31         NSRange aRange;
32
33         if (line == 0) {
34                 return;
35         }
36
37
38         aRange.location = 0;
39         aRange.length = 0;
40
41         for (i = 1; i <= line; i++) {
42
43                 [data getLineStart:&startIndex end:&lineEndIndex contentsEnd:nil forRange:aRange];
44                 aRange.location = lineEndIndex;
45
46         }
47
48         aRange.location = startIndex;
49         aRange.length = lineEndIndex - startIndex;
50
51         [self setSelectedRange:aRange];
52         [self scrollRangeToVisible:aRange];
53
54         
55 }
56
57
58
59 -(void)pasteAsRichText:(id)sender {
60
61
62         NSLog(@"paste as rich text");
63         //      [self pasteAsPlainText:sender];
64
65 }
66
67
68
69
70 - (void)keyDown:(NSEvent *)event {
71
72         NSRange selectedRange;
73         
74         if ([[event characters] isEqual:@"\033"]) {
75
76                 if ([event modifierFlags] || [event isARepeat]) {
77                         return;
78                 }
79                 [self complete:nil];
80                 return;
81
82         } else if ([[event characters] isEqual:@"/"]) {
83
84                 selectedRange = [self selectedRange];
85                 if (selectedRange.location < 2 || selectedRange.length > 0) {
86                         [super keyDown:event];
87                         return;
88                 }
89
90                 if ([[[self string] substringWithRange:NSMakeRange(selectedRange.location - 1, 1)] isEqual:@"<"]) {
91                         if([self completeAfterSlash]) {
92                                 return;
93                         }
94                 }
95         }
96
97         [super keyDown:event];
98
99 }
100
101
102
103 - (BOOL)completeAfterSlash {
104
105         NSRange selectedRange = [self selectedRange], tagNameRange;
106         NSString *tagName = nil;
107
108         tagNameRange = [self scanBackwardsForOpeningTagNameInRange:NSMakeRange(0, selectedRange.location - 1)];
109
110         if (tagNameRange.location == NSNotFound) {
111                 return NO;
112         }
113
114         tagName = [[self string] substringWithRange:tagNameRange];
115         
116 //      NSLog(@"completion!");
117
118         if (tagName == nil) {
119                 return NO;
120         }
121
122         [self insertText:[NSString stringWithFormat:@"/%@>", tagName]];
123
124         [self flashRange:tagNameRange];
125                 
126         return YES;
127 }
128
129
130 - (void)flashRange:(NSRange)range {
131
132         NSRect tagNameRect;
133
134         tagNameRect = [self firstRectForCharacterRange:range];
135
136         tagNameRect.origin = [[self window] convertScreenToBase:tagNameRect.origin];
137
138         tagNameRect = [self convertRect:tagNameRect fromView:[[self window] contentView]];
139
140
141         [self lockFocus];
142         [[[NSColor selectedControlColor] colorWithAlphaComponent:0.75] set];
143 /*
144         NSFrameRectWithWidth(tagNameRect, 2);
145  */
146         [NSBezierPath fillRect:tagNameRect];
147         [self unlockFocus];
148         [[self window] flushWindow];
149
150         usleep(100000);
151
152         [self setNeedsDisplay:YES];
153
154         
155 }
156
157
158
159 - (void)complete:(id)sender {
160
161         NSRange selectedRange, leftRange, rightRange, tagNameRange;
162         NSString *tagName = nil;
163         NSString *data = nil;
164         int location;
165
166         data = [self string];
167
168         selectedRange = [self selectedRange];
169         location = selectedRange.location;
170
171         /* May not have a selection, and insertion point must be preceded by at
172                 * least one tag, which means at least 3 characters must be to its left
173                 */
174         if (selectedRange.length > 0 || location < 3) {
175                 NSBeep();
176                 return;
177         }
178
179         /* leftRange is everything from beginning of data to the insertion point.
180                 * rightRange is everything after the insertion point to the end of the data
181                 */
182         leftRange  = NSMakeRange(0, location);
183         rightRange = NSMakeRange(location, [data length] - leftRange.length);
184
185
186
187         tagNameRange = [self scanBackwardsForOpeningTagNameInRange:leftRange];
188
189         if (tagNameRange.location == NSNotFound) {
190                 NSBeep();
191                 return;
192         }
193
194         tagName = [data substringWithRange:tagNameRange];
195         
196         [self insertText:[NSString stringWithFormat:@"</%@>", tagName]];
197         [self setSelectedRange:selectedRange];
198
199         [self flashRange:tagNameRange];
200         
201 }
202
203
204
205 -(NSRange)scanBackwardsForOpeningTagNameInRange:(NSRange)scanRange {
206
207         NSRange tagNameEndRange, tagContentPlusEndRange, leftAngleRange, openingTagRange, slashRange;
208         NSCharacterSet *angleSet = [NSCharacterSet characterSetWithCharactersInString:@"<>"];
209         NSCharacterSet *tagNameDelimiterSet = [NSCharacterSet characterSetWithCharactersInString:@" <>"];
210         NSString *tagName = nil;
211         NSString *data = [self string];
212         NSString *character;
213         NSRange tagNameRange;
214
215         
216         /* find the first occurence of angle brackets, both opening or closing,
217         * in the range to the left of the insertion point, searching backwards
218         * from the insertion point
219         */
220         leftAngleRange = [data rangeOfCharacterFromSet:angleSet options:NSBackwardsSearch range:scanRange];
221
222         /* abort unless we found something */
223         if (leftAngleRange.location == NSNotFound) {
224                 return NSMakeRange(NSNotFound, 0);
225         }
226
227         //      NSLog(@"leftbracket: %d %d", leftAngleRange.location, leftAngleRange.length);
228
229
230         /* We expect the nearest angle bracket to our left to be a closing one, we will
231                 * not try to complete an open tag if we find an opening bracket instead.
232                 */
233         character = [data substringWithRange:leftAngleRange];
234         if (![character isEqual:@">"]) {
235                 return NSMakeRange(NSNotFound, 0);
236         }
237
238         /* shift end of scanRange so it ends after the closing angle bracket we just found */
239         scanRange = NSMakeRange(0, leftAngleRange.location);
240
241
242         /* Now search again from the end of that range backward to the beginning of the data
243                 * and look for another angle bracket.
244                 */
245         leftAngleRange = [data rangeOfCharacterFromSet:angleSet options:NSBackwardsSearch range:NSMakeRange(0, scanRange.length - 1)];
246
247         /* again abort unless we found something */
248         if (leftAngleRange.location == NSNotFound) {
249                 return NSMakeRange(NSNotFound, 0);
250         }
251
252         //NSLog(@"leftbracket: %d %d", leftAngleRange.location, leftAngleRange.length);
253
254         /* This time we expect to see an opening angle bracket */
255         character = [data substringWithRange:leftAngleRange];
256         if (![character isEqual:@"<"]) {
257                 return NSMakeRange(NSNotFound, 0);
258         }
259
260
261         /* This will indicate the range of the entire nearest tag to our left,
262                 * we assume that's the opening tag */
263         openingTagRange = NSMakeRange(leftAngleRange.location, (scanRange.length - leftAngleRange.location) + 1);
264
265         /* check if it is indeed an opening tag, i.e. no slash after the opening bracket */
266         slashRange = NSMakeRange(openingTagRange.location + 1, 1);
267         character = [data substringWithRange:slashRange];
268         if ([character isEqual:@"/"]) {
269                 return NSMakeRange(NSNotFound, 0);
270         }
271
272         /* Don't complete processing instructions, comments etc. */
273         if ([character isEqual:@"?"] || [character isEqual:@"!"]) {
274                 return NSMakeRange(NSNotFound, 0);
275         }
276         
277         
278         /* check if it is a merged start and end tag, i.e. slash before closing bracket */
279         slashRange = NSMakeRange((openingTagRange.location + openingTagRange.length) - 2, 1);
280         character = [data substringWithRange:slashRange];
281         if ([character isEqual:@"/"]) {
282                 return NSMakeRange(NSNotFound, 0);
283         }
284
285
286         tagContentPlusEndRange = NSMakeRange(openingTagRange.location + 1, openingTagRange.length - 1);
287
288         //NSLog(@"tagcontent: %d %d", tagContentPlusEndRange.location, tagContentPlusEndRange.length);
289
290         tagNameEndRange = [data rangeOfCharacterFromSet:tagNameDelimiterSet options:0 range:tagContentPlusEndRange];
291
292
293         tagNameRange = NSMakeRange(tagContentPlusEndRange.location, tagNameEndRange.location - tagContentPlusEndRange.location);
294
295         tagName = [data substringWithRange:tagNameRange];
296
297         if ([tagName length] < 1) {
298                 return NSMakeRange(NSNotFound, 0);
299         }
300
301
302         return tagNameRange;
303         
304         //NSLog(@"tagrange: %d %d / %@", tagNameEndRange.location, tagNameEndRange.length, tagName);
305
306         
307
308
309
310
311
312 }
313
314
315
316
317 @end