1
2/**
3 * Implementation of the native Cocoa View that serves as container for the scintilla parts.
4 * @file ScintillaView.mm
5 *
6 * Created by Mike Lischke.
7 *
8 * Copyright 2011, 2013, Oracle and/or its affiliates. All rights reserved.
9 * Copyright 2009, 2011 Sun Microsystems, Inc. All rights reserved.
10 * This file is dual licensed under LGPL v2.1 and the Scintilla license (http://www.scintilla.org/License.txt).
11 */
12
13#include <cmath>
14
15#include <string_view>
16#include <vector>
17
18#import "Platform.h"
19#import "ScintillaView.h"
20#import "ScintillaCocoa.h"
21
22using namespace Scintilla;
23
24// Add backend property to ScintillaView as a private category.
25// Specified here as backend accessed by SCIMarginView and SCIContentView.
26@interface ScintillaView()
27@property(nonatomic, readonly) Scintilla::ScintillaCocoa *backend;
28@end
29
30// Two additional cursors we need, which aren't provided by Cocoa.
31static NSCursor *reverseArrowCursor;
32static NSCursor *waitCursor;
33
34NSString *const SCIUpdateUINotification = @"SCIUpdateUI";
35
36/**
37 * Provide an NSCursor object that matches the Window::Cursor enumeration.
38 */
39static NSCursor *cursorFromEnum(Window::Cursor cursor) {
40	switch (cursor) {
41	case Window::cursorText:
42		return [NSCursor IBeamCursor];
43	case Window::cursorArrow:
44		return [NSCursor arrowCursor];
45	case Window::cursorWait:
46		return waitCursor;
47	case Window::cursorHoriz:
48		return [NSCursor resizeLeftRightCursor];
49	case Window::cursorVert:
50		return [NSCursor resizeUpDownCursor];
51	case Window::cursorReverseArrow:
52		return reverseArrowCursor;
53	case Window::cursorUp:
54	default:
55		return [NSCursor arrowCursor];
56	}
57}
58
59@implementation SCIScrollView
60- (void) tile {
61	[super tile];
62
63#if defined(MAC_OS_X_VERSION_10_14)
64	if (std::floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_13) {
65		NSRect frame = self.contentView.frame;
66		frame.origin.x = self.verticalRulerView.requiredThickness;
67		frame.size.width -= frame.origin.x;
68		self.contentView.frame = frame;
69	}
70#endif
71}
72@end
73
74// Add marginWidth and owner properties as a private category.
75@interface SCIMarginView()
76@property(assign) int marginWidth;
77@property(nonatomic, weak) ScintillaView *owner;
78@end
79
80@implementation SCIMarginView {
81	int marginWidth;
82	ScintillaView *__weak owner;
83	NSMutableArray *currentCursors;
84}
85
86@synthesize marginWidth, owner;
87
88- (instancetype) initWithScrollView: (NSScrollView *) aScrollView {
89	self = [super initWithScrollView: aScrollView orientation: NSVerticalRuler];
90	if (self != nil) {
91		owner = nil;
92		marginWidth = 20;
93		currentCursors = [NSMutableArray arrayWithCapacity: 0];
94		for (size_t i=0; i<=SC_MAX_MARGIN; i++) {
95			[currentCursors addObject: reverseArrowCursor];
96		}
97		self.clientView = aScrollView.documentView;
98		if ([self respondsToSelector: @selector(setAccessibilityLabel:)])
99			self.accessibilityLabel = @"Scintilla Margin";
100	}
101	return self;
102}
103
104
105- (void) setFrame: (NSRect) frame {
106	super.frame = frame;
107
108	[self.window invalidateCursorRectsForView: self];
109}
110
111- (CGFloat) requiredThickness {
112	return marginWidth;
113}
114
115- (void) drawHashMarksAndLabelsInRect: (NSRect) aRect {
116	if (owner) {
117		NSRect contentRect = self.scrollView.contentView.bounds;
118		NSRect marginRect = self.bounds;
119		// Ensure paint to bottom of view to avoid glitches
120		if (marginRect.size.height > contentRect.size.height) {
121			// Legacy scroll bar mode leaves a poorly painted corner
122			aRect = marginRect;
123		}
124		owner.backend->PaintMargin(aRect);
125	}
126}
127
128/**
129 * Called by the framework if it wants to show a context menu for the margin.
130 */
131- (NSMenu *) menuForEvent: (NSEvent *) theEvent {
132	NSMenu *menu = [owner menuForEvent: theEvent];
133	if (menu) {
134		return menu;
135	} else if (owner.backend->ShouldDisplayPopupOnMargin()) {
136		return owner.backend->CreateContextMenu(theEvent);
137	} else {
138		return nil;
139	}
140}
141
142- (void) mouseDown: (NSEvent *) theEvent {
143	NSClipView *textView = self.scrollView.contentView;
144	[textView.window makeFirstResponder: textView];
145	owner.backend->MouseDown(theEvent);
146}
147
148- (void) rightMouseDown: (NSEvent *) theEvent {
149	[NSMenu popUpContextMenu: [self menuForEvent: theEvent] withEvent: theEvent forView: self];
150
151	owner.backend->RightMouseDown(theEvent);
152}
153
154- (void) mouseDragged: (NSEvent *) theEvent {
155	owner.backend->MouseMove(theEvent);
156}
157
158- (void) mouseMoved: (NSEvent *) theEvent {
159	owner.backend->MouseMove(theEvent);
160}
161
162- (void) mouseUp: (NSEvent *) theEvent {
163	owner.backend->MouseUp(theEvent);
164}
165
166// Not a simple button so return failure
167- (BOOL) accessibilityPerformPress {
168	return NO;
169}
170
171/**
172 * This method is called to give us the opportunity to define our mouse sensitive rectangle.
173 */
174- (void) resetCursorRects {
175	[super resetCursorRects];
176
177	int x = 0;
178	NSRect marginRect = self.bounds;
179	size_t co = currentCursors.count;
180	for (size_t i=0; i<co; i++) {
181		long cursType = owner.backend->WndProc(SCI_GETMARGINCURSORN, i, 0);
182		long width =owner.backend->WndProc(SCI_GETMARGINWIDTHN, i, 0);
183		NSCursor *cc = cursorFromEnum(static_cast<Window::Cursor>(cursType));
184		currentCursors[i] = cc;
185		marginRect.origin.x = x;
186		marginRect.size.width = width;
187		[self addCursorRect: marginRect cursor: cc];
188		[cc setOnMouseEntered: YES];
189		x += width;
190	}
191}
192
193@end
194
195// Add owner property as a private category.
196@interface SCIContentView()
197@property(nonatomic, weak) ScintillaView *owner;
198@end
199
200@implementation SCIContentView {
201	ScintillaView *__weak mOwner;
202	NSCursor *mCurrentCursor;
203	NSTrackingArea *trackingArea;
204
205	// Set when we are in composition mode and partial input is displayed.
206	NSRange mMarkedTextRange;
207}
208
209@synthesize owner = mOwner;
210
211//--------------------------------------------------------------------------------------------------
212
213- (NSView *) initWithFrame: (NSRect) frame {
214	self = [super initWithFrame: frame];
215
216	if (self != nil) {
217		// Some initialization for our view.
218		mCurrentCursor = [NSCursor arrowCursor];
219		trackingArea = nil;
220		mMarkedTextRange = NSMakeRange(NSNotFound, 0);
221
222		[self registerForDraggedTypes: @[NSStringPboardType, ScintillaRecPboardType, NSFilenamesPboardType]];
223
224		// Set up accessibility in the text role
225		if ([self respondsToSelector: @selector(setAccessibilityElement:)]) {
226			self.accessibilityElement = TRUE;
227			self.accessibilityEnabled = TRUE;
228			self.accessibilityLabel = NSLocalizedString(@"Scintilla", nil);	// No real localization
229			self.accessibilityRoleDescription = @"source code editor";
230			self.accessibilityRole = NSAccessibilityTextAreaRole;
231			self.accessibilityIdentifier = @"Scintilla";
232		}
233	}
234
235	return self;
236}
237
238//--------------------------------------------------------------------------------------------------
239
240/**
241 * When the view is resized or scrolled we need to update our tracking area.
242 */
243- (void) updateTrackingAreas {
244	if (trackingArea) {
245		[self removeTrackingArea: trackingArea];
246	}
247
248	int opts = (NSTrackingActiveAlways | NSTrackingInVisibleRect | NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved);
249	trackingArea = [[NSTrackingArea alloc] initWithRect: self.bounds
250						    options: opts
251						      owner: self
252						   userInfo: nil];
253	[self addTrackingArea: trackingArea];
254	[super updateTrackingAreas];
255}
256
257//--------------------------------------------------------------------------------------------------
258
259/**
260 * When the view is resized we need to let the backend know.
261 */
262- (void) setFrame: (NSRect) frame {
263	super.frame = frame;
264
265	mOwner.backend->Resize();
266}
267
268//--------------------------------------------------------------------------------------------------
269
270/**
271 * Called by the backend if a new cursor must be set for the view.
272 */
273- (void) setCursor: (int) cursor {
274	Window::Cursor eCursor = (Window::Cursor)cursor;
275	mCurrentCursor = cursorFromEnum(eCursor);
276
277	// Trigger recreation of the cursor rectangle(s).
278	[self.window invalidateCursorRectsForView: self];
279	[mOwner updateMarginCursors];
280}
281
282//--------------------------------------------------------------------------------------------------
283
284/**
285 * This method is called to give us the opportunity to define our mouse sensitive rectangle.
286 */
287- (void) resetCursorRects {
288	[super resetCursorRects];
289
290	// We only have one cursor rect: our bounds.
291	const NSRect visibleBounds = mOwner.backend->GetBounds();
292	[self addCursorRect: visibleBounds cursor: mCurrentCursor];
293	[mCurrentCursor setOnMouseEntered: YES];
294}
295
296//--------------------------------------------------------------------------------------------------
297
298/**
299 * Called before repainting.
300 */
301- (void) viewWillDraw {
302	if (!mOwner) {
303		[super viewWillDraw];
304		return;
305	}
306
307	const NSRect *rects;
308	NSInteger nRects = 0;
309	[self getRectsBeingDrawn: &rects count: &nRects];
310	if (nRects > 0) {
311		NSRect rectUnion = rects[0];
312		for (int i=0; i<nRects; i++) {
313			rectUnion = NSUnionRect(rectUnion, rects[i]);
314		}
315		mOwner.backend->WillDraw(rectUnion);
316	}
317	[super viewWillDraw];
318}
319
320//--------------------------------------------------------------------------------------------------
321
322/**
323 * Called before responsive scrolling overdraw.
324 */
325- (void) prepareContentInRect: (NSRect) rect {
326	if (mOwner)
327		mOwner.backend->WillDraw(rect);
328#if MAC_OS_X_VERSION_MAX_ALLOWED > 1080
329	[super prepareContentInRect: rect];
330#endif
331}
332
333//--------------------------------------------------------------------------------------------------
334
335/**
336 * Gets called by the runtime when the view needs repainting.
337 */
338- (void) drawRect: (NSRect) rect {
339	CGContextRef context = (CGContextRef) [NSGraphicsContext currentContext].graphicsPort;
340
341	if (!mOwner.backend->Draw(rect, context)) {
342		dispatch_async(dispatch_get_main_queue(), ^ {
343			[self setNeedsDisplay: YES];
344		});
345	}
346}
347
348//--------------------------------------------------------------------------------------------------
349
350/**
351 * Windows uses a client coordinate system where the upper left corner is the origin in a window
352 * (and so does Scintilla). We have to adjust for that. However by returning YES here, we are
353 * already done with that.
354 * Note that because of returning YES here most coordinates we use now (e.g. for painting,
355 * invalidating rectangles etc.) are given with +Y pointing down!
356 */
357- (BOOL) isFlipped {
358	return YES;
359}
360
361//--------------------------------------------------------------------------------------------------
362
363- (BOOL) isOpaque {
364	return YES;
365}
366
367//--------------------------------------------------------------------------------------------------
368
369/**
370 * Implement the "click through" behavior by telling the caller we accept the first mouse event too.
371 */
372- (BOOL) acceptsFirstMouse: (NSEvent *) theEvent {
373#pragma unused(theEvent)
374	return YES;
375}
376
377//--------------------------------------------------------------------------------------------------
378
379/**
380 * Make this view accepting events as first responder.
381 */
382- (BOOL) acceptsFirstResponder {
383	return YES;
384}
385
386//--------------------------------------------------------------------------------------------------
387
388/**
389 * Called by the framework if it wants to show a context menu for the editor.
390 */
391- (NSMenu *) menuForEvent: (NSEvent *) theEvent {
392	NSMenu *menu = [mOwner menuForEvent: theEvent];
393	if (menu) {
394		return menu;
395	} else if (mOwner.backend->ShouldDisplayPopupOnText()) {
396		return mOwner.backend->CreateContextMenu(theEvent);
397	} else {
398		return nil;
399	}
400}
401
402//--------------------------------------------------------------------------------------------------
403
404// Adoption of NSTextInputClient protocol.
405
406- (NSAttributedString *) attributedSubstringForProposedRange: (NSRange) aRange actualRange: (NSRangePointer) actualRange {
407	const NSInteger lengthCharacters = self.accessibilityNumberOfCharacters;
408	if (aRange.location > lengthCharacters) {
409		return nil;
410	}
411	const NSRange posRange = mOwner.backend->PositionsFromCharacters(aRange);
412	// The backend validated aRange and may have removed characters beyond the end of the document.
413	const NSRange charRange = mOwner.backend->CharactersFromPositions(posRange);
414	if (!NSEqualRanges(aRange, charRange)) {
415		*actualRange = charRange;
416	}
417
418	[mOwner message: SCI_SETTARGETRANGE wParam: posRange.location lParam: NSMaxRange(posRange)];
419	std::string text([mOwner message: SCI_TARGETASUTF8], 0);
420	[mOwner message: SCI_TARGETASUTF8 wParam: 0 lParam: reinterpret_cast<sptr_t>(&text[0])];
421	text = FixInvalidUTF8(text);
422	NSString *result = @(text.c_str());
423	NSMutableAttributedString *asResult = [[NSMutableAttributedString alloc] initWithString: result];
424
425	const NSRange rangeAS = NSMakeRange(0, asResult.length);
426	// SCI_GETSTYLEAT reports a signed byte but want an unsigned to index into styles
427	const char styleByte = static_cast<char>([mOwner message: SCI_GETSTYLEAT wParam: posRange.location]);
428	const long style = static_cast<unsigned char>(styleByte);
429	std::string fontName([mOwner message: SCI_STYLEGETFONT wParam: style lParam: 0], 0);
430	[mOwner message: SCI_STYLEGETFONT wParam: style lParam: (sptr_t)&fontName[0]];
431	const CGFloat fontSize = [mOwner message: SCI_STYLEGETSIZEFRACTIONAL wParam: style] / 100.0f;
432	NSString *sFontName = @(fontName.c_str());
433	NSFont *font = [NSFont fontWithName: sFontName size: fontSize];
434	if (font) {
435		[asResult addAttribute: NSFontAttributeName value: font range: rangeAS];
436	}
437
438	return asResult;
439}
440
441//--------------------------------------------------------------------------------------------------
442
443- (NSUInteger) characterIndexForPoint: (NSPoint) point {
444	const NSRect rectPoint = {point, NSZeroSize};
445	const NSRect rectInWindow = [self.window convertRectFromScreen: rectPoint];
446	const NSRect rectLocal = [self.superview.superview convertRect: rectInWindow fromView: nil];
447
448	const long position = [mOwner message: SCI_CHARPOSITIONFROMPOINT
449				       wParam: rectLocal.origin.x
450				       lParam: rectLocal.origin.y];
451	if (position == INVALID_POSITION) {
452		return NSNotFound;
453	} else {
454		const NSRange index = mOwner.backend->CharactersFromPositions(NSMakeRange(position, 0));
455		return index.location;
456	}
457}
458
459//--------------------------------------------------------------------------------------------------
460
461- (void) doCommandBySelector: (SEL) selector {
462#pragma clang diagnostic push
463#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
464	if ([self respondsToSelector: selector])
465		[self performSelector: selector withObject: nil];
466#pragma clang diagnostic pop
467}
468
469//--------------------------------------------------------------------------------------------------
470
471- (NSRect) firstRectForCharacterRange: (NSRange) aRange actualRange: (NSRangePointer) actualRange {
472#pragma unused(actualRange)
473	const NSRange posRange = mOwner.backend->PositionsFromCharacters(aRange);
474
475	NSRect rect;
476	rect.origin.x = [mOwner message: SCI_POINTXFROMPOSITION wParam: 0 lParam: posRange.location];
477	rect.origin.y = [mOwner message: SCI_POINTYFROMPOSITION wParam: 0 lParam: posRange.location];
478	const NSUInteger rangeEnd = NSMaxRange(posRange);
479	rect.size.width = [mOwner message: SCI_POINTXFROMPOSITION wParam: 0 lParam: rangeEnd] - rect.origin.x;
480	rect.size.height = [mOwner message: SCI_POINTYFROMPOSITION wParam: 0 lParam: rangeEnd] - rect.origin.y;
481	rect.size.height += [mOwner message: SCI_TEXTHEIGHT wParam: 0 lParam: 0];
482	const NSRect rectInWindow = [self.superview.superview convertRect: rect toView: nil];
483	const NSRect rectScreen = [self.window convertRectToScreen: rectInWindow];
484
485	return rectScreen;
486}
487
488//--------------------------------------------------------------------------------------------------
489
490- (BOOL) hasMarkedText {
491	return mMarkedTextRange.length > 0;
492}
493
494//--------------------------------------------------------------------------------------------------
495
496/**
497 * General text input. Used to insert new text at the current input position, replacing the current
498 * selection if there is any.
499 * First removes the replacementRange.
500 */
501- (void) insertText: (id) aString replacementRange: (NSRange) replacementRange {
502	if ((mMarkedTextRange.location != NSNotFound) && (replacementRange.location != NSNotFound)) {
503		NSLog(@"Trying to insertText when there is both a marked range and a replacement range");
504	}
505
506	// Remove any previously marked text first.
507	mOwner.backend->CompositionUndo();
508	if (mMarkedTextRange.location != NSNotFound) {
509		const NSRange posRangeMark = mOwner.backend->PositionsFromCharacters(mMarkedTextRange);
510		[mOwner message: SCI_SETEMPTYSELECTION wParam: posRangeMark.location];
511	}
512	mMarkedTextRange = NSMakeRange(NSNotFound, 0);
513
514	if (replacementRange.location == (NSNotFound-1))
515		// This occurs when the accent popup is visible and menu selected.
516		// Its replacing a non-existent position so do nothing.
517		return;
518
519	if (replacementRange.location != NSNotFound) {
520		const NSRange posRangeReplacement = mOwner.backend->PositionsFromCharacters(replacementRange);
521		[mOwner message: SCI_DELETERANGE
522			 wParam: posRangeReplacement.location
523			 lParam: posRangeReplacement.length];
524		[mOwner message: SCI_SETEMPTYSELECTION wParam: posRangeReplacement.location];
525	}
526
527	NSString *newText = @"";
528	if ([aString isKindOfClass: [NSString class]])
529		newText = (NSString *) aString;
530	else if ([aString isKindOfClass: [NSAttributedString class]])
531		newText = (NSString *) [aString string];
532
533	mOwner.backend->InsertText(newText, EditModel::CharacterSource::directInput);
534}
535
536//--------------------------------------------------------------------------------------------------
537
538- (NSRange) markedRange {
539	return mMarkedTextRange;
540}
541
542//--------------------------------------------------------------------------------------------------
543
544- (NSRange) selectedRange {
545	const NSRange posRangeSel = [mOwner selectedRangePositions];
546	if (posRangeSel.length == 0) {
547		NSTextInputContext *tic = [NSTextInputContext currentInputContext];
548		// Chinese input causes malloc crash when empty selection returned with actual
549		// position so return NSNotFound.
550		// If this is applied to European input, it stops the accented character
551		// chooser from appearing.
552		// May need to add more input source names.
553		if ([tic.selectedKeyboardInputSource
554				isEqualToString: @"com.apple.inputmethod.TCIM.Cangjie"]) {
555			return NSMakeRange(NSNotFound, 0);
556		}
557	}
558	return mOwner.backend->CharactersFromPositions(posRangeSel);
559}
560
561//--------------------------------------------------------------------------------------------------
562
563/**
564 * Called by the input manager to set text which might be combined with further input to form
565 * the final text (e.g. composition of ^ and a to â).
566 *
567 * @param aString The text to insert, either what has been marked already or what is selected already
568 *                or simply added at the current insertion point. Depending on what is available.
569 * @param range The range of the new text to select (given relative to the insertion point of the new text).
570 * @param replacementRange The range to remove before insertion.
571 */
572- (void) setMarkedText: (id) aString selectedRange: (NSRange) range replacementRange: (NSRange) replacementRange {
573	NSString *newText = @"";
574	if ([aString isKindOfClass: [NSString class]])
575		newText = (NSString *) aString;
576	else if ([aString isKindOfClass: [NSAttributedString class]])
577		newText = (NSString *) [aString string];
578
579	// Replace marked text if there is one.
580	if (mMarkedTextRange.length > 0) {
581		mOwner.backend->CompositionUndo();
582		if (replacementRange.location != NSNotFound) {
583			// This situation makes no sense and has not occurred in practice.
584			NSLog(@"Can not handle a replacement range when there is also a marked range");
585		} else {
586			replacementRange = mMarkedTextRange;
587			const NSRange posRangeMark = mOwner.backend->PositionsFromCharacters(mMarkedTextRange);
588			[mOwner message: SCI_SETEMPTYSELECTION wParam: posRangeMark.location];
589		}
590	} else {
591		// Must perform deletion before entering composition mode or else
592		// both document and undo history will not contain the deleted text
593		// leading to an inaccurate and unusable undo history.
594
595		// Convert selection virtual space into real space
596		mOwner.backend->ConvertSelectionVirtualSpace();
597
598		if (replacementRange.location != NSNotFound) {
599			const NSRange posRangeReplacement = mOwner.backend->PositionsFromCharacters(replacementRange);
600			[mOwner message: SCI_DELETERANGE
601				 wParam: posRangeReplacement.location
602				 lParam: posRangeReplacement.length];
603		} else { // No marked or replacement range, so replace selection
604			if (!mOwner.backend->ScintillaCocoa::ClearAllSelections()) {
605				// Some of the selection is protected so can not perform composition here
606				return;
607			}
608			// Ensure only a single selection.
609			mOwner.backend->SelectOnlyMainSelection();
610			const NSRange posRangeSel = [mOwner selectedRangePositions];
611			replacementRange = mOwner.backend->CharactersFromPositions(posRangeSel);
612		}
613	}
614
615	// To support IME input to multiple selections, the following code would
616	// need to insert newText at each selection, mark each piece of new text and then
617	// select range relative to each insertion.
618
619	if (newText.length) {
620		// Switching into composition.
621		mOwner.backend->CompositionStart();
622
623		NSRange posRangeCurrent = mOwner.backend->PositionsFromCharacters(NSMakeRange(replacementRange.location, 0));
624		// Note: Scintilla internally works almost always with bytes instead chars, so we need to take
625		//       this into account when determining selection ranges and such.
626		ptrdiff_t lengthInserted = mOwner.backend->InsertText(newText, EditModel::CharacterSource::tentativeInput);
627		posRangeCurrent.length = lengthInserted;
628		mMarkedTextRange = mOwner.backend->CharactersFromPositions(posRangeCurrent);
629		// Mark the just inserted text. Keep the marked range for later reset.
630		[mOwner setGeneralProperty: SCI_SETINDICATORCURRENT value: INDICATOR_IME];
631		[mOwner setGeneralProperty: SCI_INDICATORFILLRANGE
632				 parameter: posRangeCurrent.location
633				     value: posRangeCurrent.length];
634	} else {
635		mMarkedTextRange = NSMakeRange(NSNotFound, 0);
636		// Re-enable undo action collection if composition ended (indicated by an empty mark string).
637		mOwner.backend->CompositionCommit();
638	}
639
640	// Select the part which is indicated in the given range. It does not scroll the caret into view.
641	if (range.length > 0) {
642		// range is in characters so convert to bytes for selection.
643		range.location += replacementRange.location;
644		NSRange posRangeSelect = mOwner.backend->PositionsFromCharacters(range);
645		[mOwner setGeneralProperty: SCI_SETSELECTION parameter: NSMaxRange(posRangeSelect) value: posRangeSelect.location];
646	}
647}
648
649//--------------------------------------------------------------------------------------------------
650
651- (void) unmarkText {
652	if (mMarkedTextRange.length > 0) {
653		mOwner.backend->CompositionCommit();
654		mMarkedTextRange = NSMakeRange(NSNotFound, 0);
655	}
656}
657
658//--------------------------------------------------------------------------------------------------
659
660- (NSArray *) validAttributesForMarkedText {
661	return @[];
662}
663
664// End of the NSTextInputClient protocol adoption.
665
666//--------------------------------------------------------------------------------------------------
667
668/**
669 * Generic input method. It is used to pass on keyboard input to Scintilla. The control itself only
670 * handles shortcuts. The input is then forwarded to the Cocoa text input system, which in turn does
671 * its own input handling (character composition via NSTextInputClient protocol):
672 */
673- (void) keyDown: (NSEvent *) theEvent {
674	bool handled = false;
675	if (mMarkedTextRange.length == 0)
676		handled = mOwner.backend->KeyboardInput(theEvent);
677	if (!handled) {
678		NSArray *events = @[theEvent];
679		[self interpretKeyEvents: events];
680	}
681}
682
683//--------------------------------------------------------------------------------------------------
684
685- (void) mouseDown: (NSEvent *) theEvent {
686	mOwner.backend->MouseDown(theEvent);
687}
688
689//--------------------------------------------------------------------------------------------------
690
691- (void) mouseDragged: (NSEvent *) theEvent {
692	mOwner.backend->MouseMove(theEvent);
693}
694
695//--------------------------------------------------------------------------------------------------
696
697- (void) mouseUp: (NSEvent *) theEvent {
698	mOwner.backend->MouseUp(theEvent);
699}
700
701//--------------------------------------------------------------------------------------------------
702
703- (void) mouseMoved: (NSEvent *) theEvent {
704	mOwner.backend->MouseMove(theEvent);
705}
706
707//--------------------------------------------------------------------------------------------------
708
709- (void) mouseEntered: (NSEvent *) theEvent {
710	mOwner.backend->MouseEntered(theEvent);
711}
712
713//--------------------------------------------------------------------------------------------------
714
715- (void) mouseExited: (NSEvent *) theEvent {
716	mOwner.backend->MouseExited(theEvent);
717}
718
719//--------------------------------------------------------------------------------------------------
720
721/**
722 * Implementing scrollWheel makes scrolling work better even if just
723 * calling super.
724 * Mouse wheel with command key may magnify text if enabled.
725 * Pinch gestures and key commands can also be used for magnification.
726 */
727- (void) scrollWheel: (NSEvent *) theEvent {
728#ifdef SCROLL_WHEEL_MAGNIFICATION
729	if (([theEvent modifierFlags] & NSEventModifierFlagCommand) != 0) {
730		mOwner.backend->MouseWheel(theEvent);
731		return;
732	}
733#endif
734	[super scrollWheel: theEvent];
735}
736
737//--------------------------------------------------------------------------------------------------
738
739/**
740 * Ensure scrolling is aligned to whole lines instead of starting part-way through a line
741 */
742- (NSRect) adjustScroll: (NSRect) proposedVisibleRect {
743	if (!mOwner)
744		return proposedVisibleRect;
745	NSRect rc = proposedVisibleRect;
746	// Snap to lines
747	NSRect contentRect = self.bounds;
748	if ((rc.origin.y > 0) && (NSMaxY(rc) < contentRect.size.height)) {
749		// Only snap for positions inside the document - allow outside
750		// for overshoot.
751		long lineHeight = mOwner.backend->WndProc(SCI_TEXTHEIGHT, 0, 0);
752		rc.origin.y = std::round(static_cast<XYPOSITION>(rc.origin.y) / lineHeight) * lineHeight;
753	}
754	// Snap to whole points - on retina displays this avoids visual debris
755	// when scrolling horizontally.
756	if ((rc.origin.x > 0) && (NSMaxX(rc) < contentRect.size.width)) {
757		// Only snap for positions inside the document - allow outside
758		// for overshoot.
759		rc.origin.x = std::round(static_cast<XYPOSITION>(rc.origin.x));
760	}
761	return rc;
762}
763
764//--------------------------------------------------------------------------------------------------
765
766/**
767 * The editor is getting the foreground control (the one getting the input focus).
768 */
769- (BOOL) becomeFirstResponder {
770	mOwner.backend->WndProc(SCI_SETFOCUS, 1, 0);
771	return YES;
772}
773
774//--------------------------------------------------------------------------------------------------
775
776/**
777 * The editor is losing the input focus.
778 */
779- (BOOL) resignFirstResponder {
780	mOwner.backend->WndProc(SCI_SETFOCUS, 0, 0);
781	return YES;
782}
783
784//--------------------------------------------------------------------------------------------------
785
786/**
787 * Implement NSDraggingSource.
788 */
789
790- (NSDragOperation) draggingSession: (NSDraggingSession *) session
791	sourceOperationMaskForDraggingContext: (NSDraggingContext) context {
792#pragma unused(session)
793	switch (context) {
794	case NSDraggingContextOutsideApplication:
795		return NSDragOperationCopy | NSDragOperationMove | NSDragOperationDelete;
796
797	case NSDraggingContextWithinApplication:
798	default:
799		return NSDragOperationCopy | NSDragOperationMove | NSDragOperationDelete;
800	}
801}
802
803- (void) draggingSession: (NSDraggingSession *) session
804	    movedToPoint: (NSPoint) screenPoint {
805#pragma unused(session, screenPoint)
806}
807
808- (void) draggingSession: (NSDraggingSession *) session
809	    endedAtPoint: (NSPoint) screenPoint
810	       operation: (NSDragOperation) operation {
811#pragma unused(session, screenPoint)
812	if (operation == NSDragOperationDelete) {
813		mOwner.backend->WndProc(SCI_CLEAR, 0, 0);
814	}
815}
816
817/**
818 * Implement NSDraggingDestination.
819 */
820
821//--------------------------------------------------------------------------------------------------
822
823/**
824 * Called when an external drag operation enters the view.
825 */
826- (NSDragOperation) draggingEntered: (id <NSDraggingInfo>) sender {
827	return mOwner.backend->DraggingEntered(sender);
828}
829
830//--------------------------------------------------------------------------------------------------
831
832/**
833 * Called frequently during an external drag operation if we are the target.
834 */
835- (NSDragOperation) draggingUpdated: (id <NSDraggingInfo>) sender {
836	return mOwner.backend->DraggingUpdated(sender);
837}
838
839//--------------------------------------------------------------------------------------------------
840
841/**
842 * Drag image left the view. Clean up if necessary.
843 */
844- (void) draggingExited: (id <NSDraggingInfo>) sender {
845	mOwner.backend->DraggingExited(sender);
846}
847
848//--------------------------------------------------------------------------------------------------
849
850- (BOOL) prepareForDragOperation: (id <NSDraggingInfo>) sender {
851#pragma unused(sender)
852	return YES;
853}
854
855//--------------------------------------------------------------------------------------------------
856
857- (BOOL) performDragOperation: (id <NSDraggingInfo>) sender {
858	return mOwner.backend->PerformDragOperation(sender);
859}
860
861//--------------------------------------------------------------------------------------------------
862
863/**
864 * Drag operation is done. Notify editor.
865 */
866- (void) concludeDragOperation: (id <NSDraggingInfo>) sender {
867	// Clean up is the same as if we are no longer the drag target.
868	mOwner.backend->DraggingExited(sender);
869}
870
871//--------------------------------------------------------------------------------------------------
872
873// NSResponder actions.
874
875- (void) selectAll: (id) sender {
876#pragma unused(sender)
877	mOwner.backend->SelectAll();
878}
879
880- (void) deleteBackward: (id) sender {
881#pragma unused(sender)
882	mOwner.backend->DeleteBackward();
883}
884
885- (void) cut: (id) sender {
886#pragma unused(sender)
887	mOwner.backend->Cut();
888}
889
890- (void) copy: (id) sender {
891#pragma unused(sender)
892	mOwner.backend->Copy();
893}
894
895- (void) paste: (id) sender {
896#pragma unused(sender)
897	if (mMarkedTextRange.location != NSNotFound) {
898		[[NSTextInputContext currentInputContext] discardMarkedText];
899		mOwner.backend->CompositionCommit();
900		mMarkedTextRange = NSMakeRange(NSNotFound, 0);
901	}
902	mOwner.backend->Paste();
903}
904
905- (void) undo: (id) sender {
906#pragma unused(sender)
907	if (mMarkedTextRange.location != NSNotFound) {
908		[[NSTextInputContext currentInputContext] discardMarkedText];
909		mOwner.backend->CompositionCommit();
910		mMarkedTextRange = NSMakeRange(NSNotFound, 0);
911	}
912	mOwner.backend->Undo();
913}
914
915- (void) redo: (id) sender {
916#pragma unused(sender)
917	mOwner.backend->Redo();
918}
919
920- (BOOL) canUndo {
921	return mOwner.backend->CanUndo() && (mMarkedTextRange.location == NSNotFound);
922}
923
924- (BOOL) canRedo {
925	return mOwner.backend->CanRedo();
926}
927
928- (BOOL) validateUserInterfaceItem: (id <NSValidatedUserInterfaceItem>) anItem {
929	SEL action = anItem.action;
930	if (action==@selector(undo:)) {
931		return [self canUndo];
932	} else if (action==@selector(redo:)) {
933		return [self canRedo];
934	} else if (action==@selector(cut:) || action==@selector(copy:) || action==@selector(clear:)) {
935		return mOwner.backend->HasSelection();
936	} else if (action==@selector(paste:)) {
937		return mOwner.backend->CanPaste();
938	}
939	return YES;
940}
941
942- (void) clear: (id) sender {
943	[self deleteBackward: sender];
944}
945
946- (BOOL) isEditable {
947	return mOwner.backend->WndProc(SCI_GETREADONLY, 0, 0) == 0;
948}
949
950#pragma mark - NSAccessibility
951
952//--------------------------------------------------------------------------------------------------
953
954// Adoption of NSAccessibility protocol.
955// NSAccessibility wants to pass ranges in UTF-16 code units, not bytes (like Scintilla)
956// or characters.
957// Needs more testing with non-ASCII and non-BMP text.
958// Needs to take account of folding and wraping.
959
960//--------------------------------------------------------------------------------------------------
961
962/**
963 * NSAccessibility : Text of the whole document as a string.
964 */
965- (id) accessibilityValue {
966	const sptr_t length = [mOwner message: SCI_GETLENGTH];
967	return mOwner.backend->RangeTextAsString(NSMakeRange(0, length));
968}
969
970//--------------------------------------------------------------------------------------------------
971
972/**
973 * NSAccessibility : Line of the caret.
974 */
975- (NSInteger) accessibilityInsertionPointLineNumber {
976	const Sci::Position caret = [mOwner message: SCI_GETCURRENTPOS];
977	const NSRange rangeCharactersCaret = mOwner.backend->CharactersFromPositions(NSMakeRange(caret, 0));
978	return mOwner.backend->VisibleLineForIndex(rangeCharactersCaret.location);
979}
980
981//--------------------------------------------------------------------------------------------------
982
983/**
984 * NSAccessibility : Not implemented and not called by VoiceOver.
985 */
986- (NSRange) accessibilityRangeForPosition: (NSPoint) point {
987	return NSMakeRange(0, 0);
988}
989
990//--------------------------------------------------------------------------------------------------
991
992/**
993 * NSAccessibility : Number of characters in the whole document.
994 */
995- (NSInteger) accessibilityNumberOfCharacters {
996	sptr_t length = [mOwner message: SCI_GETLENGTH];
997	const NSRange posRange = mOwner.backend->CharactersFromPositions(NSMakeRange(length, 0));
998	return posRange.location;
999}
1000
1001//--------------------------------------------------------------------------------------------------
1002
1003/**
1004 * NSAccessibility : The selection text as a string.
1005 */
1006- (NSString *) accessibilitySelectedText {
1007	const sptr_t positionBegin = [mOwner message: SCI_GETSELECTIONSTART];
1008	const sptr_t positionEnd = [mOwner message: SCI_GETSELECTIONEND];
1009	const NSRange posRangeSel = NSMakeRange(positionBegin, positionEnd-positionBegin);
1010	return mOwner.backend->RangeTextAsString(posRangeSel);
1011}
1012
1013//--------------------------------------------------------------------------------------------------
1014
1015/**
1016 * NSAccessibility : The character range of the main selection.
1017 */
1018- (NSRange) accessibilitySelectedTextRange {
1019	const sptr_t positionBegin = [mOwner message: SCI_GETSELECTIONSTART];
1020	const sptr_t positionEnd = [mOwner message: SCI_GETSELECTIONEND];
1021	const NSRange posRangeSel = NSMakeRange(positionBegin, positionEnd-positionBegin);
1022	return mOwner.backend->CharactersFromPositions(posRangeSel);
1023}
1024
1025//--------------------------------------------------------------------------------------------------
1026
1027/**
1028 * NSAccessibility : The setter for accessibilitySelectedTextRange.
1029 * This method is the only setter required for reasonable VoiceOver behaviour.
1030 */
1031- (void) setAccessibilitySelectedTextRange: (NSRange) range {
1032	NSRange rangePositions = mOwner.backend->PositionsFromCharacters(range);
1033	[mOwner message: SCI_SETSELECTION wParam: rangePositions.location lParam: NSMaxRange(rangePositions)];
1034}
1035
1036//--------------------------------------------------------------------------------------------------
1037
1038/**
1039 * NSAccessibility : Range of the glyph at a character index.
1040 * Currently doesn't try to handle composite characters.
1041 */
1042- (NSRange) accessibilityRangeForIndex: (NSInteger) index {
1043	sptr_t length = [mOwner message: SCI_GETLENGTH];
1044	const NSRange rangeLength = mOwner.backend->CharactersFromPositions(NSMakeRange(length, 0));
1045	NSRange rangePositions = NSMakeRange(length, 0);
1046	if (index < rangeLength.location) {
1047		rangePositions = mOwner.backend->PositionsFromCharacters(NSMakeRange(index, 1));
1048	}
1049	return mOwner.backend->CharactersFromPositions(rangePositions);
1050}
1051
1052//--------------------------------------------------------------------------------------------------
1053
1054/**
1055 * NSAccessibility : All the text ranges.
1056 * Currently only returns the main selection.
1057 */
1058- (NSArray<NSValue *> *) accessibilitySelectedTextRanges {
1059	const sptr_t positionBegin = [mOwner message: SCI_GETSELECTIONSTART];
1060	const sptr_t positionEnd = [mOwner message: SCI_GETSELECTIONEND];
1061	const NSRange posRangeSel = NSMakeRange(positionBegin, positionEnd-positionBegin);
1062	NSRange rangeCharacters = mOwner.backend->CharactersFromPositions(posRangeSel);
1063	NSValue *valueRange = [NSValue valueWithRange: (NSRange)rangeCharacters];
1064	return @[valueRange];
1065}
1066
1067//--------------------------------------------------------------------------------------------------
1068
1069/**
1070 * NSAccessibility : Character range currently visible.
1071 */
1072- (NSRange) accessibilityVisibleCharacterRange {
1073	const sptr_t lineTopVisible = [mOwner message: SCI_GETFIRSTVISIBLELINE];
1074	const sptr_t lineTop = [mOwner message: SCI_DOCLINEFROMVISIBLE wParam: lineTopVisible];
1075	const sptr_t lineEndVisible = lineTopVisible + [mOwner message: SCI_LINESONSCREEN] - 1;
1076	const sptr_t lineEnd = [mOwner message: SCI_DOCLINEFROMVISIBLE wParam: lineEndVisible];
1077	const sptr_t posStartView = [mOwner message: SCI_POSITIONFROMLINE wParam: lineTop];
1078	const sptr_t posEndView = [mOwner message: SCI_GETLINEENDPOSITION wParam: lineEnd];
1079	const NSRange posRangeSel = NSMakeRange(posStartView, posEndView-posStartView);
1080	return mOwner.backend->CharactersFromPositions(posRangeSel);
1081}
1082
1083//--------------------------------------------------------------------------------------------------
1084
1085/**
1086 * NSAccessibility : Character range of a line.
1087 */
1088- (NSRange) accessibilityRangeForLine: (NSInteger) line {
1089	return mOwner.backend->RangeForVisibleLine(line);
1090}
1091
1092//--------------------------------------------------------------------------------------------------
1093
1094/**
1095 * NSAccessibility : Line number of a text position in characters.
1096 */
1097- (NSInteger) accessibilityLineForIndex: (NSInteger) index {
1098	return mOwner.backend->VisibleLineForIndex(index);
1099}
1100
1101//--------------------------------------------------------------------------------------------------
1102
1103/**
1104 * NSAccessibility : A rectangle that covers a range which will be shown as the
1105 * VoiceOver cursor.
1106 * Producing a nice rectangle is a little tricky particularly when including new
1107 * lines. Needs to improve the case where parts of two lines are included.
1108 */
1109- (NSRect) accessibilityFrameForRange: (NSRange) range {
1110	const NSRect rectInView = mOwner.backend->FrameForRange(range);
1111	const NSRect rectInWindow = [self.superview.superview convertRect: rectInView toView: nil];
1112	return [self.window convertRectToScreen: rectInWindow];
1113}
1114
1115//--------------------------------------------------------------------------------------------------
1116
1117/**
1118 * NSAccessibility : A range of text as a string.
1119 */
1120- (NSString *) accessibilityStringForRange: (NSRange) range {
1121	const NSRange posRange = mOwner.backend->PositionsFromCharacters(range);
1122	return mOwner.backend->RangeTextAsString(posRange);
1123}
1124
1125//--------------------------------------------------------------------------------------------------
1126
1127/**
1128 * NSAccessibility : A range of text as an attributed string.
1129 * Currently no attributes are set.
1130 */
1131- (NSAttributedString *) accessibilityAttributedStringForRange: (NSRange) range {
1132	const NSRange posRange = mOwner.backend->PositionsFromCharacters(range);
1133	NSString *result = mOwner.backend->RangeTextAsString(posRange);
1134	return [[NSMutableAttributedString alloc] initWithString: result];
1135}
1136
1137//--------------------------------------------------------------------------------------------------
1138
1139/**
1140 * NSAccessibility : Show the context menu at the caret.
1141 */
1142- (BOOL) accessibilityPerformShowMenu {
1143	const sptr_t caret = [mOwner message: SCI_GETCURRENTPOS];
1144	NSRect rect;
1145	rect.origin.x = [mOwner message: SCI_POINTXFROMPOSITION wParam: 0 lParam: caret];
1146	rect.origin.y = [mOwner message: SCI_POINTYFROMPOSITION wParam: 0 lParam: caret];
1147	rect.origin.y += [mOwner message: SCI_TEXTHEIGHT wParam: 0 lParam: 0];
1148	rect.size.width = 1.0;
1149	rect.size.height = 1.0;
1150	NSRect rectInWindow = [self.superview.superview convertRect: rect toView: nil];
1151	NSPoint pt = rectInWindow.origin;
1152	NSEvent *event = [NSEvent mouseEventWithType: NSEventTypeRightMouseDown
1153					    location: pt
1154				       modifierFlags: 0
1155					   timestamp: 0
1156					windowNumber: self.window.windowNumber
1157					     context: nil
1158					 eventNumber: 0
1159					  clickCount: 1
1160					    pressure: 0.0];
1161	NSMenu *menu = mOwner.backend->CreateContextMenu(event);
1162	[NSMenu popUpContextMenu: menu withEvent: event forView: self];
1163	return YES;
1164}
1165
1166//--------------------------------------------------------------------------------------------------
1167
1168
1169@end
1170
1171//--------------------------------------------------------------------------------------------------
1172
1173@implementation ScintillaView {
1174	// The back end is kind of a controller and model in one.
1175	// It uses the content view for display.
1176	Scintilla::ScintillaCocoa *mBackend;
1177
1178	// This is the actual content to which the backend renders itself.
1179	SCIContentView *mContent;
1180
1181	NSScrollView *scrollView;
1182	SCIMarginView *marginView;
1183
1184	CGFloat zoomDelta;
1185
1186	// Area to display additional controls (e.g. zoom info, caret position, status info).
1187	NSView <InfoBarCommunicator> *mInfoBar;
1188	BOOL mInfoBarAtTop;
1189
1190	id<ScintillaNotificationProtocol> __unsafe_unretained mDelegate;
1191}
1192
1193@synthesize backend = mBackend;
1194@synthesize delegate = mDelegate;
1195@synthesize scrollView;
1196
1197/**
1198 * ScintillaView is a composite control made from an NSView and an embedded NSView that is
1199 * used as canvas for the output (by the backend, using its CGContext), plus other elements
1200 * (scrollers, info bar).
1201 */
1202
1203//--------------------------------------------------------------------------------------------------
1204
1205/**
1206 * Initialize custom cursor.
1207 */
1208+ (void) initialize {
1209	if (self == [ScintillaView class]) {
1210		NSBundle *bundle = [NSBundle bundleForClass: [ScintillaView class]];
1211
1212		NSString *path = [bundle pathForResource: @"mac_cursor_busy" ofType: @"tiff" inDirectory: nil];
1213		NSImage *image = [[NSImage alloc] initWithContentsOfFile: path];
1214		waitCursor = [[NSCursor alloc] initWithImage: image hotSpot: NSMakePoint(2, 2)];
1215
1216		path = [bundle pathForResource: @"mac_cursor_flipped" ofType: @"tiff" inDirectory: nil];
1217		image = [[NSImage alloc] initWithContentsOfFile: path];
1218		reverseArrowCursor = [[NSCursor alloc] initWithImage: image hotSpot: NSMakePoint(15, 2)];
1219	}
1220}
1221
1222//--------------------------------------------------------------------------------------------------
1223
1224/**
1225 * Specify the SCIContentView class. Can be overridden in a subclass to provide an SCIContentView subclass.
1226 */
1227
1228+ (Class) contentViewClass {
1229	return [SCIContentView class];
1230}
1231
1232//--------------------------------------------------------------------------------------------------
1233
1234/**
1235 * Receives zoom messages, for example when a "pinch zoom" is performed on the trackpad.
1236 */
1237- (void) magnifyWithEvent: (NSEvent *) event {
1238#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5
1239	zoomDelta += event.magnification * 10.0;
1240
1241	if (std::abs(zoomDelta)>=1.0) {
1242		long zoomFactor = static_cast<long>([self getGeneralProperty: SCI_GETZOOM] + zoomDelta);
1243		[self setGeneralProperty: SCI_SETZOOM parameter: zoomFactor value: 0];
1244		zoomDelta = 0.0;
1245	}
1246#endif
1247}
1248
1249- (void) beginGestureWithEvent: (NSEvent *) event {
1250// Scintilla is only interested in this event as the starft of a zoom
1251#pragma unused(event)
1252	zoomDelta = 0.0;
1253}
1254
1255//--------------------------------------------------------------------------------------------------
1256
1257/**
1258 * Sends a new notification of the given type to the default notification center.
1259 */
1260- (void) sendNotification: (NSString *) notificationName {
1261	NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
1262	[center postNotificationName: notificationName object: self];
1263}
1264
1265//--------------------------------------------------------------------------------------------------
1266
1267/**
1268 * Called by a connected component (usually the info bar) if something changed there.
1269 *
1270 * @param type The type of the notification.
1271 * @param message Carries the new status message if the type is a status message change.
1272 * @param location Carries the new location (e.g. caret) if the type is a caret change or similar type.
1273 * @param value Carries the new zoom value if the type is a zoom change.
1274 */
1275- (void) notify: (NotificationType) type message: (NSString *) message location: (NSPoint) location
1276	  value: (float) value {
1277// These parameters are just to conform to the protocol
1278#pragma unused(message)
1279#pragma unused(location)
1280	switch (type) {
1281	case IBNZoomChanged: {
1282			// Compute point increase/decrease based on default font size.
1283			long fontSize = [self getGeneralProperty: SCI_STYLEGETSIZE parameter: STYLE_DEFAULT];
1284			int zoom = (int)(fontSize * (value - 1));
1285			[self setGeneralProperty: SCI_SETZOOM value: zoom];
1286			break;
1287		}
1288	default:
1289		break;
1290	};
1291}
1292
1293//--------------------------------------------------------------------------------------------------
1294
1295- (void) setCallback: (id <InfoBarCommunicator>) callback {
1296// Not used. Only here to satisfy protocol.
1297#pragma unused(callback)
1298}
1299
1300//--------------------------------------------------------------------------------------------------
1301
1302/**
1303 * Prevents drawing of the inner view to avoid flickering when doing many visual updates
1304 * (like clearing all marks and setting new ones etc.).
1305 */
1306- (void) suspendDrawing: (BOOL) suspend {
1307	if (suspend)
1308		[self.window disableFlushWindow];
1309	else
1310		[self.window enableFlushWindow];
1311}
1312
1313//--------------------------------------------------------------------------------------------------
1314
1315/**
1316 * Method receives notifications from Scintilla (e.g. for handling clicks on the
1317 * folder margin or changes in the editor).
1318 * A delegate can be set to receive all notifications. If set no handling takes place here, except
1319 * for action pertaining to internal stuff (like the info bar).
1320 */
1321- (void) notification: (SCNotification *) scn {
1322	// Parent notification. Details are passed as SCNotification structure.
1323
1324	if (mDelegate != nil) {
1325		[mDelegate notification: scn];
1326		if (scn->nmhdr.code != SCN_ZOOM && scn->nmhdr.code != SCN_UPDATEUI)
1327			return;
1328	}
1329
1330	switch (scn->nmhdr.code) {
1331	case SCN_MARGINCLICK: {
1332			if (scn->margin == 2) {
1333				// Click on the folder margin. Toggle the current line if possible.
1334				long line = [self getGeneralProperty: SCI_LINEFROMPOSITION parameter: scn->position];
1335				[self setGeneralProperty: SCI_TOGGLEFOLD value: line];
1336			}
1337			break;
1338		};
1339	case SCN_MODIFIED: {
1340			// Decide depending on the modification type what to do.
1341			// There can be more than one modification carried by one notification.
1342			if (scn->modificationType & (SC_MOD_INSERTTEXT | SC_MOD_DELETETEXT))
1343				[self sendNotification: NSTextDidChangeNotification];
1344			break;
1345		}
1346	case SCN_ZOOM: {
1347			// A zoom change happened. Notify info bar if there is one.
1348			float zoom = [self getGeneralProperty: SCI_GETZOOM parameter: 0];
1349			long fontSize = [self getGeneralProperty: SCI_STYLEGETSIZE parameter: STYLE_DEFAULT];
1350			float factor = (zoom / fontSize) + 1;
1351			[mInfoBar notify: IBNZoomChanged message: nil location: NSZeroPoint value: factor];
1352			break;
1353		}
1354	case SCN_UPDATEUI: {
1355			// Triggered whenever changes in the UI state need to be reflected.
1356			// These can be: caret changes, selection changes etc.
1357			NSPoint caretPosition = mBackend->GetCaretPosition();
1358			[mInfoBar notify: IBNCaretChanged message: nil location: caretPosition value: 0];
1359			[self sendNotification: SCIUpdateUINotification];
1360			if (scn->updated & (SC_UPDATE_SELECTION | SC_UPDATE_CONTENT)) {
1361				[self sendNotification: NSTextViewDidChangeSelectionNotification];
1362			}
1363			break;
1364		}
1365	case SCN_FOCUSOUT:
1366		[self sendNotification: NSTextDidEndEditingNotification];
1367		break;
1368	case SCN_FOCUSIN: // Nothing to do for now.
1369		break;
1370	}
1371}
1372
1373//--------------------------------------------------------------------------------------------------
1374
1375/**
1376 * Setup a special indicator used in the editor to provide visual feedback for
1377 * input composition, depending on language, keyboard etc.
1378 */
1379- (void) updateIndicatorIME {
1380	[self setColorProperty: SCI_INDICSETFORE parameter: INDICATOR_IME fromHTML: @"#FF0000"];
1381	const bool drawInBackground = [self message: SCI_GETPHASESDRAW] != 0;
1382	[self setGeneralProperty: SCI_INDICSETUNDER parameter: INDICATOR_IME value: drawInBackground];
1383	[self setGeneralProperty: SCI_INDICSETSTYLE parameter: INDICATOR_IME value: INDIC_PLAIN];
1384	[self setGeneralProperty: SCI_INDICSETALPHA parameter: INDICATOR_IME value: 100];
1385}
1386
1387//--------------------------------------------------------------------------------------------------
1388
1389/**
1390 * Initialization of the view. Used to setup a few other things we need.
1391 */
1392- (instancetype) initWithFrame: (NSRect) frame {
1393	self = [super initWithFrame: frame];
1394	if (self) {
1395		mContent = [[[[self class] contentViewClass] alloc] initWithFrame: NSZeroRect];
1396		mContent.owner = self;
1397
1398		// Initialize the scrollers but don't show them yet.
1399		// Pick an arbitrary size, just to make NSScroller selecting the proper scroller direction
1400		// (horizontal or vertical).
1401		NSRect scrollerRect = NSMakeRect(0, 0, 100, 10);
1402		scrollView = (NSScrollView *)[[SCIScrollView alloc] initWithFrame: scrollerRect];
1403#if defined(MAC_OS_X_VERSION_10_14)
1404		// Let SCIScrollView account for other subviews such as vertical ruler by turning off
1405		// automaticallyAdjustsContentInsets.
1406		if (std::floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_13) {
1407			scrollView.contentView.automaticallyAdjustsContentInsets = NO;
1408			scrollView.contentView.contentInsets = NSEdgeInsetsMake(0., 0., 0., 0.);
1409		}
1410#endif
1411		scrollView.documentView = mContent;
1412		[scrollView setHasVerticalScroller: YES];
1413		[scrollView setHasHorizontalScroller: YES];
1414		scrollView.autoresizingMask = NSViewWidthSizable|NSViewHeightSizable;
1415		//[scrollView setScrollerStyle:NSScrollerStyleLegacy];
1416		//[scrollView setScrollerKnobStyle:NSScrollerKnobStyleDark];
1417		//[scrollView setHorizontalScrollElasticity:NSScrollElasticityNone];
1418		[self addSubview: scrollView];
1419
1420		marginView = [[SCIMarginView alloc] initWithScrollView: scrollView];
1421		marginView.owner = self;
1422		marginView.ruleThickness = marginView.requiredThickness;
1423		scrollView.verticalRulerView = marginView;
1424		[scrollView setHasHorizontalRuler: NO];
1425		[scrollView setHasVerticalRuler: YES];
1426		[scrollView setRulersVisible: YES];
1427
1428		mBackend = new ScintillaCocoa(self, mContent, marginView);
1429
1430		// Establish a connection from the back end to this container so we can handle situations
1431		// which require our attention.
1432		mBackend->SetDelegate(self);
1433
1434		[self updateIndicatorIME];
1435
1436		NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
1437		[center addObserver: self
1438			   selector: @selector(applicationDidResignActive:)
1439			       name: NSApplicationDidResignActiveNotification
1440			     object: nil];
1441
1442		[center addObserver: self
1443			   selector: @selector(applicationDidBecomeActive:)
1444			       name: NSApplicationDidBecomeActiveNotification
1445			     object: nil];
1446
1447		[center addObserver: self
1448			   selector: @selector(windowWillMove:)
1449			       name: NSWindowWillMoveNotification
1450			     object: self.window];
1451
1452		[scrollView.contentView setPostsBoundsChangedNotifications: YES];
1453		[center addObserver: self
1454			   selector: @selector(scrollerAction:)
1455			       name: NSViewBoundsDidChangeNotification
1456			     object: scrollView.contentView];
1457	}
1458	return self;
1459}
1460
1461//--------------------------------------------------------------------------------------------------
1462
1463- (void) dealloc {
1464	[[NSNotificationCenter defaultCenter] removeObserver: self];
1465	mBackend->Finalise();
1466	delete mBackend;
1467	mBackend = NULL;
1468	mContent.owner = nil;
1469	[marginView setClientView: nil];
1470	[scrollView removeFromSuperview];
1471}
1472
1473//--------------------------------------------------------------------------------------------------
1474
1475- (void) applicationDidResignActive: (NSNotification *) note {
1476#pragma unused(note)
1477	mBackend->ActiveStateChanged(false);
1478}
1479
1480//--------------------------------------------------------------------------------------------------
1481
1482- (void) applicationDidBecomeActive: (NSNotification *) note {
1483#pragma unused(note)
1484	mBackend->ActiveStateChanged(true);
1485}
1486
1487//--------------------------------------------------------------------------------------------------
1488
1489- (void) windowWillMove: (NSNotification *) note {
1490#pragma unused(note)
1491	mBackend->WindowWillMove();
1492}
1493
1494//--------------------------------------------------------------------------------------------------
1495
1496- (void) viewDidMoveToWindow {
1497	[super viewDidMoveToWindow];
1498
1499	[self positionSubViews];
1500
1501	// Enable also mouse move events for our window (and so this view).
1502	[self.window setAcceptsMouseMovedEvents: YES];
1503}
1504
1505//--------------------------------------------------------------------------------------------------
1506
1507/**
1508 * Used to position and size the parts of the editor (content, scrollers, info bar).
1509 */
1510- (void) positionSubViews {
1511	CGFloat scrollerWidth = [NSScroller scrollerWidthForControlSize: NSControlSizeRegular
1512							  scrollerStyle: NSScrollerStyleLegacy];
1513
1514	NSSize size = self.frame.size;
1515	NSRect barFrame = {{0, size.height - scrollerWidth}, {size.width, scrollerWidth}};
1516	BOOL infoBarVisible = mInfoBar != nil && !mInfoBar.hidden;
1517
1518	// Horizontal offset of the content. Almost always 0 unless the vertical scroller
1519	// is on the left side.
1520	CGFloat contentX = 0;
1521	NSRect scrollRect = {{contentX, 0}, {size.width, size.height}};
1522
1523	// Info bar frame.
1524	if (infoBarVisible) {
1525		scrollRect.size.height -= scrollerWidth;
1526		// Initial value already is as if the bar is at top.
1527		if (!mInfoBarAtTop) {
1528			scrollRect.origin.y += scrollerWidth;
1529			barFrame.origin.y = 0;
1530		}
1531	}
1532
1533	if (!NSEqualRects(scrollView.frame, scrollRect)) {
1534		scrollView.frame = scrollRect;
1535	}
1536
1537	if (infoBarVisible)
1538		mInfoBar.frame = barFrame;
1539}
1540
1541//--------------------------------------------------------------------------------------------------
1542
1543/**
1544 * Set the width of the margin.
1545 */
1546- (void) setMarginWidth: (int) width {
1547	if (marginView.ruleThickness != width) {
1548		marginView.marginWidth = width;
1549		marginView.ruleThickness = marginView.requiredThickness;
1550	}
1551}
1552
1553//--------------------------------------------------------------------------------------------------
1554
1555/**
1556 * Triggered by one of the scrollers when it gets manipulated by the user. Notify the backend
1557 * about the change.
1558 */
1559- (void) scrollerAction: (id) sender {
1560#pragma unused(sender)
1561	mBackend->UpdateForScroll();
1562}
1563
1564//--------------------------------------------------------------------------------------------------
1565
1566/**
1567 * Used to reposition our content depending on the size of the view.
1568 */
1569- (void) setFrame: (NSRect) newFrame {
1570	NSRect previousFrame = self.frame;
1571	super.frame = newFrame;
1572	[self positionSubViews];
1573	if (!NSEqualRects(previousFrame, newFrame)) {
1574		mBackend->Resize();
1575	}
1576}
1577
1578//--------------------------------------------------------------------------------------------------
1579
1580/**
1581 * Getter for the currently selected text in raw form (no formatting information included).
1582 * If there is no text available an empty string is returned.
1583 */
1584- (NSString *) selectedString {
1585	NSString *result = @"";
1586
1587	const long length = mBackend->WndProc(SCI_GETSELTEXT, 0, 0);
1588	if (length > 0) {
1589		std::string buffer(length + 1, '\0');
1590		try {
1591			mBackend->WndProc(SCI_GETSELTEXT, length + 1, (sptr_t) &buffer[0]);
1592
1593			result = @(buffer.c_str());
1594		} catch (...) {
1595		}
1596	}
1597
1598	return result;
1599}
1600
1601//--------------------------------------------------------------------------------------------------
1602
1603/**
1604 * Delete a range from the document.
1605 */
1606- (void) deleteRange: (NSRange) aRange {
1607	if (aRange.length > 0) {
1608		NSRange posRange = mBackend->PositionsFromCharacters(aRange);
1609		[self message: SCI_DELETERANGE wParam: posRange.location lParam: posRange.length];
1610	}
1611}
1612
1613//--------------------------------------------------------------------------------------------------
1614
1615/**
1616 * Getter for the current text in raw form (no formatting information included).
1617 * If there is no text available an empty string is returned.
1618 */
1619- (NSString *) string {
1620	NSString *result = @"";
1621
1622	const long length = mBackend->WndProc(SCI_GETLENGTH, 0, 0);
1623	if (length > 0) {
1624		std::string buffer(length + 1, '\0');
1625		try {
1626			mBackend->WndProc(SCI_GETTEXT, length + 1, (sptr_t) &buffer[0]);
1627
1628			result = @(buffer.c_str());
1629		} catch (...) {
1630		}
1631	}
1632
1633	return result;
1634}
1635
1636//--------------------------------------------------------------------------------------------------
1637
1638/**
1639 * Setter for the current text (no formatting included).
1640 */
1641- (void) setString: (NSString *) aString {
1642	const char *text = aString.UTF8String;
1643	mBackend->WndProc(SCI_SETTEXT, 0, (long) text);
1644}
1645
1646//--------------------------------------------------------------------------------------------------
1647
1648- (void) insertString: (NSString *) aString atOffset: (int) offset {
1649	const char *text = aString.UTF8String;
1650	mBackend->WndProc(SCI_ADDTEXT, offset, (long) text);
1651}
1652
1653//--------------------------------------------------------------------------------------------------
1654
1655- (void) setEditable: (BOOL) editable {
1656	mBackend->WndProc(SCI_SETREADONLY, editable ? 0 : 1, 0);
1657}
1658
1659//--------------------------------------------------------------------------------------------------
1660
1661- (BOOL) isEditable {
1662	return mBackend->WndProc(SCI_GETREADONLY, 0, 0) == 0;
1663}
1664
1665//--------------------------------------------------------------------------------------------------
1666
1667- (SCIContentView *) content {
1668	return mContent;
1669}
1670
1671//--------------------------------------------------------------------------------------------------
1672
1673- (void) updateMarginCursors {
1674	[self.window invalidateCursorRectsForView: marginView];
1675}
1676
1677//--------------------------------------------------------------------------------------------------
1678
1679/**
1680 * Direct call into the backend to allow uninterpreted access to it. The values to be passed in and
1681 * the result heavily depend on the message that is used for the call. Refer to the Scintilla
1682 * documentation to learn what can be used here.
1683 */
1684+ (sptr_t) directCall: (ScintillaView *) sender message: (unsigned int) message wParam: (uptr_t) wParam
1685	       lParam: (sptr_t) lParam {
1686	return ScintillaCocoa::DirectFunction(
1687		       reinterpret_cast<sptr_t>(sender->mBackend), message, wParam, lParam);
1688}
1689
1690- (sptr_t) message: (unsigned int) message wParam: (uptr_t) wParam lParam: (sptr_t) lParam {
1691	return mBackend->WndProc(message, wParam, lParam);
1692}
1693
1694- (sptr_t) message: (unsigned int) message wParam: (uptr_t) wParam {
1695	return mBackend->WndProc(message, wParam, 0);
1696}
1697
1698- (sptr_t) message: (unsigned int) message {
1699	return mBackend->WndProc(message, 0, 0);
1700}
1701
1702//--------------------------------------------------------------------------------------------------
1703
1704/**
1705 * This is a helper method to set properties in the backend, with native parameters.
1706 *
1707 * @param property Main property like SCI_STYLESETFORE for which a value is to be set.
1708 * @param parameter Additional info for this property like a parameter or index.
1709 * @param value The actual value. It depends on the property what this parameter means.
1710 */
1711- (void) setGeneralProperty: (int) property parameter: (long) parameter value: (long) value {
1712	mBackend->WndProc(property, parameter, value);
1713}
1714
1715//--------------------------------------------------------------------------------------------------
1716
1717/**
1718 * A simplified version for setting properties which only require one parameter.
1719 *
1720 * @param property Main property like SCI_STYLESETFORE for which a value is to be set.
1721 * @param value The actual value. It depends on the property what this parameter means.
1722 */
1723- (void) setGeneralProperty: (int) property value: (long) value {
1724	mBackend->WndProc(property, value, 0);
1725}
1726
1727//--------------------------------------------------------------------------------------------------
1728
1729/**
1730 * This is a helper method to get a property in the backend, with native parameters.
1731 *
1732 * @param property Main property like SCI_STYLESETFORE for which a value is to get.
1733 * @param parameter Additional info for this property like a parameter or index.
1734 * @param extra Yet another parameter if needed.
1735 * @result A generic value which must be interpreted depending on the property queried.
1736 */
1737- (long) getGeneralProperty: (int) property parameter: (long) parameter extra: (long) extra {
1738	return mBackend->WndProc(property, parameter, extra);
1739}
1740
1741//--------------------------------------------------------------------------------------------------
1742
1743/**
1744 * Convenience function to avoid unneeded extra parameter.
1745 */
1746- (long) getGeneralProperty: (int) property parameter: (long) parameter {
1747	return mBackend->WndProc(property, parameter, 0);
1748}
1749
1750//--------------------------------------------------------------------------------------------------
1751
1752/**
1753 * Convenience function to avoid unneeded parameters.
1754 */
1755- (long) getGeneralProperty: (int) property {
1756	return mBackend->WndProc(property, 0, 0);
1757}
1758
1759//--------------------------------------------------------------------------------------------------
1760
1761/**
1762 * Use this variant if you have to pass in a reference to something (e.g. a text range).
1763 */
1764- (long) getGeneralProperty: (int) property ref: (const void *) ref {
1765	return mBackend->WndProc(property, 0, (sptr_t) ref);
1766}
1767
1768//--------------------------------------------------------------------------------------------------
1769
1770/**
1771 * Specialized property setter for colors.
1772 */
1773- (void) setColorProperty: (int) property parameter: (long) parameter value: (NSColor *) value {
1774	if (value.colorSpaceName != NSDeviceRGBColorSpace)
1775		value = [value colorUsingColorSpaceName: NSDeviceRGBColorSpace];
1776	long red = static_cast<long>(value.redComponent * 255);
1777	long green = static_cast<long>(value.greenComponent * 255);
1778	long blue = static_cast<long>(value.blueComponent * 255);
1779
1780	long color = (blue << 16) + (green << 8) + red;
1781	mBackend->WndProc(property, parameter, color);
1782}
1783
1784//--------------------------------------------------------------------------------------------------
1785
1786/**
1787 * Another color property setting, which allows to specify the color as string like in HTML
1788 * documents (i.e. with leading # and either 3 hex digits or 6).
1789 */
1790- (void) setColorProperty: (int) property parameter: (long) parameter fromHTML: (NSString *) fromHTML {
1791	if (fromHTML.length > 3 && [fromHTML characterAtIndex: 0] == '#') {
1792		bool longVersion = fromHTML.length > 6;
1793		int index = 1;
1794
1795		char value[3] = {0, 0, 0};
1796		value[0] = static_cast<char>([fromHTML characterAtIndex: index++]);
1797		if (longVersion)
1798			value[1] = static_cast<char>([fromHTML characterAtIndex: index++]);
1799		else
1800			value[1] = value[0];
1801
1802		unsigned rawRed;
1803		[[NSScanner scannerWithString: @(value)] scanHexInt: &rawRed];
1804
1805		value[0] = static_cast<char>([fromHTML characterAtIndex: index++]);
1806		if (longVersion)
1807			value[1] = static_cast<char>([fromHTML characterAtIndex: index++]);
1808		else
1809			value[1] = value[0];
1810
1811		unsigned rawGreen;
1812		[[NSScanner scannerWithString: @(value)] scanHexInt: &rawGreen];
1813
1814		value[0] = static_cast<char>([fromHTML characterAtIndex: index++]);
1815		if (longVersion)
1816			value[1] = static_cast<char>([fromHTML characterAtIndex: index++]);
1817		else
1818			value[1] = value[0];
1819
1820		unsigned rawBlue;
1821		[[NSScanner scannerWithString: @(value)] scanHexInt: &rawBlue];
1822
1823		long color = (rawBlue << 16) + (rawGreen << 8) + rawRed;
1824		mBackend->WndProc(property, parameter, color);
1825	}
1826}
1827
1828//--------------------------------------------------------------------------------------------------
1829
1830/**
1831 * Specialized property getter for colors.
1832 */
1833- (NSColor *) getColorProperty: (int) property parameter: (long) parameter {
1834	long color = mBackend->WndProc(property, parameter, 0);
1835	CGFloat red = (color & 0xFF) / 255.0;
1836	CGFloat green = ((color >> 8) & 0xFF) / 255.0;
1837	CGFloat blue = ((color >> 16) & 0xFF) / 255.0;
1838	NSColor *result = [NSColor colorWithDeviceRed: red green: green blue: blue alpha: 1];
1839	return result;
1840}
1841
1842//--------------------------------------------------------------------------------------------------
1843
1844/**
1845 * Specialized property setter for references (pointers, addresses).
1846 */
1847- (void) setReferenceProperty: (int) property parameter: (long) parameter value: (const void *) value {
1848	mBackend->WndProc(property, parameter, (sptr_t) value);
1849}
1850
1851//--------------------------------------------------------------------------------------------------
1852
1853/**
1854 * Specialized property getter for references (pointers, addresses).
1855 */
1856- (const void *) getReferenceProperty: (int) property parameter: (long) parameter {
1857	return (const void *) mBackend->WndProc(property, parameter, 0);
1858}
1859
1860//--------------------------------------------------------------------------------------------------
1861
1862/**
1863 * Specialized property setter for string values.
1864 */
1865- (void) setStringProperty: (int) property parameter: (long) parameter value: (NSString *) value {
1866	const char *rawValue = value.UTF8String;
1867	mBackend->WndProc(property, parameter, (sptr_t) rawValue);
1868}
1869
1870
1871//--------------------------------------------------------------------------------------------------
1872
1873/**
1874 * Specialized property getter for string values.
1875 */
1876- (NSString *) getStringProperty: (int) property parameter: (long) parameter {
1877	const char *rawValue = (const char *) mBackend->WndProc(property, parameter, 0);
1878	return @(rawValue);
1879}
1880
1881//--------------------------------------------------------------------------------------------------
1882
1883/**
1884 * Specialized property setter for lexer properties, which are commonly passed as strings.
1885 */
1886- (void) setLexerProperty: (NSString *) name value: (NSString *) value {
1887	const char *rawName = name.UTF8String;
1888	const char *rawValue = value.UTF8String;
1889	mBackend->WndProc(SCI_SETPROPERTY, (sptr_t) rawName, (sptr_t) rawValue);
1890}
1891
1892//--------------------------------------------------------------------------------------------------
1893
1894/**
1895 * Specialized property getter for references (pointers, addresses).
1896 */
1897- (NSString *) getLexerProperty: (NSString *) name {
1898	const char *rawName = name.UTF8String;
1899	const char *result = (const char *) mBackend->WndProc(SCI_SETPROPERTY, (sptr_t) rawName, 0);
1900	return @(result);
1901}
1902
1903//--------------------------------------------------------------------------------------------------
1904
1905/**
1906 * Sets the notification callback
1907 */
1908- (void) registerNotifyCallback: (intptr_t) windowid value: (SciNotifyFunc) callback {
1909	mBackend->RegisterNotifyCallback(windowid, callback);
1910}
1911
1912
1913//--------------------------------------------------------------------------------------------------
1914
1915/**
1916 * Sets the new control which is displayed as info bar at the top or bottom of the editor.
1917 * Set newBar to nil if you want to hide the bar again.
1918 * The info bar's height is set to the height of the scrollbar.
1919 */
1920- (void) setInfoBar: (NSView <InfoBarCommunicator> *) newBar top: (BOOL) top {
1921	if (mInfoBar != newBar) {
1922		[mInfoBar removeFromSuperview];
1923
1924		mInfoBar = newBar;
1925		mInfoBarAtTop = top;
1926		if (mInfoBar != nil) {
1927			[self addSubview: mInfoBar];
1928			[mInfoBar setCallback: self];
1929		}
1930
1931		[self positionSubViews];
1932	}
1933}
1934
1935//--------------------------------------------------------------------------------------------------
1936
1937/**
1938 * Sets the edit's info bar status message. This call only has an effect if there is an info bar.
1939 */
1940- (void) setStatusText: (NSString *) text {
1941	if (mInfoBar != nil)
1942		[mInfoBar notify: IBNStatusChanged message: text location: NSZeroPoint value: 0];
1943}
1944
1945//--------------------------------------------------------------------------------------------------
1946
1947- (NSRange) selectedRange {
1948	return [mContent selectedRange];
1949}
1950
1951//--------------------------------------------------------------------------------------------------
1952
1953/**
1954 * Return the main selection as an NSRange of positions (not characters).
1955 * Unlike selectedRange, this can return empty ranges inside the document.
1956 */
1957
1958- (NSRange) selectedRangePositions {
1959	const sptr_t positionBegin = [self message: SCI_GETSELECTIONSTART];
1960	const sptr_t positionEnd = [self message: SCI_GETSELECTIONEND];
1961	return NSMakeRange(positionBegin, positionEnd-positionBegin);
1962}
1963
1964
1965//--------------------------------------------------------------------------------------------------
1966
1967- (void) insertText: (id) aString {
1968	if ([aString isKindOfClass: [NSString class]])
1969		mBackend->InsertText(aString, EditModel::CharacterSource::directInput);
1970	else if ([aString isKindOfClass: [NSAttributedString class]])
1971		mBackend->InsertText([aString string], EditModel::CharacterSource::directInput);
1972}
1973
1974//--------------------------------------------------------------------------------------------------
1975
1976/**
1977 * For backwards compatibility.
1978 */
1979- (BOOL) findAndHighlightText: (NSString *) searchText
1980		    matchCase: (BOOL) matchCase
1981		    wholeWord: (BOOL) wholeWord
1982		     scrollTo: (BOOL) scrollTo
1983			 wrap: (BOOL) wrap {
1984	return [self findAndHighlightText: searchText
1985				matchCase: matchCase
1986				wholeWord: wholeWord
1987				 scrollTo: scrollTo
1988				     wrap: wrap
1989				backwards: NO];
1990}
1991
1992//--------------------------------------------------------------------------------------------------
1993
1994/**
1995 * Searches and marks the first occurrence of the given text and optionally scrolls it into view.
1996 *
1997 * @result YES if something was found, NO otherwise.
1998 */
1999- (BOOL) findAndHighlightText: (NSString *) searchText
2000		    matchCase: (BOOL) matchCase
2001		    wholeWord: (BOOL) wholeWord
2002		     scrollTo: (BOOL) scrollTo
2003			 wrap: (BOOL) wrap
2004		    backwards: (BOOL) backwards {
2005	int searchFlags= 0;
2006	if (matchCase)
2007		searchFlags |= SCFIND_MATCHCASE;
2008	if (wholeWord)
2009		searchFlags |= SCFIND_WHOLEWORD;
2010
2011	long selectionStart = [self getGeneralProperty: SCI_GETSELECTIONSTART parameter: 0];
2012	long selectionEnd = [self getGeneralProperty: SCI_GETSELECTIONEND parameter: 0];
2013
2014	// Sets the start point for the coming search to the beginning of the current selection.
2015	// For forward searches we have therefore to set the selection start to the current selection end
2016	// for proper incremental search. This does not harm as we either get a new selection if something
2017	// is found or the previous selection is restored.
2018	if (!backwards)
2019		[self getGeneralProperty: SCI_SETSELECTIONSTART parameter: selectionEnd];
2020	[self setGeneralProperty: SCI_SEARCHANCHOR value: 0];
2021	sptr_t result;
2022	const char *textToSearch = searchText.UTF8String;
2023
2024	// The following call will also set the selection if something was found.
2025	if (backwards) {
2026		result = [ScintillaView directCall: self
2027					   message: SCI_SEARCHPREV
2028					    wParam: searchFlags
2029					    lParam: (sptr_t) textToSearch];
2030		if (result < 0 && wrap) {
2031			// Try again from the end of the document if nothing could be found so far and
2032			// wrapped search is set.
2033			[self getGeneralProperty: SCI_SETSELECTIONSTART parameter: [self getGeneralProperty: SCI_GETTEXTLENGTH parameter: 0]];
2034			[self setGeneralProperty: SCI_SEARCHANCHOR value: 0];
2035			result = [ScintillaView directCall: self
2036						   message: SCI_SEARCHNEXT
2037						    wParam: searchFlags
2038						    lParam: (sptr_t) textToSearch];
2039		}
2040	} else {
2041		result = [ScintillaView directCall: self
2042					   message: SCI_SEARCHNEXT
2043					    wParam: searchFlags
2044					    lParam: (sptr_t) textToSearch];
2045		if (result < 0 && wrap) {
2046			// Try again from the start of the document if nothing could be found so far and
2047			// wrapped search is set.
2048			[self getGeneralProperty: SCI_SETSELECTIONSTART parameter: 0];
2049			[self setGeneralProperty: SCI_SEARCHANCHOR value: 0];
2050			result = [ScintillaView directCall: self
2051						   message: SCI_SEARCHNEXT
2052						    wParam: searchFlags
2053						    lParam: (sptr_t) textToSearch];
2054		}
2055	}
2056
2057	if (result >= 0) {
2058		if (scrollTo)
2059			[self setGeneralProperty: SCI_SCROLLCARET value: 0];
2060	} else {
2061		// Restore the former selection if we did not find anything.
2062		[self setGeneralProperty: SCI_SETSELECTIONSTART value: selectionStart];
2063		[self setGeneralProperty: SCI_SETSELECTIONEND value: selectionEnd];
2064	}
2065	return (result >= 0) ? YES : NO;
2066}
2067
2068//--------------------------------------------------------------------------------------------------
2069
2070/**
2071 * Searches the given text and replaces
2072 *
2073 * @result Number of entries replaced, 0 if none.
2074 */
2075- (int) findAndReplaceText: (NSString *) searchText
2076		    byText: (NSString *) newText
2077		 matchCase: (BOOL) matchCase
2078		 wholeWord: (BOOL) wholeWord
2079		     doAll: (BOOL) doAll {
2080	// The current position is where we start searching for single occurrences. Otherwise we start at
2081	// the beginning of the document.
2082	long startPosition;
2083	if (doAll)
2084		startPosition = 0; // Start at the beginning of the text if we replace all occurrences.
2085	else
2086		// For a single replacement we start at the current caret position.
2087		startPosition = [self getGeneralProperty: SCI_GETCURRENTPOS];
2088	long endPosition = [self getGeneralProperty: SCI_GETTEXTLENGTH];
2089
2090	int searchFlags= 0;
2091	if (matchCase)
2092		searchFlags |= SCFIND_MATCHCASE;
2093	if (wholeWord)
2094		searchFlags |= SCFIND_WHOLEWORD;
2095	[self setGeneralProperty: SCI_SETSEARCHFLAGS value: searchFlags];
2096	[self setGeneralProperty: SCI_SETTARGETSTART value: startPosition];
2097	[self setGeneralProperty: SCI_SETTARGETEND value: endPosition];
2098
2099	const char *textToSearch = searchText.UTF8String;
2100	long sourceLength = strlen(textToSearch); // Length in bytes.
2101	const char *replacement = newText.UTF8String;
2102	long targetLength = strlen(replacement);  // Length in bytes.
2103	sptr_t result;
2104
2105	int replaceCount = 0;
2106	if (doAll) {
2107		while (true) {
2108			result = [ScintillaView directCall: self
2109						   message: SCI_SEARCHINTARGET
2110						    wParam: sourceLength
2111						    lParam: (sptr_t) textToSearch];
2112			if (result < 0)
2113				break;
2114
2115			replaceCount++;
2116			[ScintillaView directCall: self
2117					  message: SCI_REPLACETARGET
2118					   wParam: targetLength
2119					   lParam: (sptr_t) replacement];
2120
2121			// The replacement changes the target range to the replaced text. Continue after that till the end.
2122			// The text length might be changed by the replacement so make sure the target end is the actual
2123			// text end.
2124			[self setGeneralProperty: SCI_SETTARGETSTART value: [self getGeneralProperty: SCI_GETTARGETEND]];
2125			[self setGeneralProperty: SCI_SETTARGETEND value: [self getGeneralProperty: SCI_GETTEXTLENGTH]];
2126		}
2127	} else {
2128		result = [ScintillaView directCall: self
2129					   message: SCI_SEARCHINTARGET
2130					    wParam: sourceLength
2131					    lParam: (sptr_t) textToSearch];
2132		replaceCount = (result < 0) ? 0 : 1;
2133
2134		if (replaceCount > 0) {
2135			[ScintillaView directCall: self
2136					  message: SCI_REPLACETARGET
2137					   wParam: targetLength
2138					   lParam: (sptr_t) replacement];
2139
2140			// For a single replace we set the new selection to the replaced text.
2141			[self setGeneralProperty: SCI_SETSELECTIONSTART value: [self getGeneralProperty: SCI_GETTARGETSTART]];
2142			[self setGeneralProperty: SCI_SETSELECTIONEND value: [self getGeneralProperty: SCI_GETTARGETEND]];
2143		}
2144	}
2145
2146	return replaceCount;
2147}
2148
2149//--------------------------------------------------------------------------------------------------
2150
2151- (void) setFontName: (NSString *) font
2152		size: (int) size
2153		bold: (BOOL) bold
2154	      italic: (BOOL) italic {
2155	for (int i = 0; i < 128; i++) {
2156		[self setGeneralProperty: SCI_STYLESETFONT
2157			       parameter: i
2158				   value: (sptr_t)font.UTF8String];
2159		[self setGeneralProperty: SCI_STYLESETSIZE
2160			       parameter: i
2161				   value: size];
2162		[self setGeneralProperty: SCI_STYLESETBOLD
2163			       parameter: i
2164				   value: bold];
2165		[self setGeneralProperty: SCI_STYLESETITALIC
2166			       parameter: i
2167				   value: italic];
2168	}
2169}
2170
2171//--------------------------------------------------------------------------------------------------
2172
2173@end
2174
2175