1 /*
2  * This file is part of the Code::Blocks IDE and licensed under the GNU Lesser General Public License, version 3
3  * http://www.gnu.org/licenses/lgpl-3.0.html
4  *
5  * $Revision: 11970 $
6  * $Id: cbeditor.cpp 11970 2020-02-23 14:25:19Z fuscated $
7  * $HeadURL: svn://svn.code.sf.net/p/codeblocks/code/branches/release-20.xx/src/sdk/cbeditor.cpp $
8  */
9 
10 #include "sdk_precomp.h"
11 
12 #ifndef CB_PRECOMP
13     #include <wx/app.h>
14     #include <wx/filedlg.h>
15     #include <wx/filename.h>
16     #include <wx/menu.h>
17     #include <wx/notebook.h>
18     #include <wx/wfstream.h>
19 
20     #include "cbeditor.h" // class's header file
21 
22     #include "cbauibook.h"
23     #include "cbplugin.h"
24     #include "cbproject.h"
25     #include "configmanager.h"
26     #include "debuggermanager.h"
27     #include "editorcolourset.h"
28     #include "editormanager.h"
29     #include "globals.h"
30     #include "infowindow.h"
31     #include "logmanager.h"
32     #include "macrosmanager.h" // ReplaceMacros
33     #include "manager.h"
34     #include "pluginmanager.h"
35     #include "projectbuildtarget.h"
36     #include "projectfile.h"
37     #include "projectmanager.h"
38     #include "sdk_events.h"
39 #endif
40 #include "cbstyledtextctrl.h"
41 #include "cbcolourmanager.h"
42 
43 #include <stack>
44 
45 #include <wx/fontutil.h>
46 #include <wx/splitter.h>
47 
48 #include "cbeditorprintout.h"
49 #include "cbdebugger_interfaces.h"
50 #include "editor_hooks.h"
51 #include "encodingdetector.h"
52 #include "filefilters.h"
53 #include "projectfileoptionsdlg.h"
54 
55 const wxString g_EditorModified = _T("*");
56 
57 #define ERROR_STYLE      wxSCI_MARK_SMALLRECT
58 #define BOOKMARK_STYLE   wxSCI_MARK_ARROW
59 #define BREAKPOINT_STYLE wxSCI_MARK_CIRCLE
60 #define DEBUG_STYLE      wxSCI_MARK_ARROW
61 #define DEBUG_STYLE_HIGHLIGHT wxSCI_MARK_BACKGROUND
62 
63 #define BREAKPOINT_OTHER_MARKER    1
64 #define BREAKPOINT_DISABLED_MARKER 2
65 #define BREAKPOINT_MARKER          3
66 #define BOOKMARK_MARKER            4
67 #define ERROR_MARKER               5
68 #define DEBUG_MARKER               6
69 #define DEBUG_MARKER_HIGHLIGHT     7
70 
71 #define C_LINE_MARGIN      0 // Line numbers
72 #define C_MARKER_MARGIN    1 // Bookmarks, Breakpoints...
73 #define C_CHANGEBAR_MARGIN 2
74 #define C_FOLDING_MARGIN   3
75 
76 const int foldingMarginBaseWidth = 16;
77 const int changeBarMarginBaseWidth = 4;
78 
79 /* This struct holds private data for the cbEditor class.
80  * It's a paradigm to avoid rebuilding the entire project (as cbEditor is a basic dependency)
81  * for just adding a private var or method.
82  * What happens is that we 've declared a cbEditorInternalData* private member in cbEditor
83  * and define it in the .cpp file (here). Effectively, this means that we can now add/remove
84  * elements from cbEditorInternalData without needing to rebuild the project :)
85  * The cbEditor::m_pData is the variable to use in code. It's the very first thing
86  * constructed and the very last destructed.
87  *
88  * So, if we want to add a new private member in cbEditor, we add it here instead
89  * and access it with m_pData->
90  * e.g. m_pData->lastPosForCodeCompletion
91  * and of course you can add member functions here ;)
92  *
93  * cbEditorInternalData also contains a pointer to the owner cbEditor (named m_pOwner).
94  * Using m_pOwner the struct's member functions can access cbEditor functions
95  * (even private ones - it's a friend).
96  *
97  * The same logic should be used all around the project's classes, gradually.
98  */
99 struct cbEditorInternalData
100 {
101     cbEditor* m_pOwner;
102 
cbEditorInternalDatacbEditorInternalData103     cbEditorInternalData(cbEditor* owner, LoaderBase* fileLoader = nullptr)
104         : m_pOwner(owner),
105         m_strip_trailing_spaces(true),
106         m_ensure_final_line_end(false),
107         m_ensure_consistent_line_ends(true),
108         m_LastMarginMenuLine(-1),
109         m_LastDebugLine(-1),
110         m_useByteOrderMark(false),
111         m_byteOrderMarkLength(0),
112         m_lineNumbersWidth(0),
113         m_lineNumbersWidth2(0),
114         m_pFileLoader(fileLoader)
115     {
116         m_encoding = wxLocale::GetSystemEncoding();
117 
118         if (m_pFileLoader)
119         {
120 #ifdef fileload_measuring
121             wxStopWatch sw;
122 #endif
123             EncodingDetector enc(fileLoader);
124             if (enc.IsOK())
125             {
126                 m_byteOrderMarkLength = enc.GetBOMSizeInBytes();
127                 m_useByteOrderMark    = enc.UsesBOM();
128                 m_encoding            = enc.GetFontEncoding();
129             }
130 #ifdef fileload_measuring
131             Manager::Get()->GetLogManager()->DebugLog(F(_T("Encoding via fileloader took : %d ms"),(int)sw.Time()));
132 #endif
133         }
134     }
135 
~cbEditorInternalDatacbEditorInternalData136     ~cbEditorInternalData()
137     {
138         if (m_pFileLoader)
139         {
140             delete m_pFileLoader;
141             m_pFileLoader = nullptr;
142         }
143     }
144 
145     // funcs
146 
147     /** Strip trailing blanks before saving */
StripTrailingSpacescbEditorInternalData148     void StripTrailingSpaces()
149     {
150         cbStyledTextCtrl* control = m_pOwner->GetControl();
151         // The following code was adapted from the SciTE sourcecode
152 
153         int maxLines = control->GetLineCount();
154         for (int line = 0; line < maxLines; line++)
155         {
156             int lineStart = control->PositionFromLine(line);
157             int lineEnd = control->GetLineEndPosition(line);
158             int i = lineEnd-1;
159             wxChar ch = (wxChar)(control->GetCharAt(i));
160             if (control->GetLexer() == wxSCI_LEX_DIFF)
161                 lineStart++;
162             while ((i >= lineStart) && ((ch == _T(' ')) || (ch == _T('\t'))))
163             {
164                 i--;
165                 ch = (wxChar)(control->GetCharAt(i));
166             }
167             if (i < (lineEnd-1))
168             {
169                 control->SetTargetStart(i+1);
170                 control->SetTargetEnd(lineEnd);
171                 control->ReplaceTarget(_T(""));
172             }
173         }
174     }
175 
176     /** Add extra blank line to the file */
EnsureFinalLineEndcbEditorInternalData177     void EnsureFinalLineEnd()
178     {
179         cbStyledTextCtrl* control = m_pOwner->GetControl();
180         // The following code was adapted from the SciTE sourcecode
181         int maxLines = control->GetLineCount();
182         int enddoc = control->PositionFromLine(maxLines);
183         if (maxLines <= 1 || enddoc > control->PositionFromLine(maxLines-1))
184             control->InsertText(enddoc, GetEOLStr(m_pOwner->GetControl()->GetEOLMode()));
185     }
186 
187     /** Make sure all the lines end with the same EOL mode */
EnsureConsistentLineEndscbEditorInternalData188     void EnsureConsistentLineEnds()
189     {
190         cbStyledTextCtrl* control = m_pOwner->GetControl();
191         // The following code was adapted from the SciTE sourcecode
192         control->ConvertEOLs(control->GetEOLMode());
193     }
194 
195     /** Set line number column width */
SetLineNumberColWidthcbEditorInternalData196     void SetLineNumberColWidth(bool both=true)
197     {
198         ConfigManager* cfg = Manager::Get()->GetConfigManager(_T("editor"));
199 
200         if (cfg->ReadBool(_T("/show_line_numbers"), true))
201         {
202             if (m_pOwner->m_pControl2 && both)
203             {
204                 int pixelWidth = m_pOwner->m_pControl->TextWidth(wxSCI_STYLE_LINENUMBER, _T("9"));
205                 int pixelWidth2 = m_pOwner->m_pControl2->TextWidth(wxSCI_STYLE_LINENUMBER, _T("9"));
206 
207                 if (cfg->ReadBool(_T("/margin/dynamic_width"), false))
208                 {
209                     int lineNumChars = 1;
210                     int lineCount = m_pOwner->m_pControl->GetLineCount();
211 
212                     while (lineCount >= 10)
213                     {
214                         lineCount /= 10;
215                         ++lineNumChars;
216                     }
217 
218                     int lineNumWidth =  lineNumChars * pixelWidth + pixelWidth * 0.75;
219 
220                     if (lineNumWidth != m_lineNumbersWidth)
221                     {
222                         m_pOwner->m_pControl->SetMarginWidth(C_LINE_MARGIN, lineNumWidth);
223                         m_lineNumbersWidth = lineNumWidth;
224                     }
225 
226                     lineNumWidth =  lineNumChars * pixelWidth2 + pixelWidth2 * 0.75;
227                     if (lineNumWidth != m_lineNumbersWidth2)
228                     {
229                         m_pOwner->m_pControl2->SetMarginWidth(C_LINE_MARGIN, lineNumWidth);
230                         m_lineNumbersWidth2 = lineNumWidth;
231                     }
232                 }
233                 else
234                 {
235                     const int charsWidth = cfg->ReadInt(_T("/margin/width_chars"), 6);
236                     const int newWidth = pixelWidth * (0.75 + charsWidth);
237                     m_pOwner->m_pControl->SetMarginWidth(C_LINE_MARGIN, newWidth);
238                     m_pOwner->m_pControl2->SetMarginWidth(C_LINE_MARGIN, newWidth);
239                 }
240             }
241             else
242             {
243                 cbStyledTextCtrl* control = m_pOwner->GetControl();
244                 int* pLineNumbersWidth = nullptr;
245                 if (control == m_pOwner->m_pControl)
246                     pLineNumbersWidth = &m_lineNumbersWidth;
247                 else
248                     pLineNumbersWidth = &m_lineNumbersWidth2;
249 
250                 int pixelWidth = control->TextWidth(wxSCI_STYLE_LINENUMBER, _T("9"));
251 
252                 if (cfg->ReadBool(_T("/margin/dynamic_width"), false))
253                 {
254                     int lineNumChars = 1;
255                     int lineCount = control->GetLineCount();
256 
257                     while (lineCount >= 10)
258                     {
259                         lineCount /= 10;
260                         ++lineNumChars;
261                     }
262 
263                     int lineNumWidth =  lineNumChars * pixelWidth + pixelWidth * 0.75;
264 
265                     if (lineNumWidth != *pLineNumbersWidth)
266                     {
267                         control->SetMarginWidth(C_LINE_MARGIN, lineNumWidth);
268                         *pLineNumbersWidth = lineNumWidth;
269                     }
270                 }
271                 else
272                 {
273                     const int charsWidth = cfg->ReadInt(_T("/margin/width_chars"), 6);
274                     const int newWidth = pixelWidth * (0.75 + charsWidth);
275                     control->SetMarginWidth(C_LINE_MARGIN, newWidth);
276                 }
277             }
278         }
279         else
280         {
281             m_pOwner->m_pControl->SetMarginWidth(C_LINE_MARGIN, 0);
282             if (m_pOwner->m_pControl2 && both)
283                 m_pOwner->m_pControl2->SetMarginWidth(C_LINE_MARGIN, 0);
284         }
285     }
286 
CalcWidthcbEditorInternalData287     static int CalcWidth(cbStyledTextCtrl* control, int baseWidth, int minWidth,
288                          float defaultPointSize)
289     {
290         int width = baseWidth * (defaultPointSize + control->GetZoom()) / defaultPointSize;
291         if (width < minWidth)
292             width = minWidth;
293         return width;
294     }
295 
296     /// Call this only if the margin with marginId is enabled and is visible.
SetColumnWidthcbEditorInternalData297     void SetColumnWidth(int marginId, int baseWidth, int minWidth, bool both)
298     {
299         const float pointSize = m_pOwner->m_pControl->StyleGetFont(wxSCI_STYLE_DEFAULT).GetPointSize();
300         if (both)
301         {
302             const int width = CalcWidth(m_pOwner->m_pControl, baseWidth, minWidth, pointSize);
303             m_pOwner->m_pControl->SetMarginWidth(marginId, width);
304             if (m_pOwner->m_pControl2)
305             {
306                 const int width = CalcWidth(m_pOwner->m_pControl2, baseWidth, minWidth, pointSize);
307                 m_pOwner->m_pControl2->SetMarginWidth(marginId, width);
308             }
309         }
310         else
311         {
312             cbStyledTextCtrl* control = m_pOwner->GetControl();
313             const int width = CalcWidth(control, baseWidth, minWidth, pointSize);
314             control->SetMarginWidth(marginId, width);
315         }
316     }
317 
SetMarkerWidthcbEditorInternalData318     static void SetMarkerWidth(cbStyledTextCtrl* control, float defaultPointSize)
319     {
320         // Leave 15% for borders.
321         const float scale = 1.0f/1.15f;
322 
323         const int oldWidth = floorf(control->GetMarginWidth(C_MARKER_MARGIN) * scale);
324         const int newWidth = CalcWidth(control, 18, 1, defaultPointSize);
325 
326         const int width = floorf(newWidth * scale);
327 
328         const int possibleWidths[] = { 8, 10, 12, 16, 20, 24, 28, 32, 40, 48, 56, 64 };
329 
330         const int selectedWidth = cbFindMinSize(width, possibleWidths, cbCountOf(possibleWidths));
331         const int oldSelectedWidth = cbFindMinSize(oldWidth, possibleWidths, cbCountOf(possibleWidths));
332 
333 
334         // We don't want to reload images if the width haven't changed!
335         if (selectedWidth != oldSelectedWidth)
336         {
337             control->SetMarginWidth(C_MARKER_MARGIN, newWidth);
338             SetupBreakpointMarkers(control, selectedWidth);
339         }
340     }
341 
SetMarkerColumnWidthcbEditorInternalData342     void SetMarkerColumnWidth(bool both)
343     {
344         const float pointSize = m_pOwner->m_pControl->StyleGetFont(wxSCI_STYLE_DEFAULT).GetPointSize();
345         if (both)
346         {
347             SetMarkerWidth(m_pOwner->m_pControl, pointSize);
348             if (m_pOwner->m_pControl2)
349                 SetMarkerWidth(m_pOwner->m_pControl2, pointSize);
350         }
351         else
352             SetMarkerWidth(m_pOwner->GetControl(), pointSize);
353     }
354 
SetupBreakpointMarkerscbEditorInternalData355     static void SetupBreakpointMarkers(cbStyledTextCtrl* control, int size)
356     {
357         wxString basepath = ConfigManager::GetDataFolder() + wxT("/manager_resources.zip#zip:/images/");
358         basepath += wxString::Format(wxT("%dx%d/"), size, size);
359         ConfigManager* mgr = Manager::Get()->GetConfigManager(_T("editor"));
360         bool imageBP = mgr->ReadBool(_T("/margin_1_image_bp"), true);
361         if (imageBP)
362         {
363             wxBitmap iconBP    = cbLoadBitmap(basepath + wxT("breakpoint.png"),          wxBITMAP_TYPE_PNG);
364             wxBitmap iconBPDis = cbLoadBitmap(basepath + wxT("breakpoint_disabled.png"), wxBITMAP_TYPE_PNG);
365             wxBitmap iconBPOth = cbLoadBitmap(basepath + wxT("breakpoint_other.png"),    wxBITMAP_TYPE_PNG);
366             if (iconBP.IsOk() && iconBPDis.IsOk() && iconBPOth.IsOk())
367             {
368                 control->MarkerDefineBitmap(BREAKPOINT_MARKER,          iconBP   );
369                 control->MarkerDefineBitmap(BREAKPOINT_DISABLED_MARKER, iconBPDis);
370                 control->MarkerDefineBitmap(BREAKPOINT_OTHER_MARKER,    iconBPOth);
371             }
372             else
373                 imageBP = false; // apply default markers
374         }
375         if (!imageBP)
376         {
377             control->MarkerDefine(BREAKPOINT_MARKER,                 BREAKPOINT_STYLE);
378             control->MarkerSetBackground(BREAKPOINT_MARKER,          wxColour(0xFF, 0x00, 0x00));
379             control->MarkerDefine(BREAKPOINT_DISABLED_MARKER,        BREAKPOINT_STYLE);
380             control->MarkerSetBackground(BREAKPOINT_DISABLED_MARKER, wxColour(0x90, 0x90, 0x90));
381             control->MarkerDefine(BREAKPOINT_OTHER_MARKER,           BREAKPOINT_STYLE);
382             control->MarkerSetBackground(BREAKPOINT_OTHER_MARKER,    wxColour(0x59, 0x74, 0x8e));
383         }
384     }
385 
GetUrlcbEditorInternalData386     wxString GetUrl()
387     {
388         cbStyledTextCtrl* control = m_pOwner->GetControl();
389         if (!control)
390             return wxEmptyString;
391 
392         wxRegEx reUrl(wxT("***:("
393                                 "((ht|f)tp(s?)\\:\\/\\/)"
394                                 "|(www\\.)"
395                               ")"
396                               "("
397                                 "([\\w\\-]+(\\.[\\w\\-]+)+)"
398                                 "|localhost"
399                               ")"
400                               "(\\/?)([\\w\\-\\.\\?\\,\\'\\/\\\\\\+&amp;%\\$#]*)?"
401                               "([\\d\\w\\.\\/\\%\\+\\-\\=\\&amp;\\?\\:\\\\\\&quot;\\'\\,\\|\\~\\;]*)"));
402         wxString url = control->GetSelectedText();
403         // Is the URL selected?
404         if (reUrl.Matches(url))
405             return reUrl.GetMatch(url);
406         // else is there a URL near the cursor?
407 
408         // Find out start position
409         int startPos = control->GetCurrentPos();
410         const wxString space = wxT(" \n\r\t{}");
411         wxChar curCh = control->GetCharAt(startPos);
412         while ( (startPos > 0) && (space.Find(curCh) == -1) )
413         {
414             startPos--;
415             curCh = control->GetCharAt(startPos);
416         }
417 
418         // Find out end position
419         int endPos = control->GetCurrentPos();
420         int maxPos = control->GetLineEndPosition(control->GetLineCount());
421         curCh = control->GetCharAt(endPos);
422         while ( (endPos < maxPos) && (space.Find(curCh) == -1) )
423         {
424             endPos++;
425             curCh = control->GetCharAt(endPos);
426         }
427 
428         url = control->GetTextRange(startPos, endPos);
429         if (    (control->GetLexer() == wxSCI_LEX_CPP)
430             &&  (   (control->GetStyleAt(control->GetCurrentPos()) == wxSCI_C_STRING)
431                  || (control->GetStyleAt(control->GetCurrentPos()) == wxSCI_C_STRINGEOL) ) )
432         {
433             url.Replace(wxT("\\n"), wxT("\n"));
434             url.Replace(wxT("\\r"), wxT("\r"));
435             url.Replace(wxT("\\t"), wxT("\t"));
436         }
437 
438         if (reUrl.Matches(url))
439         {
440             wxString match = reUrl.GetMatch(url);
441             if (   (url.Find(match) + startPos                       < control->GetCurrentPos())
442                 && (url.Find(match) + startPos + (int)match.Length() > control->GetCurrentPos()) )
443             {
444                 url = match(0, match.find_last_not_of(wxT(",.")) + 1); // trim trailing
445             }
446             else
447                 url = wxEmptyString; // nope, too far from cursor, return invalid (empty)
448         }
449         else
450             url = wxEmptyString; // nope, return invalid (empty)
451 
452         return url;
453     }
454 
GetLineIndentStringcbEditorInternalData455     static wxString GetLineIndentString(int line, cbStyledTextCtrl* stc)
456     {
457         int currLine = (line == -1)
458                         ? stc->LineFromPosition(stc->GetCurrentPos())
459                         : line;
460         wxString text = stc->GetLine(currLine);
461         unsigned int len = text.Length();
462         wxString indent;
463         for (unsigned int i = 0; i < len; ++i)
464         {
465             if (text[i] == _T(' ') || text[i] == _T('\t'))
466             {
467                 indent << text[i];
468             }
469             else
470             {
471                 break;
472             }
473         }
474         return indent;
475     }
476 
477     /** Detect the indentation style used in a document.
478      * Values for the thresholds are from trial and error over many
479      * example files. If this function is not certain on the style
480      * used, it will return -1.
481      */
DetectIndentStylecbEditorInternalData482     static int DetectIndentStyle(cbStyledTextCtrl* stc)
483     {
484         int lineCount[] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
485         // lineCount[0] == number of lines with tabs
486         // lineCount[1] == number of lines with only spaces
487         // lineCount[2 ... 8] == number of lines divisible by that number
488         // Scan 1000 lines from the middle of the file to generate statistics
489         const int maxLine = std::min(stc->GetLineCount(), stc->GetLineCount() / 2 + 500);
490         for (int line = std::max(0, stc->GetLineCount() / 2 - 500); line < maxLine; ++line)
491         {
492             const wxString& indent = cbEditorInternalData::GetLineIndentString(line, stc);
493             if (indent.IsEmpty())
494                 continue;
495             if (indent.Find(wxT('\t')) != wxNOT_FOUND)
496                 ++lineCount[0];
497             else
498             {
499                 ++lineCount[1];
500                 for (int i = 2; i < 9; ++i)
501                 {
502                     if (indent.Length() % i == 0)
503                         ++lineCount[i];
504                 }
505             }
506         }
507 
508         if (lineCount[0] > 0 && lineCount[1] == 0)
509             return 0;  // tabs
510         double total = lineCount[0] + lineCount[1];
511         if (total < 10)
512             return -1; // not sure -> use defaults
513         else if (lineCount[0] / total > 0.75)
514             return 0;  // tabs
515         else if (lineCount[1] / total < 0.75)
516             return -1; // not sure -> use defaults
517 
518         total = lineCount[1];
519         int tabSize = 8;
520         for (int i = 2; i < 8; ++i)
521         {
522             if (lineCount[i] > lineCount[tabSize])
523                 tabSize = i;
524         }
525         if (lineCount[tabSize] / total < 0.65)
526             return -1; // not sure -> use defaults
527 
528         switch (tabSize)
529         {
530             case 2:
531                 if ((lineCount[2] - lineCount[6]) / total < 0.1)
532                     return 6;
533                 if (   lineCount[2] > lineCount[4] * 1.8
534                     || lineCount[4] / total < 0.5 )
535                     return 2;
536                 // fall through
537             case 4:
538                 if (   lineCount[4] > lineCount[8] * 1.8
539                     || lineCount[8] / total < 0.5 )
540                     return 4;
541                 // fall through
542             case 8:
543                 if (lineCount[8] / total < 0.6)
544                     return -1; // not sure -> use defaults
545                 return 8;
546 
547             case 3:
548                 if (   lineCount[3] > lineCount[6] * 1.8
549                     || lineCount[6] / total < 0.5 )
550                     return 3;
551                 if (lineCount[6] / total < 0.6)
552                     return -1; // not sure -> use defaults
553                 return 6;
554 
555             default:
556                 if (lineCount[tabSize] / total < 0.7)
557                     return -1; // not sure -> use defaults
558                 return tabSize;
559         }
560     }
561 
562     // vars
563     bool m_strip_trailing_spaces;
564     bool m_ensure_final_line_end;
565     bool m_ensure_consistent_line_ends;
566 
567     int m_LastMarginMenuLine;
568     int m_LastDebugLine;
569 
570     bool mFoldingLimit;
571     int mFoldingLimitLevel;
572 
573     wxFontEncoding m_encoding;
574     bool m_useByteOrderMark;
575     int m_byteOrderMarkLength;
576 
577     int m_lineNumbersWidth;
578     int m_lineNumbersWidth2;
579 
580     LoaderBase* m_pFileLoader;
581 };
582 ////////////////////////////////////////////////////////////////////////////////
583 
584 const int idEmptyMenu = wxNewId();
585 const int idEdit = wxNewId();
586 const int idUndo = wxNewId();
587 const int idRedo = wxNewId();
588 const int idClearHistory = wxNewId();
589 const int idCut = wxNewId();
590 const int idCopy = wxNewId();
591 const int idPaste = wxNewId();
592 const int idDelete = wxNewId();
593 const int idUpperCase = wxNewId();
594 const int idLowerCase = wxNewId();
595 const int idSelectAll = wxNewId();
596 const int idSwapHeaderSource = wxNewId();
597 const int idOpenContainingFolder = wxNewId();
598 const int idBookmarks = wxNewId();
599 const int idBookmarksToggle = wxNewId();
600 const int idBookmarksPrevious = wxNewId();
601 const int idBookmarksNext = wxNewId();
602 const int idBookmarksClearAll = wxNewId();
603 const int idFolding = wxNewId();
604 const int idFoldingFoldAll = wxNewId();
605 const int idFoldingUnfoldAll = wxNewId();
606 const int idFoldingToggleAll = wxNewId();
607 const int idFoldingFoldCurrent = wxNewId();
608 const int idFoldingUnfoldCurrent = wxNewId();
609 const int idFoldingToggleCurrent = wxNewId();
610 const int idInsert = wxNewId();
611 const int idSplit = wxNewId();
612 const int idSplitHorz = wxNewId();
613 const int idSplitVert = wxNewId();
614 const int idUnsplit = wxNewId();
615 const int idProperties = wxNewId();
616 const int idAddFileToProject = wxNewId();
617 const int idRemoveFileFromProject = wxNewId();
618 const int idShowFileInProject = wxNewId();
619 const int idOpenUrl = wxNewId();
620 
621 const int idBookmarkAdd = wxNewId();
622 const int idBookmarkRemove = wxNewId();
623 const int idBookmarkRemoveAll = wxNewId();
624 
625 const int idBreakpointAdd = wxNewId();
626 const int idBreakpointEdit = wxNewId();
627 const int idBreakpointRemove = wxNewId();
628 const long idBreakpointEnable = wxNewId();
629 const long idBreakpointDisable = wxNewId();
630 
BEGIN_EVENT_TABLE(cbEditor,EditorBase)631 BEGIN_EVENT_TABLE(cbEditor, EditorBase)
632     EVT_CLOSE(cbEditor::OnClose)
633     // we got dynamic events; look in ConnectEvents()
634 
635     EVT_MENU(idUndo, cbEditor::OnContextMenuEntry)
636     EVT_MENU(idRedo, cbEditor::OnContextMenuEntry)
637     EVT_MENU(idClearHistory, cbEditor::OnContextMenuEntry)
638     EVT_MENU(idCut, cbEditor::OnContextMenuEntry)
639     EVT_MENU(idCopy, cbEditor::OnContextMenuEntry)
640     EVT_MENU(idPaste, cbEditor::OnContextMenuEntry)
641     EVT_MENU(idDelete, cbEditor::OnContextMenuEntry)
642     EVT_MENU(idUpperCase, cbEditor::OnContextMenuEntry)
643     EVT_MENU(idLowerCase, cbEditor::OnContextMenuEntry)
644     EVT_MENU(idSelectAll, cbEditor::OnContextMenuEntry)
645     EVT_MENU(idSwapHeaderSource, cbEditor::OnContextMenuEntry)
646     EVT_MENU(idOpenContainingFolder, cbEditor::OnContextMenuEntry)
647     EVT_MENU(idBookmarksToggle, cbEditor::OnContextMenuEntry)
648     EVT_MENU(idBookmarksPrevious, cbEditor::OnContextMenuEntry)
649     EVT_MENU(idBookmarksNext, cbEditor::OnContextMenuEntry)
650     EVT_MENU(idBookmarksClearAll, cbEditor::OnContextMenuEntry)
651     EVT_MENU(idFoldingFoldAll, cbEditor::OnContextMenuEntry)
652     EVT_MENU(idFoldingUnfoldAll, cbEditor::OnContextMenuEntry)
653     EVT_MENU(idFoldingToggleAll, cbEditor::OnContextMenuEntry)
654     EVT_MENU(idFoldingFoldCurrent, cbEditor::OnContextMenuEntry)
655     EVT_MENU(idFoldingUnfoldCurrent, cbEditor::OnContextMenuEntry)
656     EVT_MENU(idFoldingToggleCurrent, cbEditor::OnContextMenuEntry)
657     EVT_MENU(idProperties, cbEditor::OnContextMenuEntry)
658     EVT_MENU(idAddFileToProject, cbEditor::OnContextMenuEntry)
659     EVT_MENU(idRemoveFileFromProject, cbEditor::OnContextMenuEntry)
660     EVT_MENU(idShowFileInProject, cbEditor::OnContextMenuEntry)
661     EVT_MENU(idBookmarkAdd, cbEditor::OnContextMenuEntry)
662     EVT_MENU(idBookmarkRemove, cbEditor::OnContextMenuEntry)
663     EVT_MENU(idBookmarkRemoveAll, cbEditor::OnContextMenuEntry)
664     EVT_MENU(idBreakpointAdd, cbEditor::OnContextMenuEntry)
665     EVT_MENU(idBreakpointEdit, cbEditor::OnContextMenuEntry)
666     EVT_MENU(idBreakpointRemove, cbEditor::OnContextMenuEntry)
667     EVT_MENU(idBreakpointEnable, cbEditor::OnContextMenuEntry)
668     EVT_MENU(idBreakpointDisable, cbEditor::OnContextMenuEntry)
669     EVT_MENU(idSplitHorz, cbEditor::OnContextMenuEntry)
670     EVT_MENU(idSplitVert, cbEditor::OnContextMenuEntry)
671     EVT_MENU(idUnsplit, cbEditor::OnContextMenuEntry)
672     EVT_MENU(idOpenUrl, cbEditor::OnContextMenuEntry)
673 
674     EVT_SCI_ZOOM(-1, cbEditor::OnZoom)
675     EVT_SCI_ZOOM(-1, cbEditor::OnZoom)
676 
677 END_EVENT_TABLE()
678 
679 // Count lines of EOL style in the opened file
680 static void CountLineEnds(cbStyledTextCtrl* control, int &linesCR, int &linesLF, int &linesCRLF)
681 {
682     linesCR = 0;
683     linesLF = 0;
684     linesCRLF = 0;
685 
686     int lengthDoc = control->GetLength();
687     const int maxLengthDoc = 1000000;
688     char chPrev = ' ';
689     char chNext = control->GetCharAt(0);
690     for (int i = 0; i < lengthDoc; i++)
691     {
692         char ch = chNext;
693         chNext = control->GetCharAt(i + 1);
694         if (ch == '\r')
695         {
696             if (chNext == '\n')
697                 linesCRLF++;
698             else
699                 linesCR++;
700         }
701         else if (ch == '\n')
702         {
703             if (chPrev != '\r')
704                 linesLF++;
705         }
706         else if (i > maxLengthDoc)     // stop the loop if the file contains too many characters
707             return;
708 
709         chPrev = ch;
710     }
711 }
712 
713 // Detect the EOL mode of the control. If a file has mixed EOLs, we will using the voting
714 // logic, and give user a InfoWindow notification.
DetectLineEnds(cbStyledTextCtrl * control)715 static int DetectLineEnds(cbStyledTextCtrl* control)
716 {
717     int eolMode;
718     wxString eolModeStr;
719     // initial EOL mode depend on OS
720     if (platform::windows)
721     {
722         eolMode =  wxSCI_EOL_CRLF;
723         eolModeStr = _T("\"CR-LF\"");
724     }
725     else
726     {
727         eolMode =  wxSCI_EOL_LF;
728         eolModeStr = _T("\"LF\"");
729     }
730 
731     int linesCR;
732     int linesLF;
733     int linesCRLF;
734     // count lines of each EOL style
735     CountLineEnds(control, linesCR, linesLF, linesCRLF);
736 
737     // voting logic
738     // if the file does not contain any line-feed or the most largest counts are equal( e.g.: linesLF=5,
739     // linesCRLF=5, linesCR=0 ), then we will use the initial EOL mode
740     if ( (linesLF > linesCR) && (linesLF > linesCRLF) )
741     {
742         eolMode = wxSCI_EOL_LF;
743         eolModeStr = _T("\"LF\"");
744     }
745     else if ( (linesCR > linesLF) && (linesCR > linesCRLF) )
746     {
747         eolMode = wxSCI_EOL_CR;
748         eolModeStr = _T("\"CR\"");
749     }
750     else if ( (linesCRLF > linesLF) && (linesCRLF > linesCR))
751     {
752         eolMode = wxSCI_EOL_CRLF;
753         eolModeStr = _T("\"CR-LF\"");
754     }
755 
756     unsigned int delay = 2000;
757     if (  ( (linesCR>0) && (linesCRLF>0) )
758        || ( (linesLF>0) && (linesCRLF>0) )
759        || ( (linesCR>0) && (linesLF>0) ) )
760     {
761         //In mixed EOL file, give the user a beep and InfoWindow notification.
762         wxBell();
763         InfoWindow::Display(_("Mixed Line Endings"), _("Mixed line endings found, setting mode ") + eolModeStr, delay);
764     }
765     return eolMode;
766 }
767 
768 // class constructor
cbEditor(wxWindow * parent,const wxString & filename,EditorColourSet * theme)769 cbEditor::cbEditor(wxWindow* parent, const wxString& filename, EditorColourSet* theme)
770     : EditorBase(parent, filename),
771     m_pSplitter(nullptr),
772     m_pSizer(nullptr),
773     m_pControl(nullptr),
774     m_pControl2(nullptr),
775     m_foldBackup(nullptr),
776     m_SplitType(stNoSplit),
777     m_Modified(false),
778     m_Index(-1),
779     m_pProjectFile(nullptr),
780     m_pTheme(theme),
781     m_lang(HL_AUTO)
782 {
783     DoInitializations(filename);
784 }
785 
786 // class constructor
cbEditor(wxWindow * parent,LoaderBase * fileLdr,const wxString & filename,EditorColourSet * theme)787 cbEditor::cbEditor(wxWindow* parent, LoaderBase* fileLdr, const wxString& filename, EditorColourSet* theme)
788     : EditorBase(parent, filename),
789     m_pSplitter(nullptr),
790     m_pSizer(nullptr),
791     m_pControl(nullptr),
792     m_pControl2(nullptr),
793     m_foldBackup(nullptr),
794     m_SplitType(stNoSplit),
795     m_Modified(false),
796     m_Index(-1),
797     m_pProjectFile(nullptr),
798     m_pTheme(theme),
799     m_lang(HL_AUTO)
800 {
801     DoInitializations(filename, fileLdr);
802 }
803 
804 // class destructor
~cbEditor()805 cbEditor::~cbEditor()
806 {
807     SetSizer(nullptr);
808 
809     // moved in ~EditorBase
810 //    NotifyPlugins(cbEVT_EDITOR_CLOSE, 0, m_Filename);
811 
812     UpdateProjectFile();
813     if (m_pControl)
814     {
815         if (m_pProjectFile)
816             m_pProjectFile->editorOpen = false;
817         m_pControl->Destroy();
818         m_pControl = nullptr;
819     }
820     DestroySplitView();
821 
822     delete m_pData;
823 }
824 
DoInitializations(const wxString & filename,LoaderBase * fileLdr)825 void cbEditor::DoInitializations(const wxString& filename, LoaderBase* fileLdr)
826 {
827     // first thing to do!
828     // if we add more constructors in the future, don't forget to set this!
829     m_pData = new cbEditorInternalData(this);//, fileLdr);
830     m_pData->m_pFileLoader = fileLdr;
831     m_IsBuiltinEditor = true;
832 
833     if (!filename.IsEmpty())
834     {
835         InitFilename(filename);
836         wxFileName fname(m_Filename);
837         NormalizePath(fname, wxEmptyString);
838         m_Filename = fname.GetFullPath();
839     }
840     else
841     {
842         static int untitledCounter = 1;
843         wxString f;
844         cbProject* prj = Manager::Get()->GetProjectManager()->GetActiveProject();
845         if (prj)
846             f.Printf(_("%sUntitled%d"), prj->GetBasePath().c_str(), untitledCounter++);
847         else
848             f.Printf(_("Untitled%d"), untitledCounter++);
849 
850         InitFilename(f);
851     }
852 //    Manager::Get()->GetLogManager()->DebugLog(_T("ctor: Filename=%s\nShort=%s"), m_Filename.c_str(), m_Shortname.c_str());
853 
854     // initialize left control (unsplit state)
855     m_pSizer = new wxBoxSizer(wxVERTICAL);
856     m_pControl = CreateEditor();
857     m_pSizer->Add(m_pControl, 1, wxEXPAND);
858     SetSizer(m_pSizer);
859 
860     // the following two lines make the editors behave strangely in linux:
861     // when resizing other docked windows, the editors do NOT resize too
862     // and they stame the same size...
863     // if commenting the following two lines causes problems in other platforms,
864     // simply put an "#ifdef __WXGTK__" guard around and uncomment them.
865 //    m_pSizer->Fit(this);
866 //    m_pSizer->SetSizeHints(this);
867 
868     m_pSizer->SetItemMinSize(m_pControl, 32, 32);
869 
870     // by default we show no markers, marginMasks are set explicitly in "InternalSetEditorStyleBeforeFileOpen()"
871     // and/or by plugins, that use markers, like browsemarks-plugin
872     m_pControl->SetMarginMask(C_LINE_MARGIN,      0);
873     m_pControl->SetMarginMask(C_MARKER_MARGIN,    0);
874     m_pControl->SetMarginMask(C_CHANGEBAR_MARGIN, 0);
875     m_pControl->SetMarginMask(C_FOLDING_MARGIN,   0);
876 
877     SetEditorStyleBeforeFileOpen();
878     m_IsOK = Open();
879     SetEditorStyleAfterFileOpen();
880     if (Manager::Get()->GetConfigManager(_T("editor"))->ReadBool(_T("/folding/fold_all_on_open"), false))
881         FoldAll();
882 
883     // if !m_IsOK then it's a new file, so set the modified flag ON
884     if (!m_IsOK || filename.IsEmpty())
885     {
886         SetModified(true);
887         m_IsOK = false;
888     }
889     ConnectEvents(m_pControl);
890 }
891 
NotifyPlugins(wxEventType type,int intArg,const wxString & strArg,int xArg,int yArg)892 void cbEditor::NotifyPlugins(wxEventType type, int intArg, const wxString& strArg, int xArg, int yArg)
893 {
894     if (!Manager::Get()->GetPluginManager())
895         return; // no plugin manager! app shutting down?
896     CodeBlocksEvent event(type);
897     event.SetEditor(this);
898     event.SetInt(intArg);
899     event.SetString(strArg);
900     event.SetX(xArg);
901     event.SetY(yArg);
902     if (m_pProjectFile)
903         event.SetProject(m_pProjectFile->GetParentProject());
904     //wxPostEvent(Manager::Get()->GetAppWindow(), event);
905     Manager::Get()->GetPluginManager()->NotifyPlugins(event);
906 }
907 
DestroySplitView()908 void cbEditor::DestroySplitView()
909 {
910     if (m_pControl2)
911     {
912         m_pControl2->Destroy();
913         m_pControl2 = nullptr;
914     }
915     if (m_pSplitter)
916     {
917         m_pSplitter->Destroy();
918         m_pSplitter = nullptr;
919     }
920 }
921 
GetControl() const922 cbStyledTextCtrl* cbEditor::GetControl() const
923 {
924     // return the last focused control (left or right)
925     if (m_pControl2)
926     {
927         // every time a control gets the focus it stores the actual timestamp, the timestamp defaults to 0 so the
928         // greater is the timestamp of the control that had the focus last time
929         // finding the focused window does not work if another control has the keyboard-focus
930         if ( m_pControl2->GetLastFocusTime() > m_pControl->GetLastFocusTime() )
931             return m_pControl2;
932     }
933     return m_pControl;
934 }
935 
GetModified() const936 bool cbEditor::GetModified() const
937 {
938     return m_Modified || m_pControl->GetModify();
939 }
940 
SetModified(bool modified)941 void cbEditor::SetModified(bool modified)
942 {
943     if (modified != m_Modified)
944     {
945         m_Modified = modified;
946         if (!m_Modified)
947             m_pControl->SetSavePoint();
948 
949         SetEditorTitle(m_Shortname);
950         NotifyPlugins(cbEVT_EDITOR_MODIFIED);
951         // visual state
952         if (m_pProjectFile)
953             m_pProjectFile->SetFileState(m_pControl->GetReadOnly() ? fvsReadOnly : (m_Modified ? fvsModified : fvsNormal));
954     }
955 }
956 
SetEditorTitle(const wxString & title)957 void cbEditor::SetEditorTitle(const wxString& title)
958 {
959     if (m_Modified)
960         SetTitle(g_EditorModified + title);
961     else
962         SetTitle(title);
963 }
964 
SetProjectFile(ProjectFile * project_file,bool preserve_modified)965 void cbEditor::SetProjectFile(ProjectFile* project_file, bool preserve_modified)
966 {
967     if (m_pProjectFile == project_file)
968         return; // we 've been here before ;)
969 
970     bool wasmodified = false;
971     if (preserve_modified)
972         wasmodified = GetModified();
973 
974     m_pProjectFile = project_file;
975     if (m_pProjectFile)
976     {
977         // update our filename
978         m_Filename = UnixFilename(project_file->file.GetFullPath());
979 
980         m_pControl->GotoPos(m_pProjectFile->editorPos);
981         m_pControl->ScrollToLine(m_pProjectFile->editorTopLine);
982         m_pControl->ScrollToColumn(0);
983         m_pControl->SetZoom(m_pProjectFile->editorZoom);
984         if (m_pProjectFile->editorSplit != (int)stNoSplit)
985         {
986             Split((SplitType)m_pProjectFile->editorSplit);
987             if (m_pControl2)
988             {
989                 m_pSplitter->SetSashPosition(m_pProjectFile->editorSplitPos);
990                 m_pControl2->GotoPos(m_pProjectFile->editorPos_2);
991                 m_pControl2->ScrollToLine(m_pProjectFile->editorTopLine_2);
992                 m_pControl2->ScrollToColumn(0);
993                 m_pControl2->SetZoom(m_pProjectFile->editorZoom_2);
994             }
995         }
996 
997         m_pData->SetLineNumberColWidth();
998 
999         if ( Manager::Get()->GetConfigManager(_T("editor"))->ReadBool(_T("/folding/show_folds"), true) )
1000         {
1001             for (unsigned int i = 0; i < m_pProjectFile->editorFoldLinesArray.GetCount(); i++)
1002                 m_pControl->ToggleFold(m_pProjectFile->editorFoldLinesArray[i]);
1003         }
1004 
1005         m_pProjectFile->editorOpen = true;
1006 
1007         if (Manager::Get()->GetConfigManager(_T("editor"))->ReadBool(_T("/tab_text_relative"), true))
1008             m_Shortname = m_pProjectFile->relativeToCommonTopLevelPath;
1009         else
1010             m_Shortname = m_pProjectFile->file.GetFullName();
1011         SetEditorTitle(m_Shortname);
1012 
1013         if (!wxFileExists(m_Filename))
1014             m_pProjectFile->SetFileState(fvsMissing);
1015         else if (!wxFile::Access(m_Filename.c_str(), wxFile::write)) // readonly
1016             m_pProjectFile->SetFileState(fvsReadOnly);
1017     }
1018 
1019 #if 0
1020     wxString dbg;
1021     dbg << _T("[ed] Filename: ") << GetFilename() << _T('\n');
1022     dbg << _T("[ed] Short name: ") << GetShortName() << _T('\n');
1023     dbg << _T("[ed] Modified: ") << GetModified() << _T('\n');
1024     dbg << _T("[ed] Project: ") << ((m_pProjectFile && m_pProjectFile->project) ? m_pProjectFile->project->GetTitle() : _T("unknown")) << _T('\n');
1025     dbg << _T("[ed] Project file: ") << (m_pProjectFile ? m_pProjectFile->relativeFilename : _T("unknown")) << _T('\n');
1026     Manager::Get()->GetLogManager()->DebugLog(dbg);
1027 #endif
1028     if (preserve_modified)
1029         SetModified(wasmodified);
1030 }
1031 
UpdateProjectFile()1032 void cbEditor::UpdateProjectFile()
1033 {
1034     if (m_pControl && m_pProjectFile)
1035     {
1036         m_pProjectFile->editorOpen = true;
1037         m_pProjectFile->editorSplit = m_SplitType;
1038         m_pProjectFile->editorPos = m_pControl->GetCurrentPos();
1039         m_pProjectFile->editorTopLine = m_pControl->GetFirstVisibleLine();
1040         m_pProjectFile->editorZoom = m_pControl->GetZoom();
1041         m_pProjectFile->editorSplitActive = 1;
1042         if (m_pControl2)
1043         {
1044             m_pProjectFile->editorSplitPos = m_pSplitter->GetSashPosition();
1045             m_pProjectFile->editorPos_2 = m_pControl2->GetCurrentPos();
1046             m_pProjectFile->editorTopLine_2 = m_pControl2->GetFirstVisibleLine();
1047             m_pProjectFile->editorZoom_2 = m_pControl2->GetZoom();
1048             if (GetControl()==m_pControl2)
1049                 m_pProjectFile->editorSplitActive = 2;
1050         }
1051 
1052         if (m_pProjectFile->editorFoldLinesArray.GetCount() != 0)
1053             m_pProjectFile->editorFoldLinesArray.Clear();
1054 
1055         int i = 0;
1056         while ((i = m_pControl->ContractedFoldNext(i)) != -1)
1057             m_pProjectFile->editorFoldLinesArray.Add(i++);
1058     }
1059 }
1060 
SetMarkerStyle(int marker,int markerType,wxColor fore,wxColor back)1061 void cbEditor::SetMarkerStyle(int marker, int markerType, wxColor fore, wxColor back)
1062 {
1063     m_pControl->MarkerDefine(marker, markerType);
1064     m_pControl->MarkerSetForeground(marker, fore);
1065     m_pControl->MarkerSetBackground(marker, back);
1066 
1067     if (m_pControl2)
1068     {
1069         m_pControl2->MarkerDefine(marker, markerType);
1070         m_pControl2->MarkerSetForeground(marker, fore);
1071         m_pControl2->MarkerSetBackground(marker, back);
1072     }
1073 }
1074 
UnderlineFoldedLines(bool underline)1075 void cbEditor::UnderlineFoldedLines(bool underline)
1076 {
1077     m_pControl->SetFoldFlags(underline ? 16 : 0);
1078     if (m_pControl2)
1079         m_pControl2->SetFoldFlags(underline ? 16 : 0);
1080 }
1081 
CreateEditor()1082 cbStyledTextCtrl* cbEditor::CreateEditor()
1083 {
1084     // avoid gtk-critical because of sizes less than -1 (can happen with wxAuiNotebook/cbAuiNotebook)
1085     wxSize size = m_pControl ? wxDefaultSize : GetSize();
1086     size.x = std::max(size.x, -1);
1087     size.y = std::max(size.y, -1);
1088 
1089     cbStyledTextCtrl* control = new cbStyledTextCtrl(this, wxNewId(), wxDefaultPosition, size);
1090     control->UsePopUp(false);
1091 
1092     ConfigManager *config = Manager::Get()->GetConfigManager(_T("editor"));
1093     wxString encodingName = config->Read(_T("/default_encoding"), wxLocale::GetSystemEncodingName());
1094     m_pData->m_encoding = wxFontMapper::GetEncodingFromName(encodingName);
1095     if (m_pData->m_encoding == wxFONTENCODING_MAX && encodingName == wxT("default"))
1096         m_pData->m_encoding = wxFont::GetDefaultEncoding();
1097 
1098     for (int marker = 0 ; marker <= wxSCI_MARKNUM_LASTUNUSED ; ++marker)
1099         control->MarkerDefine(marker, wxSCI_MARK_EMPTY);
1100 
1101     return control;
1102 }
1103 
ConnectEvents(cbStyledTextCtrl * stc)1104 void cbEditor::ConnectEvents(cbStyledTextCtrl* stc)
1105 {
1106     wxWindowID stcID = stc->GetId();
1107     // dynamic events
1108     Connect( stcID, wxEVT_SCI_MARGINCLICK,       wxScintillaEventHandler(cbEditor::OnMarginClick)       );
1109     Connect( stcID, wxEVT_SCI_UPDATEUI,          wxScintillaEventHandler(cbEditor::OnEditorUpdateUI)    );
1110     Connect( stcID, wxEVT_SCI_CHANGE,            wxScintillaEventHandler(cbEditor::OnEditorChange)      );
1111     Connect( stcID, wxEVT_SCI_CHARADDED,         wxScintillaEventHandler(cbEditor::OnEditorCharAdded)   );
1112     Connect( stcID, wxEVT_SCI_DWELLSTART,        wxScintillaEventHandler(cbEditor::OnEditorDwellStart)  );
1113     Connect( stcID, wxEVT_SCI_DWELLEND,          wxScintillaEventHandler(cbEditor::OnEditorDwellEnd)    );
1114     Connect( stcID, wxEVT_SCI_USERLISTSELECTION, wxScintillaEventHandler(cbEditor::OnUserListSelection) );
1115     Connect( stcID, wxEVT_SCI_MODIFIED,          wxScintillaEventHandler(cbEditor::OnEditorModified)    );
1116 
1117     // Now bind all *other* scintilla events to a common function so that editor hooks
1118     // can be informed for them too.
1119     // If you implement one of these events using a different function, do the following:
1120     //  * comment it out here,
1121     //  * "connect" it in the above block
1122     //  * and make sure you call OnScintillaEvent() from your new handler function
1123     // This will make sure that all editor hooks will be called when needed.
1124     int scintilla_events[] =
1125     {
1126 //        wxEVT_SCI_CHANGE,
1127         wxEVT_SCI_STYLENEEDED,
1128 //        wxEVT_SCI_CHARADDED,
1129         wxEVT_SCI_SAVEPOINTREACHED,
1130         wxEVT_SCI_SAVEPOINTLEFT,
1131         wxEVT_SCI_ROMODIFYATTEMPT,
1132         wxEVT_SCI_DOUBLECLICK,
1133 //        wxEVT_SCI_UPDATEUI,
1134 //        wxEVT_SCI_MODIFIED,
1135         wxEVT_SCI_MACRORECORD,
1136 //        wxEVT_SCI_MARGINCLICK,
1137         wxEVT_SCI_NEEDSHOWN,
1138         wxEVT_SCI_PAINTED,
1139 //        wxEVT_SCI_USERLISTSELECTION,
1140 //        wxEVT_SCI_DWELLSTART,
1141 //        wxEVT_SCI_DWELLEND,
1142         wxEVT_SCI_START_DRAG,
1143         wxEVT_SCI_DRAG_OVER,
1144         wxEVT_SCI_DO_DROP,
1145         wxEVT_SCI_ZOOM,
1146         wxEVT_SCI_HOTSPOT_CLICK,
1147         wxEVT_SCI_HOTSPOT_DCLICK,
1148         wxEVT_SCI_CALLTIP_CLICK,
1149         wxEVT_SCI_AUTOCOMP_SELECTION,
1150 //        wxEVT_SCI_INDICATOR_CLICK,
1151 //        wxEVT_SCI_INDICATOR_RELEASE,
1152         wxEVT_SCI_AUTOCOMP_CANCELLED,
1153 
1154         -1 // to help enumeration of this array
1155     };
1156     int i = 0;
1157     while (scintilla_events[i] != -1)
1158     {
1159         Connect( stcID, scintilla_events[i], wxScintillaEventHandler(cbEditor::OnScintillaEvent) );
1160         ++i;
1161     }
1162 }
1163 
Split(cbEditor::SplitType split)1164 void cbEditor::Split(cbEditor::SplitType split)
1165 {
1166     Freeze();
1167 
1168     // unsplit first, if needed
1169     if (m_pSplitter)
1170     {
1171         Unsplit();
1172         Manager::Yield();
1173     }
1174     m_SplitType = split;
1175     if (m_SplitType == stNoSplit)
1176     {
1177         Thaw();
1178         return;
1179     }
1180 
1181     // remove the left control from the sizer
1182     m_pSizer->Detach(m_pControl);
1183 
1184     // create the splitter window
1185     m_pSplitter = new wxSplitterWindow(this, wxNewId(), wxDefaultPosition, wxDefaultSize, wxSP_NOBORDER | wxSP_LIVE_UPDATE);
1186     m_pSplitter->SetMinimumPaneSize(32);
1187 
1188     // create the right control
1189     m_pControl2 = CreateEditor();
1190 
1191     // update controls' look'n'feel
1192     // do it here (before) document is attached, speeds up syntaxhighlighting
1193     // we do not call "SetEditorStyleAfterFileOpen" here because it calls SetLanguage for the already loaded text inside
1194     // the left control and slows down loading of large files a lot.
1195     InternalSetEditorStyleBeforeFileOpen(m_pControl2);
1196 
1197     // make sure basic settings of indicators (maybe set by plugins) are used for the new control
1198     for (int i = 0; i < wxSCI_INDIC_MAX; ++i )
1199     {
1200         m_pControl2->IndicatorSetStyle(i, m_pControl->IndicatorGetStyle(i));
1201         m_pControl2->IndicatorSetUnder(i, m_pControl->IndicatorGetUnder(i));
1202         m_pControl2->IndicatorSetForeground(i, m_pControl->IndicatorGetForeground(i));
1203     }
1204 
1205     ConfigManager* mgr = Manager::Get()->GetConfigManager(_T("editor"));
1206     SetFoldingIndicator(mgr->ReadInt(_T("/folding/indicator"), 2));
1207     UnderlineFoldedLines(mgr->ReadBool(_T("/folding/underline_folded_line"), true));
1208 
1209     if (m_pTheme)
1210     {
1211         m_pTheme->Apply(m_lang, m_pControl2, false, true);
1212         SetLanguageDependentColours(*m_pControl2);
1213     }
1214 
1215     // and make it a live copy of left control
1216     m_pControl2->SetDocPointer(m_pControl->GetDocPointer());
1217 
1218     // on wxGTK > 2.9 we need to thaw before reparent and refreeze the editor here or the whole app stays frozen
1219     #if defined ( __WXGTK__ ) && wxCHECK_VERSION(3, 0, 0)
1220     Thaw();
1221     #endif
1222     // parent both controls under the splitter
1223     m_pControl->Reparent(m_pSplitter);
1224     m_pControl2->Reparent(m_pSplitter);
1225     #if defined ( __WXGTK__ ) && wxCHECK_VERSION(3, 0, 0)
1226     Freeze();
1227     #endif
1228 
1229     // add the splitter in the sizer
1230     m_pSizer->SetDimension(0, 0, GetSize().x, GetSize().y);
1231     m_pSizer->Add(m_pSplitter, 1, wxEXPAND);
1232 
1233     m_pSizer->Layout();
1234 
1235     // split as needed
1236     switch (m_SplitType)
1237     {
1238         case stHorizontal:
1239             m_pSplitter->SplitHorizontally(m_pControl, m_pControl2, 0);
1240             break;
1241 
1242         case stVertical:
1243             m_pSplitter->SplitVertically(m_pControl, m_pControl2, 0);
1244             break;
1245 
1246         case stNoSplit: // fall-trough
1247         default:
1248             break;
1249     }
1250 
1251     SetEditorStyleAfterFileOpen();
1252 
1253     // initial zoom is same as left/top control
1254     m_pControl2->SetZoom(m_pControl->GetZoom());
1255     // make sure the line numbers margin is correct for the new control
1256     m_pControl2->SetMarginWidth(C_LINE_MARGIN, m_pControl->GetMarginWidth(C_LINE_MARGIN));
1257 
1258     ConnectEvents(m_pControl2);
1259 
1260     NotifyPlugins(cbEVT_EDITOR_SPLIT);
1261 
1262     Thaw();
1263 }
1264 
Unsplit()1265 void cbEditor::Unsplit()
1266 {
1267     m_SplitType = stNoSplit;
1268     if (!m_pSplitter)
1269         return;
1270 
1271     Freeze();
1272 
1273     // if "unsplit" requested on right control, swap left-right first
1274     if (GetControl() == m_pControl2)
1275     {
1276         cbStyledTextCtrl* tmp = m_pControl;
1277         m_pControl = m_pControl2;
1278         m_pControl2 = tmp;
1279     }
1280 
1281     // remove the splitter from the sizer
1282     m_pSizer->Detach(m_pSplitter);
1283 
1284     // on wxGTK > 2.9 we need to thaw before reparent and refreeze the editor here or the whole app stays frozen
1285     #if defined ( __WXGTK__ ) && wxCHECK_VERSION(3, 0, 0)
1286     Thaw();
1287     #endif
1288     // parent the left control under this
1289     m_pControl->Reparent(this);
1290     #if defined ( __WXGTK__ ) && wxCHECK_VERSION(3, 0, 0)
1291     Freeze();
1292     #endif
1293     // add it in the sizer
1294     m_pSizer->Add(m_pControl, 1, wxEXPAND);
1295     // notify the plugin when the right splitter window is not destroyed and the left window is reparented to cbEditor
1296     NotifyPlugins(cbEVT_EDITOR_UNSPLIT);
1297     // destroy the splitter and right control
1298     DestroySplitView();
1299     // and layout
1300     m_pSizer->Layout();
1301 
1302     Thaw();
1303 }
1304 
SetLanguageDependentColours(cbStyledTextCtrl & control)1305 void cbEditor::SetLanguageDependentColours(cbStyledTextCtrl &control)
1306 {
1307     control.MarkerSetBackground(DEBUG_MARKER_HIGHLIGHT, control.GetCaretLineBackground());
1308 }
1309 
SetEditorStyle()1310 void cbEditor::SetEditorStyle()
1311 {
1312     SetEditorStyleBeforeFileOpen();
1313     SetEditorStyleAfterFileOpen();
1314 }
1315 
OverrideUseTabsPerLanguage(cbStyledTextCtrl * control)1316 inline void OverrideUseTabsPerLanguage(cbStyledTextCtrl *control)
1317 {
1318     if (!control)
1319         return;
1320     // Override the use tab setting for languages which have explicit requirements about tab/space
1321     // usage.
1322     int lexer = control->GetLexer();
1323     switch (lexer)
1324     {
1325         case wxSCI_LEX_PYTHON:
1326         case wxSCI_LEX_YAML:
1327             control->SetUseTabs(false);
1328             break;
1329         case wxSCI_LEX_MAKEFILE:
1330             control->SetUseTabs(true);
1331             break;
1332         default:
1333             break;
1334     }
1335 }
1336 
SetEditorTechnology(cbStyledTextCtrl * control,ConfigManager * config)1337 static void SetEditorTechnology(cbStyledTextCtrl *control, ConfigManager *config)
1338 {
1339     if (!control)
1340         return;
1341 #if defined(__WXMSW__) && wxCHECK_VERSION(3, 1, 0)
1342     const int technology = config->ReadInt(wxT("/technology"), 0);
1343     if (technology == 1)
1344         control->SetTechnology(wxSCI_TECHNOLOGY_DIRECTWRITE);
1345     else
1346         control->SetTechnology(wxSCI_TECHNOLOGY_DEFAULT);
1347 
1348     const int fontQuality = config->ReadInt(wxT("/font_quality"), 0);
1349     switch (fontQuality)
1350     {
1351     default:
1352     case 0:
1353         control->SetFontQuality(wxSCI_EFF_QUALITY_DEFAULT);
1354         break;
1355     case 1:
1356         control->SetFontQuality(wxSCI_EFF_QUALITY_NON_ANTIALIASED);
1357         break;
1358     case 2:
1359         control->SetFontQuality(wxSCI_EFF_QUALITY_ANTIALIASED);
1360         break;
1361     case 3:
1362         control->SetFontQuality(wxSCI_EFF_QUALITY_LCD_OPTIMIZED);
1363         break;
1364     }
1365 #endif // defined(__WXMSW__) && wxCHECK_VERSION(3, 1, 0)
1366 }
1367 
SetEditorStyleBeforeFileOpen()1368 void cbEditor::SetEditorStyleBeforeFileOpen()
1369 {
1370     ConfigManager* mgr = Manager::Get()->GetConfigManager(_T("editor"));
1371 
1372     // update the tab text based on preferences
1373     if (m_pProjectFile)
1374     {
1375         if (mgr->ReadBool(_T("/tab_text_relative"), true))
1376             m_Shortname = m_pProjectFile->relativeToCommonTopLevelPath;
1377         else
1378             m_Shortname = m_pProjectFile->file.GetFullName();
1379         SetEditorTitle(m_Shortname);
1380     }
1381 
1382     // Folding properties.
1383     m_pData->mFoldingLimit = mgr->ReadBool(_T("/folding/limit"), false);
1384     m_pData->mFoldingLimitLevel = mgr->ReadInt(_T("/folding/limit_level"), 1);
1385 
1386     // EOL properties
1387     m_pData->m_strip_trailing_spaces = mgr->ReadBool(_T("/eol/strip_trailing_spaces"), true);
1388     m_pData->m_ensure_final_line_end = mgr->ReadBool(_T("/eol/ensure_final_line_end"), true);
1389     m_pData->m_ensure_consistent_line_ends = mgr->ReadBool(_T("/eol/ensure_consistent_line_ends"), false);
1390 
1391     InternalSetEditorStyleBeforeFileOpen(m_pControl);
1392 
1393     if (m_pControl2)
1394         InternalSetEditorStyleBeforeFileOpen(m_pControl2);
1395 
1396     SetFoldingIndicator(mgr->ReadInt(_T("/folding/indicator"), 2));
1397 
1398     SetLanguage(HL_AUTO, false);
1399 
1400     OverrideUseTabsPerLanguage(m_pControl);
1401     OverrideUseTabsPerLanguage(m_pControl2);
1402 
1403     SetEditorTechnology(m_pControl, mgr);
1404     SetEditorTechnology(m_pControl2, mgr);
1405 }
1406 
SetEditorStyleAfterFileOpen()1407 void cbEditor::SetEditorStyleAfterFileOpen()
1408 {
1409     InternalSetEditorStyleAfterFileOpen(m_pControl);
1410     if (m_pControl2)
1411         InternalSetEditorStyleAfterFileOpen(m_pControl2);
1412 
1413     ConfigManager* mgr = Manager::Get()->GetConfigManager(_T("editor"));
1414 
1415     UnderlineFoldedLines(mgr->ReadBool(_T("/folding/underline_folded_line"), true));
1416 
1417     // line numbers
1418     m_pData->SetLineNumberColWidth();
1419 }
1420 
1421 // static
1422 // public version of InternalSetEditorStyleBeforeFileOpen
ApplyStyles(cbStyledTextCtrl * control)1423 void cbEditor::ApplyStyles(cbStyledTextCtrl* control)
1424 {
1425     if (!control)
1426         return;
1427 
1428     InternalSetEditorStyleBeforeFileOpen(control);
1429     InternalSetEditorStyleAfterFileOpen(control);
1430 }
1431 
1432 // static
InternalSetEditorStyleBeforeFileOpen(cbStyledTextCtrl * control)1433 void cbEditor::InternalSetEditorStyleBeforeFileOpen(cbStyledTextCtrl* control)
1434 {
1435     if (!control)
1436         return;
1437 
1438     ConfigManager* mgr = Manager::Get()->GetConfigManager(_T("editor"));
1439 
1440     // setting the default editor font size to 10 point
1441     wxFont font(10, wxFONTFAMILY_MODERN, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
1442 
1443     wxString fontstring = mgr->Read(_T("/font"), wxEmptyString);
1444 
1445     if (!fontstring.IsEmpty())
1446     {
1447         wxNativeFontInfo nfi;
1448         nfi.FromString(fontstring);
1449         font.SetNativeFontInfo(nfi);
1450     }
1451 
1452     control->SetMouseDwellTime(1000);
1453 
1454     int caretStyle = mgr->ReadInt(_T("/caret/style"), wxSCI_CARETSTYLE_LINE);
1455     control->SetCaretStyle(caretStyle);
1456     if (caretStyle == wxSCI_CARETSTYLE_LINE)
1457         control->SetCaretWidth(mgr->ReadInt(_T("/caret/width"), 1));
1458     else
1459         control->SetCaretWidth(1);
1460 
1461     ColourManager *colours = Manager::Get()->GetColourManager();
1462 
1463     control->SetCaretForeground(colours->GetColour(wxT("editor_caret")));
1464     control->SetCaretPeriod(mgr->ReadInt(_T("/caret/period"), 500));
1465     control->SetCaretLineVisible(mgr->ReadBool(_T("/highlight_caret_line"), false));
1466     control->SetFoldMarginColour(true, colours->GetColour(wxT("editor_margin_chrome")));
1467     control->SetFoldMarginHiColour(true, colours->GetColour(wxT("editor_margin_chrome_highlight")));
1468 
1469     control->SetWhitespaceForeground(true, colours->GetColour(wxT("editor_whitespace")));
1470 
1471     // setup for "CamelCase selection"
1472     if (mgr->ReadBool(_T("/camel_case"), false))
1473     {
1474         // consider CamelCase for both: cursor movement with CTRL and selection with CTRL+SHIFT:
1475         control->CmdKeyAssign(wxSCI_KEY_LEFT,  wxSCI_KEYMOD_CTRL,                   wxSCI_CMD_WORDPARTLEFT);
1476         control->CmdKeyAssign(wxSCI_KEY_RIGHT, wxSCI_KEYMOD_CTRL,                   wxSCI_CMD_WORDPARTRIGHT);
1477         control->CmdKeyAssign(wxSCI_KEY_LEFT,  wxSCI_KEYMOD_CTRL|wxSCI_KEYMOD_SHIFT, wxSCI_CMD_WORDPARTLEFTEXTEND);
1478         control->CmdKeyAssign(wxSCI_KEY_RIGHT, wxSCI_KEYMOD_CTRL|wxSCI_KEYMOD_SHIFT, wxSCI_CMD_WORDPARTRIGHTEXTEND);
1479     }
1480     else // else set default "none CamelCase" key behavior (also default scintilla behaviour, see scintilla docs)
1481     {
1482         control->CmdKeyAssign(wxSCI_KEY_LEFT,  wxSCI_KEYMOD_CTRL,                   wxSCI_CMD_WORDLEFT);
1483         control->CmdKeyAssign(wxSCI_KEY_RIGHT, wxSCI_KEYMOD_CTRL,                   wxSCI_CMD_WORDRIGHT);
1484         control->CmdKeyAssign(wxSCI_KEY_LEFT,  wxSCI_KEYMOD_CTRL|wxSCI_KEYMOD_SHIFT, wxSCI_CMD_WORDLEFTEXTEND);
1485         control->CmdKeyAssign(wxSCI_KEY_RIGHT, wxSCI_KEYMOD_CTRL|wxSCI_KEYMOD_SHIFT, wxSCI_CMD_WORDRIGHTEXTEND);
1486     }
1487 
1488     control->SetUseTabs(mgr->ReadBool(_T("/use_tab"), false));
1489     control->SetIndentationGuides(mgr->ReadBool(_T("/show_indent_guides"), false)?wxSCI_IV_LOOKBOTH:wxSCI_IV_NONE);
1490     control->SetTabIndents(mgr->ReadBool(_T("/tab_indents"), true));
1491     control->SetBackSpaceUnIndents(mgr->ReadBool(_T("/backspace_unindents"), true));
1492     control->SetWrapMode(mgr->ReadBool(_T("/word_wrap"), false));
1493     if (mgr->ReadBool(_T("/word_wrap_style_home_end"), true))
1494     {
1495         // in word wrap mode, home/end keys goto the wrap point if not already there,
1496         // otherwise to the start/end of the entire line.
1497         // alt+home/end go to start/end of the entire line.
1498         // in unwrapped mode, there is no difference between home/end and alt+home/end
1499         control->CmdKeyAssign(wxSCI_KEY_END,  wxSCI_KEYMOD_NORM,                  wxSCI_CMD_LINEENDWRAP);
1500         control->CmdKeyAssign(wxSCI_KEY_END,  wxSCI_KEYMOD_ALT,                   wxSCI_CMD_LINEEND);
1501         control->CmdKeyAssign(wxSCI_KEY_END,  wxSCI_KEYMOD_SHIFT,                 wxSCI_CMD_LINEENDWRAPEXTEND);
1502         control->CmdKeyAssign(wxSCI_KEY_END,  wxSCI_KEYMOD_SHIFT|wxSCI_KEYMOD_ALT, wxSCI_CMD_LINEENDEXTEND);
1503 
1504         // if user wants "Home" key to set cursor to the very beginning of line
1505         if (mgr->ReadBool(_T("/simplified_home"), false))
1506         {
1507             control->CmdKeyAssign(wxSCI_KEY_HOME,wxSCI_KEYMOD_NORM,wxSCI_CMD_HOMEWRAP);
1508             control->CmdKeyAssign(wxSCI_KEY_HOME,wxSCI_KEYMOD_ALT,wxSCI_CMD_HOME);
1509             control->CmdKeyAssign(wxSCI_KEY_HOME,wxSCI_KEYMOD_SHIFT,wxSCI_CMD_HOMEWRAPEXTEND);
1510             control->CmdKeyAssign(wxSCI_KEY_HOME,wxSCI_KEYMOD_SHIFT|wxSCI_KEYMOD_ALT,wxSCI_CMD_HOMEEXTEND);
1511         }
1512         else // else set default "Home" key behaviour
1513         {
1514             control->CmdKeyAssign(wxSCI_KEY_HOME,wxSCI_KEYMOD_NORM,wxSCI_CMD_VCHOMEWRAP);
1515             control->CmdKeyAssign(wxSCI_KEY_HOME,wxSCI_KEYMOD_ALT,wxSCI_CMD_VCHOME);
1516             control->CmdKeyAssign(wxSCI_KEY_HOME,wxSCI_KEYMOD_SHIFT,wxSCI_CMD_VCHOMEWRAPEXTEND);
1517             control->CmdKeyAssign(wxSCI_KEY_HOME,wxSCI_KEYMOD_SHIFT|wxSCI_KEYMOD_ALT,wxSCI_CMD_VCHOMEEXTEND);
1518         }
1519     }
1520     else
1521     {   // in word wrap mode, home/end keys goto start/end of the entire line. alt+home/end goes to wrap points
1522         control->CmdKeyAssign(wxSCI_KEY_END,  wxSCI_KEYMOD_ALT,                   wxSCI_CMD_LINEENDWRAP);
1523         control->CmdKeyAssign(wxSCI_KEY_END,  wxSCI_KEYMOD_SHIFT|wxSCI_KEYMOD_ALT, wxSCI_CMD_LINEENDWRAPEXTEND);
1524 
1525         // if user wants "Home" key to set cursor to the very beginning of line
1526         if (mgr->ReadBool(_T("/simplified_home"), false))
1527         {
1528             control->CmdKeyAssign(wxSCI_KEY_HOME,wxSCI_KEYMOD_ALT,wxSCI_CMD_HOMEWRAP);
1529             control->CmdKeyAssign(wxSCI_KEY_HOME,wxSCI_KEYMOD_SHIFT|wxSCI_KEYMOD_ALT,wxSCI_CMD_HOMEWRAPEXTEND);
1530         }
1531         else // else set default "Home" key behaviour
1532         {
1533             control->CmdKeyAssign(wxSCI_KEY_HOME,wxSCI_KEYMOD_ALT,wxSCI_CMD_VCHOMEWRAP);
1534             control->CmdKeyAssign(wxSCI_KEY_HOME,wxSCI_KEYMOD_SHIFT|wxSCI_KEYMOD_ALT,wxSCI_CMD_VCHOMEWRAPEXTEND);
1535         }
1536     }
1537     control->SetViewEOL(mgr->ReadBool(_T("/show_eol"), false));
1538     control->SetViewWhiteSpace(mgr->ReadInt(_T("/view_whitespace"), 0));
1539 
1540     const int caretBuffer = mgr->ReadInt(wxT("/caret_buffer"), 2);
1541     if (caretBuffer == 0)
1542     {
1543         control->SetYCaretPolicy(wxSCI_CARET_EVEN, 0); // default
1544         control->SetVisiblePolicy(wxSCI_CARET_EVEN, 0); // default
1545     }
1546     else if (caretBuffer > 0 && caretBuffer <= 10)
1547     {
1548         // margin of N lines at top/bottom
1549         control->SetYCaretPolicy(wxSCI_CARET_SLOP | wxSCI_CARET_STRICT | wxSCI_CARET_EVEN,
1550                                  caretBuffer);
1551         control->SetVisiblePolicy(wxSCI_CARET_SLOP | wxSCI_CARET_STRICT | wxSCI_CARET_EVEN,
1552                                   caretBuffer);
1553     }
1554     else
1555     {
1556         // centred mode
1557         control->SetYCaretPolicy(wxSCI_CARET_STRICT | wxSCI_CARET_EVEN, 4);
1558         control->SetVisiblePolicy(wxSCI_CARET_STRICT | wxSCI_CARET_EVEN, 4);
1559     }
1560 
1561     // gutter
1562     control->SetEdgeMode(mgr->ReadInt(_T("/gutter/mode"), 0));
1563     control->SetEdgeColour(colours->GetColour(wxT("editor_gutter")));
1564     control->SetEdgeColumn(mgr->ReadInt(_T("/gutter/column"), 80));
1565 
1566     control->StyleSetFont(wxSCI_STYLE_DEFAULT, font);
1567     control->StyleClearAll();
1568 
1569     control->SetTabWidth(mgr->ReadInt(_T("/tab_size"), 4));
1570 
1571     // margin for bookmarks, breakpoints etc.
1572     // FIXME: how to display a mark with an offset???
1573     control->SetMarginWidth(C_MARKER_MARGIN, 16);
1574     control->SetMarginType(C_MARKER_MARGIN, wxSCI_MARGIN_SYMBOL);
1575     control->SetMarginSensitive(C_MARKER_MARGIN, mgr->ReadBool(_T("/margin_1_sensitive"), true));
1576     // use "|" here or we might break plugins that use the margin (like browsemarks)
1577     control->SetMarginMask(C_MARKER_MARGIN,
1578                            control->GetMarginMask(C_MARKER_MARGIN)
1579                            | (1 << BOOKMARK_MARKER)
1580                            | (1 << BREAKPOINT_MARKER)
1581                            | (1 << BREAKPOINT_DISABLED_MARKER)
1582                            | (1 << BREAKPOINT_OTHER_MARKER)
1583                            | (1 << DEBUG_MARKER)
1584                            | (1 << DEBUG_MARKER_HIGHLIGHT)
1585                            | (1 << ERROR_MARKER) );
1586 
1587     // 1.) Marker for Bookmarks etc...
1588     control->MarkerDefine(BOOKMARK_MARKER, BOOKMARK_STYLE);
1589     control->MarkerSetBackground(BOOKMARK_MARKER, wxColour(0xA0, 0xA0, 0xFF));
1590 
1591     // 2.) Marker for Breakpoints etc...
1592     cbEditorInternalData::SetupBreakpointMarkers(control, 16);
1593 
1594     // 3.) Marker for Debugging (currently debugged line) etc...
1595     control->MarkerDefine(DEBUG_MARKER, DEBUG_STYLE);
1596     control->MarkerSetBackground(DEBUG_MARKER, wxColour(0xFF, 0xFF, 0x00));
1597 
1598     control->MarkerDefine(DEBUG_MARKER_HIGHLIGHT, DEBUG_STYLE_HIGHLIGHT);
1599     control->MarkerSetBackground(DEBUG_MARKER_HIGHLIGHT, wxColour(0xFF, 0xFF, 0x00));
1600 
1601     // 4.) Marker for Errors...
1602     control->MarkerDefine(ERROR_MARKER, ERROR_STYLE);
1603     control->MarkerSetBackground(ERROR_MARKER, wxColour(0xFF, 0x00, 0x00));
1604 
1605     // changebar margin
1606     if (mgr->ReadBool(_T("/margin/use_changebar"), true))
1607     {
1608         control->SetMarginWidth(C_CHANGEBAR_MARGIN, changeBarMarginBaseWidth);
1609         control->SetMarginType(C_CHANGEBAR_MARGIN,  wxSCI_MARGIN_SYMBOL);
1610         // use "|" here or we might break plugins that use the margin (none at the moment)
1611         control->SetMarginMask(C_CHANGEBAR_MARGIN,
1612                                control->GetMarginMask(C_CHANGEBAR_MARGIN)
1613                                | (1 << wxSCI_MARKNUM_CHANGEUNSAVED)
1614                                | (1 << wxSCI_MARKNUM_CHANGESAVED) );
1615 
1616         control->MarkerDefine(wxSCI_MARKNUM_CHANGEUNSAVED, wxSCI_MARK_FULLRECT);
1617         control->MarkerSetBackground(wxSCI_MARKNUM_CHANGEUNSAVED, wxColour(0xFF, 0xE6, 0x04));
1618         control->MarkerDefine(wxSCI_MARKNUM_CHANGESAVED, wxSCI_MARK_FULLRECT);
1619         control->MarkerSetBackground(wxSCI_MARKNUM_CHANGESAVED,   wxColour(0x04, 0xFF, 0x50));
1620     }
1621     else
1622         control->SetMarginWidth(C_CHANGEBAR_MARGIN, 0);
1623 
1624     // NOTE: duplicate line in editorconfigurationdlg.cpp (ctor)
1625     control->SetScrollWidthTracking(      mgr->ReadBool(_T("/margin/scroll_width_tracking"), false));
1626     control->SetMultipleSelection(        mgr->ReadBool(_T("/selection/multi_select"),       false));
1627     const bool multiTyping = mgr->ReadBool(_T("/selection/multi_typing"), false);
1628     control->SetAdditionalSelectionTyping(multiTyping);
1629     control->SetMultiPaste(multiTyping);
1630 
1631     unsigned virtualSpace = 0;
1632     if (mgr->ReadBool(_T("/selection/use_rect_vspace"), false))
1633         virtualSpace |= wxSCI_VS_RECTANGULARSELECTION;
1634     if (mgr->ReadBool(_T("/selection/use_vspace"), false))
1635         virtualSpace |= wxSCI_VS_USERACCESSIBLE;
1636     if (!virtualSpace)
1637         virtualSpace = wxSCI_VS_NONE; // Just in case wxSCI_VS_NONE != 0
1638     control->SetVirtualSpaceOptions(virtualSpace);
1639 }
1640 
1641 // static
InternalSetEditorStyleAfterFileOpen(cbStyledTextCtrl * control)1642 void cbEditor::InternalSetEditorStyleAfterFileOpen(cbStyledTextCtrl* control)
1643 {
1644     if (!control)
1645         return;
1646 
1647     ConfigManager* mgr = Manager::Get()->GetConfigManager(_T("editor"));
1648 
1649     // set the EOL, fall back value: Windows takes CR+LF, other platforms LF only
1650     int eolMode = mgr->ReadInt(_T("/eol/eolmode"), platform::windows ? wxSCI_EOL_CRLF : wxSCI_EOL_LF);
1651 
1652     if (eolMode == 3) //auto detect the EOL
1653         eolMode = DetectLineEnds(control);
1654 
1655     control->SetEOLMode(eolMode);
1656 
1657     // indentation style is already set
1658     if (mgr->ReadBool(_T("/detect_indent"), false))
1659     {
1660         // override style if auto-detection succeeds
1661         int indentStyle = cbEditorInternalData::DetectIndentStyle(control);
1662         if (indentStyle == 0)
1663             control->SetUseTabs(true);
1664         else if (indentStyle != -1)
1665         {
1666             control->SetUseTabs(false);
1667             control->SetTabWidth(indentStyle);
1668         }
1669     }
1670 
1671     // Interpret #if/#else/#endif to grey out code that is not active
1672     control->SetProperty(_T("lexer.cpp.track.preprocessor"), mgr->ReadBool(_T("/track_preprocessor"), true) ? _T("1") : _T("0"));
1673 
1674     // code folding
1675     if (mgr->ReadBool(_T("/folding/show_folds"), true))
1676     {
1677         control->SetProperty(_T("fold"),              _T("1"));
1678         control->SetProperty(_T("fold.html"),         mgr->ReadBool(_T("/folding/fold_xml"), true) ? _T("1") : _T("0"));
1679         control->SetProperty(_T("fold.comment"),      mgr->ReadBool(_T("/folding/fold_comments"), false) ? _T("1") : _T("0"));
1680         control->SetProperty(_T("fold.compact"),      _T("0"));
1681         control->SetProperty(_T("fold.preprocessor"), mgr->ReadBool(_T("/folding/fold_preprocessor"), false) ? _T("1") : _T("0"));
1682 
1683         control->SetFoldFlags(16);
1684         control->SetMarginType(C_FOLDING_MARGIN, wxSCI_MARGIN_SYMBOL);
1685         control->SetMarginWidth(C_FOLDING_MARGIN, foldingMarginBaseWidth);
1686         // use "|" here or we might break plugins that use the margin (none at the moment)
1687         control->SetMarginMask(C_FOLDING_MARGIN,
1688                                  control->GetMarginMask(C_FOLDING_MARGIN)
1689                                | (  wxSCI_MASK_FOLDERS
1690                                   - (  (1 << wxSCI_MARKNUM_CHANGEUNSAVED)
1691                                      | (1 << wxSCI_MARKNUM_CHANGESAVED))) );
1692         control->SetMarginSensitive(C_FOLDING_MARGIN, 1);
1693     }
1694     else
1695     {
1696         control->SetProperty(_T("fold"), _T("0"));
1697         control->SetMarginWidth(C_FOLDING_MARGIN, 0);
1698     }
1699     control->SetProperty(_T("highlight.wxsmith"), mgr->ReadBool(_T("/highlight_wxsmith"), true) ? _T("1") : _T("0"));
1700 
1701     // line numbering
1702     control->SetMarginType(C_LINE_MARGIN, wxSCI_MARGIN_NUMBER);
1703 
1704     // As a final step colourise the document. This make sure that style and folding information is
1705     // set on every line/character of the editor. If Colourise is called earlier restoring the
1706     // folding of the editor might not work. Also this is probably slow for larger files, so it is
1707     // best if we call it minimal number of times.
1708     control->Colourise(0, -1);
1709 }
1710 
SetColourSet(EditorColourSet * theme)1711 void cbEditor::SetColourSet(EditorColourSet* theme)
1712 {
1713     m_pTheme = theme;
1714     SetLanguage(m_lang, true);
1715 }
1716 
GetEncoding() const1717 wxFontEncoding cbEditor::GetEncoding() const
1718 {
1719     if (!m_pData)
1720         return wxFONTENCODING_SYSTEM;
1721 
1722     return m_pData->m_encoding;
1723 }
1724 
GetEncodingName() const1725 wxString cbEditor::GetEncodingName( ) const
1726 {
1727     return wxFontMapper::GetEncodingName(GetEncoding());
1728 }
1729 
SetEncoding(wxFontEncoding encoding)1730 void cbEditor::SetEncoding(wxFontEncoding encoding)
1731 {
1732     if (!m_pData)
1733         return;
1734 
1735     if (encoding == wxFONTENCODING_SYSTEM)
1736         encoding = wxLocale::GetSystemEncoding();
1737 
1738     if (encoding == m_pData->m_encoding)
1739         return;
1740 
1741     m_pData->m_encoding = encoding;
1742     SetModified(true);
1743 }
1744 
GetUseBom() const1745 bool cbEditor::GetUseBom() const
1746 {
1747     if (!m_pData)
1748         return false;
1749     return m_pData->m_useByteOrderMark;
1750 }
1751 
SetUseBom(bool bom)1752 void cbEditor::SetUseBom( bool bom )
1753 {
1754     if (!m_pData)
1755         return;
1756 
1757     if ( bom == GetUseBom() )
1758         return;
1759 
1760     m_pData->m_useByteOrderMark = bom;
1761     SetModified(true);
1762 }
1763 
Reload(bool detect_encoding)1764 bool cbEditor::Reload(bool detect_encoding)
1765 {
1766     // keep current pos
1767     const int pos = m_pControl ? m_pControl->GetCurrentPos() : 0;
1768     const int pos2 = m_pControl2 ? m_pControl2->GetCurrentPos() : 0;
1769 
1770     // call open
1771     if (!Open(detect_encoding))
1772     {
1773         return false;
1774     }
1775     // Re-establish margin styles, width,  etc
1776     SetEditorStyleAfterFileOpen();
1777 
1778     // return (if possible) to old pos
1779     if (m_pControl)
1780     {
1781         m_pControl->GotoPos(pos);
1782     }
1783     if (m_pControl2)
1784     {
1785         m_pControl2->GotoPos(pos2);
1786     }
1787     return true;
1788 } // end of Reload
1789 
Touch()1790 void cbEditor::Touch()
1791 {
1792     m_LastModified = wxDateTime::Now();
1793     SetModified(true);
1794 }
1795 
SetLanguage(HighlightLanguage lang,bool colourise)1796 void cbEditor::SetLanguage(HighlightLanguage lang, bool colourise)
1797 {
1798     if (m_pTheme)
1799     {
1800         m_lang = m_pTheme->Apply(this, lang, colourise);
1801         if (m_pControl)
1802             SetLanguageDependentColours(*m_pControl);
1803         if (m_pControl2)
1804             SetLanguageDependentColours(*m_pControl2);
1805     }
1806     else
1807         m_lang = HL_AUTO;
1808 }
1809 
Open(bool detectEncoding)1810 bool cbEditor::Open(bool detectEncoding)
1811 {
1812     if (m_pProjectFile)
1813     {
1814         if (!wxFileExists(m_Filename))
1815             m_pProjectFile->SetFileState(fvsMissing);
1816         else if (!wxFile::Access(m_Filename.c_str(), wxFile::write)) // readonly
1817             m_pProjectFile->SetFileState(fvsReadOnly);
1818     }
1819 
1820     if (!wxFileExists(m_Filename))
1821         return false;
1822 
1823     // open file
1824     SetReadOnly(false);
1825 
1826     m_pControl->ClearAll();
1827     m_pControl->SetModEventMask(0);
1828 
1829     if (!m_pData)
1830         return false;
1831 
1832     if (!m_pData->m_pFileLoader)
1833         m_pData->m_pFileLoader = Manager::Get()->GetFileManager()->Load(m_Filename, false);
1834 
1835 #ifdef fileload_measuring
1836     wxStopWatch sw;
1837 #endif
1838     EncodingDetector enc((wxByte*)m_pData->m_pFileLoader->GetData(), m_pData->m_pFileLoader->GetLength());
1839     if (detectEncoding)
1840     {
1841         m_pData->m_useByteOrderMark    = enc.UsesBOM();
1842         m_pData->m_byteOrderMarkLength = enc.GetBOMSizeInBytes();
1843         m_pData->m_encoding            = enc.GetFontEncoding();
1844 
1845         SetEncoding(enc.GetFontEncoding());
1846         SetUseBom(m_pData->m_byteOrderMarkLength > 0);
1847     }
1848 
1849     ConfigManager* mgr = Manager::Get()->GetConfigManager(_T("editor"));
1850 #ifdef fileload_measuring
1851     Manager::Get()->GetLogManager()->DebugLog(F(_T("cbEditor::Open() => Encoding detection and conversion took : %d ms"),(int)sw.Time()));
1852     sw.Start();
1853 #endif
1854 
1855     m_pControl->InsertText(0, enc.GetWxStr());
1856     m_pControl->EmptyUndoBuffer(mgr->ReadBool(_T("/margin/use_changebar"), true));
1857     m_pControl->SetModEventMask(wxSCI_MODEVENTMASKALL);
1858 
1859     // mark the file read-only, if applicable
1860     bool read_only = !wxFile::Access(m_Filename.c_str(), wxFile::write);
1861     SetReadOnly(read_only);
1862 
1863     wxFileName fname(m_Filename);
1864     m_LastModified = fname.GetModificationTime();
1865 
1866     SetModified(false);
1867 
1868     NotifyPlugins(cbEVT_EDITOR_OPEN);
1869 
1870     if (m_pData->m_pFileLoader)
1871     {
1872         delete m_pData->m_pFileLoader;
1873         m_pData->m_pFileLoader = nullptr;
1874     }
1875 #ifdef fileload_measuring
1876     Manager::Get()->GetLogManager()->DebugLog(F(_T("loading into editor needs : %d ms"),(int)sw.Time()));
1877 #endif
1878     return true;
1879 }
1880 
Save()1881 bool cbEditor::Save()
1882 {
1883     if ( !GetModified() )
1884         return true;
1885 
1886     // remember current column (caret and anchor)
1887     int columnC = m_pControl->GetColumn(m_pControl->GetCurrentPos());
1888     int columnA = m_pControl->GetColumn(m_pControl->GetAnchor());
1889 
1890     // one undo action for all modifications in this context
1891     // (angled braces added for clarity)
1892     m_pControl->BeginUndoAction();
1893     {
1894         if (m_pData->m_strip_trailing_spaces)
1895             m_pData->StripTrailingSpaces();
1896         if (m_pData->m_ensure_consistent_line_ends)
1897             m_pData->EnsureConsistentLineEnds();
1898         if (m_pData->m_ensure_final_line_end)
1899             m_pData->EnsureFinalLineEnd();
1900     }
1901     m_pControl->EndUndoAction();
1902 
1903     // restore virtual position ( if changed by StripTrailingSpaces() )
1904     columnC -= m_pControl->GetColumn(m_pControl->GetCurrentPos());
1905     columnA -= m_pControl->GetColumn(m_pControl->GetAnchor());
1906     if (columnC > 0)
1907         m_pControl->SetSelectionNCaretVirtualSpace(0,  columnC);
1908     if (columnA > 0)
1909         m_pControl->SetSelectionNAnchorVirtualSpace(0, columnA);
1910 
1911     if (!m_IsOK)
1912         return SaveAs();
1913 
1914     m_pControl->BeginUndoAction();
1915     NotifyPlugins(cbEVT_EDITOR_BEFORE_SAVE);
1916     m_pControl->EndUndoAction();
1917 
1918     ConfigManager *cfg = Manager::Get()->GetConfigManager(wxT("app"));
1919     const bool robustSave = cfg->ReadBool(wxT("/environment/robust_save"), true);
1920 
1921     if (!cbSaveToFile(m_Filename, m_pControl->GetText(), GetEncoding(), GetUseBom(), robustSave))
1922     {
1923         wxString msg;
1924         msg.Printf(_("File %s could not be saved..."), GetFilename().c_str());
1925         cbMessageBox(msg, _("Error saving file"), wxICON_ERROR);
1926         return false; // failed; file is read-only?
1927     }
1928 
1929     wxFileName fname(m_Filename);
1930     m_LastModified = fname.GetModificationTime();
1931 
1932     m_IsOK = true;
1933 
1934     m_pControl->SetSavePoint();
1935     SetModified(false);
1936 
1937     NotifyPlugins(cbEVT_EDITOR_SAVE);
1938     return true;
1939 } // end of Save
1940 
SaveAs()1941 bool cbEditor::SaveAs()
1942 {
1943     wxFileName fname;
1944     fname.Assign(m_Filename);
1945     ConfigManager* mgr = Manager::Get()->GetConfigManager(_T("app"));
1946     int StoredIndex = 0;
1947     wxString Filters = FileFilters::GetFilterString();
1948     wxString Path = fname.GetPath();
1949     wxString Extension = fname.GetExt();
1950     wxString Filter;
1951     if (!Extension.IsEmpty())
1952     {    // use the current extension as the filter
1953         // Select filter belonging to this file type:
1954         Extension.Prepend(_T("."));
1955         Filter = FileFilters::GetFilterString(Extension);
1956     }
1957     else if (mgr)
1958     {
1959         // File type is unknown. Select the last used filter:
1960         Filter = mgr->Read(_T("/file_dialogs/save_file_as/filter"), _T("C/C++ files"));
1961     }
1962     if (!Filter.IsEmpty())
1963     {
1964         // We found a filter, look up its index:
1965         int sep = Filter.find(_T("|"));
1966         if (sep != wxNOT_FOUND)
1967             Filter.Truncate(sep);
1968         if (!Filter.IsEmpty())
1969             FileFilters::GetFilterIndexFromName(Filters, Filter, StoredIndex);
1970     }
1971     if (mgr && Path.IsEmpty())
1972         Path = mgr->Read(_T("/file_dialogs/save_file_as/directory"), Path);
1973 
1974     wxFileDialog dlg(Manager::Get()->GetAppWindow(), _("Save file"), Path, fname.GetFullName(),
1975                      wxEmptyString, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
1976     // Initialize the wildcards here. If we do it in the wxFileDialog constructor it will detect
1977     // that our file name doesn't have extension and it will try to add one. It doens't have a
1978     // correct value for the filter index we want, so it uses the first one which is ads for Ada
1979     // files. This is happening only on wxGTK.
1980     dlg.SetWildcard(Filters);
1981     dlg.SetFilterIndex(StoredIndex);
1982     PlaceWindow(&dlg);
1983     if (dlg.ShowModal() != wxID_OK)
1984     {  // cancelled out
1985         return false;
1986     }
1987     m_Filename = dlg.GetPath();
1988     Manager::Get()->GetLogManager()->Log(m_Filename);
1989     fname.Assign(m_Filename);
1990     m_Shortname = fname.GetFullName();
1991     SetEditorTitle(m_Shortname);
1992     // invalidate m_pProjectFile, because if kept, it would point to the ProjectFile with old name and
1993     // cause ProjectManager::RemoveFileFromProject called via context menu to crash
1994     SetProjectFile(nullptr);
1995     //Manager::Get()->GetLogManager()->Log(mltDevDebug, "Filename=%s\nShort=%s", m_Filename.c_str(), m_Shortname.c_str());
1996     m_IsOK = true;
1997     SetLanguage(HL_AUTO, true);
1998     SetModified(true);
1999     SetEditorStyleAfterFileOpen();
2000     OverrideUseTabsPerLanguage(m_pControl);
2001     OverrideUseTabsPerLanguage(m_pControl2);
2002     // store the last used filter and directory
2003     if (mgr)
2004     {
2005         int Index = dlg.GetFilterIndex();
2006         Filter.Empty();
2007         if (FileFilters::GetFilterNameFromIndex(Filters, Index, Filter))
2008             mgr->Write(_T("/file_dialogs/save_file_as/filter"), Filter);
2009         wxString Test = dlg.GetDirectory();
2010         mgr->Write(_T("/file_dialogs/save_file_as/directory"), dlg.GetDirectory());
2011     }
2012     return Save();
2013 } // end of SaveAs
2014 
SaveFoldState()2015 bool cbEditor::SaveFoldState()
2016 {
2017     bool bRet = false;
2018     if ((m_foldBackup = CreateEditor()))
2019     {
2020         ApplyStyles(m_foldBackup);
2021         m_foldBackup->SetText(m_pControl->GetText());
2022         int count = m_pControl->GetLineCount();
2023         for (int i = 0; i < count; ++i)
2024             m_foldBackup->SetFoldLevel(i,m_pControl->GetFoldLevel(i));
2025         bRet = true;
2026     }
2027     return bRet;
2028 } // end of SaveFoldState
2029 
FixFoldState()2030 bool cbEditor::FixFoldState()
2031 {
2032     bool bRet = false;
2033     if (m_foldBackup)
2034     {
2035         int backupLength = m_foldBackup->GetLineCount();
2036         int realLength = m_pControl->GetLineCount();
2037         if (backupLength == realLength) // It is supposed to be the same, but you never know :)
2038         {
2039             ConfigManager* mgr = Manager::Get()->GetConfigManager(_T("editor"));
2040             if (mgr->ReadBool(_T("/folding/show_folds"), true)) // Only fix the folds if the folds are enabled
2041             {
2042                 int count = m_pControl->GetLineCount();
2043                 for (int i = 0; i < count; ++i)
2044                 {
2045                     int oldFoldLevel = m_foldBackup->GetFoldLevel(i);
2046                     int newFoldLevel = m_pControl->GetFoldLevel(i);
2047                     if (oldFoldLevel != newFoldLevel)
2048                     {
2049                         if (m_pControl->GetLineVisible(i) == true)
2050                             m_pControl->SetFoldExpanded(i, true);
2051                         else
2052                         {
2053                             int parent = m_foldBackup->GetFoldParent(i);
2054                             while(parent != -1)
2055                             {
2056                                 m_pControl->ToggleFold(parent);
2057                                 parent = m_foldBackup->GetFoldParent(parent);
2058                             }
2059                             m_pControl->ShowLines(i, i);
2060                             parent = m_foldBackup->GetFoldParent(i);
2061                             while(parent != -1)
2062                             {
2063                                 m_pControl->ToggleFold(parent);
2064                                 parent = m_foldBackup->GetFoldParent(parent);
2065                             }
2066                         }
2067                     }
2068                 }
2069             }
2070             bRet = true;
2071         }
2072         m_foldBackup->Destroy();
2073         m_foldBackup = nullptr;
2074     }
2075     return bRet;
2076 } // end of FixFoldState
2077 
AutoComplete()2078 void cbEditor::AutoComplete()
2079 {
2080     Manager::Get()->GetLogManager()->Log(_T("cbEditor::AutoComplete() is obsolete.\nUse AutoComplete(cbEditor &ed) from the Abbreviations plugin instead."));
2081 }
2082 
DoFoldAll(FoldMode fold)2083 void cbEditor::DoFoldAll(FoldMode fold)
2084 {
2085     cbAssert(m_pControl);
2086     if (m_SplitType != stNoSplit)
2087         cbAssert(m_pControl2);
2088     cbStyledTextCtrl* ctrl = GetControl();
2089 
2090     ctrl->Colourise(0, -1); // the *most* important part!
2091 
2092     if (fold != FoldMode::toggle)
2093     {
2094         const int count = ctrl->GetLineCount();
2095         for (int line = 0; line < count; ++line)
2096         {
2097             const int level = ctrl->GetFoldLevel(line);
2098             if ((level & wxSCI_FOLDLEVELHEADERFLAG) == 0)
2099                 continue;
2100 
2101             if (m_pData->mFoldingLimit)
2102             {
2103                 const bool isExpanded = ctrl->GetFoldExpanded(line);
2104 
2105                 // Apply the folding level limit only if the current block will be
2106                 // folded (that means it's currently expanded), folding level limiter
2107                 // must be enabled of course. Unfolding will not be affected.
2108                 if (isExpanded && (fold == FoldMode::contract || fold == FoldMode::toggle))
2109                 {
2110                     int levelNumber = (level & wxSCI_FOLDLEVELNUMBERMASK) - wxSCI_FOLDLEVELBASE;
2111                     if (levelNumber > m_pData->mFoldingLimitLevel - 1)
2112                         continue;
2113                 }
2114             }
2115 
2116             ctrl->FoldLine(line, int(fold));
2117         }
2118     }
2119     else
2120     {
2121         // Toggle implementation - we need this one because the implementation for fold/unfold
2122         // doesn't work correctly - it expands parent folds when there is an expanded child fold.
2123         // We want to toggle all fold points in the editor no matter what level are they at. If
2124         // there is a folding limit we respect it for both contracting and expanding.
2125 
2126         struct FoldRange
2127         {
2128             FoldRange(int start, int end, bool hasContracted) :
2129                 start(start),
2130                 end(end),
2131                 hasContracted(hasContracted)
2132             {}
2133 
2134             int start, end;
2135             bool hasContracted;
2136         };
2137         // We need to know if a parent fold has been contracted. For this we use a stack with the
2138         // active folds at the line we are processing.
2139         std::stack<FoldRange> parentFolds;
2140 
2141         const int count = ctrl->GetLineCount();
2142         for (int line = 0; line < count; ++line)
2143         {
2144             while (!parentFolds.empty() && parentFolds.top().end < line)
2145                 parentFolds.pop();
2146 
2147             const int level = ctrl->GetFoldLevel(line);
2148             if ((level & wxSCI_FOLDLEVELHEADERFLAG) == 0)
2149                 continue;
2150             const int levelNumber = (level & wxSCI_FOLDLEVELNUMBERMASK) - wxSCI_FOLDLEVELBASE;
2151             if (m_pData->mFoldingLimit)
2152             {
2153                 // Apply folding level limit.
2154                 if (levelNumber > m_pData->mFoldingLimitLevel - 1)
2155                     continue;
2156             }
2157 
2158             const bool toContract = ctrl->GetFoldExpanded(line);
2159             const int lastChild = ctrl->GetLastChild(line, -1);
2160 
2161             bool hasContracted;
2162             if (toContract)
2163                 hasContracted = true;
2164             else
2165             {
2166                 if (parentFolds.empty())
2167                     hasContracted = false;
2168                 else
2169                     hasContracted = parentFolds.top().hasContracted;
2170             }
2171             parentFolds.push(FoldRange(line, lastChild, hasContracted));
2172 
2173             if (toContract)
2174             {
2175                 ctrl->SetFoldExpanded(line, false);
2176                 ctrl->HideLines(line + 1, lastChild);
2177             }
2178             else
2179             {
2180                 ctrl->SetFoldExpanded(line, true);
2181                 // If there are contracted parent folds we must not show lines for child folds even
2182                 // if they are expanded. When the user does an expand operation for the parent
2183                 // Scintilla will make sure to show these lines.
2184                 if (!hasContracted)
2185                     ctrl->ShowLines(line + 1, lastChild);
2186             }
2187         }
2188     }
2189 }
2190 
DoFoldBlockFromLine(int line,FoldMode fold,unsigned foldFlags)2191 void cbEditor::DoFoldBlockFromLine(int line, FoldMode fold, unsigned foldFlags)
2192 {
2193     // Use static asserts to verify the constants are the same, because we don't want to include
2194     // wxscintilla.h in cbeditor.h. This code is here, because we want the Foldmode to be private.
2195     static_assert(int(cbEditor::FoldMode::contract) == wxSCI_FOLDACTION_CONTRACT, "Must match");
2196     static_assert(int(cbEditor::FoldMode::expand) == wxSCI_FOLDACTION_EXPAND, "Must match");
2197     static_assert(int(cbEditor::FoldMode::toggle) == wxSCI_FOLDACTION_TOGGLE, "Must match");
2198 
2199     cbAssert(m_pControl);
2200     if (m_SplitType != stNoSplit)
2201         cbAssert(m_pControl2);
2202     cbStyledTextCtrl* ctrl = GetControl();
2203     if (!ctrl)
2204         return;
2205 
2206     // This is needed to update the folding information for the current view.
2207     ctrl->Colourise(0, -1);
2208 
2209     int foldLine;
2210     int level = ctrl->GetFoldLevel(line);
2211     if (level & wxSCI_FOLDLEVELHEADERFLAG)
2212         foldLine = line;
2213     else
2214     {
2215         // If the line is not a block start line, find the block start. This makes it possible to
2216         // toggle the folding when the cursor is in the middle of some block of code.
2217         foldLine = ctrl->GetFoldParent(line);
2218         if (foldLine == -1)
2219             return;
2220     }
2221     const bool isExpanded = ctrl->GetFoldExpanded(foldLine);
2222 
2223     ctrl->FoldLine(foldLine, int(fold));
2224 
2225     if (foldFlags & FoldFlags::ensureVisible)
2226     {
2227         if (fold == FoldMode::expand || (fold == FoldMode::toggle && !isExpanded))
2228             ctrl->EnsureVisibleEnforcePolicy(line);
2229     }
2230 }
2231 
FoldAll()2232 void cbEditor::FoldAll()
2233 {
2234     DoFoldAll(FoldMode::contract);
2235 }
2236 
UnfoldAll()2237 void cbEditor::UnfoldAll()
2238 {
2239     DoFoldAll(FoldMode::expand);
2240 }
2241 
ToggleAllFolds()2242 void cbEditor::ToggleAllFolds()
2243 {
2244     DoFoldAll(FoldMode::toggle);
2245 }
2246 
SetFoldingIndicator(int id)2247 void cbEditor::SetFoldingIndicator(int id)
2248 {
2249     wxColor f(0xff, 0xff, 0xff); // foreground colour
2250     wxColor b(0x80, 0x80, 0x80); // background colour
2251     // Arrow
2252     if (id == 0)
2253     {
2254         SetMarkerStyle(wxSCI_MARKNUM_FOLDEROPEN,    wxSCI_MARK_ARROWDOWN,  f, b);
2255         SetMarkerStyle(wxSCI_MARKNUM_FOLDER,        wxSCI_MARK_ARROW,      f, b);
2256         SetMarkerStyle(wxSCI_MARKNUM_FOLDERSUB,     wxSCI_MARK_BACKGROUND, f, b);
2257         SetMarkerStyle(wxSCI_MARKNUM_FOLDERTAIL,    wxSCI_MARK_BACKGROUND, f, b);
2258         SetMarkerStyle(wxSCI_MARKNUM_FOLDEREND,     wxSCI_MARK_ARROW,      f, b);
2259         SetMarkerStyle(wxSCI_MARKNUM_FOLDEROPENMID, wxSCI_MARK_ARROWDOWN,  f, b);
2260         SetMarkerStyle(wxSCI_MARKNUM_FOLDERMIDTAIL, wxSCI_MARK_BACKGROUND, f, b);
2261     }
2262     // Circle
2263     else if (id == 1)
2264     {
2265         SetMarkerStyle(wxSCI_MARKNUM_FOLDEROPEN,    wxSCI_MARK_CIRCLEMINUS,          f, b);
2266         SetMarkerStyle(wxSCI_MARKNUM_FOLDER,        wxSCI_MARK_CIRCLEPLUS,           f, b);
2267         SetMarkerStyle(wxSCI_MARKNUM_FOLDERSUB,     wxSCI_MARK_VLINE,                f, b);
2268         SetMarkerStyle(wxSCI_MARKNUM_FOLDERTAIL,    wxSCI_MARK_LCORNERCURVE,         f, b);
2269         SetMarkerStyle(wxSCI_MARKNUM_FOLDEREND,     wxSCI_MARK_CIRCLEPLUSCONNECTED,  f, b);
2270         SetMarkerStyle(wxSCI_MARKNUM_FOLDEROPENMID, wxSCI_MARK_CIRCLEMINUSCONNECTED, f, b);
2271         SetMarkerStyle(wxSCI_MARKNUM_FOLDERMIDTAIL, wxSCI_MARK_TCORNER,              f, b);
2272     }
2273     // Square
2274     else if (id == 2)
2275     {
2276         SetMarkerStyle(wxSCI_MARKNUM_FOLDEROPEN,    wxSCI_MARK_BOXMINUS,          f, b);
2277         SetMarkerStyle(wxSCI_MARKNUM_FOLDER,        wxSCI_MARK_BOXPLUS,           f, b);
2278         SetMarkerStyle(wxSCI_MARKNUM_FOLDERSUB,     wxSCI_MARK_VLINE,             f, b);
2279         SetMarkerStyle(wxSCI_MARKNUM_FOLDERTAIL,    wxSCI_MARK_LCORNER,           f, b);
2280         SetMarkerStyle(wxSCI_MARKNUM_FOLDEREND,     wxSCI_MARK_BOXPLUSCONNECTED,  f, b);
2281         SetMarkerStyle(wxSCI_MARKNUM_FOLDEROPENMID, wxSCI_MARK_BOXMINUSCONNECTED, f, b);
2282         SetMarkerStyle(wxSCI_MARKNUM_FOLDERMIDTAIL, wxSCI_MARK_TCORNER,           f, b);
2283     }
2284     // Simple
2285     else if (id == 3)
2286     {
2287         SetMarkerStyle(wxSCI_MARKNUM_FOLDEROPEN,    wxSCI_MARK_MINUS,      f, b);
2288         SetMarkerStyle(wxSCI_MARKNUM_FOLDER,        wxSCI_MARK_PLUS,       f, b);
2289         SetMarkerStyle(wxSCI_MARKNUM_FOLDERSUB,     wxSCI_MARK_BACKGROUND, f, b);
2290         SetMarkerStyle(wxSCI_MARKNUM_FOLDERTAIL,    wxSCI_MARK_BACKGROUND, f, b);
2291         SetMarkerStyle(wxSCI_MARKNUM_FOLDEREND,     wxSCI_MARK_PLUS,       f, b);
2292         SetMarkerStyle(wxSCI_MARKNUM_FOLDEROPENMID, wxSCI_MARK_MINUS,      f, b);
2293         SetMarkerStyle(wxSCI_MARKNUM_FOLDERMIDTAIL, wxSCI_MARK_BACKGROUND, f, b);
2294     }
2295 }
2296 
FoldBlockFromLine(int line)2297 void cbEditor::FoldBlockFromLine(int line)
2298 {
2299     if (line == -1)
2300         line = GetControl()->GetCurrentLine();
2301     DoFoldBlockFromLine(line, FoldMode::contract, FoldFlags::ensureVisible);
2302 }
2303 
UnfoldBlockFromLine(int line)2304 void cbEditor::UnfoldBlockFromLine(int line)
2305 {
2306     if (line == -1)
2307         line = GetControl()->GetCurrentLine();
2308     DoFoldBlockFromLine(line, FoldMode::expand, FoldFlags::ensureVisible);
2309 }
2310 
ToggleFoldBlockFromLine(int line)2311 void cbEditor::ToggleFoldBlockFromLine(int line)
2312 {
2313     if (line == -1)
2314         line = GetControl()->GetCurrentLine();
2315     DoFoldBlockFromLine(line, FoldMode::toggle, FoldFlags::ensureVisible);
2316 }
2317 
GotoLine(int line,cb_unused bool centerOnScreen)2318 void cbEditor::GotoLine(int line, cb_unused bool centerOnScreen)
2319 {
2320     cbStyledTextCtrl* control = GetControl();
2321 
2322     // Make sure the line is not folded. This is done before moving to that
2323     // line because folding may change the lines layout.
2324     control->EnsureVisible(line);
2325 
2326     // If the line or the following is a fold point it will be unfolded, in this way
2327     // when the line is a function declaration (or only contains the opening brace of it [yes, that happens sometimes] )
2328     // the body is shown.
2329     DoFoldBlockFromLine(line, FoldMode::expand, FoldFlags::none);
2330     DoFoldBlockFromLine(line+1, FoldMode::expand, FoldFlags::none);
2331 
2332     control->GotoLine(line);
2333 }
2334 
GotoTokenPosition(int line,const wxString & tokenName)2335 bool cbEditor::GotoTokenPosition(int line, const wxString& tokenName)
2336 {
2337     cbStyledTextCtrl* control = GetControl();
2338     if (line > control->GetLineCount())
2339         return false;
2340 
2341     GotoLine(line, true); // center function on screen
2342     SetFocus();           // ...and set focus to this editor
2343 
2344     // Now highlight the token
2345     const int startPos = control->GetCurrentPos();
2346     const int endPos   = startPos + control->LineLength(line);
2347     if (endPos <= startPos)
2348         return false;
2349 
2350     int tokenPos = control->FindText(startPos, endPos, tokenName,
2351                                      wxSCI_FIND_WHOLEWORD | wxSCI_FIND_MATCHCASE, nullptr);
2352     if (tokenPos != wxSCI_INVALID_POSITION)
2353         control->SetSelectionInt(tokenPos, tokenPos + tokenName.Len());
2354     else
2355         control->GotoPos(startPos); // fall back, point the cursor to it
2356 
2357     return true;
2358 }
2359 
BreakpointMarkerToggle(int line)2360 void cbEditor::BreakpointMarkerToggle(int line)
2361 {
2362     int marker = m_pControl->MarkerGet(line);
2363     if      (marker & (1 << BREAKPOINT_MARKER))
2364         m_pControl->MarkerDelete(line, BREAKPOINT_MARKER);
2365     else if (marker & (1 << BREAKPOINT_DISABLED_MARKER))
2366         m_pControl->MarkerDelete(line, BREAKPOINT_DISABLED_MARKER);
2367     else
2368         m_pControl->MarkerAdd(line, BREAKPOINT_MARKER);
2369 }
2370 
AddBreakpoint(int line,bool notifyDebugger)2371 bool cbEditor::AddBreakpoint(int line, bool notifyDebugger)
2372 {
2373     if (HasBreakpoint(line))
2374         return false;
2375 
2376     if (line == -1)
2377         line = GetControl()->GetCurrentLine();
2378 
2379     if (!notifyDebugger)
2380     {
2381         BreakpointMarkerToggle(line);
2382         return false;
2383     }
2384 
2385     DebuggerManager *dbgManager = Manager::Get()->GetDebuggerManager();
2386     if (dbgManager->GetBreakpointDialog()->AddBreakpoint(dbgManager->GetActiveDebugger(), m_Filename, line + 1))
2387     {
2388         BreakpointMarkerToggle(line);
2389         return true;
2390     }
2391     return false;
2392 }
2393 
RemoveBreakpoint(int line,bool notifyDebugger)2394 bool cbEditor::RemoveBreakpoint(int line, bool notifyDebugger)
2395 {
2396     if (!HasBreakpoint(line))
2397         return false;
2398 
2399     if (line == -1)
2400         line = GetControl()->GetCurrentLine();
2401 
2402     if (!notifyDebugger)
2403     {
2404         BreakpointMarkerToggle(line);
2405         return false;
2406     }
2407 
2408     DebuggerManager *dbgManager = Manager::Get()->GetDebuggerManager();
2409     if (dbgManager->GetBreakpointDialog()->RemoveBreakpoint(dbgManager->GetActiveDebugger(), m_Filename, line + 1))
2410     {
2411         BreakpointMarkerToggle(line);
2412         return true;
2413     }
2414     return false;
2415 }
2416 
ToggleBreakpoint(int line,bool notifyDebugger)2417 void cbEditor::ToggleBreakpoint(int line, bool notifyDebugger)
2418 {
2419     if (line == -1)
2420         line = GetControl()->GetCurrentLine();
2421     if (!notifyDebugger)
2422     {
2423         BreakpointMarkerToggle(line);
2424         return;
2425     }
2426 
2427     DebuggerManager *dbgManager = Manager::Get()->GetDebuggerManager();
2428     cbBreakpointsDlg *dialog = dbgManager->GetBreakpointDialog();
2429     cbDebuggerPlugin *plugin = dbgManager->GetActiveDebugger();
2430     if (!plugin || !plugin->SupportsFeature(cbDebuggerFeature::Breakpoints))
2431         return;
2432 
2433     bool toggle = false;
2434     if (HasBreakpoint(line))
2435     {
2436         if (dialog->RemoveBreakpoint(plugin, m_Filename, line + 1))
2437             toggle = true;
2438     }
2439     else
2440     {
2441         if (dialog->AddBreakpoint(plugin, m_Filename, line + 1))
2442             toggle = true;
2443     }
2444 
2445     if (toggle)
2446     {
2447         BreakpointMarkerToggle(line);
2448         dialog->Reload();
2449     }
2450 }
2451 
HasBreakpoint(int line) const2452 bool cbEditor::HasBreakpoint(int line) const
2453 {
2454     if (line == -1)
2455         line = GetControl()->GetCurrentLine();
2456     return LineHasMarker(BREAKPOINT_MARKER, line) || LineHasMarker(BREAKPOINT_DISABLED_MARKER, line);
2457 }
2458 
GotoNextBreakpoint()2459 void cbEditor::GotoNextBreakpoint()
2460 {
2461     MarkerNext(BREAKPOINT_MARKER);
2462 }
2463 
GotoPreviousBreakpoint()2464 void cbEditor::GotoPreviousBreakpoint()
2465 {
2466     MarkerPrevious(BREAKPOINT_MARKER);
2467 }
2468 
ToggleBookmark(int line)2469 void cbEditor::ToggleBookmark(int line)
2470 {
2471     MarkerToggle(BOOKMARK_MARKER, line);
2472 }
2473 
RefreshBreakpointMarkers()2474 void cbEditor::RefreshBreakpointMarkers()
2475 {
2476     // First remove all breakpoint markers, then add the markers for the active debugger
2477     cbStyledTextCtrl *c = GetControl();
2478     int line = 0;
2479     while ((line = c->MarkerNext(line, (1 << BREAKPOINT_MARKER))) != -1)
2480         MarkerToggle(BREAKPOINT_MARKER, line);
2481 
2482     line = 0;
2483     while ((line = c->MarkerNext(line, (1 << BREAKPOINT_DISABLED_MARKER))) != -1)
2484         MarkerToggle(BREAKPOINT_DISABLED_MARKER, line);
2485 
2486     line = 0;
2487     while ((line = c->MarkerNext(line, (1 << BREAKPOINT_OTHER_MARKER))) != -1)
2488         MarkerToggle(BREAKPOINT_OTHER_MARKER, line);
2489 
2490     const DebuggerManager::RegisteredPlugins &plugins = Manager::Get()->GetDebuggerManager()->GetAllDebuggers();
2491     for (DebuggerManager::RegisteredPlugins::const_iterator it = plugins.begin(); it != plugins.end(); ++it)
2492     {
2493         const cbDebuggerPlugin *debugger = it->first;
2494         if (debugger == Manager::Get()->GetDebuggerManager()->GetActiveDebugger())
2495         {
2496             for (int ii = 0; ii < debugger->GetBreakpointsCount(); ++ii)
2497             {
2498                 cb::shared_ptr<const cbBreakpoint> bp = debugger->GetBreakpoint(ii);
2499                 if (bp->GetLocation() == GetFilename())
2500                 {
2501                     if (bp->IsEnabled())
2502                         MarkerToggle(BREAKPOINT_MARKER,          bp->GetLine() - 1);
2503                     else
2504                         MarkerToggle(BREAKPOINT_DISABLED_MARKER, bp->GetLine() - 1);
2505                 }
2506             }
2507         }
2508         else
2509         {
2510             // all breakpoints for the non active debugger use the other breakpoint marker
2511             for (int ii = 0; ii < debugger->GetBreakpointsCount(); ++ii)
2512             {
2513                 cb::shared_ptr<const cbBreakpoint> bp = debugger->GetBreakpoint(ii);
2514                 if (bp->GetLocation() == GetFilename())
2515                     MarkerToggle(BREAKPOINT_OTHER_MARKER, bp->GetLine() - 1);
2516             }
2517         }
2518     }
2519 }
2520 
HasBookmark(int line) const2521 bool cbEditor::HasBookmark(int line) const
2522 {
2523     return LineHasMarker(BOOKMARK_MARKER, line);
2524 }
2525 
GotoNextBookmark()2526 void cbEditor::GotoNextBookmark()
2527 {
2528     MarkerNext(BOOKMARK_MARKER);
2529 }
2530 
GotoPreviousBookmark()2531 void cbEditor::GotoPreviousBookmark()
2532 {
2533     MarkerPrevious(BOOKMARK_MARKER);
2534 }
2535 
ClearAllBookmarks()2536 void cbEditor::ClearAllBookmarks()
2537 {
2538     cbStyledTextCtrl* control = GetControl();
2539     control->MarkerDeleteAll(BOOKMARK_MARKER);
2540 }
2541 
SetDebugLine(int line)2542 void cbEditor::SetDebugLine(int line)
2543 {
2544     MarkLine(DEBUG_MARKER, line);
2545     if (GetControl()->GetCaretLineVisible())
2546         MarkLine(DEBUG_MARKER_HIGHLIGHT, line);
2547     m_pData->m_LastDebugLine = line;
2548 }
2549 
SetErrorLine(int line)2550 void cbEditor::SetErrorLine(int line)
2551 {
2552     MarkLine(ERROR_MARKER, line);
2553 }
2554 
Undo()2555 void cbEditor::Undo()
2556 {
2557     cbAssert(GetControl());
2558     GetControl()->Undo();
2559 }
2560 
Redo()2561 void cbEditor::Redo()
2562 {
2563     cbAssert(GetControl());
2564     GetControl()->Redo();
2565 }
2566 
ClearHistory()2567 void cbEditor::ClearHistory()
2568 {
2569     cbAssert(GetControl());
2570     GetControl()->EmptyUndoBuffer(Manager::Get()->GetConfigManager(_T("editor"))->ReadBool(_T("/margin/use_changebar"), true));
2571 }
2572 
GotoNextChanged()2573 void cbEditor::GotoNextChanged()
2574 {
2575     cbAssert(GetControl());
2576     cbStyledTextCtrl* p_Control = GetControl();
2577     int fromLine = p_Control->LineFromPosition(p_Control->GetCurrentPos());
2578     int toLine = p_Control->GetLineCount() - 1;
2579     if (fromLine == toLine)
2580         fromLine = 0;
2581     else
2582         fromLine++;
2583 
2584     int newLine = p_Control->FindChangedLine(fromLine, toLine);
2585     if (newLine != wxSCI_INVALID_POSITION)
2586     {
2587         p_Control->GotoLine(newLine);
2588         p_Control->MakeNearbyLinesVisible(p_Control->GetCurrentLine());
2589     }
2590 }
2591 
GotoPreviousChanged()2592 void cbEditor::GotoPreviousChanged()
2593 {
2594     cbAssert(GetControl());
2595     cbStyledTextCtrl* p_Control = GetControl();
2596     int fromLine = p_Control->LineFromPosition(p_Control->GetCurrentPos());
2597     int toLine = 0;
2598     if (fromLine == toLine)
2599         fromLine = p_Control->GetLineCount() - 1;
2600     else
2601         fromLine--;
2602 
2603     int newLine = p_Control->FindChangedLine(fromLine, toLine);
2604     if (newLine != wxSCI_INVALID_POSITION)
2605     {
2606         p_Control->GotoLine(newLine);
2607         p_Control->MakeNearbyLinesVisible(p_Control->GetCurrentLine());
2608     }
2609 }
2610 
SetChangeCollection(bool collectChange)2611 void cbEditor::SetChangeCollection(bool collectChange)
2612 {
2613     cbAssert(GetControl());
2614     GetControl()->SetChangeCollection(collectChange);
2615 }
2616 
Cut()2617 void cbEditor::Cut()
2618 {
2619     cbAssert(GetControl());
2620     GetControl()->Cut();
2621 }
2622 
Copy()2623 void cbEditor::Copy()
2624 {
2625     cbAssert(GetControl());
2626     GetControl()->Copy();
2627 }
2628 
Paste()2629 void cbEditor::Paste()
2630 {
2631     cbAssert(GetControl());
2632     GetControl()->Paste();
2633 }
2634 
CanUndo() const2635 bool cbEditor::CanUndo() const
2636 {
2637     cbAssert(GetControl());
2638     return !IsReadOnly() && GetControl()->CanUndo();
2639 }
2640 
CanRedo() const2641 bool cbEditor::CanRedo() const
2642 {
2643     cbAssert(GetControl());
2644     return !IsReadOnly() && GetControl()->CanRedo();
2645 }
2646 
HasSelection() const2647 bool cbEditor::HasSelection() const
2648 {
2649     cbAssert(GetControl());
2650     cbStyledTextCtrl* control = GetControl();
2651     return control->GetSelectionStart() != control->GetSelectionEnd();
2652 }
2653 
CanPaste() const2654 bool cbEditor::CanPaste() const
2655 {
2656     cbAssert(GetControl());
2657     if (platform::gtk)
2658         return !IsReadOnly();
2659 
2660     return GetControl()->CanPaste() && !IsReadOnly();
2661 }
2662 
IsReadOnly() const2663 bool cbEditor::IsReadOnly() const
2664 {
2665     cbAssert(GetControl());
2666     return GetControl()->GetReadOnly();
2667 }
2668 
SetReadOnly(bool readonly)2669 void cbEditor::SetReadOnly(bool readonly)
2670 {
2671     cbAssert(GetControl());
2672     GetControl()->SetReadOnly(readonly);
2673 }
2674 
LineHasMarker(int marker,int line) const2675 bool cbEditor::LineHasMarker(int marker, int line) const
2676 {
2677     if (line == -1)
2678         line = GetControl()->GetCurrentLine();
2679     return m_pControl->MarkerGet(line) & (1 << marker);
2680 }
2681 
MarkerToggle(int marker,int line)2682 void cbEditor::MarkerToggle(int marker, int line)
2683 {
2684     if (line == -1)
2685         line = GetControl()->GetCurrentLine();
2686     if (LineHasMarker(marker, line))
2687         GetControl()->MarkerDelete(line, marker);
2688     else
2689         GetControl()->MarkerAdd(line, marker);
2690 }
2691 
MarkerNext(int marker)2692 void cbEditor::MarkerNext(int marker)
2693 {
2694     int line = GetControl()->GetCurrentLine() + 1;
2695     int newLine = GetControl()->MarkerNext(line, 1 << marker);
2696 
2697     // Start from beginning if at last marker.
2698     if (newLine == -1)
2699     {
2700         line = 0;
2701         newLine = GetControl()->MarkerNext(line, 1 << marker);
2702 
2703         if (newLine != -1)
2704             InfoWindow::Display(_("Find bookmark action"), _("Reached the end of the document"), 1000);
2705     }
2706 
2707     if (newLine != -1)
2708         GotoLine(newLine);
2709 }
2710 
MarkerPrevious(int marker)2711 void cbEditor::MarkerPrevious(int marker)
2712 {
2713     int line = GetControl()->GetCurrentLine() - 1;
2714     int newLine = GetControl()->MarkerPrevious(line, 1 << marker);
2715 
2716     // Back to last one if at first marker.
2717     if (newLine == -1)
2718     {
2719         line = GetControl()->GetLineCount();
2720         newLine = GetControl()->MarkerPrevious(line, 1 << marker);
2721 
2722         if (newLine != -1)
2723             InfoWindow::Display(_("Find bookmark action"), _("Reached the end of the document"), 1000);
2724     }
2725 
2726     if (newLine != -1)
2727         GotoLine(newLine);
2728 }
2729 
MarkLine(int marker,int line)2730 void cbEditor::MarkLine(int marker, int line)
2731 {
2732     if (line == -1)
2733         GetControl()->MarkerDeleteAll(marker);
2734     else
2735         GetControl()->MarkerAdd(line, marker);
2736 }
2737 
GotoMatchingBrace()2738 void cbEditor::GotoMatchingBrace()
2739 {
2740     cbStyledTextCtrl* control = GetControl();
2741 
2742     // this works only when the caret is *before* the brace
2743     int matchingBrace = control->BraceMatch(control->GetCurrentPos());
2744 
2745     // if we haven't found it, we 'll search at pos-1 too
2746     if (matchingBrace == wxSCI_INVALID_POSITION)
2747         matchingBrace = control->BraceMatch(control->GetCurrentPos() - 1);
2748     else
2749         ++matchingBrace; // to keep the caret on the same side of the brace
2750 
2751     // else look for a matching preprocessor command
2752     if (matchingBrace == wxSCI_INVALID_POSITION)
2753     {
2754         wxRegEx ppIf(wxT("^[ \t]*#[ \t]*if"));
2755         wxRegEx ppElse(wxT("^[ \t]*#[ \t]*el"));
2756         wxRegEx ppEnd(wxT("^[ \t]*#[ \t]*endif"));
2757         wxRegEx pp(wxT("^[ \t]*#[ \t]*[a-z]*")); // generic match to get length
2758         if (ppIf.Matches(control->GetCurLine()) || ppElse.Matches(control->GetCurLine()))
2759         {
2760             int depth = 1; // search forwards
2761             for (int i = control->GetCurrentLine() + 1; i < control->GetLineCount(); ++i)
2762             {
2763                 if (control->GetLine(i).Find(wxT('#')) != wxNOT_FOUND) // limit testing due to performance cost
2764                 {
2765                     if (ppIf.Matches(control->GetLine(i))) // ignore else's, elif's, ...
2766                         ++depth;
2767                     else if (ppEnd.Matches(control->GetLine(i)))
2768                         --depth;
2769                 }
2770                 if (depth == 0)
2771                 {
2772                     pp.Matches(control->GetLine(i));
2773                     matchingBrace = control->PositionFromLine(i) + pp.GetMatch(control->GetLine(i)).Length();
2774                     break;
2775                 }
2776             }
2777         }
2778         else if (ppEnd.Matches(control->GetCurLine()))
2779         {
2780             int depth = -1; // search backwards
2781             for (int i = control->GetCurrentLine() - 1; i >= 0; --i)
2782             {
2783                 if (control->GetLine(i).Find(wxT('#')) != wxNOT_FOUND) // limit testing due to performance cost
2784                 {
2785                     if (ppIf.Matches(control->GetLine(i))) // ignore else's, elif's, ...
2786                         ++depth;
2787                     else if (ppEnd.Matches(control->GetLine(i)))
2788                         --depth;
2789                 }
2790                 if (depth == 0)
2791                 {
2792                     pp.Matches(control->GetLine(i));
2793                     matchingBrace = control->PositionFromLine(i) + pp.GetMatch(control->GetLine(i)).Length();
2794                     break;
2795                 }
2796             }
2797         }
2798     }
2799 
2800     // now, we either found it or not
2801     if (matchingBrace != wxSCI_INVALID_POSITION)
2802     {
2803         // move to the actual position
2804         control->GotoPos(matchingBrace);
2805         control->ChooseCaretX();
2806         // make nearby lines visible
2807         control->MakeNearbyLinesVisible(control->GetCurrentLine());
2808     }
2809 }
2810 
HighlightBraces()2811 void cbEditor::HighlightBraces()
2812 {
2813     cbStyledTextCtrl* control = GetControl();
2814 
2815     ////// BRACES HIGHLIGHTING ///////
2816     int currPos = control->GetCurrentPos();
2817     int newPos = control->BraceMatch(currPos);
2818     if (newPos == wxSCI_INVALID_POSITION)
2819     {
2820         if (currPos > 0)
2821             currPos--;
2822         newPos = control->BraceMatch(currPos);
2823     }
2824     wxChar ch = control->GetCharAt(currPos);
2825     if (ch == _T('{') || ch == _T('[') || ch == _T('(') ||
2826         ch == _T('}') || ch == _T(']') || ch == _T(')'))
2827     {
2828         if (newPos != wxSCI_INVALID_POSITION)
2829         {
2830             control->BraceHighlight(currPos, newPos);
2831             const int currColum = control->GetColumn(currPos);
2832             const int newColum = control->GetColumn(newPos);
2833             control->SetHighlightGuide((currColum < newColum) ? currColum :newColum);
2834         }
2835         else
2836         {
2837             control->BraceBadLight(currPos);
2838         }
2839     }
2840     else
2841     {
2842         control->BraceHighlight(-1, -1);
2843     }
2844 }
2845 
GetLineIndentInSpaces(int line) const2846 int cbEditor::GetLineIndentInSpaces(int line) const
2847 {
2848     cbStyledTextCtrl* control = GetControl();
2849     int currLine = (line == -1)
2850                     ? control->LineFromPosition(control->GetCurrentPos())
2851                     : line;
2852     wxString text = control->GetLine(currLine);
2853     unsigned int len = text.Length();
2854     int spaceCount = 0;
2855     for (unsigned int i = 0; i < len; ++i)
2856     {
2857         if (text[i] == _T(' '))
2858         {
2859             ++spaceCount;
2860         }
2861         else if (text[i] == _T('\t'))
2862         {
2863             spaceCount += control->GetTabWidth();
2864         }
2865         else
2866         {
2867             break;
2868         }
2869     }
2870     return spaceCount;
2871 }
2872 
GetLineIndentString(int line) const2873 wxString cbEditor::GetLineIndentString(int line) const
2874 {
2875     return cbEditorInternalData::GetLineIndentString(line, GetControl());
2876 }
2877 
2878 // Creates a submenu for a Context Menu based on the submenu's specific Id
CreateContextSubMenu(long id)2879 wxMenu* cbEditor::CreateContextSubMenu(long id)
2880 {
2881     wxMenu* menu = nullptr;
2882     if (id == idInsert)
2883     {
2884         menu = new wxMenu;
2885         menu->Append(idEmptyMenu, _("Empty"));
2886         menu->Enable(idEmptyMenu, false);
2887     }
2888     else if (id == idEdit)
2889     {
2890         menu = new wxMenu;
2891         menu->Append(idUndo, _("Undo"));
2892         menu->Append(idRedo, _("Redo"));
2893         menu->Append(idClearHistory, _("Clear changes history"));
2894         menu->AppendSeparator();
2895         menu->Append(idDelete, _("Delete"));
2896         menu->Append(idSelectAll, _("Select all"));
2897         menu->AppendSeparator();
2898         menu->Append(idUpperCase, _("UPPERCASE"));
2899         menu->Append(idLowerCase, _("lowercase"));
2900 
2901         cbStyledTextCtrl* control = GetControl();
2902 
2903         menu->Enable(idUndo, control->CanUndo());
2904         menu->Enable(idRedo, control->CanRedo());
2905         menu->Enable(idClearHistory, control->CanUndo() || control->CanRedo());
2906 
2907         const bool hasSelection = !control->GetSelectionEmpty();
2908         menu->Enable(idDelete, !control->GetReadOnly() && hasSelection);
2909         menu->Enable(idUpperCase, !control->GetReadOnly() && hasSelection);
2910         menu->Enable(idLowerCase, !control->GetReadOnly() && hasSelection);
2911     }
2912     else if (id == idBookmarks)
2913     {
2914         menu = new wxMenu;
2915         menu->Append(idBookmarksToggle, _("Toggle bookmark"));
2916         menu->Append(idBookmarksPrevious, _("Goto previous bookmark"));
2917         menu->Append(idBookmarksNext, _("Goto next bookmark"));
2918         menu->Append(idBookmarksClearAll, _("Clear all bookmarks"));
2919     }
2920     else if (id == idFolding)
2921     {
2922         menu = new wxMenu;
2923         menu->Append(idFoldingFoldAll, _("Fold all"));
2924         menu->Append(idFoldingUnfoldAll, _("Unfold all"));
2925         menu->Append(idFoldingToggleAll, _("Toggle all"));
2926         menu->AppendSeparator();
2927         menu->Append(idFoldingFoldCurrent, _("Fold current block"));
2928         menu->Append(idFoldingUnfoldCurrent, _("Unfold current block"));
2929         menu->Append(idFoldingToggleCurrent, _("Toggle current block"));
2930     }
2931     else
2932         menu = EditorBase::CreateContextSubMenu(id);
2933 
2934     return menu;
2935 }
2936 
2937 // Adds menu items to context menu (both before and after loading plugins' items)
AddToContextMenu(wxMenu * popup,ModuleType type,bool pluginsdone)2938 void cbEditor::AddToContextMenu(wxMenu* popup,ModuleType type,bool pluginsdone)
2939 {
2940     PluginManager *pluginManager = Manager::Get()->GetPluginManager();
2941     bool noeditor = (type != mtEditorManager);
2942     if (!pluginsdone)
2943     {
2944         wxMenu *bookmarks = nullptr, *folding = nullptr, *editsubmenu = nullptr, *insert = nullptr;
2945         if (!noeditor)
2946         {
2947             insert = CreateContextSubMenu(idInsert);
2948             editsubmenu = CreateContextSubMenu(idEdit);
2949             bookmarks = CreateContextSubMenu(idBookmarks);
2950             if (Manager::Get()->GetConfigManager(_T("editor"))->ReadBool(_T("/folding/show_folds"), false))
2951                 folding = CreateContextSubMenu(idFolding);
2952         }
2953 
2954         if (editsubmenu)
2955         {
2956             popup->Append(idCut, _("Cut"));
2957             popup->Append(idCopy, _("Copy"));
2958             popup->Append(idPaste, _("Paste"));
2959             popup->Append(idEdit, _("Edit"), editsubmenu);
2960             popup->AppendSeparator();
2961 
2962             cbStyledTextCtrl* control = GetControl();
2963             const bool hasSelection = !control->GetSelectionEmpty();
2964             popup->Enable(idCut, !control->GetReadOnly() && hasSelection);
2965             popup->Enable(idCopy, hasSelection);
2966 
2967             if (platform::gtk) // a wxGTK bug causes the triggering of unexpected events
2968                 popup->Enable(idPaste, !control->GetReadOnly());
2969             else
2970                 popup->Enable(idPaste, !control->GetReadOnly() && control->CanPaste());
2971             pluginManager->RegisterLastNonPluginMenuItem(idPaste);
2972         }
2973         if (insert)
2974         {
2975             popup->Append(idInsert, _("Insert/Refactor"), insert);
2976             pluginManager->RegisterLastNonPluginMenuItem(idInsert);
2977         }
2978         if (bookmarks)
2979         {
2980             popup->Append(idBookmarks, _("Bookmarks"), bookmarks);
2981             pluginManager->RegisterLastNonPluginMenuItem(idBookmarks);
2982         }
2983         if (folding)
2984         {
2985             popup->Append(idFolding, _("Folding"), folding);
2986             pluginManager->RegisterLastNonPluginMenuItem(idFolding);
2987         }
2988 
2989         if (insert || bookmarks || folding)
2990             popup->AppendSeparator();
2991     }
2992     else
2993     {
2994         if (!noeditor && !m_pData->GetUrl().IsEmpty())
2995         {
2996             popup->InsertSeparator(0);
2997             popup->Insert(0, idOpenUrl, _("Open link in browser"));
2998             pluginManager->RegisterFindMenuItems(true, 2);
2999         }
3000         // remove "Insert/Empty" if more than one entry
3001         wxMenu* insert = nullptr;
3002         wxMenuItem* insertitem = popup->FindItem(idInsert);
3003         if (insertitem)
3004             insert = insertitem->GetSubMenu();
3005         if (insert)
3006         {
3007             if (insert->GetMenuItemCount() > 1)
3008                 insert->Delete(idEmptyMenu);
3009         }
3010     }
3011 }
3012 
OnBeforeBuildContextMenu(const wxPoint & position,ModuleType type)3013 bool cbEditor::OnBeforeBuildContextMenu(const wxPoint& position, ModuleType type)
3014 {
3015     bool noeditor = (type != mtEditorManager);
3016     if (!noeditor && position!=wxDefaultPosition)
3017     {
3018         // right mouse click inside the editor
3019 
3020         // because here the focus has not switched yet (i.e. the left control has the focus,
3021         // but the user right-clicked inside the right control), we find out the active control differently...
3022         wxPoint clientpos(ScreenToClient(position));
3023         const int margin = m_pControl->GetMarginWidth(C_LINE_MARGIN) +     // numbers, if present
3024                            m_pControl->GetMarginWidth(C_MARKER_MARGIN) +   // breakpoints, bookmarks... if present
3025                            m_pControl->GetMarginWidth(C_FOLDING_MARGIN) +  // folding, if present
3026                            m_pControl->GetMarginWidth(C_CHANGEBAR_MARGIN); // changebar, if present
3027         wxRect r = m_pControl->GetRect();
3028 
3029         bool inside1 = r.Contains(clientpos);
3030 
3031         cbStyledTextCtrl* control = !m_pControl2 || inside1 ? m_pControl : m_pControl2;
3032 //        control->SetFocus();
3033 
3034         clientpos = control->ScreenToClient(position);
3035         if (clientpos.x < margin)
3036         {
3037             // keep the line
3038             int pos = control->PositionFromPoint(clientpos);
3039             m_pData->m_LastMarginMenuLine = control->LineFromPosition(pos);
3040 
3041             // create special menu
3042             wxMenu* popup = new wxMenu;
3043 
3044             cbDebuggerPlugin *plugin = Manager::Get()->GetDebuggerManager()->GetActiveDebugger();
3045             if (plugin && plugin->SupportsFeature(cbDebuggerFeature::Breakpoints))
3046             {
3047                 bool hasBreak = LineHasMarker(BREAKPOINT_MARKER, m_pData->m_LastMarginMenuLine);
3048                 bool hasBreakDisabled = LineHasMarker(BREAKPOINT_DISABLED_MARKER, m_pData->m_LastMarginMenuLine);
3049 
3050                 if (hasBreak || hasBreakDisabled)
3051                 {
3052                     popup->Append(idBreakpointEdit, _("Edit breakpoint"));
3053                     popup->Append(idBreakpointRemove, _("Remove breakpoint"));
3054                     if (hasBreak)
3055                         popup->Append(idBreakpointDisable, _("Disable breakpoint"));
3056                     if (hasBreakDisabled)
3057                         popup->Append(idBreakpointEnable, _("Enable breakpoint"));
3058                 }
3059                 else
3060                     popup->Append(idBreakpointAdd, _("Add breakpoint"));
3061                 popup->AppendSeparator();
3062             }
3063 
3064             if (LineHasMarker(BOOKMARK_MARKER, m_pData->m_LastMarginMenuLine))
3065             {
3066                 popup->Append(idBookmarkRemove, _("Remove bookmark"));
3067             }
3068             else
3069             {
3070                 popup->Append(idBookmarkAdd, _("Add bookmark"));
3071             }
3072 
3073             popup->Append(idBookmarkRemoveAll, _("Remove all bookmark"));
3074 
3075             // display menu... wxWindows help says not to force the position
3076             PopupMenu(popup);
3077 
3078             delete popup;
3079             return false;
3080         }
3081 
3082         // before the context menu creation, move the caret to where mouse is
3083 
3084         // get caret position and line from mouse cursor
3085         const int pos = control->PositionFromPoint(control->ScreenToClient(wxGetMousePosition()));
3086 
3087         // this re-enables 1-click "Find declaration of..."
3088         // but avoids losing selection for cut/copy
3089         if (control->GetSelectionStart() > pos ||
3090            control->GetSelectionEnd() < pos)
3091         {
3092             control->GotoPos(pos);
3093         }
3094     }
3095 
3096     // follow default strategy
3097     return EditorBase::OnBeforeBuildContextMenu(position, type);
3098 }
3099 
OnAfterBuildContextMenu(cb_unused ModuleType type)3100 void cbEditor::OnAfterBuildContextMenu(cb_unused ModuleType type)
3101 {
3102     // we don't care
3103 }
3104 
Print(bool selectionOnly,PrintColourMode pcm,bool line_numbers)3105 void cbEditor::Print(bool selectionOnly, PrintColourMode pcm, bool line_numbers)
3106 {
3107     cbStyledTextCtrl * control = GetControl();
3108     if (!control)
3109         return;
3110 
3111     // Remember same settings, so we can restore them.
3112     int oldMarginWidth = control->GetMarginWidth(C_LINE_MARGIN);
3113     int oldMarginType = control->GetMarginType(C_LINE_MARGIN);
3114     int oldEdgeMode = control->GetEdgeMode();
3115 
3116     // print line numbers?
3117     control->SetMarginType(C_LINE_MARGIN, wxSCI_MARGIN_NUMBER);
3118     if (!line_numbers)
3119     {
3120         control->SetPrintMagnification(-1);
3121         control->SetMarginWidth(C_LINE_MARGIN, 0);
3122     }
3123     else
3124     {
3125         control->SetPrintMagnification(-2);
3126         control->SetMarginWidth(C_LINE_MARGIN, 1);
3127     }
3128     // never print the gutter line
3129     control->SetEdgeMode(wxSCI_EDGE_NONE);
3130 
3131     switch (pcm)
3132     {
3133         case pcmAsIs:
3134             control->SetPrintColourMode(wxSCI_PRINT_NORMAL);
3135             break;
3136         case pcmBlackAndWhite:
3137             control->SetPrintColourMode(wxSCI_PRINT_BLACKONWHITE);
3138             break;
3139         case pcmColourOnWhite:
3140             control->SetPrintColourMode(wxSCI_PRINT_COLOURONWHITE);
3141             break;
3142         case pcmInvertColours:
3143             control->SetPrintColourMode(wxSCI_PRINT_INVERTLIGHT);
3144             break;
3145         default:
3146             break;
3147     }
3148     InitPrinting();
3149     cbEditorPrintout printout(m_Filename, control, selectionOnly);
3150     if (!g_printer->Print(this, &printout, true))
3151     {
3152         if (wxPrinter::GetLastError() == wxPRINTER_ERROR)
3153         {
3154             cbMessageBox(_("There was a problem printing.\n"
3155                             "Perhaps your current printer is not set correctly?"), _("Printing"), wxICON_ERROR);
3156             DeInitPrinting();
3157         }
3158     }
3159     else
3160     {
3161         wxPrintData* ppd = &(g_printer->GetPrintDialogData().GetPrintData());
3162         Manager::Get()->GetConfigManager(_T("app"))->Write(_T("/printerdialog/paperid"), (int)ppd->GetPaperId());
3163         Manager::Get()->GetConfigManager(_T("app"))->Write(_T("/printerdialog/paperorientation"), (int)ppd->GetOrientation());
3164     }
3165 
3166     // revert line number settings
3167     control->SetMarginType(C_LINE_MARGIN, oldMarginType);
3168     control->SetMarginWidth(C_LINE_MARGIN, oldMarginWidth);
3169 
3170     // revert gutter settings
3171     control->SetEdgeMode(oldEdgeMode);
3172 
3173     // restore line numbers if needed
3174     m_pData->SetLineNumberColWidth(m_pControl && m_pControl2);
3175 }
3176 
3177 // events
3178 
OnContextMenuEntry(wxCommandEvent & event)3179 void cbEditor::OnContextMenuEntry(wxCommandEvent& event)
3180 {
3181     cbStyledTextCtrl* control = GetControl();
3182 
3183     // we have a single event handler for all popup menu entries,
3184     // so that we can add/remove options without the need to recompile
3185     // the whole project (almost) but more importantly, to
3186     // *not* break cbEditor's interface for such a trivial task...
3187     const int id = event.GetId();
3188 
3189     if (id == idUndo)
3190         control->Undo();
3191     else if (id == idRedo)
3192         control->Redo();
3193     else if (id == idClearHistory)
3194         control->EmptyUndoBuffer(Manager::Get()->GetConfigManager(_T("editor"))->ReadBool(_T("/margin/use_changebar"), true));
3195     else if (id == idCut)
3196         control->Cut();
3197     else if (id == idCopy)
3198         control->Copy();
3199     else if (id == idPaste)
3200         control->Paste();
3201     else if (id == idDelete)
3202         control->ReplaceSelection(wxEmptyString);
3203     else if (id == idUpperCase)
3204         control->UpperCase();
3205     else if (id == idLowerCase)
3206         control->LowerCase();
3207     else if (id == idSelectAll)
3208         control->SelectAll();
3209     else if (id == idSwapHeaderSource)
3210         Manager::Get()->GetEditorManager()->SwapActiveHeaderSource();
3211     else if (id == idOpenContainingFolder)
3212         Manager::Get()->GetEditorManager()->OpenContainingFolder();
3213     else if (id == idBookmarkAdd)
3214         control->MarkerAdd(m_pData->m_LastMarginMenuLine, BOOKMARK_MARKER);
3215     else if (id == idBookmarkRemove)
3216         control->MarkerDelete(m_pData->m_LastMarginMenuLine, BOOKMARK_MARKER);
3217     else if (id == idBookmarkRemoveAll)
3218         control->MarkerDeleteAll(BOOKMARK_MARKER);
3219     else if (id == idBookmarksToggle)
3220         MarkerToggle(BOOKMARK_MARKER);
3221     else if (id == idBookmarksNext)
3222         MarkerNext(BOOKMARK_MARKER);
3223     else if (id == idBookmarksPrevious)
3224         MarkerPrevious(BOOKMARK_MARKER);
3225     else if (id == idBookmarksClearAll)
3226         control->MarkerDeleteAll(BOOKMARK_MARKER);
3227     else if (id == idFoldingFoldAll)
3228         FoldAll();
3229     else if (id == idFoldingUnfoldAll)
3230         UnfoldAll();
3231     else if (id == idFoldingToggleAll)
3232         ToggleAllFolds();
3233     else if (id == idFoldingFoldCurrent)
3234         FoldBlockFromLine();
3235     else if (id == idFoldingUnfoldCurrent)
3236         UnfoldBlockFromLine();
3237     else if (id == idFoldingToggleCurrent)
3238         ToggleFoldBlockFromLine();
3239     else if (id == idOpenUrl)
3240         wxLaunchDefaultBrowser(m_pData->GetUrl());
3241     else if (id == idSplitHorz)
3242         Split(stHorizontal);
3243     else if (id == idSplitVert)
3244         Split(stVertical);
3245     else if (id == idUnsplit)
3246         Unsplit();
3247     else if (id == idProperties)
3248     {
3249         if (m_pProjectFile)
3250             m_pProjectFile->ShowOptions(this);
3251         else
3252         {
3253             // active editor not-in-project
3254             ProjectFileOptionsDlg dlg(this, GetFilename());
3255             PlaceWindow(&dlg);
3256             dlg.ShowModal();
3257         }
3258     }
3259     else if (id == idAddFileToProject)
3260     {
3261         cbProject *prj = Manager::Get()->GetProjectManager()->GetActiveProject();
3262 
3263         wxArrayInt targets;
3264         if (Manager::Get()->GetProjectManager()->AddFileToProject(m_Filename, prj, targets) != 0)
3265         {
3266             ProjectFile* pf = prj->GetFileByFilename(m_Filename, false);
3267             SetProjectFile(pf);
3268             Manager::Get()->GetProjectManager()->GetUI().RebuildTree();
3269         }
3270     }
3271     else if (id == idRemoveFileFromProject)
3272     {
3273         if (m_pProjectFile)
3274         {
3275             cbProject *prj = m_pProjectFile->GetParentProject();
3276             Manager::Get()->GetProjectManager()->RemoveFileFromProject(m_pProjectFile, prj);
3277             Manager::Get()->GetProjectManager()->GetUI().RebuildTree();
3278         }
3279     }
3280     else if (id == idShowFileInProject)
3281     {
3282         cbProjectManagerUI& ui = Manager::Get()->GetProjectManager()->GetUI();
3283         ui.SwitchToProjectsPage();
3284         ui.ShowFileInTree(*m_pProjectFile);
3285     }
3286     else if (id == idBreakpointAdd)
3287         AddBreakpoint(m_pData->m_LastMarginMenuLine);
3288     else if (id == idBreakpointEdit)
3289     {
3290         cbBreakpointsDlg *dialog = Manager::Get()->GetDebuggerManager()->GetBreakpointDialog();
3291         dialog->EditBreakpoint(m_Filename, m_pData->m_LastMarginMenuLine + 1);
3292     }
3293     else if (id == idBreakpointRemove)
3294         RemoveBreakpoint(m_pData->m_LastMarginMenuLine);
3295     else if (id == idBreakpointEnable)
3296     {
3297         cbBreakpointsDlg *dialog = Manager::Get()->GetDebuggerManager()->GetBreakpointDialog();
3298         dialog->EnableBreakpoint(m_Filename, m_pData->m_LastMarginMenuLine + 1, true);
3299     }
3300     else if (id == idBreakpointDisable)
3301     {
3302         cbBreakpointsDlg *dialog = Manager::Get()->GetDebuggerManager()->GetBreakpointDialog();
3303         dialog->EnableBreakpoint(m_Filename, m_pData->m_LastMarginMenuLine + 1, false);
3304     }
3305     else
3306         event.Skip();
3307     //Manager::Get()->GetLogManager()->DebugLog(_T("Leaving OnContextMenuEntry"));
3308 }
3309 
OnMarginClick(wxScintillaEvent & event)3310 void cbEditor::OnMarginClick(wxScintillaEvent& event)
3311 {
3312     switch (event.GetMargin())
3313     {
3314         case C_MARKER_MARGIN: // bookmarks and breakpoints margin
3315         {
3316             int lineYpix = event.GetPosition();
3317             int line = GetControl()->LineFromPosition(lineYpix);
3318 
3319             ToggleBreakpoint(line);
3320             break;
3321         }
3322         case C_FOLDING_MARGIN: // folding margin
3323         {
3324             int lineYpix = event.GetPosition();
3325             int line = GetControl()->LineFromPosition(lineYpix);
3326 
3327             GetControl()->ToggleFold(line);
3328             break;
3329         }
3330         default:
3331             break;
3332     }
3333     OnScintillaEvent(event);
3334 }
3335 
OnEditorUpdateUI(wxScintillaEvent & event)3336 void cbEditor::OnEditorUpdateUI(wxScintillaEvent& event)
3337 {
3338     if (Manager::Get()->GetEditorManager()->GetActiveEditor() == this)
3339     {
3340         NotifyPlugins(cbEVT_EDITOR_UPDATE_UI);
3341         HighlightBraces(); // brace highlighting
3342     }
3343     OnScintillaEvent(event);
3344 }
3345 
OnEditorChange(wxScintillaEvent & event)3346 void cbEditor::OnEditorChange(wxScintillaEvent& event)
3347 {
3348     SetModified(m_pControl->GetModify());
3349     OnScintillaEvent(event);
3350 }
3351 
OnEditorCharAdded(wxScintillaEvent & event)3352 void cbEditor::OnEditorCharAdded(wxScintillaEvent& event)
3353 {
3354     // if message manager is auto-hiding, this will close it if not needed open
3355 //    Manager::Get()->GetLogManager()->Close();
3356 
3357     m_autoIndentDone = false;
3358     OnScintillaEvent(event); // smart indent plugins will be called here
3359     if (!m_autoIndentDone)
3360     {
3361         const wxChar ch = event.GetKey();
3362         cbStyledTextCtrl* control = GetControl();
3363         // auto indent
3364         if ( (ch == _T('\n')) || ( (control->GetEOLMode() == wxSCI_EOL_CR) && (ch == _T('\r')) ) )
3365         {
3366             const int pos = control->GetCurrentPos();
3367             const int currLine = control->LineFromPosition(pos);
3368             const bool autoIndent = Manager::Get()->GetConfigManager(_T("editor"))->ReadBool(_T("/auto_indent"), true);
3369             if (autoIndent && currLine > 0)
3370             {
3371                 wxString indent;
3372                 if (control->GetCurLine().Trim().IsEmpty())
3373                 {
3374                     // copy the indentation of the last non-empty line
3375                     for (int i = currLine - 1; i >= 0; --i)
3376                     {
3377                         const wxString& prevLineStr = control->GetLine(i);
3378                         if (!(prevLineStr.IsEmpty() || prevLineStr[0] == _T('\n') || prevLineStr[0] == _T('\r')))
3379                         {
3380                             indent = GetLineIndentString(i);
3381                             break;
3382                         }
3383                     }
3384                 }
3385                 else
3386                     indent = GetLineIndentString(currLine - 1);
3387                 if (!indent.IsEmpty())
3388                 {
3389                     control->BeginUndoAction();
3390 
3391                     control->InsertText(pos, indent);
3392                     control->GotoPos(pos + indent.Length());
3393                     control->ChooseCaretX();
3394 
3395                     control->EndUndoAction();
3396                 }
3397             }
3398         }
3399 
3400         // selection brace completion
3401         bool braceCompleted = false;
3402         if (   Manager::Get()->GetConfigManager(_T("editor"))->ReadBool(_T("/selection_brace_completion"), false)
3403             || control->IsBraceShortcutActive() )
3404         {
3405             braceCompleted = control->DoSelectionBraceCompletion(ch);
3406         }
3407 
3408         // brace completion
3409         if (  !braceCompleted
3410             && Manager::Get()->GetConfigManager(_T("editor"))->ReadBool(_T("/brace_completion"), true) )
3411         {
3412             control->DoBraceCompletion(ch);
3413         }
3414     }
3415 }
3416 
AutoIndentDone()3417 void cbEditor::AutoIndentDone()
3418 {
3419     m_autoIndentDone = true;
3420 }
3421 
OnEditorDwellStart(wxScintillaEvent & event)3422 void cbEditor::OnEditorDwellStart(wxScintillaEvent& event)
3423 {
3424     if ( !wxTheApp->IsActive() )
3425         return;
3426 
3427     cbStyledTextCtrl* control = GetControl();
3428     if (!control)
3429         return;
3430 
3431     wxRect screenRect = control->GetScreenRect();
3432     wxPoint ptEvent(event.GetX(), event.GetY());
3433     ptEvent = control->ClientToScreen(ptEvent);
3434     wxPoint ptScreen = wxGetMousePosition();
3435     wxPoint ptClient = control->ScreenToClient(ptScreen);
3436 
3437     double distance = sqrt(  (ptScreen.x - ptEvent.x) * (ptScreen.x - ptEvent.x)
3438                            + (ptScreen.y - ptEvent.y) * (ptScreen.y - ptEvent.y) );
3439     if (!screenRect.Contains(ptScreen) || distance > 10)
3440         return;
3441 
3442     int pos = control->PositionFromPoint(ptClient);
3443     int style = control->GetStyleAt(pos);
3444     NotifyPlugins(cbEVT_EDITOR_TOOLTIP, style, wxEmptyString, ptClient.x, ptClient.y);
3445     wxScintillaEvent newEvent(event);
3446     newEvent.SetX(ptClient.x);
3447     newEvent.SetY(ptClient.y);
3448     OnScintillaEvent(event);
3449 }
3450 
OnEditorDwellEnd(wxScintillaEvent & event)3451 void cbEditor::OnEditorDwellEnd(wxScintillaEvent& event)
3452 {
3453     NotifyPlugins(cbEVT_EDITOR_TOOLTIP_CANCEL);
3454     OnScintillaEvent(event);
3455 }
3456 
OnEditorModified(wxScintillaEvent & event)3457 void cbEditor::OnEditorModified(wxScintillaEvent& event)
3458 {
3459 //    wxString txt = _T("OnEditorModified(): ");
3460 //    int flags = event.GetModificationType();
3461 //    if (flags & wxSCI_MOD_CHANGEMARKER) txt << _T("wxSCI_MOD_CHANGEMARKER, ");
3462 //    if (flags & wxSCI_MOD_INSERTTEXT) txt << _T("wxSCI_MOD_INSERTTEXT, ");
3463 //    if (flags & wxSCI_MOD_DELETETEXT) txt << _T("wxSCI_MOD_DELETETEXT, ");
3464 //    if (flags & wxSCI_MOD_CHANGEFOLD) txt << _T("wxSCI_MOD_CHANGEFOLD, ");
3465 //    if (flags & wxSCI_PERFORMED_USER) txt << _T("wxSCI_PERFORMED_USER, ");
3466 //    if (flags & wxSCI_MOD_BEFOREINSERT) txt << _T("wxSCI_MOD_BEFOREINSERT, ");
3467 //    if (flags & wxSCI_MOD_BEFOREDELETE) txt << _T("wxSCI_MOD_BEFOREDELETE, ");
3468 //    txt << _T("pos=")
3469 //        << wxString::Format(_T("%d"), event.GetPosition())
3470 //        << _T(", line=")
3471 //        << wxString::Format(_T("%d"), event.GetLine())
3472 //        << _T(", linesAdded=")
3473 //        << wxString::Format(_T("%d"), event.GetLinesAdded());
3474 //    Manager::Get()->GetLogManager()->DebugLog(txt);
3475 
3476     // whenever event.GetLinesAdded() != 0, we must re-set breakpoints for lines greater
3477     // than LineFromPosition(event.GetPosition())
3478     int linesAdded = event.GetLinesAdded();
3479     bool isAdd = event.GetModificationType() & wxSCI_MOD_INSERTTEXT;
3480     bool isDel = event.GetModificationType() & wxSCI_MOD_DELETETEXT;
3481     if ((isAdd || isDel) && linesAdded != 0)
3482     {
3483         // whether to show line-numbers or not is handled in SetLineNumberColWidth() now
3484         m_pData->SetLineNumberColWidth();
3485 
3486         // NB: I don't think polling for each debugger every time will slow things down enough
3487         // to worry about unless there are automated tasks that call this routine regularly
3488         //
3489         // well, scintilla events happen regularly
3490         // although we only reach this part of the code only if a line has been added/removed
3491         // so, yes, it might not be that bad after all
3492         int startline = m_pControl->LineFromPosition(event.GetPosition());
3493         if (m_pControl == event.GetEventObject())
3494         {
3495             const DebuggerManager::RegisteredPlugins &plugins = Manager::Get()->GetDebuggerManager()->GetAllDebuggers();
3496             cbDebuggerPlugin *active = Manager::Get()->GetDebuggerManager()->GetActiveDebugger();
3497             for (DebuggerManager::RegisteredPlugins::const_iterator it = plugins.begin(); it != plugins.end(); ++it)
3498             {
3499                 if (it->first != active)
3500                     it->first->EditorLinesAddedOrRemoved(this, startline + 1, linesAdded);
3501             }
3502             if (active)
3503                 active->EditorLinesAddedOrRemoved(this, startline + 1, linesAdded);
3504 
3505             cbBreakpointsDlg *dlg = Manager::Get()->GetDebuggerManager()->GetBreakpointDialog();
3506             if (dlg)
3507                 dlg->Reload();
3508             RefreshBreakpointMarkers();
3509         }
3510     }
3511     // If we remove the folding-point (the brace or whatever) from a folded block,
3512     // we have to make the hidden lines visible, otherwise, they
3513     // will no longer be reachable, until the editor is closed and reopened again
3514     if (   (event.GetModificationType() & wxSCI_MOD_CHANGEFOLD)
3515         && (event.GetFoldLevelPrev() & wxSCI_FOLDLEVELHEADERFLAG) )
3516     {
3517         cbStyledTextCtrl* control = GetControl();
3518         int line = event.GetLine();
3519         if (! control->GetFoldExpanded(line))
3520         {
3521             control->SetFoldExpanded(line, true);
3522             control->ShowLines(line, control->GetLastChild(line, -1));
3523         }
3524     }
3525     OnScintillaEvent(event);
3526 } // end of OnEditorModified
3527 
OnUserListSelection(wxScintillaEvent & event)3528 void cbEditor::OnUserListSelection(wxScintillaEvent& event)
3529 {
3530     OnScintillaEvent(event);
3531 }
3532 
OnClose(cb_unused wxCloseEvent & event)3533 void cbEditor::OnClose(cb_unused wxCloseEvent& event)
3534 {
3535     Manager::Get()->GetEditorManager()->Close(this);
3536 }
3537 
DoIndent()3538 void cbEditor::DoIndent()
3539 {
3540     cbStyledTextCtrl* control = GetControl();
3541     if (control)
3542         control->SendMsg(wxSCI_CMD_TAB);
3543 }
3544 
DoUnIndent()3545 void cbEditor::DoUnIndent()
3546 {
3547     cbStyledTextCtrl* control = GetControl();
3548     if (control)
3549         control->SendMsg(wxSCI_CMD_BACKTAB);
3550 }
3551 
OnZoom(wxScintillaEvent & event)3552 void cbEditor::OnZoom(wxScintillaEvent& event)
3553 {
3554     ConfigManager* mgr =  Manager::Get()->GetConfigManager(_T("editor"));
3555 
3556     int zoom = GetControl()->GetZoom();
3557     Manager::Get()->GetEditorManager()->SetZoom(zoom);
3558     // if all editors should be zoomed, we call cbAuiNotebooks SetZoom()
3559     bool both = mgr->ReadBool(_T("/zoom_all"));
3560     if (both)
3561         Manager::Get()->GetEditorManager()->GetNotebook()->SetZoom(zoom);
3562 
3563     m_pData->SetLineNumberColWidth(both);
3564 
3565     if (mgr->ReadBool(_T("/folding/show_folds"), true))
3566         m_pData->SetColumnWidth(C_FOLDING_MARGIN, foldingMarginBaseWidth, 1, both);
3567 
3568     if (mgr->ReadBool(_T("/margin/use_changebar"), true))
3569         m_pData->SetColumnWidth(C_CHANGEBAR_MARGIN, changeBarMarginBaseWidth, 1, both);
3570 
3571     m_pData->SetMarkerColumnWidth(both);
3572 
3573     OnScintillaEvent(event);
3574 }
3575 
3576 // used to set zoom for both (if splitted) or just the last active control,
3577 // called from cbAuiNotebook
SetZoom(int zoom,bool both)3578 void cbEditor::SetZoom(int zoom, bool both)
3579 {
3580     if (both)
3581     {
3582         if (m_pControl->GetZoom() != zoom)
3583             m_pControl->SetZoom(zoom);
3584         if (m_pControl2 && (m_pControl2->GetZoom() != zoom))
3585             m_pControl2->SetZoom(zoom);
3586     }
3587     else
3588     {
3589         if (GetControl()->GetZoom() != zoom)
3590             GetControl()->SetZoom(zoom);
3591     }
3592 }
3593 
3594 // generic scintilla event handler
OnScintillaEvent(wxScintillaEvent & event)3595 void cbEditor::OnScintillaEvent(wxScintillaEvent& event)
3596 {
3597 //  wxString txt;
3598 //    wxEventType type = event.GetEventType();
3599 //  if (type == wxEVT_SCI_CHANGE) txt << _T("wxEVT_SCI_CHANGE");
3600 //  else if (type == wxEVT_SCI_STYLENEEDED) txt << _T("wxEVT_SCI_STYLENEEDED");
3601 //  else if (type == wxEVT_SCI_CHARADDED) txt << _T("wxEVT_SCI_CHARADDED");
3602 //  else if (type == wxEVT_SCI_SAVEPOINTREACHED) txt << _T("wxEVT_SCI_SAVEPOINTREACHED");
3603 //  else if (type == wxEVT_SCI_SAVEPOINTLEFT) txt << _T("wxEVT_SCI_SAVEPOINTLEFT");
3604 //  else if (type == wxEVT_SCI_ROMODIFYATTEMPT) txt << _T("wxEVT_SCI_ROMODIFYATTEMPT");
3605 //  else if (type == wxEVT_SCI_KEY) txt << _T("wxEVT_SCI_KEY");
3606 //  else if (type == wxEVT_SCI_DOUBLECLICK) txt << _T("wxEVT_SCI_DOUBLECLICK");
3607 //  else if (type == wxEVT_SCI_UPDATEUI) txt << _T("wxEVT_SCI_UPDATEUI");
3608 //  else if (type == wxEVT_SCI_MODIFIED) txt << _T("wxEVT_SCI_MODIFIED");
3609 //  else if (type == wxEVT_SCI_MACRORECORD) txt << _T("wxEVT_SCI_MACRORECORD");
3610 //  else if (type == wxEVT_SCI_MARGINCLICK) txt << _T("wxEVT_SCI_MARGINCLICK");
3611 //  else if (type == wxEVT_SCI_NEEDSHOWN) txt << _T("wxEVT_SCI_NEEDSHOWN");
3612 //  else if (type == wxEVT_SCI_PAINTED) txt << _T("wxEVT_SCI_PAINTED");
3613 //  else if (type == wxEVT_SCI_USERLISTSELECTION) txt << _T("wxEVT_SCI_USERLISTSELECTION");
3614 //  else if (type == wxEVT_SCI_URIDROPPED) txt << _T("wxEVT_SCI_URIDROPPED");
3615 //  else if (type == wxEVT_SCI_DWELLSTART) txt << _T("wxEVT_SCI_DWELLSTART");
3616 //  else if (type == wxEVT_SCI_DWELLEND) txt << _T("wxEVT_SCI_DWELLEND");
3617 //  else if (type == wxEVT_SCI_START_DRAG) txt << _T("wxEVT_SCI_START_DRAG");
3618 //  else if (type == wxEVT_SCI_DRAG_OVER) txt << _T("wxEVT_SCI_DRAG_OVER");
3619 //  else if (type == wxEVT_SCI_DO_DROP) txt << _T("wxEVT_SCI_DO_DROP");
3620 //  else if (type == wxEVT_SCI_ZOOM) txt << _T("wxEVT_SCI_ZOOM");
3621 //  else if (type == wxEVT_SCI_HOTSPOT_CLICK) txt << _T("wxEVT_SCI_HOTSPOT_CLICK");
3622 //  else if (type == wxEVT_SCI_HOTSPOT_DCLICK) txt << _T("wxEVT_SCI_HOTSPOT_DCLICK");
3623 //  else if (type == wxEVT_SCI_CALLTIP_CLICK) txt << _T("wxEVT_SCI_CALLTIP_CLICK");
3624 //  else if (type == wxEVT_SCI_AUTOCOMP_SELECTION) txt << _T("wxEVT_SCI_AUTOCOMP_SELECTION");
3625 //  else if (type == wxEVT_SCI_INDICATOR_CLICK) txt << _T("wxEVT_SCI_INDICATOR_CLICK");
3626 //  else if (type == wxEVT_SCI_INDICATOR_RELEASE) txt << _T("wxEVT_SCI_INDICATOR_RELEASE");
3627 //    Manager::Get()->GetLogManager()->DebugLog(txt);
3628 
3629     // call any hooked functors
3630     if (!ProjectManager::IsBusy() && EditorHooks::HasRegisteredHooks())
3631     {
3632         EditorHooks::CallHooks(this, event);
3633     }
3634 }
3635 
CanSelectAll() const3636 bool cbEditor::CanSelectAll() const
3637 {
3638     int res = 0;
3639     cbStyledTextCtrl* control = GetControl();
3640     if (control)
3641         res = control->GetLength();
3642     return res > 0;
3643 }
3644 
SelectAll()3645 void cbEditor::SelectAll()
3646 {
3647     cbStyledTextCtrl* control = GetControl();
3648     if (control)
3649         control->SelectAll();
3650 }
3651