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 * Dispatches keyboard and mouse input to the backend. Handles drag-n-drop of
17 #import "MMAtsuiTextView.h"
18 #import "MMVimController.h"
23 static char MMKeypadEnter[2] = { 'K', 'A' };
24 static NSString *MMKeypadEnterString = @"KA";
28 @interface MMAtsuiTextView (Private)
29 - (void)dispatchKeyEvent:(NSEvent *)event;
30 - (void)sendKeyDown:(const char *)chars length:(int)len modifiers:(int)flags;
31 - (MMVimController *)vimController;
34 @interface MMAtsuiTextView (Drawing)
37 - (void)drawString:(UniChar *)string length:(UniCharCount)length
38 atRow:(int)row column:(int)col cells:(int)cells
39 withFlags:(int)flags foregroundColor:(NSColor *)fg
40 backgroundColor:(NSColor *)bg specialColor:(NSColor *)sp;
41 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
42 scrollBottom:(int)bottom left:(int)left right:(int)right
43 color:(NSColor *)color;
44 - (void)insertLinesAtRow:(int)row lineCount:(int)count
45 scrollBottom:(int)bottom left:(int)left right:(int)right
46 color:(NSColor *)color;
47 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
48 column:(int)col2 color:(NSColor *)color;
53 @implementation MMAtsuiTextView
55 - (id)initWithFrame:(NSRect)frame
57 if ((self = [super initWithFrame:frame])) {
58 // NOTE! It does not matter which font is set here, Vim will set its
59 // own font on startup anyway. Just set some bogus values.
60 font = [[NSFont userFixedPitchFontOfSize:0] retain];
61 cellSize.width = cellSize.height = 1;
69 [font release]; font = nil;
70 [defaultBackgroundColor release]; defaultBackgroundColor = nil;
71 [defaultForegroundColor release]; defaultForegroundColor = nil;
76 - (void)getMaxRows:(int*)rows columns:(int*)cols
78 if (rows) *rows = maxRows;
79 if (cols) *cols = maxColumns;
82 - (void)setMaxRows:(int)rows columns:(int)cols
84 // NOTE: Just remember the new values, the actual resizing is done lazily.
89 - (void)setDefaultColorsBackground:(NSColor *)bgColor
90 foreground:(NSColor *)fgColor
92 if (defaultBackgroundColor != bgColor) {
93 [defaultBackgroundColor release];
94 defaultBackgroundColor = bgColor ? [bgColor retain] : nil;
97 // NOTE: The default foreground color isn't actually used for anything, but
98 // other class instances might want to be able to access it so it is stored
100 if (defaultForegroundColor != fgColor) {
101 [defaultForegroundColor release];
102 defaultForegroundColor = fgColor ? [fgColor retain] : nil;
108 return NSMakeSize(maxColumns*cellSize.width, maxRows*cellSize.height);
111 - (NSSize)fitToSize:(NSSize)size rows:(int *)rows columns:(int *)columns
113 NSSize curSize = [self size];
114 NSSize fitSize = curSize;
115 int fitRows = maxRows;
116 int fitCols = maxColumns;
118 if (size.height < curSize.height) {
119 // Remove lines until the height of the text storage fits inside
120 // 'size'. However, always make sure there are at least 3 lines in the
121 // text storage. (Why 3? It seem Vim never allows less than 3 lines.)
123 // TODO: No need to search since line height is fixed, just calculate
125 int rowCount = maxRows;
127 for (rowsToRemove = 0; rowsToRemove < maxRows-3; ++rowsToRemove) {
128 float height = cellSize.height*rowCount;
130 if (height <= size.height) {
131 fitSize.height = height;
138 fitRows -= rowsToRemove;
139 } else if (size.height > curSize.height) {
140 float fh = cellSize.height;
141 if (fh < 1.0f) fh = 1.0f;
143 fitRows = floor(size.height/fh);
144 fitSize.height = fh*fitRows;
147 if (size.width != curSize.width) {
148 float fw = cellSize.width;
149 if (fw < 1.0f) fw = 1.0f;
151 fitCols = floor(size.width/fw);
152 fitSize.width = fw*fitCols;
155 if (rows) *rows = fitRows;
156 if (columns) *columns = fitCols;
161 - (NSRect)rectForRowsInRange:(NSRange)range
163 NSRect rect = { 0, 0, 0, 0 };
164 unsigned start = range.location > maxRows ? maxRows : range.location;
165 unsigned length = range.length;
167 if (start+length > maxRows)
168 length = maxRows - start;
170 rect.origin.y = cellSize.height * start;
171 rect.size.height = cellSize.height * length;
176 - (NSRect)rectForColumnsInRange:(NSRange)range
178 NSRect rect = { 0, 0, 0, 0 };
179 unsigned start = range.location > maxColumns ? maxColumns : range.location;
180 unsigned length = range.length;
182 if (start+length > maxColumns)
183 length = maxColumns - start;
185 rect.origin.x = cellSize.width * start;
186 rect.size.width = cellSize.width * length;
192 - (void)setFont:(NSFont *)newFont
194 if (newFont && font != newFont) {
196 font = [newFont retain];
198 float em = [newFont widthOfString:@"m"];
199 float cellWidthMultiplier = [[NSUserDefaults standardUserDefaults]
200 floatForKey:MMCellWidthMultiplierKey];
202 // NOTE! Even though NSFontFixedAdvanceAttribute is a float, it will
203 // only render at integer sizes. Hence, we restrict the cell width to
204 // an integer here, otherwise the window width and the actual text
205 // width will not match.
206 cellSize.width = ceilf(em * cellWidthMultiplier);
207 cellSize.height = linespace + [newFont defaultLineHeightForFont];
211 - (void)setWideFont:(NSFont *)newFont
225 - (void)setLinespace:(float)newLinespace
227 linespace = newLinespace;
229 // NOTE: The linespace is added to the cell height in order for a multiline
230 // selection not to have white (background color) gaps between lines. Also
231 // this simplifies the code a lot because there is no need to check the
232 // linespace when calculating the size of the text view etc. When the
233 // linespace is non-zero the baseline will be adjusted as well; check
235 cellSize.height = linespace + [font defaultLineHeightForFont];
241 - (NSEvent *)lastMouseDownEvent
246 - (void)setShouldDrawInsertionPoint:(BOOL)on
250 - (void)setPreEditRow:(int)row column:(int)col
254 - (void)drawInsertionPointAtRow:(int)row column:(int)col shape:(int)shape
255 fraction:(int)percent color:(NSColor *)color
259 - (void)hideMarkedTextField
266 - (void)keyDown:(NSEvent *)event
268 //NSLog(@"%s %@", _cmd, event);
269 // HACK! If control modifier is held, don't pass the event along to
270 // interpretKeyEvents: since some keys are bound to multiple commands which
271 // means doCommandBySelector: is called several times.
273 // TODO: Figure out a way to disable Cocoa key bindings entirely, without
274 // affecting input management.
275 if ([event modifierFlags] & NSControlKeyMask) {
276 NSString *unmod = [event charactersIgnoringModifiers];
277 if ([unmod length] == 1 && [unmod characterAtIndex:0] <= 0x7f
278 && [unmod characterAtIndex:0] >= 0x60) {
279 // HACK! Send Ctrl-letter keys (and C-@, C-[, C-\, C-], C-^, C-_)
280 // as normal text to be added to the Vim input buffer. This must
281 // be done in order for the backend to be able to separate e.g.
282 // Ctrl-i and Ctrl-tab.
283 [self insertText:[event characters]];
285 [self dispatchKeyEvent:event];
288 [super keyDown:event];
292 - (void)insertText:(id)string
294 //NSLog(@"%s %@", _cmd, string);
295 // NOTE! This method is called for normal key presses but also for
296 // Option-key presses --- even when Ctrl is held as well as Option. When
297 // Ctrl is held, the AppKit translates the character to a Ctrl+key stroke,
298 // so 'string' need not be a printable character! In this case it still
299 // works to pass 'string' on to Vim as a printable character (since
300 // modifiers are already included and should not be added to the input
301 // buffer using CSI, K_MODIFIER).
303 [self hideMarkedTextField];
305 NSEvent *event = [NSApp currentEvent];
307 // HACK! In order to be able to bind to <S-Space>, <S-M-Tab>, etc. we have
308 // to watch for them here.
309 if ([event type] == NSKeyDown
310 && [[event charactersIgnoringModifiers] length] > 0
311 && [event modifierFlags]
312 & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask)) {
313 unichar c = [[event charactersIgnoringModifiers] characterAtIndex:0];
315 // <S-M-Tab> translates to 0x19
316 if (' ' == c || 0x19 == c) {
317 [self dispatchKeyEvent:event];
322 // TODO: Support 'mousehide' (check p_mh)
323 [NSCursor setHiddenUntilMouseMoves:YES];
325 // NOTE: 'string' is either an NSString or an NSAttributedString. Since we
326 // do not support attributes, simply pass the corresponding NSString in the
328 if ([string isKindOfClass:[NSAttributedString class]])
329 string = [string string];
331 //NSLog(@"send InsertTextMsgID: %@", string);
333 [[self vimController] sendMessage:InsertTextMsgID
334 data:[string dataUsingEncoding:NSUTF8StringEncoding]];
337 - (void)doCommandBySelector:(SEL)selector
339 //NSLog(@"%s %@", _cmd, NSStringFromSelector(selector));
340 // By ignoring the selector we effectively disable the key binding
341 // mechanism of Cocoa. Hopefully this is what the user will expect
342 // (pressing Ctrl+P would otherwise result in moveUp: instead of previous
345 // We usually end up here if the user pressed Ctrl+key (but not
348 NSEvent *event = [NSApp currentEvent];
350 if (selector == @selector(cancelOperation:)
351 || selector == @selector(insertNewline:)) {
352 // HACK! If there was marked text which got abandoned as a result of
353 // hitting escape or enter, then 'insertText:' is called with the
354 // abandoned text but '[event characters]' includes the abandoned text
355 // as well. Since 'dispatchKeyEvent:' looks at '[event characters]' we
356 // must intercept these keys here or the abandonded text gets inserted
358 NSString *key = [event charactersIgnoringModifiers];
359 const char *chars = [key UTF8String];
360 int len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
362 if (0x3 == chars[0]) {
363 // HACK! AppKit turns enter (not return) into Ctrl-C, so we need to
364 // handle it separately (else Ctrl-C doesn't work).
365 len = sizeof(MMKeypadEnter)/sizeof(MMKeypadEnter[0]);
366 chars = MMKeypadEnter;
369 [self sendKeyDown:chars length:len modifiers:[event modifierFlags]];
371 [self dispatchKeyEvent:event];
375 - (BOOL)performKeyEquivalent:(NSEvent *)event
377 //NSLog(@"%s %@", _cmd, event);
378 // Called for Cmd+key keystrokes, function keys, arrow keys, page
379 // up/down, home, end.
381 // NOTE: This message cannot be ignored since Cmd+letter keys never are
382 // passed to keyDown:. It seems as if the main menu consumes Cmd-key
383 // strokes, unless the key is a function key.
385 // NOTE: If the event that triggered this method represents a function key
386 // down then we do nothing, otherwise the input method never gets the key
387 // stroke (some input methods use e.g. arrow keys). The function key down
388 // event will still reach Vim though (via keyDown:). The exceptions to
389 // this rule are: PageUp/PageDown (keycode 116/121).
390 int flags = [event modifierFlags];
391 if ([event type] != NSKeyDown || flags & NSFunctionKeyMask
392 && !(116 == [event keyCode] || 121 == [event keyCode]))
395 // HACK! Let the main menu try to handle any key down event, before
396 // passing it on to vim, otherwise key equivalents for menus will
397 // effectively be disabled.
398 if ([[NSApp mainMenu] performKeyEquivalent:event])
401 // HACK! KeyCode 50 represent the key which switches between windows
402 // within an application (like Cmd+Tab is used to switch between
403 // applications). Return NO here, else the window switching does not work.
405 // Will this hack work for all languages / keyboard layouts?
406 if ([event keyCode] == 50)
409 // HACK! On Leopard Ctrl-key events end up here instead of keyDown:.
410 if (flags & NSControlKeyMask) {
411 [self keyDown:event];
415 //NSLog(@"%s%@", _cmd, event);
417 NSString *chars = [event characters];
418 NSString *unmodchars = [event charactersIgnoringModifiers];
419 int len = [unmodchars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
420 NSMutableData *data = [NSMutableData data];
425 // If 'chars' and 'unmodchars' differs when shift flag is present, then we
426 // can clear the shift flag as it is already included in 'unmodchars'.
427 // Failing to clear the shift flag means <D-Bar> turns into <S-D-Bar> (on
428 // an English keyboard).
429 if (flags & NSShiftKeyMask && ![chars isEqual:unmodchars])
430 flags &= ~NSShiftKeyMask;
432 if (0x3 == [unmodchars characterAtIndex:0]) {
433 // HACK! AppKit turns enter (not return) into Ctrl-C, so we need to
434 // handle it separately (else Cmd-enter turns into Ctrl-C).
435 unmodchars = MMKeypadEnterString;
436 len = [unmodchars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
439 [data appendBytes:&flags length:sizeof(int)];
440 [data appendBytes:&len length:sizeof(int)];
441 [data appendBytes:[unmodchars UTF8String] length:len];
443 [[self vimController] sendMessage:CmdKeyMsgID data:data];
448 - (NSPoint)textContainerOrigin
453 - (void)setTextContainerInset:(NSSize)inset
457 - (void)setBackgroundColor:(NSColor *)color
464 - (BOOL)acceptsFirstResponder
474 - (void)drawRect:(NSRect)rect
476 NSColor *color = defaultBackgroundColor ? defaultBackgroundColor
477 : [NSColor lightGrayColor];
479 [NSBezierPath fillRect:rect];
485 #define MM_DEBUG_DRAWING 0
487 - (void)performBatchDrawWithData:(NSData *)data
489 const void *bytes = [data bytes];
490 const void *end = bytes + [data length];
493 NSLog(@"====> BEGIN %s", _cmd);
497 // TODO: Sanity check input
499 while (bytes < end) {
500 int type = *((int*)bytes); bytes += sizeof(int);
502 if (ClearAllDrawType == type) {
504 NSLog(@" Clear all");
507 } else if (ClearBlockDrawType == type) {
508 unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned);
509 int row1 = *((int*)bytes); bytes += sizeof(int);
510 int col1 = *((int*)bytes); bytes += sizeof(int);
511 int row2 = *((int*)bytes); bytes += sizeof(int);
512 int col2 = *((int*)bytes); bytes += sizeof(int);
515 NSLog(@" Clear block (%d,%d) -> (%d,%d)", row1, col1,
518 [self clearBlockFromRow:row1 column:col1
519 toRow:row2 column:col2
520 color:[NSColor colorWithArgbInt:color]];
521 } else if (DeleteLinesDrawType == type) {
522 unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned);
523 int row = *((int*)bytes); bytes += sizeof(int);
524 int count = *((int*)bytes); bytes += sizeof(int);
525 int bot = *((int*)bytes); bytes += sizeof(int);
526 int left = *((int*)bytes); bytes += sizeof(int);
527 int right = *((int*)bytes); bytes += sizeof(int);
530 NSLog(@" Delete %d line(s) from %d", count, row);
532 [self deleteLinesFromRow:row lineCount:count
533 scrollBottom:bot left:left right:right
534 color:[NSColor colorWithArgbInt:color]];
535 } else if (DrawStringDrawType == type) {
536 int bg = *((int*)bytes); bytes += sizeof(int);
537 int fg = *((int*)bytes); bytes += sizeof(int);
538 int sp = *((int*)bytes); bytes += sizeof(int);
539 int row = *((int*)bytes); bytes += sizeof(int);
540 int col = *((int*)bytes); bytes += sizeof(int);
541 int cells = *((int*)bytes); bytes += sizeof(int);
542 int flags = *((int*)bytes); bytes += sizeof(int);
543 int len = *((int*)bytes); bytes += sizeof(int);
544 UniChar *string = (UniChar*)bytes; bytes += len;
547 NSLog(@" Draw string at (%d,%d) length=%d flags=%d fg=0x%x "
548 "bg=0x%x sp=0x%x", row, col, len, flags, fg, bg, sp);
550 [self drawString:string length:len atRow:row column:col
551 cells:cells withFlags:flags
552 foregroundColor:[NSColor colorWithRgbInt:fg]
553 backgroundColor:[NSColor colorWithArgbInt:bg]
554 specialColor:[NSColor colorWithRgbInt:sp]];
555 } else if (InsertLinesDrawType == type) {
556 unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned);
557 int row = *((int*)bytes); bytes += sizeof(int);
558 int count = *((int*)bytes); bytes += sizeof(int);
559 int bot = *((int*)bytes); bytes += sizeof(int);
560 int left = *((int*)bytes); bytes += sizeof(int);
561 int right = *((int*)bytes); bytes += sizeof(int);
564 NSLog(@" Insert %d line(s) at row %d", count, row);
566 [self insertLinesAtRow:row lineCount:count
567 scrollBottom:bot left:left right:right
568 color:[NSColor colorWithArgbInt:color]];
569 } else if (DrawCursorDrawType == type) {
570 unsigned color = *((unsigned*)bytes); bytes += sizeof(unsigned);
571 int row = *((int*)bytes); bytes += sizeof(int);
572 int col = *((int*)bytes); bytes += sizeof(int);
573 int shape = *((int*)bytes); bytes += sizeof(int);
574 int percent = *((int*)bytes); bytes += sizeof(int);
577 NSLog(@" Draw cursor at (%d,%d)", row, col);
579 [self drawInsertionPointAtRow:row column:col shape:shape
581 color:[NSColor colorWithRgbInt:color]];
583 NSLog(@"WARNING: Unknown draw type (type=%d)", type);
589 // NOTE: During resizing, Cocoa only sends draw messages before Vim's rows
590 // and columns are changed (due to ipc delays). Force a redraw here.
591 [self displayIfNeeded];
594 NSLog(@"<==== END %s", _cmd);
598 @end // MMAtsuiTextView
603 @implementation MMAtsuiTextView (Private)
605 - (void)dispatchKeyEvent:(NSEvent *)event
607 // Only handle the command if it came from a keyDown event
608 if ([event type] != NSKeyDown)
611 NSString *chars = [event characters];
612 NSString *unmodchars = [event charactersIgnoringModifiers];
613 unichar c = [chars characterAtIndex:0];
614 unichar imc = [unmodchars characterAtIndex:0];
616 const char *bytes = 0;
617 int mods = [event modifierFlags];
619 //NSLog(@"%s chars[0]=0x%x unmodchars[0]=0x%x (chars=%@ unmodchars=%@)",
620 // _cmd, c, imc, chars, unmodchars);
622 if (' ' == imc && 0xa0 != c) {
623 // HACK! The AppKit turns <C-Space> into <C-@> which is not standard
624 // Vim behaviour, so bypass this problem. (0xa0 is <M-Space>, which
625 // should be passed on as is.)
626 len = [unmodchars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
627 bytes = [unmodchars UTF8String];
628 } else if (imc == c && '2' == c) {
629 // HACK! Translate Ctrl+2 to <C-@>.
630 static char ctrl_at = 0;
631 len = 1; bytes = &ctrl_at;
632 } else if (imc == c && '6' == c) {
633 // HACK! Translate Ctrl+6 to <C-^>.
634 static char ctrl_hat = 0x1e;
635 len = 1; bytes = &ctrl_hat;
636 } else if (c == 0x19 && imc == 0x19) {
637 // HACK! AppKit turns back tab into Ctrl-Y, so we need to handle it
638 // separately (else Ctrl-Y doesn't work).
639 static char tab = 0x9;
640 len = 1; bytes = &tab; mods |= NSShiftKeyMask;
642 len = [chars lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
643 bytes = [chars UTF8String];
646 [self sendKeyDown:bytes length:len modifiers:mods];
649 - (void)sendKeyDown:(const char *)chars length:(int)len modifiers:(int)flags
651 if (chars && len > 0) {
652 NSMutableData *data = [NSMutableData data];
654 [data appendBytes:&flags length:sizeof(int)];
655 [data appendBytes:&len length:sizeof(int)];
656 [data appendBytes:chars length:len];
658 // TODO: Support 'mousehide' (check p_mh)
659 [NSCursor setHiddenUntilMouseMoves:YES];
661 //NSLog(@"%s len=%d chars=0x%x", _cmd, len, chars[0]);
662 [[self vimController] sendMessage:KeyDownMsgID data:data];
666 - (MMVimController *)vimController
668 id windowController = [[self window] windowController];
670 // TODO: Make sure 'windowController' is a MMWindowController before type
672 return [(MMWindowController*)windowController vimController];
675 @end // MMAtsuiTextView (Private)
680 @implementation MMAtsuiTextView (Drawing)
690 - (void)drawString:(UniChar *)string length:(UniCharCount)length
691 atRow:(int)row column:(int)col cells:(int)cells
692 withFlags:(int)flags foregroundColor:(NSColor *)fg
693 backgroundColor:(NSColor *)bg specialColor:(NSColor *)sp
695 // 'string' consists of 'length' utf-16 code pairs and should cover 'cells'
696 // display cells (a normal character takes up one display cell, a wide
697 // character takes up two)
700 - (void)deleteLinesFromRow:(int)row lineCount:(int)count
701 scrollBottom:(int)bottom left:(int)left right:(int)right
702 color:(NSColor *)color
706 - (void)insertLinesAtRow:(int)row lineCount:(int)count
707 scrollBottom:(int)bottom left:(int)left right:(int)right
708 color:(NSColor *)color
712 - (void)clearBlockFromRow:(int)row1 column:(int)col1 toRow:(int)row2
713 column:(int)col2 color:(NSColor *)color
721 @end // MMAtsuiTextView (Drawing)