mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-15 14:16:07 -06:00
1437 lines
31 KiB
C++
1437 lines
31 KiB
C++
#include "CodeEditor.h"
|
|
|
|
namespace Upp {
|
|
|
|
#define LLOG(x) // DLOG(x)
|
|
#define LTIMING(x) // RTIMING(x)
|
|
|
|
#define IMAGEVECTOR Vector
|
|
#define IMAGECLASS CodeEditorImg
|
|
#define IMAGEFILE <CodeEditor/CodeEditor.iml>
|
|
#include <Draw/iml_source.h>
|
|
|
|
#define TFILE <CodeEditor/CodeEditor.t>
|
|
#include <Core/t.h>
|
|
|
|
void RegisterSyntaxModules();
|
|
|
|
INITBLOCK {
|
|
RegisterSyntaxModules();
|
|
}
|
|
|
|
One<EditorSyntax> CodeEditor::GetSyntax(int line)
|
|
{
|
|
LTIMING("GetSyntax");
|
|
One<EditorSyntax> syntax = EditorSyntax::Create(highlight);
|
|
syntax->SpellCheckComments(spellcheck_comments);
|
|
int ln = 0;
|
|
for(int i = 0; i < __countof(syntax_cache); i++)
|
|
if(line >= syntax_cache[i].line && syntax_cache[i].line > 0) {
|
|
syntax->Set(syntax_cache[i].data);
|
|
ln = syntax_cache[i].line;
|
|
LLOG("Found " << line << " " << syntax_cache[i].line);
|
|
break;
|
|
}
|
|
line = min(line, GetLineCount());
|
|
if(line - ln > 10000) { // optimization hack for very huge files
|
|
syntax = EditorSyntax::Create(highlight);
|
|
ln = line - 10000; // we just pray it gets synced
|
|
}
|
|
while(ln < line) {
|
|
WString l = GetWLine(ln);
|
|
CTIMING("ScanSyntax3");
|
|
syntax->ScanSyntax(l, l.End(), ln, GetTabSize());
|
|
ln++;
|
|
static int d[] = { 0, 100, 1000, 3000, 7000 };
|
|
for(int i = 0; i < __countof(d); i++)
|
|
if(ln == cline - d[i]) {
|
|
syntax_cache[i + 1].data = syntax->Get();
|
|
syntax_cache[i + 1].line = ln;
|
|
}
|
|
}
|
|
syntax_cache[0].data = syntax->Get();
|
|
syntax_cache[0].line = ln;
|
|
return pick(syntax);
|
|
}
|
|
|
|
void CodeEditor::Highlight(const String& h)
|
|
{
|
|
highlight = h;
|
|
SetColor(LineEdit::INK_NORMAL, hl_style[HighlightSetup::INK_NORMAL].color);
|
|
SetColor(LineEdit::INK_DISABLED, hl_style[HighlightSetup::INK_DISABLED].color);
|
|
SetColor(LineEdit::INK_SELECTED, hl_style[HighlightSetup::INK_SELECTED].color);
|
|
SetColor(LineEdit::PAPER_NORMAL, hl_style[HighlightSetup::PAPER_NORMAL].color);
|
|
SetColor(LineEdit::PAPER_READONLY, hl_style[HighlightSetup::PAPER_READONLY].color);
|
|
SetColor(LineEdit::PAPER_SELECTED, hl_style[HighlightSetup::PAPER_SELECTED].color);
|
|
SetColor(LineEdit::WHITESPACE, hl_style[HighlightSetup::WHITESPACE].color);
|
|
SetColor(LineEdit::WARN_WHITESPACE, hl_style[HighlightSetup::WARN_WHITESPACE].color);
|
|
Refresh();
|
|
EditorBarLayout();
|
|
}
|
|
|
|
void CodeEditor::HighlightLine(int line, Vector<LineEdit::Highlight>& hl, int64 pos)
|
|
{
|
|
CTIMING("HighlightLine");
|
|
HighlightOutput hls(hl);
|
|
WString l = GetWLine(line);
|
|
GetSyntax(line)->Highlight(l.Begin(), l.End(), hls, this, line, pos);
|
|
if(illuminated.GetCount()) {
|
|
int q = 0;
|
|
while(q < l.GetCount() && q < hl.GetCount()) {
|
|
q = l.Find(illuminated, q);
|
|
if(q < 0)
|
|
break;
|
|
int n = illuminated.GetCount();
|
|
if(n > 1 || !iscid(illuminated[0]) ||
|
|
(q == 0 || !iscid(l[q - 1])) && (q + n >= l.GetCount() || !iscid(l[q + n])))
|
|
while(n-- && q < hl.GetCount()) {
|
|
const HlStyle& st = hl_style[PAPER_SELWORD];
|
|
hl[q].paper = st.color;
|
|
if(st.bold)
|
|
hl[q].font.Bold();
|
|
q++;
|
|
}
|
|
else
|
|
q++;
|
|
}
|
|
}
|
|
for(Point p : errors)
|
|
if(p.y == line && p.x < hl.GetCount()) {
|
|
hl[p.x].paper = hl_style[PAPER_ERROR_FILE].color;
|
|
hl[p.x].flags |= LineEdit::NOENDFILL;
|
|
}
|
|
}
|
|
|
|
void CodeEditor::Paint(Draw& w)
|
|
{
|
|
LineEdit::Paint(w);
|
|
|
|
if(!blk0_header)
|
|
return;
|
|
|
|
int sline = GetColumnLine(GetCursor()).y - GetScrollPos().y;
|
|
if(sline >= 0 && sline < 2)
|
|
return;
|
|
Point start, end; // paint zero level block header
|
|
if(!GetSyntax(GetScrollPos().y + 1)->GetBlockHeader(start, end))
|
|
return;
|
|
if(IsNull(start) || (!IsNull(end) && end.y > start.y))
|
|
return;
|
|
|
|
int line0 = -1;
|
|
for(int i = IsNull(end) ? 0 : end.y + 1; i <= start.y; i++) {
|
|
String l = GetUtf8Line(i);
|
|
bool hdr = false;
|
|
bool empty = true;
|
|
try {
|
|
CParser p(l);
|
|
while(!p.IsEof()) {
|
|
if(p.Char('(') || p.Id("class") || p.Id("struct") || p.Id("enum")) {
|
|
hdr = true;
|
|
break;
|
|
}
|
|
p.Skip();
|
|
}
|
|
}
|
|
catch(CParser::Error) {}
|
|
if(hdr) {
|
|
line0 = i;
|
|
break;
|
|
}
|
|
if(!empty && line0 < 0)
|
|
line0 = i;
|
|
}
|
|
|
|
if(line0 < 0 || GetScrollPos().y <= line0)
|
|
return;
|
|
|
|
int l = GetPos(line0, 0);
|
|
int h = GetPos(start.y, start.x) - 1;
|
|
if(l < 0 || l >= h)
|
|
return;
|
|
|
|
Size sz = GetSize();
|
|
|
|
bool dark = IsDark(HighlightSetup::GetHlStyle(HighlightSetup::PAPER_NORMAL).color);
|
|
Color paper = GrayColor(dark ? 40 : 245);
|
|
|
|
w.DrawRect(0, 0, GetSize().cx, GetFontSize().cy, paper);
|
|
|
|
Size hsz = CodeEditorImg::HdrSep0().GetSize();
|
|
for(int x = 0; x < sz.cx; x += hsz.cx) {
|
|
w.DrawImage(x, GetFontSize().cy, CodeEditorImg::HdrSep0(), paper);
|
|
w.DrawImage(x, GetFontSize().cy, CodeEditorImg::HdrSep(), dark ? White() : Black());
|
|
}
|
|
|
|
String ls = TrimBoth(Get(l, h - l));
|
|
WString r;
|
|
for(const char *s = ls; *s;) {
|
|
if(IsSpace(*s)) {
|
|
while(IsSpace(*s))
|
|
s++;
|
|
r.Cat(' ');
|
|
}
|
|
else
|
|
r.Cat(*s++);
|
|
}
|
|
r << " {";
|
|
|
|
Vector<LineEdit::Highlight> hln;
|
|
hln.SetCount(r.GetCount() + 1);
|
|
for(int i = 0; i < hln.GetCount(); i++) {
|
|
LineEdit::Highlight& h = hln[i];
|
|
h.paper = paper;
|
|
h.ink = SColorText();
|
|
h.chr = r[i];
|
|
h.font = GetFont();
|
|
}
|
|
|
|
HighlightOutput hls(hln);
|
|
GetSyntax(0)->Highlight(r.Begin(), r.End(), hls, this, 0, 0);
|
|
|
|
int ii = 0;
|
|
int x = 0;
|
|
while(ii < hln.GetCount() - 1) {
|
|
int n = 1;
|
|
while(ii + n < hln.GetCount() - 1 && hln[ii + n].ink == hln[ii].ink && hln[ii + n].font == hln[ii].font)
|
|
n++;
|
|
w.DrawText(x, 0, ~r + ii, hln[ii].font, hln[ii].ink, n);
|
|
x += GetTextSize(~r + ii, hln[ii].font, n).cx;
|
|
ii += n;
|
|
}
|
|
}
|
|
|
|
void CodeEditor::RefreshBlkHeader()
|
|
{
|
|
if(blk0_header)
|
|
Refresh(0, 0, GetSize().cx, GetFontSize().cy + CodeEditorImg::HdrSep().GetSize().cy);
|
|
}
|
|
|
|
void CodeEditor::NewScrollPos() {
|
|
bar.Scroll();
|
|
RefreshBlkHeader();
|
|
}
|
|
|
|
void CodeEditor::DirtyFrom(int line) {
|
|
for(int i = 0; i < __countof(syntax_cache); i++)
|
|
if(syntax_cache[i].line >= line)
|
|
syntax_cache[i].Clear();
|
|
|
|
if(check_edited)
|
|
bar.Refresh();
|
|
}
|
|
|
|
inline bool IsComment(int a, int b) {
|
|
return a == '/' && b == '*' || a == '*' && b == '/' || a == '/' && b == '/';
|
|
}
|
|
|
|
inline bool RBR(int c) {
|
|
return isbrkt(c);
|
|
}
|
|
|
|
String CodeEditor::GetRefreshInfo(int pos)
|
|
{
|
|
int ii = GetLine(pos) + 1;
|
|
return ii < GetLineCount() ? GetSyntax(ii)->Get() : String();
|
|
}
|
|
|
|
void CodeEditor::CheckSyntaxRefresh(int64 pos, const WString& text)
|
|
{
|
|
GetSyntax(GetLine(pos))->CheckSyntaxRefresh(*this, pos, text);
|
|
}
|
|
|
|
void CodeEditor::PreInsert(int pos, const WString& text)
|
|
{
|
|
refresh_info = GetRefreshInfo(pos);
|
|
}
|
|
|
|
void CodeEditor::PostInsert(int pos, const WString& text) {
|
|
if(check_edited)
|
|
bar.SetEdited(GetLine(pos));
|
|
if(!IsFullRefresh()) {
|
|
if(text.GetCount() > 200 || GetRefreshInfo(pos) != refresh_info || text.Find('\n') >= 0)
|
|
Refresh();
|
|
else
|
|
if(!console_mode)
|
|
CheckSyntaxRefresh(pos, text);
|
|
}
|
|
WhenUpdate();
|
|
EditorBarLayout();
|
|
}
|
|
|
|
void CodeEditor::PreRemove(int pos, int size) {
|
|
if(IsFullRefresh()) return;
|
|
if(size > 200)
|
|
Refresh();
|
|
else {
|
|
WString text = GetW(pos, size);
|
|
if(text.Find('\n') >= 0)
|
|
Refresh();
|
|
else {
|
|
CheckSyntaxRefresh(pos, text);
|
|
refresh_info = GetRefreshInfo(pos);
|
|
}
|
|
}
|
|
sbi.Refresh();
|
|
}
|
|
|
|
void CodeEditor::PostRemove(int pos, int size) {
|
|
if(check_edited)
|
|
bar.SetEdited(GetLine(pos));
|
|
WhenUpdate();
|
|
EditorBarLayout();
|
|
if(GetRefreshInfo(pos) != refresh_info)
|
|
Refresh();
|
|
sbi.Refresh();
|
|
}
|
|
|
|
void CodeEditor::ClearLines() {
|
|
bar.ClearLines();
|
|
sbi.Refresh();
|
|
}
|
|
|
|
void CodeEditor::InsertLines(int line, int count) {
|
|
if(IsView())
|
|
return;
|
|
bar.InsertLines(line, count);
|
|
if(line <= line2.GetCount())
|
|
line2.Insert(line, GetLine2(line), count);
|
|
EditorBarLayout();
|
|
}
|
|
|
|
void CodeEditor::RemoveLines(int line, int count) {
|
|
bar.RemoveLines(line, count);
|
|
if(line + count <= line2.GetCount())
|
|
line2.Remove(line, count);
|
|
EditorBarLayout();
|
|
}
|
|
|
|
void CodeEditor::Renumber2()
|
|
{
|
|
if(IsView())
|
|
return;
|
|
line2.SetCount(GetLineCount());
|
|
for(int i = 0; i < GetLineCount(); i++)
|
|
line2[i] = i;
|
|
}
|
|
|
|
int CodeEditor::GetLine2(int i) const
|
|
{
|
|
return line2.GetCount() ? line2[min(line2.GetCount() - 1, i)] : 0;
|
|
}
|
|
|
|
String CodeEditor::GetPasteText()
|
|
{
|
|
String h;
|
|
WhenPaste(h);
|
|
return h;
|
|
}
|
|
|
|
bool CodeEditor::IsCursorBracket(int64 pos) const
|
|
{
|
|
return pos == highlight_bracket_pos0 && hilite_bracket;
|
|
}
|
|
|
|
bool CodeEditor::IsMatchingBracket(int64 pos) const
|
|
{
|
|
return pos == highlight_bracket_pos && (hilite_bracket == 1 || hilite_bracket == 2 && bracket_flash);
|
|
}
|
|
|
|
void CodeEditor::CheckBrackets()
|
|
{
|
|
CancelBracketHighlight(highlight_bracket_pos0);
|
|
CancelBracketHighlight(highlight_bracket_pos);
|
|
if(!IsSelection()) {
|
|
if(!console_mode && GetSyntax(GetCursorLine())->CheckBrackets(*this, highlight_bracket_pos0, highlight_bracket_pos)) {
|
|
RefreshLine(GetLine(highlight_bracket_pos0));
|
|
RefreshLine(GetLine(highlight_bracket_pos));
|
|
bracket_start = GetTimeClick();
|
|
}
|
|
}
|
|
WhenSelection();
|
|
}
|
|
|
|
void CodeEditor::CopyWord() {
|
|
int64 p = GetCursor64();
|
|
if(iscidl(GetChar(p)) || (p > 0 && iscidl(GetChar(--p)))) {
|
|
int64 e = GetLength64();
|
|
int64 f = p;
|
|
while(--p >= 0 && iscidl(GetChar(p))) {}
|
|
++p;
|
|
while(++f < e && iscidl(GetChar(f)));
|
|
WString txt = GetW(p, LimitSize(f - p));
|
|
WriteClipboardUnicodeText(txt);
|
|
AppendClipboardText(txt.ToString());
|
|
}
|
|
}
|
|
|
|
void CodeEditor::DuplicateLine()
|
|
{
|
|
if(IsReadOnly()) return;
|
|
int i = GetLine(cursor);
|
|
int pos = GetPos32(i);
|
|
int len = GetLineLength(i);
|
|
Insert(pos + len, "\n" + GetW(pos, len));
|
|
}
|
|
|
|
void CodeEditor::SwapChars() {
|
|
if(IsReadOnly()) return;
|
|
int i = GetLine(cursor);
|
|
int j = GetPos32(i);
|
|
if (j < cursor && cursor - j < GetLineLength(i)) {
|
|
int p = (int)cursor;
|
|
WString txt(GetChar(p-1),1);
|
|
Remove(p-1,1);
|
|
Insert(p, txt, true);
|
|
PlaceCaret(p);
|
|
}
|
|
}
|
|
|
|
void CodeEditor::Put(int chr)
|
|
{
|
|
Insert((int)cursor++, WString(chr, 1), true);
|
|
}
|
|
|
|
void CodeEditor::FinishPut()
|
|
{
|
|
PlaceCaret(cursor);
|
|
Action();
|
|
}
|
|
|
|
void CodeEditor::ReformatComment()
|
|
{
|
|
if(IsReadOnly()) return;
|
|
NextUndo();
|
|
GetSyntax(GetLine(cursor))->ReformatComment(*this);
|
|
}
|
|
|
|
void CodeEditor::CancelBracketHighlight(int64& pos)
|
|
{
|
|
if(pos >= 0) {
|
|
RefreshLine(GetLine(pos));
|
|
pos = -1;
|
|
}
|
|
}
|
|
|
|
void CodeEditor::Periodic()
|
|
{
|
|
bool b = (((GetTimeClick() - bracket_start) >> 8) & 1) == 0;
|
|
if(b != bracket_flash && EditorSyntax::hilite_bracket == 2) {
|
|
bracket_flash = b;
|
|
if(highlight_bracket_pos0 >= 0)
|
|
RefreshLine(GetLine(highlight_bracket_pos0));
|
|
if(highlight_bracket_pos >= 0)
|
|
RefreshLine(GetLine(highlight_bracket_pos));
|
|
}
|
|
}
|
|
|
|
void CodeEditor::SelectionChanged()
|
|
{
|
|
int64 l, h;
|
|
WString nilluminated;
|
|
bool sel = GetSelection(l, h);
|
|
bool ill = false;
|
|
if(sel && h - l < 128) {
|
|
for(int64 i = l; i < h; i++) {
|
|
int c = GetChar(i);
|
|
if(c == '\n') {
|
|
nilluminated.Clear();
|
|
break;
|
|
}
|
|
if(!IsSpace(c))
|
|
ill = true;
|
|
nilluminated.Cat(c);
|
|
}
|
|
}
|
|
if(!ill)
|
|
nilluminated.Clear();
|
|
if(illuminated != nilluminated) {
|
|
illuminated = nilluminated;
|
|
Refresh();
|
|
}
|
|
if(!foundsel) {
|
|
if(!persistent_find_replace)
|
|
CloseFindReplace();
|
|
found = false;
|
|
notfoundfw = notfoundbk = false;
|
|
findreplace.amend.Disable();
|
|
}
|
|
CheckBrackets();
|
|
bar.Refresh();
|
|
RefreshBlkHeader();
|
|
}
|
|
|
|
void CodeEditor::Illuminate(const WString& text)
|
|
{
|
|
if(illuminated != text) {
|
|
illuminated = text;
|
|
Refresh();
|
|
}
|
|
}
|
|
|
|
bool CodeEditor::InsertRS(int chr, int count) {
|
|
if(IsReadOnly()) return true;
|
|
if(IsSelection()) {
|
|
InsertChar(chr, count);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void CodeEditor::IndentInsert(int chr, int count) {
|
|
if(InsertRS(chr)) return;
|
|
One<EditorSyntax> s = GetSyntax(GetCursorLine());
|
|
if(s)
|
|
s->IndentInsert(*this, chr, count);
|
|
else
|
|
InsertChar(chr, count, true);
|
|
}
|
|
|
|
void CodeEditor::Make(Event<String&> op)
|
|
{
|
|
if(IsReadOnly()) return;
|
|
Point cursor = GetColumnLine(GetCursor32());
|
|
Point scroll = GetScrollPos();
|
|
int l, h;
|
|
bool is_sel = GetSelection32(l, h);
|
|
if(!is_sel) { l = 0; h = GetLength32(); }
|
|
if(h <= l)
|
|
{
|
|
BeepExclamation();
|
|
return;
|
|
}
|
|
l = GetPos32(GetLine(l));
|
|
h = GetPos32(GetLine(h - 1) + 1);
|
|
String substring = Get(l, h - l);
|
|
String out = substring;
|
|
op(out);
|
|
if(out == substring)
|
|
{
|
|
BeepInformation();
|
|
return;
|
|
}
|
|
Remove(l, h - l);
|
|
Insert(l, out.ToWString());
|
|
if(is_sel)
|
|
SetSelection(l, l + out.GetLength());
|
|
SetCursor(GetGPos(cursor.y, cursor.x));
|
|
SetScrollPos(scroll);
|
|
}
|
|
|
|
void CodeEditor::TabsOrSpaces(String& out, bool maketabs)
|
|
{
|
|
String substring = out;
|
|
out.Clear();
|
|
int tab = GetTabSize();
|
|
if(tab <= 0) tab = 8;
|
|
for(const char *p = substring.Begin(), *e = substring.End(); p < e;)
|
|
{
|
|
int pos = 0;
|
|
for(; p < e; p++)
|
|
if(*p == '\t')
|
|
pos = (pos / tab + 1) * tab;
|
|
else if(*p == ' ')
|
|
pos++;
|
|
else
|
|
break;
|
|
if(maketabs)
|
|
{
|
|
out.Cat('\t', pos / tab);
|
|
const char *b = p;
|
|
while(p < e && *p++ != '\n')
|
|
;
|
|
out.Cat(b, int(p - b));
|
|
}
|
|
else
|
|
{
|
|
out.Cat(' ', pos);
|
|
for(; p < e && *p != '\n'; p++)
|
|
if(*p == '\t')
|
|
{
|
|
int npos = (pos / tab + 1) * tab;
|
|
out.Cat(' ', npos - pos);
|
|
pos = npos;
|
|
}
|
|
else
|
|
{
|
|
out.Cat(*p);
|
|
pos++;
|
|
}
|
|
if(p < e)
|
|
out.Cat(*p++);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CodeEditor::MakeTabsOrSpaces(bool maketabs)
|
|
{
|
|
Make(THISBACK1(TabsOrSpaces, maketabs));
|
|
}
|
|
|
|
void CodeEditor::LineEnds(String& out)
|
|
{
|
|
String substring = out;
|
|
out.Clear();
|
|
const char *q = ~substring;
|
|
const char *b = q;
|
|
for(const char *p = b, *e = substring.End(); p < e; p++)
|
|
{
|
|
if(*p == '\n') {
|
|
out.Cat(b, q);
|
|
out.Cat("\r\n");
|
|
b = q = p + 1;
|
|
}
|
|
else
|
|
if(*p != '\t' && *p != ' ' && *p != '\r')
|
|
q = p + 1;
|
|
}
|
|
out.Cat(b, substring.End());
|
|
}
|
|
|
|
void CodeEditor::MakeLineEnds()
|
|
{
|
|
Make(THISBACK(LineEnds));
|
|
}
|
|
|
|
void CodeEditor::MoveNextWord(bool sel) {
|
|
int64 p = GetCursor64();
|
|
int64 e = GetLength64();
|
|
if(iscidl(GetChar(p)))
|
|
while(p < e && iscidl(GetChar(p))) p++;
|
|
else
|
|
while(p < e && !iscidl(GetChar(p))) p++;
|
|
PlaceCaret(p, sel);
|
|
}
|
|
|
|
void CodeEditor::MovePrevWord(bool sel) {
|
|
int64 p = GetCursor64();
|
|
if(p == 0) return;
|
|
if(iscidl(GetChar(p - 1)))
|
|
while(p > 0 && iscidl(GetChar(p - 1))) p--;
|
|
else
|
|
while(p > 0 && !iscidl(GetChar(p - 1))) p--;
|
|
PlaceCaret(p, sel);
|
|
}
|
|
|
|
void CodeEditor::MoveNextBrk(bool sel) {
|
|
int64 p = GetCursor64();
|
|
int64 e = GetLength64();
|
|
if(!islbrkt(GetChar(p)))
|
|
while(p < e && !islbrkt(GetChar(p))) p++;
|
|
else {
|
|
int lvl = 1;
|
|
p++;
|
|
for(;;) {
|
|
if(p >= e) break;
|
|
int c = GetChar(p++);
|
|
if(islbrkt(c)) lvl++;
|
|
if(isrbrkt(c) && --lvl == 0) break;
|
|
}
|
|
}
|
|
PlaceCaret(p, sel);
|
|
}
|
|
|
|
void CodeEditor::MovePrevBrk(bool sel) {
|
|
int64 p = GetCursor64();
|
|
if(p < 2) return;
|
|
if(!isrbrkt(GetChar(p - 1))) {
|
|
if(p < GetLength64() - 1 && isrbrkt(GetChar(p)))
|
|
p++;
|
|
else {
|
|
while(p > 0 && !isrbrkt(GetChar(p - 1))) p--;
|
|
PlaceCaret(p, sel);
|
|
return;
|
|
}
|
|
}
|
|
int lvl = 1;
|
|
p -= 2;
|
|
for(;;) {
|
|
if(p <= 1) break;
|
|
int c = GetChar(p);
|
|
if(isrbrkt(c)) lvl++;
|
|
if(islbrkt(c) && --lvl == 0) break;
|
|
p--;
|
|
}
|
|
PlaceCaret(p, sel);
|
|
}
|
|
|
|
bool isspctab(int c) {
|
|
return c == ' ' || c == '\t';
|
|
}
|
|
|
|
void CodeEditor::DeleteWord() {
|
|
if(IsReadOnly() || RemoveSelection()) return;
|
|
int p = GetCursor32();
|
|
int e = GetLength32();
|
|
int c = GetChar(p);
|
|
if(iscidl(c))
|
|
while(p < e && iscidl(GetChar(p))) p++;
|
|
else
|
|
if(isspctab(c))
|
|
while(p < e && isspctab(GetChar(p))) p++;
|
|
else {
|
|
DeleteChar();
|
|
return;
|
|
}
|
|
Remove(GetCursor32(), p - GetCursor32());
|
|
}
|
|
|
|
void CodeEditor::DeleteWordBack() {
|
|
if(IsReadOnly() || RemoveSelection()) return;
|
|
int p = GetCursor32();
|
|
if(p < 1) return;
|
|
int c = GetChar(p - 1);
|
|
if(iscidl(GetChar(p - 1)))
|
|
while(p > 1 && iscidl(GetChar(p - 1))) p--;
|
|
else
|
|
if(isspctab(c))
|
|
while(p > 1 && isspctab(GetChar(p - 1))) p--;
|
|
else {
|
|
Backspace();
|
|
return;
|
|
}
|
|
Remove(p, GetCursor32() - p);
|
|
PlaceCaret(p);
|
|
}
|
|
|
|
void CodeEditor::SetLineSelection(int l, int h) {
|
|
SetSelection(GetPos64(l), GetPos64(h));
|
|
}
|
|
|
|
bool CodeEditor::GetLineSelection(int& l, int& h) {
|
|
int64 ll, hh;
|
|
if(!GetSelection(ll, hh)) return false;
|
|
l = GetLine(ll);
|
|
h = GetLinePos64(hh);
|
|
if(hh && h < GetLineCount()) h++;
|
|
SetLineSelection(l, h);
|
|
return true;
|
|
}
|
|
|
|
void CodeEditor::SwapUpDown(bool up)
|
|
{
|
|
int l, h;
|
|
if(GetSelection(l, h)) {
|
|
l = GetLine(l);
|
|
int hh = h;
|
|
h = GetLinePos(hh);
|
|
if(hh && h < GetLineCount()) h++;
|
|
}
|
|
else {
|
|
l = GetLine(cursor);
|
|
h = l + 1;
|
|
}
|
|
if(up) {
|
|
if(l == 0)
|
|
return;
|
|
Insert(GetPos(h), GetWLine(l - 1) + "\n");
|
|
Remove(GetPos(l - 1), GetLineLength(l - 1) + 1);
|
|
SetSelection(GetPos(l - 1), GetPos(h - 1));
|
|
}
|
|
else {
|
|
if(h >= GetLineCount() - 1)
|
|
return;
|
|
WString line = GetWLine(h) + "\n";
|
|
Remove(GetPos(h), GetLineLength(h) + 1);
|
|
Insert(GetPos(l), line);
|
|
SetSelection(GetPos(l + 1), GetPos(h + 1));
|
|
}
|
|
}
|
|
|
|
void CodeEditor::TabRight() {
|
|
if(IsReadOnly()) return;
|
|
int l, h;
|
|
if(!GetLineSelection(l, h)) return;
|
|
int ll = l;
|
|
String tab(indent_spaces ? ' ' : '\t', indent_spaces ? GetTabSize() : 1);
|
|
while(l < h)
|
|
Insert(GetPos32(l++), tab);
|
|
SetLineSelection(ll, h);
|
|
}
|
|
|
|
void CodeEditor::TabLeft() {
|
|
if(IsReadOnly()) return;
|
|
int l, h;
|
|
if(!GetLineSelection(l, h)) return;
|
|
int ll = l;
|
|
while(l < h) {
|
|
WString ln = GetWLine(l);
|
|
int spc = 0;
|
|
while(spc < tabsize && ln[spc] == ' ') spc++;
|
|
if(spc < tabsize && ln[spc] == '\t') spc++;
|
|
Remove(GetPos32(l++), spc);
|
|
}
|
|
SetLineSelection(ll, h);
|
|
}
|
|
|
|
bool CodeEditor::GetWordPos(int64 pos, int64& l, int64& h) {
|
|
l = h = pos;
|
|
if(!iscidl(GetChar(pos))) return false;
|
|
while(l > 0 && iscidl(GetChar(l - 1))) l--;
|
|
while(iscidl(GetChar(h))) h++;
|
|
return true;
|
|
}
|
|
|
|
String CodeEditor::GetWord(int64 pos)
|
|
{
|
|
int64 l, h;
|
|
GetWordPos(pos, l, h);
|
|
return Get(l, LimitSize(h - l));
|
|
}
|
|
|
|
String CodeEditor::GetWord()
|
|
{
|
|
return GetWord(cursor);
|
|
}
|
|
|
|
void CodeEditor::LeftDouble(Point p, dword keyflags) {
|
|
int64 l, h;
|
|
int64 pos = GetMousePos(p);
|
|
if(GetWordPos(pos, l, h))
|
|
SetSelection(l, h);
|
|
else
|
|
SetSelection(pos, pos + 1);
|
|
selkind = SEL_WORDS;
|
|
SetFocus();
|
|
SetCapture();
|
|
}
|
|
|
|
void CodeEditor::LeftTriple(Point p, dword keyflags)
|
|
{
|
|
LineEdit::LeftTriple(p, keyflags);
|
|
selkind = SEL_LINES;
|
|
SetFocus();
|
|
SetCapture();
|
|
}
|
|
|
|
void CodeEditor::LeftDown(Point p, dword keyflags) {
|
|
if((keyflags & K_CTRL) && WhenCtrlClick) {
|
|
WhenCtrlClick(GetMousePos(p));
|
|
return;
|
|
}
|
|
LineEdit::LeftDown(p, keyflags);
|
|
WhenLeftDown();
|
|
CloseFindReplace();
|
|
selkind = SEL_CHARS;
|
|
}
|
|
|
|
void CodeEditor::Tip::Paint(Draw& w)
|
|
{
|
|
Rect r = GetSize();
|
|
w.DrawRect(r, background);
|
|
r.left++;
|
|
if(d)
|
|
d->Paint(w, r, v, SColorText(), background, 0);
|
|
}
|
|
|
|
CodeEditor::Tip::Tip()
|
|
{
|
|
SetFrame(BlackFrame());
|
|
BackPaint();
|
|
}
|
|
|
|
bool CodeEditor::SyncCloseTip()
|
|
{
|
|
if(HasMouse() && IsVisible() && GetTopCtrl()->HasFocusDeep())
|
|
return false;
|
|
CloseTip();
|
|
return true;
|
|
}
|
|
|
|
void CodeEditor::SyncTip()
|
|
{
|
|
if(SyncCloseTip())
|
|
return;
|
|
Rect wa = GetWorkArea();
|
|
Point p = Upp::GetMousePos();
|
|
MouseTip mt;
|
|
mt.background = Blend(SWhite(), SLtYellow());
|
|
mt.pos = tippos;
|
|
mt.sz.cx = min(DPI(1000), 2 * wa.GetWidth() / 3);
|
|
if(tippos >= 0 &&
|
|
(WhenTip(mt) || delayed_tip && DelayedTip(mt) && p == delayed_pos)) {
|
|
mt.sz.cy = min(wa.GetHeight() / 2 - DPI(20), mt.sz.cy);
|
|
tip.d = mt.display;
|
|
tip.v = mt.value;
|
|
tip.background = mt.background;
|
|
Size sz = tip.AddFrameSize(mt.sz);
|
|
int y = p.y + DPI(24);
|
|
if(y + sz.cy > wa.bottom)
|
|
y = max(0, p.y - sz.cy - DPI(4));
|
|
int x = p.x;
|
|
if(x + sz.cx > wa.right)
|
|
x = max(0, wa.right - sz.cx);
|
|
tip.SetRect(RectC(x, y, sz.cx, sz.cy) & wa);
|
|
if(!tip.IsOpen())
|
|
tip.PopUp(this, false, false, true);
|
|
tip.Refresh();
|
|
}
|
|
else
|
|
CloseTip();
|
|
}
|
|
|
|
void CodeEditor::CloseTip()
|
|
{
|
|
if(tip.IsOpen())
|
|
tip.Close();
|
|
tip.d = NULL;
|
|
}
|
|
|
|
bool CodeEditor::MouseSelSpecial(Point p, dword flags) {
|
|
if((flags & K_MOUSELEFT) && HasFocus() && HasCapture() && !(flags & K_ALT) && selkind != SEL_CHARS) {
|
|
int64 c = GetMousePos(p);
|
|
int64 l, h;
|
|
|
|
if(selkind == SEL_LINES) {
|
|
l = c;
|
|
int i = GetLinePos64(l);
|
|
l = c - l;
|
|
h = min(l + GetLineLength(i) + 1, GetLength64() - 1);
|
|
c = c < anchor ? l : h;
|
|
}
|
|
else
|
|
c = iscidl(GetChar(c - 1)) && GetWordPos(c, l, h) ? c < anchor ? l : h : c;
|
|
PlaceCaret(c, mpos != c);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void CodeEditor::LeftRepeat(Point p, dword flags)
|
|
{
|
|
if(!MouseSelSpecial(p, flags))
|
|
LineEdit::LeftRepeat(p, flags);
|
|
}
|
|
|
|
void CodeEditor::MouseMove(Point p, dword flags) {
|
|
if(!MouseSelSpecial(p, flags))
|
|
LineEdit::MouseMove(p, flags);
|
|
|
|
tippos = Null;
|
|
|
|
if(IsSelection()) return;
|
|
|
|
if(p.x > 0) { // ignore calls from EditorBar::MouseMove
|
|
Size fsz = GetFontSize();
|
|
p = (p + fsz * (Size)sb.Get()) / fsz;
|
|
int64 h = GetGPos(p.y, p.x);
|
|
tippos = h < INT_MAX && GetColumnLine(h) == p ? (int)h : -1;
|
|
}
|
|
|
|
SyncTip();
|
|
delayed_tip = false;
|
|
delayed_pos = Upp::GetMousePos();
|
|
delayed.KillSet(1000, [=] {
|
|
delayed_tip = true;
|
|
SyncTip();
|
|
});
|
|
}
|
|
|
|
bool CodeEditor::DelayedTip(MouseTip& tip) { return false; }
|
|
|
|
Image CodeEditor::CursorImage(Point p, dword keyflags)
|
|
{
|
|
if(WhenCtrlClick && (keyflags & K_CTRL))
|
|
return Image::Hand();
|
|
if(tip.IsOpen())
|
|
return Image::Arrow();
|
|
return LineEdit::CursorImage(p, keyflags);
|
|
}
|
|
|
|
void CodeEditor::MouseLeave()
|
|
{
|
|
delayed_tip = false;
|
|
delayed_pos = Null;
|
|
tippos = -1;
|
|
LineEdit::MouseLeave();
|
|
CloseTip();
|
|
delayed.Kill();
|
|
}
|
|
|
|
WString CodeEditor::GetI()
|
|
{
|
|
int64 l, h;
|
|
WString ft;
|
|
if((GetSelection(l, h) || GetWordPos(GetCursor64(), l, h)) && h - l < 60)
|
|
while(l < h) {
|
|
int c = GetChar(l++);
|
|
if(c == '\n')
|
|
break;
|
|
ft.Cat(c);
|
|
}
|
|
return ft;
|
|
}
|
|
|
|
void CodeEditor::SetI(Ctrl *edit)
|
|
{
|
|
*edit <<= GetI();
|
|
}
|
|
|
|
void CodeEditor::Goto() {
|
|
String line = AsString(GetCursorLine());
|
|
if(EditText(line, t_("Go to"), t_("Line:")))
|
|
SetCursor(GetPos64(atoi(line) - 1));
|
|
}
|
|
|
|
bool CodeEditor::ToggleSimpleComment(int &start_line, int &end_line, bool usestars)
|
|
{
|
|
if(IsReadOnly()) return true;
|
|
|
|
int l, h;
|
|
if(!GetSelection32(l, h))
|
|
return true;
|
|
|
|
int pos = h;
|
|
start_line = GetLine(l);
|
|
end_line = GetLinePos32(pos);
|
|
|
|
if(usestars && start_line == end_line) {
|
|
Enclose("/*", "*/", l, h);
|
|
return true;
|
|
}
|
|
|
|
if(pos && end_line < GetLineCount()) end_line++;
|
|
SetLineSelection(start_line, end_line);
|
|
|
|
return false;
|
|
}
|
|
|
|
void CodeEditor::ToggleLineComments(bool usestars)
|
|
{
|
|
if(IsReadOnly()) return;
|
|
|
|
int start_line, end_line;
|
|
if(ToggleSimpleComment(start_line, end_line))
|
|
return;
|
|
|
|
int us = static_cast<int>(usestars);
|
|
|
|
bool is_commented = true;
|
|
|
|
if(usestars) {
|
|
is_commented &= GetChar(GetPos64(start_line) + 0) == '/' &&
|
|
GetChar(GetPos64(start_line) + 1) == '*';
|
|
|
|
is_commented &= GetChar(GetPos64(end_line - 1) + 1) == '*' &&
|
|
GetChar(GetPos64(end_line - 1) + 2) == '/';
|
|
}
|
|
|
|
for(int line = start_line + us; is_commented && (line < end_line - us * 2); ++line)
|
|
is_commented &= GetChar(GetPos64(line)) == (usestars ? ' ' : '/') &&
|
|
GetChar(GetPos64(line)+1) == (usestars ? '*' : '/');
|
|
|
|
if(!is_commented) {
|
|
|
|
if(usestars) {
|
|
Insert(GetPos32(end_line)," */\n");
|
|
Insert(GetPos32(start_line),"/*\n");
|
|
}
|
|
|
|
for(int line = start_line + us; line < end_line + us; ++line)
|
|
Insert(GetPos32(line), usestars ? " * " : "//");
|
|
}
|
|
else
|
|
{
|
|
int line = start_line;
|
|
if(usestars)
|
|
Remove(GetPos32(start_line), 3);
|
|
for(; line < end_line - us * 2; ++line)
|
|
Remove(GetPos32(line), 2 + us);
|
|
if(usestars)
|
|
Remove(GetPos32(line), 4);
|
|
}
|
|
|
|
if(usestars)
|
|
SetLineSelection(start_line, end_line + (is_commented ? -2 : 2));
|
|
else
|
|
SetLineSelection(start_line, end_line);
|
|
}
|
|
|
|
void CodeEditor::ToggleStarComments()
|
|
{
|
|
if(IsReadOnly()) return;
|
|
|
|
int start_line, end_line;
|
|
if(ToggleSimpleComment(start_line, end_line))
|
|
return;
|
|
|
|
bool is_commented =
|
|
GetChar(GetPos64(start_line)) == '/' &&
|
|
GetChar(GetPos64(start_line)+1) == '*' &&
|
|
GetChar(GetPos64(start_line)+2) == '\n' &&
|
|
GetChar(GetPos64(end_line-1)) == '*' &&
|
|
GetChar(GetPos64(end_line-1)+1) == '/' &&
|
|
GetChar(GetPos64(end_line-1)+2) == '\n';
|
|
|
|
if(!is_commented) {
|
|
// Backwards because inserting changes the end line #
|
|
Insert(GetPos32(end_line),"*/\n");
|
|
Insert(GetPos32(start_line),"/*\n");
|
|
SetLineSelection(start_line, end_line+2);
|
|
} else {
|
|
// Backwards because inserting changes the end line #
|
|
Remove(GetPos32(end_line-1),3);
|
|
Remove(GetPos32(start_line),3);
|
|
SetLineSelection(start_line, end_line-2);
|
|
}
|
|
}
|
|
|
|
void CodeEditor::Enclose(const char *c1, const char *c2, int l, int h)
|
|
{
|
|
if(IsReadOnly()) return;
|
|
|
|
if((l < 0 || h < 0) && !GetSelection32(l, h))
|
|
return;
|
|
Insert(l, WString(c1));
|
|
Insert(h + (int)strlen(c1), WString(c2));
|
|
ClearSelection();
|
|
SetCursor(h + (int)strlen(c1) + (int)strlen(c2));
|
|
}
|
|
|
|
bool CodeEditor::Key(dword code, int count) {
|
|
Time key_time = GetSysTime();
|
|
double diff;
|
|
if(!IsNull(last_key_time) && (diff = int(key_time - last_key_time)) <= 3)
|
|
stat_edit_time += diff;
|
|
last_key_time = key_time;
|
|
|
|
NextUndo();
|
|
if(code == replace_key) {
|
|
Replace();
|
|
return true;
|
|
}
|
|
if(IsRectSelection())
|
|
return LineEdit::Key(code, count);
|
|
switch(code) {
|
|
case K_SHIFT_CTRL_UP:
|
|
SwapUpDown(true);
|
|
return true;
|
|
case K_SHIFT_CTRL_DOWN:
|
|
SwapUpDown(false);
|
|
return true;
|
|
case K_CTRL_DELETE:
|
|
DeleteWord();
|
|
return true;
|
|
case K_CTRL_BACKSPACE:
|
|
DeleteWordBack();
|
|
return true;
|
|
case K_BACKSPACE:
|
|
if(!IsReadOnly() && !IsAnySelection() && indent_spaces) {
|
|
int c = GetCursor32();
|
|
Point ixln = GetIndexLine(c);
|
|
WString ln = GetWLine(ixln.y);
|
|
bool white = true;
|
|
int startindex = -1, pos = 0, tabsz = GetTabSize();
|
|
for(int i = 0; i < ixln.x; i++) {
|
|
if(ln[i] == '\t' || ln[i] == ' ') {
|
|
if(pos == 0)
|
|
startindex = i;
|
|
if(ln[i] == '\t' || ++pos >= tabsz)
|
|
pos = 0;
|
|
}
|
|
else {
|
|
white = false;
|
|
break;
|
|
}
|
|
}
|
|
if(white && startindex >= 0) {
|
|
int count = ixln.x - startindex;
|
|
PlaceCaret(c - count);
|
|
Remove(c - count, count);
|
|
Action();
|
|
return true;
|
|
}
|
|
}
|
|
break;
|
|
case K_SHIFT_CTRL_TAB:
|
|
return LineEdit::Key(K_TAB, count);
|
|
case K_ENTER:
|
|
if(!IsRectSelection())
|
|
IndentInsert('\n', count);
|
|
return true;
|
|
}
|
|
bool sel = code & K_SHIFT;
|
|
switch(code & ~K_SHIFT) {
|
|
case K_CTRL_F:
|
|
if(withfindreplace) {
|
|
FindReplace(sel, true, false);
|
|
return true;
|
|
}
|
|
break;
|
|
case K_CTRL_H:
|
|
if(withfindreplace) {
|
|
FindReplace(sel, true, true);
|
|
return true;
|
|
}
|
|
break;
|
|
case K_F3:
|
|
if(sel)
|
|
FindPrev();
|
|
else
|
|
FindNext();
|
|
return true;
|
|
// case K_CTRL_F3:
|
|
// FindWord(sel);
|
|
// return true;
|
|
case K_CTRL_RIGHT:
|
|
MoveNextWord(sel);
|
|
return true;
|
|
case K_CTRL_LEFT:
|
|
MovePrevWord(sel);
|
|
return true;
|
|
case K_CTRL_RBRACKET:
|
|
MoveNextBrk(sel);
|
|
return true;
|
|
case K_CTRL_LBRACKET:
|
|
MovePrevBrk(sel);
|
|
return true;
|
|
case K_CTRL_ADD:
|
|
Zoom(1);
|
|
return true;
|
|
case K_CTRL_SUBTRACT:
|
|
Zoom(-1);
|
|
return true;
|
|
case K_TAB:
|
|
if(!IsReadOnly()) {
|
|
if(IsSelection()) {
|
|
if(sel)
|
|
TabLeft();
|
|
else
|
|
TabRight();
|
|
return true;
|
|
}
|
|
if(!sel && indent_spaces) {
|
|
int x = GetColumnLine(GetCursor64()).x;
|
|
int add = GetTabSize() - x % GetTabSize();
|
|
InsertChar(' ', add, false);
|
|
return true;
|
|
}
|
|
}
|
|
default:
|
|
if(IsSelection() && auto_enclose) {
|
|
if(code == '(') {
|
|
Enclose("(", ")");
|
|
return true;
|
|
}
|
|
if(code == '{') {
|
|
Enclose("{", "}");
|
|
return true;
|
|
}
|
|
if(code == '\"') {
|
|
Enclose("\"", "\"");
|
|
return true;
|
|
}
|
|
if(code == '[') {
|
|
Enclose("[", "]");
|
|
return true;
|
|
}
|
|
if(code == '/') {
|
|
//Enclose("/*", "*/");
|
|
ToggleLineComments(false);
|
|
return true;
|
|
}
|
|
if(code == K_CTRL_SLASH)
|
|
{
|
|
ToggleLineComments(true);
|
|
return true;
|
|
}
|
|
if(code == '*') {
|
|
//Enclose("/*", "*/");
|
|
ToggleStarComments();
|
|
return true;
|
|
}
|
|
}
|
|
if(wordwrap && code > 0 && code < 65535) {
|
|
int limit = GetBorderColumn();
|
|
int pos = GetCursor32();
|
|
int lp = pos;
|
|
int l = GetLinePos32(lp);
|
|
if(limit > 10 && GetColumnLine(pos).x >= limit && lp == GetLineLength(l)) {
|
|
int lp0 = GetPos32(l);
|
|
WString ln = GetWLine(l);
|
|
int wl = (int)GetGPos(l, limit) - lp0;
|
|
while(wl > 0 && ln[wl - 1] != ' ')
|
|
wl--;
|
|
int sl = wl - 1;
|
|
while(sl > 0 && ln[wl - 1] != '\n' && ln[sl - 1] == ' ')
|
|
sl--;
|
|
wordwrap = false;
|
|
Remove(lp0 + sl, pos - (lp0 + sl));
|
|
SetCursor(lp0 + sl);
|
|
Put('\n');
|
|
for(int i = 0; i < wl && findarg(ln[i], ' ', '\t') >= 0; i++)
|
|
Put(ln[i]);
|
|
for(int i = wl; i < ln.GetCount(); i++)
|
|
Put(ln[i]);
|
|
while(count--)
|
|
Put(code);
|
|
FinishPut();
|
|
wordwrap = true;
|
|
return true;
|
|
}
|
|
|
|
}
|
|
if(code >= 32 && code < 128 && count == 1) {
|
|
IndentInsert(code, 1);
|
|
return true;
|
|
}
|
|
if(code == 9 && IsSelection())
|
|
return true;
|
|
}
|
|
if(GetCharset() != CHARSET_UTF8)
|
|
if(code >= 128 && code < 65536 && FromUnicode((wchar)code, GetCharset()) == DEFAULTCHAR)
|
|
return true;
|
|
return LineEdit::Key(code, count);
|
|
}
|
|
|
|
void CodeEditor::ForwardWhenBreakpoint(int i) {
|
|
WhenBreakpoint(i);
|
|
}
|
|
|
|
void CodeEditor::GotoBarLine(int line)
|
|
{
|
|
SetCursor(GetPos64(GetLineNo(line)));
|
|
}
|
|
|
|
void CodeEditor::Serialize(Stream& s) {
|
|
int version = 0;
|
|
s / version;
|
|
SerializeFind(s);
|
|
}
|
|
|
|
void CodeEditor::SetLineInfo(const LineInfo& lf)
|
|
{
|
|
bar.SetLineInfo(lf, GetLineCount());
|
|
}
|
|
|
|
void CodeEditor::PutI(WithDropChoice<EditString>& edit)
|
|
{
|
|
edit.AddButton().SetMonoImage(CodeEditorImg::I()).Tip(t_("Set word/selection (Ctrl+I)"))
|
|
<<= THISBACK1(SetI, &edit);
|
|
}
|
|
|
|
void CodeEditor::Zoom(int d)
|
|
{
|
|
Font f = GetFont();
|
|
int h = f.GetCy();
|
|
int q = f.GetHeight();
|
|
while(f.GetCy() == h && (d < 0 ? f.GetCy() > 5 : f.GetCy() < 80))
|
|
f.Height(q += d);
|
|
SetFont(f);
|
|
EditorBarLayout();
|
|
}
|
|
|
|
void CodeEditor::MouseWheel(Point p, int zdelta, dword keyFlags) {
|
|
if(keyFlags & K_CTRL)
|
|
Zoom(sgn(zdelta));
|
|
else
|
|
LineEdit::MouseWheel(p, zdelta, keyFlags);
|
|
}
|
|
|
|
void CodeEditor::Clear()
|
|
{
|
|
for(SyntaxPos& p : syntax_cache)
|
|
p.Clear();
|
|
LineEdit::Clear();
|
|
found = notfoundfw = notfoundbk = false;
|
|
}
|
|
|
|
void CodeEditor::ScrollBarItems::Paint(Draw& w)
|
|
{
|
|
Rect sr = sb.GetSliderRect();
|
|
for(const Tuple<int, Image, Color>& x : pos) {
|
|
int y = sb.GetSliderPos(x.Get<int>());
|
|
if(!IsNull(y)) {
|
|
Image m = x.Get<Image>();
|
|
Size isz = m.GetSize();
|
|
if(y + isz.cy >= sr.top && y < sr.bottom)
|
|
w.DrawImage(sr.CenterPoint().x - isz.cx / 2, sr.top + y - isz.cy / 2, m, x.Get<Color>());
|
|
}
|
|
}
|
|
Color bg = IsDarkTheme() ? GrayColor(70) : SColorLtFace();
|
|
|
|
if(editor.bar.li.GetCount() > 200000) // otherwise it gets too slow
|
|
return;
|
|
|
|
int py = -1; // accelerate long files
|
|
int h = max(sr.GetHeight() / max(sb.GetTotal(), 1) + DPI(1), DPI(4));
|
|
for(int i = 0; i < editor.bar.li.GetCount(); i++) {
|
|
int edit = editor.bar.li[i].edited;
|
|
if(edit) {
|
|
int y = sb.GetSliderPos(i);
|
|
if(y > sr.bottom)
|
|
break;
|
|
if(!IsNull(y) && y + h >= sr.top && y != py) {
|
|
int age = (int)(log((double)(editor.GetUndoCount() + 1 - edit)) * 30);
|
|
w.DrawRect(sr.left + DPI(2), sr.top + y, DPI(2), h,
|
|
Blend(SLtBlue(), bg, min(220, age)));
|
|
py = y;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CodeEditor::ScrollBarItems::ScrollBarItems(ScrollBar& sb, CodeEditor& e)
|
|
: sb(sb), editor(e) {
|
|
sb.Add(SizePos());
|
|
Transparent();
|
|
IgnoreMouse();
|
|
}
|
|
|
|
void CodeEditor::Errors(Vector<Point>&& errs)
|
|
{
|
|
errors = pick(errs);
|
|
Refresh();
|
|
sbi.pos.Clear();
|
|
for(Point p : errors)
|
|
sbi.pos.Add({ p.y, CodeEditorImg::dot(), LtRed() });
|
|
sbi.Refresh();
|
|
}
|
|
|
|
void CodeEditor::Layout()
|
|
{
|
|
sbi.Refresh();
|
|
LineEdit::Layout();
|
|
}
|
|
|
|
CodeEditor::CodeEditor()
|
|
: sbi(sb.y, *this) {
|
|
bracket_flash = false;
|
|
highlight_bracket_pos0 = 0;
|
|
bracket_start = 0;
|
|
stat_edit_time = 0;
|
|
last_key_time = Null;
|
|
SetFont(CourierZ(12));
|
|
AddFrame(bar);
|
|
bar.SetEditor(this);
|
|
UndoSteps(10000);
|
|
InitFindReplace();
|
|
bar.WhenBreakpoint = THISBACK(ForwardWhenBreakpoint);
|
|
bar.WhenAnnotationMove = WhenAnnotationMove.Proxy();
|
|
bar.WhenAnnotationClick = WhenAnnotationClick.Proxy();
|
|
bar.WhenAnnotationRightClick = WhenAnnotationRightClick.Proxy();
|
|
barline = true;
|
|
sb.WithSizeGrip();
|
|
HighlightSetup::InitOnce();
|
|
Highlight(Null);
|
|
sb.y.NoAutoHide();
|
|
sb.y.AddFrame(topsbbutton);
|
|
sb.y.AddFrame(topsbbutton1);
|
|
topsbbutton.Hide();
|
|
topsbbutton1.Hide();
|
|
highlight_bracket_pos = 10;
|
|
SetTimeCallback(-20, THISBACK(Periodic), TIMEID_PERIODIC);
|
|
auto_enclose = false;
|
|
mark_lines = true;
|
|
check_edited = false;
|
|
tippos = -1;
|
|
selkind = SEL_CHARS;
|
|
withfindreplace = true;
|
|
wordwrap = false;
|
|
blk0_header = false;
|
|
closetip.Set(-200, [=] { SyncCloseTip(); });
|
|
}
|
|
|
|
CodeEditor::~CodeEditor() {}
|
|
|
|
}
|