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