1
2/**
3 * Scintilla source code edit control
4 * @file ScintillaCocoa.mm - Cocoa subclass of ScintillaBase
5 *
6 * Written by Mike Lischke <mlischke@sun.com>
7 *
8 * Loosely based on ScintillaMacOSX.cxx.
9 * Copyright 2003 by Evan Jones <ejones@uwaterloo.ca>
10 * Based on ScintillaGTK.cxx Copyright 1998-2002 by Neil Hodgson <neilh@scintilla.org>
11 * The License.txt file describes the conditions under which this software may be distributed.
12  *
13 * Copyright (c) 2009, 2010 Sun Microsystems, Inc. All rights reserved.
14 * This file is dual licensed under LGPL v2.1 and the Scintilla license (http://www.scintilla.org/License.txt).
15 */
16
17#include <cmath>
18
19#include <string_view>
20#include <vector>
21
22#import <Cocoa/Cocoa.h>
23#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5
24#import <QuartzCore/CAGradientLayer.h>
25#endif
26#import <QuartzCore/CAAnimation.h>
27#import <QuartzCore/CATransaction.h>
28
29#import "Platform.h"
30#import "ScintillaView.h"
31#import "ScintillaCocoa.h"
32#import "PlatCocoa.h"
33
34using namespace Scintilla;
35
36NSString *ScintillaRecPboardType = @"com.scintilla.utf16-plain-text.rectangular";
37
38//--------------------------------------------------------------------------------------------------
39
40// Define keyboard shortcuts (equivalents) the Mac way.
41#define SCI_CMD ( SCI_CTRL)
42#define SCI_SCMD ( SCI_CMD | SCI_SHIFT)
43#define SCI_SMETA ( SCI_META | SCI_SHIFT)
44
45static const KeyToCommand macMapDefault[] = {
46	// OS X specific
47	{SCK_DOWN,      SCI_CTRL,   SCI_DOCUMENTEND},
48	{SCK_DOWN,      SCI_CSHIFT, SCI_DOCUMENTENDEXTEND},
49	{SCK_UP,        SCI_CTRL,   SCI_DOCUMENTSTART},
50	{SCK_UP,        SCI_CSHIFT, SCI_DOCUMENTSTARTEXTEND},
51	{SCK_LEFT,      SCI_CTRL,   SCI_VCHOME},
52	{SCK_LEFT,      SCI_CSHIFT, SCI_VCHOMEEXTEND},
53	{SCK_RIGHT,     SCI_CTRL,   SCI_LINEEND},
54	{SCK_RIGHT,     SCI_CSHIFT, SCI_LINEENDEXTEND},
55
56	// Similar to Windows and GTK+
57	// Where equivalent clashes with OS X standard, use Meta instead
58	{SCK_DOWN,      SCI_NORM,   SCI_LINEDOWN},
59	{SCK_DOWN,      SCI_SHIFT,  SCI_LINEDOWNEXTEND},
60	{SCK_DOWN,      SCI_META,   SCI_LINESCROLLDOWN},
61	{SCK_DOWN,      SCI_ASHIFT, SCI_LINEDOWNRECTEXTEND},
62	{SCK_UP,        SCI_NORM,   SCI_LINEUP},
63	{SCK_UP,        SCI_SHIFT,  SCI_LINEUPEXTEND},
64	{SCK_UP,        SCI_META,   SCI_LINESCROLLUP},
65	{SCK_UP,        SCI_ASHIFT, SCI_LINEUPRECTEXTEND},
66	{'[',           SCI_CTRL,   SCI_PARAUP},
67	{'[',           SCI_CSHIFT, SCI_PARAUPEXTEND},
68	{']',           SCI_CTRL,   SCI_PARADOWN},
69	{']',           SCI_CSHIFT, SCI_PARADOWNEXTEND},
70	{SCK_LEFT,      SCI_NORM,   SCI_CHARLEFT},
71	{SCK_LEFT,      SCI_SHIFT,  SCI_CHARLEFTEXTEND},
72	{SCK_LEFT,      SCI_ALT,    SCI_WORDLEFT},
73	{SCK_LEFT,      SCI_META,   SCI_WORDLEFT},
74	{SCK_LEFT,      SCI_SMETA,  SCI_WORDLEFTEXTEND},
75	{SCK_LEFT,      SCI_ASHIFT, SCI_CHARLEFTRECTEXTEND},
76	{SCK_RIGHT,     SCI_NORM,   SCI_CHARRIGHT},
77	{SCK_RIGHT,     SCI_SHIFT,  SCI_CHARRIGHTEXTEND},
78	{SCK_RIGHT,     SCI_ALT,    SCI_WORDRIGHT},
79	{SCK_RIGHT,     SCI_META,   SCI_WORDRIGHT},
80	{SCK_RIGHT,     SCI_SMETA,  SCI_WORDRIGHTEXTEND},
81	{SCK_RIGHT,     SCI_ASHIFT, SCI_CHARRIGHTRECTEXTEND},
82	{'/',           SCI_CTRL,   SCI_WORDPARTLEFT},
83	{'/',           SCI_CSHIFT, SCI_WORDPARTLEFTEXTEND},
84	{'\\',          SCI_CTRL,   SCI_WORDPARTRIGHT},
85	{'\\',          SCI_CSHIFT, SCI_WORDPARTRIGHTEXTEND},
86	{SCK_HOME,      SCI_NORM,   SCI_VCHOME},
87	{SCK_HOME,      SCI_SHIFT,  SCI_VCHOMEEXTEND},
88	{SCK_HOME,      SCI_CTRL,   SCI_DOCUMENTSTART},
89	{SCK_HOME,      SCI_CSHIFT, SCI_DOCUMENTSTARTEXTEND},
90	{SCK_HOME,      SCI_ALT,    SCI_HOMEDISPLAY},
91	{SCK_HOME,      SCI_ASHIFT, SCI_VCHOMERECTEXTEND},
92	{SCK_END,       SCI_NORM,   SCI_LINEEND},
93	{SCK_END,       SCI_SHIFT,  SCI_LINEENDEXTEND},
94	{SCK_END,       SCI_CTRL,   SCI_DOCUMENTEND},
95	{SCK_END,       SCI_CSHIFT, SCI_DOCUMENTENDEXTEND},
96	{SCK_END,       SCI_ALT,    SCI_LINEENDDISPLAY},
97	{SCK_END,       SCI_ASHIFT, SCI_LINEENDRECTEXTEND},
98	{SCK_PRIOR,     SCI_NORM,   SCI_PAGEUP},
99	{SCK_PRIOR,     SCI_SHIFT,  SCI_PAGEUPEXTEND},
100	{SCK_PRIOR,     SCI_ASHIFT, SCI_PAGEUPRECTEXTEND},
101	{SCK_NEXT,      SCI_NORM,   SCI_PAGEDOWN},
102	{SCK_NEXT,      SCI_SHIFT,  SCI_PAGEDOWNEXTEND},
103	{SCK_NEXT,      SCI_ASHIFT, SCI_PAGEDOWNRECTEXTEND},
104	{SCK_DELETE,    SCI_NORM,   SCI_CLEAR},
105	{SCK_DELETE,    SCI_SHIFT,  SCI_CUT},
106	{SCK_DELETE,    SCI_CTRL,   SCI_DELWORDRIGHT},
107	{SCK_DELETE,    SCI_CSHIFT, SCI_DELLINERIGHT},
108	{SCK_INSERT,    SCI_NORM,   SCI_EDITTOGGLEOVERTYPE},
109	{SCK_INSERT,    SCI_SHIFT,  SCI_PASTE},
110	{SCK_INSERT,    SCI_CTRL,   SCI_COPY},
111	{SCK_ESCAPE,    SCI_NORM,   SCI_CANCEL},
112	{SCK_BACK,      SCI_NORM,   SCI_DELETEBACK},
113	{SCK_BACK,      SCI_SHIFT,  SCI_DELETEBACK},
114	{SCK_BACK,      SCI_CTRL,   SCI_DELWORDLEFT},
115	{SCK_BACK,      SCI_ALT,    SCI_DELWORDLEFT},
116	{SCK_BACK,      SCI_CSHIFT, SCI_DELLINELEFT},
117	{'z',           SCI_CMD,    SCI_UNDO},
118	{'z',           SCI_SCMD,   SCI_REDO},
119	{'x',           SCI_CMD,    SCI_CUT},
120	{'c',           SCI_CMD,    SCI_COPY},
121	{'v',           SCI_CMD,    SCI_PASTE},
122	{'a',           SCI_CMD,    SCI_SELECTALL},
123	{SCK_TAB,       SCI_NORM,   SCI_TAB},
124	{SCK_TAB,       SCI_SHIFT,  SCI_BACKTAB},
125	{SCK_RETURN,    SCI_NORM,   SCI_NEWLINE},
126	{SCK_RETURN,    SCI_SHIFT,  SCI_NEWLINE},
127	{SCK_ADD,       SCI_CMD,    SCI_ZOOMIN},
128	{SCK_SUBTRACT,  SCI_CMD,    SCI_ZOOMOUT},
129	{SCK_DIVIDE,    SCI_CMD,    SCI_SETZOOM},
130	{'l',           SCI_CMD,    SCI_LINECUT},
131	{'l',           SCI_CSHIFT, SCI_LINEDELETE},
132	{'t',           SCI_CSHIFT, SCI_LINECOPY},
133	{'t',           SCI_CTRL,   SCI_LINETRANSPOSE},
134	{'d',           SCI_CTRL,   SCI_SELECTIONDUPLICATE},
135	{'u',           SCI_CTRL,   SCI_LOWERCASE},
136	{'u',           SCI_CSHIFT, SCI_UPPERCASE},
137	{0, 0, 0},
138};
139
140//--------------------------------------------------------------------------------------------------
141
142#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5
143
144// Only implement FindHighlightLayer on OS X 10.6+
145
146/**
147 * Class to display the animated gold roundrect used on OS X for matches.
148 */
149@interface FindHighlightLayer : CAGradientLayer {
150@private
151	NSString *sFind;
152	long positionFind;
153	BOOL retaining;
154	CGFloat widthText;
155	CGFloat heightLine;
156	NSString *sFont;
157	CGFloat fontSize;
158}
159
160@property(copy) NSString *sFind;
161@property(assign) long positionFind;
162@property(assign) BOOL retaining;
163@property(assign) CGFloat widthText;
164@property(assign) CGFloat heightLine;
165@property(copy) NSString *sFont;
166@property(assign) CGFloat fontSize;
167
168- (void) animateMatch: (CGPoint) ptText bounce: (BOOL) bounce;
169- (void) hideMatch;
170
171@end
172
173//--------------------------------------------------------------------------------------------------
174
175@implementation FindHighlightLayer
176
177@synthesize sFind, positionFind, retaining, widthText, heightLine, sFont, fontSize;
178
179- (id) init {
180	if (self = [super init]) {
181		[self setNeedsDisplayOnBoundsChange: YES];
182		// A gold to slightly redder gradient to match other applications
183		CGColorRef colGold = CGColorCreateGenericRGB(1.0, 1.0, 0, 1.0);
184		CGColorRef colGoldRed = CGColorCreateGenericRGB(1.0, 0.8, 0, 1.0);
185		self.colors = @[(__bridge id)colGoldRed, (__bridge id)colGold];
186		CGColorRelease(colGoldRed);
187		CGColorRelease(colGold);
188
189		CGColorRef colGreyBorder = CGColorCreateGenericGray(0.756f, 0.5f);
190		self.borderColor = colGreyBorder;
191		CGColorRelease(colGreyBorder);
192
193		self.borderWidth = 1.0;
194		self.cornerRadius = 5.0f;
195		self.shadowRadius = 1.0f;
196		self.shadowOpacity = 0.9f;
197		self.shadowOffset = CGSizeMake(0.0f, -2.0f);
198		self.anchorPoint = CGPointMake(0.5, 0.5);
199	}
200	return self;
201
202}
203
204
205const CGFloat paddingHighlightX = 4;
206const CGFloat paddingHighlightY = 2;
207
208- (void) drawInContext: (CGContextRef) context {
209	if (!sFind || !sFont)
210		return;
211
212	CFStringRef str = (__bridge CFStringRef)(sFind);
213
214	CFMutableDictionaryRef styleDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 2,
215					   &kCFTypeDictionaryKeyCallBacks,
216					   &kCFTypeDictionaryValueCallBacks);
217	CGColorRef color = CGColorCreateGenericRGB(0.0, 0.0, 0.0, 1.0);
218	CFDictionarySetValue(styleDict, kCTForegroundColorAttributeName, color);
219	CTFontRef fontRef = ::CTFontCreateWithName((CFStringRef)sFont, fontSize, NULL);
220	CFDictionaryAddValue(styleDict, kCTFontAttributeName, fontRef);
221
222	CFAttributedStringRef attrString = ::CFAttributedStringCreate(NULL, str, styleDict);
223	CTLineRef textLine = ::CTLineCreateWithAttributedString(attrString);
224	// Indent from corner of bounds
225	CGContextSetTextPosition(context, paddingHighlightX, 3 + paddingHighlightY);
226	CTLineDraw(textLine, context);
227
228	CFRelease(textLine);
229	CFRelease(attrString);
230	CFRelease(fontRef);
231	CGColorRelease(color);
232	CFRelease(styleDict);
233}
234
235- (void) animateMatch: (CGPoint) ptText bounce: (BOOL) bounce {
236	if (!self.sFind || !(self.sFind).length) {
237		[self hideMatch];
238		return;
239	}
240
241	CGFloat width = self.widthText + paddingHighlightX * 2;
242	CGFloat height = self.heightLine + paddingHighlightY * 2;
243
244	CGFloat flipper = self.geometryFlipped ? -1.0 : 1.0;
245
246	// Adjust for padding
247	ptText.x -= paddingHighlightX;
248	ptText.y += flipper * paddingHighlightY;
249
250	// Shift point to centre as expanding about centre
251	ptText.x += width / 2.0;
252	ptText.y -= flipper * height / 2.0;
253
254	[CATransaction begin];
255	[CATransaction setValue: @0.0f forKey: kCATransactionAnimationDuration];
256	self.bounds = CGRectMake(0, 0, width, height);
257	self.position = ptText;
258	if (bounce) {
259		// Do not reset visibility when just moving
260		self.hidden = NO;
261		self.opacity = 1.0;
262	}
263	[self setNeedsDisplay];
264	[CATransaction commit];
265
266	if (bounce) {
267		CABasicAnimation *animBounce = [CABasicAnimation animationWithKeyPath: @"transform.scale"];
268		animBounce.duration = 0.15;
269		animBounce.autoreverses = YES;
270		animBounce.removedOnCompletion = NO;
271		animBounce.fromValue = @1.0f;
272		animBounce.toValue = @1.25f;
273
274		if (self.retaining) {
275
276			[self addAnimation: animBounce forKey: @"animateFound"];
277
278		} else {
279
280			CABasicAnimation *animFade = [CABasicAnimation animationWithKeyPath: @"opacity"];
281			animFade.duration = 0.1;
282			animFade.beginTime = 0.4;
283			animFade.removedOnCompletion = NO;
284			animFade.fromValue = @1.0f;
285			animFade.toValue = @0.0f;
286
287			CAAnimationGroup *group = [CAAnimationGroup animation];
288			group.duration = 0.5;
289			group.removedOnCompletion = NO;
290			group.fillMode = kCAFillModeForwards;
291			group.animations = @[animBounce, animFade];
292
293			[self addAnimation: group forKey: @"animateFound"];
294		}
295	}
296}
297
298- (void) hideMatch {
299	self.sFind = @"";
300	self.positionFind = INVALID_POSITION;
301	self.hidden = YES;
302}
303
304@end
305
306#endif
307
308//--------------------------------------------------------------------------------------------------
309
310@implementation TimerTarget
311
312- (id) init: (void *) target {
313	self = [super init];
314	if (self != nil) {
315		mTarget = target;
316
317		// Get the default notification queue for the thread which created the instance (usually the
318		// main thread). We need that later for idle event processing.
319		NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
320		notificationQueue = [[NSNotificationQueue alloc] initWithNotificationCenter: center];
321		[center addObserver: self selector: @selector(idleTriggered:) name: @"Idle" object: self];
322	}
323	return self;
324}
325
326//--------------------------------------------------------------------------------------------------
327
328- (void) dealloc {
329	NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
330	[center removeObserver: self];
331}
332
333//--------------------------------------------------------------------------------------------------
334
335/**
336 * Method called by owning ScintillaCocoa object when it is destroyed.
337 */
338- (void) ownerDestroyed {
339	mTarget = NULL;
340	notificationQueue = nil;
341}
342
343//--------------------------------------------------------------------------------------------------
344
345/**
346 * Method called by a timer installed by ScintillaCocoa. This two step approach is needed because
347 * a native Obj-C class is required as target for the timer.
348 */
349- (void) timerFired: (NSTimer *) timer {
350	if (mTarget)
351		static_cast<ScintillaCocoa *>(mTarget)->TimerFired(timer);
352}
353
354//--------------------------------------------------------------------------------------------------
355
356/**
357 * Another timer callback for the idle timer.
358 */
359- (void) idleTimerFired: (NSTimer *) timer {
360#pragma unused(timer)
361	// Idle timer event.
362	// Post a new idle notification, which gets executed when the run loop is idle.
363	// Since we are coalescing on name and sender there will always be only one actual notification
364	// even for multiple requests.
365	NSNotification *notification = [NSNotification notificationWithName: @"Idle" object: self];
366	[notificationQueue enqueueNotification: notification
367				  postingStyle: NSPostWhenIdle
368				  coalesceMask: (NSNotificationCoalescingOnName | NSNotificationCoalescingOnSender)
369				      forModes: @[NSDefaultRunLoopMode, NSModalPanelRunLoopMode]];
370}
371
372//--------------------------------------------------------------------------------------------------
373
374/**
375 * Another step for idle events. The timer (for idle events) simply requests a notification on
376 * idle time. Only when this notification is send we actually call back the editor.
377 */
378- (void) idleTriggered: (NSNotification *) notification {
379#pragma unused(notification)
380	if (mTarget)
381		static_cast<ScintillaCocoa *>(mTarget)->IdleTimerFired();
382}
383
384@end
385
386//----------------- ScintillaCocoa -----------------------------------------------------------------
387
388ScintillaCocoa::ScintillaCocoa(ScintillaView *sciView_, SCIContentView *viewContent, SCIMarginView *viewMargin) {
389	vs.marginInside = false;
390
391	// Don't retain since we're owned by view, which would cause a cycle
392	sciView = sciView_;
393	wMain = (__bridge WindowID)viewContent;
394	wMargin = (__bridge WindowID)viewMargin;
395
396	timerTarget = [[TimerTarget alloc] init: this];
397	lastMouseEvent = NULL;
398	delegate = NULL;
399	notifyObj = NULL;
400	notifyProc = NULL;
401	capturedMouse = false;
402	enteredSetScrollingSize = false;
403	scrollSpeed = 1;
404	scrollTicks = 2000;
405	observer = NULL;
406	layerFindIndicator = NULL;
407	imeInteraction = imeInline;
408	for (TickReason tr=tickCaret; tr<=tickPlatform; tr = static_cast<TickReason>(tr+1)) {
409		timers[tr] = nil;
410	}
411	Init();
412}
413
414//--------------------------------------------------------------------------------------------------
415
416ScintillaCocoa::~ScintillaCocoa() {
417	[timerTarget ownerDestroyed];
418}
419
420//--------------------------------------------------------------------------------------------------
421
422/**
423 * Core initialization of the control. Everything that needs to be set up happens here.
424 */
425void ScintillaCocoa::Init() {
426
427	// Tell Scintilla not to buffer: Quartz buffers drawing for us.
428	WndProc(SCI_SETBUFFEREDDRAW, 0, 0);
429
430	// We are working with Unicode exclusively.
431	WndProc(SCI_SETCODEPAGE, SC_CP_UTF8, 0);
432
433	// Add Mac specific key bindings.
434	for (int i = 0; macMapDefault[i].key; i++)
435		kmap.AssignCmdKey(macMapDefault[i].key, macMapDefault[i].modifiers, macMapDefault[i].msg);
436
437}
438
439//--------------------------------------------------------------------------------------------------
440
441/**
442 * We need some clean up. Do it here.
443 */
444void ScintillaCocoa::Finalise() {
445	ObserverRemove();
446	for (TickReason tr=tickCaret; tr<=tickPlatform; tr = static_cast<TickReason>(tr+1)) {
447		FineTickerCancel(tr);
448	}
449	ScintillaBase::Finalise();
450}
451
452//--------------------------------------------------------------------------------------------------
453
454void ScintillaCocoa::UpdateObserver(CFRunLoopObserverRef /* observer */, CFRunLoopActivity /* activity */, void *info) {
455	ScintillaCocoa *sci = static_cast<ScintillaCocoa *>(info);
456	sci->IdleWork();
457}
458
459//--------------------------------------------------------------------------------------------------
460
461/**
462 * Add an observer to the run loop to perform styling as high-priority idle task.
463 */
464
465void ScintillaCocoa::ObserverAdd() {
466	if (!observer) {
467		CFRunLoopObserverContext context;
468		context.version = 0;
469		context.info = this;
470		context.retain = NULL;
471		context.release = NULL;
472		context.copyDescription = NULL;
473
474		CFRunLoopRef mainRunLoop = CFRunLoopGetMain();
475		observer = CFRunLoopObserverCreate(NULL, kCFRunLoopEntry | kCFRunLoopBeforeWaiting,
476						   true, 0, UpdateObserver, &context);
477		CFRunLoopAddObserver(mainRunLoop, observer, kCFRunLoopCommonModes);
478	}
479}
480
481//--------------------------------------------------------------------------------------------------
482
483/**
484 * Remove the run loop observer.
485 */
486void ScintillaCocoa::ObserverRemove() {
487	if (observer) {
488		CFRunLoopRef mainRunLoop = CFRunLoopGetMain();
489		CFRunLoopRemoveObserver(mainRunLoop, observer, kCFRunLoopCommonModes);
490		CFRelease(observer);
491	}
492	observer = NULL;
493}
494
495//--------------------------------------------------------------------------------------------------
496
497void ScintillaCocoa::IdleWork() {
498	Editor::IdleWork();
499	ObserverRemove();
500}
501
502//--------------------------------------------------------------------------------------------------
503
504void ScintillaCocoa::QueueIdleWork(WorkNeeded::workItems items, Sci::Position upTo) {
505	Editor::QueueIdleWork(items, upTo);
506	ObserverAdd();
507}
508
509//--------------------------------------------------------------------------------------------------
510
511/**
512 * Convert a Core Foundation string into a std::string in a particular encoding.
513 */
514
515static std::string EncodedBytesString(CFStringRef cfsRef, CFStringEncoding encoding) {
516	const CFRange rangeAll = {0, CFStringGetLength(cfsRef)};
517	CFIndex usedLen = 0;
518	CFStringGetBytes(cfsRef, rangeAll, encoding, '?', false,
519			 NULL, 0, &usedLen);
520
521	std::string buffer(usedLen, '\0');
522	if (usedLen > 0) {
523		CFStringGetBytes(cfsRef, rangeAll, encoding, '?', false,
524				 reinterpret_cast<UInt8 *>(&buffer[0]), usedLen, NULL);
525	}
526	return buffer;
527}
528
529//--------------------------------------------------------------------------------------------------
530
531/**
532 * Create a Core Foundation string from a string.
533 * This is a simple wrapper that specifies common arguments (the default allocator and
534 * false for isExternalRepresentation) and avoids casting since strings in Scintilla
535 * contain char, not UInt8 (unsigned char).
536 */
537
538static CFStringRef CFStringFromString(const char *s, size_t len, CFStringEncoding encoding) {
539	return CFStringCreateWithBytes(kCFAllocatorDefault,
540				       reinterpret_cast<const UInt8 *>(s),
541				       len, encoding, false);
542}
543
544//--------------------------------------------------------------------------------------------------
545
546/**
547 * Case folders.
548 */
549
550class CaseFolderDBCS : public CaseFolderTable {
551	CFStringEncoding encoding;
552public:
553	explicit CaseFolderDBCS(CFStringEncoding encoding_) : encoding(encoding_) {
554		StandardASCII();
555	}
556	size_t Fold(char *folded, size_t sizeFolded, const char *mixed, size_t lenMixed) override {
557		if ((lenMixed == 1) && (sizeFolded > 0)) {
558			folded[0] = mapping[static_cast<unsigned char>(mixed[0])];
559			return 1;
560		} else {
561			CFStringRef cfsVal = CFStringFromString(mixed, lenMixed, encoding);
562			if (!cfsVal) {
563				folded[0] = '\0';
564				return 1;
565			}
566
567			NSString *sMapped = [(__bridge NSString *)cfsVal stringByFoldingWithOptions: NSCaseInsensitiveSearch
568											     locale: [NSLocale currentLocale]];
569
570			std::string encoded = EncodedBytesString((__bridge CFStringRef)sMapped, encoding);
571
572			size_t lenMapped = encoded.length();
573			if (lenMapped < sizeFolded) {
574				memcpy(folded, encoded.c_str(), lenMapped);
575			} else {
576				folded[0] = '\0';
577				lenMapped = 1;
578			}
579			CFRelease(cfsVal);
580			return lenMapped;
581		}
582	}
583};
584
585CaseFolder *ScintillaCocoa::CaseFolderForEncoding() {
586	if (pdoc->dbcsCodePage == SC_CP_UTF8) {
587		return new CaseFolderUnicode();
588	} else {
589		CFStringEncoding encoding = EncodingFromCharacterSet(IsUnicodeMode(),
590					    vs.styles[STYLE_DEFAULT].characterSet);
591		if (pdoc->dbcsCodePage == 0) {
592			CaseFolderTable *pcf = new CaseFolderTable();
593			pcf->StandardASCII();
594			// Only for single byte encodings
595			for (int i=0x80; i<0x100; i++) {
596				char sCharacter[2] = "A";
597				sCharacter[0] = static_cast<char>(i);
598				CFStringRef cfsVal = CFStringFromString(sCharacter, 1, encoding);
599				if (!cfsVal)
600					continue;
601
602				NSString *sMapped = [(__bridge NSString *)cfsVal stringByFoldingWithOptions: NSCaseInsensitiveSearch
603												     locale: [NSLocale currentLocale]];
604
605				std::string encoded = EncodedBytesString((__bridge CFStringRef)sMapped, encoding);
606
607				if (encoded.length() == 1) {
608					pcf->SetTranslation(sCharacter[0], encoded[0]);
609				}
610
611				CFRelease(cfsVal);
612			}
613			return pcf;
614		} else {
615			return new CaseFolderDBCS(encoding);
616		}
617	}
618}
619
620
621//--------------------------------------------------------------------------------------------------
622
623/**
624 * Case-fold the given string depending on the specified case mapping type.
625 */
626std::string ScintillaCocoa::CaseMapString(const std::string &s, int caseMapping) {
627	if ((s.size() == 0) || (caseMapping == cmSame))
628		return s;
629
630	if (IsUnicodeMode()) {
631		std::string retMapped(s.length() * maxExpansionCaseConversion, 0);
632		size_t lenMapped = CaseConvertString(&retMapped[0], retMapped.length(), s.c_str(), s.length(),
633						     (caseMapping == cmUpper) ? CaseConversionUpper : CaseConversionLower);
634		retMapped.resize(lenMapped);
635		return retMapped;
636	}
637
638	CFStringEncoding encoding = EncodingFromCharacterSet(IsUnicodeMode(),
639				    vs.styles[STYLE_DEFAULT].characterSet);
640
641	CFStringRef cfsVal = CFStringFromString(s.c_str(), s.length(), encoding);
642	if (!cfsVal) {
643		return s;
644	}
645
646	NSString *sMapped;
647	switch (caseMapping) {
648	case cmUpper:
649		sMapped = ((__bridge NSString *)cfsVal).uppercaseString;
650		break;
651	case cmLower:
652		sMapped = ((__bridge NSString *)cfsVal).lowercaseString;
653		break;
654	default:
655		sMapped = (__bridge NSString *)cfsVal;
656	}
657
658	// Back to encoding
659	std::string result = EncodedBytesString((__bridge CFStringRef)sMapped, encoding);
660	CFRelease(cfsVal);
661	return result;
662}
663
664//--------------------------------------------------------------------------------------------------
665
666/**
667 * Cancel all modes, both for base class and any find indicator.
668 */
669void ScintillaCocoa::CancelModes() {
670	ScintillaBase::CancelModes();
671	HideFindIndicator();
672}
673
674//--------------------------------------------------------------------------------------------------
675
676/**
677 * Helper function to get the scrolling view.
678 */
679NSScrollView *ScintillaCocoa::ScrollContainer() const {
680	NSView *container = (__bridge NSView *)(wMain.GetID());
681	return static_cast<NSScrollView *>(container.superview.superview);
682}
683
684//--------------------------------------------------------------------------------------------------
685
686/**
687 * Helper function to get the inner container which represents the actual "canvas" we work with.
688 */
689SCIContentView *ScintillaCocoa::ContentView() {
690	return (__bridge SCIContentView *)(wMain.GetID());
691}
692
693//--------------------------------------------------------------------------------------------------
694
695/**
696 * Return the top left visible point relative to the origin point of the whole document.
697 */
698Scintilla::Point ScintillaCocoa::GetVisibleOriginInMain() const {
699	NSScrollView *scrollView = ScrollContainer();
700	NSRect contentRect = scrollView.contentView.bounds;
701	return Point(static_cast<XYPOSITION>(contentRect.origin.x), static_cast<XYPOSITION>(contentRect.origin.y));
702}
703
704//--------------------------------------------------------------------------------------------------
705
706/**
707 * Instead of returning the size of the inner view we have to return the visible part of it
708 * in order to make scrolling working properly.
709 * The returned value is in document coordinates.
710 */
711PRectangle ScintillaCocoa::GetClientRectangle() const {
712	NSScrollView *scrollView = ScrollContainer();
713	NSSize size = scrollView.contentView.bounds.size;
714	Point origin = GetVisibleOriginInMain();
715	return PRectangle(origin.x, origin.y, static_cast<XYPOSITION>(origin.x+size.width),
716			  static_cast<XYPOSITION>(origin.y + size.height));
717}
718
719//--------------------------------------------------------------------------------------------------
720
721/**
722 * Allow for prepared rectangle
723 */
724PRectangle ScintillaCocoa::GetClientDrawingRectangle() {
725#if MAC_OS_X_VERSION_MAX_ALLOWED > 1080
726	NSView *content = ContentView();
727	if ([content respondsToSelector: @selector(setPreparedContentRect:)]) {
728		NSRect rcPrepared = content.preparedContentRect;
729		if (!NSIsEmptyRect(rcPrepared))
730			return NSRectToPRectangle(rcPrepared);
731	}
732#endif
733	return ScintillaCocoa::GetClientRectangle();
734}
735
736//--------------------------------------------------------------------------------------------------
737
738/**
739 * Converts the given point from base coordinates to local coordinates and at the same time into
740 * a native Point structure. Base coordinates are used for the top window used in the view hierarchy.
741 * Returned value is in view coordinates.
742 */
743Scintilla::Point ScintillaCocoa::ConvertPoint(NSPoint point) {
744	NSView *container = ContentView();
745	NSPoint result = [container convertPoint: point fromView: nil];
746	Scintilla::Point ptOrigin = GetVisibleOriginInMain();
747	return Point(static_cast<XYPOSITION>(result.x - ptOrigin.x), static_cast<XYPOSITION>(result.y - ptOrigin.y));
748}
749
750//--------------------------------------------------------------------------------------------------
751
752/**
753 * Do not clip like superclass as Cocoa is not reporting all of prepared area.
754 */
755void ScintillaCocoa::RedrawRect(PRectangle rc) {
756	if (!rc.Empty())
757		wMain.InvalidateRectangle(rc);
758}
759
760//--------------------------------------------------------------------------------------------------
761
762void ScintillaCocoa::DiscardOverdraw() {
763#if MAC_OS_X_VERSION_MAX_ALLOWED > 1080
764	// If running on 10.9, reset prepared area to visible area
765	NSView *content = ContentView();
766	if ([content respondsToSelector: @selector(setPreparedContentRect:)]) {
767		content.preparedContentRect = content.visibleRect;
768	}
769#endif
770}
771
772//--------------------------------------------------------------------------------------------------
773
774/**
775 * Ensure all of prepared content is also redrawn.
776 */
777void ScintillaCocoa::Redraw() {
778	wMargin.InvalidateAll();
779	DiscardOverdraw();
780	wMain.InvalidateAll();
781}
782
783//--------------------------------------------------------------------------------------------------
784
785/**
786 * A function to directly execute code that would usually go the long way via window messages.
787 * However this is a Windows metaphor and not used here, hence we just call our fake
788 * window proc. The given parameters directly reflect the message parameters used on Windows.
789 *
790 * @param ptr The target which is to be called.
791 * @param iMessage A code that indicates which message was sent.
792 * @param wParam One of the two free parameters for the message. Traditionally a word sized parameter
793 *               (hence the w prefix).
794 * @param lParam The other of the two free parameters. A signed long.
795 */
796sptr_t ScintillaCocoa::DirectFunction(sptr_t ptr, unsigned int iMessage, uptr_t wParam,
797				      sptr_t lParam) {
798	return reinterpret_cast<ScintillaCocoa *>(ptr)->WndProc(iMessage, wParam, lParam);
799}
800
801//--------------------------------------------------------------------------------------------------
802
803/**
804 * This method is very similar to DirectFunction. On Windows it sends a message (not in the Obj-C sense)
805 * to the target window. Here we simply call our fake window proc.
806 */
807sptr_t scintilla_send_message(void *sci, unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
808	ScintillaView *control = (__bridge ScintillaView *)(sci);
809	return [control message: iMessage wParam: wParam lParam: lParam];
810}
811
812//--------------------------------------------------------------------------------------------------
813
814namespace {
815
816/**
817 * The animated find indicator fails with a "bogus layer size" message on macOS 10.13
818 * and causes drawing failures on macOS 10.12.
819 */
820
821bool SupportAnimatedFind() {
822	return std::floor(NSAppKitVersionNumber) < NSAppKitVersionNumber10_12;
823}
824
825}
826
827//--------------------------------------------------------------------------------------------------
828
829/**
830 * That's our fake window procedure. On Windows each window has a dedicated procedure to handle
831 * commands (also used to synchronize UI and background threads), which is not the case in Cocoa.
832 *
833 * Messages handled here are almost solely for special commands of the backend. Everything which
834 * would be system messages on Windows (e.g. for key down, mouse move etc.) are handled by
835 * directly calling appropriate handlers.
836 */
837sptr_t ScintillaCocoa::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
838	try {
839		switch (iMessage) {
840		case SCI_GETDIRECTFUNCTION:
841			return reinterpret_cast<sptr_t>(DirectFunction);
842
843		case SCI_GETDIRECTPOINTER:
844			return reinterpret_cast<sptr_t>(this);
845
846		case SCI_SETBIDIRECTIONAL:
847			bidirectional = static_cast<EditModel::Bidirectional>(wParam);
848			// Invalidate all cached information including layout.
849			DropGraphics(true);
850			InvalidateStyleRedraw();
851			return 0;
852
853		case SCI_TARGETASUTF8:
854			return TargetAsUTF8(CharPtrFromSPtr(lParam));
855
856		case SCI_ENCODEDFROMUTF8:
857			return EncodedFromUTF8(ConstCharPtrFromUPtr(wParam),
858					       CharPtrFromSPtr(lParam));
859
860		case SCI_SETIMEINTERACTION:
861			// Only inline IME supported on Cocoa
862			break;
863
864		case SCI_GRABFOCUS:
865			[ContentView().window makeFirstResponder: ContentView()];
866			break;
867
868		case SCI_SETBUFFEREDDRAW:
869			// Buffered drawing not supported on Cocoa
870			view.bufferedDraw = false;
871			break;
872
873		case SCI_FINDINDICATORSHOW:
874			if (SupportAnimatedFind()) {
875				ShowFindIndicatorForRange(NSMakeRange(wParam, lParam-wParam), YES);
876			}
877			return 0;
878
879		case SCI_FINDINDICATORFLASH:
880			if (SupportAnimatedFind()) {
881				ShowFindIndicatorForRange(NSMakeRange(wParam, lParam-wParam), NO);
882			}
883			return 0;
884
885		case SCI_FINDINDICATORHIDE:
886			HideFindIndicator();
887			return 0;
888
889		case SCI_SETPHASESDRAW: {
890				sptr_t r = ScintillaBase::WndProc(iMessage, wParam, lParam);
891				[sciView updateIndicatorIME];
892				return r;
893			}
894
895		case SCI_GETACCESSIBILITY:
896			return SC_ACCESSIBILITY_ENABLED;
897
898		default:
899			sptr_t r = ScintillaBase::WndProc(iMessage, wParam, lParam);
900
901			return r;
902		}
903	} catch (std::bad_alloc &) {
904		errorStatus = SC_STATUS_BADALLOC;
905	} catch (...) {
906		errorStatus = SC_STATUS_FAILURE;
907	}
908	return 0;
909}
910
911//--------------------------------------------------------------------------------------------------
912
913/**
914 * In Windows lingo this is the handler which handles anything that wasn't handled in the normal
915 * window proc which would usually send the message back to generic window proc that Windows uses.
916 */
917sptr_t ScintillaCocoa::DefWndProc(unsigned int, uptr_t, sptr_t) {
918	return 0;
919}
920
921//--------------------------------------------------------------------------------------------------
922
923/**
924 * Handle any ScintillaCocoa-specific ticking or call superclass.
925 */
926void ScintillaCocoa::TickFor(TickReason reason) {
927	if (reason == tickPlatform) {
928		DragScroll();
929	} else {
930		Editor::TickFor(reason);
931	}
932}
933
934//--------------------------------------------------------------------------------------------------
935
936/**
937 * Is a particular timer currently running?
938 */
939bool ScintillaCocoa::FineTickerRunning(TickReason reason) {
940	return timers[reason] != nil;
941}
942
943//--------------------------------------------------------------------------------------------------
944
945/**
946 * Start a fine-grained timer.
947 */
948void ScintillaCocoa::FineTickerStart(TickReason reason, int millis, int tolerance) {
949	FineTickerCancel(reason);
950	NSTimer *fineTimer = [NSTimer timerWithTimeInterval: millis / 1000.0
951						     target: timerTarget
952						   selector: @selector(timerFired:)
953						   userInfo: nil
954						    repeats: YES];
955	if (tolerance && [fineTimer respondsToSelector: @selector(setTolerance:)]) {
956		fineTimer.tolerance = tolerance / 1000.0;
957	}
958	timers[reason] = fineTimer;
959	[NSRunLoop.currentRunLoop addTimer: fineTimer forMode: NSDefaultRunLoopMode];
960	[NSRunLoop.currentRunLoop addTimer: fineTimer forMode: NSModalPanelRunLoopMode];
961}
962
963//--------------------------------------------------------------------------------------------------
964
965/**
966 * Cancel a fine-grained timer.
967 */
968void ScintillaCocoa::FineTickerCancel(TickReason reason) {
969	if (timers[reason]) {
970		[timers[reason] invalidate];
971		timers[reason] = nil;
972	}
973}
974
975//--------------------------------------------------------------------------------------------------
976
977bool ScintillaCocoa::SetIdle(bool on) {
978	if (idler.state != on) {
979		idler.state = on;
980		if (idler.state) {
981			// Scintilla ticks = milliseconds
982			NSTimer *idleTimer = [NSTimer scheduledTimerWithTimeInterval: timer.tickSize / 1000.0
983									      target: timerTarget
984									    selector: @selector(idleTimerFired:)
985									    userInfo: nil
986									     repeats: YES];
987			[NSRunLoop.currentRunLoop addTimer: idleTimer forMode: NSModalPanelRunLoopMode];
988			idler.idlerID = (__bridge IdlerID)idleTimer;
989		} else if (idler.idlerID != NULL) {
990			[(__bridge NSTimer *)(idler.idlerID) invalidate];
991			idler.idlerID = 0;
992		}
993	}
994	return true;
995}
996
997//--------------------------------------------------------------------------------------------------
998
999void ScintillaCocoa::CopyToClipboard(const SelectionText &selectedText) {
1000	SetPasteboardData([NSPasteboard generalPasteboard], selectedText);
1001}
1002
1003//--------------------------------------------------------------------------------------------------
1004
1005void ScintillaCocoa::Copy() {
1006	if (!sel.Empty()) {
1007		SelectionText selectedText;
1008		CopySelectionRange(&selectedText);
1009		CopyToClipboard(selectedText);
1010	}
1011}
1012
1013//--------------------------------------------------------------------------------------------------
1014
1015bool ScintillaCocoa::CanPaste() {
1016	if (!Editor::CanPaste())
1017		return false;
1018
1019	return GetPasteboardData([NSPasteboard generalPasteboard], NULL);
1020}
1021
1022//--------------------------------------------------------------------------------------------------
1023
1024void ScintillaCocoa::Paste() {
1025	Paste(false);
1026}
1027
1028//--------------------------------------------------------------------------------------------------
1029
1030/**
1031 * Pastes data from the paste board into the editor.
1032 */
1033void ScintillaCocoa::Paste(bool forceRectangular) {
1034	SelectionText selectedText;
1035	bool ok = GetPasteboardData([NSPasteboard generalPasteboard], &selectedText);
1036	if (forceRectangular)
1037		selectedText.rectangular = forceRectangular;
1038
1039	if (!ok || selectedText.Empty())
1040		// No data or no flavor we support.
1041		return;
1042
1043	pdoc->BeginUndoAction();
1044	ClearSelection(false);
1045	InsertPasteShape(selectedText.Data(), selectedText.Length(),
1046			 selectedText.rectangular ? pasteRectangular : pasteStream);
1047	pdoc->EndUndoAction();
1048
1049	Redraw();
1050	EnsureCaretVisible();
1051}
1052
1053//--------------------------------------------------------------------------------------------------
1054
1055void ScintillaCocoa::CTPaint(void *gc, NSRect rc) {
1056#pragma unused(rc)
1057	std::unique_ptr<Surface> surfaceWindow(Surface::Allocate(SC_TECHNOLOGY_DEFAULT));
1058	surfaceWindow->Init(gc, wMain.GetID());
1059	surfaceWindow->SetUnicodeMode(SC_CP_UTF8 == ct.codePage);
1060	surfaceWindow->SetDBCSMode(ct.codePage);
1061	ct.PaintCT(surfaceWindow.get());
1062	surfaceWindow->Release();
1063}
1064
1065@interface CallTipView : NSControl {
1066	ScintillaCocoa *sci;
1067}
1068
1069@end
1070
1071@implementation CallTipView
1072
1073- (NSView *) initWithFrame: (NSRect) frame {
1074	self = [super initWithFrame: frame];
1075
1076	if (self) {
1077		sci = NULL;
1078	}
1079
1080	return self;
1081}
1082
1083
1084- (BOOL) isFlipped {
1085	return YES;
1086}
1087
1088- (void) setSci: (ScintillaCocoa *) sci_ {
1089	sci = sci_;
1090}
1091
1092- (void) drawRect: (NSRect) needsDisplayInRect {
1093	if (sci) {
1094		CGContextRef context = (CGContextRef) [NSGraphicsContext currentContext].graphicsPort;
1095		sci->CTPaint(context, needsDisplayInRect);
1096	}
1097}
1098
1099- (void) mouseDown: (NSEvent *) event {
1100	if (sci) {
1101		sci->CallTipMouseDown(event.locationInWindow);
1102	}
1103}
1104
1105// On OS X, only the key view should modify the cursor so the calltip can't.
1106// This view does not become key so resetCursorRects never called.
1107- (void) resetCursorRects {
1108	//[super resetCursorRects];
1109	//[self addCursorRect: [self bounds] cursor: [NSCursor arrowCursor]];
1110}
1111
1112@end
1113
1114void ScintillaCocoa::CallTipMouseDown(NSPoint pt) {
1115	NSRect rectBounds = ((__bridge NSView *)(ct.wDraw.GetID())).bounds;
1116	Point location(static_cast<XYPOSITION>(pt.x),
1117		       static_cast<XYPOSITION>(rectBounds.size.height - pt.y));
1118	ct.MouseClick(location);
1119	CallTipClick();
1120}
1121
1122static bool HeightDifferent(WindowID wCallTip, PRectangle rc) {
1123	NSWindow *callTip = (__bridge NSWindow *)wCallTip;
1124	CGFloat height = NSHeight(callTip.frame);
1125	return height != rc.Height();
1126}
1127
1128void ScintillaCocoa::CreateCallTipWindow(PRectangle rc) {
1129	if (ct.wCallTip.Created() && HeightDifferent(ct.wCallTip.GetID(), rc)) {
1130		ct.wCallTip.Destroy();
1131	}
1132	if (!ct.wCallTip.Created()) {
1133		NSRect ctRect = NSMakeRect(rc.top, rc.bottom, rc.Width(), rc.Height());
1134		NSWindow *callTip = [[NSWindow alloc] initWithContentRect: ctRect
1135								styleMask: NSWindowStyleMaskBorderless
1136								  backing: NSBackingStoreBuffered
1137								    defer: NO];
1138		[callTip setLevel: NSFloatingWindowLevel];
1139		[callTip setHasShadow: YES];
1140		NSRect ctContent = NSMakeRect(0, 0, rc.Width(), rc.Height());
1141		CallTipView *caption = [[CallTipView alloc] initWithFrame: ctContent];
1142		caption.autoresizingMask = NSViewWidthSizable | NSViewMaxYMargin;
1143		[caption setSci: this];
1144		[callTip.contentView addSubview: caption];
1145		[callTip orderFront: caption];
1146		ct.wCallTip = (__bridge_retained WindowID)callTip;
1147		ct.wDraw = (__bridge WindowID)caption;
1148	}
1149}
1150
1151void ScintillaCocoa::AddToPopUp(const char *label, int cmd, bool enabled) {
1152	NSMenuItem *item;
1153	ScintillaContextMenu *menu = (__bridge ScintillaContextMenu *)(popup.GetID());
1154	[menu setOwner: this];
1155	[menu setAutoenablesItems: NO];
1156
1157	if (cmd == 0) {
1158		item = [NSMenuItem separatorItem];
1159	} else {
1160		item = [[NSMenuItem alloc] init];
1161		item.title = @(label);
1162	}
1163	item.target = menu;
1164	item.action = @selector(handleCommand:);
1165	item.tag = cmd;
1166	item.enabled = enabled;
1167
1168	[menu addItem: item];
1169}
1170
1171// -------------------------------------------------------------------------------------------------
1172
1173void ScintillaCocoa::ClaimSelection() {
1174	// Mac OS X does not have a primary selection.
1175}
1176
1177// -------------------------------------------------------------------------------------------------
1178
1179/**
1180 * Returns the current caret position (which is tracked as an offset into the entire text string)
1181 * as a row:column pair. The result is zero-based.
1182 */
1183NSPoint ScintillaCocoa::GetCaretPosition() {
1184	const Sci::Line line = static_cast<Sci::Line>(
1185		pdoc->LineFromPosition(sel.RangeMain().caret.Position()));
1186	NSPoint result;
1187
1188	result.y = line;
1189	result.x = sel.RangeMain().caret.Position() - pdoc->LineStart(line);
1190	return result;
1191}
1192
1193// -------------------------------------------------------------------------------------------------
1194
1195#pragma mark Drag
1196
1197/**
1198 * Triggered by the tick timer on a regular basis to scroll the content during a drag operation.
1199 */
1200void ScintillaCocoa::DragScroll() {
1201	if (!posDrag.IsValid()) {
1202		scrollSpeed = 1;
1203		scrollTicks = 2000;
1204		return;
1205	}
1206
1207	// TODO: does not work for wrapped lines, fix it.
1208	Sci::Line line = static_cast<Sci::Line>(pdoc->LineFromPosition(posDrag.Position()));
1209	Sci::Line currentVisibleLine = pcs->DisplayFromDoc(line);
1210	Sci::Line lastVisibleLine = std::min(topLine + LinesOnScreen(), pcs->LinesDisplayed()) - 2;
1211
1212	if (currentVisibleLine <= topLine && topLine > 0)
1213		ScrollTo(topLine - scrollSpeed);
1214	else if (currentVisibleLine >= lastVisibleLine)
1215		ScrollTo(topLine + scrollSpeed);
1216	else {
1217		scrollSpeed = 1;
1218		scrollTicks = 2000;
1219		return;
1220	}
1221
1222	// TODO: also handle horizontal scrolling.
1223
1224	if (scrollSpeed == 1) {
1225		scrollTicks -= timer.tickSize;
1226		if (scrollTicks <= 0) {
1227			scrollSpeed = 5;
1228			scrollTicks = 2000;
1229		}
1230	}
1231
1232}
1233
1234//----------------- DragProviderSource -------------------------------------------------------
1235
1236@interface DragProviderSource : NSObject <NSPasteboardItemDataProvider> {
1237	SelectionText selectedText;
1238}
1239
1240@end
1241
1242@implementation DragProviderSource
1243
1244- (id) initWithSelectedText: (const SelectionText *) other {
1245	self = [super init];
1246
1247	if (self) {
1248		selectedText.Copy(*other);
1249	}
1250
1251	return self;
1252}
1253
1254- (void) pasteboard: (NSPasteboard *) pasteboard item: (NSPasteboardItem *) item provideDataForType: (NSString *) type {
1255#pragma unused(item)
1256	if (selectedText.Length() == 0)
1257		return;
1258
1259	if (([type compare: NSPasteboardTypeString] != NSOrderedSame) &&
1260			([type compare: ScintillaRecPboardType] != NSOrderedSame))
1261		return;
1262
1263	CFStringEncoding encoding = EncodingFromCharacterSet(selectedText.codePage == SC_CP_UTF8,
1264				    selectedText.characterSet);
1265
1266	CFStringRef cfsVal = CFStringFromString(selectedText.Data(), selectedText.Length(), encoding);
1267	if (!cfsVal)
1268		return;
1269
1270	if ([type compare: NSPasteboardTypeString] == NSOrderedSame) {
1271		[pasteboard setString: (__bridge NSString *)cfsVal forType: NSStringPboardType];
1272	} else if ([type compare: ScintillaRecPboardType] == NSOrderedSame) {
1273		// This is specific to scintilla, allows us to drag rectangular selections around the document.
1274		if (selectedText.rectangular)
1275			[pasteboard setString: (__bridge NSString *)cfsVal forType: ScintillaRecPboardType];
1276	}
1277
1278	if (cfsVal)
1279		CFRelease(cfsVal);
1280}
1281
1282@end
1283
1284//--------------------------------------------------------------------------------------------------
1285
1286/**
1287 * Called when a drag operation was initiated from within Scintilla.
1288 */
1289void ScintillaCocoa::StartDrag() {
1290	if (sel.Empty())
1291		return;
1292
1293	inDragDrop = ddDragging;
1294
1295	FineTickerStart(tickPlatform, timer.tickSize, 0);
1296
1297	// Put the data to be dragged on the drag pasteboard.
1298	SelectionText selectedText;
1299	NSPasteboard *pasteboard = [NSPasteboard pasteboardWithName: NSDragPboard];
1300	CopySelectionRange(&selectedText);
1301	SetPasteboardData(pasteboard, selectedText);
1302
1303	// calculate the bounds of the selection
1304	PRectangle client = GetTextRectangle();
1305	Sci::Position selStart = sel.RangeMain().Start().Position();
1306	Sci::Position selEnd = sel.RangeMain().End().Position();
1307	Sci::Line startLine = static_cast<Sci::Line>(pdoc->LineFromPosition(selStart));
1308	Sci::Line endLine = static_cast<Sci::Line>(pdoc->LineFromPosition(selEnd));
1309	Point pt;
1310	Sci::Position startPos;
1311	Sci::Position endPos;
1312	Sci::Position ep;
1313	PRectangle rcSel;
1314
1315	if (startLine==endLine && WndProc(SCI_GETWRAPMODE, 0, 0) != SC_WRAP_NONE) {
1316		// Komodo bug http://bugs.activestate.com/show_bug.cgi?id=87571
1317		// Scintilla bug https://sourceforge.net/tracker/?func=detail&atid=102439&aid=3040200&group_id=2439
1318		// If the width on a wrapped-line selection is negative,
1319		// find a better bounding rectangle.
1320
1321		Point ptStart, ptEnd;
1322		startPos = WndProc(SCI_GETLINESELSTARTPOSITION, startLine, 0);
1323		endPos =   WndProc(SCI_GETLINESELENDPOSITION,   startLine, 0);
1324		// step back a position if we're counting the newline
1325		ep =       WndProc(SCI_GETLINEENDPOSITION,      startLine, 0);
1326		if (endPos > ep) endPos = ep;
1327		ptStart = LocationFromPosition(startPos);
1328		ptEnd =   LocationFromPosition(endPos);
1329		if (ptStart.y == ptEnd.y) {
1330			// We're just selecting part of one visible line
1331			rcSel.left = ptStart.x;
1332			rcSel.right = ptEnd.x < client.right ? ptEnd.x : client.right;
1333		} else {
1334			// Find the bounding box.
1335			startPos = WndProc(SCI_POSITIONFROMLINE, startLine, 0);
1336			rcSel.left = LocationFromPosition(startPos).x;
1337			rcSel.right = client.right;
1338		}
1339		rcSel.top = ptStart.y;
1340		rcSel.bottom = ptEnd.y + vs.lineHeight;
1341		if (rcSel.bottom > client.bottom) {
1342			rcSel.bottom = client.bottom;
1343		}
1344	} else {
1345		rcSel.top = rcSel.bottom = rcSel.right = rcSel.left = -1;
1346		for (Sci::Line l = startLine; l <= endLine; l++) {
1347			startPos = WndProc(SCI_GETLINESELSTARTPOSITION, l, 0);
1348			endPos = WndProc(SCI_GETLINESELENDPOSITION, l, 0);
1349			if (endPos == startPos) continue;
1350			// step back a position if we're counting the newline
1351			ep = WndProc(SCI_GETLINEENDPOSITION, l, 0);
1352			if (endPos > ep) endPos = ep;
1353			pt = LocationFromPosition(startPos); // top left of line selection
1354			if (pt.x < rcSel.left || rcSel.left < 0) rcSel.left = pt.x;
1355			if (pt.y < rcSel.top || rcSel.top < 0) rcSel.top = pt.y;
1356			pt = LocationFromPosition(endPos); // top right of line selection
1357			pt.y += vs.lineHeight; // get to the bottom of the line
1358			if (pt.x > rcSel.right || rcSel.right < 0) {
1359				if (pt.x > client.right)
1360					rcSel.right = client.right;
1361				else
1362					rcSel.right = pt.x;
1363			}
1364			if (pt.y > rcSel.bottom || rcSel.bottom < 0) {
1365				if (pt.y > client.bottom)
1366					rcSel.bottom = client.bottom;
1367				else
1368					rcSel.bottom = pt.y;
1369			}
1370		}
1371	}
1372	// must convert to global coordinates for drag regions, but also save the
1373	// image rectangle for further calculations and copy operations
1374
1375	// Prepare drag image.
1376	NSRect selectionRectangle = PRectangleToNSRect(rcSel);
1377
1378	SCIContentView *content = ContentView();
1379
1380	// To get a bitmap of the text we're dragging, we just use Paint on a pixmap surface.
1381	SurfaceImpl sw;
1382	sw.InitPixMap(static_cast<int>(client.Width()), static_cast<int>(client.Height()), NULL, NULL);
1383
1384	const bool lastHideSelection = view.hideSelection;
1385	view.hideSelection = true;
1386	PRectangle imageRect = rcSel;
1387	paintState = painting;
1388	paintingAllText = true;
1389	CGContextRef gcsw = sw.GetContext();
1390	CGContextTranslateCTM(gcsw, -client.left, -client.top);
1391	Paint(&sw, client);
1392	paintState = notPainting;
1393	view.hideSelection = lastHideSelection;
1394
1395	SurfaceImpl pixmap;
1396	pixmap.InitPixMap(static_cast<int>(imageRect.Width()), static_cast<int>(imageRect.Height()), NULL, NULL);
1397	pixmap.SetUnicodeMode(IsUnicodeMode());
1398	pixmap.SetDBCSMode(CodePage());
1399
1400	CGContextRef gc = pixmap.GetContext();
1401	// To make Paint() work on a bitmap, we have to flip our coordinates and translate the origin
1402	CGContextTranslateCTM(gc, 0, imageRect.Height());
1403	CGContextScaleCTM(gc, 1.0, -1.0);
1404
1405	pixmap.CopyImageRectangle(sw, imageRect, PRectangle(0.0f, 0.0f, imageRect.Width(), imageRect.Height()));
1406	// XXX TODO: overwrite any part of the image that is not part of the
1407	//           selection to make it transparent.  right now we just use
1408	//           the full rectangle which may include non-selected text.
1409
1410	NSBitmapImageRep *bitmap = NULL;
1411	CGImageRef imagePixmap = pixmap.CreateImage();
1412	if (imagePixmap)
1413		bitmap = [[NSBitmapImageRep alloc] initWithCGImage: imagePixmap];
1414	CGImageRelease(imagePixmap);
1415
1416	NSImage *image = [[NSImage alloc] initWithSize: selectionRectangle.size];
1417	[image addRepresentation: bitmap];
1418
1419	NSImage *dragImage = [[NSImage alloc] initWithSize: selectionRectangle.size];
1420	dragImage.backgroundColor = [NSColor clearColor];
1421	[dragImage lockFocus];
1422	[image drawAtPoint: NSZeroPoint fromRect: NSZeroRect operation: NSCompositingOperationSourceOver fraction: 0.5];
1423	[dragImage unlockFocus];
1424
1425	NSPoint startPoint;
1426	startPoint.x = selectionRectangle.origin.x + client.left;
1427	startPoint.y = selectionRectangle.origin.y + selectionRectangle.size.height + client.top;
1428
1429	NSPasteboardItem *pbItem = [NSPasteboardItem new];
1430	DragProviderSource *dps = [[DragProviderSource alloc] initWithSelectedText: &selectedText];
1431
1432	NSArray *pbTypes = selectedText.rectangular ?
1433			   @[NSPasteboardTypeString, ScintillaRecPboardType] :
1434			   @[NSPasteboardTypeString];
1435	[pbItem setDataProvider: dps forTypes: pbTypes];
1436	NSDraggingItem *dragItem = [[NSDraggingItem alloc ]initWithPasteboardWriter: pbItem];
1437
1438	NSScrollView *scrollContainer = ScrollContainer();
1439	NSRect contentRect = scrollContainer.contentView.bounds;
1440	NSRect draggingRect = NSOffsetRect(selectionRectangle, contentRect.origin.x, contentRect.origin.y);
1441	[dragItem setDraggingFrame: draggingRect contents: dragImage];
1442	NSDraggingSession *dragSession =
1443		[content beginDraggingSessionWithItems: @[dragItem]
1444						 event: lastMouseEvent
1445						source: content];
1446	dragSession.animatesToStartingPositionsOnCancelOrFail = YES;
1447	dragSession.draggingFormation = NSDraggingFormationNone;
1448}
1449
1450//--------------------------------------------------------------------------------------------------
1451
1452/**
1453 * Called when a drag operation reaches the control which was initiated outside.
1454 */
1455NSDragOperation ScintillaCocoa::DraggingEntered(id <NSDraggingInfo> info) {
1456	FineTickerStart(tickPlatform, timer.tickSize, 0);
1457	return DraggingUpdated(info);
1458}
1459
1460//--------------------------------------------------------------------------------------------------
1461
1462/**
1463 * Called frequently during a drag operation if we are the target. Keep telling the caller
1464 * what drag operation we accept and update the drop caret position to indicate the
1465 * potential insertion point of the dragged data.
1466 */
1467NSDragOperation ScintillaCocoa::DraggingUpdated(id <NSDraggingInfo> info) {
1468	// Convert the drag location from window coordinates to view coordinates and
1469	// from there to a text position to finally set the drag position.
1470	Point location = ConvertPoint([info draggingLocation]);
1471	SetDragPosition(SPositionFromLocation(location));
1472
1473	NSDragOperation sourceDragMask = [info draggingSourceOperationMask];
1474	if (sourceDragMask == NSDragOperationNone)
1475		return sourceDragMask;
1476
1477	NSPasteboard *pasteboard = [info draggingPasteboard];
1478
1479	// Return what type of operation we will perform. Prefer move over copy.
1480	if ([pasteboard.types containsObject: NSStringPboardType] ||
1481			[pasteboard.types containsObject: ScintillaRecPboardType])
1482		return (sourceDragMask & NSDragOperationMove) ? NSDragOperationMove : NSDragOperationCopy;
1483
1484	if ([pasteboard.types containsObject: NSFilenamesPboardType])
1485		return (sourceDragMask & NSDragOperationGeneric);
1486	return NSDragOperationNone;
1487}
1488
1489//--------------------------------------------------------------------------------------------------
1490
1491/**
1492 * Resets the current drag position as we are no longer the drag target.
1493 */
1494void ScintillaCocoa::DraggingExited(id <NSDraggingInfo> info) {
1495#pragma unused(info)
1496	SetDragPosition(SelectionPosition(Sci::invalidPosition));
1497	FineTickerCancel(tickPlatform);
1498	inDragDrop = ddNone;
1499}
1500
1501//--------------------------------------------------------------------------------------------------
1502
1503/**
1504 * Here is where the real work is done. Insert the text from the pasteboard.
1505 */
1506bool ScintillaCocoa::PerformDragOperation(id <NSDraggingInfo> info) {
1507	NSPasteboard *pasteboard = [info draggingPasteboard];
1508
1509	if ([pasteboard.types containsObject: NSFilenamesPboardType]) {
1510		NSArray *files = [pasteboard propertyListForType: NSFilenamesPboardType];
1511		for (NSString* uri in files)
1512			NotifyURIDropped(uri.UTF8String);
1513	} else {
1514		SelectionText text;
1515		GetPasteboardData(pasteboard, &text);
1516
1517		if (text.Length() > 0) {
1518			NSDragOperation operation = [info draggingSourceOperationMask];
1519			bool moving = (operation & NSDragOperationMove) != 0;
1520
1521			DropAt(posDrag, text.Data(), text.Length(), moving, text.rectangular);
1522		};
1523	}
1524
1525	return true;
1526}
1527
1528//--------------------------------------------------------------------------------------------------
1529
1530void ScintillaCocoa::SetPasteboardData(NSPasteboard *board, const SelectionText &selectedText) {
1531	if (selectedText.Length() == 0)
1532		return;
1533
1534	CFStringEncoding encoding = EncodingFromCharacterSet(selectedText.codePage == SC_CP_UTF8,
1535				    selectedText.characterSet);
1536
1537	CFStringRef cfsVal = CFStringFromString(selectedText.Data(), selectedText.Length(), encoding);
1538	if (!cfsVal)
1539		return;
1540
1541	NSArray *pbTypes = selectedText.rectangular ?
1542			   @[NSStringPboardType, ScintillaRecPboardType] :
1543			   @[NSStringPboardType];
1544	[board declareTypes: pbTypes owner: nil];
1545
1546	if (selectedText.rectangular) {
1547		// This is specific to scintilla, allows us to drag rectangular selections around the document.
1548		[board setString: (__bridge NSString *)cfsVal forType: ScintillaRecPboardType];
1549	}
1550
1551	[board setString: (__bridge NSString *)cfsVal forType: NSStringPboardType];
1552
1553	if (cfsVal)
1554		CFRelease(cfsVal);
1555}
1556
1557//--------------------------------------------------------------------------------------------------
1558
1559/**
1560 * Helper method to retrieve the best fitting alternative from the general pasteboard.
1561 */
1562bool ScintillaCocoa::GetPasteboardData(NSPasteboard *board, SelectionText *selectedText) {
1563	NSArray *supportedTypes = @[ScintillaRecPboardType,
1564				    NSStringPboardType];
1565	NSString *bestType = [board availableTypeFromArray: supportedTypes];
1566	NSString *data = [board stringForType: bestType];
1567
1568	if (data != nil) {
1569		if (selectedText != nil) {
1570			CFStringEncoding encoding = EncodingFromCharacterSet(IsUnicodeMode(),
1571						    vs.styles[STYLE_DEFAULT].characterSet);
1572			CFRange rangeAll = {0, static_cast<CFIndex>(data.length)};
1573			CFIndex usedLen = 0;
1574			CFStringGetBytes((CFStringRef)data, rangeAll, encoding, '?',
1575					 false, NULL, 0, &usedLen);
1576
1577			std::vector<UInt8> buffer(usedLen);
1578
1579			CFStringGetBytes((CFStringRef)data, rangeAll, encoding, '?',
1580					 false, buffer.data(), usedLen, NULL);
1581
1582			bool rectangular = bestType == ScintillaRecPboardType;
1583
1584			std::string dest(reinterpret_cast<const char *>(buffer.data()), usedLen);
1585
1586			selectedText->Copy(dest, pdoc->dbcsCodePage,
1587					   vs.styles[STYLE_DEFAULT].characterSet, rectangular, false);
1588		}
1589		return true;
1590	}
1591
1592	return false;
1593}
1594
1595//--------------------------------------------------------------------------------------------------
1596
1597// Returns the target converted to UTF8.
1598// Return the length in bytes.
1599Sci::Position ScintillaCocoa::TargetAsUTF8(char *text) const {
1600	const Sci::Position targetLength = targetRange.Length();
1601	if (IsUnicodeMode()) {
1602		if (text)
1603			pdoc->GetCharRange(text, targetRange.start.Position(), targetLength);
1604	} else {
1605		// Need to convert
1606		const CFStringEncoding encoding = EncodingFromCharacterSet(IsUnicodeMode(),
1607						  vs.styles[STYLE_DEFAULT].characterSet);
1608		const std::string s = RangeText(targetRange.start.Position(), targetRange.end.Position());
1609		CFStringRef cfsVal = CFStringFromString(s.c_str(), s.length(), encoding);
1610		if (!cfsVal) {
1611			return 0;
1612		}
1613
1614		const std::string tmputf = EncodedBytesString(cfsVal, kCFStringEncodingUTF8);
1615
1616		if (text)
1617			memcpy(text, tmputf.c_str(), tmputf.length());
1618		CFRelease(cfsVal);
1619		return tmputf.length();
1620	}
1621	return targetLength;
1622}
1623
1624//--------------------------------------------------------------------------------------------------
1625
1626// Returns the text in the range converted to an NSString.
1627NSString *ScintillaCocoa::RangeTextAsString(NSRange rangePositions) const {
1628	const std::string text = RangeText(rangePositions.location,
1629					   NSMaxRange(rangePositions));
1630	if (IsUnicodeMode()) {
1631		return @(text.c_str());
1632	} else {
1633		// Need to convert
1634		const CFStringEncoding encoding = EncodingFromCharacterSet(IsUnicodeMode(),
1635						  vs.styles[STYLE_DEFAULT].characterSet);
1636		CFStringRef cfsVal = CFStringFromString(text.c_str(), text.length(), encoding);
1637
1638		return (__bridge NSString *)cfsVal;
1639	}
1640}
1641
1642//--------------------------------------------------------------------------------------------------
1643
1644// Return character range of a line.
1645NSRange ScintillaCocoa::RangeForVisibleLine(NSInteger lineVisible) {
1646	const Range posRangeLine = RangeDisplayLine(static_cast<Sci::Line>(lineVisible));
1647	return CharactersFromPositions(NSMakeRange(posRangeLine.First(),
1648				       posRangeLine.Last() - posRangeLine.First()));
1649}
1650
1651//--------------------------------------------------------------------------------------------------
1652
1653// Returns visible line number of a text position in characters.
1654NSInteger ScintillaCocoa::VisibleLineForIndex(NSInteger index) {
1655	const NSRange rangePosition = PositionsFromCharacters(NSMakeRange(index, 0));
1656	const Sci::Line lineVisible = DisplayFromPosition(rangePosition.location);
1657	return lineVisible;
1658}
1659
1660//--------------------------------------------------------------------------------------------------
1661
1662// Returns a rectangle that frames the range for use by the VoiceOver cursor.
1663NSRect ScintillaCocoa::FrameForRange(NSRange rangeCharacters) {
1664	const NSRange posRange = PositionsFromCharacters(rangeCharacters);
1665
1666	NSUInteger rangeEnd = NSMaxRange(posRange);
1667	const bool endsWithLineEnd = rangeCharacters.length &&
1668				     (pdoc->GetColumn(rangeEnd) == 0);
1669
1670	Point ptStart = LocationFromPosition(posRange.location);
1671	const PointEnd peEndRange = static_cast<PointEnd>(peSubLineEnd|peLineEnd);
1672	Point ptEnd = LocationFromPosition(rangeEnd, peEndRange);
1673
1674	NSRect rect = NSMakeRect(ptStart.x, ptStart.y,
1675				 ptEnd.x - ptStart.x,
1676				 ptEnd.y - ptStart.y);
1677
1678	rect.size.width += 2;	// Shows the last character better
1679	if (endsWithLineEnd) {
1680		// Add a block to the right to indicate a line end is selected
1681		rect.size.width += 20;
1682	}
1683
1684	rect.size.height += vs.lineHeight;
1685
1686	// Adjust for margin and scroll
1687	rect.origin.x = rect.origin.x - vs.textStart + vs.fixedColumnWidth;
1688
1689	return rect;
1690}
1691
1692//--------------------------------------------------------------------------------------------------
1693
1694// Returns a rectangle that frames the range for use by the VoiceOver cursor.
1695NSRect ScintillaCocoa::GetBounds() const {
1696	return PRectangleToNSRect(GetClientRectangle());
1697}
1698
1699//--------------------------------------------------------------------------------------------------
1700
1701// Translates a UTF8 string into the document encoding.
1702// Return the length of the result in bytes.
1703Sci::Position ScintillaCocoa::EncodedFromUTF8(const char *utf8, char *encoded) const {
1704	const size_t inputLength = (lengthForEncode >= 0) ? lengthForEncode : strlen(utf8);
1705	if (IsUnicodeMode()) {
1706		if (encoded)
1707			memcpy(encoded, utf8, inputLength);
1708		return inputLength;
1709	} else {
1710		// Need to convert
1711		const CFStringEncoding encoding = EncodingFromCharacterSet(IsUnicodeMode(),
1712						  vs.styles[STYLE_DEFAULT].characterSet);
1713
1714		CFStringRef cfsVal = CFStringFromString(utf8, inputLength, kCFStringEncodingUTF8);
1715		const std::string sEncoded = EncodedBytesString(cfsVal, encoding);
1716		if (encoded)
1717			memcpy(encoded, sEncoded.c_str(), sEncoded.length());
1718		CFRelease(cfsVal);
1719		return sEncoded.length();
1720	}
1721}
1722
1723//--------------------------------------------------------------------------------------------------
1724
1725void ScintillaCocoa::SetMouseCapture(bool on) {
1726	capturedMouse = on;
1727}
1728
1729//--------------------------------------------------------------------------------------------------
1730
1731bool ScintillaCocoa::HaveMouseCapture() {
1732	return capturedMouse;
1733}
1734
1735//--------------------------------------------------------------------------------------------------
1736
1737/**
1738 * Synchronously paint a rectangle of the window.
1739 */
1740bool ScintillaCocoa::SyncPaint(void *gc, PRectangle rc) {
1741	paintState = painting;
1742	rcPaint = rc;
1743	PRectangle rcText = GetTextRectangle();
1744	paintingAllText = rcPaint.Contains(rcText);
1745	std::unique_ptr<Surface> sw(Surface::Allocate(SC_TECHNOLOGY_DEFAULT));
1746	CGContextSetAllowsAntialiasing((CGContextRef)gc,
1747				       vs.extraFontFlag != SC_EFF_QUALITY_NON_ANTIALIASED);
1748	CGContextSetAllowsFontSmoothing((CGContextRef)gc,
1749					vs.extraFontFlag == SC_EFF_QUALITY_LCD_OPTIMIZED);
1750	CGContextSetAllowsFontSubpixelPositioning((CGContextRef)gc,
1751			vs.extraFontFlag == SC_EFF_QUALITY_DEFAULT ||
1752			vs.extraFontFlag == SC_EFF_QUALITY_LCD_OPTIMIZED);
1753	sw->Init(gc, wMain.GetID());
1754	Paint(sw.get(), rc);
1755	const bool succeeded = paintState != paintAbandoned;
1756	sw->Release();
1757	paintState = notPainting;
1758	if (!succeeded) {
1759		NSView *marginView = (__bridge NSView *)(wMargin.GetID());
1760		[marginView setNeedsDisplay: YES];
1761	}
1762	return succeeded;
1763}
1764
1765//--------------------------------------------------------------------------------------------------
1766
1767/**
1768 * Paint the margin into the SCIMarginView space.
1769 */
1770void ScintillaCocoa::PaintMargin(NSRect aRect) {
1771	CGContextRef gc = (CGContextRef) [NSGraphicsContext currentContext].graphicsPort;
1772
1773	PRectangle rc = NSRectToPRectangle(aRect);
1774	rcPaint = rc;
1775	std::unique_ptr<Surface> sw(Surface::Allocate(SC_TECHNOLOGY_DEFAULT));
1776	if (sw) {
1777		CGContextSetAllowsAntialiasing(gc,
1778					       vs.extraFontFlag != SC_EFF_QUALITY_NON_ANTIALIASED);
1779		CGContextSetAllowsFontSmoothing(gc,
1780						vs.extraFontFlag == SC_EFF_QUALITY_LCD_OPTIMIZED);
1781		CGContextSetAllowsFontSubpixelPositioning(gc,
1782				vs.extraFontFlag == SC_EFF_QUALITY_DEFAULT ||
1783				vs.extraFontFlag == SC_EFF_QUALITY_LCD_OPTIMIZED);
1784		sw->Init(gc, wMargin.GetID());
1785		PaintSelMargin(sw.get(), rc);
1786		sw->Release();
1787	}
1788}
1789
1790//--------------------------------------------------------------------------------------------------
1791
1792/**
1793 * Prepare for drawing.
1794 *
1795 * @param rect The area that will be drawn, given in the sender's coordinate system.
1796 */
1797void ScintillaCocoa::WillDraw(NSRect rect) {
1798	RefreshStyleData();
1799	PRectangle rcWillDraw = NSRectToPRectangle(rect);
1800	const Sci::Position posAfterArea = PositionAfterArea(rcWillDraw);
1801	const Sci::Position posAfterMax = PositionAfterMaxStyling(posAfterArea, true);
1802	pdoc->StyleToAdjustingLineDuration(posAfterMax);
1803	StartIdleStyling(posAfterMax < posAfterArea);
1804	NotifyUpdateUI();
1805	if (WrapLines(WrapScope::wsVisible)) {
1806		// Wrap may have reduced number of lines so more lines may need to be styled
1807		const Sci::Position posAfterAreaWrapped = PositionAfterArea(rcWillDraw);
1808		pdoc->EnsureStyledTo(posAfterAreaWrapped);
1809		// The wrapping process has changed the height of some lines so redraw all.
1810		Redraw();
1811	}
1812}
1813
1814//--------------------------------------------------------------------------------------------------
1815
1816/**
1817 * ScrollText is empty because scrolling is handled by the NSScrollView.
1818 */
1819void ScintillaCocoa::ScrollText(Sci::Line) {
1820}
1821
1822//--------------------------------------------------------------------------------------------------
1823
1824/**
1825 * Modifies the vertical scroll position to make the current top line show up as such.
1826 */
1827void ScintillaCocoa::SetVerticalScrollPos() {
1828	NSScrollView *scrollView = ScrollContainer();
1829	if (scrollView) {
1830		NSClipView *clipView = scrollView.contentView;
1831		NSRect contentRect = clipView.bounds;
1832		[clipView scrollToPoint: NSMakePoint(contentRect.origin.x, topLine * vs.lineHeight)];
1833		[scrollView reflectScrolledClipView: clipView];
1834	}
1835}
1836
1837//--------------------------------------------------------------------------------------------------
1838
1839/**
1840 * Modifies the horizontal scroll position to match xOffset.
1841 */
1842void ScintillaCocoa::SetHorizontalScrollPos() {
1843	PRectangle textRect = GetTextRectangle();
1844
1845	int maxXOffset = scrollWidth - static_cast<int>(textRect.Width());
1846	if (maxXOffset < 0)
1847		maxXOffset = 0;
1848	if (xOffset > maxXOffset)
1849		xOffset = maxXOffset;
1850	NSScrollView *scrollView = ScrollContainer();
1851	if (scrollView) {
1852		NSClipView *clipView = scrollView.contentView;
1853		NSRect contentRect = clipView.bounds;
1854		[clipView scrollToPoint: NSMakePoint(xOffset, contentRect.origin.y)];
1855		[scrollView reflectScrolledClipView: clipView];
1856	}
1857	MoveFindIndicatorWithBounce(NO);
1858}
1859
1860//--------------------------------------------------------------------------------------------------
1861
1862/**
1863 * Used to adjust both scrollers to reflect the current scroll range and position in the editor.
1864 * Arguments no longer used as NSScrollView handles details of scroll bar sizes.
1865 *
1866 * @param nMax Number of lines in the editor.
1867 * @param nPage Number of lines per scroll page.
1868 * @return True if there was a change, otherwise false.
1869 */
1870bool ScintillaCocoa::ModifyScrollBars(Sci::Line nMax, Sci::Line nPage) {
1871#pragma unused(nMax, nPage)
1872	return SetScrollingSize();
1873}
1874
1875bool ScintillaCocoa::SetScrollingSize(void) {
1876	bool changes = false;
1877	SCIContentView *inner = ContentView();
1878	if (!enteredSetScrollingSize) {
1879		enteredSetScrollingSize = true;
1880		NSScrollView *scrollView = ScrollContainer();
1881		NSClipView *clipView = ScrollContainer().contentView;
1882		NSRect clipRect = clipView.bounds;
1883		CGFloat docHeight = pcs->LinesDisplayed() * vs.lineHeight;
1884		if (!endAtLastLine)
1885			docHeight += (int(scrollView.bounds.size.height / vs.lineHeight)-3) * vs.lineHeight;
1886		// Allow extra space so that last scroll position places whole line at top
1887		int clipExtra = int(clipRect.size.height) % vs.lineHeight;
1888		docHeight += clipExtra;
1889		// Ensure all of clipRect covered by Scintilla drawing
1890		if (docHeight < clipRect.size.height)
1891			docHeight = clipRect.size.height;
1892		CGFloat docWidth = scrollWidth;
1893		bool showHorizontalScroll = horizontalScrollBarVisible &&
1894					    !Wrapping();
1895		if (!showHorizontalScroll)
1896			docWidth = clipRect.size.width;
1897		NSRect contentRect = {{0, 0}, {docWidth, docHeight}};
1898		NSRect contentRectNow = inner.frame;
1899		changes = (contentRect.size.width != contentRectNow.size.width) ||
1900			  (contentRect.size.height != contentRectNow.size.height);
1901		if (changes) {
1902			inner.frame = contentRect;
1903		}
1904		scrollView.hasVerticalScroller = verticalScrollBarVisible;
1905		scrollView.hasHorizontalScroller = showHorizontalScroll;
1906		SetVerticalScrollPos();
1907		enteredSetScrollingSize = false;
1908	}
1909	[sciView setMarginWidth: vs.fixedColumnWidth];
1910	return changes;
1911}
1912
1913//--------------------------------------------------------------------------------------------------
1914
1915void ScintillaCocoa::Resize() {
1916	SetScrollingSize();
1917	ChangeSize();
1918}
1919
1920//--------------------------------------------------------------------------------------------------
1921
1922/**
1923 * Update fields to match scroll position after receiving a notification that the user has scrolled.
1924 */
1925void ScintillaCocoa::UpdateForScroll() {
1926	Point ptOrigin = GetVisibleOriginInMain();
1927	xOffset = static_cast<int>(ptOrigin.x);
1928	Sci::Line newTop = std::min(static_cast<Sci::Line>(ptOrigin.y / vs.lineHeight), MaxScrollPos());
1929	SetTopLine(newTop);
1930}
1931
1932//--------------------------------------------------------------------------------------------------
1933
1934/**
1935 * Register a delegate that will be called for notifications and commands.
1936 * This provides similar functionality to RegisterNotifyCallback but in an
1937 * Objective C way.
1938 *
1939 * @param delegate_ A pointer to an object that implements ScintillaNotificationProtocol.
1940 */
1941
1942void ScintillaCocoa::SetDelegate(id<ScintillaNotificationProtocol> delegate_) {
1943	delegate = delegate_;
1944}
1945
1946//--------------------------------------------------------------------------------------------------
1947
1948/**
1949 * Used to register a callback function for a given window. This is used to emulate the way
1950 * Windows notifies other controls (mainly up in the view hierarchy) about certain events.
1951 *
1952 * @param windowid A handle to a window. That value is generic and can be anything. It is passed
1953 *                 through to the callback.
1954 * @param callback The callback function to be used for future notifications. If NULL then no
1955 *                 notifications will be sent anymore.
1956 */
1957void ScintillaCocoa::RegisterNotifyCallback(intptr_t windowid, SciNotifyFunc callback) {
1958	notifyObj = windowid;
1959	notifyProc = callback;
1960}
1961
1962//--------------------------------------------------------------------------------------------------
1963
1964void ScintillaCocoa::NotifyChange() {
1965	if (notifyProc != NULL)
1966		notifyProc(notifyObj, WM_COMMAND, Platform::LongFromTwoShorts(static_cast<short>(GetCtrlID()), SCEN_CHANGE),
1967			   (uintptr_t) this);
1968}
1969
1970//--------------------------------------------------------------------------------------------------
1971
1972void ScintillaCocoa::NotifyFocus(bool focus) {
1973	if (commandEvents && notifyProc)
1974		notifyProc(notifyObj, WM_COMMAND, Platform::LongFromTwoShorts(static_cast<short>(GetCtrlID()),
1975				(focus ? SCEN_SETFOCUS : SCEN_KILLFOCUS)),
1976			   (uintptr_t) this);
1977
1978	Editor::NotifyFocus(focus);
1979}
1980
1981//--------------------------------------------------------------------------------------------------
1982
1983/**
1984 * Used to send a notification (as WM_NOTIFY call) to the procedure, which has been set by the call
1985 * to RegisterNotifyCallback (so it is not necessarily the parent window).
1986 *
1987 * @param scn The notification to send.
1988 */
1989void ScintillaCocoa::NotifyParent(SCNotification scn) {
1990	scn.nmhdr.hwndFrom = (void *) this;
1991	scn.nmhdr.idFrom = GetCtrlID();
1992	if (notifyProc != NULL)
1993		notifyProc(notifyObj, WM_NOTIFY, GetCtrlID(), (uintptr_t) &scn);
1994	if (delegate)
1995		[delegate notification: &scn];
1996	if (scn.nmhdr.code == SCN_UPDATEUI) {
1997		NSView *content = ContentView();
1998		if (scn.updated & SC_UPDATE_CONTENT) {
1999			NSAccessibilityPostNotification(content, NSAccessibilityValueChangedNotification);
2000		}
2001		if (scn.updated & SC_UPDATE_SELECTION) {
2002			NSAccessibilityPostNotification(content, NSAccessibilitySelectedTextChangedNotification);
2003		}
2004	}
2005}
2006
2007//--------------------------------------------------------------------------------------------------
2008
2009void ScintillaCocoa::NotifyURIDropped(const char *uri) {
2010	SCNotification scn;
2011	scn.nmhdr.code = SCN_URIDROPPED;
2012	scn.text = uri;
2013
2014	NotifyParent(scn);
2015}
2016
2017//--------------------------------------------------------------------------------------------------
2018
2019bool ScintillaCocoa::HasSelection() {
2020	return !sel.Empty();
2021}
2022
2023//--------------------------------------------------------------------------------------------------
2024
2025bool ScintillaCocoa::CanUndo() {
2026	return pdoc->CanUndo();
2027}
2028
2029//--------------------------------------------------------------------------------------------------
2030
2031bool ScintillaCocoa::CanRedo() {
2032	return pdoc->CanRedo();
2033}
2034
2035//--------------------------------------------------------------------------------------------------
2036
2037void ScintillaCocoa::TimerFired(NSTimer *timer) {
2038	for (TickReason tr=tickCaret; tr<=tickPlatform; tr = static_cast<TickReason>(tr+1)) {
2039		if (timers[tr] == timer) {
2040			TickFor(tr);
2041		}
2042	}
2043}
2044
2045//--------------------------------------------------------------------------------------------------
2046
2047void ScintillaCocoa::IdleTimerFired() {
2048	bool more = Idle();
2049	if (!more)
2050		SetIdle(false);
2051}
2052
2053//--------------------------------------------------------------------------------------------------
2054
2055/**
2056 * Main entry point for drawing the control.
2057 *
2058 * @param rect The area to paint, given in the sender's coordinate system.
2059 * @param gc The context we can use to paint.
2060 */
2061bool ScintillaCocoa::Draw(NSRect rect, CGContextRef gc) {
2062	return SyncPaint(gc, NSRectToPRectangle(rect));
2063}
2064
2065//--------------------------------------------------------------------------------------------------
2066
2067/**
2068 * Helper function to translate OS X key codes to Scintilla key codes.
2069 */
2070static inline UniChar KeyTranslate(UniChar unicodeChar, NSEventModifierFlags modifierFlags) {
2071	switch (unicodeChar) {
2072	case NSDownArrowFunctionKey:
2073		return SCK_DOWN;
2074	case NSUpArrowFunctionKey:
2075		return SCK_UP;
2076	case NSLeftArrowFunctionKey:
2077		return SCK_LEFT;
2078	case NSRightArrowFunctionKey:
2079		return SCK_RIGHT;
2080	case NSHomeFunctionKey:
2081		return SCK_HOME;
2082	case NSEndFunctionKey:
2083		return SCK_END;
2084	case NSPageUpFunctionKey:
2085		return SCK_PRIOR;
2086	case NSPageDownFunctionKey:
2087		return SCK_NEXT;
2088	case NSDeleteFunctionKey:
2089		return SCK_DELETE;
2090	case NSInsertFunctionKey:
2091		return SCK_INSERT;
2092	case '\n':
2093	case 3:
2094		return SCK_RETURN;
2095	case 27:
2096		return SCK_ESCAPE;
2097	case '+':
2098		if (modifierFlags & NSEventModifierFlagNumericPad)
2099			return SCK_ADD;
2100		else
2101			return unicodeChar;
2102	case '-':
2103		if (modifierFlags & NSEventModifierFlagNumericPad)
2104			return SCK_SUBTRACT;
2105		else
2106			return unicodeChar;
2107	case '/':
2108		if (modifierFlags & NSEventModifierFlagNumericPad)
2109			return SCK_DIVIDE;
2110		else
2111			return unicodeChar;
2112	case 127:
2113		return SCK_BACK;
2114	case '\t':
2115	case 25: // Shift tab, return to unmodified tab and handle that via modifiers.
2116		return SCK_TAB;
2117	default:
2118		return unicodeChar;
2119	}
2120}
2121
2122//--------------------------------------------------------------------------------------------------
2123
2124/**
2125 * Translate NSEvent modifier flags into SCI_* modifier flags.
2126 *
2127 * @param modifiers An integer bit set of NSSEvent modifier flags.
2128 * @return A set of SCI_* modifier flags.
2129 */
2130static int TranslateModifierFlags(NSUInteger modifiers) {
2131	// Signal Control as SCI_META
2132	return
2133		(((modifiers & NSEventModifierFlagShift) != 0) ? SCI_SHIFT : 0) |
2134		(((modifiers & NSEventModifierFlagCommand) != 0) ? SCI_CTRL : 0) |
2135		(((modifiers & NSEventModifierFlagOption) != 0) ? SCI_ALT : 0) |
2136		(((modifiers & NSEventModifierFlagControl) != 0) ? SCI_META : 0);
2137}
2138
2139//--------------------------------------------------------------------------------------------------
2140
2141/**
2142 * Main keyboard input handling method. It is called for any key down event, including function keys,
2143 * numeric keypad input and whatnot.
2144 *
2145 * @param event The event instance associated with the key down event.
2146 * @return True if the input was handled, false otherwise.
2147 */
2148bool ScintillaCocoa::KeyboardInput(NSEvent *event) {
2149	// For now filter out function keys.
2150	NSString *input = event.charactersIgnoringModifiers;
2151
2152	bool handled = false;
2153
2154	// Handle each entry individually. Usually we only have one entry anyway.
2155	for (size_t i = 0; i < input.length; i++) {
2156		const UniChar originalKey = [input characterAtIndex: i];
2157		NSEventModifierFlags modifierFlags = event.modifierFlags;
2158
2159		UniChar key = KeyTranslate(originalKey, modifierFlags);
2160
2161		bool consumed = false; // Consumed as command?
2162
2163		if (KeyDownWithModifiers(key, TranslateModifierFlags(modifierFlags), &consumed))
2164			handled = true;
2165		if (consumed)
2166			handled = true;
2167	}
2168
2169	return handled;
2170}
2171
2172//--------------------------------------------------------------------------------------------------
2173
2174/**
2175 * Used to insert already processed text provided by the Cocoa text input system.
2176 */
2177ptrdiff_t ScintillaCocoa::InsertText(NSString *input, CharacterSource charSource) {
2178	if ([input length] == 0) {
2179		return 0;
2180	}
2181
2182	// There may be multiple characters in input so loop over them
2183	if (IsUnicodeMode()) {
2184		// There may be non-BMP characters as 2 elements in NSString so
2185		// convert to UTF-8 and use UTF8BytesOfLead.
2186		std::string encoded = EncodedBytesString((__bridge CFStringRef)input,
2187							 kCFStringEncodingUTF8);
2188		std::string_view sv = encoded;
2189		while (sv.length()) {
2190			const unsigned char leadByte = sv[0];
2191			const unsigned int bytesInCharacter = UTF8BytesOfLead[leadByte];
2192			InsertCharacter(sv.substr(0, bytesInCharacter), charSource);
2193			sv.remove_prefix(bytesInCharacter);
2194		}
2195		return encoded.length();
2196	} else {
2197		const CFStringEncoding encoding = EncodingFromCharacterSet(IsUnicodeMode(),
2198									   vs.styles[STYLE_DEFAULT].characterSet);
2199		ptrdiff_t lengthInserted = 0;
2200		for (NSInteger i = 0; i < [input length]; i++) {
2201			NSString *character = [input substringWithRange:NSMakeRange(i, 1)];
2202			std::string encoded = EncodedBytesString((__bridge CFStringRef)character,
2203								 encoding);
2204			lengthInserted += encoded.length();
2205			InsertCharacter(encoded, charSource);
2206		}
2207
2208		return lengthInserted;
2209	}
2210}
2211
2212//--------------------------------------------------------------------------------------------------
2213
2214/**
2215 * Convert from a range of characters to a range of bytes.
2216 */
2217NSRange ScintillaCocoa::PositionsFromCharacters(NSRange rangeCharacters) const {
2218	Sci::Position start = pdoc->GetRelativePositionUTF16(0, rangeCharacters.location);
2219	if (start == INVALID_POSITION)
2220		start = pdoc->Length();
2221	Sci::Position end = pdoc->GetRelativePositionUTF16(start, rangeCharacters.length);
2222	if (end == INVALID_POSITION)
2223		end = pdoc->Length();
2224	return NSMakeRange(start, end - start);
2225}
2226
2227//--------------------------------------------------------------------------------------------------
2228
2229/**
2230 * Convert from a range of characters from a range of bytes.
2231 */
2232NSRange ScintillaCocoa::CharactersFromPositions(NSRange rangePositions) const {
2233	const Sci::Position start = pdoc->CountUTF16(0, rangePositions.location);
2234	const Sci::Position len = pdoc->CountUTF16(rangePositions.location,
2235					  NSMaxRange(rangePositions));
2236	return NSMakeRange(start, len);
2237}
2238
2239//--------------------------------------------------------------------------------------------------
2240
2241/**
2242 * Used to ensure that only one selection is active for input composition as composition
2243 * does not support multi-typing.
2244 */
2245void ScintillaCocoa::SelectOnlyMainSelection() {
2246	sel.SetSelection(sel.RangeMain());
2247	Redraw();
2248}
2249
2250//--------------------------------------------------------------------------------------------------
2251
2252/**
2253 * Convert virtual space before selection into real space.
2254 */
2255void ScintillaCocoa::ConvertSelectionVirtualSpace() {
2256	ClearBeforeTentativeStart();
2257}
2258
2259//--------------------------------------------------------------------------------------------------
2260
2261/**
2262 * Erase all selected text and return whether the selection is now empty.
2263 * The selection may not be empty if the selection contained protected text.
2264 */
2265bool ScintillaCocoa::ClearAllSelections() {
2266	ClearSelection(true);
2267	return sel.Empty();
2268}
2269
2270//--------------------------------------------------------------------------------------------------
2271
2272/**
2273 * Start composing for IME.
2274 */
2275void ScintillaCocoa::CompositionStart() {
2276	if (!sel.Empty()) {
2277		NSLog(@"Selection not empty when starting composition");
2278	}
2279	pdoc->TentativeStart();
2280}
2281
2282//--------------------------------------------------------------------------------------------------
2283
2284/**
2285 * Commit the IME text.
2286 */
2287void ScintillaCocoa::CompositionCommit() {
2288	pdoc->TentativeCommit();
2289	pdoc->DecorationSetCurrentIndicator(INDICATOR_IME);
2290	pdoc->DecorationFillRange(0, 0, pdoc->Length());
2291}
2292
2293//--------------------------------------------------------------------------------------------------
2294
2295/**
2296 * Remove the IME text.
2297 */
2298void ScintillaCocoa::CompositionUndo() {
2299	pdoc->TentativeUndo();
2300}
2301
2302//--------------------------------------------------------------------------------------------------
2303/**
2304 * When switching documents discard any incomplete character composition state as otherwise tries to
2305 * act on the new document.
2306 */
2307void ScintillaCocoa::SetDocPointer(Document *document) {
2308	// Drop input composition.
2309	NSTextInputContext *inctxt = [NSTextInputContext currentInputContext];
2310	[inctxt discardMarkedText];
2311	SCIContentView *inner = ContentView();
2312	[inner unmarkText];
2313	Editor::SetDocPointer(document);
2314}
2315
2316//--------------------------------------------------------------------------------------------------
2317
2318/**
2319 * Convert NSEvent timestamp NSTimeInterval into unsigned int milliseconds wanted by Editor methods.
2320 */
2321
2322namespace {
2323
2324unsigned int TimeOfEvent(NSEvent *event) {
2325	return static_cast<unsigned int>(event.timestamp * 1000);
2326}
2327
2328}
2329
2330//--------------------------------------------------------------------------------------------------
2331
2332/**
2333 * Called by the owning view when the mouse pointer enters the control.
2334 */
2335void ScintillaCocoa::MouseEntered(NSEvent *event) {
2336	if (!HaveMouseCapture()) {
2337		WndProc(SCI_SETCURSOR, (long int)SC_CURSORNORMAL, 0);
2338
2339		// Mouse location is given in screen coordinates and might also be outside of our bounds.
2340		Point location = ConvertPoint(event.locationInWindow);
2341		ButtonMoveWithModifiers(location,
2342					TimeOfEvent(event),
2343					TranslateModifierFlags(event.modifierFlags));
2344	}
2345}
2346
2347//--------------------------------------------------------------------------------------------------
2348
2349void ScintillaCocoa::MouseExited(NSEvent * /* event */) {
2350	// Nothing to do here.
2351}
2352
2353//--------------------------------------------------------------------------------------------------
2354
2355void ScintillaCocoa::MouseDown(NSEvent *event) {
2356	Point location = ConvertPoint(event.locationInWindow);
2357	ButtonDownWithModifiers(location,
2358				TimeOfEvent(event),
2359				TranslateModifierFlags(event.modifierFlags));
2360}
2361
2362void ScintillaCocoa::RightMouseDown(NSEvent *event) {
2363	Point location = ConvertPoint(event.locationInWindow);
2364	RightButtonDownWithModifiers(location,
2365				     TimeOfEvent(event),
2366				     TranslateModifierFlags(event.modifierFlags));
2367}
2368
2369//--------------------------------------------------------------------------------------------------
2370
2371void ScintillaCocoa::MouseMove(NSEvent *event) {
2372	lastMouseEvent = event;
2373
2374	ButtonMoveWithModifiers(ConvertPoint(event.locationInWindow),
2375				TimeOfEvent(event),
2376				TranslateModifierFlags(event.modifierFlags));
2377}
2378
2379//--------------------------------------------------------------------------------------------------
2380
2381void ScintillaCocoa::MouseUp(NSEvent *event) {
2382	ButtonUpWithModifiers(ConvertPoint(event.locationInWindow),
2383		 TimeOfEvent(event),
2384		 TranslateModifierFlags(event.modifierFlags));
2385}
2386
2387//--------------------------------------------------------------------------------------------------
2388
2389void ScintillaCocoa::MouseWheel(NSEvent *event) {
2390	bool command = (event.modifierFlags & NSEventModifierFlagCommand) != 0;
2391	int dY = 0;
2392
2393	// In order to make scrolling with larger offset smoother we scroll less lines the larger the
2394	// delta value is.
2395	if (event.deltaY < 0)
2396		dY = -static_cast<int>(sqrt(-10.0 * event.deltaY));
2397	else
2398		dY = static_cast<int>(sqrt(10.0 * event.deltaY));
2399
2400	if (command) {
2401		// Zoom! We play with the font sizes in the styles.
2402		// Number of steps/line is ignored, we just care if sizing up or down.
2403		if (dY > 0.5)
2404			KeyCommand(SCI_ZOOMIN);
2405		else if (dY < -0.5)
2406			KeyCommand(SCI_ZOOMOUT);
2407	} else {
2408	}
2409}
2410
2411//--------------------------------------------------------------------------------------------------
2412
2413// Helper methods for NSResponder actions.
2414
2415void ScintillaCocoa::SelectAll() {
2416	Editor::SelectAll();
2417}
2418
2419void ScintillaCocoa::DeleteBackward() {
2420	KeyDownWithModifiers(SCK_BACK, 0, nil);
2421}
2422
2423void ScintillaCocoa::Cut() {
2424	Editor::Cut();
2425}
2426
2427void ScintillaCocoa::Undo() {
2428	Editor::Undo();
2429}
2430
2431void ScintillaCocoa::Redo() {
2432	Editor::Redo();
2433}
2434
2435//--------------------------------------------------------------------------------------------------
2436
2437bool ScintillaCocoa::ShouldDisplayPopupOnMargin() {
2438	return displayPopupMenu == SC_POPUP_ALL;
2439}
2440
2441bool ScintillaCocoa::ShouldDisplayPopupOnText() {
2442	return displayPopupMenu == SC_POPUP_ALL || displayPopupMenu == SC_POPUP_TEXT;
2443}
2444
2445/**
2446 * Creates and returns a popup menu, which is then displayed by the Cocoa framework.
2447 */
2448NSMenu *ScintillaCocoa::CreateContextMenu(NSEvent * /* event */) {
2449	// Call ScintillaBase to create the context menu.
2450	ContextMenu(Point(0, 0));
2451
2452	return (__bridge NSMenu *)(popup.GetID());
2453}
2454
2455//--------------------------------------------------------------------------------------------------
2456
2457/**
2458 * An intermediate function to forward context menu commands from the menu action handler to
2459 * scintilla.
2460 */
2461void ScintillaCocoa::HandleCommand(NSInteger command) {
2462	Command(static_cast<int>(command));
2463}
2464
2465//--------------------------------------------------------------------------------------------------
2466
2467void ScintillaCocoa::ActiveStateChanged(bool isActive) {
2468	// If the window is being deactivated, lose the focus and turn off the ticking
2469	if (!isActive) {
2470		DropCaret();
2471		//SetFocusState( false );
2472		FineTickerCancel(tickCaret);
2473	} else {
2474		ShowCaretAtCurrentPosition();
2475	}
2476}
2477
2478//--------------------------------------------------------------------------------------------------
2479
2480/**
2481 * When the window is about to move, the calltip and autcoimpletion stay in the same spot,
2482 * so cancel them.
2483 */
2484void ScintillaCocoa::WindowWillMove() {
2485	AutoCompleteCancel();
2486	ct.CallTipCancel();
2487}
2488
2489// If building with old SDK, need to define version number for 10.8
2490#ifndef NSAppKitVersionNumber10_8
2491#define NSAppKitVersionNumber10_8 1187
2492#endif
2493
2494//--------------------------------------------------------------------------------------------------
2495
2496void ScintillaCocoa::ShowFindIndicatorForRange(NSRange charRange, BOOL retaining) {
2497#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5
2498	NSView *content = ContentView();
2499	if (!layerFindIndicator) {
2500		layerFindIndicator = [[FindHighlightLayer alloc] init];
2501		[content setWantsLayer: YES];
2502		layerFindIndicator.geometryFlipped = content.layer.geometryFlipped;
2503		if (std::floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_8) {
2504			// Content layer is unflipped on 10.9, but the indicator shows wrong unless flipped
2505			layerFindIndicator.geometryFlipped = YES;
2506		}
2507		[content.layer addSublayer: layerFindIndicator];
2508	}
2509	[layerFindIndicator removeAnimationForKey: @"animateFound"];
2510
2511	if (charRange.length) {
2512		CFStringEncoding encoding = EncodingFromCharacterSet(IsUnicodeMode(),
2513					    vs.styles[STYLE_DEFAULT].characterSet);
2514		std::vector<char> buffer(charRange.length);
2515		pdoc->GetCharRange(&buffer[0], charRange.location, charRange.length);
2516
2517		CFStringRef cfsFind = CFStringFromString(&buffer[0], charRange.length, encoding);
2518		layerFindIndicator.sFind = (__bridge NSString *)cfsFind;
2519		if (cfsFind)
2520			CFRelease(cfsFind);
2521		layerFindIndicator.retaining = retaining;
2522		layerFindIndicator.positionFind = charRange.location;
2523		// SCI_GETSTYLEAT reports a signed byte but want an unsigned to index into styles
2524		const char styleByte = static_cast<char>(WndProc(SCI_GETSTYLEAT, charRange.location, 0));
2525		const long style = static_cast<unsigned char>(styleByte);
2526		std::vector<char> bufferFontName(WndProc(SCI_STYLEGETFONT, style, 0) + 1);
2527		WndProc(SCI_STYLEGETFONT, style, (sptr_t)&bufferFontName[0]);
2528		layerFindIndicator.sFont = @(&bufferFontName[0]);
2529
2530		layerFindIndicator.fontSize = WndProc(SCI_STYLEGETSIZEFRACTIONAL, style, 0) /
2531					      (float)SC_FONT_SIZE_MULTIPLIER;
2532		layerFindIndicator.widthText = WndProc(SCI_POINTXFROMPOSITION, 0, charRange.location + charRange.length) -
2533					       WndProc(SCI_POINTXFROMPOSITION, 0, charRange.location);
2534		layerFindIndicator.heightLine = WndProc(SCI_TEXTHEIGHT, 0, 0);
2535		MoveFindIndicatorWithBounce(YES);
2536	} else {
2537		[layerFindIndicator hideMatch];
2538	}
2539#endif
2540}
2541
2542void ScintillaCocoa::MoveFindIndicatorWithBounce(BOOL bounce) {
2543#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5
2544	if (layerFindIndicator) {
2545		CGPoint ptText = CGPointMake(
2546					 WndProc(SCI_POINTXFROMPOSITION, 0, layerFindIndicator.positionFind),
2547					 WndProc(SCI_POINTYFROMPOSITION, 0, layerFindIndicator.positionFind));
2548		ptText.x = ptText.x - vs.fixedColumnWidth + xOffset;
2549		ptText.y += topLine * vs.lineHeight;
2550		if (!layerFindIndicator.geometryFlipped) {
2551			NSView *content = ContentView();
2552			ptText.y = content.bounds.size.height - ptText.y;
2553		}
2554		[layerFindIndicator animateMatch: ptText bounce: bounce];
2555	}
2556#endif
2557}
2558
2559void ScintillaCocoa::HideFindIndicator() {
2560#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5
2561	if (layerFindIndicator) {
2562		[layerFindIndicator hideMatch];
2563	}
2564#endif
2565}
2566
2567
2568