1 // SciTE - Scintilla based Text Editor
2 /** @file SciTEBase.cxx
3  ** Platform independent base class of editor.
4  **/
5 // Copyright 1998-2011 by Neil Hodgson <neilh@scintilla.org>
6 // The License.txt file describes the conditions under which this software may be distributed.
7 
8 #include <cstddef>
9 #include <cstdlib>
10 #include <cstdint>
11 #include <cstring>
12 #include <cstdio>
13 #include <cstdarg>
14 #include <ctime>
15 #include <cmath>
16 
17 #include <string>
18 #include <string_view>
19 #include <vector>
20 #include <map>
21 #include <set>
22 #include <algorithm>
23 #include <memory>
24 #include <chrono>
25 #include <atomic>
26 #include <mutex>
27 #include <thread>
28 
29 #include <fcntl.h>
30 #include <sys/stat.h>
31 
32 #include "ILoader.h"
33 
34 #include "ScintillaTypes.h"
35 #include "ScintillaMessages.h"
36 #include "ScintillaCall.h"
37 
38 #include "Scintilla.h"
39 #include "SciLexer.h"
40 
41 #include "GUI.h"
42 #include "ScintillaWindow.h"
43 #include "StringList.h"
44 #include "StringHelpers.h"
45 #include "FilePath.h"
46 #include "StyleDefinition.h"
47 #include "PropSetFile.h"
48 #include "StyleWriter.h"
49 #include "Extender.h"
50 #include "SciTE.h"
51 #include "JobQueue.h"
52 
53 #include "Cookie.h"
54 #include "Worker.h"
55 #include "FileWorker.h"
56 #include "MatchMarker.h"
57 #include "EditorConfig.h"
58 #include "SciTEBase.h"
59 
Searcher()60 Searcher::Searcher() {
61 	wholeWord = false;
62 	matchCase = false;
63 	regExp = false;
64 	unSlash = false;
65 	wrapFind = true;
66 	reverseFind = false;
67 
68 	searchStartPosition = 0;
69 	replacing = false;
70 	havefound = false;
71 	failedfind = false;
72 	findInStyle = false;
73 	findStyle = 0;
74 	closeFind = CloseFind::closeAlways;
75 
76 	focusOnReplace = false;
77 }
78 
InsertFindInMemory()79 void Searcher::InsertFindInMemory() {
80 	memFinds.Insert(findWhat.c_str());
81 }
82 
83 // The find and replace dialogs and strips often manipulate boolean
84 // flags based on dialog control IDs and menu IDs.
FlagFromCmd(int cmd)85 bool &Searcher::FlagFromCmd(int cmd) noexcept {
86 	static bool notFound;
87 	switch (cmd) {
88 	case IDWHOLEWORD:
89 	case IDM_WHOLEWORD:
90 		return wholeWord;
91 	case IDMATCHCASE:
92 	case IDM_MATCHCASE:
93 		return matchCase;
94 	case IDREGEXP:
95 	case IDM_REGEXP:
96 		return regExp;
97 	case IDUNSLASH:
98 	case IDM_UNSLASH:
99 		return unSlash;
100 	case IDWRAP:
101 	case IDM_WRAPAROUND:
102 		return wrapFind;
103 	case IDDIRECTIONUP:
104 	case IDM_DIRECTIONUP:
105 		return reverseFind;
106 	}
107 	return notFound;
108 }
109 
SciTEBase(Extension * ext)110 SciTEBase::SciTEBase(Extension *ext) : apis(true), pwFocussed(&wEditor), extender(ext) {
111 	needIdle = false;
112 	codePage = 0;
113 	characterSet = SA::CharacterSet::Ansi;
114 	language = "java";
115 	lexLanguage = SCLEX_CPP;
116 	lexLPeg = -1;
117 	functionDefinition = "";
118 	diagnosticStyleStart = 0;
119 	stripTrailingSpaces = false;
120 	ensureFinalLineEnd = false;
121 	ensureConsistentLineEnds = false;
122 	indentOpening = true;
123 	indentClosing = true;
124 	indentMaintain = false;
125 	statementLookback = 10;
126 	preprocessorSymbol = '\0';
127 
128 	tbVisible = false;
129 	sbVisible = false;
130 	tabVisible = false;
131 	tabHideOne = false;
132 	tabMultiLine = false;
133 	sbNum = 1;
134 	visHeightTools = 0;
135 	visHeightTab = 0;
136 	visHeightStatus = 0;
137 	visHeightEditor = 1;
138 	heightBar = 7;
139 	dialogsOnScreen = 0;
140 	topMost = false;
141 	wrap = false;
142 	wrapOutput = false;
143 	wrapStyle = SA::Wrap::Word;
144 	idleStyling = SA::IdleStyling::None;
145 	alphaIndicator = static_cast<SA::Alpha>(30);
146 	underIndicator = false;
147 	openFilesHere = false;
148 	fullScreen = false;
149 	appearance = {};
150 
151 	heightOutput = 0;
152 	heightOutputStartDrag = 0;
153 	previousHeightOutput = 0;
154 
155 	allowMenuActions = true;
156 	scrollOutput = 1;
157 	returnOutputToCommand = true;
158 
159 	ptStartDrag.x = 0;
160 	ptStartDrag.y = 0;
161 	capturedMouse = false;
162 	firstPropertiesRead = true;
163 	localiser.read = false;
164 	splitVertical = false;
165 	bufferedDraw = true;
166 	bracesCheck = true;
167 	bracesSloppy = false;
168 	bracesStyle = 0;
169 	braceCount = 0;
170 
171 	indentationWSVisible = true;
172 	indentExamine = SA::IndentView::LookBoth;
173 	autoCompleteIgnoreCase = false;
174 	imeAutoComplete = false;
175 	callTipUseEscapes = false;
176 	callTipIgnoreCase = false;
177 	autoCCausedByOnlyOne = false;
178 	startCalltipWord = 0;
179 	currentCallTip = 0;
180 	maxCallTips = 1;
181 	currentCallTipWord = "";
182 	lastPosCallTip = 0;
183 
184 	margin = false;
185 	marginWidth = marginWidthDefault;
186 	foldMargin = true;
187 	foldMarginWidth = foldMarginWidthDefault;
188 	lineNumbers = false;
189 	lineNumbersWidth = lineNumbersWidthDefault;
190 	lineNumbersExpand = false;
191 
192 	macrosEnabled = false;
193 	recording = false;
194 
195 	propsEmbed.superPS = &propsPlatform;
196 	propsBase.superPS = &propsEmbed;
197 	propsUser.superPS = &propsBase;
198 	propsDirectory.superPS = &propsUser;
199 	propsLocal.superPS = &propsDirectory;
200 	propsDiscovered.superPS = &propsLocal;
201 	props.superPS = &propsDiscovered;
202 
203 	propsStatus.superPS = &props;
204 
205 	needReadProperties = false;
206 	quitting = false;
207 
208 	timerMask = 0;
209 	delayBeforeAutoSave = 0;
210 
211 	editorConfig = IEditorConfig::Create();
212 }
213 
~SciTEBase()214 SciTEBase::~SciTEBase() {
215 	if (extender)
216 		extender->Finalise();
217 	popup.Destroy();
218 }
219 
Finalise()220 void SciTEBase::Finalise() {
221 	TimerEnd(timerAutoSave);
222 }
223 
PerformOnNewThread(Worker * pWorker)224 bool SciTEBase::PerformOnNewThread(Worker *pWorker) {
225 	try {
226 		std::thread thread([pWorker] {
227 			pWorker->Execute();
228 		});
229 		thread.detach();
230 		return true;
231 	} catch (std::system_error &) {
232 		return false;
233 	}
234 }
235 
WorkerCommand(int cmd,Worker * pWorker)236 void SciTEBase::WorkerCommand(int cmd, Worker *pWorker) {
237 	switch (cmd) {
238 	case WORK_FILEREAD:
239 		TextRead(static_cast<FileLoader *>(pWorker));
240 		UpdateProgress(pWorker);
241 		break;
242 	case WORK_FILEWRITTEN:
243 		TextWritten(static_cast<FileStorer *>(pWorker));
244 		UpdateProgress(pWorker);
245 		break;
246 	case WORK_FILEPROGRESS:
247 		UpdateProgress(pWorker);
248 		break;
249 	}
250 }
251 
CurrentAppearance() const252 SystemAppearance SciTEBase::CurrentAppearance() const noexcept {
253 	return {};
254 }
255 
CheckAppearanceChanged()256 void SciTEBase::CheckAppearanceChanged() {
257 	const SystemAppearance currentAppearance = CurrentAppearance();
258 	if (!(appearance == currentAppearance)) {
259 		appearance = currentAppearance;
260 		ReloadProperties();
261 	}
262 }
263 
264 // The system focus may move to other controls including the menu bar
265 // but we are normally interested in whether the edit or output pane was
266 // most recently focused and should be used by menu commands.
SetPaneFocus(bool editPane)267 void SciTEBase::SetPaneFocus(bool editPane) noexcept {
268 	pwFocussed = editPane ? &wEditor : &wOutput;
269 }
270 
PaneFocused()271 GUI::ScintillaWindow &SciTEBase::PaneFocused() {
272 	return wOutput.HasFocus() ? wOutput : wEditor;
273 }
274 
PaneSource(int destination)275 GUI::ScintillaWindow &SciTEBase::PaneSource(int destination) {
276 	if (destination == IDM_SRCWIN)
277 		return wEditor;
278 	else if (destination == IDM_RUNWIN)
279 		return wOutput;
280 	else
281 		return PaneFocused();
282 }
283 
CallFocusedElseDefault(int defaultValue,SA::Message msg,uintptr_t wParam,intptr_t lParam)284 intptr_t SciTEBase::CallFocusedElseDefault(int defaultValue, SA::Message msg, uintptr_t wParam, intptr_t lParam) {
285 	if (wOutput.HasFocus())
286 		return wOutput.Call(msg, wParam, lParam);
287 	else if (wEditor.HasFocus())
288 		return wEditor.Call(msg, wParam, lParam);
289 	else
290 		return defaultValue;
291 }
292 
CallChildren(SA::Message msg,uintptr_t wParam,intptr_t lParam)293 void SciTEBase::CallChildren(SA::Message msg, uintptr_t wParam, intptr_t lParam) {
294 	wEditor.Call(msg, wParam, lParam);
295 	wOutput.Call(msg, wParam, lParam);
296 }
297 
GetTranslationToAbout(const char * const propname,bool retainIfNotFound)298 std::string SciTEBase::GetTranslationToAbout(const char *const propname, bool retainIfNotFound) {
299 #if !defined(GTK)
300 	return GUI::UTF8FromString(localiser.Text(propname, retainIfNotFound));
301 #else
302 	// On GTK, localiser.Text always converts to UTF-8.
303 	return localiser.Text(propname, retainIfNotFound);
304 #endif
305 }
306 
ViewWhitespace(bool view)307 void SciTEBase::ViewWhitespace(bool view) {
308 	if (view && indentationWSVisible == 2)
309 		wEditor.SetViewWS(SA::WhiteSpace::VisibleOnlyInIndent);
310 	else if (view && indentationWSVisible)
311 		wEditor.SetViewWS(SA::WhiteSpace::VisibleAlways);
312 	else if (view)
313 		wEditor.SetViewWS(SA::WhiteSpace::VisibleAfterIndent);
314 	else
315 		wEditor.SetViewWS(SA::WhiteSpace::Invisible);
316 }
317 
GetStyleAndWords(const char * base)318 StyleAndWords SciTEBase::GetStyleAndWords(const char *base) {
319 	StyleAndWords sw;
320 	std::string fileNameForExtension = ExtensionFileName();
321 	std::string sAndW = props.GetNewExpandString(base, fileNameForExtension.c_str());
322 	sw.styleNumber = atoi(sAndW.c_str());
323 	const char *space = strchr(sAndW.c_str(), ' ');
324 	if (space)
325 		sw.words = space + 1;
326 	return sw;
327 }
328 
AssignKey(SA::Keys key,SA::KeyMod mods,int cmd)329 void SciTEBase::AssignKey(SA::Keys key, SA::KeyMod mods, int cmd) {
330 	wEditor.AssignCmdKey(
331 		IntFromTwoShorts(static_cast<short>(key),
332 				 static_cast<short>(mods)), cmd);
333 }
334 
335 /**
336  * Override the language of the current file with the one indicated by @a cmdID.
337  * Mostly used to set a language on a file of unknown extension.
338  */
SetOverrideLanguage(int cmdID)339 void SciTEBase::SetOverrideLanguage(int cmdID) {
340 	RecentFile rf = GetFilePosition();
341 	EnsureRangeVisible(wEditor, SA::Range(0, wEditor.Length()), false);
342 	// Zero all the style bytes
343 	wEditor.ClearDocumentStyle();
344 
345 	CurrentBuffer()->overrideExtension = "x.";
346 	CurrentBuffer()->overrideExtension += languageMenu[cmdID].extension;
347 	ReadProperties();
348 	SetIndentSettings();
349 	wEditor.ColouriseAll();
350 	Redraw();
351 	DisplayAround(rf);
352 }
353 
LengthDocument()354 SA::Position SciTEBase::LengthDocument() {
355 	return wEditor.Length();
356 }
357 
GetCaretInLine()358 SA::Position SciTEBase::GetCaretInLine() {
359 	const SA::Position caret = wEditor.CurrentPos();
360 	const SA::Line line = wEditor.LineFromPosition(caret);
361 	const SA::Position lineStart = wEditor.LineStart(line);
362 	return caret - lineStart;
363 }
364 
GetLine(SA::Line line)365 std::string SciTEBase::GetLine(SA::Line line) {
366 	const SA::Range rangeLine(wEditor.LineStart(line), wEditor.LineEnd(line));
367 	return wEditor.StringOfRange(rangeLine);
368 }
369 
GetCurrentLine()370 std::string SciTEBase::GetCurrentLine() {
371 	// Get needed buffer size
372 	const SA::Position len = wEditor.GetCurLine(0, nullptr);
373 	// Allocate buffer, including space for NUL
374 	std::string text(len, '\0');
375 	// And get the line
376 	wEditor.GetCurLine(len, &text[0]);
377 	// Return without extra NUL
378 	return text.substr(0, text.length()-1);
379 }
380 
381 /**
382  * Check if the given line is a preprocessor condition line.
383  * @return The kind of preprocessor condition (enum values).
384  */
LinePreprocessorCondition(SA::Line line)385 SciTEBase::PreProc SciTEBase::LinePreprocessorCondition(SA::Line line) {
386 	const std::string text = GetLine(line);
387 
388 	const char *currChar = text.c_str();
389 
390 	if (!currChar) {
391 		return PreProc::None;
392 	}
393 	while (IsASpace(*currChar) && *currChar) {
394 		currChar++;
395 	}
396 	if (preprocessorSymbol && (*currChar == preprocessorSymbol)) {
397 		currChar++;
398 		while (IsASpace(*currChar) && *currChar) {
399 			currChar++;
400 		}
401 		char word[32] = "";
402 		size_t i = 0;
403 		while (!IsASpace(*currChar) && *currChar && (i < (sizeof(word) - 1))) {
404 			word[i++] = *currChar++;
405 		}
406 		word[i] = '\0';
407 		std::map<std::string, PreProc>::const_iterator it = preprocOfString.find(word);
408 		if (it != preprocOfString.end()) {
409 			return it->second;
410 		}
411 	}
412 	return PreProc::None;
413 }
414 
415 /**
416  * Search a matching preprocessor condition line.
417  * @return @c true if the end condition are meet.
418  * Also set curLine to the line where one of these conditions is mmet.
419  */
FindMatchingPreprocessorCondition(SA::Line & curLine,int direction,PreProc condEnd1,PreProc condEnd2)420 bool SciTEBase::FindMatchingPreprocessorCondition(
421 	SA::Line &curLine,   		///< Number of the line where to start the search
422 	int direction,   		///< Direction of search: 1 = forward, -1 = backward
423 	PreProc condEnd1,   		///< First status of line for which the search is OK
424 	PreProc condEnd2) {		///< Second one
425 
426 	bool isInside = false;
427 	int level = 0;
428 	const SA::Line maxLines = wEditor.LineCount() - 1;
429 
430 	while (curLine < maxLines && curLine > 0 && !isInside) {
431 		curLine += direction;	// Increment or decrement
432 		const PreProc status = LinePreprocessorCondition(curLine);
433 
434 		if ((direction == 1 && status == PreProc::Start) || (direction == -1 && status == PreProc::End)) {
435 			level++;
436 		} else if (level > 0 && ((direction == 1 && status == PreProc::End) || (direction == -1 && status == PreProc::Start))) {
437 			level--;
438 		} else if (level == 0 && (status == condEnd1 || status == condEnd2)) {
439 			isInside = true;
440 		}
441 	}
442 
443 	return isInside;
444 }
445 
446 /**
447  * Find if there is a preprocessor condition after or before the caret position,
448  * @return @c true if inside a preprocessor condition.
449  */
FindMatchingPreprocCondPosition(bool isForward,SA::Position mppcAtCaret,SA::Position & mppcMatch)450 bool SciTEBase::FindMatchingPreprocCondPosition(
451 	bool isForward,   		///< @c true if search forward
452 	SA::Position mppcAtCaret,   	///< Matching preproc. cond.: current position of caret
453 	SA::Position &mppcMatch) {		///< Matching preproc. cond.: matching position
454 
455 	bool isInside = false;
456 
457 	// Get current line
458 	SA::Line curLine = wEditor.LineFromPosition(mppcAtCaret);
459 	const PreProc status = LinePreprocessorCondition(curLine);
460 
461 	switch (status) {
462 	case PreProc::Start:
463 		if (isForward) {
464 			isInside = FindMatchingPreprocessorCondition(curLine, 1,
465 					PreProc::Middle, PreProc::End);
466 		} else {
467 			mppcMatch = mppcAtCaret;
468 			return true;
469 		}
470 		break;
471 	case PreProc::Middle:
472 		if (isForward) {
473 			isInside = FindMatchingPreprocessorCondition(curLine, 1,
474 					PreProc::Middle, PreProc::End);
475 		} else {
476 			isInside = FindMatchingPreprocessorCondition(curLine, -1,
477 					PreProc::Start, PreProc::Middle);
478 		}
479 		break;
480 	case PreProc::End:
481 		if (isForward) {
482 			mppcMatch = mppcAtCaret;
483 			return true;
484 		} else {
485 			isInside = FindMatchingPreprocessorCondition(curLine, -1,
486 					PreProc::Start, PreProc::Middle);
487 		}
488 		break;
489 	default:   	// Should be noPPC
490 
491 		if (isForward) {
492 			isInside = FindMatchingPreprocessorCondition(curLine, 1,
493 					PreProc::Middle, PreProc::End);
494 		} else {
495 			isInside = FindMatchingPreprocessorCondition(curLine, -1,
496 					PreProc::Start, PreProc::Middle);
497 		}
498 		break;
499 	}
500 
501 	if (isInside) {
502 		mppcMatch = wEditor.LineStart(curLine);
503 	}
504 	return isInside;
505 }
506 
IsBrace(char ch)507 static bool IsBrace(char ch) noexcept {
508 	return ch == '[' || ch == ']' || ch == '(' || ch == ')' || ch == '{' || ch == '}';
509 }
510 
511 /**
512  * Find if there is a brace next to the caret, checking before caret first, then
513  * after caret. If brace found also find its matching brace.
514  * @return @c true if inside a bracket pair.
515  */
FindMatchingBracePosition(bool editor,SA::Position & braceAtCaret,SA::Position & braceOpposite,bool sloppy)516 bool SciTEBase::FindMatchingBracePosition(bool editor, SA::Position &braceAtCaret, SA::Position &braceOpposite, bool sloppy) {
517 	bool isInside = false;
518 	GUI::ScintillaWindow &win = editor ? wEditor : wOutput;
519 
520 	const int mainSel = win.MainSelection();
521 	if (win.SelectionNCaretVirtualSpace(mainSel) > 0)
522 		return false;
523 
524 	const int bracesStyleCheck = editor ? bracesStyle : 0;
525 	SA::Position caretPos = win.CurrentPos();
526 	braceAtCaret = -1;
527 	braceOpposite = -1;
528 	char charBefore = '\0';
529 	int styleBefore = 0;
530 	const SA::Position lengthDoc = win.Length();
531 	TextReader acc(win);
532 	if ((lengthDoc > 0) && (caretPos > 0)) {
533 		// Check to ensure not matching brace that is part of a multibyte character
534 		if (win.PositionBefore(caretPos) == (caretPos - 1)) {
535 			charBefore = acc[caretPos - 1];
536 			styleBefore = acc.StyleAt(caretPos - 1);
537 		}
538 	}
539 	// Priority goes to character before caret
540 	if (charBefore && IsBrace(charBefore) &&
541 			((styleBefore == bracesStyleCheck) || (!bracesStyle))) {
542 		braceAtCaret = caretPos - 1;
543 	}
544 	bool colonMode = false;
545 	if ((lexLanguage == SCLEX_PYTHON) &&
546 			(':' == charBefore) && (SCE_P_OPERATOR == styleBefore)) {
547 		braceAtCaret = caretPos - 1;
548 		colonMode = true;
549 	}
550 	bool isAfter = true;
551 	if (lengthDoc > 0 && sloppy && (braceAtCaret < 0) && (caretPos < lengthDoc)) {
552 		// No brace found so check other side
553 		// Check to ensure not matching brace that is part of a multibyte character
554 		if (win.PositionAfter(caretPos) == (caretPos + 1)) {
555 			const char charAfter = acc[caretPos];
556 			const int styleAfter = acc.StyleAt(caretPos);
557 			if (charAfter && IsBrace(charAfter) && ((styleAfter == bracesStyleCheck) || (!bracesStyle))) {
558 				braceAtCaret = caretPos;
559 				isAfter = false;
560 			}
561 			if ((lexLanguage == SCLEX_PYTHON) &&
562 					(':' == charAfter) && (SCE_P_OPERATOR == styleAfter)) {
563 				braceAtCaret = caretPos;
564 				colonMode = true;
565 			}
566 		}
567 	}
568 	if (braceAtCaret >= 0) {
569 		if (colonMode) {
570 			const SA::Line lineStart = win.LineFromPosition(braceAtCaret);
571 			const SA::Line lineMaxSubord = win.LastChild(lineStart, static_cast<SA::FoldLevel>(-1));
572 			braceOpposite = win.LineEnd(lineMaxSubord);
573 		} else {
574 			braceOpposite = win.BraceMatch(braceAtCaret, 0);
575 		}
576 		if (braceOpposite > braceAtCaret) {
577 			isInside = isAfter;
578 		} else {
579 			isInside = !isAfter;
580 		}
581 	}
582 	return isInside;
583 }
584 
BraceMatch(bool editor)585 void SciTEBase::BraceMatch(bool editor) {
586 	if (!bracesCheck)
587 		return;
588 	SA::Position braceAtCaret = -1;
589 	SA::Position braceOpposite = -1;
590 	FindMatchingBracePosition(editor, braceAtCaret, braceOpposite, bracesSloppy);
591 	GUI::ScintillaWindow &win = editor ? wEditor : wOutput;
592 	if ((braceAtCaret != -1) && (braceOpposite == -1)) {
593 		win.BraceBadLight(braceAtCaret);
594 		wEditor.SetHighlightGuide(0);
595 	} else {
596 		char chBrace = 0;
597 		if (braceAtCaret >= 0)
598 			chBrace = win.CharacterAt(braceAtCaret);
599 		win.BraceHighlight(braceAtCaret, braceOpposite);
600 		SA::Position columnAtCaret = win.Column(braceAtCaret);
601 		SA::Position columnOpposite = win.Column(braceOpposite);
602 		if (chBrace == ':') {
603 			const SA::Line lineStart = win.LineFromPosition(braceAtCaret);
604 			const SA::Position indentPos = win.LineIndentPosition(lineStart);
605 			const SA::Position indentPosNext = win.LineIndentPosition(lineStart + 1);
606 			columnAtCaret = win.Column(indentPos);
607 			const SA::Position columnAtCaretNext = win.Column(indentPosNext);
608 			const int indentSize = win.Indent();
609 			if (columnAtCaretNext - indentSize > 1)
610 				columnAtCaret = columnAtCaretNext - indentSize;
611 			if (columnOpposite == 0)	// If the final line of the structure is empty
612 				columnOpposite = columnAtCaret;
613 		} else {
614 			if (win.LineFromPosition(braceAtCaret) == win.LineFromPosition(braceOpposite)) {
615 				// Avoid attempting to draw a highlight guide
616 				columnAtCaret = 0;
617 				columnOpposite = 0;
618 			}
619 		}
620 
621 		if (props.GetInt("highlight.indentation.guides"))
622 			win.SetHighlightGuide(std::min(columnAtCaret, columnOpposite));
623 	}
624 }
625 
SetWindowName()626 void SciTEBase::SetWindowName() {
627 	if (filePath.IsUntitled()) {
628 		windowName = localiser.Text("Untitled");
629 		windowName.insert(0, GUI_TEXT("("));
630 		windowName += GUI_TEXT(")");
631 	} else if (props.GetInt("title.full.path") == 2) {
632 		windowName = FileNameExt().AsInternal();
633 		windowName += GUI_TEXT(" ");
634 		windowName += localiser.Text("in");
635 		windowName += GUI_TEXT(" ");
636 		windowName += filePath.Directory().AsInternal();
637 	} else if (props.GetInt("title.full.path") == 1) {
638 		windowName = filePath.AsInternal();
639 	} else {
640 		windowName = FileNameExt().AsInternal();
641 	}
642 	if (CurrentBufferConst()->isDirty)
643 		windowName += GUI_TEXT(" * ");
644 	else
645 		windowName += GUI_TEXT(" - ");
646 	windowName += appName;
647 
648 	if (buffers.length > 1 && props.GetInt("title.show.buffers")) {
649 		windowName += GUI_TEXT(" [");
650 		windowName += GUI::StringFromInteger(buffers.Current() + 1);
651 		windowName += GUI_TEXT(" ");
652 		windowName += localiser.Text("of");
653 		windowName += GUI_TEXT(" ");
654 		windowName += GUI::StringFromInteger(buffers.length);
655 		windowName += GUI_TEXT("]");
656 	}
657 
658 	wSciTE.SetTitle(windowName.c_str());
659 }
660 
GetSelection()661 SA::Range SciTEBase::GetSelection() {
662 	return wEditor.SelectionRange();
663 }
664 
GetSelectedRange()665 SelectedRange SciTEBase::GetSelectedRange() {
666 	return SelectedRange(wEditor.CurrentPos(), wEditor.Anchor());
667 }
668 
SetSelection(SA::Position anchor,SA::Position currentPos)669 void SciTEBase::SetSelection(SA::Position anchor, SA::Position currentPos) {
670 	wEditor.SetSel(anchor, currentPos);
671 }
672 
GetCTag()673 std::string SciTEBase::GetCTag() {
674 	const SA::Position lengthDoc = pwFocussed->Length();
675 	SA::Position selEnd = pwFocussed->SelectionEnd();
676 	SA::Position selStart = selEnd;
677 	TextReader acc(*pwFocussed);
678 	int mustStop = 0;
679 	while (!mustStop) {
680 		if (selStart < lengthDoc - 1) {
681 			selStart++;
682 			const char c = acc[selStart];
683 			if (c == '\r' || c == '\n') {
684 				mustStop = -1;
685 			} else if (c == '\t' && ((acc[selStart + 1] == '/' && acc[selStart + 2] == '^') || IsADigit(acc[selStart + 1]))) {
686 				mustStop = 1;
687 			}
688 		} else {
689 			mustStop = -1;
690 		}
691 	}
692 	if (mustStop == 1 && (acc[selStart + 1] == '/' && acc[selStart + 2] == '^')) {	// Found
693 		selEnd = selStart += 3;
694 		mustStop = 0;
695 		while (!mustStop) {
696 			if (selEnd < lengthDoc - 1) {
697 				selEnd++;
698 				const char c = acc[selEnd];
699 				if (c == '\r' || c == '\n') {
700 					mustStop = -1;
701 				} else if (c == '$' && acc[selEnd + 1] == '/') {
702 					mustStop = 1;	// Found!
703 				}
704 
705 			} else {
706 				mustStop = -1;
707 			}
708 		}
709 	} else if (mustStop == 1 && IsADigit(acc[selStart + 1])) {
710 		// a Tag can be referenced by line Number also
711 		selEnd = selStart += 1;
712 		while ((selEnd < lengthDoc) && IsADigit(acc[selEnd])) {
713 			selEnd++;
714 		}
715 	}
716 
717 	if (selStart < selEnd) {
718 		return pwFocussed->StringOfRange(SA::Range(selStart, selEnd));
719 	} else {
720 		return std::string();
721 	}
722 }
723 
724 // Default characters that can appear in a word
iswordcharforsel(char ch)725 bool SciTEBase::iswordcharforsel(char ch) {
726 	return !strchr("\t\n\r !\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", ch);
727 }
728 
729 // Accept slightly more characters than for a word
730 // Doesn't accept all valid characters, as they are rarely used in source filenames...
731 // Accept path separators '/' and '\', extension separator '.', and ':', MS drive unit
732 // separator, and also used for separating the line number for grep. Same for '(' and ')' for cl.
733 // Accept '?' and '%' which are used in URL.
isfilenamecharforsel(char ch)734 bool SciTEBase::isfilenamecharforsel(char ch) {
735 	return !strchr("\t\n\r \"$'*,;<>[]^`{|}", ch);
736 }
737 
islexerwordcharforsel(char ch)738 bool SciTEBase::islexerwordcharforsel(char ch) {
739 	// If there are no word.characters defined for the current file, fall back on the original function
740 	if (wordCharacters.length())
741 		return Contains(wordCharacters, ch);
742 	else
743 		return iswordcharforsel(ch);
744 }
745 
HighlightCurrentWord(bool highlight)746 void SciTEBase::HighlightCurrentWord(bool highlight) {
747 	if (!currentWordHighlight.isEnabled)
748 		return;
749 	if (!wEditor.HasFocus() && !wOutput.HasFocus()) {
750 		// Neither text window has focus, possibly app is inactive so do not highlight
751 		return;
752 	}
753 	GUI::ScintillaWindow &wCurrent = wOutput.HasFocus() ? wOutput : wEditor;
754 	// Remove old indicators if any exist.
755 	wCurrent.SetIndicatorCurrent(indicatorHighlightCurrentWord);
756 	const SA::Position lenDoc = wCurrent.Length();
757 	wCurrent.IndicatorClearRange(0, lenDoc);
758 	if (!highlight)
759 		return;
760 	// Get start & end selection.
761 	SA::Range sel = wCurrent.SelectionRange();
762 	const bool noUserSelection = sel.start == sel.end;
763 	std::string sWordToFind = RangeExtendAndGrab(wCurrent, sel,
764 				  &SciTEBase::islexerwordcharforsel);
765 	if (sWordToFind.length() == 0 || (sWordToFind.find_first_of("\n\r ") != std::string::npos))
766 		return; // No highlight when no selection or multi-lines selection.
767 	if (noUserSelection && currentWordHighlight.statesOfDelay == currentWordHighlight.noDelay) {
768 		// Manage delay before highlight when no user selection but there is word at the caret.
769 		currentWordHighlight.statesOfDelay = currentWordHighlight.delay;
770 		// Reset timer
771 		currentWordHighlight.elapsedTimes.Duration(true);
772 		return;
773 	}
774 	// Get style of the current word to highlight only word with same style.
775 	int selectedStyle = wCurrent.UnsignedStyleAt(sel.start);
776 	if (!currentWordHighlight.isOnlyWithSameStyle)
777 		selectedStyle = -1;
778 
779 	// Manage word with DBCS.
780 	const std::string wordToFind = EncodeString(sWordToFind);
781 
782 	const SA::FindOption searchFlags = SA::FindOption::MatchCase | SA::FindOption::WholeWord;
783 	matchMarker.StartMatch(&wCurrent, wordToFind,
784 			       searchFlags, selectedStyle,
785 			       indicatorHighlightCurrentWord, -1);
786 	SetIdler(true);
787 }
788 
GetRangeInUIEncoding(GUI::ScintillaWindow & win,SA::Range range)789 std::string SciTEBase::GetRangeInUIEncoding(GUI::ScintillaWindow &win, SA::Range range) {
790 	return win.StringOfRange(range);
791 }
792 
GetLine(GUI::ScintillaWindow & win,SA::Line line)793 std::string SciTEBase::GetLine(GUI::ScintillaWindow &win, SA::Line line) {
794 	const SA::Position lineStart = win.LineStart(line);
795 	const SA::Position lineEnd = win.LineEnd(line);
796 	if ((lineStart < 0) || (lineEnd < 0))
797 		return std::string();
798 	return win.StringOfRange(SA::Range(lineStart, lineEnd));
799 }
800 
RangeExtend(GUI::ScintillaWindow & wCurrent,SA::Range & range,bool (SciTEBase::* ischarforsel)(char ch))801 void SciTEBase::RangeExtend(
802 	GUI::ScintillaWindow &wCurrent,
803 	SA::Range &range,
804 	bool (SciTEBase::*ischarforsel)(char ch)) {	///< Function returning @c true if the given char. is part of the selection.
805 	if (range.start == range.end && ischarforsel) {
806 		// Empty range and have a function to extend it
807 		const SA::Position lengthDoc = wCurrent.Length();
808 		TextReader acc(wCurrent);
809 		// Try and find a word at the caret
810 		// On the left...
811 		while ((range.start > 0) && ((this->*ischarforsel)(acc[range.start - 1]))) {
812 			range.start--;
813 		}
814 		// and on the right
815 		while ((range.end < lengthDoc) && ((this->*ischarforsel)(acc[range.end]))) {
816 			range.end++;
817 		}
818 	}
819 }
820 
RangeExtendAndGrab(GUI::ScintillaWindow & wCurrent,SA::Range & range,bool (SciTEBase::* ischarforsel)(char ch),bool stripEol)821 std::string SciTEBase::RangeExtendAndGrab(
822 	GUI::ScintillaWindow &wCurrent,
823 	SA::Range &range,
824 	bool (SciTEBase::*ischarforsel)(char ch),	///< Function returning @c true if the given char. is part of the selection.
825 	bool stripEol /*=true*/) {
826 
827 	RangeExtend(wCurrent, range, ischarforsel);
828 	std::string selected;
829 	if (range.start != range.end) {
830 		selected = GetRangeInUIEncoding(wCurrent, range);
831 	}
832 	if (stripEol) {
833 		// Change whole line selected but normally end of line characters not wanted.
834 		// Remove possible terminating \r, \n, or \r\n.
835 		const size_t sellen = selected.length();
836 		if (sellen >= 2 && (selected[sellen - 2] == '\r' && selected[sellen - 1] == '\n')) {
837 			selected.erase(sellen - 2);
838 		} else if (sellen >= 1 && (selected[sellen - 1] == '\r' || selected[sellen - 1] == '\n')) {
839 			selected.erase(sellen - 1);
840 		}
841 	}
842 
843 	return selected;
844 }
845 
846 /**
847  * If there is selected text, either in the editor or the output pane,
848  * put the selection in the @a sel buffer, up to @a len characters.
849  * Otherwise, try and select characters around the caret, as long as they are OK
850  * for the @a ischarforsel function.
851  * Remove the last two character controls from the result, as they are likely
852  * to be CR and/or LF.
853  */
SelectionExtend(bool (SciTEBase::* ischarforsel)(char ch),bool stripEol)854 std::string SciTEBase::SelectionExtend(
855 	bool (SciTEBase::*ischarforsel)(char ch),	///< Function returning @c true if the given char. is part of the selection.
856 	bool stripEol /*=true*/) {
857 
858 	SA::Range sel = pwFocussed->SelectionRange();
859 	return RangeExtendAndGrab(*pwFocussed, sel, ischarforsel, stripEol);
860 }
861 
SelectionWord(bool stripEol)862 std::string SciTEBase::SelectionWord(bool stripEol /*=true*/) {
863 	return SelectionExtend(&SciTEBase::islexerwordcharforsel, stripEol);
864 }
865 
SelectionFilename()866 std::string SciTEBase::SelectionFilename() {
867 	return SelectionExtend(&SciTEBase::isfilenamecharforsel);
868 }
869 
SelectionIntoProperties()870 void SciTEBase::SelectionIntoProperties() {
871 	std::string currentSelection = SelectionExtend(0, false);
872 	props.Set("CurrentSelection", currentSelection.c_str());
873 
874 	std::string word = SelectionWord();
875 	props.Set("CurrentWord", word.c_str());
876 
877 	const SA::Range range = PaneFocused().SelectionRange();
878 	props.Set("SelectionStartLine", std::to_string(PaneFocused().LineFromPosition(range.start) + 1));
879 	props.Set("SelectionStartColumn", std::to_string(PaneFocused().Column(range.start) + 1));
880 	props.Set("SelectionEndLine", std::to_string(PaneFocused().LineFromPosition(range.end) + 1));
881 	props.Set("SelectionEndColumn", std::to_string(PaneFocused().Column(range.end) + 1));
882 }
883 
SelectionIntoFind(bool stripEol)884 void SciTEBase::SelectionIntoFind(bool stripEol /*=true*/) {
885 	std::string sel = SelectionWord(stripEol);
886 	if (sel.length() && (sel.find_first_of("\r\n") == std::string::npos)) {
887 		// The selection does not include a new line, so is likely to be
888 		// the expression to search...
889 		findWhat = sel;
890 		if (unSlash) {
891 			std::string slashedFind = Slash(findWhat, false);
892 			findWhat = slashedFind;
893 		}
894 	}
895 	// else findWhat remains the same as last time.
896 }
897 
SelectionAdd(AddSelection add)898 void SciTEBase::SelectionAdd(AddSelection add) {
899 	SA::FindOption flags = SA::FindOption::None;
900 	if (!pwFocussed->SelectionEmpty()) {
901 		// If selection is word then match as word.
902 		if (pwFocussed->IsRangeWord(pwFocussed->SelectionStart(),
903 					    pwFocussed->SelectionEnd()))
904 			flags = SA::FindOption::WholeWord;
905 	}
906 	pwFocussed->TargetWholeDocument();
907 	pwFocussed->SetSearchFlags(flags);
908 	if (add == addNext) {
909 		pwFocussed->MultipleSelectAddNext();
910 	} else {
911 		if (pwFocussed->SelectionEmpty()) {
912 			pwFocussed->MultipleSelectAddNext();
913 		}
914 		pwFocussed->MultipleSelectAddEach();
915 	}
916 }
917 
EncodeString(const std::string & s)918 std::string SciTEBase::EncodeString(const std::string &s) {
919 	return s;
920 }
921 
UnSlashAsNeeded(const std::string & s,bool escapes,bool regularExpression)922 static std::string UnSlashAsNeeded(const std::string &s, bool escapes, bool regularExpression) {
923 	if (escapes) {
924 		if (regularExpression) {
925 			// For regular expressions, the only escape sequences allowed start with \0
926 			// Other sequences, like \t, are handled by the RE engine.
927 			return UnSlashLowOctalString(s.c_str());
928 		} else {
929 			// C style escapes allowed
930 			return UnSlashString(s.c_str());
931 		}
932 	} else {
933 		return s;
934 	}
935 }
936 
RemoveFindMarks()937 void SciTEBase::RemoveFindMarks() {
938 	findMarker.Stop();	// Cancel ongoing background find
939 	if (CurrentBuffer()->findMarks != Buffer::fmNone) {
940 		wEditor.SetIndicatorCurrent(indicatorMatch);
941 		wEditor.IndicatorClearRange(0, LengthDocument());
942 		CurrentBuffer()->findMarks = Buffer::fmNone;
943 	}
944 	wEditor.AnnotationClearAll();
945 }
946 
SearchFlags(bool regularExpressions) const947 SA::FindOption SciTEBase::SearchFlags(bool regularExpressions) const {
948 	SA::FindOption opt = SA::FindOption::None;
949 	if (wholeWord)
950 		opt |= SA::FindOption::WholeWord;
951 	if (matchCase)
952 		opt |= SA::FindOption::MatchCase;
953 	if (regularExpressions)
954 		opt |= SA::FindOption::RegExp;
955 	if (props.GetInt("find.replace.regexp.posix"))
956 		opt |= SA::FindOption::Posix;
957 	if (props.GetInt("find.replace.regexp.cpp11"))
958 		opt |= SA::FindOption::Cxx11RegEx;
959 	return opt;
960 }
961 
MarkAll(MarkPurpose purpose)962 void SciTEBase::MarkAll(MarkPurpose purpose) {
963 	RemoveFindMarks();
964 	wEditor.SetIndicatorCurrent(indicatorMatch);
965 	if (purpose == markIncremental) {
966 		CurrentBuffer()->findMarks = Buffer::fmTemporary;
967 		SetOneIndicator(wEditor, indicatorMatch,
968 				IndicatorDefinition(props.GetString("find.indicator.incremental")));
969 	} else {
970 		CurrentBuffer()->findMarks = Buffer::fmMarked;
971 		std::string findIndicatorString = props.GetString("find.mark.indicator");
972 		IndicatorDefinition findIndicator(findIndicatorString);
973 		if (!findIndicatorString.length()) {
974 			findIndicator.style = SA::IndicatorStyle::RoundBox;
975 			std::string findMark = props.GetString("find.mark");
976 			if (findMark.length())
977 				findIndicator.colour = ColourFromString(findMark);
978 			findIndicator.fillAlpha = alphaIndicator;
979 			findIndicator.under = underIndicator;
980 		}
981 		SetOneIndicator(wEditor, indicatorMatch, findIndicator);
982 	}
983 
984 	const std::string findTarget = UnSlashAsNeeded(EncodeString(findWhat), unSlash, regExp);
985 	if (findTarget.length() == 0) {
986 		return;
987 	}
988 
989 	findMarker.StartMatch(&wEditor, findTarget,
990 			      SearchFlags(regExp), -1,
991 			      indicatorMatch, (purpose == markWithBookMarks) ? markerBookmark : -1);
992 	SetIdler(true);
993 }
994 
IncrementSearchMode()995 int SciTEBase::IncrementSearchMode() {
996 	FindIncrement();
997 	return 0;
998 }
999 
FailedSaveMessageBox(const FilePath & filePathSaving)1000 void SciTEBase::FailedSaveMessageBox(const FilePath &filePathSaving) {
1001 	const GUI::gui_string msg = LocaliseMessage(
1002 					    "Could not save file \"^0\".", filePathSaving.AsInternal());
1003 	WindowMessageBox(wSciTE, msg);
1004 }
1005 
FindReplaceAdvanced() const1006 bool SciTEBase::FindReplaceAdvanced() const {
1007 	return props.GetInt("find.replace.advanced");
1008 }
1009 
FindInTarget(const std::string & findWhatText,SA::Range range)1010 SA::Position SciTEBase::FindInTarget(const std::string &findWhatText, SA::Range range) {
1011 	wEditor.SetTarget(range);
1012 	SA::Position posFind = wEditor.SearchInTarget(findWhatText);
1013 	while (findInStyle && (posFind >= 0) && (findStyle != wEditor.UnsignedStyleAt(posFind))) {
1014 		if (range.start < range.end) {
1015 			wEditor.SetTarget(SA::Range(posFind + 1, range.end));
1016 		} else {
1017 			wEditor.SetTarget(SA::Range(range.start, posFind + 1));
1018 		}
1019 		posFind = wEditor.SearchInTarget(findWhatText);
1020 	}
1021 	return posFind;
1022 }
1023 
SetFindText(const char * sFind)1024 void SciTEBase::SetFindText(const char *sFind) {
1025 	findWhat = sFind;
1026 	props.Set("find.what", findWhat.c_str());
1027 }
1028 
SetFind(const char * sFind)1029 void SciTEBase::SetFind(const char *sFind) {
1030 	SetFindText(sFind);
1031 	InsertFindInMemory();
1032 }
1033 
FindHasText() const1034 bool SciTEBase::FindHasText() const noexcept {
1035 	return !findWhat.empty();
1036 }
1037 
SetReplace(const char * sReplace)1038 void SciTEBase::SetReplace(const char *sReplace) {
1039 	replaceWhat = sReplace;
1040 	memReplaces.Insert(replaceWhat.c_str());
1041 }
1042 
SetCaretAsStart()1043 void SciTEBase::SetCaretAsStart() {
1044 	searchStartPosition = wEditor.SelectionStart();
1045 }
1046 
MoveBack()1047 void SciTEBase::MoveBack() {
1048 	SetSelection(searchStartPosition, searchStartPosition);
1049 }
1050 
ScrollEditorIfNeeded()1051 void SciTEBase::ScrollEditorIfNeeded() {
1052 	GUI::Point ptCaret;
1053 	const SA::Position caret = wEditor.CurrentPos();
1054 	ptCaret.x = wEditor.PointXFromPosition(caret);
1055 	ptCaret.y = wEditor.PointYFromPosition(caret);
1056 	ptCaret.y += wEditor.TextHeight(0) - 1;
1057 
1058 	const GUI::Rectangle rcEditor = wEditor.GetClientPosition();
1059 	if (!rcEditor.Contains(ptCaret))
1060 		wEditor.ScrollCaret();
1061 }
1062 
FindNext(bool reverseDirection,bool showWarnings,bool allowRegExp)1063 SA::Position SciTEBase::FindNext(bool reverseDirection, bool showWarnings, bool allowRegExp) {
1064 	if (findWhat.length() == 0) {
1065 		Find();
1066 		return -1;
1067 	}
1068 	const std::string findTarget = UnSlashAsNeeded(EncodeString(findWhat), unSlash, regExp);
1069 	if (findTarget.length() == 0)
1070 		return -1;
1071 
1072 	const SA::Position lengthDoc = wEditor.Length();
1073 	const SA::Range rangeSelection = wEditor.SelectionRange();
1074 	SA::Range rangeSearch(rangeSelection.end, lengthDoc);
1075 	if (reverseDirection) {
1076 		rangeSearch = SA::Range(rangeSelection.start, 0);
1077 	}
1078 
1079 	wEditor.SetSearchFlags(SearchFlags(allowRegExp && regExp));
1080 	SA::Position posFind = FindInTarget(findTarget, rangeSearch);
1081 	if (posFind == -1 && wrapFind) {
1082 		// Failed to find in indicated direction
1083 		// so search from the beginning (forward) or from the end (reverse)
1084 		// unless wrapFind is false
1085 		const SA::Range rangeAll = reverseDirection ?
1086 					   SA::Range(lengthDoc, 0) : SA::Range(0, lengthDoc);
1087 		posFind = FindInTarget(findTarget, rangeAll);
1088 		WarnUser(warnFindWrapped);
1089 	}
1090 	if (posFind < 0) {
1091 		havefound = false;
1092 		failedfind = true;
1093 		if (showWarnings) {
1094 			WarnUser(warnNotFound);
1095 			FindMessageBox("Can not find the string '^0'.",
1096 				       &findWhat);
1097 		}
1098 	} else {
1099 		havefound = true;
1100 		failedfind = false;
1101 		const SA::Range rangeTarget = wEditor.TargetRange();
1102 		// Ensure found text is styled so that caret will be made visible but
1103 		// only perform style in synchronous styling mode.
1104 		const SA::Position endStyled = wEditor.EndStyled();
1105 		if ((endStyled < rangeTarget.end) && (idleStyling == SA::IdleStyling::None)) {
1106 			wEditor.Colourise(endStyled,
1107 					  wEditor.LineStart(wEditor.LineFromPosition(rangeTarget.end) + 1));
1108 		}
1109 		EnsureRangeVisible(wEditor, rangeTarget);
1110 		wEditor.ScrollRange(rangeTarget.start, rangeTarget.end);
1111 		SetSelection(rangeTarget.start, rangeTarget.end);
1112 		if (!replacing && (closeFind != CloseFind::closePrevent)) {
1113 			DestroyFindReplace();
1114 		}
1115 	}
1116 	return posFind;
1117 }
1118 
HideMatch()1119 void SciTEBase::HideMatch() {
1120 }
1121 
ReplaceOnce(bool showWarnings)1122 void SciTEBase::ReplaceOnce(bool showWarnings) {
1123 	if (!FindHasText())
1124 		return;
1125 
1126 	bool haveWarned = false;
1127 	if (!havefound) {
1128 		const SA::Range rangeSelection = wEditor.SelectionRange();
1129 		SetSelection(rangeSelection.start, rangeSelection.start);
1130 		FindNext(false);
1131 		haveWarned = !havefound;
1132 	}
1133 
1134 	if (havefound) {
1135 		const std::string replaceTarget = UnSlashAsNeeded(EncodeString(replaceWhat), unSlash, regExp);
1136 		const SA::Range rangeSelection = wEditor.SelectionRange();
1137 		wEditor.SetTarget(rangeSelection);
1138 		SA::Position lenReplaced = replaceTarget.length();
1139 		if (regExp)
1140 			lenReplaced = wEditor.ReplaceTargetRE(replaceTarget);
1141 		else	// Allow \0 in replacement
1142 			wEditor.ReplaceTarget(replaceTarget);
1143 		SetSelection(rangeSelection.start + lenReplaced, rangeSelection.start);
1144 		havefound = false;
1145 	}
1146 
1147 	FindNext(false, showWarnings && !haveWarned);
1148 }
1149 
DoReplaceAll(bool inSelection)1150 intptr_t SciTEBase::DoReplaceAll(bool inSelection) {
1151 	const std::string findTarget = UnSlashAsNeeded(EncodeString(findWhat), unSlash, regExp);
1152 	if (findTarget.length() == 0) {
1153 		return -1;
1154 	}
1155 
1156 	const SA::Range rangeSelection = wEditor.SelectionRange();
1157 	SA::Range rangeSearch = rangeSelection;
1158 	const int countSelections = wEditor.Selections();
1159 	if (inSelection) {
1160 		const SA::SelectionMode selType = wEditor.SelectionMode();
1161 		if (selType == SA::SelectionMode::Lines) {
1162 			// Take care to replace in whole lines
1163 			const SA::Line startLine = wEditor.LineFromPosition(rangeSearch.start);
1164 			rangeSearch.start = wEditor.LineStart(startLine);
1165 			const SA::Line endLine = wEditor.LineFromPosition(rangeSearch.end);
1166 			rangeSearch.end = wEditor.LineStart(endLine + 1);
1167 		} else {
1168 			for (int i=0; i<countSelections; i++) {
1169 				rangeSearch.start = std::min(rangeSearch.start, wEditor.SelectionNStart(i));
1170 				rangeSearch.end = std::max(rangeSearch.end, wEditor.SelectionNEnd(i));
1171 			}
1172 		}
1173 		if (rangeSearch.Length() == 0) {
1174 			return -2;
1175 		}
1176 	} else {
1177 		rangeSearch.end = LengthDocument();
1178 		if (wrapFind) {
1179 			// Whole document
1180 			rangeSearch.start = 0;
1181 		}
1182 		// If not wrapFind, replace all only from caret to end of document
1183 	}
1184 
1185 	const std::string replaceTarget = UnSlashAsNeeded(EncodeString(replaceWhat), unSlash, regExp);
1186 	wEditor.SetSearchFlags(SearchFlags(regExp));
1187 	SA::Position posFind = FindInTarget(findTarget, rangeSearch);
1188 	if ((posFind >= 0) && (posFind <= rangeSearch.end)) {
1189 		SA::Position lastMatch = posFind;
1190 		intptr_t replacements = 0;
1191 		wEditor.BeginUndoAction();
1192 		// Replacement loop
1193 		while (posFind >= 0) {
1194 			const SA::Position lenTarget = wEditor.TargetEnd() - wEditor.TargetStart();
1195 			if (inSelection && countSelections > 1) {
1196 				// We must check that the found target is entirely inside a selection
1197 				bool insideASelection = false;
1198 				for (int i=0; i<countSelections && !insideASelection; i++) {
1199 					const SA::Position startPos = wEditor.SelectionNStart(i);
1200 					const SA::Position endPos = wEditor.SelectionNEnd(i);
1201 					if (posFind >= startPos && posFind + lenTarget <= endPos)
1202 						insideASelection = true;
1203 				}
1204 				if (!insideASelection) {
1205 					// Found target is totally or partly outside the selections
1206 					lastMatch = posFind + 1;
1207 					if (lastMatch >= rangeSearch.end) {
1208 						// Run off the end of the document/selection with an empty match
1209 						posFind = -1;
1210 					} else {
1211 						posFind = FindInTarget(findTarget, SA::Range(lastMatch, rangeSearch.end));
1212 					}
1213 					continue;	// No replacement
1214 				}
1215 			}
1216 			SA::Position lenReplaced = replaceTarget.length();
1217 			if (regExp) {
1218 				lenReplaced = wEditor.ReplaceTargetRE(replaceTarget);
1219 			} else {
1220 				wEditor.ReplaceTarget(replaceTarget);
1221 			}
1222 			// Modify for change caused by replacement
1223 			rangeSearch.end += lenReplaced - lenTarget;
1224 			// For the special cases of start of line and end of line
1225 			// something better could be done but there are too many special cases
1226 			lastMatch = posFind + lenReplaced;
1227 			if (lenTarget <= 0) {
1228 				lastMatch = wEditor.PositionAfter(lastMatch);
1229 			}
1230 			if (lastMatch >= rangeSearch.end) {
1231 				// Run off the end of the document/selection with an empty match
1232 				posFind = -1;
1233 			} else {
1234 				posFind = FindInTarget(findTarget, SA::Range(lastMatch, rangeSearch.end));
1235 			}
1236 			replacements++;
1237 		}
1238 		if (inSelection) {
1239 			if (countSelections == 1)
1240 				SetSelection(rangeSearch.start, rangeSearch.end);
1241 		} else {
1242 			SetSelection(lastMatch, lastMatch);
1243 		}
1244 		wEditor.EndUndoAction();
1245 		return replacements;
1246 	}
1247 	return 0;
1248 }
1249 
ReplaceAll(bool inSelection)1250 intptr_t SciTEBase::ReplaceAll(bool inSelection) {
1251 	const intptr_t replacements = DoReplaceAll(inSelection);
1252 	props.Set("Replacements", std::to_string(replacements > 0 ? replacements : 0));
1253 	UpdateStatusBar(false);
1254 	if (replacements == -1) {
1255 		FindMessageBox(
1256 			inSelection ?
1257 			"Find string must not be empty for 'Replace in Selection' command." :
1258 			"Find string must not be empty for 'Replace All' command.");
1259 	} else if (replacements == -2) {
1260 		FindMessageBox(
1261 			"Selection must not be empty for 'Replace in Selection' command.");
1262 	} else if (replacements == 0) {
1263 		FindMessageBox(
1264 			"No replacements because string '^0' was not present.", &findWhat);
1265 	}
1266 	return replacements;
1267 }
1268 
ReplaceInBuffers()1269 intptr_t SciTEBase::ReplaceInBuffers() {
1270 	const int currentBuffer = buffers.Current();
1271 	intptr_t replacements = 0;
1272 	for (int i = 0; i < buffers.length; i++) {
1273 		SetDocumentAt(i);
1274 		replacements += DoReplaceAll(false);
1275 		if (i == 0 && replacements < 0) {
1276 			FindMessageBox(
1277 				"Find string must not be empty for 'Replace in Buffers' command.");
1278 			break;
1279 		}
1280 	}
1281 	SetDocumentAt(currentBuffer);
1282 	props.Set("Replacements", std::to_string(replacements));
1283 	UpdateStatusBar(false);
1284 	if (replacements == 0) {
1285 		FindMessageBox(
1286 			"No replacements because string '^0' was not present.", &findWhat);
1287 	}
1288 	return replacements;
1289 }
1290 
UIClosed()1291 void SciTEBase::UIClosed() {
1292 	if (CurrentBuffer()->findMarks == Buffer::fmTemporary) {
1293 		RemoveFindMarks();
1294 	}
1295 }
1296 
UIHasFocus()1297 void SciTEBase::UIHasFocus() {
1298 }
1299 
OutputAppendString(const char * s,SA::Position len)1300 void SciTEBase::OutputAppendString(const char *s, SA::Position len) {
1301 	if (len == -1)
1302 		len = strlen(s);
1303 	wOutput.AppendText(len, s);
1304 	if (scrollOutput) {
1305 		const SA::Line line = wOutput.LineCount();
1306 		const SA::Position lineStart = wOutput.LineStart(line);
1307 		wOutput.GotoPos(lineStart);
1308 	}
1309 }
1310 
OutputAppendStringSynchronised(const char * s,SA::Position len)1311 void SciTEBase::OutputAppendStringSynchronised(const char *s, SA::Position len) {
1312 	// This may be called from secondary thread so always use Send instead of Call
1313 	if (len == -1)
1314 		len = strlen(s);
1315 	wOutput.Send(SCI_APPENDTEXT, len, SptrFromString(s));
1316 	if (scrollOutput) {
1317 		const SA::Line line = wOutput.Send(SCI_GETLINECOUNT);
1318 		const SA::Position lineStart = wOutput.Send(SCI_POSITIONFROMLINE, line);
1319 		wOutput.Send(SCI_GOTOPOS, lineStart);
1320 	}
1321 }
1322 
Execute()1323 void SciTEBase::Execute() {
1324 	props.Set("CurrentMessage", "");
1325 	dirNameForExecute = FilePath();
1326 	bool displayParameterDialog = false;
1327 	parameterisedCommand = "";
1328 	for (size_t ic = 0; ic < jobQueue.commandMax; ic++) {
1329 		if (StartsWith(jobQueue.jobQueue[ic].command, "*")) {
1330 			displayParameterDialog = true;
1331 			jobQueue.jobQueue[ic].command.erase(0, 1);
1332 			parameterisedCommand = jobQueue.jobQueue[ic].command;
1333 		}
1334 		if (jobQueue.jobQueue[ic].directory.IsSet()) {
1335 			dirNameForExecute = jobQueue.jobQueue[ic].directory;
1336 		}
1337 	}
1338 	if (displayParameterDialog) {
1339 		if (!ParametersDialog(true)) {
1340 			jobQueue.ClearJobs();
1341 			return;
1342 		}
1343 	} else {
1344 		ParamGrab();
1345 	}
1346 	for (size_t ic = 0; ic < jobQueue.commandMax; ic++) {
1347 		if (jobQueue.jobQueue[ic].jobType != jobGrep) {
1348 			jobQueue.jobQueue[ic].command = props.Expand(jobQueue.jobQueue[ic].command);
1349 		}
1350 	}
1351 
1352 	if (jobQueue.ClearBeforeExecute()) {
1353 		wOutput.ClearAll();
1354 	}
1355 
1356 	wOutput.MarkerDeleteAll(-1);
1357 	wEditor.MarkerDeleteAll(0);
1358 	// Ensure the output pane is visible
1359 	if (jobQueue.ShowOutputPane()) {
1360 		SetOutputVisibility(true);
1361 	}
1362 
1363 	jobQueue.SetCancelFlag(false);
1364 	if (jobQueue.HasCommandToRun()) {
1365 		jobQueue.SetExecuting(true);
1366 	}
1367 	CheckMenus();
1368 	dirNameAtExecute = filePath.Directory();
1369 }
1370 
SetOutputVisibility(bool show)1371 void SciTEBase::SetOutputVisibility(bool show) {
1372 	if (show) {
1373 		if (heightOutput <= 0) {
1374 			if (previousHeightOutput < 20) {
1375 				if (splitVertical)
1376 					heightOutput = NormaliseSplit(300);
1377 				else
1378 					heightOutput = NormaliseSplit(100);
1379 				previousHeightOutput = heightOutput;
1380 			} else {
1381 				heightOutput = NormaliseSplit(previousHeightOutput);
1382 			}
1383 		}
1384 	} else {
1385 		if (heightOutput > 0) {
1386 			heightOutput = NormaliseSplit(0);
1387 			WindowSetFocus(wEditor);
1388 		}
1389 	}
1390 	SizeSubWindows();
1391 	Redraw();
1392 }
1393 
1394 // Background threads that are send text to the output pane want it to be made visible.
1395 // Derived methods for each platform may perform thread synchronization.
ShowOutputOnMainThread()1396 void SciTEBase::ShowOutputOnMainThread() {
1397 	SetOutputVisibility(true);
1398 }
1399 
ToggleOutputVisible()1400 void SciTEBase::ToggleOutputVisible() {
1401 	SetOutputVisibility(heightOutput <= 0);
1402 }
1403 
BookmarkAdd(SA::Line lineno)1404 void SciTEBase::BookmarkAdd(SA::Line lineno) {
1405 	if (lineno == -1)
1406 		lineno = GetCurrentLineNumber();
1407 	if (!BookmarkPresent(lineno))
1408 		wEditor.MarkerAdd(lineno, markerBookmark);
1409 }
1410 
BookmarkDelete(SA::Line lineno)1411 void SciTEBase::BookmarkDelete(SA::Line lineno) {
1412 	if (lineno == -1)
1413 		lineno = GetCurrentLineNumber();
1414 	if (BookmarkPresent(lineno))
1415 		wEditor.MarkerDelete(lineno, markerBookmark);
1416 }
1417 
BookmarkPresent(SA::Line lineno)1418 bool SciTEBase::BookmarkPresent(SA::Line lineno) {
1419 	if (lineno == -1)
1420 		lineno = GetCurrentLineNumber();
1421 	const int state = wEditor.MarkerGet(lineno);
1422 	return state & (1 << markerBookmark);
1423 }
1424 
BookmarkToggle(SA::Line lineno)1425 void SciTEBase::BookmarkToggle(SA::Line lineno) {
1426 	if (lineno == -1)
1427 		lineno = GetCurrentLineNumber();
1428 	if (BookmarkPresent(lineno)) {
1429 		while (BookmarkPresent(lineno)) {
1430 			BookmarkDelete(lineno);
1431 		}
1432 	} else {
1433 		BookmarkAdd(lineno);
1434 	}
1435 }
1436 
BookmarkNext(bool forwardScan,bool select)1437 void SciTEBase::BookmarkNext(bool forwardScan, bool select) {
1438 	const SA::Line lineno = GetCurrentLineNumber();
1439 	SA::Message sciMarker = SA::Message::MarkerNext;
1440 	SA::Line lineStart = lineno + 1;	//Scan starting from next line
1441 	SA::Line lineRetry = 0;				//If not found, try from the beginning
1442 	const SA::Position anchor = wEditor.Anchor();
1443 	if (!forwardScan) {
1444 		lineStart = lineno - 1;		//Scan starting from previous line
1445 		lineRetry = wEditor.LineCount();	//If not found, try from the end
1446 		sciMarker = SA::Message::MarkerPrevious;
1447 	}
1448 	SA::Line nextLine = wEditor.Call(sciMarker, lineStart, 1 << markerBookmark);
1449 	if (nextLine < 0)
1450 		nextLine = wEditor.Call(sciMarker, lineRetry, 1 << markerBookmark);
1451 	if (nextLine < 0 || nextLine == lineno)	// No bookmark (of the given type) or only one, and already on it
1452 		WarnUser(warnNoOtherBookmark);
1453 	else {
1454 		GotoLineEnsureVisible(nextLine);
1455 		if (select) {
1456 			wEditor.SetAnchor(anchor);
1457 		}
1458 	}
1459 }
1460 
BookmarkSelectAll()1461 void SciTEBase::BookmarkSelectAll() {
1462 	std::vector<SA::Line> bookmarks;
1463 	SA::Line lineBookmark = -1;
1464 	while ((lineBookmark = wEditor.MarkerNext(lineBookmark + 1, 1 << markerBookmark)) >= 0) {
1465 		bookmarks.push_back(lineBookmark);
1466 	}
1467 	for (size_t i = 0; i < bookmarks.size(); i++) {
1468 		const SA::Range range = {
1469 			wEditor.LineStart(bookmarks[i]),
1470 			wEditor.LineStart(bookmarks[i] + 1)
1471 		};
1472 		if (i == 0) {
1473 			wEditor.SetSelection(range.end, range.start);
1474 		} else {
1475 			wEditor.AddSelection(range.end, range.start);
1476 		}
1477 	}
1478 }
1479 
GetClientRectangle()1480 GUI::Rectangle SciTEBase::GetClientRectangle() {
1481 	return wContent.GetClientPosition();
1482 }
1483 
Redraw()1484 void SciTEBase::Redraw() {
1485 	wSciTE.InvalidateAll();
1486 	wEditor.InvalidateAll();
1487 	wOutput.InvalidateAll();
1488 }
1489 
GetNearestWords(const char * wordStart,size_t searchLen,const char * separators,bool ignoreCase,bool exactLen)1490 std::string SciTEBase::GetNearestWords(const char *wordStart, size_t searchLen,
1491 				       const char *separators, bool ignoreCase /*=false*/, bool exactLen /*=false*/) {
1492 	std::string words;
1493 	while (words.empty() && *separators) {
1494 		words = apis.GetNearestWords(wordStart, searchLen, ignoreCase, *separators, exactLen);
1495 		separators++;
1496 	}
1497 	return words;
1498 }
1499 
FillFunctionDefinition(SA::Position pos)1500 void SciTEBase::FillFunctionDefinition(SA::Position pos /*= -1*/) {
1501 	if (pos > 0) {
1502 		lastPosCallTip = pos;
1503 	}
1504 	if (apis) {
1505 		std::string words = GetNearestWords(currentCallTipWord.c_str(), currentCallTipWord.length(),
1506 						    calltipParametersStart.c_str(), callTipIgnoreCase, true);
1507 		if (words.empty())
1508 			return;
1509 		// Counts how many call tips
1510 		maxCallTips = static_cast<int>(std::count(words.begin(), words.end(), ' ') + 1);
1511 
1512 		// Should get current api definition
1513 		std::string word = apis.GetNearestWord(currentCallTipWord.c_str(), currentCallTipWord.length(),
1514 						       callTipIgnoreCase, calltipWordCharacters, currentCallTip);
1515 		if (word.length()) {
1516 			functionDefinition = word;
1517 			if (maxCallTips > 1) {
1518 				functionDefinition.insert(0, "\001");
1519 			}
1520 
1521 			if (calltipEndDefinition != "") {
1522 				const size_t posEndDef = functionDefinition.find(calltipEndDefinition.c_str());
1523 				if (maxCallTips > 1) {
1524 					if (posEndDef != std::string::npos) {
1525 						functionDefinition.insert(posEndDef + calltipEndDefinition.length(), "\n\002");
1526 					} else {
1527 						functionDefinition.append("\n\002");
1528 					}
1529 				} else {
1530 					if (posEndDef != std::string::npos) {
1531 						functionDefinition.insert(posEndDef + calltipEndDefinition.length(), "\n");
1532 					}
1533 				}
1534 			} else if (maxCallTips > 1) {
1535 				functionDefinition.insert(1, "\002");
1536 			}
1537 
1538 			std::string definitionForDisplay;
1539 			if (callTipUseEscapes) {
1540 				definitionForDisplay = UnSlashString(functionDefinition.c_str());
1541 			} else {
1542 				definitionForDisplay = functionDefinition;
1543 			}
1544 
1545 			wEditor.CallTipShow(lastPosCallTip - currentCallTipWord.length(), definitionForDisplay.c_str());
1546 			ContinueCallTip();
1547 		}
1548 	}
1549 }
1550 
StartCallTip()1551 bool SciTEBase::StartCallTip() {
1552 	currentCallTip = 0;
1553 	currentCallTipWord = "";
1554 	std::string line = GetCurrentLine();
1555 	SA::Position current = GetCaretInLine();
1556 	SA::Position pos = wEditor.CurrentPos();
1557 	do {
1558 		int braces = 0;
1559 		while (current > 0 && (braces || !Contains(calltipParametersStart, line[current - 1]))) {
1560 			if (Contains(calltipParametersStart, line[current - 1]))
1561 				braces--;
1562 			else if (Contains(calltipParametersEnd, line[current - 1]))
1563 				braces++;
1564 			current--;
1565 			pos--;
1566 		}
1567 		if (current > 0) {
1568 			current--;
1569 			pos--;
1570 		} else
1571 			break;
1572 		while (current > 0 && IsASpace(line[current - 1])) {
1573 			current--;
1574 			pos--;
1575 		}
1576 	} while (current > 0 && !Contains(calltipWordCharacters, line[current - 1]));
1577 	if (current <= 0)
1578 		return true;
1579 
1580 	startCalltipWord = current - 1;
1581 	while (startCalltipWord > 0 &&
1582 			Contains(calltipWordCharacters, line[startCalltipWord - 1])) {
1583 		startCalltipWord--;
1584 	}
1585 
1586 	line.at(current) = '\0';
1587 	currentCallTipWord = line.c_str() + startCalltipWord;
1588 	functionDefinition = "";
1589 	FillFunctionDefinition(pos);
1590 	return true;
1591 }
1592 
ContinueCallTip()1593 void SciTEBase::ContinueCallTip() {
1594 	std::string line = GetCurrentLine();
1595 	const SA::Position current = GetCaretInLine();
1596 
1597 	int braces = 0;
1598 	int commas = 0;
1599 	for (SA::Position i = startCalltipWord; i < current; i++) {
1600 		if (Contains(calltipParametersStart, line[i]))
1601 			braces++;
1602 		else if (Contains(calltipParametersEnd, line[i]) && braces > 0)
1603 			braces--;
1604 		else if (braces == 1 && Contains(calltipParametersSeparators, line[i]))
1605 			commas++;
1606 	}
1607 
1608 	size_t startHighlight = 0;
1609 	while ((startHighlight < functionDefinition.length()) && !Contains(calltipParametersStart, functionDefinition[startHighlight]))
1610 		startHighlight++;
1611 	if ((startHighlight < functionDefinition.length()) && Contains(calltipParametersStart, functionDefinition[startHighlight]))
1612 		startHighlight++;
1613 	while ((startHighlight < functionDefinition.length()) && commas > 0) {
1614 		if (Contains(calltipParametersSeparators, functionDefinition[startHighlight]))
1615 			commas--;
1616 		// If it reached the end of the argument list it means that the user typed in more
1617 		// arguments than the ones listed in the calltip
1618 		if (Contains(calltipParametersEnd, functionDefinition[startHighlight]))
1619 			commas = 0;
1620 		else
1621 			startHighlight++;
1622 	}
1623 	if ((startHighlight < functionDefinition.length()) && Contains(calltipParametersSeparators, functionDefinition[startHighlight]))
1624 		startHighlight++;
1625 	size_t endHighlight = startHighlight;
1626 	while ((endHighlight < functionDefinition.length()) && !Contains(calltipParametersSeparators, functionDefinition[endHighlight]) && !Contains(calltipParametersEnd, functionDefinition[endHighlight]))
1627 		endHighlight++;
1628 	if (callTipUseEscapes) {
1629 		std::string sPreHighlight = functionDefinition.substr(0, startHighlight);
1630 		std::vector<char> vPreHighlight(sPreHighlight.c_str(), sPreHighlight.c_str() + sPreHighlight.length() + 1);
1631 		const int unslashedStartHighlight = UnSlash(&vPreHighlight[0]);
1632 
1633 		int unslashedEndHighlight = unslashedStartHighlight;
1634 		if (startHighlight < endHighlight) {
1635 			std::string sHighlight = functionDefinition.substr(startHighlight, endHighlight - startHighlight);
1636 			std::vector<char> vHighlight(sHighlight.c_str(), sHighlight.c_str() + sHighlight.length() + 1);
1637 			unslashedEndHighlight = unslashedStartHighlight + UnSlash(&vHighlight[0]);
1638 		}
1639 
1640 		startHighlight = unslashedStartHighlight;
1641 		endHighlight = unslashedEndHighlight;
1642 	}
1643 
1644 	wEditor.CallTipSetHlt(startHighlight, endHighlight);
1645 }
1646 
EliminateDuplicateWords(const std::string & words)1647 std::string SciTEBase::EliminateDuplicateWords(const std::string &words) {
1648 	std::set<std::string> wordSet;
1649 	std::string wordsOut;
1650 
1651 	size_t current = 0;
1652 	while (current < words.length()) {
1653 		const size_t afterWord = words.find(' ', current);
1654 		const std::string word(words, current, afterWord - current);
1655 		std::pair<std::set<std::string>::iterator, bool> result = wordSet.insert(word);
1656 		if (result.second) {
1657 			if (!wordsOut.empty())
1658 				wordsOut += ' ';
1659 			wordsOut += word;
1660 		}
1661 		current = afterWord;
1662 		if (current < words.length())
1663 			current++;
1664 	}
1665 	return wordsOut;
1666 }
1667 
StartAutoComplete()1668 bool SciTEBase::StartAutoComplete() {
1669 	const std::string line = GetCurrentLine();
1670 	const SA::Position current = GetCaretInLine();
1671 
1672 	SA::Position startword = current;
1673 
1674 	while ((startword > 0) &&
1675 			(Contains(calltipWordCharacters, line[startword - 1]) ||
1676 			 Contains(autoCompleteStartCharacters, line[startword - 1]))) {
1677 		startword--;
1678 	}
1679 
1680 	const std::string root = line.substr(startword, current - startword);
1681 	if (apis) {
1682 		const std::string words = GetNearestWords(root.c_str(), root.length(),
1683 						    calltipParametersStart.c_str(), autoCompleteIgnoreCase);
1684 		if (!words.empty()) {
1685 			std::string wordsUnique = EliminateDuplicateWords(words);
1686 			wEditor.AutoCSetSeparator(' ');
1687 			wEditor.AutoCShow(root.length(), wordsUnique.c_str());
1688 		}
1689 	}
1690 	return true;
1691 }
1692 
StartAutoCompleteWord(bool onlyOneWord)1693 bool SciTEBase::StartAutoCompleteWord(bool onlyOneWord) {
1694 	const std::string line = GetCurrentLine();
1695 	const SA::Position current = GetCaretInLine();
1696 
1697 	SA::Position startword = current;
1698 	// Autocompletion of pure numbers is mostly an annoyance
1699 	bool allNumber = true;
1700 	while (startword > 0 && Contains(wordCharacters, line[startword - 1])) {
1701 		startword--;
1702 		if (line[startword] < '0' || line[startword] > '9') {
1703 			allNumber = false;
1704 		}
1705 	}
1706 	if (startword == current || allNumber)
1707 		return true;
1708 	const std::string root = line.substr(startword, current - startword);
1709 	const SA::Position rootLength = root.length();
1710 	const SA::Position doclen = LengthDocument();
1711 	const SA::FindOption flags =
1712 		SA::FindOption::WordStart | (autoCompleteIgnoreCase ? SA::FindOption::None : SA::FindOption::MatchCase);
1713 	const SA::Position posCurrentWord = wEditor.CurrentPos() - rootLength;
1714 	SA::Position minWordLength = 0;
1715 	unsigned int nwords = 0;
1716 
1717 	// wordsNear contains a list of words separated by single spaces and with a space
1718 	// at the start and end. This makes it easy to search for words.
1719 	std::string wordsNear;
1720 	wordsNear.append("\n");
1721 
1722 	wEditor.SetTarget(SA::Range(0, doclen));
1723 	wEditor.SetSearchFlags(flags);
1724 	SA::Position posFind = wEditor.SearchInTarget(root);
1725 	TextReader acc(wEditor);
1726 	while (posFind >= 0 && posFind < doclen) {	// search all the document
1727 		SA::Position wordEnd = posFind + rootLength;
1728 		if (posFind != posCurrentWord) {
1729 			while (Contains(wordCharacters, acc.SafeGetCharAt(wordEnd)))
1730 				wordEnd++;
1731 			const SA::Position wordLength = wordEnd - posFind;
1732 			if (wordLength > rootLength) {
1733 				std::string word = wEditor.StringOfRange(SA::Range(posFind, wordEnd));
1734 				word.insert(0, "\n");
1735 				word.append("\n");
1736 				if (wordsNear.find(word.c_str()) == std::string::npos) {	// add a new entry
1737 					wordsNear += word.c_str() + 1;
1738 					if (minWordLength < wordLength)
1739 						minWordLength = wordLength;
1740 
1741 					nwords++;
1742 					if (onlyOneWord && nwords > 1) {
1743 						return true;
1744 					}
1745 				}
1746 			}
1747 		}
1748 		wEditor.SetTarget(SA::Range(wordEnd, doclen));
1749 		posFind = wEditor.SearchInTarget(root);
1750 	}
1751 	const size_t length = wordsNear.length();
1752 	if ((length > 2) && (!onlyOneWord || (minWordLength > rootLength))) {
1753 		// Protect spaces by temporarily transforming to \001
1754 		std::replace(wordsNear.begin(), wordsNear.end(), ' ', '\001');
1755 		StringList wl(true);
1756 		wl.Set(wordsNear.c_str());
1757 		std::string acText = wl.GetNearestWords("", 0, autoCompleteIgnoreCase);
1758 		// Use \n as word separator
1759 		std::replace(acText.begin(), acText.end(), ' ', '\n');
1760 		// Return spaces from \001
1761 		std::replace(acText.begin(), acText.end(), '\001', ' ');
1762 		wEditor.AutoCSetSeparator('\n');
1763 		wEditor.AutoCShow(rootLength, acText.c_str());
1764 	} else {
1765 		wEditor.AutoCCancel();
1766 	}
1767 	return true;
1768 }
1769 
PerformInsertAbbreviation()1770 bool SciTEBase::PerformInsertAbbreviation() {
1771 	const std::string data = propsAbbrev.GetString(abbrevInsert.c_str());
1772 	if (data.empty()) {
1773 		return true; // returning if expanded abbreviation is empty
1774 	}
1775 
1776 	const std::string expbuf = UnSlashString(data.c_str());
1777 	const size_t expbuflen = expbuf.length();
1778 
1779 	SA::Position caretPos = wEditor.SelectionStart();
1780 	SA::Position selStart = caretPos;
1781 	SA::Position selLength = wEditor.SelectionEnd() - selStart;
1782 	bool atStart = true;
1783 	bool doublePipe = false;
1784 	size_t lastPipe = expbuflen;
1785 	SA::Line currentLineNumber = wEditor.LineFromPosition(caretPos);
1786 	int indent = 0;
1787 	const int indentSize = wEditor.Indent();
1788 	const int indentChars = (wEditor.UseTabs() && wEditor.TabWidth() ? wEditor.TabWidth() : 1);
1789 	int indentExtra = 0;
1790 	bool isIndent = true;
1791 	const SA::EndOfLine eolMode = wEditor.EOLMode();
1792 	if (props.GetInt("indent.automatic")) {
1793 		indent = GetLineIndentation(currentLineNumber);
1794 	}
1795 
1796 	// find last |, can't be strrchr(exbuf, '|') because of ||
1797 	for (size_t i = expbuflen; i--;) {
1798 		if (expbuf[i] == '|' && (i == 0 || expbuf[i-1] != '|')) {
1799 			lastPipe = i;
1800 			break;
1801 		}
1802 	}
1803 
1804 	wEditor.BeginUndoAction();
1805 
1806 	// add the abbreviation one character at a time
1807 	for (size_t i = 0; i < expbuflen; i++) {
1808 		const char c = expbuf[i];
1809 		std::string abbrevText("");
1810 		if (isIndent && c == '\t') {
1811 			if (props.GetInt("indent.automatic")) {
1812 				indentExtra++;
1813 				SetLineIndentation(currentLineNumber, indent + indentSize * indentExtra);
1814 				caretPos += indentSize / indentChars;
1815 			}
1816 		} else {
1817 			switch (c) {
1818 			case '|':
1819 				// user may want to insert '|' instead of caret
1820 				if (i < (expbuflen - 1) && expbuf[i + 1] == '|') {
1821 					// put '|' into the line
1822 					abbrevText += c;
1823 					i++;
1824 				} else if (i != lastPipe) {
1825 					doublePipe = true;
1826 				} else {
1827 					// indent on multiple lines
1828 					SA::Line j = currentLineNumber + 1; // first line indented as others
1829 					currentLineNumber = wEditor.LineFromPosition(caretPos + selLength);
1830 					for (; j <= currentLineNumber; j++) {
1831 						SetLineIndentation(j, GetLineIndentation(j) + indentSize * indentExtra);
1832 						caretPos += indentExtra * indentSize / indentChars;
1833 					}
1834 
1835 					atStart = false;
1836 					caretPos += selLength;
1837 				}
1838 				break;
1839 			case '\r':
1840 				// backward compatibility
1841 				break;
1842 			case '\n':
1843 				if (eolMode == SA::EndOfLine::CrLf || eolMode == SA::EndOfLine::Cr) {
1844 					abbrevText += '\r';
1845 				}
1846 				if (eolMode == SA::EndOfLine::CrLf || eolMode == SA::EndOfLine::Lf) {
1847 					abbrevText += '\n';
1848 				}
1849 				break;
1850 			default:
1851 				abbrevText += c;
1852 				break;
1853 			}
1854 			if (caretPos > wEditor.Length()) {
1855 				caretPos = wEditor.Length();
1856 			}
1857 			wEditor.InsertText(caretPos, abbrevText.c_str());
1858 			if (!doublePipe && atStart) {
1859 				selStart += abbrevText.length();
1860 			}
1861 			caretPos += abbrevText.length();
1862 			if (c == '\n') {
1863 				isIndent = true;
1864 				indentExtra = 0;
1865 				currentLineNumber++;
1866 				SetLineIndentation(currentLineNumber, indent);
1867 				caretPos += indent / indentChars;
1868 				if (!doublePipe && atStart) {
1869 					selStart += indent / indentChars;
1870 				}
1871 			} else {
1872 				isIndent = false;
1873 			}
1874 		}
1875 	}
1876 
1877 	// set the caret to the desired position
1878 	if (doublePipe) {
1879 		selLength = 0;
1880 	}
1881 	wEditor.SetSel(selStart, selStart + selLength);
1882 
1883 	wEditor.EndUndoAction();
1884 	return true;
1885 }
1886 
StartInsertAbbreviation()1887 bool SciTEBase::StartInsertAbbreviation() {
1888 	if (!AbbrevDialog()) {
1889 		return true;
1890 	}
1891 
1892 	return PerformInsertAbbreviation();
1893 }
1894 
StartExpandAbbreviation()1895 bool SciTEBase::StartExpandAbbreviation() {
1896 	const SA::Position currentPos = GetCaretInLine();
1897 	const SA::Position position = wEditor.CurrentPos(); // from the beginning
1898 	const std::string linebuf(GetCurrentLine(), 0, currentPos);	// Just get text to the left of the caret
1899 	const SA::Position abbrevPos = (currentPos > 32 ? currentPos - 32 : 0);
1900 	const char *abbrev = linebuf.c_str() + abbrevPos;
1901 	std::string data;
1902 	SA::Position abbrevLength = currentPos - abbrevPos;
1903 	// Try each potential abbreviation from the first letter on a line
1904 	// and expanding to the right.
1905 	// We arbitrarily limit the length of an abbreviation (seems a reasonable value..),
1906 	// and of course stop on the caret.
1907 	while (abbrevLength > 0) {
1908 		data = propsAbbrev.GetString(abbrev);
1909 		if (!data.empty()) {
1910 			break;	/* Found */
1911 		}
1912 		abbrev++;	// One more letter to the right
1913 		abbrevLength--;
1914 	}
1915 
1916 	if (data.empty()) {
1917 		WarnUser(warnNotFound);	// No need for a special warning
1918 		return true; // returning if expanded abbreviation is empty
1919 	}
1920 
1921 	const std::string expbuf = UnSlashString(data.c_str());
1922 	const size_t expbuflen = expbuf.length();
1923 
1924 	SA::Position caretPos = -1; // caret position
1925 	SA::Line currentLineNumber = GetCurrentLineNumber();
1926 	int indent = 0;
1927 	const int indentSize = wEditor.Indent();
1928 	int indentExtra = 0;
1929 	bool isIndent = true;
1930 	const SA::EndOfLine eolMode = wEditor.EOLMode();
1931 	if (props.GetInt("indent.automatic")) {
1932 		indent = GetLineIndentation(currentLineNumber);
1933 	}
1934 
1935 	wEditor.BeginUndoAction();
1936 	wEditor.SetSel(position - abbrevLength, position);
1937 
1938 	// add the abbreviation one character at a time
1939 	for (size_t i = 0; i < expbuflen; i++) {
1940 		const char c = expbuf[i];
1941 		std::string abbrevText("");
1942 		if (isIndent && c == '\t') {
1943 			indentExtra++;
1944 			SetLineIndentation(currentLineNumber, indent + indentSize * indentExtra);
1945 		} else {
1946 			switch (c) {
1947 			case '|':
1948 				// user may want to insert '|' instead of caret
1949 				if (i < (expbuflen - 1) && expbuf[i + 1] == '|') {
1950 					// put '|' into the line
1951 					abbrevText += c;
1952 					i++;
1953 				} else if (caretPos == -1) {
1954 					if (i == 0) {
1955 						// when caret is set at the first place in abbreviation
1956 						caretPos = wEditor.CurrentPos() - abbrevLength;
1957 					} else {
1958 						caretPos = wEditor.CurrentPos();
1959 					}
1960 				}
1961 				break;
1962 			case '\n':
1963 				if (eolMode == SA::EndOfLine::CrLf || eolMode == SA::EndOfLine::Cr) {
1964 					abbrevText += '\r';
1965 				}
1966 				if (eolMode == SA::EndOfLine::CrLf || eolMode == SA::EndOfLine::Lf) {
1967 					abbrevText += '\n';
1968 				}
1969 				break;
1970 			default:
1971 				abbrevText += c;
1972 				break;
1973 			}
1974 			wEditor.ReplaceSel(abbrevText.c_str());
1975 			if (c == '\n') {
1976 				isIndent = true;
1977 				indentExtra = 0;
1978 				currentLineNumber++;
1979 				SetLineIndentation(currentLineNumber, indent);
1980 			} else {
1981 				isIndent = false;
1982 			}
1983 		}
1984 	}
1985 
1986 	// set the caret to the desired position
1987 	if (caretPos != -1) {
1988 		wEditor.GotoPos(caretPos);
1989 	}
1990 
1991 	wEditor.EndUndoAction();
1992 	return true;
1993 }
1994 
StartBlockComment()1995 bool SciTEBase::StartBlockComment() {
1996 	std::string fileNameForExtension = ExtensionFileName();
1997 	std::string lexerName = props.GetNewExpandString("lexer.", fileNameForExtension.c_str());
1998 	std::string base("comment.block.");
1999 	std::string commentAtLineStart("comment.block.at.line.start.");
2000 	base += lexerName;
2001 	commentAtLineStart += lexerName;
2002 	const bool placeCommentsAtLineStart = props.GetInt(commentAtLineStart.c_str()) != 0;
2003 
2004 	std::string comment = props.GetString(base.c_str());
2005 	if (comment == "") { // user friendly error message box
2006 		GUI::gui_string sBase = GUI::StringFromUTF8(base);
2007 		GUI::gui_string error = LocaliseMessage(
2008 						"Block comment variable '^0' is not defined in SciTE *.properties!", sBase.c_str());
2009 		WindowMessageBox(wSciTE, error);
2010 		return true;
2011 	}
2012 	const std::string longComment = comment + " ";
2013 	const SA::Position longCommentLength = longComment.length();
2014 	SA::Position selectionStart = wEditor.SelectionStart();
2015 	SA::Position selectionEnd = wEditor.SelectionEnd();
2016 	const SA::Position caretPosition = wEditor.CurrentPos();
2017 	// checking if caret is located in _beginning_ of selected block
2018 	const bool moveCaret = caretPosition < selectionEnd;
2019 	const SA::Line selStartLine = wEditor.LineFromPosition(selectionStart);
2020 	SA::Line selEndLine = wEditor.LineFromPosition(selectionEnd);
2021 	const SA::Line lines = selEndLine - selStartLine;
2022 	const SA::Position firstSelLineStart = wEditor.LineStart(selStartLine);
2023 	// "caret return" is part of the last selected line
2024 	if ((lines > 0) &&
2025 			(selectionEnd == wEditor.LineStart(selEndLine)))
2026 		selEndLine--;
2027 	wEditor.BeginUndoAction();
2028 	for (SA::Line i = selStartLine; i <= selEndLine; i++) {
2029 		const SA::Position lineStart = wEditor.LineStart(i);
2030 		SA::Position lineIndent = lineStart;
2031 		const SA::Position lineEnd = wEditor.LineEnd(i);
2032 		if (!placeCommentsAtLineStart) {
2033 			lineIndent = GetLineIndentPosition(i);
2034 		}
2035 		std::string linebuf = wEditor.StringOfRange(SA::Range(lineIndent, lineEnd));
2036 		// empty lines are not commented
2037 		if (linebuf.length() < 1)
2038 			continue;
2039 		if (StartsWith(linebuf, comment.c_str())) {
2040 			SA::Position commentLength = comment.length();
2041 			if (StartsWith(linebuf, longComment.c_str())) {
2042 				// Removing comment with space after it.
2043 				commentLength = longCommentLength;
2044 			}
2045 			wEditor.SetSel(lineIndent, lineIndent + commentLength);
2046 			wEditor.ReplaceSel("");
2047 			if (i == selStartLine) // is this the first selected line?
2048 				selectionStart -= commentLength;
2049 			selectionEnd -= commentLength; // every iteration
2050 			continue;
2051 		}
2052 		if (i == selStartLine) // is this the first selected line?
2053 			selectionStart += longCommentLength;
2054 		selectionEnd += longCommentLength; // every iteration
2055 		wEditor.InsertText(lineIndent, longComment.c_str());
2056 	}
2057 	// after uncommenting selection may promote itself to the lines
2058 	// before the first initially selected line;
2059 	// another problem - if only comment symbol was selected;
2060 	if (selectionStart < firstSelLineStart) {
2061 		if (selectionStart >= selectionEnd - (longCommentLength - 1))
2062 			selectionEnd = firstSelLineStart;
2063 		selectionStart = firstSelLineStart;
2064 	}
2065 	if (moveCaret) {
2066 		// moving caret to the beginning of selected block
2067 		wEditor.GotoPos(selectionEnd);
2068 		wEditor.SetCurrentPos(selectionStart);
2069 	} else {
2070 		wEditor.SetSel(selectionStart, selectionEnd);
2071 	}
2072 	wEditor.EndUndoAction();
2073 	return true;
2074 }
2075 
LineEndString(SA::EndOfLine eolMode)2076 static const char *LineEndString(SA::EndOfLine eolMode) noexcept {
2077 	switch (eolMode) {
2078 	case SA::EndOfLine::CrLf:
2079 		return "\r\n";
2080 	case SA::EndOfLine::Cr:
2081 		return "\r";
2082 	case SA::EndOfLine::Lf:
2083 	default:
2084 		return "\n";
2085 	}
2086 }
2087 
StartBoxComment()2088 bool SciTEBase::StartBoxComment() {
2089 	// Get start/middle/end comment strings from options file(s)
2090 	std::string fileNameForExtension = ExtensionFileName();
2091 	std::string lexerName = props.GetNewExpandString("lexer.", fileNameForExtension.c_str());
2092 	std::string startBase("comment.box.start.");
2093 	std::string middleBase("comment.box.middle.");
2094 	std::string endBase("comment.box.end.");
2095 	const std::string whiteSpace(" ");
2096 	const std::string eol(LineEndString(wEditor.EOLMode()));
2097 	startBase += lexerName;
2098 	middleBase += lexerName;
2099 	endBase += lexerName;
2100 	std::string startComment = props.GetString(startBase.c_str());
2101 	std::string middleComment = props.GetString(middleBase.c_str());
2102 	std::string endComment = props.GetString(endBase.c_str());
2103 	if (startComment == "" || middleComment == "" || endComment == "") {
2104 		GUI::gui_string sStart = GUI::StringFromUTF8(startBase);
2105 		GUI::gui_string sMiddle = GUI::StringFromUTF8(middleBase);
2106 		GUI::gui_string sEnd = GUI::StringFromUTF8(endBase);
2107 		GUI::gui_string error = LocaliseMessage(
2108 						"Box comment variables '^0', '^1' and '^2' are not defined in SciTE *.properties!",
2109 						sStart.c_str(), sMiddle.c_str(), sEnd.c_str());
2110 		WindowMessageBox(wSciTE, error);
2111 		return true;
2112 	}
2113 
2114 	// Note selection and cursor location so that we can reselect text and reposition cursor after we insert comment strings
2115 	SA::Position selectionStart = wEditor.SelectionStart();
2116 	SA::Position selectionEnd = wEditor.SelectionEnd();
2117 	const SA::Position caretPosition = wEditor.CurrentPos();
2118 	const bool moveCaret = caretPosition < selectionEnd;
2119 	const SA::Line selStartLine = wEditor.LineFromPosition(selectionStart);
2120 	SA::Line selEndLine = wEditor.LineFromPosition(selectionEnd);
2121 	SA::Line lines = selEndLine - selStartLine + 1;
2122 
2123 	// If selection ends at start of last selected line, fake it so that selection goes to end of second-last selected line
2124 	if (lines > 1 && selectionEnd == wEditor.LineStart(selEndLine)) {
2125 		selEndLine--;
2126 		lines--;
2127 		selectionEnd = wEditor.LineEnd(selEndLine);
2128 	}
2129 
2130 	// Pad comment strings with appropriate whitespace, then figure out their lengths (endComment is a bit special-- see below)
2131 	startComment += whiteSpace;
2132 	middleComment += whiteSpace;
2133 	const SA::Position startCommentLength = startComment.length();
2134 	const SA::Position middleCommentLength = middleComment.length();
2135 	const SA::Position endCommentLength = endComment.length();
2136 
2137 	wEditor.BeginUndoAction();
2138 
2139 	// Insert startComment if needed
2140 	SA::Position lineStart = wEditor.LineStart(selStartLine);
2141 	std::string tempString = wEditor.StringOfRange(SA::Range(lineStart, lineStart + startCommentLength));
2142 	if (startComment != tempString) {
2143 		wEditor.InsertText(lineStart, startComment.c_str());
2144 		selectionStart += startCommentLength;
2145 		selectionEnd += startCommentLength;
2146 	}
2147 
2148 	if (lines <= 1) {
2149 		// Only a single line was selected, so just append whitespace + end-comment at end of line if needed
2150 		const SA::Position lineEnd = wEditor.LineEnd(selEndLine);
2151 		tempString = wEditor.StringOfRange(SA::Range(lineEnd - endCommentLength, lineEnd));
2152 		if (endComment != tempString) {
2153 			endComment.insert(0, whiteSpace.c_str());
2154 			wEditor.InsertText(lineEnd, endComment.c_str());
2155 		}
2156 	} else {
2157 		// More than one line selected, so insert middleComments where needed
2158 		for (SA::Line i = selStartLine + 1; i < selEndLine; i++) {
2159 			lineStart = wEditor.LineStart(i);
2160 			tempString = wEditor.StringOfRange(SA::Range(lineStart, lineStart + middleCommentLength));
2161 			if (middleComment != tempString) {
2162 				wEditor.InsertText(lineStart, middleComment.c_str());
2163 				selectionEnd += middleCommentLength;
2164 			}
2165 		}
2166 
2167 		// If last selected line is not middle-comment or end-comment, we need to insert
2168 		// a middle-comment at the start of last selected line and possibly still insert
2169 		// and end-comment tag after the last line (extra logic is necessary to
2170 		// deal with the case that user selected the end-comment tag)
2171 		lineStart = wEditor.LineStart(selEndLine);
2172 		tempString = wEditor.StringOfRange(SA::Range(lineStart, lineStart + endCommentLength));
2173 		if (endComment != tempString) {
2174 			tempString = wEditor.StringOfRange(SA::Range(lineStart, lineStart + middleCommentLength));
2175 			if (middleComment != tempString) {
2176 				wEditor.InsertText(lineStart, middleComment.c_str());
2177 				selectionEnd += middleCommentLength;
2178 			}
2179 
2180 			// And since we didn't find the end-comment string yet, we need to check the *next* line
2181 			//  to see if it's necessary to insert an end-comment string and a linefeed there....
2182 			lineStart = wEditor.LineStart(selEndLine + 1);
2183 			tempString = wEditor.StringOfRange(SA::Range(lineStart, lineStart + endCommentLength));
2184 			if (endComment != tempString) {
2185 				endComment += eol;
2186 				wEditor.InsertText(lineStart, endComment.c_str());
2187 			}
2188 		}
2189 	}
2190 
2191 	if (moveCaret) {
2192 		// moving caret to the beginning of selected block
2193 		wEditor.GotoPos(selectionEnd);
2194 		wEditor.SetCurrentPos(selectionStart);
2195 	} else {
2196 		wEditor.SetSel(selectionStart, selectionEnd);
2197 	}
2198 
2199 	wEditor.EndUndoAction();
2200 
2201 	return true;
2202 }
2203 
StartStreamComment()2204 bool SciTEBase::StartStreamComment() {
2205 	std::string fileNameForExtension = ExtensionFileName();
2206 	const std::string lexerName = props.GetNewExpandString("lexer.", fileNameForExtension.c_str());
2207 	std::string startBase("comment.stream.start.");
2208 	std::string endBase("comment.stream.end.");
2209 	std::string whiteSpace(" ");
2210 	startBase += lexerName;
2211 	endBase += lexerName;
2212 	std::string startComment = props.GetString(startBase.c_str());
2213 	std::string endComment = props.GetString(endBase.c_str());
2214 	if (startComment == "" || endComment == "") {
2215 		GUI::gui_string sStart = GUI::StringFromUTF8(startBase);
2216 		GUI::gui_string sEnd = GUI::StringFromUTF8(endBase);
2217 		GUI::gui_string error = LocaliseMessage(
2218 						"Stream comment variables '^0' and '^1' are not defined in SciTE *.properties!",
2219 						sStart.c_str(), sEnd.c_str());
2220 		WindowMessageBox(wSciTE, error);
2221 		return true;
2222 	}
2223 	startComment += whiteSpace;
2224 	whiteSpace += endComment;
2225 	endComment = whiteSpace;
2226 	const SA::Position startCommentLength = startComment.length();
2227 	SA::Range selection = wEditor.SelectionRange();
2228 	const SA::Position caretPosition = wEditor.CurrentPos();
2229 	// checking if caret is located in _beginning_ of selected block
2230 	const bool moveCaret = caretPosition < selection.end;
2231 	// if there is no selection?
2232 	if (selection.start == selection.end) {
2233 		RangeExtend(wEditor, selection,
2234 			    &SciTEBase::islexerwordcharforsel);
2235 		if (selection.start == selection.end)
2236 			return true; // caret is located _between_ words
2237 	}
2238 	wEditor.BeginUndoAction();
2239 	wEditor.InsertText(selection.start, startComment.c_str());
2240 	selection.end += startCommentLength;
2241 	selection.start += startCommentLength;
2242 	wEditor.InsertText(selection.end, endComment.c_str());
2243 	if (moveCaret) {
2244 		// moving caret to the beginning of selected block
2245 		wEditor.GotoPos(selection.end);
2246 		wEditor.SetCurrentPos(selection.start);
2247 	} else {
2248 		wEditor.SetSel(selection.start, selection.end);
2249 	}
2250 	wEditor.EndUndoAction();
2251 	return true;
2252 }
2253 
2254 /**
2255  * Return the length of the given line, not counting the EOL.
2256  */
GetLineLength(SA::Line line)2257 SA::Position SciTEBase::GetLineLength(SA::Line line) {
2258 	return wEditor.LineEnd(line) - wEditor.LineStart(line);
2259 }
2260 
GetCurrentLineNumber()2261 SA::Line SciTEBase::GetCurrentLineNumber() {
2262 	return wEditor.LineFromPosition(
2263 		       wEditor.CurrentPos());
2264 }
2265 
GetCurrentColumnNumber()2266 SA::Position SciTEBase::GetCurrentColumnNumber() {
2267 	const int mainSel = wEditor.MainSelection();
2268 	return wEditor.Column(wEditor.SelectionNCaret(mainSel)) +
2269 	       wEditor.SelectionNCaretVirtualSpace(mainSel);
2270 }
2271 
GetCurrentScrollPosition()2272 SA::Line SciTEBase::GetCurrentScrollPosition() {
2273 	const SA::Line lineDisplayTop = wEditor.FirstVisibleLine();
2274 	return wEditor.DocLineFromVisible(lineDisplayTop);
2275 }
2276 
2277 /**
2278  * Set up properties for ReadOnly, EOLMode, BufferLength, NbOfLines, SelLength, SelHeight.
2279  */
SetTextProperties(PropSetFile & ps)2280 void SciTEBase::SetTextProperties(
2281 	PropSetFile &ps) {			///< Property set to update.
2282 
2283 	std::string ro = GUI::UTF8FromString(localiser.Text("READ"));
2284 	ps.Set("ReadOnly", CurrentBuffer()->isReadOnly ? ro.c_str() : "");
2285 
2286 	const SA::EndOfLine eolMode = wEditor.EOLMode();
2287 	ps.Set("EOLMode", eolMode == SA::EndOfLine::CrLf ? "CR+LF" : (eolMode == SA::EndOfLine::Lf ? "LF" : "CR"));
2288 
2289 	ps.Set("BufferLength", std::to_string(LengthDocument()));
2290 
2291 	ps.Set("NbOfLines", std::to_string(wEditor.LineCount()));
2292 
2293 	const SA::Range range = wEditor.SelectionRange();
2294 	const SA::Line selFirstLine = wEditor.LineFromPosition(range.start);
2295 	const SA::Line selLastLine = wEditor.LineFromPosition(range.end);
2296 	SA::Position charCount = 0;
2297 	if (wEditor.SelectionMode() == SA::SelectionMode::Rectangle) {
2298 		for (SA::Line line = selFirstLine; line <= selLastLine; line++) {
2299 			const SA::Position startPos = wEditor.GetLineSelStartPosition(line);
2300 			const SA::Position endPos = wEditor.GetLineSelEndPosition(line);
2301 			charCount += wEditor.CountCharacters(startPos, endPos);
2302 		}
2303 	} else {
2304 		charCount = wEditor.CountCharacters(range.start, range.end);
2305 	}
2306 	ps.Set("SelLength", std::to_string(charCount));
2307 	const SA::Position caretPos = wEditor.CurrentPos();
2308 	const SA::Position selAnchor = wEditor.Anchor();
2309 	SA::Line selHeight = selLastLine - selFirstLine + 1;
2310 	if (0 == range.Length()) {
2311 		selHeight = 0;
2312 	} else if (selLastLine == selFirstLine) {
2313 		selHeight = 1;
2314 	} else if ((wEditor.Column(caretPos) == 0 && (selAnchor <= caretPos)) ||
2315 			((wEditor.Column(selAnchor) == 0) && (selAnchor > caretPos))) {
2316 		selHeight = selLastLine - selFirstLine;
2317 	}
2318 	ps.Set("SelHeight", std::to_string(selHeight));
2319 }
2320 
UpdateStatusBar(bool bUpdateSlowData)2321 void SciTEBase::UpdateStatusBar(bool bUpdateSlowData) {
2322 	if (sbVisible) {
2323 		if (bUpdateSlowData) {
2324 			SetFileProperties(propsStatus);
2325 		}
2326 		SetTextProperties(propsStatus);
2327 		propsStatus.Set("LineNumber", std::to_string(GetCurrentLineNumber() + 1));
2328 		propsStatus.Set("ColumnNumber", std::to_string(GetCurrentColumnNumber() + 1));
2329 		propsStatus.Set("OverType", wEditor.Overtype() ? "OVR" : "INS");
2330 
2331 		char sbKey[32];
2332 		sprintf(sbKey, "statusbar.text.%d", sbNum);
2333 		std::string msg = propsStatus.GetExpandedString(sbKey);
2334 		if (msg.size() && sbValue != msg) {	// To avoid flickering, update only if needed
2335 			SetStatusBarText(msg.c_str());
2336 			sbValue = msg;
2337 		}
2338 	} else {
2339 		sbValue = "";
2340 	}
2341 }
2342 
SetLineIndentation(SA::Line line,int indent)2343 void SciTEBase::SetLineIndentation(SA::Line line, int indent) {
2344 	if (indent < 0)
2345 		return;
2346 	const SA::Range rangeStart = GetSelection();
2347 	SA::Range range = rangeStart;
2348 	const SA::Position posBefore = GetLineIndentPosition(line);
2349 	wEditor.SetLineIndentation(line, indent);
2350 	const SA::Position posAfter = GetLineIndentPosition(line);
2351 	const SA::Position posDifference = posAfter - posBefore;
2352 	if (posAfter > posBefore) {
2353 		// Move selection on
2354 		if (range.start >= posBefore) {
2355 			range.start += posDifference;
2356 		}
2357 		if (range.end >= posBefore) {
2358 			range.end += posDifference;
2359 		}
2360 	} else if (posAfter < posBefore) {
2361 		// Move selection back
2362 		if (range.start >= posAfter) {
2363 			if (range.start >= posBefore)
2364 				range.start += posDifference;
2365 			else
2366 				range.start = posAfter;
2367 		}
2368 		if (range.end >= posAfter) {
2369 			if (range.end >= posBefore)
2370 				range.end += posDifference;
2371 			else
2372 				range.end = posAfter;
2373 		}
2374 	}
2375 	if (!(rangeStart == range)) {
2376 		SetSelection(range.start, range.end);
2377 	}
2378 }
2379 
GetLineIndentation(SA::Line line)2380 int SciTEBase::GetLineIndentation(SA::Line line) {
2381 	return wEditor.LineIndentation(line);
2382 }
2383 
GetLineIndentPosition(SA::Line line)2384 SA::Position SciTEBase::GetLineIndentPosition(SA::Line line) {
2385 	return wEditor.LineIndentPosition(line);
2386 }
2387 
CreateIndentation(int indent,int tabSize,bool insertSpaces)2388 static std::string CreateIndentation(int indent, int tabSize, bool insertSpaces) {
2389 	std::string indentation;
2390 	if (!insertSpaces) {
2391 		while (indent >= tabSize) {
2392 			indentation.append("\t", 1);
2393 			indent -= tabSize;
2394 		}
2395 	}
2396 	while (indent > 0) {
2397 		indentation.append(" ", 1);
2398 		indent--;
2399 	}
2400 	return indentation;
2401 }
2402 
ConvertIndentation(int tabSize,int useTabs)2403 void SciTEBase::ConvertIndentation(int tabSize, int useTabs) {
2404 	wEditor.BeginUndoAction();
2405 	const SA::Line maxLine = wEditor.LineCount();
2406 	for (SA::Line line = 0; line < maxLine; line++) {
2407 		const SA::Position lineStart = wEditor.LineStart(line);
2408 		const int indent = GetLineIndentation(line);
2409 		const SA::Position indentPos = GetLineIndentPosition(line);
2410 		constexpr int maxIndentation = 1000;
2411 		if (indent < maxIndentation) {
2412 			std::string indentationNow = wEditor.StringOfRange(SA::Range(lineStart, indentPos));
2413 			std::string indentationWanted = CreateIndentation(indent, tabSize, !useTabs);
2414 			if (indentationNow != indentationWanted) {
2415 				wEditor.SetTarget(SA::Range(lineStart, indentPos));
2416 				wEditor.ReplaceTarget(indentationWanted);
2417 			}
2418 		}
2419 	}
2420 	wEditor.EndUndoAction();
2421 }
2422 
RangeIsAllWhitespace(SA::Position start,SA::Position end)2423 bool SciTEBase::RangeIsAllWhitespace(SA::Position start, SA::Position end) {
2424 	TextReader acc(wEditor);
2425 	for (SA::Position i = start; i < end; i++) {
2426 		if ((acc[i] != ' ') && (acc[i] != '\t'))
2427 			return false;
2428 	}
2429 	return true;
2430 }
2431 
GetLinePartsInStyle(SA::Line line,const StyleAndWords & saw)2432 std::vector<std::string> SciTEBase::GetLinePartsInStyle(SA::Line line, const StyleAndWords &saw) {
2433 	std::vector<std::string> sv;
2434 	TextReader acc(wEditor);
2435 	std::string s;
2436 	const bool separateCharacters = saw.IsSingleChar();
2437 	const SA::Position thisLineStart = wEditor.LineStart(line);
2438 	const SA::Position nextLineStart = wEditor.LineStart(line + 1);
2439 	for (SA::Position pos = thisLineStart; pos < nextLineStart; pos++) {
2440 		if (acc.StyleAt(pos) == saw.styleNumber) {
2441 			if (separateCharacters) {
2442 				// Add one character at a time, even if there is an adjacent character in the same style
2443 				if (s.length() > 0) {
2444 					sv.push_back(s);
2445 				}
2446 				s = "";
2447 			}
2448 			s += acc[pos];
2449 		} else if (s.length() > 0) {
2450 			sv.push_back(s);
2451 			s = "";
2452 		}
2453 	}
2454 	if (s.length() > 0) {
2455 		sv.push_back(s);
2456 	}
2457 	return sv;
2458 }
2459 
includes(const StyleAndWords & symbols,const std::string & value)2460 static bool includes(const StyleAndWords &symbols, const std::string &value) {
2461 	if (symbols.words.length() == 0) {
2462 		return false;
2463 	} else if (IsAlphabetic(symbols.words[0])) {
2464 		// Set of symbols separated by spaces
2465 		const size_t lenVal = value.length();
2466 		const char *symbol = symbols.words.c_str();
2467 		while (symbol) {
2468 			const char *symbolEnd = strchr(symbol, ' ');
2469 			size_t lenSymbol = strlen(symbol);
2470 			if (symbolEnd)
2471 				lenSymbol = symbolEnd - symbol;
2472 			if (lenSymbol == lenVal) {
2473 				if (strncmp(symbol, value.c_str(), lenSymbol) == 0) {
2474 					return true;
2475 				}
2476 			}
2477 			symbol = symbolEnd;
2478 			if (symbol)
2479 				symbol++;
2480 		}
2481 	} else {
2482 		// Set of individual characters. Only one character allowed for now
2483 		const char ch = symbols.words[0];
2484 		return strchr(value.c_str(), ch) != nullptr;
2485 	}
2486 	return false;
2487 }
2488 
GetIndentState(SA::Line line)2489 IndentationStatus SciTEBase::GetIndentState(SA::Line line) {
2490 	// C like language indentation defined by braces and keywords
2491 	IndentationStatus indentState = isNone;
2492 	const std::vector<std::string> controlIndents = GetLinePartsInStyle(line, statementIndent);
2493 	for (const std::string &sIndent : controlIndents) {
2494 		if (includes(statementIndent, sIndent))
2495 			indentState = isKeyWordStart;
2496 	}
2497 	const std::vector<std::string> controlEnds = GetLinePartsInStyle(line, statementEnd);
2498 	for (const std::string &sEnd : controlEnds) {
2499 		if (includes(statementEnd, sEnd))
2500 			indentState = isNone;
2501 	}
2502 	// Braces override keywords
2503 	const std::vector<std::string> controlBlocks = GetLinePartsInStyle(line, blockEnd);
2504 	for (const std::string &sBlock : controlBlocks) {
2505 		if (includes(blockEnd, sBlock))
2506 			indentState = isBlockEnd;
2507 		if (includes(blockStart, sBlock))
2508 			indentState = isBlockStart;
2509 	}
2510 	return indentState;
2511 }
2512 
IndentOfBlock(SA::Line line)2513 int SciTEBase::IndentOfBlock(SA::Line line) {
2514 	if (line < 0)
2515 		return 0;
2516 	const int indentSize = wEditor.Indent();
2517 	int indentBlock = GetLineIndentation(line);
2518 	SA::Line backLine = line;
2519 	IndentationStatus indentState = isNone;
2520 	if (statementIndent.IsEmpty() && blockStart.IsEmpty() && blockEnd.IsEmpty())
2521 		indentState = isBlockStart;	// Don't bother searching backwards
2522 
2523 	SA::Line lineLimit = line - statementLookback;
2524 	if (lineLimit < 0)
2525 		lineLimit = 0;
2526 	while ((backLine >= lineLimit) && (indentState == 0)) {
2527 		indentState = GetIndentState(backLine);
2528 		if (indentState != 0) {
2529 			indentBlock = GetLineIndentation(backLine);
2530 			if (indentState == isBlockStart) {
2531 				if (!indentOpening)
2532 					indentBlock += indentSize;
2533 			}
2534 			if (indentState == isBlockEnd) {
2535 				if (indentClosing)
2536 					indentBlock -= indentSize;
2537 				if (indentBlock < 0)
2538 					indentBlock = 0;
2539 			}
2540 			if ((indentState == isKeyWordStart) && (backLine == line))
2541 				indentBlock += indentSize;
2542 		}
2543 		backLine--;
2544 	}
2545 	return indentBlock;
2546 }
2547 
MaintainIndentation(char ch)2548 void SciTEBase::MaintainIndentation(char ch) {
2549 	const SA::EndOfLine eolMode = wEditor.EOLMode();
2550 	const SA::Line curLine = GetCurrentLineNumber();
2551 	SA::Line lastLine = curLine - 1;
2552 
2553 	if (((eolMode == SA::EndOfLine::CrLf || eolMode == SA::EndOfLine::Lf) && ch == '\n') ||
2554 			(eolMode == SA::EndOfLine::Cr && ch == '\r')) {
2555 		if (props.GetInt("indent.automatic")) {
2556 			while (lastLine >= 0 && GetLineLength(lastLine) == 0)
2557 				lastLine--;
2558 		}
2559 		int indentAmount = 0;
2560 		if (lastLine >= 0) {
2561 			indentAmount = GetLineIndentation(lastLine);
2562 		}
2563 		if (indentAmount > 0) {
2564 			SetLineIndentation(curLine, indentAmount);
2565 		}
2566 	}
2567 }
2568 
AutomaticIndentation(char ch)2569 void SciTEBase::AutomaticIndentation(char ch) {
2570 	const SA::Range range = wEditor.SelectionRange();
2571 	const SA::Position selStart = range.start;
2572 	const SA::Line curLine = GetCurrentLineNumber();
2573 	const SA::Position thisLineStart = wEditor.LineStart(curLine);
2574 	const int indentSize = wEditor.Indent();
2575 	int indentBlock = IndentOfBlock(curLine - 1);
2576 
2577 	if ((wEditor.Lexer() == SCLEX_PYTHON) &&
2578 			(props.GetInt("indent.python.colon") == 1)) {
2579 		const SA::EndOfLine eolMode = wEditor.EOLMode();
2580 		const int eolChar = (eolMode == SA::EndOfLine::Cr ? '\r' : '\n');
2581 		const int eolChars = (eolMode == SA::EndOfLine::CrLf ? 2 : 1);
2582 		const SA::Position prevLineStart = wEditor.LineStart(curLine - 1);
2583 		const SA::Position prevIndentPos = GetLineIndentPosition(curLine - 1);
2584 		const int indentExisting = GetLineIndentation(curLine);
2585 
2586 		if (ch == eolChar) {
2587 			// Find last noncomment, nonwhitespace character on previous line
2588 			char character = '\0';
2589 			int style = 0;
2590 			for (SA::Position p = selStart - eolChars - 1; p > prevLineStart; p--) {
2591 				style = wEditor.UnsignedStyleAt(p);
2592 				if (style != SCE_P_DEFAULT && style != SCE_P_COMMENTLINE &&
2593 						style != SCE_P_COMMENTBLOCK) {
2594 					character = wEditor.CharacterAt(p);
2595 					break;
2596 				}
2597 			}
2598 			indentBlock = GetLineIndentation(curLine - 1);
2599 			if (style == SCE_P_OPERATOR && character == ':') {
2600 				SetLineIndentation(curLine, indentBlock + indentSize);
2601 			} else if (selStart == prevIndentPos + eolChars) {
2602 				// Preserve the indentation of preexisting text beyond the caret
2603 				SetLineIndentation(curLine, indentBlock + indentExisting);
2604 			} else {
2605 				SetLineIndentation(curLine, indentBlock);
2606 			}
2607 		}
2608 		return;
2609 	}
2610 
2611 	if (blockEnd.IsSingleChar() && ch == blockEnd.words[0]) {	// Dedent maybe
2612 		if (!indentClosing) {
2613 			if (RangeIsAllWhitespace(thisLineStart, selStart - 1)) {
2614 				SetLineIndentation(curLine, indentBlock - indentSize);
2615 			}
2616 		}
2617 	} else if (!blockEnd.IsSingleChar() && (ch == ' ')) {	// Dedent maybe
2618 		if (!indentClosing && (GetIndentState(curLine) == isBlockEnd)) {}
2619 	} else if (blockStart.IsSingleChar() && (ch == blockStart.words[0])) {
2620 		// Dedent maybe if first on line and previous line was starting keyword
2621 		if (!indentOpening && (GetIndentState(curLine - 1) == isKeyWordStart)) {
2622 			if (RangeIsAllWhitespace(thisLineStart, selStart - 1)) {
2623 				SetLineIndentation(curLine, indentBlock - indentSize);
2624 			}
2625 		}
2626 	} else if ((ch == '\r' || ch == '\n') && (selStart == thisLineStart)) {
2627 		if (!indentClosing && !blockEnd.IsSingleChar()) {	// Dedent previous line maybe
2628 			const std::vector<std::string> controlWords = GetLinePartsInStyle(curLine - 1, blockEnd);
2629 			if (!controlWords.empty()) {
2630 				if (includes(blockEnd, controlWords[0])) {
2631 					// Check if first keyword on line is an ender
2632 					SetLineIndentation(curLine - 1, IndentOfBlock(curLine - 2) - indentSize);
2633 					// Recalculate as may have changed previous line
2634 					indentBlock = IndentOfBlock(curLine - 1);
2635 				}
2636 			}
2637 		}
2638 		SetLineIndentation(curLine, indentBlock);
2639 	}
2640 }
2641 
2642 /**
2643  * Upon a character being added, SciTE may decide to perform some action
2644  * such as displaying a completion list or auto-indentation.
2645  */
CharAdded(int utf32)2646 void SciTEBase::CharAdded(int utf32) {
2647 	if (recording)
2648 		return;
2649 	const SA::Range rangeSelection = GetSelection();
2650 	const SA::Position selStart = rangeSelection.start;
2651 	const SA::Position selEnd = rangeSelection.end;
2652 
2653 	if (utf32 > 0XFF) { // MBCS, never let it go.
2654 		if (imeAutoComplete) {
2655 			if ((selEnd == selStart) && (selStart > 0)) {
2656 				if (wEditor.CallTipActive()) {
2657 					ContinueCallTip();
2658 				} else if (wEditor.AutoCActive()) {
2659 					wEditor.AutoCCancel();
2660 					StartAutoComplete();
2661 				} else {
2662 					StartAutoComplete();
2663 				}
2664 			}
2665 		}
2666 		return;
2667 	}
2668 
2669 	// SBCS
2670 	const char ch = static_cast<char>(utf32);
2671 	if ((selEnd == selStart) && (selStart > 0)) {
2672 		if (wEditor.CallTipActive()) {
2673 			if (Contains(calltipParametersEnd, ch)) {
2674 				braceCount--;
2675 				if (braceCount < 1)
2676 					wEditor.CallTipCancel();
2677 				else
2678 					StartCallTip();
2679 			} else if (Contains(calltipParametersStart, ch)) {
2680 				braceCount++;
2681 				StartCallTip();
2682 			} else {
2683 				ContinueCallTip();
2684 			}
2685 		} else if (wEditor.AutoCActive()) {
2686 			if (Contains(calltipParametersStart, ch)) {
2687 				braceCount++;
2688 				StartCallTip();
2689 			} else if (Contains(calltipParametersEnd, ch)) {
2690 				braceCount--;
2691 			} else if (!Contains(wordCharacters, ch)) {
2692 				wEditor.AutoCCancel();
2693 				if (Contains(autoCompleteStartCharacters, ch)) {
2694 					StartAutoComplete();
2695 				}
2696 			} else if (autoCCausedByOnlyOne) {
2697 				StartAutoCompleteWord(true);
2698 			}
2699 		} else if (HandleXml(ch)) {
2700 			// Handled in the routine
2701 		} else {
2702 			if (Contains(calltipParametersStart, ch)) {
2703 				braceCount = 1;
2704 				StartCallTip();
2705 			} else {
2706 				autoCCausedByOnlyOne = false;
2707 				if (indentMaintain)
2708 					MaintainIndentation(ch);
2709 				else if (props.GetInt("indent.automatic"))
2710 					AutomaticIndentation(ch);
2711 				if (Contains(autoCompleteStartCharacters, ch)) {
2712 					StartAutoComplete();
2713 				} else if (props.GetInt("autocompleteword.automatic") && Contains(wordCharacters, ch)) {
2714 					StartAutoCompleteWord(true);
2715 					autoCCausedByOnlyOne = wEditor.AutoCActive();
2716 				}
2717 			}
2718 		}
2719 	}
2720 }
2721 
2722 /**
2723  * Upon a character being added to the output, SciTE may decide to perform some action
2724  * such as displaying a completion list or running a shell command.
2725  */
CharAddedOutput(int ch)2726 void SciTEBase::CharAddedOutput(int ch) {
2727 	if (ch == '\n') {
2728 		NewLineInOutput();
2729 	} else if (ch == '(') {
2730 		// Potential autocompletion of symbols when $( typed
2731 		const SA::Position selStart = wOutput.SelectionStart();
2732 		if ((selStart > 1) && (wOutput.CharacterAt(selStart - 2) == '$')) {
2733 			std::string symbols;
2734 			const char *key = nullptr;
2735 			const char *val = nullptr;
2736 			bool b = props.GetFirst(key, val);
2737 			while (b) {
2738 				symbols.append(key);
2739 				symbols.append(") ");
2740 				b = props.GetNext(key, val);
2741 			}
2742 			StringList symList;
2743 			symList.Set(symbols.c_str());
2744 			std::string words = symList.GetNearestWords("", 0, true);
2745 			if (words.length()) {
2746 				wOutput.AutoCSetSeparator(' ');
2747 				wOutput.AutoCShow(0, words.c_str());
2748 			}
2749 		}
2750 	}
2751 }
2752 
2753 /**
2754  * This routine will auto complete XML or HTML tags that are still open by closing them
2755  * @param ch The character we are dealing with, currently only works with the '>' character
2756  * @return True if handled, false otherwise
2757  */
HandleXml(char ch)2758 bool SciTEBase::HandleXml(char ch) {
2759 	// We're looking for this char
2760 	// Quit quickly if not found
2761 	if (ch != '>') {
2762 		return false;
2763 	}
2764 
2765 	// This may make sense only in certain languages
2766 	if (lexLanguage != SCLEX_HTML && lexLanguage != SCLEX_XML) {
2767 		return false;
2768 	}
2769 
2770 	// If the user has turned us off, quit now.
2771 	// Default is off
2772 	const std::string value = props.GetExpandedString("xml.auto.close.tags");
2773 	if ((value.length() == 0) || (value == "0")) {
2774 		return false;
2775 	}
2776 
2777 	// Grab the last 512 characters or so
2778 	const SA::Position nCaret = wEditor.CurrentPos();
2779 	SA::Position nMin = nCaret - 512;
2780 	if (nMin < 0) {
2781 		nMin = 0;
2782 	}
2783 
2784 	if (nCaret - nMin < 3) {
2785 		return false; // Smallest tag is 3 characters ex. <p>
2786 	}
2787 	std::string sel = wEditor.StringOfRange(SA::Range(nMin, nCaret));
2788 
2789 	if (sel[nCaret - nMin - 2] == '/') {
2790 		// User typed something like "<br/>"
2791 		return false;
2792 	}
2793 
2794 	if (sel[nCaret - nMin - 2] == '-') {
2795 		// User typed something like "<a $this->"
2796 		return false;
2797 	}
2798 
2799 	std::string strFound = FindOpenXmlTag(sel.c_str(), nCaret - nMin);
2800 
2801 	if (strFound.length() > 0) {
2802 		wEditor.BeginUndoAction();
2803 		std::string toInsert = "</";
2804 		toInsert += strFound;
2805 		toInsert += ">";
2806 		wEditor.ReplaceSel(toInsert.c_str());
2807 		SetSelection(nCaret, nCaret);
2808 		wEditor.EndUndoAction();
2809 		return true;
2810 	}
2811 
2812 	return false;
2813 }
2814 
2815 /** Search backward through nSize bytes looking for a '<', then return the tag if any
2816  * @return The tag name
2817  */
FindOpenXmlTag(const char sel[],SA::Position nSize)2818 std::string SciTEBase::FindOpenXmlTag(const char sel[], SA::Position nSize) {
2819 	std::string strRet = "";
2820 
2821 	if (nSize < 3) {
2822 		// Smallest tag is "<p>" which is 3 characters
2823 		return strRet;
2824 	}
2825 	const char *pBegin = &sel[0];
2826 	const char *pCur = &sel[nSize - 1];
2827 
2828 	pCur--; // Skip past the >
2829 	while (pCur > pBegin) {
2830 		if (*pCur == '<') {
2831 			break;
2832 		} else if (*pCur == '>') {
2833 			if (*(pCur - 1) != '-') {
2834 				break;
2835 			}
2836 		}
2837 		--pCur;
2838 	}
2839 
2840 	if (*pCur == '<') {
2841 		pCur++;
2842 		while (strchr(":_-.", *pCur) || IsAlphaNumeric(*pCur)) {
2843 			strRet += *pCur;
2844 			pCur++;
2845 		}
2846 	}
2847 
2848 	// Return the tag name or ""
2849 	return strRet;
2850 }
2851 
GoMatchingBrace(bool select)2852 void SciTEBase::GoMatchingBrace(bool select) {
2853 	SA::Position braceAtCaret = -1;
2854 	SA::Position braceOpposite = -1;
2855 	const bool isInside = FindMatchingBracePosition(pwFocussed == &wEditor, braceAtCaret, braceOpposite, true);
2856 	// Convert the character positions into caret positions based on whether
2857 	// the caret position was inside or outside the braces.
2858 	if (isInside) {
2859 		if (braceOpposite > braceAtCaret) {
2860 			braceAtCaret++;
2861 		} else if (braceOpposite >= 0) {
2862 			braceOpposite++;
2863 		}
2864 	} else {    // Outside
2865 		if (braceOpposite > braceAtCaret) {
2866 			braceOpposite++;
2867 		} else {
2868 			braceAtCaret++;
2869 		}
2870 	}
2871 	if (braceOpposite >= 0) {
2872 		EnsureRangeVisible(*pwFocussed, SA::Range(braceOpposite));
2873 		if (select) {
2874 			pwFocussed->SetSel(braceAtCaret, braceOpposite);
2875 		} else {
2876 			pwFocussed->SetSel(braceOpposite, braceOpposite);
2877 		}
2878 	}
2879 }
2880 
2881 // Text	ConditionalUp	Ctrl+J	Finds the previous matching preprocessor condition
2882 // Text	ConditionalDown	Ctrl+K	Finds the next matching preprocessor condition
GoMatchingPreprocCond(int direction,bool select)2883 void SciTEBase::GoMatchingPreprocCond(int direction, bool select) {
2884 	const SA::Position mppcAtCaret = wEditor.CurrentPos();
2885 	SA::Position mppcMatch = -1;
2886 	const int forward = (direction == IDM_NEXTMATCHPPC);
2887 	const bool isInside = FindMatchingPreprocCondPosition(forward, mppcAtCaret, mppcMatch);
2888 
2889 	if (isInside && mppcMatch >= 0) {
2890 		EnsureRangeVisible(wEditor, SA::Range(mppcMatch));
2891 		if (select) {
2892 			// Selection changes the rules a bit...
2893 			const SA::Position selStart = wEditor.SelectionStart();
2894 			const SA::Position selEnd = wEditor.SelectionEnd();
2895 			// pivot isn't the caret position but the opposite (if there is a selection)
2896 			const SA::Position pivot = (mppcAtCaret == selStart ? selEnd : selStart);
2897 			if (forward) {
2898 				// Caret goes one line beyond the target, to allow selecting the whole line
2899 				const SA::Line lineNb = wEditor.LineFromPosition(mppcMatch);
2900 				mppcMatch = wEditor.LineStart(lineNb + 1);
2901 			}
2902 			SetSelection(pivot, mppcMatch);
2903 		} else {
2904 			SetSelection(mppcMatch, mppcMatch);
2905 		}
2906 	} else {
2907 		WarnUser(warnNotFound);
2908 	}
2909 }
2910 
AddCommand(const std::string & cmd,const std::string & dir,JobSubsystem jobType,const std::string & input,int flags)2911 void SciTEBase::AddCommand(const std::string &cmd, const std::string &dir, JobSubsystem jobType, const std::string &input, int flags) {
2912 	// If no explicit directory, use the directory of the current file
2913 	FilePath directoryRun;
2914 	if (dir.length()) {
2915 		FilePath directoryExplicit(GUI::StringFromUTF8(dir));
2916 		if (directoryExplicit.IsAbsolute()) {
2917 			directoryRun = directoryExplicit;
2918 		} else {
2919 			// Relative paths are relative to the current file
2920 			directoryRun = FilePath(filePath.Directory(), directoryExplicit).NormalizePath();
2921 		}
2922 	} else {
2923 		directoryRun = filePath.Directory();
2924 	}
2925 	jobQueue.AddCommand(cmd, directoryRun, jobType, input, flags);
2926 }
2927 
ControlIDOfCommand(unsigned long wParam)2928 int ControlIDOfCommand(unsigned long wParam) noexcept {
2929 	return wParam & 0xffff;
2930 }
2931 
WindowSetFocus(GUI::ScintillaWindow & w)2932 void WindowSetFocus(GUI::ScintillaWindow &w) {
2933 	w.Send(SCI_GRABFOCUS);
2934 }
2935 
SetLineNumberWidth()2936 void SciTEBase::SetLineNumberWidth() {
2937 	if (lineNumbers) {
2938 		int lineNumWidth = lineNumbersWidth;
2939 
2940 		if (lineNumbersExpand) {
2941 			// The margin size will be expanded if the current buffer's maximum
2942 			// line number would overflow the margin.
2943 
2944 			SA::Line lineCount = wEditor.LineCount();
2945 
2946 			lineNumWidth = 1;
2947 			while (lineCount >= 10) {
2948 				lineCount /= 10;
2949 				++lineNumWidth;
2950 			}
2951 
2952 			if (lineNumWidth < lineNumbersWidth) {
2953 				lineNumWidth = lineNumbersWidth;
2954 			}
2955 		}
2956 		if (lineNumWidth < 0)
2957 			lineNumWidth = 0;
2958 		// The 4 here allows for spacing: 1 pixel on left and 3 on right.
2959 		std::string nNines(lineNumWidth, '9');
2960 		const int pixelWidth = 4 + wEditor.TextWidth(
2961 					       static_cast<int>(SA::StylesCommon::LineNumber), nNines.c_str());
2962 
2963 		wEditor.SetMarginWidthN(0, pixelWidth);
2964 	} else {
2965 		wEditor.SetMarginWidthN(0, 0);
2966 	}
2967 }
2968 
MenuCommand(int cmdID,int source)2969 void SciTEBase::MenuCommand(int cmdID, int source) {
2970 	switch (cmdID) {
2971 	case IDM_NEW:
2972 		// For the New command, the "are you sure" question is always asked as this gives
2973 		// an opportunity to abandon the edits made to a file when are.you.sure is turned off.
2974 		if (CanMakeRoom()) {
2975 			New();
2976 			ReadProperties();
2977 			SetIndentSettings();
2978 			SetEol();
2979 			UpdateStatusBar(true);
2980 			WindowSetFocus(wEditor);
2981 		}
2982 		break;
2983 	case IDM_OPEN:
2984 		// No need to see if can make room as that will occur
2985 		// when doing the opening. Must be done there as user
2986 		// may decide to open multiple files so do not know yet
2987 		// how much room needed.
2988 		OpenDialog(filePath.Directory(), GUI::StringFromUTF8(props.GetExpandedString("open.filter")).c_str());
2989 		WindowSetFocus(wEditor);
2990 		break;
2991 	case IDM_OPENSELECTED:
2992 		if (OpenSelected())
2993 			WindowSetFocus(wEditor);
2994 		break;
2995 	case IDM_REVERT:
2996 		Revert();
2997 		WindowSetFocus(wEditor);
2998 		break;
2999 	case IDM_CLOSE:
3000 		if (SaveIfUnsure() != saveCancelled) {
3001 			Close();
3002 			WindowSetFocus(wEditor);
3003 		}
3004 		break;
3005 	case IDM_CLOSEALL:
3006 		CloseAllBuffers();
3007 		break;
3008 	case IDM_SAVE:
3009 		Save();
3010 		WindowSetFocus(wEditor);
3011 		break;
3012 	case IDM_SAVEALL:
3013 		SaveAllBuffers(true);
3014 		break;
3015 	case IDM_SAVEAS:
3016 		SaveAsDialog();
3017 		WindowSetFocus(wEditor);
3018 		break;
3019 	case IDM_SAVEACOPY:
3020 		SaveACopy();
3021 		WindowSetFocus(wEditor);
3022 		break;
3023 	case IDM_COPYPATH:
3024 		CopyPath();
3025 		break;
3026 	case IDM_SAVEASHTML:
3027 		SaveAsHTML();
3028 		WindowSetFocus(wEditor);
3029 		break;
3030 	case IDM_SAVEASRTF:
3031 		SaveAsRTF();
3032 		WindowSetFocus(wEditor);
3033 		break;
3034 	case IDM_SAVEASPDF:
3035 		SaveAsPDF();
3036 		WindowSetFocus(wEditor);
3037 		break;
3038 	case IDM_SAVEASTEX:
3039 		SaveAsTEX();
3040 		WindowSetFocus(wEditor);
3041 		break;
3042 	case IDM_SAVEASXML:
3043 		SaveAsXML();
3044 		WindowSetFocus(wEditor);
3045 		break;
3046 	case IDM_PRINT:
3047 		Print(true);
3048 		break;
3049 	case IDM_PRINTSETUP:
3050 		PrintSetup();
3051 		break;
3052 	case IDM_LOADSESSION:
3053 		LoadSessionDialog();
3054 		WindowSetFocus(wEditor);
3055 		break;
3056 	case IDM_SAVESESSION:
3057 		SaveSessionDialog();
3058 		WindowSetFocus(wEditor);
3059 		break;
3060 	case IDM_ABOUT:
3061 		AboutDialog();
3062 		break;
3063 	case IDM_QUIT:
3064 		QuitProgram();
3065 		break;
3066 	case IDM_ENCODING_DEFAULT:
3067 	case IDM_ENCODING_UCS2BE:
3068 	case IDM_ENCODING_UCS2LE:
3069 	case IDM_ENCODING_UTF8:
3070 	case IDM_ENCODING_UCOOKIE:
3071 		CurrentBuffer()->unicodeMode = static_cast<UniMode>(cmdID - IDM_ENCODING_DEFAULT);
3072 		if (CurrentBuffer()->unicodeMode != uni8Bit) {
3073 			// Override the code page if Unicode
3074 			codePage = SA::CpUtf8;
3075 		} else {
3076 			codePage = props.GetInt("code.page");
3077 		}
3078 		wEditor.SetCodePage(codePage);
3079 		break;
3080 
3081 	case IDM_NEXTFILESTACK:
3082 		if (buffers.size() > 1 && props.GetInt("buffers.zorder.switching")) {
3083 			NextInStack(); // next most recently selected buffer
3084 			WindowSetFocus(wEditor);
3085 			break;
3086 		}
3087 		[[fallthrough]];
3088 	// else fall through and do NEXTFILE behaviour...
3089 	case IDM_NEXTFILE:
3090 		if (buffers.size() > 1) {
3091 			Next(); // Use Next to tabs move left-to-right
3092 			WindowSetFocus(wEditor);
3093 		} else {
3094 			// Not using buffers - switch to next file on MRU
3095 			StackMenuNext();
3096 		}
3097 		break;
3098 
3099 	case IDM_PREVFILESTACK:
3100 		if (buffers.size() > 1 && props.GetInt("buffers.zorder.switching")) {
3101 			PrevInStack(); // next least recently selected buffer
3102 			WindowSetFocus(wEditor);
3103 			break;
3104 		}
3105 		[[fallthrough]];
3106 	// else fall through and do PREVFILE behaviour...
3107 	case IDM_PREVFILE:
3108 		if (buffers.size() > 1) {
3109 			Prev(); // Use Prev to tabs move right-to-left
3110 			WindowSetFocus(wEditor);
3111 		} else {
3112 			// Not using buffers - switch to previous file on MRU
3113 			StackMenuPrev();
3114 		}
3115 		break;
3116 
3117 	case IDM_MOVETABRIGHT:
3118 		MoveTabRight();
3119 		WindowSetFocus(wEditor);
3120 		break;
3121 	case IDM_MOVETABLEFT:
3122 		MoveTabLeft();
3123 		WindowSetFocus(wEditor);
3124 		break;
3125 
3126 	case IDM_UNDO:
3127 		PaneSource(source).Undo();
3128 		CheckMenus();
3129 		break;
3130 	case IDM_REDO:
3131 		PaneSource(source).Redo();
3132 		CheckMenus();
3133 		break;
3134 
3135 	case IDM_CUT:
3136 		if (!PaneSource(source).SelectionEmpty()) {
3137 			PaneSource(source).Cut();
3138 		}
3139 		break;
3140 	case IDM_COPY:
3141 		if (!PaneSource(source).SelectionEmpty()) {
3142 			//fprintf(stderr, "Copy from %d\n", source);
3143 			PaneSource(source).Copy();
3144 		}
3145 		// does not trigger Notification::UpdateUI, so do CheckMenusClipboard() here
3146 		CheckMenusClipboard();
3147 		break;
3148 	case IDM_PASTE:
3149 		PaneSource(source).Paste();
3150 		break;
3151 	case IDM_DUPLICATE:
3152 		PaneSource(source).SelectionDuplicate();
3153 		break;
3154 	case IDM_PASTEANDDOWN: {
3155 			const SA::Position pos = PaneFocused().CurrentPos();
3156 			PaneFocused().Paste();
3157 			PaneFocused().SetCurrentPos(pos);
3158 			PaneFocused().CharLeft();
3159 			PaneFocused().LineDown();
3160 		}
3161 		break;
3162 	case IDM_CLEAR:
3163 		PaneSource(source).Clear();
3164 		break;
3165 	case IDM_SELECTALL:
3166 		PaneSource(source).SelectAll();
3167 		break;
3168 	case IDM_COPYASRTF:
3169 		CopyAsRTF();
3170 		break;
3171 
3172 	case IDM_FIND:
3173 		Find();
3174 		break;
3175 
3176 	case IDM_INCSEARCH:
3177 		IncrementSearchMode();
3178 		break;
3179 
3180 	case IDM_FINDNEXT:
3181 		FindNext(reverseFind);
3182 		break;
3183 
3184 	case IDM_FINDNEXTBACK:
3185 		FindNext(!reverseFind);
3186 		break;
3187 
3188 	case IDM_FINDNEXTSEL:
3189 		SelectionIntoFind();
3190 		FindNext(reverseFind, true, false);
3191 		break;
3192 
3193 	case IDM_ENTERSELECTION:
3194 		SelectionIntoFind();
3195 		break;
3196 
3197 	case IDM_SELECTIONADDNEXT:
3198 		SelectionAdd(addNext);
3199 		break;
3200 
3201 	case IDM_SELECTIONADDEACH:
3202 		SelectionAdd(addEach);
3203 		break;
3204 
3205 	case IDM_FINDNEXTBACKSEL:
3206 		SelectionIntoFind();
3207 		FindNext(!reverseFind, true, false);
3208 		break;
3209 
3210 	case IDM_FINDINFILES:
3211 		FindInFiles();
3212 		break;
3213 
3214 	case IDM_REPLACE:
3215 		Replace();
3216 		break;
3217 
3218 	case IDM_GOTO:
3219 		GoLineDialog();
3220 		break;
3221 
3222 	case IDM_MATCHBRACE:
3223 		GoMatchingBrace(false);
3224 		break;
3225 
3226 	case IDM_SELECTTOBRACE:
3227 		GoMatchingBrace(true);
3228 		break;
3229 
3230 	case IDM_PREVMATCHPPC:
3231 		GoMatchingPreprocCond(IDM_PREVMATCHPPC, false);
3232 		break;
3233 
3234 	case IDM_SELECTTOPREVMATCHPPC:
3235 		GoMatchingPreprocCond(IDM_PREVMATCHPPC, true);
3236 		break;
3237 
3238 	case IDM_NEXTMATCHPPC:
3239 		GoMatchingPreprocCond(IDM_NEXTMATCHPPC, false);
3240 		break;
3241 
3242 	case IDM_SELECTTONEXTMATCHPPC:
3243 		GoMatchingPreprocCond(IDM_NEXTMATCHPPC, true);
3244 		break;
3245 	case IDM_SHOWCALLTIP:
3246 		if (wEditor.CallTipActive()) {
3247 			currentCallTip = (currentCallTip + 1 == maxCallTips) ? 0 : currentCallTip + 1;
3248 			FillFunctionDefinition();
3249 		} else {
3250 			StartCallTip();
3251 		}
3252 		break;
3253 	case IDM_COMPLETE:
3254 		autoCCausedByOnlyOne = false;
3255 		StartAutoComplete();
3256 		break;
3257 
3258 	case IDM_COMPLETEWORD:
3259 		autoCCausedByOnlyOne = false;
3260 		StartAutoCompleteWord(false);
3261 		break;
3262 
3263 	case IDM_ABBREV:
3264 		wEditor.Cancel();
3265 		StartExpandAbbreviation();
3266 		break;
3267 
3268 	case IDM_INS_ABBREV:
3269 		wEditor.Cancel();
3270 		StartInsertAbbreviation();
3271 		break;
3272 
3273 	case IDM_BLOCK_COMMENT:
3274 		StartBlockComment();
3275 		break;
3276 
3277 	case IDM_BOX_COMMENT:
3278 		StartBoxComment();
3279 		break;
3280 
3281 	case IDM_STREAM_COMMENT:
3282 		StartStreamComment();
3283 		break;
3284 
3285 	case IDM_TOGGLE_FOLDALL:
3286 		FoldAll();
3287 		break;
3288 
3289 	case IDM_UPRCASE:
3290 		PaneFocused().UpperCase();
3291 		break;
3292 
3293 	case IDM_LWRCASE:
3294 		PaneFocused().LowerCase();
3295 		break;
3296 
3297 	case IDM_LINEREVERSE:
3298 		PaneFocused().LineReverse();
3299 		break;
3300 
3301 	case IDM_JOIN:
3302 		PaneFocused().TargetFromSelection();
3303 		PaneFocused().LinesJoin();
3304 		break;
3305 
3306 	case IDM_SPLIT:
3307 		PaneFocused().TargetFromSelection();
3308 		PaneFocused().LinesSplit(0);
3309 		break;
3310 
3311 	case IDM_EXPAND:
3312 		wEditor.ToggleFold(GetCurrentLineNumber());
3313 		break;
3314 
3315 	case IDM_TOGGLE_FOLDRECURSIVE: {
3316 			const SA::Line line = GetCurrentLineNumber();
3317 			const SA::FoldLevel level = wEditor.FoldLevel(line);
3318 			ToggleFoldRecursive(line, level);
3319 		}
3320 		break;
3321 
3322 	case IDM_EXPAND_ENSURECHILDRENVISIBLE: {
3323 			const SA::Line line = GetCurrentLineNumber();
3324 			const SA::FoldLevel level = wEditor.FoldLevel(line);
3325 			EnsureAllChildrenVisible(line, level);
3326 		}
3327 		break;
3328 
3329 	case IDM_SPLITVERTICAL: {
3330 			const GUI::Rectangle rcClient = GetClientRectangle();
3331 			const double doubleHeightOutput = heightOutput;
3332 			const double doublePreviousHeightOutput = previousHeightOutput;
3333 			heightOutput = static_cast<int>(splitVertical ?
3334 							std::lround(doubleHeightOutput * rcClient.Height() / rcClient.Width()) :
3335 							std::lround(doubleHeightOutput * rcClient.Width() / rcClient.Height()));
3336 			previousHeightOutput = static_cast<int>(splitVertical ?
3337 								std::lround(doublePreviousHeightOutput * rcClient.Height() / rcClient.Width()) :
3338 								std::lround(doublePreviousHeightOutput * rcClient.Width() / rcClient.Height()));
3339 		}
3340 		splitVertical = !splitVertical;
3341 		heightOutput = NormaliseSplit(heightOutput);
3342 		SizeSubWindows();
3343 		CheckMenus();
3344 		Redraw();
3345 		break;
3346 
3347 	case IDM_LINENUMBERMARGIN:
3348 		lineNumbers = !lineNumbers;
3349 		SetLineNumberWidth();
3350 		CheckMenus();
3351 		break;
3352 
3353 	case IDM_SELMARGIN:
3354 		margin = !margin;
3355 		wEditor.SetMarginWidthN(1, margin ? marginWidth : 0);
3356 		CheckMenus();
3357 		break;
3358 
3359 	case IDM_FOLDMARGIN:
3360 		foldMargin = !foldMargin;
3361 		wEditor.SetMarginWidthN(2, foldMargin ? foldMarginWidth : 0);
3362 		CheckMenus();
3363 		break;
3364 
3365 	case IDM_VIEWEOL:
3366 		wEditor.SetViewEOL(!wEditor.ViewEOL());
3367 		CheckMenus();
3368 		break;
3369 
3370 	case IDM_VIEWTOOLBAR:
3371 		tbVisible = !tbVisible;
3372 		ShowToolBar();
3373 		CheckMenus();
3374 		break;
3375 
3376 	case IDM_TOGGLEOUTPUT:
3377 		ToggleOutputVisible();
3378 		CheckMenus();
3379 		break;
3380 
3381 	case IDM_TOGGLEPARAMETERS:
3382 		ParametersDialog(false);
3383 		CheckMenus();
3384 		break;
3385 
3386 	case IDM_WRAP:
3387 		wrap = !wrap;
3388 		wEditor.SetWrapMode(wrap ? wrapStyle : SA::Wrap::None);
3389 		CheckMenus();
3390 		break;
3391 
3392 	case IDM_WRAPOUTPUT:
3393 		wrapOutput = !wrapOutput;
3394 		wOutput.SetWrapMode(wrapOutput ? wrapStyle : SA::Wrap::None);
3395 		CheckMenus();
3396 		break;
3397 
3398 	case IDM_READONLY:
3399 		CurrentBuffer()->isReadOnly = !CurrentBuffer()->isReadOnly;
3400 		wEditor.SetReadOnly(CurrentBuffer()->isReadOnly);
3401 		UpdateStatusBar(true);
3402 		CheckMenus();
3403 		SetBuffersMenu();
3404 		break;
3405 
3406 	case IDM_VIEWTABBAR:
3407 		tabVisible = !tabVisible;
3408 		ShowTabBar();
3409 		CheckMenus();
3410 		break;
3411 
3412 	case IDM_VIEWSTATUSBAR:
3413 		sbVisible = !sbVisible;
3414 		ShowStatusBar();
3415 		UpdateStatusBar(true);
3416 		CheckMenus();
3417 		break;
3418 
3419 	case IDM_CLEAROUTPUT:
3420 		wOutput.ClearAll();
3421 		break;
3422 
3423 	case IDM_SWITCHPANE:
3424 		if (pwFocussed == &wEditor)
3425 			WindowSetFocus(wOutput);
3426 		else
3427 			WindowSetFocus(wEditor);
3428 		break;
3429 
3430 	case IDM_EOL_CRLF:
3431 		wEditor.SetEOLMode(SA::EndOfLine::CrLf);
3432 		CheckMenus();
3433 		UpdateStatusBar(false);
3434 		break;
3435 
3436 	case IDM_EOL_CR:
3437 		wEditor.SetEOLMode(SA::EndOfLine::Cr);
3438 		CheckMenus();
3439 		UpdateStatusBar(false);
3440 		break;
3441 	case IDM_EOL_LF:
3442 		wEditor.SetEOLMode(SA::EndOfLine::Lf);
3443 		CheckMenus();
3444 		UpdateStatusBar(false);
3445 		break;
3446 	case IDM_EOL_CONVERT:
3447 		wEditor.ConvertEOLs(wEditor.EOLMode());
3448 		break;
3449 
3450 	case IDM_VIEWSPACE:
3451 		ViewWhitespace(wEditor.ViewWS() == SA::WhiteSpace::Invisible);
3452 		CheckMenus();
3453 		Redraw();
3454 		break;
3455 
3456 	case IDM_VIEWGUIDES: {
3457 			const bool viewIG = wEditor.IndentationGuides() == SA::IndentView::None;
3458 			wEditor.SetIndentationGuides(viewIG ? indentExamine : SA::IndentView::None);
3459 			CheckMenus();
3460 			Redraw();
3461 		}
3462 		break;
3463 
3464 	case IDM_COMPILE: {
3465 			if (SaveIfUnsureForBuilt() != saveCancelled) {
3466 				SelectionIntoProperties();
3467 				AddCommand(props.GetWild("command.compile.", FileNameExt().AsUTF8().c_str()), "",
3468 					   SubsystemType("command.compile.subsystem."));
3469 				if (jobQueue.HasCommandToRun())
3470 					Execute();
3471 			}
3472 		}
3473 		break;
3474 
3475 	case IDM_BUILD: {
3476 			if (SaveIfUnsureForBuilt() != saveCancelled) {
3477 				SelectionIntoProperties();
3478 				AddCommand(
3479 					props.GetWild("command.build.", FileNameExt().AsUTF8().c_str()),
3480 					props.GetNewExpandString("command.build.directory.", FileNameExt().AsUTF8().c_str()),
3481 					SubsystemType("command.build.subsystem."));
3482 				if (jobQueue.HasCommandToRun()) {
3483 					jobQueue.isBuilding = true;
3484 					Execute();
3485 				}
3486 			}
3487 		}
3488 		break;
3489 
3490 	case IDM_CLEAN: {
3491 			if (SaveIfUnsureForBuilt() != saveCancelled) {
3492 				SelectionIntoProperties();
3493 				AddCommand(props.GetWild("command.clean.", FileNameExt().AsUTF8().c_str()), "",
3494 					   SubsystemType("command.clean.subsystem."));
3495 				if (jobQueue.HasCommandToRun())
3496 					Execute();
3497 			}
3498 		}
3499 		break;
3500 
3501 	case IDM_GO: {
3502 			if (SaveIfUnsureForBuilt() != saveCancelled) {
3503 				SelectionIntoProperties();
3504 				int flags = 0;
3505 
3506 				if (!jobQueue.isBuilt) {
3507 					std::string buildcmd = props.GetNewExpandString("command.go.needs.", FileNameExt().AsUTF8().c_str());
3508 					AddCommand(buildcmd, "",
3509 						   SubsystemType("command.go.needs.subsystem."));
3510 					if (buildcmd.length() > 0) {
3511 						jobQueue.isBuilding = true;
3512 						flags |= jobForceQueue;
3513 					}
3514 				}
3515 				AddCommand(props.GetWild("command.go.", FileNameExt().AsUTF8().c_str()), "",
3516 					   SubsystemType("command.go.subsystem."), "", flags);
3517 				if (jobQueue.HasCommandToRun())
3518 					Execute();
3519 			}
3520 		}
3521 		break;
3522 
3523 	case IDM_STOPEXECUTE:
3524 		StopExecute();
3525 		break;
3526 
3527 	case IDM_NEXTMSG:
3528 		GoMessage(1);
3529 		break;
3530 
3531 	case IDM_PREVMSG:
3532 		GoMessage(-1);
3533 		break;
3534 
3535 	case IDM_OPENLOCALPROPERTIES:
3536 		OpenProperties(IDM_OPENLOCALPROPERTIES);
3537 		WindowSetFocus(wEditor);
3538 		break;
3539 
3540 	case IDM_OPENUSERPROPERTIES:
3541 		OpenProperties(IDM_OPENUSERPROPERTIES);
3542 		WindowSetFocus(wEditor);
3543 		break;
3544 
3545 	case IDM_OPENGLOBALPROPERTIES:
3546 		OpenProperties(IDM_OPENGLOBALPROPERTIES);
3547 		WindowSetFocus(wEditor);
3548 		break;
3549 
3550 	case IDM_OPENABBREVPROPERTIES:
3551 		OpenProperties(IDM_OPENABBREVPROPERTIES);
3552 		WindowSetFocus(wEditor);
3553 		break;
3554 
3555 	case IDM_OPENLUAEXTERNALFILE:
3556 		OpenProperties(IDM_OPENLUAEXTERNALFILE);
3557 		WindowSetFocus(wEditor);
3558 		break;
3559 
3560 	case IDM_OPENDIRECTORYPROPERTIES:
3561 		OpenProperties(IDM_OPENDIRECTORYPROPERTIES);
3562 		WindowSetFocus(wEditor);
3563 		break;
3564 
3565 	case IDM_SRCWIN:
3566 		break;
3567 
3568 	case IDM_BOOKMARK_TOGGLE:
3569 		BookmarkToggle();
3570 		break;
3571 
3572 	case IDM_BOOKMARK_NEXT:
3573 		BookmarkNext(true);
3574 		break;
3575 
3576 	case IDM_BOOKMARK_PREV:
3577 		BookmarkNext(false);
3578 		break;
3579 
3580 	case IDM_BOOKMARK_NEXT_SELECT:
3581 		BookmarkNext(true, true);
3582 		break;
3583 
3584 	case IDM_BOOKMARK_PREV_SELECT:
3585 		BookmarkNext(false, true);
3586 		break;
3587 
3588 	case IDM_BOOKMARK_CLEARALL:
3589 		wEditor.MarkerDeleteAll(markerBookmark);
3590 		RemoveFindMarks();
3591 		break;
3592 
3593 	case IDM_BOOKMARK_SELECT_ALL:
3594 		BookmarkSelectAll();
3595 		break;
3596 
3597 	case IDM_TABSIZE:
3598 		TabSizeDialog();
3599 		break;
3600 
3601 	case IDM_MONOFONT:
3602 		CurrentBuffer()->useMonoFont = !CurrentBuffer()->useMonoFont;
3603 		ReadFontProperties();
3604 		Redraw();
3605 		break;
3606 
3607 	case IDM_MACROLIST:
3608 		AskMacroList();
3609 		break;
3610 	case IDM_MACROPLAY:
3611 		StartPlayMacro();
3612 		break;
3613 	case IDM_MACRORECORD:
3614 		StartRecordMacro();
3615 		break;
3616 	case IDM_MACROSTOPRECORD:
3617 		StopRecordMacro();
3618 		break;
3619 
3620 	case IDM_HELP: {
3621 			SelectionIntoProperties();
3622 			AddCommand(props.GetWild("command.help.", FileNameExt().AsUTF8().c_str()), "",
3623 				   SubsystemType("command.help.subsystem."));
3624 			if (!jobQueue.IsExecuting() && jobQueue.HasCommandToRun()) {
3625 				jobQueue.isBuilding = true;
3626 				Execute();
3627 			}
3628 		}
3629 		break;
3630 
3631 	case IDM_HELP_SCITE: {
3632 			SelectionIntoProperties();
3633 			AddCommand(props.GetString("command.scite.help"), "",
3634 				   SubsystemFromChar(props.GetString("command.scite.help.subsystem")[0]));
3635 			if (!jobQueue.IsExecuting() && jobQueue.HasCommandToRun()) {
3636 				jobQueue.isBuilding = true;
3637 				Execute();
3638 			}
3639 		}
3640 		break;
3641 
3642 	default:
3643 		if ((cmdID >= bufferCmdID) &&
3644 				(cmdID < bufferCmdID + buffers.size())) {
3645 			SetDocumentAt(cmdID - bufferCmdID);
3646 			CheckReload();
3647 		} else if ((cmdID >= fileStackCmdID) &&
3648 				(cmdID < fileStackCmdID + fileStackMax)) {
3649 			StackMenu(cmdID - fileStackCmdID);
3650 		} else if (cmdID >= importCmdID &&
3651 				(cmdID < importCmdID + importMax)) {
3652 			ImportMenu(cmdID - importCmdID);
3653 		} else if (cmdID >= IDM_TOOLS && cmdID < IDM_TOOLS + toolMax) {
3654 			ToolsMenu(cmdID - IDM_TOOLS);
3655 		} else if (cmdID >= IDM_LANGUAGE && cmdID < IDM_LANGUAGE + 100) {
3656 			SetOverrideLanguage(cmdID - IDM_LANGUAGE);
3657 		} else if (cmdID >= SCI_START) {
3658 			PaneFocused().Call(static_cast<SA::Message>(cmdID));
3659 		}
3660 		break;
3661 	}
3662 }
3663 
FoldChanged(SA::Line line,SA::FoldLevel levelNow,SA::FoldLevel levelPrev)3664 void SciTEBase::FoldChanged(SA::Line line, SA::FoldLevel levelNow, SA::FoldLevel levelPrev) {
3665 	// Unfold any regions where the new fold structure makes that fold wrong.
3666 	// Will only unfold and show lines and never fold or hide lines.
3667 	if (LevelIsHeader(levelNow)) {
3668 		if (!(LevelIsHeader(levelPrev))) {
3669 			// Adding a fold point.
3670 			wEditor.SetFoldExpanded(line, true);
3671 			if (!wEditor.AllLinesVisible())
3672 				ExpandFolds(line, true, levelPrev);
3673 		}
3674 	} else if (LevelIsHeader(levelPrev)) {
3675 		const SA::Line prevLine = line - 1;
3676 		const SA::FoldLevel levelPrevLine = wEditor.FoldLevel(prevLine);
3677 
3678 		// Combining two blocks where the first block is collapsed (e.g. by deleting the line(s) which separate(s) the two blocks)
3679 		if ((LevelNumberPart(levelPrevLine) == LevelNumberPart(levelNow)) && !wEditor.LineVisible(prevLine)) {
3680 			const SA::Line parentLine = wEditor.FoldParent(prevLine);
3681 			const SA::FoldLevel levelParentLine = wEditor.FoldLevel(parentLine);
3682 			wEditor.SetFoldExpanded(parentLine, true);
3683 			ExpandFolds(parentLine, true, levelParentLine);
3684 		}
3685 
3686 		if (!wEditor.FoldExpanded(line)) {
3687 			// Removing the fold from one that has been contracted so should expand
3688 			// otherwise lines are left invisible with no way to make them visible
3689 			wEditor.SetFoldExpanded(line, true);
3690 			if (!wEditor.AllLinesVisible())
3691 				// Combining two blocks where the second one is collapsed (e.g. by adding characters in the line which separates the two blocks)
3692 				ExpandFolds(line, true, levelPrev);
3693 		}
3694 	}
3695 	if (!(LevelIsWhitespace(levelNow)) &&
3696 			(LevelNumberPart(levelPrev) > LevelNumberPart(levelNow))) {
3697 		if (!wEditor.AllLinesVisible()) {
3698 			// See if should still be hidden
3699 			const SA::Line parentLine = wEditor.FoldParent(line);
3700 			if (parentLine < 0) {
3701 				wEditor.ShowLines(line, line);
3702 			} else if (wEditor.FoldExpanded(parentLine) && wEditor.LineVisible(parentLine)) {
3703 				wEditor.ShowLines(line, line);
3704 			}
3705 		}
3706 	}
3707 	// Combining two blocks where the first one is collapsed (e.g. by adding characters in the line which separates the two blocks)
3708 	if (!(LevelIsWhitespace(levelNow) && (LevelNumberPart(levelPrev) < LevelNumberPart(levelNow)))) {
3709 		if (!wEditor.AllLinesVisible()) {
3710 			const SA::Line parentLine = wEditor.FoldParent(line);
3711 			if (!wEditor.FoldExpanded(parentLine) && wEditor.LineVisible(line)) {
3712 				wEditor.SetFoldExpanded(parentLine, true);
3713 				const SA::FoldLevel levelParentLine = wEditor.FoldLevel(parentLine);
3714 				ExpandFolds(parentLine, true, levelParentLine);
3715 			}
3716 		}
3717 	}
3718 }
3719 
ExpandFolds(SA::Line line,bool expand,SA::FoldLevel level)3720 void SciTEBase::ExpandFolds(SA::Line line, bool expand, SA::FoldLevel level) {
3721 	// Expand or contract line and all subordinates
3722 	// level is the fold level of line
3723 	const SA::Line lineMaxSubord = wEditor.LastChild(line, LevelNumberPart(level));
3724 	line++;
3725 	wEditor.Call(expand ? SA::Message::ShowLines : SA::Message::HideLines, line, lineMaxSubord);
3726 	while (line <= lineMaxSubord) {
3727 		const SA::FoldLevel levelLine = wEditor.FoldLevel(line);
3728 		if (LevelIsHeader(levelLine)) {
3729 			wEditor.SetFoldExpanded(line, expand);
3730 		}
3731 		line++;
3732 	}
3733 }
3734 
FoldAll()3735 void SciTEBase::FoldAll() {
3736 	wEditor.ColouriseAll();
3737 	const SA::Line maxLine = wEditor.LineCount();
3738 	bool expanding = true;
3739 	for (SA::Line lineSeek = 0; lineSeek < maxLine; lineSeek++) {
3740 		if (LevelIsHeader(wEditor.FoldLevel(lineSeek))) {
3741 			expanding = !wEditor.FoldExpanded(lineSeek);
3742 			break;
3743 		}
3744 	}
3745 	for (SA::Line line = 0; line < maxLine; line++) {
3746 		const SA::FoldLevel level = wEditor.FoldLevel(line);
3747 		if (LevelIsHeader(level) &&
3748 				(SA::FoldLevel::Base == LevelNumberPart(level))) {
3749 			const SA::Line lineMaxSubord = wEditor.LastChild(line, static_cast<SA::FoldLevel>(-1));
3750 			if (expanding) {
3751 				wEditor.SetFoldExpanded(line, true);
3752 				ExpandFolds(line, true, level);
3753 				line = lineMaxSubord;
3754 			} else {
3755 				wEditor.SetFoldExpanded(line, false);
3756 				if (lineMaxSubord > line)
3757 					wEditor.HideLines(line + 1, lineMaxSubord);
3758 			}
3759 		}
3760 	}
3761 }
3762 
GotoLineEnsureVisible(SA::Line line)3763 void SciTEBase::GotoLineEnsureVisible(SA::Line line) {
3764 	wEditor.EnsureVisibleEnforcePolicy(line);
3765 	wEditor.GotoLine(line);
3766 }
3767 
EnsureRangeVisible(GUI::ScintillaWindow & win,SA::Range range,bool enforcePolicy)3768 void SciTEBase::EnsureRangeVisible(GUI::ScintillaWindow &win, SA::Range range, bool enforcePolicy) {
3769 	const SA::Line lineStart = win.LineFromPosition(range.start);
3770 	const SA::Line lineEnd = win.LineFromPosition(range.end);
3771 	for (SA::Line line = lineStart; line <= lineEnd; line++) {
3772 		win.Call(enforcePolicy ? SA::Message::EnsureVisibleEnforcePolicy : SA::Message::EnsureVisible, line);
3773 	}
3774 }
3775 
MarginClick(SA::Position position,int modifiers)3776 bool SciTEBase::MarginClick(SA::Position position, int modifiers) {
3777 	const SA::Line lineClick = wEditor.LineFromPosition(position);
3778 	const SA::KeyMod km = static_cast<SA::KeyMod>(modifiers);
3779 	if ((FlagIsSet(km, SA::KeyMod::Shift)) && (FlagIsSet(km, SA::KeyMod::Ctrl))) {
3780 		FoldAll();
3781 	} else {
3782 		const SA::FoldLevel levelClick = wEditor.FoldLevel(lineClick);
3783 		if (LevelIsHeader(levelClick)) {
3784 			if (FlagIsSet(km, SA::KeyMod::Shift)) {
3785 				EnsureAllChildrenVisible(lineClick, levelClick);
3786 			} else if (FlagIsSet(km, SA::KeyMod::Ctrl)) {
3787 				ToggleFoldRecursive(lineClick, levelClick);
3788 			} else {
3789 				// Toggle this line
3790 				wEditor.ToggleFold(lineClick);
3791 			}
3792 		}
3793 	}
3794 	return true;
3795 }
3796 
ToggleFoldRecursive(SA::Line line,SA::FoldLevel level)3797 void SciTEBase::ToggleFoldRecursive(SA::Line line, SA::FoldLevel level) {
3798 	if (wEditor.FoldExpanded(line)) {
3799 		// This ensure fold structure created before the fold is expanded
3800 		wEditor.LastChild(line, LevelNumberPart(level));
3801 		// Contract this line and all children
3802 		wEditor.SetFoldExpanded(line, false);
3803 		ExpandFolds(line, false, level);
3804 	} else {
3805 		// Expand this line and all children
3806 		wEditor.SetFoldExpanded(line, true);
3807 		ExpandFolds(line, true, level);
3808 	}
3809 }
3810 
EnsureAllChildrenVisible(SA::Line line,SA::FoldLevel level)3811 void SciTEBase::EnsureAllChildrenVisible(SA::Line line, SA::FoldLevel level) {
3812 	// Ensure all children visible
3813 	wEditor.SetFoldExpanded(line, true);
3814 	ExpandFolds(line, true, level);
3815 }
3816 
NewLineInOutput()3817 void SciTEBase::NewLineInOutput() {
3818 	if (jobQueue.IsExecuting())
3819 		return;
3820 	SA::Line line = wOutput.LineFromPosition(
3821 				wOutput.CurrentPos()) - 1;
3822 	std::string cmd = GetLine(wOutput, line);
3823 	if (cmd == ">") {
3824 		// Search output buffer for previous command
3825 		line--;
3826 		while (line >= 0) {
3827 			cmd = GetLine(wOutput, line);
3828 			if (StartsWith(cmd, ">") && !StartsWith(cmd, ">Exit")) {
3829 				cmd = cmd.substr(1);
3830 				break;
3831 			}
3832 			line--;
3833 		}
3834 	} else if (StartsWith(cmd, ">")) {
3835 		cmd = cmd.substr(1);
3836 	}
3837 	returnOutputToCommand = false;
3838 	AddCommand(cmd.c_str(), "", jobCLI);
3839 	Execute();
3840 }
3841 
UpdateUI(const SCNotification * notification)3842 void SciTEBase::UpdateUI(const SCNotification *notification) {
3843 	const bool handled = extender && extender->OnUpdateUI();
3844 	if (!handled) {
3845 		BraceMatch(notification->nmhdr.idFrom == IDM_SRCWIN);
3846 		if (notification->nmhdr.idFrom == IDM_SRCWIN) {
3847 			UpdateStatusBar(false);
3848 		}
3849 		CheckMenusClipboard();
3850 	}
3851 	if (CurrentBuffer()->findMarks == Buffer::fmModified) {
3852 		RemoveFindMarks();
3853 	}
3854 	const SA::Update updated = static_cast<SA::Update>(notification->updated);
3855 	if (FlagIsSet(updated, SA::Update::Selection) || FlagIsSet(updated, SA::Update::Content)) {
3856 		if ((notification->nmhdr.idFrom == IDM_SRCWIN) == (pwFocussed == &wEditor)) {
3857 			// Only highlight focused pane.
3858 			if (FlagIsSet(updated, SA::Update::Selection)) {
3859 				currentWordHighlight.statesOfDelay = currentWordHighlight.noDelay; // Selection has just been updated, so delay is disabled.
3860 				currentWordHighlight.textHasChanged = false;
3861 				HighlightCurrentWord(true);
3862 			} else if (currentWordHighlight.textHasChanged) {
3863 				HighlightCurrentWord(false);
3864 			}
3865 		}
3866 	}
3867 }
3868 
Modified(const SCNotification * notification)3869 void SciTEBase::Modified(const SCNotification *notification) {
3870 	const SA::ModificationFlags modificationType =
3871 		static_cast<SA::ModificationFlags>(notification->modificationType);
3872 	const bool textWasModified = FlagIsSet(modificationType, SA::ModificationFlags::InsertText) ||
3873 		FlagIsSet(modificationType, SA::ModificationFlags::DeleteText);
3874 	if ((notification->nmhdr.idFrom == IDM_SRCWIN) && textWasModified)
3875 		CurrentBuffer()->DocumentModified();
3876 	if (FlagIsSet(modificationType, SA::ModificationFlags::LastStepInUndoRedo)) {
3877 		// When the user hits undo or redo, several normal insert/delete
3878 		// notifications may fire, but we will end up here in the end
3879 		EnableAMenuItem(IDM_UNDO, CallFocusedElseDefault(true, SA::Message::CanUndo));
3880 		EnableAMenuItem(IDM_REDO, CallFocusedElseDefault(true, SA::Message::CanRedo));
3881 	} else if (textWasModified) {
3882 		if ((notification->nmhdr.idFrom == IDM_SRCWIN) == (pwFocussed == &wEditor)) {
3883 			currentWordHighlight.textHasChanged = true;
3884 		}
3885 		// This will be called a lot, and usually means "typing".
3886 		EnableAMenuItem(IDM_UNDO, true);
3887 		EnableAMenuItem(IDM_REDO, false);
3888 		if (CurrentBuffer()->findMarks == Buffer::fmMarked) {
3889 			CurrentBuffer()->findMarks = Buffer::fmModified;
3890 		}
3891 	}
3892 
3893 	if (notification->linesAdded && lineNumbers && lineNumbersExpand) {
3894 		SetLineNumberWidth();
3895 	}
3896 
3897 	if (FlagIsSet(modificationType, SA::ModificationFlags::ChangeFold)) {
3898 		FoldChanged(notification->line,
3899 			    static_cast<SA::FoldLevel>(notification->foldLevelNow),
3900 			    static_cast<SA::FoldLevel>(notification->foldLevelPrev));
3901 	}
3902 }
3903 
Notify(SCNotification * notification)3904 void SciTEBase::Notify(SCNotification *notification) {
3905 	bool handled = false;
3906 	switch (static_cast<SA::Notification>(notification->nmhdr.code)) {
3907 	case SA::Notification::Painted:
3908 		if ((notification->nmhdr.idFrom == IDM_SRCWIN) == (pwFocussed == &wEditor)) {
3909 			// Only highlight focused pane.
3910 			// Manage delay before highlight when no user selection but there is word at the caret.
3911 			// So the Delay is based on the blinking of caret, scroll...
3912 			// If currentWordHighlight.statesOfDelay == currentWordHighlight.delay,
3913 			// then there is word at the caret without selection, and need some delay.
3914 			if (currentWordHighlight.statesOfDelay == currentWordHighlight.delay) {
3915 				if (currentWordHighlight.elapsedTimes.Duration() >= 0.5) {
3916 					currentWordHighlight.statesOfDelay = currentWordHighlight.delayJustEnded;
3917 					HighlightCurrentWord(true);
3918 					pwFocussed->InvalidateAll();
3919 				}
3920 			}
3921 		}
3922 		break;
3923 
3924 	case SA::Notification::FocusIn:
3925 		SetPaneFocus(notification->nmhdr.idFrom == IDM_SRCWIN);
3926 		CheckMenus();
3927 		break;
3928 
3929 	case SA::Notification::FocusOut:
3930 		CheckMenus();
3931 		break;
3932 
3933 	case SA::Notification::StyleNeeded: {
3934 			if (extender) {
3935 				// Colourisation may be performed by script
3936 				if ((notification->nmhdr.idFrom == IDM_SRCWIN) && (lexLanguage == SCLEX_CONTAINER)) {
3937 					SA::Position endStyled = wEditor.EndStyled();
3938 					const SA::Line lineEndStyled = wEditor.LineFromPosition(endStyled);
3939 					endStyled = wEditor.LineStart(lineEndStyled);
3940 					StyleWriter styler(wEditor);
3941 					int styleStart = 0;
3942 					if (endStyled > 0)
3943 						styleStart = styler.StyleAt(endStyled - 1);
3944 					styler.SetCodePage(codePage);
3945 					extender->OnStyle(endStyled, notification->position - endStyled,
3946 							  styleStart, &styler);
3947 					styler.Flush();
3948 				}
3949 			}
3950 		}
3951 		break;
3952 
3953 	case SA::Notification::CharAdded:
3954 		if (extender)
3955 			handled = extender->OnChar(static_cast<char>(notification->ch));
3956 		if (!handled) {
3957 			if (notification->nmhdr.idFrom == IDM_SRCWIN) {
3958 				CharAdded(notification->ch);
3959 			} else {
3960 				CharAddedOutput(notification->ch);
3961 			}
3962 		}
3963 		break;
3964 
3965 	case SA::Notification::SavePointReached:
3966 		if (notification->nmhdr.idFrom == IDM_SRCWIN) {
3967 			if (extender)
3968 				handled = extender->OnSavePointReached();
3969 			if (!handled) {
3970 				CurrentBuffer()->isDirty = false;
3971 			}
3972 		}
3973 		CheckMenus();
3974 		SetWindowName();
3975 		SetBuffersMenu();
3976 		break;
3977 
3978 	case SA::Notification::SavePointLeft:
3979 		if (notification->nmhdr.idFrom == IDM_SRCWIN) {
3980 			if (extender)
3981 				handled = extender->OnSavePointLeft();
3982 			if (!handled) {
3983 				CurrentBuffer()->isDirty = true;
3984 				jobQueue.isBuilt = false;
3985 			}
3986 		}
3987 		CheckMenus();
3988 		SetWindowName();
3989 		SetBuffersMenu();
3990 		break;
3991 
3992 	case SA::Notification::DoubleClick:
3993 		if (extender)
3994 			handled = extender->OnDoubleClick();
3995 		if (!handled && notification->nmhdr.idFrom == IDM_RUNWIN) {
3996 			GoMessage(0);
3997 		}
3998 		break;
3999 
4000 	case SA::Notification::UpdateUI:
4001 		UpdateUI(notification);
4002 		break;
4003 
4004 	case SA::Notification::Modified:
4005 		Modified(notification);
4006 		break;
4007 
4008 	case SA::Notification::MarginClick: {
4009 			if (extender)
4010 				handled = extender->OnMarginClick();
4011 			if (!handled) {
4012 				if (notification->margin == 2) {
4013 					MarginClick(notification->position, notification->modifiers);
4014 				}
4015 			}
4016 		}
4017 		break;
4018 
4019 	case SA::Notification::NeedShown: {
4020 			EnsureRangeVisible(wEditor, SA::Range(notification->position, notification->position + notification->length), false);
4021 		}
4022 		break;
4023 
4024 	case SA::Notification::UserListSelection: {
4025 			if (notification->wParam == 2)
4026 				ContinueMacroList(notification->text);
4027 			else if (extender && notification->wParam > 2)
4028 				extender->OnUserListSelection(static_cast<int>(notification->wParam), notification->text);
4029 		}
4030 		break;
4031 
4032 	case SA::Notification::CallTipClick: {
4033 			if (notification->position == 1 && currentCallTip > 0) {
4034 				currentCallTip--;
4035 				FillFunctionDefinition();
4036 			} else if (notification->position == 2 && currentCallTip + 1 < maxCallTips) {
4037 				currentCallTip++;
4038 				FillFunctionDefinition();
4039 			}
4040 		}
4041 		break;
4042 
4043 	case SA::Notification::MacroRecord:
4044 		RecordMacroCommand(notification);
4045 		break;
4046 
4047 	case SA::Notification::URIDropped:
4048 		OpenUriList(notification->text);
4049 		break;
4050 
4051 	case SA::Notification::DwellStart:
4052 		if (extender && (SA::InvalidPosition != notification->position)) {
4053 			SA::Range range(notification->position);
4054 			std::string message =
4055 				RangeExtendAndGrab(wEditor,
4056 						   range, &SciTEBase::iswordcharforsel);
4057 			if (message.length()) {
4058 				extender->OnDwellStart(range.start, message.c_str());
4059 			}
4060 		}
4061 		break;
4062 
4063 	case SA::Notification::DwellEnd:
4064 		if (extender) {
4065 			extender->OnDwellStart(0, ""); // flags end of calltip
4066 		}
4067 		break;
4068 
4069 	case SA::Notification::Zoom:
4070 		SetLineNumberWidth();
4071 		break;
4072 
4073 	case SA::Notification::ModifyAttemptRO:
4074 		AbandonAutomaticSave();
4075 		break;
4076 
4077 	default:
4078 		// Avoid warning for unhandled enumeration for notifications SciTEBase not interested in
4079 		break;
4080 	}
4081 }
4082 
CheckMenusClipboard()4083 void SciTEBase::CheckMenusClipboard() {
4084 	const bool hasSelection = !CallFocusedElseDefault(false, SA::Message::GetSelectionEmpty);
4085 	EnableAMenuItem(IDM_CUT, hasSelection);
4086 	EnableAMenuItem(IDM_COPY, hasSelection);
4087 	EnableAMenuItem(IDM_CLEAR, hasSelection);
4088 	EnableAMenuItem(IDM_PASTE, CallFocusedElseDefault(true, SA::Message::CanPaste));
4089 	EnableAMenuItem(IDM_SELECTALL, true);
4090 }
4091 
CheckMenus()4092 void SciTEBase::CheckMenus() {
4093 	CheckMenusClipboard();
4094 	EnableAMenuItem(IDM_UNDO, CallFocusedElseDefault(true, SA::Message::CanUndo));
4095 	EnableAMenuItem(IDM_REDO, CallFocusedElseDefault(true, SA::Message::CanRedo));
4096 	EnableAMenuItem(IDM_DUPLICATE, !CurrentBuffer()->isReadOnly);
4097 	EnableAMenuItem(IDM_SHOWCALLTIP, apis != 0);
4098 	EnableAMenuItem(IDM_COMPLETE, apis != 0);
4099 	CheckAMenuItem(IDM_SPLITVERTICAL, splitVertical);
4100 	EnableAMenuItem(IDM_OPENFILESHERE, props.GetInt("check.if.already.open") != 0);
4101 	CheckAMenuItem(IDM_OPENFILESHERE, openFilesHere);
4102 	CheckAMenuItem(IDM_WRAP, wrap);
4103 	CheckAMenuItem(IDM_WRAPOUTPUT, wrapOutput);
4104 	CheckAMenuItem(IDM_READONLY, CurrentBuffer()->isReadOnly);
4105 	CheckAMenuItem(IDM_FULLSCREEN, fullScreen);
4106 	CheckAMenuItem(IDM_VIEWTOOLBAR, tbVisible);
4107 	CheckAMenuItem(IDM_VIEWTABBAR, tabVisible);
4108 	CheckAMenuItem(IDM_VIEWSTATUSBAR, sbVisible);
4109 	CheckAMenuItem(IDM_VIEWEOL, wEditor.ViewEOL());
4110 	CheckAMenuItem(IDM_VIEWSPACE, wEditor.ViewWS() != SA::WhiteSpace::Invisible);
4111 	CheckAMenuItem(IDM_VIEWGUIDES, wEditor.IndentationGuides() != SA::IndentView::None);
4112 	CheckAMenuItem(IDM_LINENUMBERMARGIN, lineNumbers);
4113 	CheckAMenuItem(IDM_SELMARGIN, margin);
4114 	CheckAMenuItem(IDM_FOLDMARGIN, foldMargin);
4115 	CheckAMenuItem(IDM_TOGGLEOUTPUT, heightOutput > 0);
4116 	CheckAMenuItem(IDM_TOGGLEPARAMETERS, ParametersOpen());
4117 	CheckAMenuItem(IDM_MONOFONT, CurrentBuffer()->useMonoFont);
4118 	EnableAMenuItem(IDM_COMPILE, !jobQueue.IsExecuting() &&
4119 			props.GetWild("command.compile.", FileNameExt().AsUTF8().c_str()).size() != 0);
4120 	EnableAMenuItem(IDM_BUILD, !jobQueue.IsExecuting() &&
4121 			props.GetWild("command.build.", FileNameExt().AsUTF8().c_str()).size() != 0);
4122 	EnableAMenuItem(IDM_CLEAN, !jobQueue.IsExecuting() &&
4123 			props.GetWild("command.clean.", FileNameExt().AsUTF8().c_str()).size() != 0);
4124 	EnableAMenuItem(IDM_GO, !jobQueue.IsExecuting() &&
4125 			props.GetWild("command.go.", FileNameExt().AsUTF8().c_str()).size() != 0);
4126 	EnableAMenuItem(IDM_OPENDIRECTORYPROPERTIES, props.GetInt("properties.directory.enable") != 0);
4127 	for (int toolItem = 0; toolItem < toolMax; toolItem++)
4128 		EnableAMenuItem(IDM_TOOLS + toolItem, ToolIsImmediate(toolItem) || !jobQueue.IsExecuting());
4129 	EnableAMenuItem(IDM_STOPEXECUTE, jobQueue.IsExecuting());
4130 	if (buffers.size() > 0) {
4131 		TabSelect(buffers.Current());
4132 		for (int bufferItem = 0; bufferItem < buffers.lengthVisible; bufferItem++) {
4133 			CheckAMenuItem(IDM_BUFFER + bufferItem, bufferItem == buffers.Current());
4134 		}
4135 	}
4136 	EnableAMenuItem(IDM_MACROPLAY, !recording);
4137 	EnableAMenuItem(IDM_MACRORECORD, !recording);
4138 	EnableAMenuItem(IDM_MACROSTOPRECORD, recording);
4139 }
4140 
ContextMenu(GUI::ScintillaWindow & wSource,GUI::Point pt,GUI::Window wCmd)4141 void SciTEBase::ContextMenu(GUI::ScintillaWindow &wSource, GUI::Point pt, GUI::Window wCmd) {
4142 	const SA::Position currentPos = wSource.CurrentPos();
4143 	const SA::Position anchor = wSource.Anchor();
4144 	popup.CreatePopUp();
4145 	const bool writable = !wSource.ReadOnly();
4146 	AddToPopUp("Undo", IDM_UNDO, writable && wSource.CanUndo());
4147 	AddToPopUp("Redo", IDM_REDO, writable && wSource.CanRedo());
4148 	AddToPopUp("");
4149 	AddToPopUp("Cut", IDM_CUT, writable && currentPos != anchor);
4150 	AddToPopUp("Copy", IDM_COPY, currentPos != anchor);
4151 	AddToPopUp("Paste", IDM_PASTE, writable && wSource.CanPaste());
4152 	AddToPopUp("Delete", IDM_CLEAR, writable && currentPos != anchor);
4153 	AddToPopUp("");
4154 	AddToPopUp("Select All", IDM_SELECTALL);
4155 	AddToPopUp("");
4156 	if (wSource.GetID() == wOutput.GetID()) {
4157 		AddToPopUp("Hide", IDM_TOGGLEOUTPUT, true);
4158 	} else {
4159 		AddToPopUp("Close", IDM_CLOSE, true);
4160 	}
4161 	std::string userContextMenu = props.GetNewExpandString("user.context.menu");
4162 	std::replace(userContextMenu.begin(), userContextMenu.end(), '|', '\0');
4163 	const char *userContextItem = userContextMenu.c_str();
4164 	const char *endDefinition = userContextItem + userContextMenu.length();
4165 	while (userContextItem < endDefinition) {
4166 		const char *caption = userContextItem;
4167 		userContextItem += strlen(userContextItem) + 1;
4168 		if (userContextItem < endDefinition) {
4169 			const int cmd = GetMenuCommandAsInt(userContextItem);
4170 			userContextItem += strlen(userContextItem) + 1;
4171 			AddToPopUp(caption, cmd);
4172 		}
4173 	}
4174 	popup.Show(pt, wCmd);
4175 }
4176 
4177 /**
4178  * Ensure that a splitter bar position is inside the main window.
4179  */
NormaliseSplit(int splitPos)4180 int SciTEBase::NormaliseSplit(int splitPos) {
4181 	const GUI::Rectangle rcClient = GetClientRectangle();
4182 	const int w = rcClient.Width();
4183 	const int h = rcClient.Height();
4184 	if (splitPos < 20)
4185 		splitPos = 0;
4186 	if (splitVertical) {
4187 		if (splitPos > w - heightBar - 20)
4188 			splitPos = w - heightBar;
4189 	} else {
4190 		if (splitPos > h - heightBar - 20)
4191 			splitPos = h - heightBar;
4192 	}
4193 	return splitPos;
4194 }
4195 
MoveSplit(GUI::Point ptNewDrag)4196 void SciTEBase::MoveSplit(GUI::Point ptNewDrag) {
4197 	int newHeightOutput = heightOutputStartDrag + (ptStartDrag.y - ptNewDrag.y);
4198 	if (splitVertical)
4199 		newHeightOutput = heightOutputStartDrag + (ptStartDrag.x - ptNewDrag.x);
4200 	newHeightOutput = NormaliseSplit(newHeightOutput);
4201 	if (heightOutput != newHeightOutput) {
4202 		heightOutput = newHeightOutput;
4203 		SizeContentWindows();
4204 		//Redraw();
4205 	}
4206 
4207 	previousHeightOutput = newHeightOutput;
4208 }
4209 
TimerStart(int)4210 void SciTEBase::TimerStart(int /* mask */) {
4211 }
4212 
TimerEnd(int)4213 void SciTEBase::TimerEnd(int /* mask */) {
4214 }
4215 
OnTimer()4216 void SciTEBase::OnTimer() {
4217 	if (delayBeforeAutoSave && (0 == dialogsOnScreen)) {
4218 		// First save the visible buffer to avoid any switching if not needed
4219 		if (CurrentBuffer()->NeedsSave(delayBeforeAutoSave)) {
4220 			Save(sfNone);
4221 		}
4222 		// Then look through the other buffers to save any that need to be saved
4223 		const int currentBuffer = buffers.Current();
4224 		for (int i = 0; i < buffers.length; i++) {
4225 			if (buffers.buffers[i].NeedsSave(delayBeforeAutoSave)) {
4226 				SetDocumentAt(i);
4227 				Save(sfNone);
4228 			}
4229 		}
4230 		SetDocumentAt(currentBuffer);
4231 	}
4232 }
4233 
SetIdler(bool on)4234 void SciTEBase::SetIdler(bool on) {
4235 	needIdle = on;
4236 }
4237 
OnIdle()4238 void SciTEBase::OnIdle() {
4239 	if (!findMarker.Complete()) {
4240 		findMarker.Continue();
4241 		return;
4242 	}
4243 	if (!matchMarker.Complete()) {
4244 		matchMarker.Continue();
4245 		return;
4246 	}
4247 	SetIdler(false);
4248 }
4249 
SetHomeProperties()4250 void SciTEBase::SetHomeProperties() {
4251 	FilePath homepath = GetSciteDefaultHome();
4252 	props.Set("SciteDefaultHome", homepath.AsUTF8().c_str());
4253 	homepath = GetSciteUserHome();
4254 	props.Set("SciteUserHome", homepath.AsUTF8().c_str());
4255 }
4256 
UIAvailable()4257 void SciTEBase::UIAvailable() {
4258 	SetImportMenu();
4259 	if (extender) {
4260 		SetHomeProperties();
4261 		extender->Initialise(this);
4262 	}
4263 }
4264 
4265 /**
4266  * Find the character following a name which is made up of characters from
4267  * the set [a-zA-Z.]
4268  */
AfterName(const GUI::gui_char * s)4269 static GUI::gui_char AfterName(const GUI::gui_char *s) noexcept {
4270 	while (*s && ((*s == '.') ||
4271 			(*s >= 'a' && *s <= 'z') ||
4272 			(*s >= 'A' && *s <= 'Z')))
4273 		s++;
4274 	return *s;
4275 }
4276 
PerformOne(char * action)4277 void SciTEBase::PerformOne(char *action) {
4278 	const unsigned int len = UnSlash(action);
4279 	char *arg = strchr(action, ':');
4280 	if (arg) {
4281 		arg++;
4282 		if (isprefix(action, "askfilename:")) {
4283 			extender->OnMacro("filename", filePath.AsUTF8().c_str());
4284 		} else if (isprefix(action, "askproperty:")) {
4285 			PropertyToDirector(arg);
4286 		} else if (isprefix(action, "close:")) {
4287 			Close();
4288 			WindowSetFocus(wEditor);
4289 		} else if (isprefix(action, "currentmacro:")) {
4290 			currentMacro = arg;
4291 		} else if (isprefix(action, "cwd:")) {
4292 			FilePath dirTarget(GUI::StringFromUTF8(arg));
4293 			if (!dirTarget.SetWorkingDirectory()) {
4294 				GUI::gui_string msg = LocaliseMessage("Invalid directory '^0'.", dirTarget.AsInternal());
4295 				WindowMessageBox(wSciTE, msg);
4296 			}
4297 		} else if (isprefix(action, "enumproperties:")) {
4298 			EnumProperties(arg);
4299 		} else if (isprefix(action, "exportashtml:")) {
4300 			SaveToHTML(GUI::StringFromUTF8(arg));
4301 		} else if (isprefix(action, "exportasrtf:")) {
4302 			SaveToRTF(GUI::StringFromUTF8(arg));
4303 		} else if (isprefix(action, "exportaspdf:")) {
4304 			SaveToPDF(GUI::StringFromUTF8(arg));
4305 		} else if (isprefix(action, "exportaslatex:")) {
4306 			SaveToTEX(GUI::StringFromUTF8(arg));
4307 		} else if (isprefix(action, "exportasxml:")) {
4308 			SaveToXML(GUI::StringFromUTF8(arg));
4309 		} else if (isprefix(action, "find:") && wEditor.Created()) {
4310 			findWhat = arg;
4311 			FindNext(false, false);
4312 		} else if (isprefix(action, "goto:") && wEditor.Created()) {
4313 			const SA::Line line = IntegerFromText(arg) - 1;
4314 			GotoLineEnsureVisible(line);
4315 			// jump to column if given and greater than 0
4316 			const char *colstr = strchr(arg, ',');
4317 			if (colstr) {
4318 				const SA::Position col = IntegerFromText(colstr + 1);
4319 				if (col > 0) {
4320 					const SA::Position pos = wEditor.CurrentPos() + col;
4321 					// select the word you have found there
4322 					const SA::Position wordStart = wEditor.WordStartPosition(pos, true);
4323 					const SA::Position wordEnd = wEditor.WordEndPosition(pos, true);
4324 					wEditor.SetSel(wordStart, wordEnd);
4325 				}
4326 			}
4327 		} else if (isprefix(action, "insert:") && wEditor.Created()) {
4328 			wEditor.ReplaceSel(arg);
4329 		} else if (isprefix(action, "loadsession:")) {
4330 			if (*arg) {
4331 				LoadSessionFile(GUI::StringFromUTF8(arg).c_str());
4332 				RestoreSession();
4333 			}
4334 		} else if (isprefix(action, "macrocommand:")) {
4335 			ExecuteMacroCommand(arg);
4336 		} else if (isprefix(action, "macroenable:")) {
4337 			macrosEnabled = atoi(arg);
4338 			SetToolsMenu();
4339 		} else if (isprefix(action, "macrolist:")) {
4340 			StartMacroList(arg);
4341 		} else if (isprefix(action, "menucommand:")) {
4342 			MenuCommand(atoi(arg));
4343 		} else if (isprefix(action, "open:")) {
4344 			Open(GUI::StringFromUTF8(arg), ofSynchronous);
4345 		} else if (isprefix(action, "output:") && wOutput.Created()) {
4346 			wOutput.ReplaceSel(arg);
4347 		} else if (isprefix(action, "property:")) {
4348 			PropertyFromDirector(arg);
4349 		} else if (isprefix(action, "reloadproperties:")) {
4350 			ReloadProperties();
4351 		} else if (isprefix(action, "quit:")) {
4352 			QuitProgram();
4353 		} else if (isprefix(action, "replaceall:") && wEditor.Created()) {
4354 			if (len > strlen(action)) {
4355 				const char *arg2 = arg + strlen(arg) + 1;
4356 				findWhat = arg;
4357 				replaceWhat = arg2;
4358 				ReplaceAll(false);
4359 			}
4360 		} else if (isprefix(action, "saveas:")) {
4361 			if (*arg) {
4362 				SaveAs(GUI::StringFromUTF8(arg).c_str(), true);
4363 			} else {
4364 				SaveAsDialog();
4365 			}
4366 		} else if (isprefix(action, "savesession:")) {
4367 			if (*arg) {
4368 				SaveSessionFile(GUI::StringFromUTF8(arg).c_str());
4369 			}
4370 		} else if (isprefix(action, "extender:")) {
4371 			extender->OnExecute(arg);
4372 		} else if (isprefix(action, "focus:")) {
4373 			ActivateWindow(arg);
4374 		}
4375 	}
4376 }
4377 
IsSwitchCharacter(GUI::gui_char ch)4378 static bool IsSwitchCharacter(GUI::gui_char ch) noexcept {
4379 #if defined(__unix__) || defined(__APPLE__)
4380 	return ch == '-';
4381 #else
4382 	return (ch == '-') || (ch == '/');
4383 #endif
4384 }
4385 
4386 // Called by SciTEBase::PerformOne when action="enumproperties:"
EnumProperties(const char * propkind)4387 void SciTEBase::EnumProperties(const char *propkind) {
4388 	PropSetFile *pf = nullptr;
4389 
4390 	if (!extender)
4391 		return;
4392 	if (!strcmp(propkind, "dyn")) {
4393 		SelectionIntoProperties(); // Refresh properties ...
4394 		pf = &props;
4395 	} else if (!strcmp(propkind, "local"))
4396 		pf = &propsLocal;
4397 	else if (!strcmp(propkind, "directory"))
4398 		pf = &propsDirectory;
4399 	else if (!strcmp(propkind, "user"))
4400 		pf = &propsUser;
4401 	else if (!strcmp(propkind, "base"))
4402 		pf = &propsBase;
4403 	else if (!strcmp(propkind, "embed"))
4404 		pf = &propsEmbed;
4405 	else if (!strcmp(propkind, "platform"))
4406 		pf = &propsPlatform;
4407 	else if (!strcmp(propkind, "abbrev"))
4408 		pf = &propsAbbrev;
4409 
4410 	if (pf) {
4411 		const char *key = nullptr;
4412 		const char *val = nullptr;
4413 		bool b = pf->GetFirst(key, val);
4414 		while (b) {
4415 			SendOneProperty(propkind, key, val);
4416 			b = pf->GetNext(key, val);
4417 		}
4418 	}
4419 }
4420 
SendOneProperty(const char * kind,const char * key,const char * val)4421 void SciTEBase::SendOneProperty(const char *kind, const char *key, const char *val) {
4422 	std::string m = kind;
4423 	m += ":";
4424 	m += key;
4425 	m += "=";
4426 	m += val;
4427 	extender->SendProperty(m.c_str());
4428 }
4429 
PropertyFromDirector(const char * arg)4430 void SciTEBase::PropertyFromDirector(const char *arg) {
4431 	props.SetLine(arg);
4432 }
4433 
PropertyToDirector(const char * arg)4434 void SciTEBase::PropertyToDirector(const char *arg) {
4435 	if (!extender)
4436 		return;
4437 	SelectionIntoProperties();
4438 	const std::string gotprop = props.GetString(arg);
4439 	extender->OnMacro("macro:stringinfo", gotprop.c_str());
4440 }
4441 
4442 /**
4443  * Menu/Toolbar command "Record".
4444  */
StartRecordMacro()4445 void SciTEBase::StartRecordMacro() {
4446 	recording = true;
4447 	CheckMenus();
4448 	wEditor.StartRecord();
4449 }
4450 
4451 /**
4452  * Received a Notification::MacroRecord from Scintilla: send it to director.
4453  */
RecordMacroCommand(const SCNotification * notification)4454 bool SciTEBase::RecordMacroCommand(const SCNotification *notification) {
4455 	if (extender) {
4456 		std::string sMessage = StdStringFromInteger(notification->message);
4457 		sMessage += ";";
4458 		sMessage += std::to_string(notification->wParam);
4459 		sMessage += ";";
4460 		const char *t = reinterpret_cast<const char *>(notification->lParam);
4461 		if (t) {
4462 			//format : "<message>;<wParam>;1;<text>"
4463 			sMessage += "1;";
4464 			sMessage += t;
4465 		} else {
4466 			//format : "<message>;<wParam>;0;"
4467 			sMessage += "0;";
4468 		}
4469 		const bool handled = extender->OnMacro("macro:record", sMessage.c_str());
4470 		return handled;
4471 	}
4472 	return true;
4473 }
4474 
4475 /**
4476  * Menu/Toolbar command "Stop recording".
4477  */
StopRecordMacro()4478 void SciTEBase::StopRecordMacro() {
4479 	wEditor.StopRecord();
4480 	if (extender)
4481 		extender->OnMacro("macro:stoprecord", "");
4482 	recording = false;
4483 	CheckMenus();
4484 }
4485 
4486 /**
4487  * Menu/Toolbar command "Play macro...": tell director to build list of Macro names
4488  * Through this call, user has access to all macros in Filerx.
4489  */
AskMacroList()4490 void SciTEBase::AskMacroList() {
4491 	if (extender)
4492 		extender->OnMacro("macro:getlist", "");
4493 }
4494 
4495 /**
4496  * List of Macro names has been created. Ask Scintilla to show it.
4497  */
StartMacroList(const char * words)4498 bool SciTEBase::StartMacroList(const char *words) {
4499 	if (words) {
4500 		wEditor.UserListShow(2, words); //listtype=2
4501 	}
4502 
4503 	return true;
4504 }
4505 
4506 /**
4507  * User has chosen a macro in the list. Ask director to execute it.
4508  */
ContinueMacroList(const char * stext)4509 void SciTEBase::ContinueMacroList(const char *stext) {
4510 	if ((extender) && (*stext != '\0')) {
4511 		currentMacro = stext;
4512 		StartPlayMacro();
4513 	}
4514 }
4515 
4516 /**
4517  * Menu/Toolbar command "Play current macro" (or called from ContinueMacroList).
4518  */
StartPlayMacro()4519 void SciTEBase::StartPlayMacro() {
4520 	if (extender)
4521 		extender->OnMacro("macro:run", currentMacro.c_str());
4522 }
4523 
4524 /*
4525 SciTE received a macro command from director : execute it.
4526 If command needs answer (SCI_GETTEXTLENGTH ...) : give answer to director
4527 */
4528 
ReadNum(const char * & t)4529 static uintptr_t ReadNum(const char *&t) {
4530 	const char *argend = strchr(t, ';');	// find ';'
4531 	uintptr_t v = 0;
4532 	if (*t)
4533 		v = IntegerFromText(t);					// read value
4534 	t = (argend) ? (argend + 1) : nullptr;	// update pointer
4535 	return v;						// return value
4536 }
4537 
ExecuteMacroCommand(const char * command)4538 void SciTEBase::ExecuteMacroCommand(const char *command) {
4539 	const char *nextarg = command;
4540 	uintptr_t wParam = 0;
4541 	intptr_t lParam = 0;
4542 	intptr_t rep = 0;				//Scintilla's answer
4543 	const char *answercmd = nullptr;
4544 	SA::Position l = 0;
4545 	std::string string1;
4546 	char params[4] = "";
4547 	// This code does not validate its input which may cause crashes when bad.
4548 	// 'params' describes types of return values and of arguments.
4549 	// There are exactly 3 characters: return type, wParam, lParam.
4550 	// 0 : void or no param
4551 	// I : integer
4552 	// S : string
4553 	// R : string (for wParam only)
4554 	// For example, "4004;0RS;fruit;mango" performs SCI_SETPROPERTY("fruit","mango") with no return
4555 
4556 	// Extract message, parameter specification, wParam, lParam
4557 
4558 	const SA::Message message = static_cast<SA::Message>(ReadNum(nextarg));
4559 	if (!nextarg) {
4560 		Trace("Malformed macro command.\n");
4561 		return;
4562 	}
4563 	strncpy(params, nextarg, 3);
4564 	params[3] = '\0';
4565 	nextarg += 4;
4566 	if (*(params + 1) == 'R') {
4567 		// in one function wParam is a string  : void SetProperty(string key,string name)
4568 		const char *s1 = nextarg;
4569 		while (*nextarg != ';')
4570 			nextarg++;
4571 		string1.assign(s1, nextarg - s1);
4572 		wParam = UptrFromString(string1.c_str());
4573 		nextarg++;
4574 	} else {
4575 		wParam = ReadNum(nextarg);
4576 	}
4577 
4578 	if (*(params + 2) == 'S')
4579 		lParam = SptrFromString(nextarg);
4580 	else if ((*(params + 2) == 'I') && nextarg)	// nextarg check avoids warning from clang analyze
4581 		lParam = IntegerFromText(nextarg);
4582 
4583 	if (*params == '0') {
4584 		// no answer ...
4585 		wEditor.Call(message, wParam, lParam);
4586 		return;
4587 	}
4588 
4589 	if (*params == 'S') {
4590 		// string answer
4591 		if (message == SA::Message::GetSelText) {
4592 			l = wEditor.GetSelText(nullptr);
4593 			wParam = 0;
4594 		} else if (message == SA::Message::GetCurLine) {
4595 			const SA::Line line = wEditor.LineFromPosition(wEditor.CurrentPos());
4596 			l = wEditor.LineLength(line);
4597 			wParam = l;
4598 		} else if (message == SA::Message::GetText) {
4599 			l = wEditor.Length();
4600 			wParam = l;
4601 		} else if (message == SA::Message::GetLine) {
4602 			l = wEditor.LineLength(wParam);
4603 		} else {
4604 			l = 0; //unsupported calls EM
4605 		}
4606 		answercmd = "stringinfo:";
4607 
4608 	} else {
4609 		//int answer
4610 		answercmd = "intinfo:";
4611 		l = 30;
4612 	}
4613 
4614 	std::string tbuff = answercmd;
4615 	const size_t alen = strlen(answercmd);
4616 	tbuff.resize(l + alen + 1);
4617 	if (*params == 'S')
4618 		lParam = SptrFromPointer(&tbuff[alen]);
4619 
4620 	if (l > 0)
4621 		rep = wEditor.Call(message, wParam, lParam);
4622 	if (*params == 'I') {
4623 		const std::string sRep = std::to_string(rep);
4624 		sprintf(&tbuff[alen], "%s", sRep.c_str());
4625 	}
4626 	extender->OnMacro("macro", tbuff.c_str());
4627 }
4628 
4629 /**
4630  * Process all the command line arguments.
4631  * Arguments that start with '-' (also '/' on Windows) are switches or commands with
4632  * other arguments being file names which are opened. Commands are distinguished
4633  * from switches by containing a ':' after the command name.
4634  * The print switch /p is special cased.
4635  * Processing occurs in two phases to allow switches that occur before any file opens
4636  * to be evaluated before creating the UI.
4637  * Call twice, first with phase=0, then with phase=1 after creating UI.
4638  */
ProcessCommandLine(const GUI::gui_string & args,int phase)4639 bool SciTEBase::ProcessCommandLine(const GUI::gui_string &args, int phase) {
4640 	bool performPrint = false;
4641 	bool evaluate = phase == 0;
4642 	std::vector<GUI::gui_string> wlArgs = ListFromString(args);
4643 	// Convert args to vector
4644 	for (size_t i = 0; i < wlArgs.size(); i++) {
4645 		const GUI::gui_char *arg = wlArgs[i].c_str();
4646 		if (IsSwitchCharacter(arg[0])) {
4647 			arg++;
4648 			if (arg[0] == '\0' || (arg[0] == '-' && arg[1] == '\0')) {
4649 				if (phase == 1) {
4650 					OpenFromStdin(arg[0] == '-');
4651 				}
4652 			} else if (arg[0] == '@') {
4653 				if (phase == 1) {
4654 					OpenFilesFromStdin();
4655 				}
4656 			} else if ((arg[0] == 'p' || arg[0] == 'P') && (arg[1] == 0)) {
4657 				performPrint = true;
4658 			} else if (GUI::gui_string(arg) == GUI_TEXT("grep") && (wlArgs.size() - i >= 4) && (wlArgs[i+1].size() >= 4)) {
4659 				// in form -grep [w~][c~][d~][b~] "<file-patterns>" "<search-string>"
4660 				GrepFlags gf = grepStdOut;
4661 				if (wlArgs[i+1][0] == 'w')
4662 					gf = static_cast<GrepFlags>(gf | grepWholeWord);
4663 				if (wlArgs[i+1][1] == 'c')
4664 					gf = static_cast<GrepFlags>(gf | grepMatchCase);
4665 				if (wlArgs[i+1][2] == 'd')
4666 					gf = static_cast<GrepFlags>(gf | grepDot);
4667 				if (wlArgs[i+1][3] == 'b')
4668 					gf = static_cast<GrepFlags>(gf | grepBinary);
4669 				std::string sSearch = GUI::UTF8FromString(wlArgs[i+3].c_str());
4670 				std::string unquoted = UnSlashString(sSearch.c_str());
4671 				SA::Position originalEnd = 0;
4672 				InternalGrep(gf, FilePath::GetWorkingDirectory().AsInternal(), wlArgs[i+2].c_str(), unquoted.c_str(), originalEnd);
4673 				exit(0);
4674 			} else {
4675 				if (AfterName(arg) == ':') {
4676 					if (StartsWith(arg, GUI_TEXT("open:")) || StartsWith(arg, GUI_TEXT("loadsession:"))) {
4677 						if (phase == 0)
4678 							return performPrint;
4679 						else
4680 							evaluate = true;
4681 					}
4682 					if (evaluate) {
4683 						const std::string sArg = GUI::UTF8FromString(arg);
4684 						std::vector<char> vcArg(sArg.size() + 1);
4685 						std::copy(sArg.begin(), sArg.end(), vcArg.begin());
4686 						PerformOne(&vcArg[0]);
4687 					}
4688 				} else {
4689 					if (evaluate) {
4690 						props.ReadLine(GUI::UTF8FromString(arg).c_str(), PropSetFile::rlActive,
4691 							       FilePath::GetWorkingDirectory(), filter, nullptr, 0);
4692 					}
4693 				}
4694 			}
4695 		} else {	// Not a switch: it is a file name
4696 			if (phase == 0)
4697 				return performPrint;
4698 			else
4699 				evaluate = true;
4700 
4701 			if (!buffers.initialised) {
4702 				InitialiseBuffers();
4703 				if (props.GetInt("save.recent"))
4704 					RestoreRecentMenu();
4705 			}
4706 
4707 			if (!PreOpenCheck(arg))
4708 				Open(arg, static_cast<OpenFlags>(ofQuiet|ofSynchronous));
4709 		}
4710 	}
4711 	if (phase == 1) {
4712 		// If we have finished with all args and no buffer is open
4713 		// try to load session.
4714 		if (!buffers.initialised) {
4715 			InitialiseBuffers();
4716 			if (props.GetInt("save.recent"))
4717 				RestoreRecentMenu();
4718 			if (props.GetInt("buffers") && props.GetInt("save.session"))
4719 				RestoreSession();
4720 		}
4721 		// No open file after session load so create empty document.
4722 		if (filePath.IsUntitled() && buffers.length == 1 && !buffers.buffers[0].isDirty) {
4723 			Open(FilePath());
4724 		}
4725 	}
4726 	return performPrint;
4727 }
4728 
4729 // Implement ExtensionAPI methods
Send(Pane p,SA::Message msg,uintptr_t wParam,intptr_t lParam)4730 intptr_t SciTEBase::Send(Pane p, SA::Message msg, uintptr_t wParam, intptr_t lParam) {
4731 	if (p == paneEditor)
4732 		return wEditor.Call(msg, wParam, lParam);
4733 	else
4734 		return wOutput.Call(msg, wParam, lParam);
4735 }
Range(Pane p,SA::Range range)4736 std::string SciTEBase::Range(Pane p, SA::Range range) {
4737 	if (p == paneEditor)
4738 		return wEditor.StringOfRange(range);
4739 	else
4740 		return wOutput.StringOfRange(range);
4741 }
Remove(Pane p,SA::Position start,SA::Position end)4742 void SciTEBase::Remove(Pane p, SA::Position start, SA::Position end) {
4743 	if (p == paneEditor) {
4744 		wEditor.DeleteRange(start, end-start);
4745 	} else {
4746 		wOutput.DeleteRange(start, end-start);
4747 	}
4748 }
4749 
Insert(Pane p,SA::Position pos,const char * s)4750 void SciTEBase::Insert(Pane p, SA::Position pos, const char *s) {
4751 	if (p == paneEditor)
4752 		wEditor.InsertText(pos, s);
4753 	else
4754 		wOutput.InsertText(pos, s);
4755 }
4756 
Trace(const char * s)4757 void SciTEBase::Trace(const char *s) {
4758 	ShowOutputOnMainThread();
4759 	OutputAppendStringSynchronised(s);
4760 }
4761 
Property(const char * key)4762 std::string SciTEBase::Property(const char *key) {
4763 	return props.GetExpandedString(key);
4764 }
4765 
SetProperty(const char * key,const char * val)4766 void SciTEBase::SetProperty(const char *key, const char *val) {
4767 	const std::string value = props.GetExpandedString(key);
4768 	if (value != val) {
4769 		props.Set(key, val);
4770 		needReadProperties = true;
4771 	}
4772 }
4773 
UnsetProperty(const char * key)4774 void SciTEBase::UnsetProperty(const char *key) {
4775 	props.Unset(key);
4776 	needReadProperties = true;
4777 }
4778 
GetInstance()4779 uintptr_t SciTEBase::GetInstance() {
4780 	return 0;
4781 }
4782 
ShutDown()4783 void SciTEBase::ShutDown() {
4784 	QuitProgram();
4785 }
4786 
Perform(const char * actionList)4787 void SciTEBase::Perform(const char *actionList) {
4788 	std::vector<char> vActions(actionList, actionList + strlen(actionList) + 1);
4789 	char *actions = &vActions[0];
4790 	char *nextAct;
4791 	while ((nextAct = strchr(actions, '\n')) != nullptr) {
4792 		*nextAct = '\0';
4793 		PerformOne(actions);
4794 		actions = nextAct + 1;
4795 	}
4796 	PerformOne(actions);
4797 }
4798 
DoMenuCommand(int cmdID)4799 void SciTEBase::DoMenuCommand(int cmdID) {
4800 	MenuCommand(cmdID, 0);
4801 }
4802 
PaneCaller(Pane p)4803 SA::ScintillaCall &SciTEBase::PaneCaller(Pane p) noexcept {
4804 	if (p == paneEditor)
4805 		return wEditor;
4806 	else
4807 		return wOutput;
4808 }
4809 
SetFindInFilesOptions()4810 void SciTEBase::SetFindInFilesOptions() {
4811 	const std::string wholeWordName = std::string("find.option.wholeword.") + StdStringFromInteger(wholeWord);
4812 	props.Set("find.wholeword", props.GetNewExpandString(wholeWordName.c_str()));
4813 	const std::string matchCaseName = std::string("find.option.matchcase.") + StdStringFromInteger(matchCase);
4814 	props.Set("find.matchcase", props.GetNewExpandString(matchCaseName.c_str()));
4815 }
4816