1 #include "CodeEditor.h"
2
3 namespace Upp {
4
5 #define LLOG(x) // DLOG(x)
6 #define LTIMING(x) // RTIMING(x)
7
8 #define IMAGEVECTOR Vector
9 #define IMAGECLASS CodeEditorImg
10 #define IMAGEFILE <CodeEditor/CodeEditor.iml>
11 #include <Draw/iml_source.h>
12
13 #define TFILE <CodeEditor/CodeEditor.t>
14 #include <Core/t.h>
15
16 void RegisterSyntaxModules();
17
18 INITBLOCK {
19 RegisterSyntaxModules();
20 }
21
22 One<EditorSyntax> CodeEditor::GetSyntax(int line)
23 {
24 LTIMING("GetSyntax");
25 One<EditorSyntax> syntax = EditorSyntax::Create(highlight);
26 syntax->SpellCheckComments(spellcheck_comments);
27 int ln = 0;
28 for(int i = 0; i < __countof(syntax_cache); i++)
29 if(line >= syntax_cache[i].line && syntax_cache[i].line > 0) {
30 syntax->Set(syntax_cache[i].data);
31 ln = syntax_cache[i].line;
32 LLOG("Found " << line << " " << syntax_cache[i].line);
33 break;
34 }
35 line = min(line, GetLineCount());
36 if(line - ln > 10000) { // optimization hack for huge files
37 syntax = EditorSyntax::Create(highlight);
38 ln = line - 10000;
39 }
40 while(ln < line) {
41 WString l = GetWLine(ln);
42 CTIMING("ScanSyntax3");
43 syntax->ScanSyntax(l, l.End(), ln, GetTabSize());
44 ln++;
45 static int d[] = { 0, 100, 2000, 10000, 50000 };
46 for(int i = 0; i < __countof(d); i++)
47 if(ln == cline - d[i]) {
48 syntax_cache[i + 1].data = syntax->Get();
49 syntax_cache[i + 1].line = ln;
50 }
51 }
52 syntax_cache[0].data = syntax->Get();
53 syntax_cache[0].line = ln;
54 return pick(syntax);
55 }
56
Highlight(const String & h)57 void CodeEditor::Highlight(const String& h)
58 {
59 highlight = h;
60 SetColor(LineEdit::INK_NORMAL, hl_style[HighlightSetup::INK_NORMAL].color);
61 SetColor(LineEdit::INK_DISABLED, hl_style[HighlightSetup::INK_DISABLED].color);
62 SetColor(LineEdit::INK_SELECTED, hl_style[HighlightSetup::INK_SELECTED].color);
63 SetColor(LineEdit::PAPER_NORMAL, hl_style[HighlightSetup::PAPER_NORMAL].color);
64 SetColor(LineEdit::PAPER_READONLY, hl_style[HighlightSetup::PAPER_READONLY].color);
65 SetColor(LineEdit::PAPER_SELECTED, hl_style[HighlightSetup::PAPER_SELECTED].color);
66 SetColor(LineEdit::WHITESPACE, hl_style[HighlightSetup::WHITESPACE].color);
67 SetColor(LineEdit::WARN_WHITESPACE, hl_style[HighlightSetup::WARN_WHITESPACE].color);
68 Refresh();
69 EditorBarLayout();
70 }
71
DirtyFrom(int line)72 void CodeEditor::DirtyFrom(int line) {
73 for(int i = 0; i < __countof(syntax_cache); i++)
74 if(syntax_cache[i].line >= line)
75 syntax_cache[i].Clear();
76
77 if(check_edited) {
78 bar.ClearErrors(line);
79 bar.Refresh();
80 }
81 }
82
IsComment(int a,int b)83 inline bool IsComment(int a, int b) {
84 return a == '/' && b == '*' || a == '*' && b == '/' || a == '/' && b == '/';
85 }
86
RBR(int c)87 inline bool RBR(int c) {
88 return isbrkt(c);
89 }
90
GetRefreshInfo(int pos)91 String CodeEditor::GetRefreshInfo(int pos)
92 {
93 int ii = GetLine(pos) + 1;
94 return ii < GetLineCount() ? GetSyntax(ii)->Get() : String();
95 }
96
CheckSyntaxRefresh(int64 pos,const WString & text)97 void CodeEditor::CheckSyntaxRefresh(int64 pos, const WString& text)
98 {
99 GetSyntax(GetLine(pos))->CheckSyntaxRefresh(*this, pos, text);
100 }
101
PreInsert(int pos,const WString & text)102 void CodeEditor::PreInsert(int pos, const WString& text)
103 {
104 refresh_info = GetRefreshInfo(pos);
105 }
106
PostInsert(int pos,const WString & text)107 void CodeEditor::PostInsert(int pos, const WString& text) {
108 if(check_edited)
109 bar.SetEdited(GetLine(pos));
110 if(!IsFullRefresh()) {
111 if(text.GetCount() > 200 || GetRefreshInfo(pos) != refresh_info || text.Find('\n') >= 0)
112 Refresh();
113 else
114 CheckSyntaxRefresh(pos, text);
115 }
116 WhenUpdate();
117 EditorBarLayout();
118 }
119
PreRemove(int pos,int size)120 void CodeEditor::PreRemove(int pos, int size) {
121 if(IsFullRefresh()) return;
122 if(size > 200)
123 Refresh();
124 else {
125 WString text = GetW(pos, size);
126 if(text.Find('\n') >= 0)
127 Refresh();
128 else {
129 CheckSyntaxRefresh(pos, text);
130 refresh_info = GetRefreshInfo(pos);
131 }
132 }
133 }
134
PostRemove(int pos,int size)135 void CodeEditor::PostRemove(int pos, int size) {
136 if(check_edited)
137 bar.SetEdited(GetLine(pos));
138 WhenUpdate();
139 EditorBarLayout();
140 if(GetRefreshInfo(pos) != refresh_info)
141 Refresh();
142 }
143
ClearLines()144 void CodeEditor::ClearLines() {
145 bar.ClearLines();
146 }
147
InsertLines(int line,int count)148 void CodeEditor::InsertLines(int line, int count) {
149 if(IsView())
150 return;
151 bar.InsertLines(line, count);
152 if(line <= line2.GetCount())
153 line2.Insert(line, GetLine2(line), count);
154 EditorBarLayout();
155 }
156
RemoveLines(int line,int count)157 void CodeEditor::RemoveLines(int line, int count) {
158 bar.RemoveLines(line, count);
159 if(line + count <= line2.GetCount())
160 line2.Remove(line, count);
161 EditorBarLayout();
162 }
163
Renumber2()164 void CodeEditor::Renumber2()
165 {
166 if(IsView())
167 return;
168 line2.SetCount(GetLineCount());
169 for(int i = 0; i < GetLineCount(); i++)
170 line2[i] = i;
171 }
172
GetLine2(int i) const173 int CodeEditor::GetLine2(int i) const
174 {
175 return line2.GetCount() ? line2[min(line2.GetCount() - 1, i)] : 0;
176 }
177
NewScrollPos()178 void CodeEditor::NewScrollPos() {
179 bar.Scroll();
180 }
181
GetPasteText()182 String CodeEditor::GetPasteText()
183 {
184 String h;
185 WhenPaste(h);
186 return h;
187 }
188
IsCursorBracket(int64 pos) const189 bool CodeEditor::IsCursorBracket(int64 pos) const
190 {
191 return pos == highlight_bracket_pos0 && hilite_bracket;
192 }
193
IsMatchingBracket(int64 pos) const194 bool CodeEditor::IsMatchingBracket(int64 pos) const
195 {
196 return pos == highlight_bracket_pos && (hilite_bracket == 1 || hilite_bracket == 2 && bracket_flash);
197 }
198
CheckBrackets()199 void CodeEditor::CheckBrackets()
200 {
201 CancelBracketHighlight(highlight_bracket_pos0);
202 CancelBracketHighlight(highlight_bracket_pos);
203 if(!IsSelection()) {
204 if(GetSyntax(GetCursorLine())->CheckBrackets(*this, highlight_bracket_pos0, highlight_bracket_pos)) {
205 RefreshLine(GetLine(highlight_bracket_pos0));
206 RefreshLine(GetLine(highlight_bracket_pos));
207 bracket_start = GetTimeClick();
208 }
209 }
210 WhenSelection();
211 }
212
CopyWord()213 void CodeEditor::CopyWord() {
214 int64 p = GetCursor64();
215 if(iscidl(GetChar(p)) || (p > 0 && iscidl(GetChar(--p)))) {
216 int64 e = GetLength64();
217 int64 f = p;
218 while(--p >= 0 && iscidl(GetChar(p))) {}
219 ++p;
220 while(++f < e && iscidl(GetChar(f)));
221 WString txt = GetW(p, LimitSize(f - p));
222 WriteClipboardUnicodeText(txt);
223 AppendClipboardText(txt.ToString());
224 }
225 }
226
DuplicateLine()227 void CodeEditor::DuplicateLine()
228 {
229 if(IsReadOnly()) return;
230 int i = GetLine(cursor);
231 int pos = GetPos32(i);
232 int len = GetLineLength(i);
233 Insert(pos + len, "\n" + GetW(pos, len));
234 }
235
SwapChars()236 void CodeEditor::SwapChars() {
237 if(IsReadOnly()) return;
238 int i = GetLine(cursor);
239 int j = GetPos32(i);
240 if (j < cursor && cursor - j < GetLineLength(i)) {
241 int p = (int)cursor;
242 WString txt(GetChar(p-1),1);
243 Remove(p-1,1);
244 Insert(p, txt, true);
245 PlaceCaret(p);
246 }
247 }
248
Put(int chr)249 void CodeEditor::Put(int chr)
250 {
251 Insert((int)cursor++, WString(chr, 1), true);
252 }
253
FinishPut()254 void CodeEditor::FinishPut()
255 {
256 PlaceCaret(cursor);
257 Action();
258 }
259
ReformatComment()260 void CodeEditor::ReformatComment()
261 {
262 if(IsReadOnly()) return;
263 NextUndo();
264 GetSyntax(GetLine(cursor))->ReformatComment(*this);
265 }
266
CancelBracketHighlight(int64 & pos)267 void CodeEditor::CancelBracketHighlight(int64& pos)
268 {
269 if(pos >= 0) {
270 RefreshLine(GetLine(pos));
271 pos = -1;
272 }
273 }
274
Periodic()275 void CodeEditor::Periodic()
276 {
277 bool b = (((GetTimeClick() - bracket_start) >> 8) & 1) == 0;
278 if(b != bracket_flash && EditorSyntax::hilite_bracket == 2) {
279 bracket_flash = b;
280 if(highlight_bracket_pos0 >= 0)
281 RefreshLine(GetLine(highlight_bracket_pos0));
282 if(highlight_bracket_pos >= 0)
283 RefreshLine(GetLine(highlight_bracket_pos));
284 }
285 }
286
SelectionChanged()287 void CodeEditor::SelectionChanged()
288 {
289 int64 l, h;
290 WString nilluminated;
291 bool sel = GetSelection(l, h);
292 bool ill = false;
293 if(sel && h - l < 128) {
294 for(int64 i = l; i < h; i++) {
295 int c = GetChar(i);
296 if(c == '\n') {
297 nilluminated.Clear();
298 break;
299 }
300 if(!IsSpace(c))
301 ill = true;
302 nilluminated.Cat(c);
303 }
304 }
305 if(!ill)
306 nilluminated.Clear();
307 if(illuminated != nilluminated) {
308 illuminated = nilluminated;
309 Refresh();
310 }
311 if(!foundsel) {
312 if(!persistent_find_replace)
313 CloseFindReplace();
314 found = false;
315 notfoundfw = notfoundbk = false;
316 findreplace.amend.Disable();
317 }
318 CheckBrackets();
319 bar.Refresh();
320 }
321
InsertRS(int chr,int count)322 bool CodeEditor::InsertRS(int chr, int count) {
323 if(IsReadOnly()) return true;
324 if(IsSelection()) {
325 InsertChar(chr, count);
326 return true;
327 }
328 return false;
329 }
330
IndentInsert(int chr,int count)331 void CodeEditor::IndentInsert(int chr, int count) {
332 if(InsertRS(chr)) return;
333 One<EditorSyntax> s = GetSyntax(GetCursorLine());
334 if(s)
335 s->IndentInsert(*this, chr, count);
336 else
337 InsertChar(chr, count);
338 }
339
Make(Event<String &> op)340 void CodeEditor::Make(Event<String&> op)
341 {
342 if(IsReadOnly()) return;
343 Point cursor = GetColumnLine(GetCursor32());
344 Point scroll = GetScrollPos();
345 int l, h;
346 bool is_sel = GetSelection32(l, h);
347 if(!is_sel) { l = 0; h = GetLength32(); }
348 if(h <= l)
349 {
350 BeepExclamation();
351 return;
352 }
353 l = GetPos32(GetLine(l));
354 h = GetPos32(GetLine(h - 1) + 1);
355 String substring = Get(l, h - l);
356 String out = substring;
357 op(out);
358 if(out == substring)
359 {
360 BeepInformation();
361 return;
362 }
363 Remove(l, h - l);
364 Insert(l, out.ToWString());
365 if(is_sel)
366 SetSelection(l, l + out.GetLength());
367 SetCursor(GetGPos(cursor.y, cursor.x));
368 SetScrollPos(scroll);
369 }
370
TabsOrSpaces(String & out,bool maketabs)371 void CodeEditor::TabsOrSpaces(String& out, bool maketabs)
372 {
373 String substring = out;
374 out.Clear();
375 int tab = GetTabSize();
376 if(tab <= 0) tab = 8;
377 for(const char *p = substring.Begin(), *e = substring.End(); p < e;)
378 {
379 int pos = 0;
380 for(; p < e; p++)
381 if(*p == '\t')
382 pos = (pos / tab + 1) * tab;
383 else if(*p == ' ')
384 pos++;
385 else
386 break;
387 if(maketabs)
388 {
389 out.Cat('\t', pos / tab);
390 const char *b = p;
391 while(p < e && *p++ != '\n')
392 ;
393 out.Cat(b, int(p - b));
394 }
395 else
396 {
397 out.Cat(' ', pos);
398 for(; p < e && *p != '\n'; p++)
399 if(*p == '\t')
400 {
401 int npos = (pos / tab + 1) * tab;
402 out.Cat(' ', npos - pos);
403 pos = npos;
404 }
405 else
406 {
407 out.Cat(*p);
408 pos++;
409 }
410 if(p < e)
411 out.Cat(*p++);
412 }
413 }
414 }
415
MakeTabsOrSpaces(bool maketabs)416 void CodeEditor::MakeTabsOrSpaces(bool maketabs)
417 {
418 Make(THISBACK1(TabsOrSpaces, maketabs));
419 }
420
LineEnds(String & out)421 void CodeEditor::LineEnds(String& out)
422 {
423 String substring = out;
424 out.Clear();
425 const char *q = ~substring;
426 const char *b = q;
427 for(const char *p = b, *e = substring.End(); p < e; p++)
428 {
429 if(*p == '\n') {
430 out.Cat(b, q);
431 out.Cat("\r\n");
432 b = q = p + 1;
433 }
434 else
435 if(*p != '\t' && *p != ' ' && *p != '\r')
436 q = p + 1;
437 }
438 out.Cat(b, substring.End());
439 }
440
MakeLineEnds()441 void CodeEditor::MakeLineEnds()
442 {
443 Make(THISBACK(LineEnds));
444 }
445
MoveNextWord(bool sel)446 void CodeEditor::MoveNextWord(bool sel) {
447 int64 p = GetCursor64();
448 int64 e = GetLength64();
449 if(iscidl(GetChar(p)))
450 while(p < e && iscidl(GetChar(p))) p++;
451 else
452 while(p < e && !iscidl(GetChar(p))) p++;
453 PlaceCaret(p, sel);
454 }
455
MovePrevWord(bool sel)456 void CodeEditor::MovePrevWord(bool sel) {
457 int64 p = GetCursor64();
458 if(p == 0) return;
459 if(iscidl(GetChar(p - 1)))
460 while(p > 0 && iscidl(GetChar(p - 1))) p--;
461 else
462 while(p > 0 && !iscidl(GetChar(p - 1))) p--;
463 PlaceCaret(p, sel);
464 }
465
MoveNextBrk(bool sel)466 void CodeEditor::MoveNextBrk(bool sel) {
467 int64 p = GetCursor64();
468 int64 e = GetLength64();
469 if(!islbrkt(GetChar(p)))
470 while(p < e && !islbrkt(GetChar(p))) p++;
471 else {
472 int lvl = 1;
473 p++;
474 for(;;) {
475 if(p >= e) break;
476 int c = GetChar(p++);
477 if(islbrkt(c)) lvl++;
478 if(isrbrkt(c) && --lvl == 0) break;
479 }
480 }
481 PlaceCaret(p, sel);
482 }
483
MovePrevBrk(bool sel)484 void CodeEditor::MovePrevBrk(bool sel) {
485 int64 p = GetCursor64();
486 if(p < 2) return;
487 if(!isrbrkt(GetChar(p - 1))) {
488 if(p < GetLength64() - 1 && isrbrkt(GetChar(p)))
489 p++;
490 else {
491 while(p > 0 && !isrbrkt(GetChar(p - 1))) p--;
492 PlaceCaret(p, sel);
493 return;
494 }
495 }
496 int lvl = 1;
497 p -= 2;
498 for(;;) {
499 if(p <= 1) break;
500 int c = GetChar(p);
501 if(isrbrkt(c)) lvl++;
502 if(islbrkt(c) && --lvl == 0) break;
503 p--;
504 }
505 PlaceCaret(p, sel);
506 }
507
isspctab(int c)508 bool isspctab(int c) {
509 return c == ' ' || c == '\t';
510 }
511
DeleteWord()512 void CodeEditor::DeleteWord() {
513 if(IsReadOnly() || RemoveSelection()) return;
514 int p = GetCursor32();
515 int e = GetLength32();
516 int c = GetChar(p);
517 if(iscidl(c))
518 while(p < e && iscidl(GetChar(p))) p++;
519 else
520 if(isspctab(c))
521 while(p < e && isspctab(GetChar(p))) p++;
522 else {
523 DeleteChar();
524 return;
525 }
526 Remove(GetCursor32(), p - GetCursor32());
527 }
528
DeleteWordBack()529 void CodeEditor::DeleteWordBack() {
530 if(IsReadOnly() || RemoveSelection()) return;
531 int p = GetCursor32();
532 if(p < 1) return;
533 int c = GetChar(p - 1);
534 if(iscidl(GetChar(p - 1)))
535 while(p > 1 && iscidl(GetChar(p - 1))) p--;
536 else
537 if(isspctab(c))
538 while(p > 1 && isspctab(GetChar(p - 1))) p--;
539 else {
540 Backspace();
541 return;
542 }
543 Remove(p, GetCursor32() - p);
544 PlaceCaret(p);
545 }
546
SetLineSelection(int l,int h)547 void CodeEditor::SetLineSelection(int l, int h) {
548 SetSelection(GetPos64(l), GetPos64(h));
549 }
550
GetLineSelection(int & l,int & h)551 bool CodeEditor::GetLineSelection(int& l, int& h) {
552 int64 ll, hh;
553 if(!GetSelection(ll, hh)) return false;
554 l = GetLine(ll);
555 h = GetLinePos64(hh);
556 if(hh && h < GetLineCount()) h++;
557 SetLineSelection(l, h);
558 return true;
559 }
560
TabRight()561 void CodeEditor::TabRight() {
562 if(IsReadOnly()) return;
563 int l, h;
564 if(!GetLineSelection(l, h)) return;
565 int ll = l;
566 String tab(indent_spaces ? ' ' : '\t', indent_spaces ? GetTabSize() : 1);
567 while(l < h)
568 Insert(GetPos32(l++), tab);
569 SetLineSelection(ll, h);
570 }
571
TabLeft()572 void CodeEditor::TabLeft() {
573 if(IsReadOnly()) return;
574 int l, h;
575 if(!GetLineSelection(l, h)) return;
576 int ll = l;
577 while(l < h) {
578 WString ln = GetWLine(l);
579 int spc = 0;
580 while(spc < tabsize && ln[spc] == ' ') spc++;
581 if(spc < tabsize && ln[spc] == '\t') spc++;
582 Remove(GetPos32(l++), spc);
583 }
584 SetLineSelection(ll, h);
585 }
586
GetWordPos(int64 pos,int64 & l,int64 & h)587 bool CodeEditor::GetWordPos(int64 pos, int64& l, int64& h) {
588 l = h = pos;
589 if(!iscidl(GetChar(pos))) return false;
590 while(l > 0 && iscidl(GetChar(l - 1))) l--;
591 while(iscidl(GetChar(h))) h++;
592 return true;
593 }
594
GetWord(int64 pos)595 String CodeEditor::GetWord(int64 pos)
596 {
597 int64 l, h;
598 GetWordPos(pos, l, h);
599 return Get(l, LimitSize(h - l));
600 }
601
GetWord()602 String CodeEditor::GetWord()
603 {
604 return GetWord(cursor);
605 }
606
LeftDouble(Point p,dword keyflags)607 void CodeEditor::LeftDouble(Point p, dword keyflags) {
608 int64 l, h;
609 int64 pos = GetMousePos(p);
610 if(GetWordPos(pos, l, h))
611 SetSelection(l, h);
612 else
613 SetSelection(pos, pos + 1);
614 selkind = SEL_WORDS;
615 SetFocus();
616 SetCapture();
617 }
618
LeftTriple(Point p,dword keyflags)619 void CodeEditor::LeftTriple(Point p, dword keyflags)
620 {
621 LineEdit::LeftTriple(p, keyflags);
622 selkind = SEL_LINES;
623 SetFocus();
624 SetCapture();
625 }
626
LeftDown(Point p,dword keyflags)627 void CodeEditor::LeftDown(Point p, dword keyflags) {
628 if((keyflags & K_CTRL) && WhenCtrlClick) {
629 WhenCtrlClick(GetMousePos(p));
630 return;
631 }
632 LineEdit::LeftDown(p, keyflags);
633 WhenLeftDown();
634 CloseFindReplace();
635 selkind = SEL_CHARS;
636 }
637
Paint(Draw & w)638 void CodeEditor::Tip::Paint(Draw& w)
639 {
640 Rect r = GetSize();
641 w.DrawRect(r, SColorInfo());
642 r.left++;
643 if(d)
644 d->Paint(w, r, v, SColorText(), SColorPaper(), 0);
645 }
646
Tip()647 CodeEditor::Tip::Tip()
648 {
649 SetFrame(BlackFrame());
650 BackPaint();
651 }
652
SyncTip()653 void CodeEditor::SyncTip()
654 {
655 MouseTip mt;
656 mt.pos = tippos;
657 if(tippos >= 0 && IsVisible() && WhenTip(mt)) {
658 tip.d = mt.display;
659 tip.v = mt.value;
660 Point p = Upp::GetMousePos();
661 Size sz = tip.AddFrameSize(mt.sz);
662 tip.SetRect(p.x, p.y + 24, sz.cx, sz.cy);
663 if(!tip.IsOpen())
664 tip.PopUp(this, false, false, true);
665 tip.Refresh();
666 }
667 else
668 CloseTip();
669 }
670
MouseSelSpecial(Point p,dword flags)671 bool CodeEditor::MouseSelSpecial(Point p, dword flags) {
672 if((flags & K_MOUSELEFT) && HasFocus() && HasCapture() && !(flags & K_ALT) && selkind != SEL_CHARS) {
673 int64 c = GetMousePos(p);
674 int64 l, h;
675
676 if(selkind == SEL_LINES) {
677 l = c;
678 int i = GetLinePos64(l);
679 l = c - l;
680 h = min(l + GetLineLength(i) + 1, GetLength64() - 1);
681 c = c < anchor ? l : h;
682 }
683 else
684 c = iscidl(GetChar(c - 1)) && GetWordPos(c, l, h) ? c < anchor ? l : h : c;
685 PlaceCaret(c, mpos != c);
686 return true;
687 }
688 return false;
689 }
690
LeftRepeat(Point p,dword flags)691 void CodeEditor::LeftRepeat(Point p, dword flags)
692 {
693 if(!MouseSelSpecial(p, flags))
694 LineEdit::LeftRepeat(p, flags);
695 }
696
MouseMove(Point p,dword flags)697 void CodeEditor::MouseMove(Point p, dword flags) {
698 if(!MouseSelSpecial(p, flags))
699 LineEdit::MouseMove(p, flags);
700 if(IsSelection()) return;
701 int64 h = GetMousePos(p);
702 tippos = h < INT_MAX ? (int)h : -1;
703 SyncTip();
704 }
705
CursorImage(Point p,dword keyflags)706 Image CodeEditor::CursorImage(Point p, dword keyflags)
707 {
708 if(WhenCtrlClick && (keyflags & K_CTRL))
709 return Image::Hand();
710 if(tip.IsOpen())
711 return Image::Arrow();
712 return LineEdit::CursorImage(p, keyflags);
713 }
714
MouseLeave()715 void CodeEditor::MouseLeave()
716 {
717 tippos = -1;
718 LineEdit::MouseLeave();
719 }
720
GetI()721 WString CodeEditor::GetI()
722 {
723 int64 l, h;
724 WString ft;
725 if((GetSelection(l, h) || GetWordPos(GetCursor64(), l, h)) && h - l < 60)
726 while(l < h) {
727 int c = GetChar(l++);
728 if(c == '\n')
729 break;
730 ft.Cat(c);
731 }
732 return ft;
733 }
734
735 //void CodeEditor::FindWord(bool back)
736 //{
737 // WString I = GetI();
738 // if(!IsNull(I))
739 // Find(back, I, true, false, false, false, false);
740 //}
741
SetI(Ctrl * edit)742 void CodeEditor::SetI(Ctrl *edit)
743 {
744 *edit <<= GetI();
745 }
746
Goto()747 void CodeEditor::Goto() {
748 String line = AsString(GetCursorLine());
749 if(EditText(line, t_("Go to"), t_("Line:")))
750 SetCursor(GetPos64(atoi(line) - 1));
751 }
752
ToggleSimpleComment(int & start_line,int & end_line,bool usestars)753 bool CodeEditor::ToggleSimpleComment(int &start_line, int &end_line, bool usestars)
754 {
755 if(IsReadOnly()) return true;
756
757 int l, h;
758 if(!GetSelection32(l, h))
759 return true;
760
761 int pos = h;
762 start_line = GetLine(l);
763 end_line = GetLinePos32(pos);
764
765 if(usestars && start_line == end_line) {
766 Enclose("/*", "*/", l, h);
767 return true;
768 }
769
770 if(pos && end_line < GetLineCount()) end_line++;
771 SetLineSelection(start_line, end_line);
772
773 return false;
774 }
775
ToggleLineComments(bool usestars)776 void CodeEditor::ToggleLineComments(bool usestars)
777 {
778 if(IsReadOnly()) return;
779
780 int start_line, end_line;
781 if(ToggleSimpleComment(start_line, end_line))
782 return;
783
784 int us = static_cast<int>(usestars);
785
786 bool is_commented = true;
787
788 if(usestars) {
789 is_commented &= GetChar(GetPos64(start_line) + 0) == '/' &&
790 GetChar(GetPos64(start_line) + 1) == '*';
791
792 is_commented &= GetChar(GetPos64(end_line - 1) + 1) == '*' &&
793 GetChar(GetPos64(end_line - 1) + 2) == '/';
794 }
795
796 for(int line = start_line + us; is_commented && (line < end_line - us * 2); ++line)
797 is_commented &= GetChar(GetPos64(line)) == (usestars ? ' ' : '/') &&
798 GetChar(GetPos64(line)+1) == (usestars ? '*' : '/');
799
800 if(!is_commented) {
801
802 if(usestars) {
803 Insert(GetPos32(end_line)," */\n");
804 Insert(GetPos32(start_line),"/*\n");
805 }
806
807 for(int line = start_line + us; line < end_line + us; ++line)
808 Insert(GetPos32(line), usestars ? " * " : "//");
809 }
810 else
811 {
812 int line = start_line;
813 if(usestars)
814 Remove(GetPos32(start_line), 3);
815 for(; line < end_line - us * 2; ++line)
816 Remove(GetPos32(line), 2 + us);
817 if(usestars)
818 Remove(GetPos32(line), 4);
819 }
820
821 if(usestars)
822 SetLineSelection(start_line, end_line + (is_commented ? -2 : 2));
823 else
824 SetLineSelection(start_line, end_line);
825 }
826
ToggleStarComments()827 void CodeEditor::ToggleStarComments()
828 {
829 if(IsReadOnly()) return;
830
831 int start_line, end_line;
832 if(ToggleSimpleComment(start_line, end_line))
833 return;
834
835 bool is_commented =
836 GetChar(GetPos64(start_line)) == '/' &&
837 GetChar(GetPos64(start_line)+1) == '*' &&
838 GetChar(GetPos64(start_line)+2) == '\n' &&
839 GetChar(GetPos64(end_line-1)) == '*' &&
840 GetChar(GetPos64(end_line-1)+1) == '/' &&
841 GetChar(GetPos64(end_line-1)+2) == '\n';
842
843 if(!is_commented) {
844 // Backwards because inserting changes the end line #
845 Insert(GetPos32(end_line),"*/\n");
846 Insert(GetPos32(start_line),"/*\n");
847 SetLineSelection(start_line, end_line+2);
848 } else {
849 // Backwards because inserting changes the end line #
850 Remove(GetPos32(end_line-1),3);
851 Remove(GetPos32(start_line),3);
852 SetLineSelection(start_line, end_line-2);
853 }
854 }
855
Enclose(const char * c1,const char * c2,int l,int h)856 void CodeEditor::Enclose(const char *c1, const char *c2, int l, int h)
857 {
858 if(IsReadOnly()) return;
859
860 if((l < 0 || h < 0) && !GetSelection32(l, h))
861 return;
862 Insert(l, WString(c1));
863 Insert(h + (int)strlen(c1), WString(c2));
864 ClearSelection();
865 SetCursor(h + (int)strlen(c1) + (int)strlen(c2));
866 }
867
Key(dword code,int count)868 bool CodeEditor::Key(dword code, int count) {
869 Time key_time = GetSysTime();
870 double diff;
871 if(!IsNull(last_key_time) && (diff = int(key_time - last_key_time)) <= 3)
872 stat_edit_time += diff;
873 last_key_time = key_time;
874
875 NextUndo();
876 if(code == replace_key) {
877 Replace();
878 return true;
879 }
880 switch(code) {
881 case K_CTRL_DELETE:
882 DeleteWord();
883 return true;
884 case K_CTRL_BACKSPACE:
885 DeleteWordBack();
886 return true;
887 case K_BACKSPACE:
888 if(!IsReadOnly() && !IsAnySelection() && indent_spaces) {
889 int c = GetCursor32();
890 Point ixln = GetIndexLine(c);
891 WString ln = GetWLine(ixln.y);
892 bool white = true;
893 int startindex = -1, pos = 0, tabsz = GetTabSize();
894 for(int i = 0; i < ixln.x; i++) {
895 if(ln[i] == '\t' || ln[i] == ' ') {
896 if(pos == 0)
897 startindex = i;
898 if(ln[i] == '\t' || ++pos >= tabsz)
899 pos = 0;
900 }
901 else {
902 white = false;
903 break;
904 }
905 }
906 if(white && startindex >= 0) {
907 int count = ixln.x - startindex;
908 PlaceCaret(c - count);
909 Remove(c - count, count);
910 Action();
911 return true;
912 }
913 }
914 break;
915 case K_SHIFT_CTRL_TAB:
916 return LineEdit::Key(K_TAB, count);
917 case K_ENTER:
918 IndentInsert('\n', count);
919 return true;
920 }
921 bool sel = code & K_SHIFT;
922 switch(code & ~K_SHIFT) {
923 case K_CTRL_F:
924 if(withfindreplace) {
925 FindReplace(sel, true, false);
926 return true;
927 }
928 break;
929 case K_CTRL_H:
930 if(withfindreplace) {
931 FindReplace(sel, true, true);
932 return true;
933 }
934 break;
935 case K_F3:
936 if(sel)
937 FindPrev();
938 else
939 FindNext();
940 return true;
941 // case K_CTRL_F3:
942 // FindWord(sel);
943 // return true;
944 case K_CTRL_RIGHT:
945 MoveNextWord(sel);
946 return true;
947 case K_CTRL_LEFT:
948 MovePrevWord(sel);
949 return true;
950 case K_CTRL_RBRACKET:
951 MoveNextBrk(sel);
952 return true;
953 case K_CTRL_LBRACKET:
954 MovePrevBrk(sel);
955 return true;
956 case K_CTRL_ADD:
957 Zoom(1);
958 return true;
959 case K_CTRL_SUBTRACT:
960 Zoom(-1);
961 return true;
962 case K_TAB:
963 if(!IsReadOnly()) {
964 if(IsSelection()) {
965 if(sel)
966 TabLeft();
967 else
968 TabRight();
969 return true;
970 }
971 if(!sel && indent_spaces) {
972 int x = GetColumnLine(GetCursor64()).x;
973 int add = GetTabSize() - x % GetTabSize();
974 InsertChar(' ', add, false);
975 return true;
976 }
977 }
978 default:
979 if(IsSelection() && auto_enclose) {
980 if(code == '(') {
981 Enclose("(", ")");
982 return true;
983 }
984 if(code == '{') {
985 Enclose("{", "}");
986 return true;
987 }
988 if(code == '\"') {
989 Enclose("\"", "\"");
990 return true;
991 }
992 if(code == '[') {
993 Enclose("[", "]");
994 return true;
995 }
996 if(code == '/') {
997 //Enclose("/*", "*/");
998 ToggleLineComments(false);
999 return true;
1000 }
1001 if(code == K_CTRL_SLASH)
1002 {
1003 ToggleLineComments(true);
1004 return true;
1005 }
1006 if(code == '*') {
1007 //Enclose("/*", "*/");
1008 ToggleStarComments();
1009 return true;
1010 }
1011 }
1012 if(wordwrap && code > 0 && code < 65535) {
1013 int limit = GetBorderColumn();
1014 int pos = GetCursor32();
1015 int lp = pos;
1016 int l = GetLinePos32(lp);
1017 if(limit > 10 && GetColumnLine(pos).x >= limit && lp == GetLineLength(l)) {
1018 int lp0 = GetPos32(l);
1019 WString ln = GetWLine(l);
1020 int wl = (int)GetGPos(l, limit) - lp0;
1021 while(wl > 0 && ln[wl - 1] != ' ')
1022 wl--;
1023 int sl = wl - 1;
1024 while(sl > 0 && ln[wl - 1] != '\n' && ln[sl - 1] == ' ')
1025 sl--;
1026 wordwrap = false;
1027 Remove(lp0 + sl, pos - (lp0 + sl));
1028 SetCursor(lp0 + sl);
1029 Put('\n');
1030 for(int i = 0; i < wl && findarg(ln[i], ' ', '\t') >= 0; i++)
1031 Put(ln[i]);
1032 for(int i = wl; i < ln.GetCount(); i++)
1033 Put(ln[i]);
1034 while(count--)
1035 Put(code);
1036 FinishPut();
1037 wordwrap = true;
1038 return true;
1039 }
1040
1041 }
1042 if(code >= 32 && code < 128 && count == 1) {
1043 IndentInsert(code, 1);
1044 return true;
1045 }
1046 if(code == 9 && IsSelection())
1047 return true;
1048 }
1049 if(GetCharset() != CHARSET_UTF8)
1050 if(code >= 128 && code < 65536 && FromUnicode((wchar)code, GetCharset()) == DEFAULTCHAR)
1051 return true;
1052 return LineEdit::Key(code, count);
1053 }
1054
ForwardWhenBreakpoint(int i)1055 void CodeEditor::ForwardWhenBreakpoint(int i) {
1056 WhenBreakpoint(i);
1057 }
1058
GotoLine(int line)1059 void CodeEditor::GotoLine(int line)
1060 {
1061 SetCursor(GetPos64(GetLineNo(line)));
1062 }
1063
Serialize(Stream & s)1064 void CodeEditor::Serialize(Stream& s) {
1065 int version = 0;
1066 s / version;
1067 SerializeFind(s);
1068 }
1069
SetLineInfo(const LineInfo & lf)1070 void CodeEditor::SetLineInfo(const LineInfo& lf)
1071 {
1072 bar.SetLineInfo(lf, GetLineCount());
1073 }
1074
HighlightLine(int line,Vector<LineEdit::Highlight> & hl,int64 pos)1075 void CodeEditor::HighlightLine(int line, Vector<LineEdit::Highlight>& hl, int64 pos)
1076 {
1077 CTIMING("HighlightLine");
1078 HighlightOutput hls(hl);
1079 WString l = GetWLine(line);
1080 GetSyntax(line)->Highlight(l.Begin(), l.End(), hls, this, line, pos);
1081 if(illuminated.GetCount()) {
1082 int q = 0;
1083 while(q < l.GetCount() && q < hl.GetCount()) {
1084 q = l.Find(illuminated, q);
1085 if(q < 0)
1086 break;
1087 int n = illuminated.GetCount();
1088 if(n > 1 || !iscid(illuminated[0]) ||
1089 (q == 0 || !iscid(l[q - 1])) && (q + n >= l.GetCount() || !iscid(l[q + n])))
1090 while(n-- && q < hl.GetCount()) {
1091 const HlStyle& st = hl_style[PAPER_SELWORD];
1092 hl[q].paper = st.color;
1093 if(st.bold)
1094 hl[q].font.Bold();
1095 q++;
1096 }
1097 else
1098 q++;
1099 }
1100 }
1101 }
1102
PutI(WithDropChoice<EditString> & edit)1103 void CodeEditor::PutI(WithDropChoice<EditString>& edit)
1104 {
1105 edit.AddButton().SetMonoImage(CodeEditorImg::I()).Tip(t_("Set word/selection (Ctrl+I)"))
1106 <<= THISBACK1(SetI, &edit);
1107 }
1108
Zoom(int d)1109 void CodeEditor::Zoom(int d)
1110 {
1111 Font f = GetFont();
1112 int h = f.GetCy();
1113 int q = f.GetHeight();
1114 while(f.GetCy() == h && (d < 0 ? f.GetCy() > 5 : f.GetCy() < 80))
1115 f.Height(q += d);
1116 SetFont(f);
1117 EditorBarLayout();
1118 }
1119
MouseWheel(Point p,int zdelta,dword keyFlags)1120 void CodeEditor::MouseWheel(Point p, int zdelta, dword keyFlags) {
1121 if(keyFlags & K_CTRL) {
1122 Zoom(sgn(zdelta));
1123 }
1124 else
1125 LineEdit::MouseWheel(p, zdelta, keyFlags);
1126 }
1127
Clear()1128 void CodeEditor::Clear()
1129 {
1130 for(SyntaxPos& p : syntax_cache)
1131 p.Clear();
1132 LineEdit::Clear();
1133 found = notfoundfw = notfoundbk = false;
1134 }
1135
CodeEditor()1136 CodeEditor::CodeEditor() {
1137 bracket_flash = false;
1138 highlight_bracket_pos0 = 0;
1139 bracket_start = 0;
1140 stat_edit_time = 0;
1141 last_key_time = Null;
1142 SetFont(CourierZ(12));
1143 AddFrame(bar);
1144 bar.SetEditor(this);
1145 UndoSteps(10000);
1146 InitFindReplace();
1147 bar.WhenBreakpoint = THISBACK(ForwardWhenBreakpoint);
1148 bar.WhenAnnotationMove = WhenAnnotationMove.Proxy();
1149 bar.WhenAnnotationClick = WhenAnnotationClick.Proxy();
1150 bar.WhenAnnotationRightClick = WhenAnnotationRightClick.Proxy();
1151 barline = true;
1152 sb.WithSizeGrip();
1153 DefaultHlStyles();
1154 Highlight(Null);
1155 sb.y.NoAutoHide();
1156 sb.y.AddFrame(topsbbutton);
1157 sb.y.AddFrame(topsbbutton1);
1158 topsbbutton.Hide();
1159 topsbbutton1.Hide();
1160 highlight_bracket_pos = 10;
1161 SetTimeCallback(-20, THISBACK(Periodic), TIMEID_PERIODIC);
1162 auto_enclose = false;
1163 mark_lines = true;
1164 check_edited = false;
1165 tippos = -1;
1166 selkind = SEL_CHARS;
1167 withfindreplace = true;
1168 wordwrap = false;
1169 }
1170
~CodeEditor()1171 CodeEditor::~CodeEditor() {}
1172
1173 }
1174