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 //NSLog(@"%s%@ (%x)", _cmd, string, [string characterAtIndex:0]);
62 NSEvent *event = [NSApp currentEvent];
63 if ([event type] == NSKeyDown) {
64 unsigned mods = [event modifierFlags];
65 unichar c = [[event charactersIgnoringModifiers] characterAtIndex:0];
67 if (mods & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask)
69 // HACK! In order to be able to bind to <S-Space> etc. we have to
70 // watch for when space was pressed.
71 [self dispatchKeyEvent:event];
76 [NSCursor setHiddenUntilMouseMoves:YES];
78 [[self vimController] sendMessage:InsertTextMsgID
79 data:[string dataUsingEncoding:NSUTF8StringEncoding]
84 - (void)doCommandBySelector:(SEL)selector
86 // By ignoring the selector we effectively disable the key binding
87 // mechanism of Cocoa. Hopefully this is what the user will expect
88 // (pressing Ctrl+P would otherwise result in moveUp: instead of previous
91 // We usually end up here if the user pressed Ctrl+key (but not
94 //NSLog(@"%s%@", _cmd, NSStringFromSelector(selector));
95 [self dispatchKeyEvent:[NSApp currentEvent]];
98 - (BOOL)performKeyEquivalent:(NSEvent *)event
100 // Called for Cmd+key keystrokes, function keys, arrow keys, page
101 // up/down, home, end.
103 if ([event type] != NSKeyDown)
106 // HACK! Let the main menu try to handle any key down event, before
107 // passing it on to vim, otherwise key equivalents for menus will
108 // effectively be disabled.
109 if ([[NSApp mainMenu] performKeyEquivalent:event])
112 // HACK! KeyCode 50 represent the key which switches between windows
113 // within an application (like Cmd+Tab is used to switch between
114 // applications). Return NO here, else the window switching does not work.
116 // Will this hack work for all languages / keyboard layouts?
117 if ([event keyCode] == 50)
120 //NSLog(@"%s%@", _cmd, event);
122 NSMutableData *data = [NSMutableData data];
123 NSString *string = [event charactersIgnoringModifiers];
124 int flags = [event modifierFlags];
125 int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
127 [data appendBytes:&flags length:sizeof(int)];
128 [data appendBytes:&len length:sizeof(int)];
129 [data appendBytes:[string UTF8String] length:len];
131 [[self vimController] sendMessage:CmdKeyMsgID data:data wait:NO];
136 - (void)setMarkedText:(id)text selectedRange:(NSRange)range
138 // TODO: Figure out a way to handle marked text, at the moment the user
139 // has no way of knowing what has been added so far in a multi-stroke key.
140 // E.g. hitting Option-e and then e will result in an 'e' with acute, but
141 // nothing is displayed immediately after hitting Option-e.
143 NSLog(@"setMarkedText:'%@' selectedRange:(%d,%d)", text, range.location,
147 - (void)scrollWheel:(NSEvent *)event
149 if ([event deltaY] == 0)
153 NSPoint pt = [self convertPoint:[event locationInWindow] fromView:nil];
154 if (![self convertPoint:pt toRow:&row column:&col])
157 int flags = [event modifierFlags];
158 float dy = [event deltaY];
159 NSMutableData *data = [NSMutableData data];
161 [data appendBytes:&row length:sizeof(int)];
162 [data appendBytes:&col length:sizeof(int)];
163 [data appendBytes:&flags length:sizeof(int)];
164 [data appendBytes:&dy length:sizeof(float)];
166 [[self vimController] sendMessage:ScrollWheelMsgID data:data wait:NO];
169 - (void)mouseDown:(NSEvent *)event
172 NSPoint pt = [self convertPoint:[event locationInWindow] fromView:nil];
173 if (![self convertPoint:pt toRow:&row column:&col])
176 lastMouseDownEvent = [event copy];
178 int button = [event buttonNumber];
179 int flags = [event modifierFlags];
180 int count = [event clickCount];
181 NSMutableData *data = [NSMutableData data];
183 // If desired, intepret Ctrl-Click as a right mouse click.
184 if ([[NSUserDefaults standardUserDefaults]
185 boolForKey:MMTranslateCtrlClickKey]
186 && button == 0 && flags & NSControlKeyMask) {
188 flags &= ~NSControlKeyMask;
191 [data appendBytes:&row length:sizeof(int)];
192 [data appendBytes:&col length:sizeof(int)];
193 [data appendBytes:&button length:sizeof(int)];
194 [data appendBytes:&flags length:sizeof(int)];
195 [data appendBytes:&count length:sizeof(int)];
197 [[self vimController] sendMessage:MouseDownMsgID data:data wait:NO];
200 - (void)rightMouseDown:(NSEvent *)event
202 [self mouseDown:event];
205 - (void)otherMouseDown:(NSEvent *)event
207 [self mouseDown:event];
210 - (void)mouseUp:(NSEvent *)event
213 NSPoint pt = [self convertPoint:[event locationInWindow] fromView:nil];
214 if (![self convertPoint:pt toRow:&row column:&col])
217 int flags = [event modifierFlags];
218 NSMutableData *data = [NSMutableData data];
220 [data appendBytes:&row length:sizeof(int)];
221 [data appendBytes:&col length:sizeof(int)];
222 [data appendBytes:&flags length:sizeof(int)];
224 [[self vimController] sendMessage:MouseUpMsgID data:data wait:NO];
227 - (void)rightMouseUp:(NSEvent *)event
229 [self mouseUp:event];
232 - (void)otherMouseUp:(NSEvent *)event
234 [self mouseUp:event];
237 - (void)mouseDragged:(NSEvent *)event
240 NSPoint pt = [self convertPoint:[event locationInWindow] fromView:nil];
241 if (![self convertPoint:pt toRow:&row column:&col])
244 int flags = [event modifierFlags];
245 NSMutableData *data = [NSMutableData data];
247 [data appendBytes:&row length:sizeof(int)];
248 [data appendBytes:&col length:sizeof(int)];
249 [data appendBytes:&flags length:sizeof(int)];
251 [[self vimController] sendMessage:MouseDraggedMsgID data:data wait:NO];
254 - (void)rightMouseDragged:(NSEvent *)event
256 [self mouseDragged:event];
259 - (void)otherMouseDragged:(NSEvent *)event
261 [self mouseDragged:event];
264 - (NSMenu*)menuForEvent:(NSEvent *)event
266 // HACK! Return nil to disable NSTextView's popup menus (Vim provides its
267 // own). Called when user Ctrl-clicks in the view (this is already handled
268 // in rightMouseDown:).
272 - (NSArray *)acceptableDragTypes
274 return [NSArray arrayWithObjects:NSFilenamesPboardType,
275 NSStringPboardType, nil];
278 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
280 NSPasteboard *pboard = [sender draggingPasteboard];
282 if ([[pboard types] containsObject:NSFilenamesPboardType]) {
283 NSArray *files = [pboard propertyListForType:NSFilenamesPboardType];
284 int i, numberOfFiles = [files count];
285 NSMutableData *data = [NSMutableData data];
287 [data appendBytes:&numberOfFiles length:sizeof(int)];
291 NSPoint pt = [self convertPoint:[sender draggingLocation] fromView:nil];
292 if (![self convertPoint:pt toRow:&row column:&col])
295 [data appendBytes:&row length:sizeof(int)];
296 [data appendBytes:&col length:sizeof(int)];
299 for (i = 0; i < numberOfFiles; ++i) {
300 NSString *file = [files objectAtIndex:i];
301 int len = [file lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
304 ++len; // append NUL as well
305 [data appendBytes:&len length:sizeof(int)];
306 [data appendBytes:[file UTF8String] length:len];
310 [[self vimController] sendMessage:DropFilesMsgID data:data wait:NO];
312 } else if ([[pboard types] containsObject:NSStringPboardType]) {
313 NSString *string = [pboard stringForType:NSStringPboardType];
314 NSMutableData *data = [NSMutableData data];
315 int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
317 [data appendBytes:&len length:sizeof(int)];
318 [data appendBytes:[string UTF8String] length:len];
320 [[self vimController] sendMessage:DropStringMsgID data:data wait:NO];
327 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
329 NSDragOperation sourceDragMask = [sender draggingSourceOperationMask];
330 NSPasteboard *pboard = [sender draggingPasteboard];
332 if ( [[pboard types] containsObject:NSFilenamesPboardType]
333 && (sourceDragMask & NSDragOperationCopy) )
334 return NSDragOperationCopy;
335 if ( [[pboard types] containsObject:NSStringPboardType]
336 && (sourceDragMask & NSDragOperationCopy) )
337 return NSDragOperationCopy;
339 return NSDragOperationNone;
342 - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
344 NSDragOperation sourceDragMask = [sender draggingSourceOperationMask];
345 NSPasteboard *pboard = [sender draggingPasteboard];
347 if ( [[pboard types] containsObject:NSFilenamesPboardType]
348 && (sourceDragMask & NSDragOperationCopy) )
349 return NSDragOperationCopy;
350 if ( [[pboard types] containsObject:NSStringPboardType]
351 && (sourceDragMask & NSDragOperationCopy) )
352 return NSDragOperationCopy;
354 return NSDragOperationNone;
357 - (void)changeFont:(id)sender
359 MMTextStorage *ts = (MMTextStorage*)[self textStorage];
362 NSFont *oldFont = [ts font];
363 NSFont *newFont = [sender convertFont:oldFont];
366 NSString *name = [newFont displayName];
367 unsigned len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
369 NSMutableData *data = [NSMutableData data];
370 float pointSize = [newFont pointSize];
372 [data appendBytes:&pointSize length:sizeof(float)];
374 ++len; // include NUL byte
375 [data appendBytes:&len length:sizeof(unsigned)];
376 [data appendBytes:[name UTF8String] length:len];
378 [[self vimController] sendMessage:SetFontMsgID data:data wait:NO];
388 @implementation MMTextView (Private)
390 - (BOOL)convertPoint:(NSPoint)point toRow:(int *)row column:(int *)column
392 NSLayoutManager *lm = [self layoutManager];
393 NSTextContainer *tc = [self textContainer];
394 MMTextStorage *ts = (MMTextStorage*)[self textStorage];
396 if (!(lm && tc && ts))
399 unsigned glyphIdx = [lm glyphIndexForPoint:point inTextContainer:tc];
400 unsigned charIdx = [lm characterIndexForGlyphAtIndex:glyphIdx];
402 int mod = [ts maxColumns] + 1;
404 if (row) *row = (int)(charIdx / mod);
405 if (column) *column = (int)(charIdx % mod);
410 - (void)keyDown:(NSEvent *)event
412 // HACK! If a modifier is held, don't pass the event along to
413 // interpretKeyEvents: since some keys are bound to multiple commands which
414 // means doCommandBySelector: is called several times.
416 // TODO: Figure out a way to disable Cocoa key bindings entirely, without
417 // affecting input management.
419 if ([event modifierFlags] & NSControlKeyMask)
420 [self dispatchKeyEvent:event];
422 [super keyDown:event];
425 - (void)dispatchKeyEvent:(NSEvent *)event
427 // Only handle the command if it came from a keyDown event
428 if ([event type] != NSKeyDown)
431 NSString *chars = [event characters];
432 NSString *imchars = [event charactersIgnoringModifiers];
433 unichar c = [chars characterAtIndex:0];
434 unichar imc = [imchars characterAtIndex:0];
436 const char *bytes = 0;
438 //NSLog(@"%s chars=0x%x unmodchars=0x%x", _cmd, c, imc);
440 if (' ' == imc && 0xa0 != c) {
441 // HACK! The AppKit turns <C-Space> into <C-@> which is not standard
442 // Vim behaviour, so bypass this problem. (0xa0 is <M-Space>, which
443 // should be passed on as is.)
444 len = [imchars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
445 bytes = [imchars UTF8String];
446 } else if (imc == c && '2' == c) {
447 // HACK! Translate Ctrl+2 to <C-@>.
448 static char ctrl_at = 0;
449 len = 1; bytes = &ctrl_at;
450 } else if (imc == c && '6' == c) {
451 // HACK! Translate Ctrl+6 to <C-^>.
452 static char ctrl_hat = 0x1e;
453 len = 1; bytes = &ctrl_hat;
454 } else if (c == 0x19 && imc == 0x19) {
455 // HACK! AppKit turns back tab into Ctrl-Y, so we need to handle it
456 // separately (else Ctrl-Y doesn't work).
457 static char back_tab[2] = { 'k', 'B' };
458 len = 2; bytes = back_tab;
459 } else if (c == 0x3 && imc == 0x3) {
460 // HACK! AppKit turns enter (not return) into Ctrl-C, so we need to
461 // handle it separately (else Ctrl-C doesn't work).
462 static char enter[2] = { 'K', 'A' };
463 len = 2; bytes = enter;
464 } else if (c == 0x3 && imc == 0x63) {
465 // HACK! Intercept Ctrl-C and send SIGINT to Vim.
466 int pid = [[self vimController] pid];
472 len = [chars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
473 bytes = [chars UTF8String];
476 if (len > 0 && bytes) {
477 NSMutableData *data = [NSMutableData data];
478 int flags = [event modifierFlags];
480 [data appendBytes:&flags length:sizeof(int)];
481 [data appendBytes:&len length:sizeof(int)];
482 [data appendBytes:bytes length:len];
484 [NSCursor setHiddenUntilMouseMoves:YES];
486 //NSLog(@"%s len=%d bytes=0x%x", _cmd, len, bytes[0]);
487 [[self vimController] sendMessage:KeyDownMsgID data:data wait:NO];
491 - (MMVimController *)vimController
493 id windowController = [[self window] windowController];
495 // TODO: Make sure 'windowController' is a MMWindowController before type
497 return [(MMWindowController*)windowController vimController];
500 @end // MMTextView (Private)