#include "CodeEditor.h"

namespace Upp {

void CodeEditor::InitFindReplace()
{
	findreplace.find.AddButton().SetMonoImage(CtrlImg::smallright()).Tip("Wildcard")
		<<= THISBACK(FindWildcard);
	findreplace.replace.AddButton().SetMonoImage(CtrlImg::smallright()).Tip("Wildcard")
		<<= THISBACK(ReplaceWildcard);
	PutI(findreplace.find);
	PutI(findreplace.replace);
	findreplace.amend <<= THISBACK(Replace);
	findreplace.amend_all <<= THISBACK1(ReplaceAll, false);
	findreplace.amend_rest <<= THISBACK1(ReplaceAll, true);
	findreplace.prev <<= THISBACK(DoFindBack);
	findreplace.replacing = false;
	found = notfoundfw = notfoundbk = foundsel = false;
	persistent_find_replace = false;
	findreplace.find <<= findreplace.wholeword <<= findreplace.wildcards
	                 <<= findreplace.incremental <<= findreplace.regexp
	                 <<= findreplace.ignorecase <<= THISBACK(IncrementalFind);
	ff_start_pos = -1;
	findreplace.find_all << [=] { FindAll(); };
}

FindReplaceDlg::FindReplaceDlg()
{
	find.NoUpDownKeys();
	samecase <<= true;
	close.Cancel();
	prev.SetImage(CtrlImg::SmallUp());
	amend.SetImage(CodeEditorImg::Replace());
	amend_all.SetImage(CodeEditorImg::ReplaceAll());
	amend_rest.SetImage(CodeEditorImg::ReplaceRest());
	find_all.SetImage(CodeEditorImg::FindAll());
	incremental <<= true;
	mode <<= THISBACK(Sync);
	mode.Hide();
	Sync();
	
	find.NullText("Find");
	replace.NullText("Replace");
}

void FindReplaceDlg::Sync()
{
	samecase.Enable(ignorecase);
	bool b = !regexp;
	wildcards.Enable(b);
	prev.Enable(b);
	ignorecase.Enable(b);
	wholeword.Enable(b);
	incremental_from_cursor.Enable(IsIncremental());
	b = !mode.IsVisible() || ~mode == 0;
	replace.Enable(b);
}

dword CodeEditor::find_next_key = K_F3;
dword CodeEditor::find_prev_key = K_SHIFT_F3;
dword CodeEditor::replace_key = K_CTRL_R;

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;
		}
	}
	if(key == K_ENTER) {
		next.WhenAction();
		return true;
	}
	if(findarg(key, K_TAB, K_SHIFT_TAB) >= 0 && replace.IsShown()) {
		if(find.HasFocus())
			replace.SetFocus();
		else
			find.SetFocus();
		return true;
	}
	if(key == K_ESCAPE) {
		close.WhenAction();
		return true;
	}
	return TopWindow::Key(key, cnt);
}

void FindReplaceDlg::Setup(bool doreplace)
{
	CtrlLayout(*this);
	close.SetImage(CodeEditorImg::Cancel());
	close.Tip("Close (Esc)");
	close.Normal();
	close.SetLabel("");
	next.SetImage(CtrlImg::SmallDown());
	next.Tip("Find next (" + GetKeyDesc(CodeEditor::find_next_key) + ")");
	next.Normal();
	next.SetLabel("");
	prev.Tip("Find prev (" + GetKeyDesc(CodeEditor::find_prev_key) + ")");
	amend.Tip("Replace (" + GetKeyDesc(CodeEditor::replace_key) + ")");
	find_all.Show();
	find_all.Tip("Find all");
	amend.Disable();
	replacing = doreplace;
	replace.Show(replacing);
	amend.Show(replacing);
	amend_all.Show(replacing);
	amend_rest.Show(replacing);
	Height(doreplace ? GetLayoutSize().cy : replace.GetRect().top);
	SetFrame(TopSeparatorFrame());
	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, bool block)
{
	foundsel = true;
	if(notfoundfw) MoveTextBegin();
	if(notfoundbk) MoveTextEnd();
	foundsel = false;
	int64 cursor, pos;
	if(found)
		GetSelection(pos, cursor);
	else
		GetSelection(cursor, pos);
	pos = cursor;
	bool b = FindFrom(pos, back, block);
	findreplace.amend.Enable(b);
	return b;
}

