1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
3 * VIM - Vi IMproved by Bram Moolenaar
4 * MacVim GUI port by Bjorn Winckler
6 * Do ":help uganda" in Vim to read copying and usage conditions.
7 * Do ":help credits" in Vim to see a list of people who contributed.
8 * See README.txt for an overview of the Vim source code.
11 #import "MMTextView.h"
12 #import "MMTextStorage.h"
13 #import "MMWindowController.h"
14 #import "MMVimController.h"
19 @interface MMTextView (Private)
20 - (BOOL)convertPoint:(NSPoint)point toRow:(int *)row column:(int *)column;
21 - (void)dispatchKeyEvent:(NSEvent *)event;
22 - (MMVimController *)vimController;
27 @implementation MMTextView
31 [lastMouseDownEvent release];
35 - (NSEvent *)lastMouseDownEvent
37 return lastMouseDownEvent;
40 - (void)setShouldDrawInsertionPoint:(BOOL)enable
42 shouldDrawInsertionPoint = enable;
45 - (BOOL)shouldDrawInsertionPoint
47 return shouldDrawInsertionPoint;
50 - (void)insertText:(id)string
52 // NOTE! This method is called for normal key presses but also for
53 // Option-key presses --- even when Ctrl is held as well as Option. When
54 // Ctrl is held, the AppKit translates the character to a Ctrl+key stroke,
55 // so 'string' need not be a printable character! In this case it still
56 // works to pass 'string' on to Vim as a printable character (since
57 // modifiers are already included and should not be added to the input
58 // buffer using CSI, K_MODIFIER).
60 NSEvent *event = [NSApp currentEvent];
61 //NSLog(@"%s%@ (event=%@)", _cmd, string, event);
63 // HACK! In order to be able to bind to <S-Space> etc. we have to watch
64 // for when space was pressed.
65 if ([event type] == NSKeyDown
66 && [[event charactersIgnoringModifiers] length] > 0
67 && [[event charactersIgnoringModifiers] characterAtIndex:0] == ' '
68 && [event modifierFlags]
69 & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask))
71 [self dispatchKeyEvent:event];
75 [NSCursor setHiddenUntilMouseMoves:YES];
77 [[self vimController] sendMessage:InsertTextMsgID
78 data:[string dataUsingEncoding:NSUTF8StringEncoding]
83 - (void)doCommandBySelector:(SEL)selector
85 // By ignoring the selector we effectively disable the key binding
86 // mechanism of Cocoa. Hopefully this is what the user will expect
87 // (pressing Ctrl+P would otherwise result in moveUp: instead of previous
90 // We usually end up here if the user pressed Ctrl+key (but not
93 //NSLog(@"%s%@", _cmd, NSStringFromSelector(selector));
94 [self dispatchKeyEvent:[NSApp currentEvent]];
97 - (BOOL)performKeyEquivalent:(NSEvent *)event
99 // Called for Cmd+key keystrokes, function keys, arrow keys, page
100 // up/down, home, end.
102 if ([event type] != NSKeyDown)
105 // HACK! Let the main menu try to handle any key down event, before
106 // passing it on to vim, otherwise key equivalents for menus will
107 // effectively be disabled.
108 if ([[NSApp mainMenu] performKeyEquivalent:event])
111 // HACK! KeyCode 50 represent the key which switches between windows
112 // within an application (like Cmd+Tab is used to switch between
113 // applications). Return NO here, else the window switching does not work.
115 // Will this hack work for all languages / keyboard layouts?
116 if ([event keyCode] == 50)
119 //NSLog(@"%s%@", _cmd, event);
121 NSMutableData *data = [NSMutableData data];
122 NSString *string = [event charactersIgnoringModifiers];
123 int flags = [event modifierFlags];
124 int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
126 [data appendBytes:&flags length:sizeof(int)];
127 [data appendBytes:&len length:sizeof(int)];
128 [data appendBytes:[string UTF8String] length:len];
130 [[self vimController] sendMessage:CmdKeyMsgID data:data wait:NO];
135 - (void)setMarkedText:(id)text selectedRange:(NSRange)range
137 // TODO: Figure out a way to handle marked text, at the moment the user
138 // has no way of knowing what has been added so far in a multi-stroke key.
139 // E.g. hitting Option-e and then e will result in an 'e' with acute, but
140 // nothing is displayed immediately after hitting Option-e.
142 NSLog(@"setMarkedText:'%@' selectedRange:(%d,%d)", text, range.location,
146 - (void)scrollWheel:(NSEvent *)event
148 if ([event deltaY] == 0)
152 NSPoint pt = [self convertPoint:[event locationInWindow] fromView:nil];
153 if (![self convertPoint:pt toRow:&row column:&col])
156 int flags = [event modifierFlags];
157 float dy = [event deltaY];
158 NSMutableData *data = [NSMutableData data];
160 [data appendBytes:&row length:sizeof(int)];
161 [data appendBytes:&col length:sizeof(int)];
162 [data appendBytes:&flags length:sizeof(int)];
163 [data appendBytes:&dy length:sizeof(float)];
165 [[self vimController] sendMessage:ScrollWheelMsgID data:data wait:NO];
168 - (void)mouseDown:(NSEvent *)event
171 NSPoint pt = [self convertPoint:[event locationInWindow] fromView:nil];
172 if (![self convertPoint:pt toRow:&row column:&col])
175 lastMouseDownEvent = [event copy];
177 int button = [event buttonNumber];
178 int flags = [event modifierFlags];
179 int count = [event clickCount];
180 NSMutableData *data = [NSMutableData data];
182 // If desired, intepret Ctrl-Click as a right mouse click.
183 if ([[NSUserDefaults standardUserDefaults]
184 boolForKey:MMTranslateCtrlClickKey]
185 && button == 0 && flags & NSControlKeyMask) {
187 flags &= ~NSControlKeyMask;
190 [data appendBytes:&row length:sizeof(int)];
191 [data appendBytes:&col length:sizeof(int)];
192 [data appendBytes:&button length:sizeof(int)];
193 [data appendBytes:&flags length:sizeof(int)];
194 [data appendBytes:&count length:sizeof(int)];
196 [[self vimController] sendMessage:MouseDownMsgID data:data wait:NO];
199 - (void)rightMouseDown:(NSEvent *)event
201 [self mouseDown:event];
204 - (void)otherMouseDown:(NSEvent *)event
206 [self mouseDown:event];
209 - (void)mouseUp:(NSEvent *)event
212 NSPoint pt = [self convertPoint:[event locationInWindow] fromView:nil];
213 if (![self convertPoint:pt toRow:&row column:&col])
216 int flags = [event modifierFlags];
217 NSMutableData *data = [NSMutableData data];
219 [data appendBytes:&row length:sizeof(int)];
220 [data appendBytes:&col length:sizeof(int)];
221 [data appendBytes:&flags length:sizeof(int)];
223 [[self vimController] sendMessage:MouseUpMsgID data:data wait:NO];
226 - (void)rightMouseUp:(NSEvent *)event
228 [self mouseUp:event];
231 - (void)otherMouseUp:(NSEvent *)event
233 [self mouseUp:event];
236 - (void)mouseDragged:(NSEvent *)event
239 NSPoint pt = [self convertPoint:[event locationInWindow] fromView:nil];
240 if (![self convertPoint:pt toRow:&row column:&col])
243 int flags = [event modifierFlags];
244 NSMutableData *data = [NSMutableData data];
246 [data appendBytes:&row length:sizeof(int)];
247 [data appendBytes:&col length:sizeof(int)];
248 [data appendBytes:&flags length:sizeof(int)];
250 [[self vimController] sendMessage:MouseDraggedMsgID data:data wait:NO];
253 - (void)rightMouseDragged:(NSEvent *)event
255 [self mouseDragged:event];
258 - (void)otherMouseDragged:(NSEvent *)event
260 [self mouseDragged:event];
263 - (NSMenu*)menuForEvent:(NSEvent *)event
265 // HACK! Return nil to disable NSTextView's popup menus (Vim provides its
266 // own). Called when user Ctrl-clicks in the view (this is already handled
267 // in rightMouseDown:).
271 - (NSArray *)acceptableDragTypes
273 return [NSArray arrayWithObjects:NSFilenamesPboardType,
274 NSStringPboardType, nil];
277 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
279 NSPasteboard *pboard = [sender draggingPasteboard];
281 if ([[pboard types] containsObject:NSFilenamesPboardType]) {
282 NSArray *files = [pboard propertyListForType:NSFilenamesPboardType];
283 int i, numberOfFiles = [files count];
284 NSMutableData *data = [NSMutableData data];
286 [data appendBytes:&numberOfFiles length:sizeof(int)];
290 NSPoint pt = [self convertPoint:[sender draggingLocation] fromView:nil];
291 if (![self convertPoint:pt toRow:&row column:&col])
294 [data appendBytes:&row length:sizeof(int)];
295 [data appendBytes:&col length:sizeof(int)];
298 for (i = 0; i < numberOfFiles; ++i) {
299 NSString *file = [files objectAtIndex:i];
300 int len = [file lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
303 ++len; // append NUL as well
304 [data appendBytes:&len length:sizeof(int)];
305 [data appendBytes:[file UTF8String] length:len];
309 [[self vimController] sendMessage:DropFilesMsgID data:data wait:NO];
311 } else if ([[pboard types] containsObject:NSStringPboardType]) {
312 NSString *string = [pboard stringForType:NSStringPboardType];
313 NSMutableData *data = [NSMutableData data];
314 int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
316 [data appendBytes:&len length:sizeof(int)];
317 [data appendBytes:[string UTF8String] length:len];
319 [[self vimController] sendMessage:DropStringMsgID data:data wait:NO];
326 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
328 NSDragOperation sourceDragMask = [sender draggingSourceOperationMask];
329 NSPasteboard *pboard = [sender draggingPasteboard];
331 if ( [[pboard types] containsObject:NSFilenamesPboardType]
332 && (sourceDragMask & NSDragOperationCopy) )
333 return NSDragOperationCopy;
334 if ( [[pboard types] containsObject:NSStringPboardType]
335 && (sourceDragMask & NSDragOperationCopy) )
336 return NSDragOperationCopy;
338 return NSDragOperationNone;
341 - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
343 NSDragOperation sourceDragMask = [sender draggingSourceOperationMask];
344 NSPasteboard *pboard = [sender draggingPasteboard];
346 if ( [[pboard types] containsObject:NSFilenamesPboardType]
347 && (sourceDragMask & NSDragOperationCopy) )
348 return NSDragOperationCopy;
349 if ( [[pboard types] containsObject:NSStringPboardType]
350 && (sourceDragMask & NSDragOperationCopy) )
351 return NSDragOperationCopy;
353 return NSDragOperationNone;
356 - (void)changeFont:(id)sender
358 MMTextStorage *ts = (MMTextStorage*)[self textStorage];
361 NSFont *oldFont = [ts font];
362 NSFont *newFont = [sender convertFont:oldFont];
365 NSString *name = [newFont displayName];
366 unsigned len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
368 NSMutableData *data = [NSMutableData data];
369 float pointSize = [newFont pointSize];
371 [data appendBytes:&pointSize length:sizeof(float)];
373 ++len; // include NUL byte
374 [data appendBytes:&len length:sizeof(unsigned)];
375 [data appendBytes:[name UTF8String] length:len];
377 [[self vimController] sendMessage:SetFontMsgID data:data wait:NO];
387 @implementation MMTextView (Private)
389 - (BOOL)convertPoint:(NSPoint)point toRow:(int *)row column:(int *)column
391 NSLayoutManager *lm = [self layoutManager];
392 NSTextContainer *tc = [self textContainer];
393 MMTextStorage *ts = (MMTextStorage*)[self textStorage];
395 if (!(lm && tc && ts))
398 unsigned glyphIdx = [lm glyphIndexForPoint:point inTextContainer:tc];
399 unsigned charIdx = [lm characterIndexForGlyphAtIndex:glyphIdx];
401 int mod = [ts maxColumns] + 1;
403 if (row) *row = (int)(charIdx / mod);
404 if (column) *column = (int)(charIdx % mod);
409 - (void)keyDown:(NSEvent *)event
411 // HACK! If a modifier is held, don't pass the event along to
412 // interpretKeyEvents: since some keys are bound to multiple commands which
413 // means doCommandBySelector: is called several times.
415 // TODO: Figure out a way to disable Cocoa key bindings entirely, without
416 // affecting input management.
418 if ([event modifierFlags] & NSControlKeyMask)
419 [self dispatchKeyEvent:event];
421 [super keyDown:event];
424 - (void)dispatchKeyEvent:(NSEvent *)event
426 // Only handle the command if it came from a keyDown event
427 if ([event type] != NSKeyDown)
430 NSString *chars = [event characters];
431 NSString *imchars = [event charactersIgnoringModifiers];
432 unichar c = [chars characterAtIndex:0];
433 unichar imc = [imchars characterAtIndex:0];
435 const char *bytes = 0;
437 //NSLog(@"%s chars=0x%x unmodchars=0x%x", _cmd, c, imc);
439 if (' ' == imc && 0xa0 != c) {
440 // HACK! The AppKit turns <C-Space> into <C-@> which is not standard
441 // Vim behaviour, so bypass this problem. (0xa0 is <M-Space>, which
442 // should be passed on as is.)
443 len = [imchars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
444 bytes = [imchars UTF8String];
445 } else if (imc == c && '2' == c) {
446 // HACK! Translate Ctrl+2 to <C-@>.
447 static char ctrl_at = 0;
448 len = 1; bytes = &ctrl_at;
449 } else if (imc == c && '6' == c) {
450 // HACK! Translate Ctrl+6 to <C-^>.
451 static char ctrl_hat = 0x1e;
452 len = 1; bytes = &ctrl_hat;
453 } else if (c == 0x19 && imc == 0x19) {
454 // HACK! AppKit turns back tab into Ctrl-Y, so we need to handle it
455 // separately (else Ctrl-Y doesn't work).
456 static char back_tab[2] = { 'k', 'B' };
457 len = 2; bytes = back_tab;
458 } else if (c == 0x3 && imc == 0x3) {
459 // HACK! AppKit turns enter (not return) into Ctrl-C, so we need to
460 // handle it separately (else Ctrl-C doesn't work).
461 static char enter[2] = { 'K', 'A' };
462 len = 2; bytes = enter;
463 } else if (c == 0x3 && imc == 0x63) {
464 // HACK! Intercept Ctrl-C and send SIGINT to Vim.
465 int pid = [[self vimController] pid];
471 len = [chars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
472 bytes = [chars UTF8String];
475 if (len > 0 && bytes) {
476 NSMutableData *data = [NSMutableData data];
477 int flags = [event modifierFlags];
479 [data appendBytes:&flags length:sizeof(int)];
480 [data appendBytes:&len length:sizeof(int)];
481 [data appendBytes:bytes length:len];
483 [NSCursor setHiddenUntilMouseMoves:YES];
485 //NSLog(@"%s len=%d bytes=0x%x", _cmd, len, bytes[0]);
486 [[self vimController] sendMessage:KeyDownMsgID data:data wait:NO];
490 - (MMVimController *)vimController
492 id windowController = [[self window] windowController];
494 // TODO: Make sure 'windowController' is a MMWindowController before type
496 return [(MMWindowController*)windowController vimController];
499 @end // MMTextView (Private)