mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-15 14:16:07 -06:00
640 lines
14 KiB
C++
640 lines
14 KiB
C++
#include "CodeEditor.h"
|
|
|
|
NAMESPACE_UPP
|
|
|
|
FindReplaceDlg::FindReplaceDlg()
|
|
{
|
|
ignorecase <<= THISBACK(Sync);
|
|
samecase <<= true;
|
|
Sync();
|
|
}
|
|
|
|
void FindReplaceDlg::Sync()
|
|
{
|
|
samecase.Enable(ignorecase);
|
|
}
|
|
|
|
bool FindReplaceDlg::Key(dword key, int cnt) {
|
|
if(key == K_CTRL_I) {
|
|
if(find.HasFocus()) {
|
|
find <<= itext;
|
|
return true;
|
|
}
|
|
if(replace.HasFocus()) {
|
|
replace <<= itext;
|
|
return true;
|
|
}
|
|
}
|
|
return TopWindow::Key(key, cnt);
|
|
}
|
|
|
|
Size FindReplaceDlg::GetAdjustedSize()
|
|
{
|
|
Size sz = GetLayoutSize();
|
|
if(!replacing)
|
|
sz.cy -= replace.GetRect().bottom - find.GetRect().bottom;
|
|
return sz;
|
|
}
|
|
|
|
void FindReplaceDlg::Setup(bool doreplace)
|
|
{
|
|
replacing = doreplace;
|
|
replace_lbl.Show(replacing);
|
|
replace.Show(replacing);
|
|
amend.Show(replacing);
|
|
// amendall.Show(false); // not yet implemented
|
|
Rect r = GetRect();
|
|
Size sz = GetAdjustedSize();
|
|
SetMinSize(sz);
|
|
r.SetSize(sz);
|
|
SetRect(r);
|
|
ActiveFocus(find);
|
|
Sync();
|
|
}
|
|
|
|
void CodeEditor::SetFound(int fi, int type, const WString& text)
|
|
{
|
|
Found& f = foundwild.At(fi);
|
|
f.type = type;
|
|
f.text = text;
|
|
}
|
|
|
|
int CodeEditor::Match(const wchar *f, const wchar *s, int line, bool we, bool ignorecase, int fi)
|
|
{
|
|
const wchar *b = s;
|
|
int n = 0;
|
|
WString ln;
|
|
while(*f) {
|
|
if(*f == WILDANY) {
|
|
f++;
|
|
WString wild;
|
|
for(;;) {
|
|
int nn = Match(f, s, line, we, ignorecase, fi + 1);
|
|
if(nn >= 0) {
|
|
SetFound(fi, WILDANY, wild);
|
|
return int(s - b) + n + nn;
|
|
}
|
|
wild.Cat(*s ? *s : '\n');
|
|
if(!*s++) return -1;
|
|
}
|
|
return -1;
|
|
}
|
|
else
|
|
if(*f == WILDONE) {
|
|
if(!*s) return -1;
|
|
SetFound(fi++, WILDONE, WString(*s, 1));
|
|
s++;
|
|
}
|
|
else
|
|
if(*f == WILDSPACE) {
|
|
const wchar *wb = s;
|
|
if(*s != ' ' && *s != '\t') return -1;
|
|
s++;
|
|
while(*s == ' ' || *s == '\t')
|
|
s++;
|
|
SetFound(fi++, WILDSPACE, WString(wb, s));
|
|
}
|
|
else
|
|
if(*f == WILDNUMBER) {
|
|
const wchar *wb = s;
|
|
if(*s < '0' || *s > '9') return -1;
|
|
s++;
|
|
while(*s >= '0' && *s <= '9')
|
|
s++;
|
|
SetFound(fi++, WILDNUMBER, WString(wb, s));
|
|
}
|
|
else
|
|
if(*f == WILDID) {
|
|
const wchar *wb = s;
|
|
if(!iscib(*s)) return -1;
|
|
s++;
|
|
while(iscidl(*s)) s++;
|
|
SetFound(fi++, WILDID, WString(wb, s));
|
|
}
|
|
else
|
|
if(*f == '\n') {
|
|
if(*s != '\0' || ++line >= GetLineCount()) return -1;
|
|
n += int(s - b) + 1;
|
|
ln = GetWLine(line);
|
|
s = b = ln;
|
|
}
|
|
else {
|
|
if(ignorecase ? ToUpper(*s) != ToUpper(*f) : *s != *f) return -1;
|
|
s++;
|
|
}
|
|
f++;
|
|
}
|
|
return we && iscidl(*s) ? -1 : int(s - b) + n;
|
|
}
|
|
|
|
bool CodeEditor::Find(bool back, const wchar *text, bool wholeword, bool ignorecase,
|
|
bool wildcards, bool block)
|
|
{
|
|
if(notfoundfw) MoveTextBegin();
|
|
if(notfoundbk) MoveTextEnd();
|
|
int cursor, pos;
|
|
if(found)
|
|
GetSelection(pos, cursor);
|
|
else
|
|
GetSelection(cursor, pos);
|
|
pos = cursor;
|
|
return FindFrom(pos, back, text, wholeword, ignorecase, wildcards, block);
|
|
}
|
|
|
|
bool CodeEditor::FindFrom(int pos, bool back, const wchar *text, bool wholeword, bool ignorecase,
|
|
bool wildcards, bool block) {
|
|
WString ft;
|
|
const wchar *s = text;
|
|
while(*s) {
|
|
int c = *s++;
|
|
if(c == '\\') {
|
|
c = *s++;
|
|
if(c == '\0') break;
|
|
if(c == 'n')
|
|
ft.Cat('\n');
|
|
else
|
|
if(c == 't')
|
|
ft.Cat('\t');
|
|
else
|
|
if(c >= ' ')
|
|
ft.Cat(c);
|
|
}
|
|
else
|
|
if(c >= ' ') {
|
|
if(wildcards)
|
|
ft.Cat(c == '*' ? WILDANY :
|
|
c == '?' ? WILDONE :
|
|
c == '%' ? WILDSPACE :
|
|
c == '#' ? WILDNUMBER :
|
|
c == '$' ? WILDID :
|
|
c
|
|
);
|
|
else
|
|
ft.Cat(c);
|
|
}
|
|
}
|
|
bool wb = wholeword ? iscidl(*ft) : false;
|
|
bool we = wholeword ? iscidl(*ft.Last()) : false;
|
|
if(ft.IsEmpty()) return false;
|
|
foundwild.Clear();
|
|
int line = GetLinePos(pos);
|
|
int linecount = GetLineCount();
|
|
WString ln = GetWLine(line);
|
|
const wchar *l = ln;
|
|
s = l + pos;
|
|
for(;;) {
|
|
for(;;) {
|
|
if(!wb || (s == l || !iscidl(s[-1]))) {
|
|
int n = Match(ft, s, line, we, ignorecase);
|
|
if(n >= 0) {
|
|
int pos = GetPos(line, int(s - l));
|
|
foundtext = GetW(pos, n);
|
|
if(!back || pos + n < cursor) {
|
|
if(!block) {
|
|
foundsel = true;
|
|
SetSelection(pos, pos + n);
|
|
foundsel = false;
|
|
CenterCursor();
|
|
}
|
|
foundpos = pos;
|
|
foundsize = n;
|
|
found = true;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
if(back) {
|
|
if(s-- == l) break;
|
|
}
|
|
else
|
|
if(!*s++) break;
|
|
}
|
|
if(back) {
|
|
if(--line < 0) break;
|
|
ln = GetWLine(line);
|
|
l = ln;
|
|
s = ln.End();
|
|
}
|
|
else {
|
|
if(++line >= linecount) break;
|
|
ln = GetWLine(line);
|
|
l = s = ln;
|
|
}
|
|
}
|
|
if(back)
|
|
notfoundbk = true;
|
|
else
|
|
notfoundfw = true;
|
|
ClearSelection();
|
|
return false;
|
|
}
|
|
|
|
void CodeEditor::FindReplaceAddHistory()
|
|
{
|
|
if(!IsNull(findreplace.find))
|
|
findreplace.find.AddHistory();
|
|
if(!IsNull(findreplace.replace))
|
|
findreplace.replace.AddHistory();
|
|
}
|
|
|
|
bool CodeEditor::Find(bool back, bool blockreplace, bool replace)
|
|
{
|
|
FindReplaceAddHistory();
|
|
if(Find(back, (WString)~findreplace.find, findreplace.wholeword,
|
|
findreplace.ignorecase, findreplace.wildcards, blockreplace)) {
|
|
if(!blockreplace) {
|
|
if(!findreplace.IsOpen()) {
|
|
findreplace.NoCenter();
|
|
OpenNormalFindReplace(replace);
|
|
}
|
|
Rect lr = GetLineScreenRect(GetLine(GetCursor()));
|
|
Size fsz = findreplace.GetAdjustedSize();
|
|
Rect r = GetTopCtrl()->GetRect();
|
|
int y = r.bottom - fsz.cy - 2 * GetStdFontCy(); // Subtracting 2 * Get..Cy() to fix broken LXDE WM
|
|
if(lr.bottom > y) y = r.top;
|
|
Rect wa = Ctrl::GetWorkArea();
|
|
findreplace.SetRect(RectC(wa.left + (wa.Width() - fsz.cx) / 2, y, fsz.cx, fsz.cy));
|
|
findreplace.amend.Enable();
|
|
SetFocus();
|
|
}
|
|
return true;
|
|
}
|
|
else {
|
|
CloseFindReplace();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
WString CodeEditor::GetWild(int type, int& i)
|
|
{
|
|
for(;;) {
|
|
if(i >= foundwild.GetCount()) return WString();
|
|
Found& f = foundwild[i++];
|
|
if(f.type == type) return f.text;
|
|
}
|
|
}
|
|
|
|
WString CodeEditor::GetReplaceText()
|
|
{
|
|
return GetReplaceText(~findreplace.replace, findreplace.wildcards,
|
|
findreplace.wildcards && findreplace.samecase);
|
|
}
|
|
|
|
WString CodeEditor::GetReplaceText(WString rs, bool wildcards, bool samecase)
|
|
{
|
|
int anyi = 0, onei = 0, spacei = 0, numberi = 0, idi = 0;
|
|
WString rt;
|
|
const wchar *s = rs;
|
|
while(*s) {
|
|
int c = *s++;
|
|
if(c == '\\') {
|
|
c = *s++;
|
|
if(c == '\0') break;
|
|
if(c == 'n')
|
|
rt.Cat('\n');
|
|
else
|
|
if(c == 't')
|
|
rt.Cat('\t');
|
|
else
|
|
if(c >= ' ')
|
|
rt.Cat(c);
|
|
}
|
|
else
|
|
if(c >= ' ') {
|
|
if(wildcards) {
|
|
WString w;
|
|
if(c == '*')
|
|
w = GetWild(WILDANY, anyi);
|
|
else
|
|
if(c == '?')
|
|
w = GetWild(WILDONE, onei);
|
|
else
|
|
if(c == '%')
|
|
w = GetWild(WILDSPACE, spacei);
|
|
else
|
|
if(c == '#')
|
|
w = GetWild(WILDNUMBER, numberi);
|
|
else
|
|
if(c == '$')
|
|
w = GetWild(WILDID, idi);
|
|
else
|
|
if(c == '@') {
|
|
c = *s++;
|
|
if(c == '\0') break;
|
|
if(c == '@') {
|
|
rt << AsString(replacei).ToWString();
|
|
continue;
|
|
}
|
|
if(c == '#') {
|
|
rt << AsString(replacei + 1).ToWString();
|
|
continue;
|
|
}
|
|
if(c >= '1' && c <= '9') {
|
|
c -= '1';
|
|
w = c < foundwild.GetCount() ? foundwild[c].text : WString();
|
|
}
|
|
else {
|
|
rt.Cat('@');
|
|
if(c >= ' ' && c < 255) rt.Cat(c);
|
|
continue;
|
|
}
|
|
}
|
|
else {
|
|
rt.Cat(c);
|
|
continue;
|
|
}
|
|
if(*s == '+') {
|
|
w = ToUpper(w);
|
|
s++;
|
|
}
|
|
else
|
|
if(*s == '-') {
|
|
w = ToLower(w);
|
|
s++;
|
|
}
|
|
else
|
|
if(*s == '!') {
|
|
w = InitCaps(w);
|
|
s++;
|
|
}
|
|
rt.Cat(w);
|
|
}
|
|
else
|
|
rt.Cat(c);
|
|
}
|
|
}
|
|
if(samecase) {
|
|
if(foundtext.GetCount() && rt.GetCount()) {
|
|
if(IsUpper(foundtext[0]))
|
|
rt.Set(0, ToUpper(rt[0]));
|
|
if(IsLower(foundtext[0]))
|
|
rt.Set(0, ToLower(rt[0]));
|
|
}
|
|
if(foundtext.GetCount() > 1) {
|
|
if(IsUpper(foundtext[1]))
|
|
for(int i = 1; i < rt.GetCount(); i++)
|
|
rt.Set(i, ToUpper(rt[i]));
|
|
if(IsLower(foundtext[1]))
|
|
for(int i = 1; i < rt.GetCount(); i++)
|
|
rt.Set(i, ToLower(rt[i]));
|
|
}
|
|
}
|
|
replacei++;
|
|
return rt;
|
|
}
|
|
|
|
void CodeEditor::Replace()
|
|
{
|
|
if(!findreplace.replacing)
|
|
return;
|
|
NextUndo();
|
|
FindReplaceAddHistory();
|
|
if(!found) return;
|
|
if(RemoveSelection()) {
|
|
Paste(GetReplaceText());
|
|
Find(false, false, true);
|
|
}
|
|
}
|
|
|
|
int CodeEditor::BlockReplace(WString find, WString replace, bool wholeword, bool ignorecase, bool wildcards, bool samecase)
|
|
{
|
|
NextUndo();
|
|
Refresh(); // Setting full-refresh here avoids Pre/Post Remove/Insert costs
|
|
int l, h;
|
|
if(!GetSelection(l, h)) return 0;
|
|
PlaceCaret(l);
|
|
int count = 0;
|
|
foundpos = l;
|
|
while(FindFrom(foundpos, false, find, wholeword, ignorecase, wildcards, true) && foundpos + foundsize <= h) {
|
|
CachePos(foundpos);
|
|
Remove(foundpos, foundsize);
|
|
WString rt = GetReplaceText(replace, wildcards, ignorecase && samecase);
|
|
Insert(foundpos, rt);
|
|
foundpos += rt.GetCount();
|
|
h = h - foundsize + rt.GetCount();
|
|
count++;
|
|
}
|
|
SetSelection(l, h);
|
|
return count;
|
|
}
|
|
|
|
void CodeEditor::BlockReplace()
|
|
{
|
|
BlockReplace(~findreplace.find, ~findreplace.replace, findreplace.wholeword,
|
|
findreplace.ignorecase, findreplace.wildcards, findreplace.samecase);
|
|
/*
|
|
NextUndo();
|
|
int l, h;
|
|
if(!GetSelection(l, h)) return;
|
|
PlaceCaret(l);
|
|
for(;;) {
|
|
if(!Find(false, true) || cursor >= h) break;
|
|
Refresh();
|
|
int hh, ll;
|
|
GetSelection(ll, hh);
|
|
CachePos(ll);
|
|
h = h - (hh - ll) + Paste(GetReplaceText());
|
|
}
|
|
SetSelection(l, h);
|
|
*/
|
|
}
|
|
|
|
void CodeEditor::OpenNormalFindReplace(bool replace)
|
|
{
|
|
findreplace.Setup(replace);
|
|
findreplace.itext = GetI();
|
|
findreplace.Title(replace ? "Find and Replace" : "Find");
|
|
findreplace.findback.Show();
|
|
findreplace.ok.SetLabel("Find Next");
|
|
findreplace.ok <<= THISBACK(DoFind);
|
|
findreplace.cancel <<= findreplace.WhenClose = callback(&findreplace, &TopWindow::Close);
|
|
findreplace.Open();
|
|
}
|
|
|
|
void CodeEditor::FindReplace(bool pick_selection, bool pick_text, bool replace)
|
|
{
|
|
if(findreplace.IsOpen())
|
|
findreplace.Close();
|
|
|
|
replacei = 0;
|
|
findreplace.CenterOwner();
|
|
WString find_text;
|
|
int find_pos = -1;
|
|
|
|
findreplace.Setup(replace);
|
|
|
|
if(pick_text || pick_selection)
|
|
{
|
|
WString s = GetSelection().ToWString();
|
|
if(IsSelection()) {
|
|
if(s.Find('\n') < 0)
|
|
find_text = s;
|
|
}
|
|
else
|
|
if(pick_text) {
|
|
int l, h;
|
|
l = h = GetCursor();
|
|
while(l > 0 && CharFilterCIdent(GetChar(l - 1)))
|
|
l--;
|
|
while(h < GetLength() && CharFilterCIdent(GetChar(h)))
|
|
h++;
|
|
find_text = Get(l, h - l).ToWString();
|
|
find_pos = h;
|
|
}
|
|
if(find_text.GetCount())
|
|
findreplace.find <<= find_text;
|
|
}
|
|
if(IsSelection() && replace) {
|
|
findreplace.itext = GetI();
|
|
findreplace.Title("Replace in selection");
|
|
findreplace.amend.Hide();
|
|
findreplace.findback.Hide();
|
|
findreplace.ok.SetLabel("Replace");
|
|
findreplace.ok <<= findreplace.Breaker(IDOK);
|
|
findreplace.cancel <<= findreplace.Breaker(IDCANCEL);
|
|
if(findreplace.Execute() == IDOK)
|
|
BlockReplace();
|
|
}
|
|
else {
|
|
if(find_pos >= 0)
|
|
SetCursor(find_pos);
|
|
OpenNormalFindReplace(replace);
|
|
findreplace.find.SetWantFocus();
|
|
}
|
|
}
|
|
|
|
void CodeEditor::InsertWildcard(int c)
|
|
{
|
|
iwc = c;
|
|
}
|
|
|
|
void FindWildcardMenu(Callback1<int> cb, Point p, bool tablf, Ctrl *owner)
|
|
{
|
|
MenuBar menu;
|
|
menu.Add("One or more spaces", callback1(cb, '%'));
|
|
menu.Add("One or more any characters", callback1(cb, '*'));
|
|
menu.Add("C++ identifier", callback1(cb, '$'));
|
|
menu.Add("Number", callback1(cb, '#'));
|
|
menu.Add("Any character", callback1(cb, '?'));
|
|
if(tablf) {
|
|
menu.Separator();
|
|
menu.Add("Tab", callback1(cb, '\t'));
|
|
menu.Add("Line feed", callback1(cb, '\n'));
|
|
}
|
|
menu.Execute(owner, p);
|
|
}
|
|
|
|
void CodeEditor::FindWildcard()
|
|
{
|
|
int l, h;
|
|
findreplace.find.GetSelection(l, h);
|
|
iwc = 0;
|
|
FindWildcardMenu(THISBACK(InsertWildcard), findreplace.find.GetPushScreenRect().TopRight(), true,
|
|
&findreplace);
|
|
if(iwc) {
|
|
findreplace.wildcards = true;
|
|
findreplace.find.SetFocus();
|
|
findreplace.find.SetSelection(l, h);
|
|
findreplace.find.RemoveSelection();
|
|
if(iwc == '\t') {
|
|
findreplace.find.Insert('\\');
|
|
findreplace.find.Insert('t');
|
|
}
|
|
else
|
|
if(iwc == '\n') {
|
|
findreplace.find.Insert('\\');
|
|
findreplace.find.Insert('n');
|
|
}
|
|
else
|
|
findreplace.find.Insert(iwc);
|
|
}
|
|
}
|
|
|
|
void CodeEditor::ReplaceWildcard()
|
|
{
|
|
MenuBar menu;
|
|
menu.Add("Matched spaces", THISBACK1(InsertWildcard, '%'));
|
|
menu.Add("Matched one or more any characters", THISBACK1(InsertWildcard, '*'));
|
|
menu.Add("Matched C++ identifier", THISBACK1(InsertWildcard, '$'));
|
|
menu.Add("Matched number", THISBACK1(InsertWildcard, '#'));
|
|
menu.Add("Matched any character", THISBACK1(InsertWildcard, '?'));
|
|
menu.Add("0-based replace index", THISBACK1(InsertWildcard, '0'));
|
|
menu.Add("1-based replace index", THISBACK1(InsertWildcard, '1'));
|
|
menu.Separator();
|
|
for(int i = 1; i <= 9; i++)
|
|
menu.Add(Format("Matched wildcard %d", i), THISBACK1(InsertWildcard, i));
|
|
menu.Separator();
|
|
menu.Add("To upper", THISBACK1(InsertWildcard, '+'));
|
|
menu.Add("To lower", THISBACK1(InsertWildcard, '-'));
|
|
menu.Add("InitCaps", THISBACK1(InsertWildcard, '!'));
|
|
menu.Separator();
|
|
menu.Add("Tab", THISBACK1(InsertWildcard, 20));
|
|
menu.Add("Line feed", THISBACK1(InsertWildcard, 21));
|
|
int l, h;
|
|
findreplace.replace.GetSelection(l, h);
|
|
iwc = 0;
|
|
menu.Execute(&findreplace, findreplace.replace.GetPushScreenRect().TopRight());
|
|
if(iwc) {
|
|
findreplace.wildcards = true;
|
|
findreplace.replace.SetFocus();
|
|
findreplace.replace.SetSelection(l, h);
|
|
findreplace.replace.RemoveSelection();
|
|
if(iwc == 20) {
|
|
findreplace.replace.Insert('\\');
|
|
findreplace.replace.Insert('t');
|
|
}
|
|
else
|
|
if(iwc == 21) {
|
|
findreplace.replace.Insert('\\');
|
|
findreplace.replace.Insert('n');
|
|
}
|
|
else
|
|
if(iwc == '0') {
|
|
findreplace.replace.Insert('@');
|
|
findreplace.replace.Insert('@');
|
|
}
|
|
else
|
|
if(iwc == '1') {
|
|
findreplace.replace.Insert('@');
|
|
findreplace.replace.Insert('#');
|
|
}
|
|
else
|
|
if(iwc >= 1 && iwc <= 9) {
|
|
findreplace.replace.Insert('@');
|
|
findreplace.replace.Insert(iwc + '0');
|
|
}
|
|
else
|
|
findreplace.replace.Insert(iwc);
|
|
}
|
|
}
|
|
|
|
void CodeEditor::CloseFindReplace()
|
|
{
|
|
if(!persistent_find_replace && findreplace.IsOpen())
|
|
findreplace.Close();
|
|
}
|
|
|
|
void CodeEditor::DoFind()
|
|
{
|
|
Find();
|
|
}
|
|
|
|
void CodeEditor::DoFindBack()
|
|
{
|
|
Find(true);
|
|
}
|
|
|
|
void CodeEditor::SerializeFind(Stream& s)
|
|
{
|
|
int version = 0;
|
|
s / version;
|
|
s % findreplace.find;
|
|
findreplace.find.SerializeList(s);
|
|
s % findreplace.wholeword % findreplace.ignorecase % findreplace.wildcards;
|
|
if(version >= 0)
|
|
s % findreplace.samecase;
|
|
s % findreplace.replace;
|
|
findreplace.replace.SerializeList(s);
|
|
}
|
|
|
|
END_UPP_NAMESPACE
|