bool CodeEditor::RegExpFind(int64 pos, bool block)
{
	RegExp regex((String)~findreplace.find);
	
	int line = GetLinePos64(pos);
	String ln = ToUtf8(GetWLine(line).Mid(LimitSize(pos)));
	for(;;) {
		if(regex.Match(ln)) {
			for(int i = 0; i < regex.GetCount(); i++)
				SetFound(i, WILDANY, regex.GetString(i).ToWString());
			int off = regex.GetOffset();
			int len = utf8len(~ln + off, regex.GetLength());
			pos = GetPos64(line, utf8len(~ln, off) + (int)pos);
			foundtext = GetW(pos, len);
			if(!block) {
				foundsel = true;
				SetSelection(pos, pos + len);
				foundsel = false;
				CenterCursor();
			}
			foundpos = pos;
			foundsize = len;
			found = true;
			return true;
		}
		if(++line >= GetLineCount()) {
			WaitView(line);
			if(line >= GetLineCount())
				return false;
		}
		ln = GetUtf8Line(line);
		pos = 0;
		if(!SearchProgress(line))
			return false;
	}
}

void CodeEditor::FindAll()
{
	Vector<Tuple<int64, int>> found;
	foundpos = 0;
	while(FindFrom(foundpos, false, true)) {
		found.Add(MakeTuple(foundpos, foundsize));
		foundpos += foundsize;
		if(found.GetCount() >= 10000) {
			Exclamation("Too many matches, only first 10000 will be shown.");
			break;
		}
	}
	WhenFindAll(found);
}

