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