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