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.
13 * Contains code shared between the different text renderers. Unfortunately it
14 * is not possible to let the text renderers inherit from this class since
15 * MMTextView needs to inherit from NSTextView whereas MMAtsuiTextView needs to
16 * inherit from NSView.
19 #import "MMTextView.h"
20 #import "MMTextViewHelper.h"
21 #import "MMVimController.h"
22 #import "MMWindowController.h"
23 #import "Miscellaneous.h"
26 static char MMKeypadEnter[2] = { 'K', 'A' };
27 static NSString *MMKeypadEnterString = @"KA";
29 // The max/min drag timer interval in seconds
30 static NSTimeInterval MMDragTimerMaxInterval = 0.3;
31 static NSTimeInterval MMDragTimerMinInterval = 0.01;
33 // The number of pixels in which the drag timer interval changes
34 static float MMDragAreaSize = 73.0f;
37 @interface MMTextViewHelper (Private)
38 - (MMWindowController *)windowController;
39 - (MMVimController *)vimController;
40 - (void)dispatchKeyEvent:(NSEvent *)event;
41 - (void)sendKeyDown:(const char *)chars length:(int)len modifiers:(int)flags
42 isARepeat:(BOOL)isARepeat;
43 - (void)hideMouseCursor;
44 - (void)startDragTimerWithInterval:(NSTimeInterval)t;
45 - (void)dragTimerFired:(NSTimer *)timer;
47 - (NSRect)trackingRect;
51 @implementation MMTextViewHelper
55 [markedText release]; markedText = nil;
60 - (void)setTextView:(id)view
62 // Only keep a weak reference to owning text view.
66 - (void)setInsertionPointColor:(NSColor *)color
68 if (color != insertionPointColor) {
69 [insertionPointColor release];
70 insertionPointColor = [color retain];
74 - (NSColor *)insertionPointColor
76 return insertionPointColor;
79 - (void)keyDown:(NSEvent *)event
81 //NSLog(@"%s %@", _cmd, event);
82 // HACK! If control modifier is held, don't pass the event along to
83 // interpretKeyEvents: since some keys are bound to multiple commands which
84 // means doCommandBySelector: is called several times. Do the same for
85 // Alt+Function key presses (Alt+Up and Alt+Down are bound to two
86 // commands). This hack may break input management, but unless we can
87 // figure out a way to disable key bindings there seems little else to do.
89 // TODO: Figure out a way to disable Cocoa key bindings entirely, without
90 // affecting input management.
92 // When the Input Method is activated, some special key inputs
93 // should be treated as key inputs for Input Method.
94 if ([textView hasMarkedText]) {
95 [textView interpretKeyEvents:[NSArray arrayWithObject:event]];
96 [textView setNeedsDisplay:YES];
100 int flags = [event modifierFlags];
101 if ((flags & NSControlKeyMask) ||
102 ((flags & NSAlternateKeyMask) && (flags & NSFunctionKeyMask))) {
103 NSString *unmod = [event charactersIgnoringModifiers];
104 if ([unmod length] == 1 && [unmod characterAtIndex:0] <= 0x7f
105 && [unmod characterAtIndex:0] >= 0x60) {
106 // HACK! Send Ctrl-letter keys (and C-@, C-[, C-\, C-], C-^, C-_)
107 // as normal text to be added to the Vim input buffer. This must
108 // be done in order for the backend to be able to separate e.g.
109 // Ctrl-i and Ctrl-tab.
110 [self insertText:[event characters]];
112 [self dispatchKeyEvent:event];
114 } else if ((flags & NSAlternateKeyMask) &&
115 [[[[self vimController] vimState] objectForKey:@"p_mmta"]
117 // If the 'macmeta' option is set, then send Alt+key presses directly
118 // to Vim without interpreting the key press.
119 NSString *unmod = [event charactersIgnoringModifiers];
120 int len = [unmod lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
121 const char *bytes = [unmod UTF8String];
123 [self sendKeyDown:bytes length:len modifiers:flags
124 isARepeat:[event isARepeat]];
126 [textView interpretKeyEvents:[NSArray arrayWithObject:event]];
130 - (void)insertText:(id)string
132 //NSLog(@"%s %@", _cmd, string);
133 // NOTE! This method is called for normal key presses but also for
134 // Option-key presses --- even when Ctrl is held as well as Option. When
135 // Ctrl is held, the AppKit translates the character to a Ctrl+key stroke,
136 // so 'string' need not be a printable character! In this case it still
137 // works to pass 'string' on to Vim as a printable character (since
138 // modifiers are already included and should not be added to the input
139 // buffer using CSI, K_MODIFIER).
141 if ([textView hasMarkedText]) {
142 [textView unmarkText];
145 NSEvent *event = [NSApp currentEvent];
147 // HACK! In order to be able to bind to <S-Space>, <S-M-Tab>, etc. we have
148 // to watch for them here.
149 if ([event type] == NSKeyDown
150 && [[event charactersIgnoringModifiers] length] > 0
151 && [event modifierFlags]
152 & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask)) {
153 unichar c = [[event charactersIgnoringModifiers] characterAtIndex:0];
155 // <S-M-Tab> translates to 0x19
156 if (' ' == c || 0x19 == c) {
157 [self dispatchKeyEvent:event];
162 [self hideMouseCursor];
164 // NOTE: 'string' is either an NSString or an NSAttributedString. Since we
165 // do not support attributes, simply pass the corresponding NSString in the
167 if ([string isKindOfClass:[NSAttributedString class]])
168 string = [string string];
170 //NSLog(@"send InsertTextMsgID: %@", string);
172 NSMutableData *data = [NSMutableData data];
173 int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
174 int flags = [event modifierFlags] & 0xffff0000U;
175 if ([event type] == NSKeyDown && [event isARepeat])
178 [data appendBytes:&flags length:sizeof(int)];
179 [data appendBytes:&len length:sizeof(int)];
180 [data appendBytes:[string UTF8String] length:len];
182 [[self vimController] sendMessage:InsertTextMsgID data:data];
185 - (void)doCommandBySelector:(SEL)selector
187 //NSLog(@"%s %@", _cmd, NSStringFromSelector(selector));
188 // By ignoring the selector we effectively disable the key binding
189 // mechanism of Cocoa. Hopefully this is what the user will expect
190 // (pressing Ctrl+P would otherwise result in moveUp: instead of previous
193 // We usually end up here if the user pressed Ctrl+key (but not
196 NSEvent *event = [NSApp currentEvent];
198 if (selector == @selector(cancelOperation:)
199 || selector == @selector(insertNewline:)) {
200 // HACK! If there was marked text which got abandoned as a result of
201 // hitting escape or enter, then 'insertText:' is called with the
202 // abandoned text but '[event characters]' includes the abandoned text
203 // as well. Since 'dispatchKeyEvent:' looks at '[event characters]' we
204 // must intercept these keys here or the abandonded text gets inserted
206 NSString *key = [event charactersIgnoringModifiers];
207 const char *chars = [key UTF8String];
208 int len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
210 if (0x3 == chars[0]) {
211 // HACK! AppKit turns enter (not return) into Ctrl-C, so we need to
212 // handle it separately (else Ctrl-C doesn't work).
213 len = sizeof(MMKeypadEnter)/sizeof(MMKeypadEnter[0]);
214 chars = MMKeypadEnter;
217 [self sendKeyDown:chars length:len modifiers:[event modifierFlags]
218 isARepeat:[event isARepeat]];
220 [self dispatchKeyEvent:event];
224 - (BOOL)performKeyEquivalent:(NSEvent *)event
226 //NSLog(@"%s %@", _cmd, event);
227 // Called for Cmd+key keystrokes, function keys, arrow keys, page
228 // up/down, home, end.
230 // NOTE: This message cannot be ignored since Cmd+letter keys never are
231 // passed to keyDown:. It seems as if the main menu consumes Cmd-key
232 // strokes, unless the key is a function key.
234 // NOTE: If the event that triggered this method represents a function key
235 // down then we do nothing, otherwise the input method never gets the key
236 // stroke (some input methods use e.g. arrow keys). The function key down
237 // event will still reach Vim though (via keyDown:). The exceptions to
238 // this rule are: PageUp/PageDown (keycode 116/121).
239 int flags = [event modifierFlags] & 0xffff0000U;
240 if ([event type] != NSKeyDown || flags & NSFunctionKeyMask
241 && !(116 == [event keyCode] || 121 == [event keyCode]))
244 // HACK! KeyCode 50 represent the key which switches between windows
245 // within an application (like Cmd+Tab is used to switch between
246 // applications). Return NO here, else the window switching does not work.
247 if ([event keyCode] == 50)
250 // HACK! Let the main menu try to handle any key down event, before
251 // passing it on to vim, otherwise key equivalents for menus will
252 // effectively be disabled.
253 if ([[NSApp mainMenu] performKeyEquivalent:event])
256 // HACK! On Leopard Ctrl-key events end up here instead of keyDown:.
257 if (flags & NSControlKeyMask) {
258 [self keyDown:event];
262 // HACK! Don't handle Cmd-? or the "Help" menu does not work on Leopard.
263 NSString *unmodchars = [event charactersIgnoringModifiers];
264 if ([unmodchars isEqual:@"?"])
267 // Cmd-. is hard-wired to send an interrupt (like Ctrl-C).
268 if ((flags & NSDeviceIndependentModifierFlagsMask) == NSCommandKeyMask &&
269 [unmodchars isEqual:@"."]) {
270 [[self vimController] sendMessage:InterruptMsgID data:nil];
274 //NSLog(@"%s%@", _cmd, event);
276 NSString *chars = [event characters];
277 int len = [unmodchars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
278 NSMutableData *data = [NSMutableData data];
283 // If 'chars' and 'unmodchars' differs when shift flag is present, then we
284 // can clear the shift flag as it is already included in 'unmodchars'.
285 // Failing to clear the shift flag means <D-Bar> turns into <S-D-Bar> (on
286 // an English keyboard).
287 if (flags & NSShiftKeyMask && ![chars isEqual:unmodchars])
288 flags &= ~NSShiftKeyMask;
290 if (0x3 == [unmodchars characterAtIndex:0]) {
291 // HACK! AppKit turns enter (not return) into Ctrl-C, so we need to
292 // handle it separately (else Cmd-enter turns into Ctrl-C).
293 unmodchars = MMKeypadEnterString;
294 len = [unmodchars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
297 if ([event isARepeat])
300 [data appendBytes:&flags length:sizeof(int)];
301 [data appendBytes:&len length:sizeof(int)];
302 [data appendBytes:[unmodchars UTF8String] length:len];
304 [[self vimController] sendMessage:CmdKeyMsgID data:data];
309 - (void)scrollWheel:(NSEvent *)event
311 if ([event deltaY] == 0)
315 NSPoint pt = [textView convertPoint:[event locationInWindow] fromView:nil];
316 if ([textView convertPoint:pt toRow:&row column:&col]) {
317 int flags = [event modifierFlags];
318 float dy = [event deltaY];
319 NSMutableData *data = [NSMutableData data];
321 [data appendBytes:&row length:sizeof(int)];
322 [data appendBytes:&col length:sizeof(int)];
323 [data appendBytes:&flags length:sizeof(int)];
324 [data appendBytes:&dy length:sizeof(float)];
326 [[self vimController] sendMessage:ScrollWheelMsgID data:data];
330 - (void)mouseDown:(NSEvent *)event
333 NSPoint pt = [textView convertPoint:[event locationInWindow] fromView:nil];
334 if (![textView convertPoint:pt toRow:&row column:&col])
337 int button = [event buttonNumber];
338 int flags = [event modifierFlags];
339 int count = [event clickCount];
340 NSMutableData *data = [NSMutableData data];
342 // If desired, intepret Ctrl-Click as a right mouse click.
343 BOOL translateCtrlClick = [[NSUserDefaults standardUserDefaults]
344 boolForKey:MMTranslateCtrlClickKey];
345 flags = flags & NSDeviceIndependentModifierFlagsMask;
346 if (translateCtrlClick && button == 0 &&
347 (flags == NSControlKeyMask ||
348 flags == (NSControlKeyMask|NSAlphaShiftKeyMask))) {
350 flags &= ~NSControlKeyMask;
353 [data appendBytes:&row length:sizeof(int)];
354 [data appendBytes:&col length:sizeof(int)];
355 [data appendBytes:&button length:sizeof(int)];
356 [data appendBytes:&flags length:sizeof(int)];
357 [data appendBytes:&count length:sizeof(int)];
359 [[self vimController] sendMessage:MouseDownMsgID data:data];
362 - (void)mouseUp:(NSEvent *)event
365 NSPoint pt = [textView convertPoint:[event locationInWindow] fromView:nil];
366 if (![textView convertPoint:pt toRow:&row column:&col])
369 int flags = [event modifierFlags];
370 NSMutableData *data = [NSMutableData data];
372 [data appendBytes:&row length:sizeof(int)];
373 [data appendBytes:&col length:sizeof(int)];
374 [data appendBytes:&flags length:sizeof(int)];
376 [[self vimController] sendMessage:MouseUpMsgID data:data];
381 - (void)mouseDragged:(NSEvent *)event
383 int flags = [event modifierFlags];
385 NSPoint pt = [textView convertPoint:[event locationInWindow] fromView:nil];
386 if (![textView convertPoint:pt toRow:&row column:&col])
389 // Autoscrolling is done in dragTimerFired:
390 if (!isAutoscrolling) {
391 NSMutableData *data = [NSMutableData data];
393 [data appendBytes:&row length:sizeof(int)];
394 [data appendBytes:&col length:sizeof(int)];
395 [data appendBytes:&flags length:sizeof(int)];
397 [[self vimController] sendMessage:MouseDraggedMsgID data:data];
406 [self startDragTimerWithInterval:.5];
411 - (void)mouseMoved:(NSEvent *)event
413 // HACK! NSTextView has a nasty habit of resetting the cursor to the
414 // default I-beam cursor at random moments. The only reliable way we know
415 // of to work around this is to set the cursor each time the mouse moves.
418 NSPoint pt = [textView convertPoint:[event locationInWindow] fromView:nil];
420 if (![textView convertPoint:pt toRow:&row column:&col])
423 // HACK! It seems impossible to get the tracking rects set up before the
424 // view is visible, which means that the first mouseEntered: or
425 // mouseExited: events are never received. This forces us to check if the
426 // mouseMoved: event really happened over the text.
428 [textView getMaxRows:&rows columns:&cols];
429 if (row >= 0 && row < rows && col >= 0 && col < cols) {
430 NSMutableData *data = [NSMutableData data];
432 [data appendBytes:&row length:sizeof(int)];
433 [data appendBytes:&col length:sizeof(int)];
435 [[self vimController] sendMessage:MouseMovedMsgID data:data];
437 //NSLog(@"Moved %d %d\n", col, row);
441 - (void)mouseEntered:(NSEvent *)event
443 //NSLog(@"%s", _cmd);
445 // NOTE: This event is received even when the window is not key; thus we
446 // have to take care not to enable mouse moved events unless our window is
448 if ([[textView window] isKeyWindow]) {
449 [[textView window] setAcceptsMouseMovedEvents:YES];
453 - (void)mouseExited:(NSEvent *)event
455 //NSLog(@"%s", _cmd);
457 [[textView window] setAcceptsMouseMovedEvents:NO];
459 // NOTE: This event is received even when the window is not key; if the
460 // mouse shape is set when our window is not key, the hollow (unfocused)
461 // cursor will become a block (focused) cursor.
462 if ([[textView window] isKeyWindow]) {
464 NSMutableData *data = [NSMutableData data];
465 [data appendBytes:&shape length:sizeof(int)];
466 [[self vimController] sendMessage:SetMouseShapeMsgID data:data];
470 - (void)setFrame:(NSRect)frame
472 //NSLog(@"%s", _cmd);
474 // When the frame changes we also need to update the tracking rect.
475 [textView removeTrackingRect:trackingRectTag];
476 trackingRectTag = [textView addTrackingRect:[self trackingRect]
482 - (void)viewDidMoveToWindow
484 //NSLog(@"%s (window=%@)", _cmd, [self window]);
486 // Set a tracking rect which covers the text.
487 // NOTE: While the mouse cursor is in this rect the view will receive
488 // 'mouseMoved:' events so that Vim can take care of updating the mouse
490 if ([textView window]) {
491 [[textView window] setAcceptsMouseMovedEvents:YES];
492 trackingRectTag = [textView addTrackingRect:[self trackingRect]
499 - (void)viewWillMoveToWindow:(NSWindow *)newWindow
501 //NSLog(@"%s%@", _cmd, newWindow);
503 // Remove tracking rect if view moves or is removed.
504 if ([textView window] && trackingRectTag) {
505 [textView removeTrackingRect:trackingRectTag];
510 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
512 NSPasteboard *pboard = [sender draggingPasteboard];
514 if ([[pboard types] containsObject:NSStringPboardType]) {
515 NSString *string = [pboard stringForType:NSStringPboardType];
516 [[self vimController] dropString:string];
518 } else if ([[pboard types] containsObject:NSFilenamesPboardType]) {
519 NSArray *files = [pboard propertyListForType:NSFilenamesPboardType];
520 [[self vimController] dropFiles:files forceOpen:NO];
527 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
529 NSDragOperation sourceDragMask = [sender draggingSourceOperationMask];
530 NSPasteboard *pboard = [sender draggingPasteboard];
532 if ( [[pboard types] containsObject:NSFilenamesPboardType]
533 && (sourceDragMask & NSDragOperationCopy) )
534 return NSDragOperationCopy;
535 if ( [[pboard types] containsObject:NSStringPboardType]
536 && (sourceDragMask & NSDragOperationCopy) )
537 return NSDragOperationCopy;
539 return NSDragOperationNone;
542 - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
544 NSDragOperation sourceDragMask = [sender draggingSourceOperationMask];
545 NSPasteboard *pboard = [sender draggingPasteboard];
547 if ( [[pboard types] containsObject:NSFilenamesPboardType]
548 && (sourceDragMask & NSDragOperationCopy) )
549 return NSDragOperationCopy;
550 if ( [[pboard types] containsObject:NSStringPboardType]
551 && (sourceDragMask & NSDragOperationCopy) )
552 return NSDragOperationCopy;
554 return NSDragOperationNone;
557 - (void)setMouseShape:(int)shape
563 - (BOOL)hasMarkedText
565 return markedRange.length > 0 ? YES : NO;
568 - (NSRange)markedRange
570 if ([self hasMarkedText])
573 return NSMakeRange(NSNotFound, 0);
576 - (NSDictionary *)markedTextAttributes
578 return markedTextAttributes;
581 - (void)setMarkedTextAttributes:(NSDictionary *)attr
583 if (attr != markedTextAttributes) {
584 [markedTextAttributes release];
585 markedTextAttributes = [attr retain];
589 - (void)setMarkedText:(id)text selectedRange:(NSRange)range
593 if (!(text && [text length] > 0))
596 // HACK! Determine if the marked text is wide or normal width. This seems
597 // to always use 'wide' when there are both wide and normal width
599 NSString *string = text;
600 NSFont *theFont = [textView font];
601 if ([text isKindOfClass:[NSAttributedString class]]) {
602 theFont = [textView fontWide];
603 string = [text string];
606 // TODO: Use special colors for marked text.
607 [self setMarkedTextAttributes:
608 [NSDictionary dictionaryWithObjectsAndKeys:
609 theFont, NSFontAttributeName,
610 [textView defaultBackgroundColor], NSBackgroundColorAttributeName,
611 [textView defaultForegroundColor], NSForegroundColorAttributeName,
614 markedText = [[NSMutableAttributedString alloc]
615 initWithString:string
616 attributes:[self markedTextAttributes]];
618 markedRange = NSMakeRange(0, [markedText length]);
619 if (markedRange.length) {
620 [markedText addAttribute:NSUnderlineStyleAttributeName
621 value:[NSNumber numberWithInt:1]
626 [markedText addAttribute:NSUnderlineStyleAttributeName
627 value:[NSNumber numberWithInt:2]
631 [textView setNeedsDisplay:YES];
636 imRange = NSMakeRange(0, 0);
637 markedRange = NSMakeRange(NSNotFound, 0);
638 [markedText release];
642 - (NSMutableAttributedString *)markedText
647 - (void)setPreEditRow:(int)row column:(int)col
660 return preEditColumn;
663 - (void)setImRange:(NSRange)range
673 - (void)setMarkedRange:(NSRange)range
678 - (NSRect)firstRectForCharacterRange:(NSRange)range
680 // This method is called when the input manager wants to pop up an
681 // auxiliary window. The position where this should be is controlled by
682 // Vim by sending SetPreEditPositionMsgID so compute a position based on
683 // the pre-edit (row,column) pair.
684 int col = preEditColumn;
685 int row = preEditRow + 1;
687 NSFont *theFont = [[textView markedTextAttributes]
688 valueForKey:NSFontAttributeName];
689 if (theFont == [textView fontWide]) {
690 col += imRange.location * 2;
691 if (col >= [textView maxColumns] - 1) {
692 row += (col / [textView maxColumns]);
693 col = col % 2 ? col % [textView maxColumns] + 1 :
694 col % [textView maxColumns];
697 col += imRange.location;
698 if (col >= [textView maxColumns]) {
699 row += (col / [textView maxColumns]);
700 col = col % [textView maxColumns];
704 NSRect rect = [textView rectForRow:row
707 numColumns:range.length];
709 rect.origin = [textView convertPoint:rect.origin toView:nil];
710 rect.origin = [[textView window] convertBaseToScreen:rect.origin];
715 @end // MMTextViewHelper
720 @implementation MMTextViewHelper (Private)
722 - (MMWindowController *)windowController
724 id windowController = [[textView window] windowController];
725 if ([windowController isKindOfClass:[MMWindowController class]])
726 return (MMWindowController*)windowController;
730 - (MMVimController *)vimController
732 return [[self windowController] vimController];
735 - (void)dispatchKeyEvent:(NSEvent *)event
737 // Only handle the command if it came from a keyDown event
738 if ([event type] != NSKeyDown)
741 NSString *chars = [event characters];
742 NSString *unmodchars = [event charactersIgnoringModifiers];
743 unichar c = [chars characterAtIndex:0];
744 unichar imc = [unmodchars characterAtIndex:0];
746 const char *bytes = 0;
747 int mods = [event modifierFlags];
749 //NSLog(@"%s chars[0]=0x%x unmodchars[0]=0x%x (chars=%@ unmodchars=%@)",
750 // _cmd, c, imc, chars, unmodchars);
752 if (' ' == imc && 0xa0 != c) {
753 // HACK! The AppKit turns <C-Space> into <C-@> which is not standard
754 // Vim behaviour, so bypass this problem. (0xa0 is <M-Space>, which
755 // should be passed on as is.)
756 len = [unmodchars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
757 bytes = [unmodchars UTF8String];
758 } else if (imc == c && '2' == c) {
759 // HACK! Translate Ctrl+2 to <C-@>.
760 static char ctrl_at = 0;
761 len = 1; bytes = &ctrl_at;
762 } else if (imc == c && '6' == c) {
763 // HACK! Translate Ctrl+6 to <C-^>.
764 static char ctrl_hat = 0x1e;
765 len = 1; bytes = &ctrl_hat;
766 } else if (c == 0x19 && imc == 0x19) {
767 // HACK! AppKit turns back tab into Ctrl-Y, so we need to handle it
768 // separately (else Ctrl-Y doesn't work).
769 static char tab = 0x9;
770 len = 1; bytes = &tab; mods |= NSShiftKeyMask;
772 len = [chars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
773 bytes = [chars UTF8String];
776 [self sendKeyDown:bytes length:len modifiers:mods
777 isARepeat:[event isARepeat]];
780 - (void)sendKeyDown:(const char *)chars length:(int)len modifiers:(int)flags
781 isARepeat:(BOOL)isARepeat
783 if (chars && len > 0) {
784 NSMutableData *data = [NSMutableData data];
786 // The low 16 bits are not used for modifier flags by NSEvent. Use
787 // these bits for custom flags.
792 [data appendBytes:&flags length:sizeof(int)];
793 [data appendBytes:&len length:sizeof(int)];
794 [data appendBytes:chars length:len];
796 [self hideMouseCursor];
798 //NSLog(@"%s len=%d chars=0x%x", _cmd, len, chars[0]);
799 [[self vimController] sendMessage:KeyDownMsgID data:data];
803 - (void)hideMouseCursor
805 // Check 'mousehide' option
806 id mh = [[[self vimController] vimState] objectForKey:@"p_mh"];
807 if (mh && ![mh boolValue])
808 [NSCursor setHiddenUntilMouseMoves:NO];
810 [NSCursor setHiddenUntilMouseMoves:YES];
813 - (void)startDragTimerWithInterval:(NSTimeInterval)t
815 [NSTimer scheduledTimerWithTimeInterval:t target:self
816 selector:@selector(dragTimerFired:)
817 userInfo:nil repeats:NO];
820 - (void)dragTimerFired:(NSTimer *)timer
822 // TODO: Autoscroll in horizontal direction?
823 static unsigned tick = 1;
825 isAutoscrolling = NO;
827 if (isDragging && (dragRow < 0 || dragRow >= [textView maxRows])) {
828 // HACK! If the mouse cursor is outside the text area, then send a
829 // dragged event. However, if row&col hasn't changed since the last
830 // dragged event, Vim won't do anything (see gui_send_mouse_event()).
831 // Thus we fiddle with the column to make sure something happens.
832 int col = dragColumn + (dragRow < 0 ? -(tick % 2) : +(tick % 2));
833 NSMutableData *data = [NSMutableData data];
835 [data appendBytes:&dragRow length:sizeof(int)];
836 [data appendBytes:&col length:sizeof(int)];
837 [data appendBytes:&dragFlags length:sizeof(int)];
839 [[self vimController] sendMessage:MouseDraggedMsgID data:data];
841 isAutoscrolling = YES;
845 // Compute timer interval depending on how far away the mouse cursor is
846 // from the text view.
847 NSRect rect = [self trackingRect];
849 if (dragPoint.y < rect.origin.y) dy = rect.origin.y - dragPoint.y;
850 else if (dragPoint.y > NSMaxY(rect)) dy = dragPoint.y - NSMaxY(rect);
851 if (dy > MMDragAreaSize) dy = MMDragAreaSize;
853 NSTimeInterval t = MMDragTimerMaxInterval -
854 dy*(MMDragTimerMaxInterval-MMDragTimerMinInterval)/MMDragAreaSize;
856 [self startDragTimerWithInterval:t];
864 static NSCursor *customIbeamCursor = nil;
866 if (!customIbeamCursor) {
867 // Use a custom Ibeam cursor that has better contrast against dark
869 // TODO: Is the hotspot ok?
870 NSImage *ibeamImage = [NSImage imageNamed:@"ibeam"];
872 NSSize size = [ibeamImage size];
873 NSPoint hotSpot = { size.width*.5f, size.height*.5f };
875 customIbeamCursor = [[NSCursor alloc]
876 initWithImage:ibeamImage hotSpot:hotSpot];
878 if (!customIbeamCursor) {
879 NSLog(@"WARNING: Failed to load custom Ibeam cursor");
880 customIbeamCursor = [NSCursor IBeamCursor];
884 // This switch should match mshape_names[] in misc2.c.
886 // TODO: Add missing cursor shapes.
887 switch (mouseShape) {
888 case 2: [customIbeamCursor set]; break;
889 case 3: case 4: [[NSCursor resizeUpDownCursor] set]; break;
890 case 5: case 6: [[NSCursor resizeLeftRightCursor] set]; break;
891 case 9: [[NSCursor crosshairCursor] set]; break;
892 case 10: [[NSCursor pointingHandCursor] set]; break;
893 case 11: [[NSCursor openHandCursor] set]; break;
895 [[NSCursor arrowCursor] set]; break;
898 // Shape 1 indicates that the mouse cursor should be hidden.
900 [NSCursor setHiddenUntilMouseMoves:YES];
903 - (NSRect)trackingRect
905 NSRect rect = [textView frame];
906 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
907 int left = [ud integerForKey:MMTextInsetLeftKey];
908 int top = [ud integerForKey:MMTextInsetTopKey];
909 int right = [ud integerForKey:MMTextInsetRightKey];
910 int bot = [ud integerForKey:MMTextInsetBottomKey];
912 rect.origin.x = left;
914 rect.size.width -= left + right - 1;
915 rect.size.height -= top + bot - 1;
920 @end // MMTextViewHelper (Private)