bool CodeEditor::FindFrom(int64 pos, bool back, bool block)
{
	notfoundbk = notfoundfw = false;
	if(findreplace.regexp) {
		if(RegExpFind(pos, block))
			return true;
		if(back)
			notfoundbk = true;
		else
			notfoundfw = true;
		return false;
	}
	WString text = ~findreplace.find;
	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(findreplace.wildcards)
				ft.Cat(c == '*' ? WILDANY :
					   c == '?' ? WILDONE :
					   c == '%' ? WILDSPACE :
					   c == '#' ? WILDNUMBER :
					   c == '$' ? WILDID :
								  c
				);
			else
				ft.Cat(c);
		}
	}
	bool wb = findreplace.wholeword ? iscidl(*ft) : false;
	bool we = findreplace.wholeword ? iscidl(*ft.Last()) : false;
	if(ft.IsEmpty()) return false;
	foundwild.Clear();
	int line = GetLinePos64(pos);
	WString ln = GetWLine(line);
	const wchar *l = ln;
	s = l + pos;
	bool ignorecase = findreplace.ignorecase;
	for(;;) {
		for(;;) {
			if(!wb || (s == l || !iscidl(s[-1]))) {
				int n = Match(ft, s, line, we, ignorecase);
				if(n >= 0) {
					int64 pos = GetPos64(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 >= GetLineCount()) {
				WaitView(line);
				if(line >= GetLineCount())
					break;
			}
			ln = GetWLine(line);
			l = s = ln;
		}
		if(!SearchProgress(back ? GetLineCount() - line : line))
			break;
	}
	if(back)
		notfoundbk = true;
	else
		notfoundfw = true;
	return false;
}

void CodeEditor::FindReplaceAddHistory()
{
	if(!IsNull(findreplace.find))
		findreplace.find.AddHistory();
	if(!IsNull(findreplace.replace))
		findreplace.replace.AddHistory();
}

void CodeEditor::NoFindError()
{
	findreplace.find.Error(false);
}

void CodeEditor::NotFound()
{
	findreplace.find.Error();
	if(!findreplace.IsIncremental())
		SetFocus();
	findreplace.amend.Disable();
}

bool CodeEditor::Find(bool back, bool blockreplace, bool replace)
{
	NoFindError();
	if(Find(back, blockreplace)) {
		if(!blockreplace) {
			if(!findreplace.IsOpen())
				OpenNormalFindReplace(replace);
			if(!findreplace.IsIncremental())
				SetFocus();
		}
		return true;
	}
	else {
		NotFound();
		return false;
	}
}

WString CodeEditor::GetWild(int type, int& i)
{
	for(;;) {
		if(i >= foundwild.GetCount()) break;
		Found& f = foundwild[i++];
		if(f.type == type) return f.text;
	}
	for(int j = 0; j < foundwild.GetCount(); j++) {
		Found& f = foundwild[j++];
		if(f.type == type) return f.text;
	}
	return WString();
}

WString CodeEditor::GetReplaceText()
{
	WString rs = ~findreplace.replace;
	bool wildcards = findreplace.wildcards;
	bool samecase = findreplace.ignorecase && findreplace.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;
	bool h = persistent_find_replace;
	persistent_find_replace = true; // avoid closing of findreplace by selection change
	if(RemoveSelection()) {
		Paste(GetReplaceText());
		Find(false, false, true);
	}
	persistent_find_replace = h;
}

int CodeEditor::BlockReplace()
{
	NextUndo();
	Refresh(); // Setting full-refresh here avoids Pre/Post Remove/Insert costs
	int l, h;
	if(!GetSelection32(l, h)) return 0;
	PlaceCaret(l);
	int count = 0;
	foundpos = l;
	Index<int> ln;
	StartSearchProgress(l, h);
	while(FindFrom(foundpos, false, true) && foundpos + foundsize <= h) {
		CachePos(foundpos);
		if(~findreplace.mode == 0) {
			Remove((int)foundpos, foundsize);
			WString rt = GetReplaceText();
			Insert((int)foundpos, rt);
			foundpos += rt.GetCount();
			h = h - foundsize + rt.GetCount();
			count++;
		}
		else {
			ln.FindAdd(GetLine(foundpos));
			foundpos += foundsize;
		}
	}
	if(SearchCanceled())
		return count;
	EndSearchProgress();
	Progress pi("Removing lines");
	if(~findreplace.mode != 0) {
		ClearSelection();
		int ll = GetLine(l);
		int lh = GetLine(h);
		if(GetPos64(lh) == h)
			lh--;
		bool mm = ~findreplace.mode == 1;
		String replace;
		pi.SetTotal(lh - ll + 1);
		for(int i = ll; i <= lh; i++) {
			pi.Set(i, lh - ll + 1);
			if(pi.Canceled())
				return 0;
			if((ln.Find(i) >= 0) == mm) {
				replace << GetUtf8Line(i) << "\n";
				count++;
			}
		}
		int pos = GetPos32(ll);
		Remove(pos, GetPos32(GetLine(h)) - pos);
		SetSelection(pos, pos + Insert(pos, replace));
	}
	else
		SetSelection(l, h);
	return count;
}

void CodeEditor::OpenNormalFindReplace0(bool replace)
{
	findreplace.incremental.Enable(GetLength64() < 2000000);
	findreplace.Setup(replace);
	findreplace.find_all.Show(!replace && WhenFindAll);
	findreplace.itext = GetI();
	findreplace.prev.Show();
	findreplace.next <<= THISBACK(DoFind);
	findreplace.close <<= THISBACK(EscapeFindReplace);
	if(!findreplace.IsOpen())
		InsertFrame(FindFrame(sb), findreplace);
	WhenOpenFindReplace();
	findreplace.find.SetFocus();
}

void CodeEditor::OpenNormalFindReplace(bool replace)
{
	OpenNormalFindReplace0(replace);
//	if(!findreplace.incremental_from_cursor)
//		IncrementalFind();
}

void CodeEditor::FindReplace(bool pick_selection, bool pick_text, bool replace)
{
	if(IsReadOnly())
		replace = false;
	
	if(findreplace.IsOpen())
		CloseFindReplace();

	ff_start_pos = GetCursor32();

	replacei = 0;
	WString find_text;
	int find_pos = -1;
	
	findreplace.Setup(replace);
	
	if(pick_text || pick_selection)
	{
		if(IsSelection()) {
			int l, h;
			GetSelection32(l, h);
			if(h - l < 100) {
				find_text = GetSelectionW();
				if(find_text.Find('\n') >= 0)
					find_text.Clear();
			}
		}
		else
		if(pick_text) {
			int l, h;
			l = h = GetCursor32();
			while(l > 0 && CharFilterCIdent(GetChar(l - 1)))
				l--;
			while(h < GetLength64() && 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();
		SetLayout_BlockReplaceLayout(findreplace);
		findreplace.SetRect(WithBlockReplaceLayout<EmptyClass>::GetLayoutSize());
		findreplace.Title(t_("Replace in selection"));
		findreplace.find_all.Hide();
		findreplace.amend.Hide();
		findreplace.amend_all.Hide();
		findreplace.amend_rest.Hide();
		findreplace.prev.Hide();
		findreplace.next.Ok() <<= findreplace.Breaker(IDOK);
		findreplace.close.Cancel() <<= findreplace.Breaker(IDCANCEL);
		findreplace.close.SetImage(Null);
		findreplace.close.Tip("");
		findreplace.next.SetImage(Null);
		findreplace.next.Tip("");
		findreplace.mode.Show();
		findreplace.mode <<= 0;
		findreplace.Sync();
		if(findreplace.Execute() == IDOK)
			BlockReplace();
		findreplace.mode.Hide();
	}
	else {
		if(find_pos >= 0)
			SetCursor(find_pos);
		OpenNormalFindReplace(replace);
		findreplace.find.SetFocus();
	}
}

void CodeEditor::ReplaceAll(bool rest)
{
	int l, h;
	GetSelection32(l, h);
	int c = min(l, h);
	findreplace.mode <<= 0;
	SetSelection(rest * c, GetLength64());
	BlockReplace();
	SetCursor(c);
}

void CodeEditor::InsertWildcard(const char *s)
{
	iwc = s;
}

void FindWildcardMenu(Callback1<const char *> cb, Point p, bool tablf, Ctrl *owner, bool regexp)
{
	MenuBar menu;
	if(regexp) {
		menu.Add(t_("One or more spaces"), callback1(cb, " +"));
		menu.Add(t_("One or more any characters"), callback1(cb, ".+"));
		menu.Add(t_("Word"), callback1(cb, "\\w+"));
		menu.Add(t_("Number"), callback1(cb, "\\d+"));
		menu.Add(t_("Any character"), callback1(cb, "."));
		if(tablf) {
			menu.Separator();
			menu.Add(t_("Tab"), callback1(cb, "\\t"));
		}
	}
	else {
		menu.Add(t_("One or more spaces"), callback1(cb, "%"));
		menu.Add(t_("One or more any characters"), callback1(cb, "*"));
		menu.Add(t_("C++ identifier"), callback1(cb, "$"));
		menu.Add(t_("Number"), callback1(cb, "#"));
		menu.Add(t_("Any character"), callback1(cb, "?"));
		if(tablf) {
			menu.Separator();
			menu.Add(t_("Tab"), callback1(cb, "\\t"));
			menu.Add(t_("Line feed"), callback1(cb, "\\n"));
		}
	}
	menu.Execute(owner, p);
}

void CodeEditor::FindWildcard()
{
	int l, h;
	findreplace.find.GetSelection(l, h);
	iwc.Clear();
	FindWildcardMenu(THISBACK(InsertWildcard), findreplace.find.GetPushScreenRect().TopRight(), true,
	                 &findreplace, findreplace.regexp);
	if(iwc.GetCount()) {
		if(!findreplace.regexp)
			findreplace.wildcards = true;
		findreplace.find.SetFocus();
		findreplace.find.SetSelection(l, h);
		findreplace.find.RemoveSelection();
		findreplace.find.Insert(iwc);
	}
}

void CodeEditor::ReplaceWildcard()
{
	MenuBar menu;
	String ptxt;
	if(findreplace.regexp)
		ptxt = t_("Matched subpattern %d");
	else {
		menu.Add(t_("Matched spaces"), THISBACK1(InsertWildcard, "%"));
		menu.Add(t_("Matched one or more any characters"), THISBACK1(InsertWildcard, "*"));
		menu.Add(t_("Matched C++ identifier"), THISBACK1(InsertWildcard, "$"));
		menu.Add(t_("Matched number"), THISBACK1(InsertWildcard, "#"));
		menu.Add(t_("Matched any character"), THISBACK1(InsertWildcard, "?"));
		ptxt = t_("Matched wildcard %d");
	}
	menu.Add(t_("0-based replace index"), THISBACK1(InsertWildcard, "@@"));
	menu.Add(t_("1-based replace index"), THISBACK1(InsertWildcard, "@#"));
	menu.Separator();
	for(int i = 1; i <= 9; i++)
		menu.Add(Format(ptxt, i), THISBACK1(InsertWildcard, "@"+AsString(i)));
	menu.Separator();
	menu.Add(t_("To upper"), THISBACK1(InsertWildcard, "+"));
	menu.Add(t_("To lower"), THISBACK1(InsertWildcard, "-"));
	menu.Add(t_("InitCaps"), THISBACK1(InsertWildcard, "!"));
	menu.Separator();
	menu.Add(t_("Tab"), THISBACK1(InsertWildcard, "\\t"));
	menu.Add(t_("Line feed"), THISBACK1(InsertWildcard, "\\n"));
	int l, h;
	findreplace.replace.GetSelection(l, h);
	iwc.Clear();
	menu.Execute(&findreplace, findreplace.replace.GetPushScreenRect().TopRight());
	if(iwc.GetCount()) {
		if(!findreplace.regexp)
			findreplace.wildcards = true;
		findreplace.replace.SetFocus();
		findreplace.replace.SetSelection(l, h);
		findreplace.replace.RemoveSelection();
		findreplace.replace.Insert(iwc);
	}
}

void CodeEditor::CloseFindReplace()
{
	if(findreplace.IsOpen()) {
		FindReplaceAddHistory();
		RemoveFrame(findreplace);
	}
}

void CodeEditor::EscapeFindReplace()
{
	CloseFindReplace();
	if(ff_start_pos >= 0 && ff_start_pos < GetLength64() && findreplace.IsIncremental() && do_ff_restore_pos) {
		SetCursor(ff_start_pos);
		ff_start_pos = -1;
	}
}

void CodeEditor::IncrementalFind()
{
	NoFindError();
	findreplace.Sync();
	if(!findreplace.IsIncremental() || findreplace.GetTopCtrl() == &findreplace) // || we are block replace
		return;
	bool b = FindFrom(ff_start_pos >= 0 && ff_start_pos < GetLength64()
	                  && findreplace.incremental_from_cursor ? ff_start_pos : 0, false, false);
	findreplace.amend.Enable(b);
	if(!b)
		NotFound();
}

void CodeEditor::DoFind()
{
	Find(false, false);
}

void CodeEditor::DoFindBack()
{
	Find(true, false);
}

void CodeEditor::SerializeFind(Stream& s)
{
	int version = 2;
	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;
	if(version >= 1)
		s % findreplace.incremental;
	if(version >= 2)
		s % findreplace.regexp;
	findreplace.replace.SerializeList(s);
}

String ReadList(WithDropChoice<EditString>& e)
{
	StringStream ss;
	e.SerializeList(ss);
	return ss;
}

void WriteList(WithDropChoice<EditString>& e, const String& data)
{
	StringStream ss(data);
	e.SerializeList(ss);
}

CodeEditor::FindReplaceData CodeEditor::GetFindReplaceData()
{
	FindReplaceData r;
	r.find = ~findreplace.find;
	r.replace = ~findreplace.replace;
	r.wholeword = ~findreplace.wholeword;
	r.ignorecase = ~findreplace.ignorecase;
	r.wildcards = ~findreplace.wildcards;
	r.samecase = ~findreplace.samecase;
	r.regexp = ~findreplace.regexp;
	r.find_list = ReadList(findreplace.find);
	r.replace_list = ReadList(findreplace.replace);
	return r;
}

void CodeEditor::SetFindReplaceData(const FindReplaceData& r)
{
	findreplace.find <<= r.find;
	findreplace.replace <<= r.replace;
	findreplace.wholeword <<= r.wholeword;
	findreplace.ignorecase <<= r.ignorecase;
	findreplace.wildcards <<= r.wildcards;
	findreplace.samecase <<= r.samecase;
	findreplace.regexp <<= r.regexp;
	findreplace.mode <<= 0;
	WriteList(findreplace.find, r.find_list);
	WriteList(findreplace.replace, r.replace_list);
}

void CodeEditor::FindPrevNext(bool prev)
{
	StartSearchProgress(-1, -1);
	if(!findreplace.IsOpen()) {
		WString find_text;
		if(IsSelection()) {
			int l, h;
			GetSelection32(l, h);
			if(h - l < 100) {
				find_text = GetSelectionW();
				if(find_text.Find('\n') >= 0)
					find_text.Clear();
			}
		}
		if(find_text.GetCount())
			findreplace.find <<= find_text;
		OpenNormalFindReplace0(false);
	}
	if(Find(prev, false))
		NoFindError();
	else
		NotFound();
	EndSearchProgress();
}

void CodeEditor::FindNext()
{
	FindPrevNext(false);
}

void CodeEditor::FindPrev()
{
	FindPrevNext(true);
}

void CodeEditor::StartSearchProgress(int64, int64)
{
	search_canceled = false;
	search_progress.Create();
	search_progress->SetText("Scanning the file");
	search_time0 = msecs();
}

bool CodeEditor::SearchProgress(int line)
{
	if(search_progress && !search_canceled && msecs(search_time0) > 20) {
		search_time0 = msecs();
		search_progress->Create();
		search_canceled = IsView() ? search_progress->SetCanceled(int(GetPos64(line) >> 8), int(GetViewSize() >> 8))
		                           : search_progress->SetCanceled(line, GetLineCount());
	}
	return !search_canceled;
}

bool CodeEditor::SearchCanceled()
{
	return search_canceled;
}

void CodeEditor::EndSearchProgress()
{
	search_progress.Clear();
	search_canceled = false;
}

}
