1 #include "SmartIndentCpp.h"
2 
3 #include <sdk.h> // Code::Blocks SDK
4 
5 #ifndef CB_PRECOMP
6     #include <cbeditor.h>
7     #include <configmanager.h>
8     #include <editormanager.h>
9     #include <editorcolourset.h>
10     #include <manager.h>
11 #endif
12 
13 #include <cbstyledtextctrl.h>
14 #include <wx/regex.h>
15 
16 
17 // Register the plugin with Code::Blocks.
18 // We are using an anonymous namespace so we don't litter the global one.
19 namespace
20 {
21     PluginRegistrant<SmartIndentCpp> reg(_T("SmartIndentCpp"));
22 }
OnEditorHook(cbEditor * ed,wxScintillaEvent & event) const23 void SmartIndentCpp::OnEditorHook(cbEditor* ed, wxScintillaEvent& event) const
24 {
25 
26     // check if smart indent is enabled
27     // check the event type and the currently set language
28     // if it is not a CharAdded event or the language is not C/C++, D, or Java return
29 
30     if (!ed)
31         return;
32 
33     if ( !SmartIndentEnabled() )
34         return;
35 
36     wxEventType type = event.GetEventType();
37     if ( type != wxEVT_SCI_CHARADDED )
38         return;
39 
40     cbStyledTextCtrl* stc = ed->GetControl();
41     if (!stc)
42         return;
43 
44     EditorColourSet* colour_set = Manager::Get()->GetEditorManager()->GetColourSet();
45     if (!colour_set)
46         return;
47 
48     wxString langname = colour_set->GetLanguageName(ed->GetLanguage());
49     if ( langname != wxT("D") && (stc->GetLexer() != wxSCI_LEX_CPP || langname == wxT("Hitachi asm")))
50         return;
51 
52     ed->AutoIndentDone(); // we are responsible.
53 
54     const int pos = stc->GetCurrentPos();
55     int currLine = stc->LineFromPosition(pos);
56 
57     if (currLine == 0)
58         return;
59 
60     const wxChar ch = event.GetKey();
61 
62     if ( SelectionBraceCompletionEnabled() || stc->IsBraceShortcutActive() )
63         DoSelectionBraceCompletion(stc, ch);
64 
65     DoSmartIndent(ed, ch);
66 
67     if ( BraceCompletionEnabled() )
68         DoBraceCompletion(stc, ch);
69 }
DoSmartIndent(cbEditor * ed,const wxChar & ch) const70 void SmartIndentCpp::DoSmartIndent(cbEditor* ed, const wxChar &ch)const
71 {
72     if (!ed)
73         return;
74 
75     static bool autoIndentStart = false;
76     static bool autoIndentDone = true;
77     static int autoIndentLine = -1;
78     static int autoIndentLineIndent = -1;
79 
80     static bool autoUnIndent = false; // for public: / case: / etc..
81     static int autoUnIndentValue = -1;
82     static int autoUnIndentLine = -1;
83 
84     cbStyledTextCtrl* stc = ed->GetControl();
85     if (!stc)
86         return;
87 
88     const int pos = stc->GetCurrentPos();
89     // indent
90     if ( (ch == _T('\n')) || ( (stc->GetEOLMode() == wxSCI_EOL_CR) && (ch == _T('\r')) ) )
91     {
92         stc->BeginUndoAction();
93         // new-line: adjust indentation
94         bool autoIndent = AutoIndentEnabled();
95         bool smartIndent = SmartIndentEnabled();
96         int currLine = stc->LineFromPosition(pos);
97         if (autoIndent && currLine > 0)
98         {
99             wxString indent;
100             if (stc->GetCurLine().Trim().IsEmpty())
101             {
102                 // copy the indentation of the last non-empty line
103                 for (int i = currLine - 1; i >= 0; --i)
104                 {
105                     const wxString& prevLineStr = stc->GetLine(i);
106                     if (!(prevLineStr.IsEmpty() || prevLineStr[0] == _T('\n') || prevLineStr[0] == _T('\r')))
107                     {
108                         indent = ed->GetLineIndentString(i);
109                         break;
110                     }
111                 }
112             }
113             else
114                 indent = ed->GetLineIndentString(currLine - 1);
115             if (smartIndent)
116             {
117                 wxChar b = GetLastNonWhitespaceChar(ed);
118                 // if the last entered char before newline was an opening curly brace,
119                 // increase indentation level (the closing brace is handled in another block)
120 
121                 if ( !BraceIndent(stc, indent) )
122                 {
123                     if (b == _T('{'))
124                     {
125                         int nonblankpos;
126                         wxChar c = GetNextNonWhitespaceCharOfLine(stc, pos, &nonblankpos);
127 
128                         if ( c != _T('}') )
129                             Indent(stc, indent);
130                         else
131                         {
132                             if ( pos != nonblankpos )
133                             {
134                                 stc->SetCurrentPos(nonblankpos);
135                                 stc->DeleteBack();
136                             }
137                         }
138                     }
139                     else if (b == _T(':'))
140                         Indent(stc, indent);
141                 }
142 
143             }
144             stc->InsertText(pos, indent);
145             stc->GotoPos(pos + indent.Length());
146             stc->ChooseCaretX();
147         }
148 
149         // smart indent
150         if (smartIndent && currLine > 0)
151         {
152             if (!autoIndentDone)
153             {
154                 bool valid = true;
155                 int line = stc->GetCurrentLine();
156                 if (line < autoIndentLine)
157                     valid = false;
158                 else
159                 {
160                     while (--line > autoIndentLine)
161                     {
162                         if (stc->GetLineIndentation(line) < autoIndentLineIndent)
163                         {
164                             valid = false;
165                             break;
166                         }
167                     }
168                 }
169 
170                 if (!valid)
171                 {
172                     autoIndentStart = false;
173                     autoIndentDone = true;
174                     autoIndentLine = -1;
175                     autoIndentLineIndent = -1;
176                 }
177             }
178 
179             if (autoIndentDone)
180             {
181                 const int ind_pos = stc->GetLineIndentPosition(currLine - 1);
182                 const wxString text = stc->GetTextRange(ind_pos, stc->WordEndPosition(ind_pos, true));
183                 if (   text == _T("if")
184                     || text == _T("else")
185                     || text == _T("for")
186                     || text == _T("while")
187                     || text == _T("do") )
188                 {
189                     const wxChar non_ws_ch = GetLastNonWhitespaceChar(ed);
190                     if (non_ws_ch != _T(';') && non_ws_ch != _T('}'))
191                     {
192                         autoIndentDone = false;
193                         autoIndentLine = currLine - 1;
194                         autoIndentLineIndent = stc->GetLineIndentation(currLine - 1);
195                     }
196                 }
197             }
198 
199             if (!autoIndentDone)
200             {
201                 if (autoIndentStart)
202                 {
203                     const wxChar non_ws_ch = GetLastNonWhitespaceChar(ed);
204                     if (non_ws_ch == _T(';') || non_ws_ch == _T('}'))
205                     {
206                         stc->SetLineIndentation(currLine, autoIndentLineIndent);
207                         stc->GotoPos(stc->GetLineEndPosition(currLine));
208 
209                         autoIndentStart = false;
210                         autoIndentDone = true;
211                         autoIndentLine = -1;
212                         autoIndentLineIndent = -1;
213                     }
214                 }
215                 else
216                 {
217                     int lastLine = currLine;
218                     while (--lastLine >= 0)
219                     {
220                         const int lineIndentPos = stc->GetLineIndentPosition(lastLine);
221                         const int start = stc->WordStartPosition(lineIndentPos, true);
222                         const int end = stc->WordEndPosition(lineIndentPos, true);
223                         const wxString last = stc->GetTextRange(start, end);
224 
225                         if (   last == _T("if")
226                             || last == _T("else")
227                             || last == _T("for")
228                             || last == _T("while")
229                             || last == _T("do") )
230                         {
231                             const wxString text = stc->GetTextRange(lineIndentPos + last.Len(), pos);
232                             int level = 0;
233                             for (size_t i = 0; i < text.Len(); ++i)
234                             {
235                                 if (text[i] == _T('('))
236                                 {
237                                     const int style = stc->GetStyleAt(pos - text.Len() + i);
238                                     if (   stc->IsString(style)
239                                         || stc->IsCharacter(style)
240                                         || stc->IsComment(style) )
241                                     {
242                                         continue;
243                                     }
244                                     ++level;
245                                 }
246                                 else if (text[i] == _T(')'))
247                                 {
248                                     const int style = stc->GetStyleAt(pos - text.Len() + i);
249                                     if (   stc->IsString(style)
250                                         || stc->IsCharacter(style)
251                                         || stc->IsComment(style) )
252                                     {
253                                         continue;
254                                     }
255                                     --level;
256                                 }
257                             }
258 
259                             if (!level)
260                             {
261                                 autoIndentStart = true;
262 
263                                 int nonblankpos;
264                                 wxChar c = GetNextNonWhitespaceCharOfLine(stc, pos, &nonblankpos);
265                                 if (c == _T('}') && currLine == stc->LineFromPosition(nonblankpos))
266                                 {
267                                     stc->NewLine();
268                                     stc->GotoPos(pos);
269 
270                                     autoIndentStart = false;
271                                     autoIndentDone = true;
272                                     autoIndentLine = -1;
273                                     autoIndentLineIndent = -1;
274                                 }
275 
276                                 stc->Tab();
277                             }
278 
279                             break;
280                         }
281                     }
282                 }
283             }
284 
285             // smart un-indent
286             if (autoUnIndent)
287             {
288                 if ( GetLastNonWhitespaceChar(ed) == _T(':'))
289                 {
290                     stc->SetLineIndentation(autoUnIndentLine, autoUnIndentValue);
291                     stc->SetLineIndentation(currLine, autoUnIndentValue);
292                     stc->Tab();
293                 }
294 
295                 autoUnIndent = false;
296                 autoUnIndentValue = -1;
297                 autoUnIndentLine = -1;
298             }
299         }
300 
301         stc->EndUndoAction();
302     }
303 
304     // unindent
305     else if (ch == _T('{'))
306     {
307         if (autoIndentStart)
308         {
309             bool valid = true;
310             const int start = stc->PositionFromLine(autoIndentLine);
311             const wxString text = stc->GetTextRange(start, pos);
312             if (text.Find(_T('{')) != int(text.Len() - 1))
313                 valid = false;
314             else
315             {
316                 int line = stc->GetCurrentLine();
317                 if (line < autoIndentLine)
318                     valid = false;
319                 else
320                 {
321                     while (--line > autoIndentLine)
322                     {
323                         if (stc->GetLineIndentation(line) < autoIndentLineIndent)
324                         {
325                             valid = false;
326                             break;
327                         }
328                     }
329                 }
330             }
331 
332             if (valid)
333             {
334                 stc->BeginUndoAction();
335                 stc->SetLineIndentation(stc->GetCurrentLine(), autoIndentLineIndent);
336                 stc->EndUndoAction();
337             }
338 
339             autoIndentStart = false;
340             autoIndentDone = true;
341             autoIndentLine = -1;
342             autoIndentLineIndent = -1;
343         }
344     }
345 
346     // unindent
347     else if (ch == _T('}'))
348     {
349         bool smartIndent = Manager::Get()->GetConfigManager(_T("editor"))->ReadBool(_T("/smart_indent"), true);
350         if ( smartIndent && ( (stc->GetLexer() == wxSCI_LEX_CPP) || (stc->GetLexer() == wxSCI_LEX_D) ) )
351         {
352             stc->BeginUndoAction();
353             // undo block indentation, if needed
354             wxString str = stc->GetLine(stc->GetCurrentLine());
355             str.Trim(false);
356             str.Trim(true);
357             if (str.Matches(_T("}")))
358             {
359                 // just the brace here; unindent
360                 // find opening brace (skipping nested blocks)
361                 int cur_pos = stc->GetCurrentPos() - 2;
362                 cur_pos = FindBlockStart(stc, cur_pos, _T('{'), _T('}'));
363                 if (cur_pos != -1)
364                 {
365                     wxString indent = ed->GetLineIndentString(stc->LineFromPosition(cur_pos));
366                     indent << _T('}');
367                     stc->DelLineLeft();
368                     stc->DelLineRight();
369                     cur_pos = stc->GetCurrentPos();
370                     stc->InsertText(cur_pos, indent);
371                     stc->GotoPos(cur_pos + indent.Length());
372                     stc->ChooseCaretX();
373                 }
374             }
375             stc->EndUndoAction();
376         }
377     }
378 
379     // unindent
380     else if (ch == _T(':'))
381     {
382         bool smartIndent = Manager::Get()->GetConfigManager(_T("editor"))->ReadBool(_T("/smart_indent"), true);
383         if (smartIndent && stc->GetLexer() == wxSCI_LEX_CPP && !autoUnIndent)
384         {
385             const int curLine = stc->GetCurrentLine();
386             const int li_pos = stc->GetLineIndentPosition(curLine);
387             const wxString text = stc->GetTextRange(li_pos, stc->WordEndPosition(li_pos, true));
388             if (   text == _T("public")
389                 || text == _T("protected")
390                 || text == _T("private")
391                 || text == _T("case")
392                 || text == _T("default") )
393             {
394                 const bool isSwitch = (text == _T("case") || text == _T("default"));
395                 int lastLine = curLine;
396                 int lastLineIndent = -1;
397                 while (--lastLine >= 0)
398                 {
399                     const int lineIndentPos = stc->GetLineIndentPosition(lastLine);
400                     const int start = stc->WordStartPosition(lineIndentPos, true);
401                     const int end = stc->WordEndPosition(lineIndentPos, true);
402 
403                     const wxString last = stc->GetTextRange(start, end);
404                     if (last.IsEmpty())
405                         continue;
406 
407                     if (isSwitch)
408                     {
409                         if (last == _T("case"))
410                         {
411                             lastLineIndent = stc->GetLineIndentation(lastLine);
412                             break;
413                         }
414                         else if (last == _T("switch"))
415                             break;
416                     }
417                     else
418                     {
419                         if (   last == _T("public")
420                             || last == _T("protected")
421                             || last == _T("private") )
422                         {
423                             lastLineIndent = stc->GetLineIndentation(lastLine);
424                             break;
425                         }
426                         else if (last == _T("class"))
427                             break;
428                     }
429                 }
430 
431                 if (lastLineIndent != -1)
432                 {
433                     autoUnIndent = true;
434                     autoUnIndentValue = lastLineIndent;
435                     autoUnIndentLine = curLine;
436                 }
437                 else
438                 {
439                     const int curLineIndent = stc->GetLineIndentation(curLine);
440                     const int tabWidth = stc->GetTabWidth();
441                     if (curLineIndent >= tabWidth)
442                     {
443                         autoUnIndent = true;
444                         autoUnIndentValue = curLineIndent - tabWidth;
445                         autoUnIndentLine = curLine;
446                     }
447                 }
448             }
449         }
450     }
451 
452 }
453 
BraceIndent(cbStyledTextCtrl * stc,wxString & indent) const454 bool SmartIndentCpp::BraceIndent(cbStyledTextCtrl *stc, wxString &indent)const
455 {
456     if ( BraceSmartIndentEnabled() )
457     {
458         int style = 0;
459         if (stc->GetLexer() == wxSCI_LEX_CPP)
460             style = wxSCI_C_STRING;
461         else // wxSCI_LEX_D
462             style = wxSCI_D_STRING;
463 
464         int brace_position = GetFirstBraceInLine(stc, style);
465         return Indent(stc, indent, brace_position);
466     }
467     return false;
468 }
469 
DoSelectionBraceCompletion(cbStyledTextCtrl * control,const wxChar & ch) const470 void SmartIndentCpp::DoSelectionBraceCompletion(cbStyledTextCtrl* control, const wxChar &ch)const
471 {
472     if (!control)
473         return;
474 
475     if (!control->GetLastSelectedText().IsEmpty())
476     {
477 
478         const int pos = control->GetCurrentPos();
479         wxString selectedText = control->GetLastSelectedText();
480         switch (ch)
481         {
482             case _T('\''):
483             {
484                 control->BeginUndoAction();
485                 control->DeleteBack();
486                 selectedText.Replace(wxT("\\'"), wxT("'"));
487                 selectedText.Replace(wxT("'"), wxT("\\'"));
488                 control->AddText(wxT("'") + selectedText + wxT("'"));
489                 control->EndUndoAction();
490                 return;
491             }
492             case _T('"'):
493             {
494                 control->BeginUndoAction();
495                 control->DeleteBack();
496                 selectedText.Replace(wxT("\\\""), wxT("\""));
497                 selectedText.Replace(wxT("\""), wxT("\\\""));
498                 control->AddText(wxT("\"") + selectedText + wxT("\""));
499                 control->SetSelectionVoid(pos - 1, pos + selectedText.Length() + 1);
500                 int startLine = control->LineFromPosition(control->GetSelectionStart());
501                 int endLine = control->LineFromPosition(control->GetSelectionEnd());
502                 if (startLine != endLine)
503                 {
504                     int selectionEnd = pos + selectedText.Length() + 1;
505                     for (int i = endLine; i > startLine; i--)
506                     {
507                         control->Home();
508                         for (int j = control->GetCurrentPos(); control->GetCharAt(j) == _T(' ') || control->GetCharAt(j) == _T('\t'); j++)
509                             control->CharRight();
510                         control->AddText(wxT("\""));
511                         control->SetEmptySelection(control->GetLineEndPosition(i - 1));
512                         control->AddText(wxT("\""));
513                         selectionEnd += control->GetIndent() + 2;
514                     }
515                     control->SetSelectionVoid(pos - 1, selectionEnd);
516                 }
517                 control->EndUndoAction();
518                 return;
519             }
520             case _T('('):
521             case _T(')'):
522             case _T('['):
523             case _T(']'):
524             case _T('<'):
525             case _T('>'):
526             {
527                 control->DoSelectionBraceCompletion(ch);
528                 return;
529             }
530             case _T('{'):
531             case _T('}'):
532             {
533                 control->BeginUndoAction();
534                 control->DeleteBack();
535                 control->AddText(selectedText);
536                 control->SetSelectionVoid(pos - 1, pos + selectedText.Length() - 1);
537                 int startLine = control->LineFromPosition(control->GetSelectionStart());
538                 int endLine = control->LineFromPosition(control->GetSelectionEnd());
539                 if (startLine == endLine)
540                     control->Home();
541                 control->Tab();
542                 control->SetEmptySelection(control->GetLineEndPosition(endLine));
543                 control->NewLine();
544                 control->BackTab();
545                 control->AddText(wxT("}"));
546                 control->SetEmptySelection(control->GetLineEndPosition(startLine - 1));
547                 control->NewLine();
548                 control->InsertText(control->GetCurrentPos(), wxT("{"));
549                 if (ch == _T('}'))
550                     control->SetEmptySelection(control->GetLineEndPosition(endLine + 2));
551                 control->EndUndoAction();
552                 return;
553             }
554             default: return;
555         }
556     } // SelectionBraceCompletion
557 }
558 
DoBraceCompletion(cbStyledTextCtrl * control,const wxChar & ch) const559 void SmartIndentCpp::DoBraceCompletion(cbStyledTextCtrl* control, const wxChar& ch)const
560 {
561     if (!control)
562         return;
563 
564     int pos = control->GetCurrentPos();
565     int style = control->GetStyleAt(pos);
566 
567     // match preprocessor commands
568     if ( (ch == _T('\n')) || ( (control->GetEOLMode() == wxSCI_EOL_CR) && (ch == _T('\r')) ) )
569     {
570         wxRegEx ppIf(wxT("^[ \t]*#[ \t]*if"));
571         wxRegEx ppElse(wxT("^[ \t]*#[ \t]*el"));
572         wxRegEx ppEnd(wxT("^[ \t]*#[ \t]*endif"));
573         wxRegEx pp(wxT("^([ \t]*#[ \t]*)[a-z]*([ \t]+([a-zA-Z0-9_]+)|())")); // generic match to extract parts
574         const int ppLine = control->GetCurrentLine() - 1;
575         if (ppIf.Matches(control->GetLine(ppLine)) || ppElse.Matches(control->GetLine(ppLine)))
576         {
577             int depth = 1;
578             for (int i = ppLine + 1; i < control->GetLineCount(); ++i)
579             {
580                 if (control->GetLine(i).Find(wxT('#')) != wxNOT_FOUND) // limit testing due to performance cost
581                 {
582                     if (ppIf.Matches(control->GetLine(i))) // ignore else's, elif's, ...
583                         ++depth;
584                     else if (ppEnd.Matches(control->GetLine(i)))
585                         --depth;
586                 }
587                 if (depth == 0)
588                     break;
589             }
590             if (depth > 0)
591             {
592                 wxString endIf = wxT("endif");
593                 if (pp.Matches(control->GetLine(ppLine)))
594                 {
595                     endIf.Prepend(pp.GetMatch(control->GetLine(ppLine), 1));
596                     if (!pp.GetMatch(control->GetLine(ppLine), 3).IsEmpty())
597                         endIf.Append(wxT(" // ") + pp.GetMatch(control->GetLine(ppLine), 3));
598                 }
599                 else
600                     endIf.Prepend(wxT("#"));
601                 control->InsertText(pos, GetEOLStr(control->GetEOLMode()) + endIf);
602                 return;
603             }
604         }
605     }
606 
607     if ( control->IsComment(style) || control->IsPreprocessor(style) )
608         return;
609     if (ch == _T('\'') || ch == _T('"'))
610     {
611         if (   (control->GetCharAt(pos) == ch)
612             && (control->GetCharAt(pos - 2) != _T('\\')) )
613         {
614             control->DeleteBack();
615             control->GotoPos(pos);
616         }
617         else
618         {
619             const wxChar left = control->GetCharAt(pos - 2);
620             const wxChar right = control->GetCharAt(pos);
621             if (   control->IsCharacter(style)
622                 || control->IsString(style)
623                 || left == _T('\\')
624                 || (   (left > _T(' '))
625                     && (left != _T('('))
626                     && (left != _T('=')) )
627                 || (   (right > _T(' '))
628                     && (right != _T(')')) ) )
629             {
630                 return;
631             }
632             control->AddText(ch);
633             control->GotoPos(pos);
634         }
635         return;
636     }
637     if ( control->IsCharacter(style) || control->IsString(style) )
638         return;
639     const wxString leftBrace(_T("([{"));
640     const wxString rightBrace(_T(")]}"));
641     int index = leftBrace.Find(ch);
642     const wxString unWant(_T(");\n\r\t\b "));
643     const wxChar nextChar = control->GetCharAt(pos);
644     #if wxCHECK_VERSION(3, 0, 0)
645     if ((index != wxNOT_FOUND) && ((unWant.Find(wxUniChar(nextChar)) != wxNOT_FOUND) || (pos == control->GetLength())))
646     #else
647     if ((index != wxNOT_FOUND) && ((unWant.Find(nextChar) != wxNOT_FOUND) || (pos == control->GetLength())))
648     #endif
649     {
650         control->AddText(rightBrace.GetChar(index));
651         control->GotoPos(pos);
652         if (ch == _T('{'))
653         {
654             const int curLine = control->GetCurrentLine();
655             int keyLine = curLine;
656             wxString text;
657             do
658             {
659                 int keyPos = control->GetLineIndentPosition(keyLine);
660                 int start = control->WordStartPosition(keyPos, true);
661                 int end = control->WordEndPosition(keyPos, true);
662                 text = control->GetTextRange(start, end);
663             }
664             while (   (text.IsEmpty() || text == _T("public") || text == _T("protected") || text == _T("private"))
665                    && (text != _T("namespace"))
666                    && (--keyLine >= 0) );
667 
668             if (text == _T("class") || text == _T("struct") || text == _T("enum") || text == _T("union"))
669                 control->InsertText(control->GetLineEndPosition(curLine), _T(";"));
670 
671             const wxRegEx reg(_T("^[ \t]*{}[ \t]*"));
672             if (reg.Matches(control->GetCurLine()))
673             {
674                 control->NewLine();
675                 control->GotoPos(pos);
676                 control->NewLine();
677                 return;
678             }
679         }
680     }
681     else
682     {
683         index = rightBrace.Find(ch);
684         if (index != wxNOT_FOUND)
685         {
686             if (control->GetCharAt(pos) == ch)
687             {
688                 control->DeleteBack();
689                 control->GotoPos(pos);
690                 return;
691             }
692         }
693     }
694 }
695