#include "ide.h"

String ViewCache()
{
	return ConfigFile("view_maps");
}

String ViewFileHash(const String& path)
{
	Sha1Stream s;
	FindFile ff(path);
	if(ff) {
		Sha1Stream sha;
		sha << path << ',' << Time(ff.GetLastWriteTime()) << ',' << ff.GetLength();
		return AppendFileName(ViewCache(), sha.FinishString());
	}
	return Null;
}

void ViewFile(LineEdit& edit, Stream& view_file, const String& path, byte charset)
{
	edit.View(view_file, charset);
	String f = ViewFileHash(path);
	if(f.GetCount())
		LoadFromFile([&](Stream& s) { edit.SerializeViewMap(s); }, f);
}

void CacheViewFile(LineEdit& edit, const String& path)
{
	if(edit.IsView()) {
		ReduceCacheFolder(ViewCache(), 20000000);
		String f = ViewFileHash(path);
		RealizePath(f);
		if(f.GetCount())
			StoreToFile([&](Stream& s) { edit.SerializeViewMap(s); }, f);
	}
}

void Ide::SetupEditor(int f, String hl, String path)
{
	if(IsNull(hl)) {
		hl = EditorSyntax::GetSyntaxForFilename(path);
		if(IsNull(hl))
			hl = EditorSyntax::GetSyntaxForFilename(ToLower(path));
		if(IsNull(hl) && IsNull(GetFileExt(path))) {
			FileIn in(path);
			String h = in.Get(4096);
			try {
				CParser p(h);
				while(!p.IsEof()) {
					if(p.Char('#')) {
						if(p.Id("define") || p.Id("ipathdef") || p.Id("ifdef") || p.Id("include") || p.Id("pragma")) {
							hl = "cpp";
							break;
						}
					}
					p.SkipTerm();
				}
			}
			catch(CParser::Error) {}
		}
	}
	switch(f) {
	case 1:  editor.SetFont(font1); break;
	case 2:  editor.SetFont(font2); break;
	case 3:  editor.SetFont(consolefont); break;
	default: editor.SetFont(editorsplit.GetZoom() < 0 && editorsplit.IsHorz() ? veditorfont :
	                        hl == "t" ? tfont : editorfont); break;
	}
	editor.Highlight(hl);

	editor.WarnWhiteSpace(warnwhitespace &&
	                      findarg(hl, "cpp", "java", "js", "cs", "json", "css", "lay", "sch", "t", "usc", "dbg") >= 0);

	editor.WordwrapComments(wordwrap_comments);
	editor.WordWrap(wordwrap);
	editor.Blk0Header(blk0_header);
}

void Ide::SetupEditor()
{
	if(!IsActiveFile())
		return;
	Package::File& f = ActiveFile();
	String p = GetActiveFileName();
	if(p != HELPNAME)
		p = GetActiveFilePath();
	SetupEditor(f.font, f.highlight, p);
	editor.SyncNavigatorPlacement();
}

void Ide::FileCursor()
{
	WorkspaceWork::FileCursor();
	if(IsActiveFile() && !filelist[filelist.GetCursor()].isdir) {
		Package::File& f = ActiveFile();
		editor.SetEditable(!f.readonly);
		editor.TabSize(f.tabsize > 0 ? f.tabsize : actual.tabsize > 0 ? actual.tabsize : editortabsize);
		SetupEditor();
		String headername;
		if(insert_include)
			for(int i = insert_include == 1 ? 0 : filelist.GetCursor();
			    i >= 0 && i < filelist.GetCount();
			    i += insert_include == 1 ? 1 : -1) {
					String nm = FileName(i);
					if(!IsSeparator(i) && GetFileExt(nm) == ".h") {
						headername = nm;
						break;
					}
			}
		String p = GetActiveFileName();
		if(p != HELPNAME)
			p = GetActiveFilePath();
		EditFile0(p, f.charset ? f.charset : actual.charset ? actual.charset : default_charset,
		          Nvl(f.spellcheck_comments, actual.spellcheck_comments, spellcheck_comments),
		          headername);
	}
}

bool Ide::HasFileData(const String& file)
{
	return filedata.Find(NormalizePath(file)) >= 0;
}

Ide::FileData& Ide::Filedata(const String& file) {
	return filedata.GetAdd(NormalizePath(file));
}

void DlCharsetD(DropList& d)
{
	d.Add(0, "Default");
	DlCharset(d);
}

void Ide::ChangeFileCharset(const String& name, Package& p, byte charset)
{
	if(IsNull(name))
		return;
	bool sv = false;
	for(int i = 0; i < p.file.GetCount(); i++)
		if(SourcePath(name, p.file[i]) == editfile && p.file[i].charset != charset) {
			p.file[i].charset = charset;
			sv = true;
		}
	if(sv)
		p.Save(PackageFileA(name));
}

void Ide::FileProperties()
{
	if(!IsActiveFile()) return;
	WithFileFormatLayout<TopWindow> d;
	CtrlLayoutOKCancel(d, "File properties");
	Package::File& f = ActiveFile();
	DlCharsetD(d.charset);
	d.font.Add(0, "Normal");
	d.font.Add(1, "Small");
	d.font.Add(2, "Special");
	d.font.Add(3, "Console");
	d.highlight.Add(Null, "Default");
	for(int i = 0; i < EditorSyntax::GetSyntaxCount(); i++) {
		String desc = EditorSyntax::GetSyntaxDescription(i);
		if(desc.GetCount())
			d.highlight.Add(EditorSyntax::GetSyntax(i), desc);
	}
	d.highlight.Add("none", "None");
	d.tabsize <<= f.tabsize > 0 ? f.tabsize : Null;
	d.tabsize <<= d.Breaker(111);
	d.tabsize.MinMax(1, 100);
	d.charset <<= (int)f.charset;
	d.font <<= f.font;
	d.font <<= d.Breaker(111);
	d.highlight <<= f.highlight;
	d.highlight <<= d.Breaker(111);
	d.line_endings.Add(CRLF, "CRLF");
	d.line_endings.Add(LF, "LF");
	d.line_endings <<= findarg(Nvl(editfile_line_endings, line_endings), LF, DETECT_LF) >= 0 ? LF : CRLF;
	d.line_endings.Enable(findarg(line_endings, DETECT_CRLF, DETECT_LF) >= 0);
	d.spellcheck_comments.Add(Null, "Default");
	DlSpellerLangs(d.spellcheck_comments);
	d.spellcheck_comments <<= f.spellcheck_comments;
	for(;;) {
		switch(d.Run()) {
		case IDCANCEL:
			FlushFile();
			FileCursor();
			return;
		case IDOK:
			int c = filelist.GetCursor();
			FlushFile();
			f.charset = (byte)(int)~d.charset;
			f.tabsize = Nvl((int)~d.tabsize);
			f.font = Nvl((int)~d.font);
			f.highlight = ~d.highlight;
			f.spellcheck_comments = ~d.spellcheck_comments;
			SavePackage();
			PackageCursor();
			filelist.SetCursor(c);
			editor.ClearUndo();
			if(editfile_line_endings != ~d.line_endings) {
				editfile_line_endings = ~d.line_endings;
				SaveFile(true);
			}
			MakeTitle();
			return;
		}
		if(!IsNull(editfile)) {
			int ts = Nvl((int)~d.tabsize);
			editor.TabSize(ts ? ts : editortabsize);
			SetupEditor(Nvl((int)~d.font), ~d.highlight, editfile);
		}
	}
}

void Ide::ChangeCharset()
{
	if(!IsActiveFile()) return;
	Package::File& f = ActiveFile();
	SaveFile();
	WithCharsetLayout<TopWindow> dlg;
	CtrlLayoutOKCancel(dlg, "Save file with enconding");
	DlCharsetD(dlg.charset);
	dlg.charset <<= editor.GetCharset();
	dlg.ActiveFocus(dlg.charset);
	byte cs;
	for(;;) {
		if(dlg.Run() != IDOK)
			return;
		cs = (byte)(int)~dlg.charset;
		int q = editor.GetInvalidCharPos(cs);
		if(q >= 0) {
			if(PromptYesNo("File contains characters that cannot be saved using selected encoding.&"
			               "Save anyway? (If you choose Yes, they will be replaced with question marks.)"))
								break;
			editor.SetCursor(q);
			return;
		}
		else
			break;
	}
	f.charset = cs;
	SavePackage();
	editor.SetCharset(f.charset);
	SaveFile(true);
	FlushFile();
	FileCursor();
	editor.ClearUndo();
}

void Ide::PosSync()
{
	if(designer || editfile.IsEmpty())
		return;
	for(int i = 0; i < 2; i++)
		if(PathIsEqual(posfile[i], editfile))
			editor.SetPtr(posline[i], posimg[i], i);
		else
			editor.SetPtr(-1, Image(), i);
	editor.SyncTip();
}

bool Ide::IsProjectFile(const String& f) const
{
	const Workspace& wspc = IdeWorkspace();
	for(int i = 0; i < wspc.GetCount(); i++) {
		const Package& pk = wspc.GetPackage(i);
		String n = wspc[i];
		for(int i = 0; i < pk.file.GetCount(); i++) {
			String fn = pk.file[i];
			String path = SourcePath(n, fn);
			if(f == path)
				return true;
		}
	}
	return false;
}

String ConvertTLine(const String& line, int flag)
{
	String r;
	const char *s = line;
	
	while(*s) {
		if(*s == '\"') {
			CParser p(s);
			p.NoSkipSpaces();
			try {
				r.Cat(AsCString(p.ReadOneString(), INT_MAX, NULL, flag));
			}
			catch(CParser::Error) {
				return line;
			}
			s = p.GetPtr();
		}
		else
			r.Cat(*s++);
	}
	return r;
}

void Ide::SaveFile(bool always)
{
	issaving++;
	SaveFile0(always);
	issaving--;
}

int64 Ide::EditorHash()
{
	xxHash64Stream ss;
	for(int i = 0; i < editor.GetLineCount(); i++) {
		ss.Put(editor.GetUtf8Line(i));
		ss.Put("\n");
	}
	return ss.Finish();
}

void Ide::SaveEditorFile(Stream& out)
{
	if(GetFileExt(editfile) == ".t")
		for(int i = 0; i < editor.GetLineCount(); i++) {
			if(i) out.PutCrLf();
			out.Put(ConvertTLine(editor.GetUtf8Line(i), ASCSTRING_OCTALHI));
		}
	else {
		int le = line_endings;
		if(le == DETECT_CRLF)
			le = Nvl(editfile_line_endings, CRLF);
		if(le == DETECT_LF)
			le = Nvl(editfile_line_endings, LF);
		editor.Save(out, editor.GetCharset(), le == LF ? TextCtrl::LE_LF : TextCtrl::LE_CRLF);
	}
}

void Ide::SaveFile0(bool always)
{
	if(designer) {
		String fn = designer->GetFileName();
		Time tm = FileGetTime(fn);
		designer->Save();
		if(tm != FileGetTime(fn))
			TouchFile(fn);
		TriggerIndexer();
		if(IsNull(tm))
			TriggerIdeBackgroundThread(2000);
		return;
	}

	if(editfile.IsEmpty())
		return;

	FileData& fd = Filedata(editfile);
	fd.lineinfo = editor.GetLineInfo();
	fd.lineinforem = editor.GetLineInfoRem();
	fd.editpos = editor.GetEditPos();
	fd.columnline = editor.GetColumnLine(fd.editpos.cursor);
	fd.filetime = edittime;
	if(editor.IsView())
		return;
	if(!editor.IsDirty() && !always)
		return;
	TouchFile(editfile);
	bool file_exists = FileExists(editfile);
	if(!file_exists)
		InvalidateIncludes();
	int auto_retry = 10; // file can be open by indexer
	for(;;) {
		FileOut out(editfile);
		SaveEditorFile(out);
		if(!out.IsError())
			break;
		if(auto_retry-- > 0) { // try for 2 seconds before asking
			Sleep(300);
			continue;
		}
		int art = Prompt(Ctrl::GetAppName(), CtrlImg::exclamation(),
			"Unable to save current file.&"
			"Retry save, ignore it or save file to another location?",
			"Save as...", "Retry", "Ignore");
		if(art < 0)
			break;
		if(IsDeactivationSave())
			return;
		if(art && AnySourceFs().ExecuteSaveAs()) {
			editfile = AnySourceFs();
			MakeTitle();
			PosSync();
			break;
		}
	}

	FindFile ff(editfile);
	fd.filetime = edittime = ff.GetLastWriteTime();
	editfile_length = ff.GetLength();
	editfile_isreadonly = ff.IsReadOnly();

	if(!file_exists)
		TriggerIdeBackgroundThread(2000);

	TriggerIndexer();
	editor.ClearDirty();

	if(ToLower(GetFileExt(editfile)) == ".usc")
		SyncUsc();

	MakeTitle();
}

void Ide::FlushFile() {
	editor.CloseAssist();
	SaveFile();
	CacheViewFile(editor, editfile);
	if(designer) {
		designer->SaveEditPos();
		designer->DesignerCtrl().SetFrame(NullFrame());
		if(dynamic_cast<TopicEditor *>(&designer->DesignerCtrl()))
			RefreshBrowser();
	}
	else
	if(!editfile.IsEmpty() && !editor.IsView()) {
		FileData& fd = Filedata(editfile);
		fd.undodata = editor.PickUndoData();
		fd.filehash = EditorHash();
		fd.content = editor.GetLength() < 1000000 ? FastCompress(editor.Get()) : String();
	}
	CancelCurrentFile();
	CancelAutoComplete();
	editfile.Clear();
	editfile_repo = NOT_REPO_DIR;
	editfile_isfolder = false;
	repo_dirs = RepoDirs(true).GetCount(); // Perhaps not the best place, but should be ok
	editor.Clear();
	editor.Disable();
	editor.SetEditable();
	editorsplit.Ctrl::Remove();
	designer.Clear();
	view_file.Close();
	SetBar();
}

int CharFilterMacro(int c)
{
	return iscid(c) ? c : '_';
}

void Ide::FileRename(const String& nm)
{
	tabs.RenameFile(editfile, nm);
	InvalidateIncludes();
}

bool Ide::FileRemove()
{
	InvalidateIncludes();
	int q = FindIndex(tablru, editfile);
	if(q >= 0)
		tablru.Remove(q);
	q = tabs.GetCursor();
	if(q >= 0) {
		tabs.CloseForce(q, false);
		if(filelist.GetCount())
			return true;
		if(tabs.GetCount())
			TabFile();
		else {
			tabs.Refresh();
			FlushFile();
		}
		return false;
	}
	return true;
}

void Ide::LoadFileSilent(const String& path)
{ // used with find in files replaces and replace found items
	byte charset = 0;

	const Workspace& wspc = IdeWorkspace();
	for(int i = 0; i < wspc.GetCount() && !charset; i++) {
		const Package& pk = wspc.GetPackage(i);
		String n = wspc[i];
		for(int i = 0; i < pk.file.GetCount() && !charset; i++) {
			if(PathIsEqual(SourcePath(n, pk.file[i]), path)) {
				charset = pk.file[i].charset;
				if(!charset)
					charset = pk.charset;
			}
		}
	}

	EditFile0(path, charset ? charset : default_charset, false, Null, false);
}

void Ide::EditFile0(const String& path, byte charset, int spellcheck_comments, const String& headername, bool reloading)
{
	animate_current_file = animate_current_file_dir = animate_autocomplete = animate_autocomplete_dir = 0;

	AKEditor();
	editor.CheckEdited(false);
	editor.CloseAssist();
	if(path.IsEmpty()) return;
	FlushFile();

	editfile = path;
	editor.SetCharset(charset);
	editor.SpellcheckComments(spellcheck_comments);
	AddLru();

	editfile_isfolder = IsFolder(editfile) || IsHelpName(editfile);
	repo_dirs = RepoDirs(true).GetCount(); // Perhaps not the best place, but should be ok

	FindFile ff(editfile);
	if(ff) {
		edittime = ff.GetLastWriteTime();
		editfile_length = ff.GetLength();
		editfile_isreadonly = ff.IsReadOnly();
	}
	else {
		edittime = GetSysTime().AsFileTime();
		editfile_isreadonly = false;
		editfile_length = 0;
	}

	bool candesigner = !(debugger && !editfile_isfolder && (PathIsEqual(path, posfile[0]) || PathIsEqual(path, posfile[0])))
	   && editastext.Find(path) < 0 && editashex.Find(path) < 0 && !IsNestReadOnly(editfile) && !replace_in_files;
	
	if(candesigner) {
		for(int i = 0; i < GetIdeModuleCount() && !designer; i++)
			designer = GetIdeModule(i).CreateDesigner(this, path, charset);
	}

	if(!designer && editastext.Find(path) < 0 &&
	   (findarg(GetFileExt(path), ".log") < 0 &&
	    findarg(charset, CHARSET_UTF8_BOM, CHARSET_UTF16_LE, CHARSET_UTF16_BE,
	            CHARSET_UTF16_LE_BOM, CHARSET_UTF16_BE_BOM) < 0 &&
	    FileIsBinary(path) || editashex.Find(path) >= 0))
		designer.Create<FileHexView>().Open(path);
	
	if(designer) {
		editpane.Add(designer->DesignerCtrl().SizePos());
		designer->DesignerCtrl().SetFocus();
		designer->RestoreEditPos();
		if(filetabs)
			tabs.SetAddFile(editfile);
		MakeTitle();
		SetBar();
		editor.SyncNavigatorShow();
		return;
	}

	if(!replace_in_files) {
		tabs.SetAddFile(editfile);
		tabs.SetSplitColor(editfile2, SYellow());
	}
	editor.Enable();
	editpane.Add(editorsplit);
	editor.HiliteScope(hilite_scope);
	editor.OverWriteMode(false);
	if(!replace_in_files)
		ActiveFocus(editor);
	FileData& fd = Filedata(editfile);
	bool tfile = GetFileExt(editfile) == ".t";
	if(ff) {
		view_file.SetBufferSize(256*1024);
		view_file.Open(editfile);
		if(view_file) {
			if(tfile && editastext.Find(editfile) < 0) {
				String f;
				String ln;
				for(;;) {
					int c = view_file.Get();
					if(c < 0) {
						f.Cat(ConvertTLine(ln, 0));
						break;
					}
					if(c != '\r') {
						ln.Cat(c);
						if(c == '\n') {
							f.Cat(ConvertTLine(ln, 0));
							ln.Clear();
						}
					}
				}
				editor.Set(f);
				editor.SetCharset(CHARSET_UTF8);
			}
			else {
				String s = view_file.Get(3);
				if(s.GetCount() >= 2) {
					if((byte)s[0] == 0xff && (byte)s[1] == 0xfe)
						charset = CHARSET_UTF16_LE_BOM;
					if((byte)s[0] == 0xfe && (byte)s[1] == 0xff)
						charset = CHARSET_UTF16_BE_BOM;
				}
				if(s.GetCount() >= 3 && (byte)s[0] == 0xef && (byte)s[1] == 0xbb && (byte)s[2] == 0xbf)
					charset = CHARSET_UTF8_BOM;
				view_file.Seek(0);
				int le = Null;
			#ifdef CPU_64
				const int64 max_size = (int64)2047*1024*1024;
			#else
				const int64 max_size = 768*1024*1024;
			#endif
				const int view_limit = 256*1024*1024;
				if(view_file.GetSize() < view_limit || editastext.Find(editfile) >= 0 && view_file.GetSize() < max_size) {
					le = editor.Load(view_file, charset);
					view_file.Close();
				}
				else
					ViewFile(editor, view_file, editfile, charset);
				
				editfile_line_endings = le == TextCtrl::LE_CRLF ? CRLF : le == TextCtrl::LE_LF ? LF : (int)Null;
			}
		}
		else
			Exclamation("Failed to read the file.");
		editor.SetEditPos(fd.editpos);
		if(!IsNull(fd.columnline) && fd.columnline.y >= 0 && fd.columnline.y < editor.GetLineCount())
			editor.SetCursor(editor.GetColumnLinePos(fd.columnline));
		editor.SetEditPosSbOnly(fd.editpos);
		if(!editor.IsView()) {
			if(edittime != fd.filetime || IsNull(fd.filetime) || EditorHash() != fd.filehash) { // is undo valid (file is the same as when undo was stored)?
				if(fd.content.GetCount() && fd.undodata.undo.GetCount()) { // undo is not valid, but restore content for valid undo and apply the changed file
					String new_content = editor.Get();
					editor.Set(FastDecompress(fd.content));
					editor.SetPickUndoData(pick(fd.undodata)); // valid for fd.content
					ApplyChanges(editor, new_content);
				}
			}
			else
				editor.SetPickUndoData(pick(fd.undodata));
			editor.SetLineInfo(fd.lineinfo);
			editor.SetLineInfoRem(pick(fd.lineinforem));
		}
		if(ff.IsReadOnly() || IsNestReadOnly(editfile) || editor.IsTruncated() || editor.IsView()) {
			editor.SetReadOnly();
			editor.NoShowReadOnly();
		}
		fd.ClearP();
		PosSync();
		editor.ClearDirty();
	}
	else {
		RealizePath(editfile);
		Index<String> extensions = {
			".h",
			".hpp"
		};
		String ext = GetFileExt(editfile);
		if(extensions.Find(ext) >= 0) {
			ext.Set(0, '_');
			String n = '_' + Filter(String().Cat() << GetActivePackage() << '_' << GetFileTitle(editfile) << ext <<"_",
			                        CharFilterMacro);
			String s;
			s << "#ifndef " << n << "\r\n";
			s << "#define " << n << "\r\n";
			s << "\r\n";
			s << "#endif\r\n";
			editor <<= s;
			editor.SetCursor(editor.GetPos64(2));
		}
		if(IsCSourceFile(editfile) && !IsNull(headername)) {
			editor <<= "#include \"" + headername + "\"\r\n";
			editor.SetCursor(editor.GetPos64(1));
		}
		editor.SetCharset(tfile ? CHARSET_UTF8 : charset);
	}
	static bool ShowBeginnerInfo = true;
	editor.show_beginner_info = ShowBeginnerInfo;
	ShowBeginnerInfo = false; // just a single chance!
	editor.Refresh();
	if(!replace_in_files) {
		editor.SetFocus();
		MakeTitle();
		SetBar();
		editor.SyncNavigatorShow();
		editor.CheckEdited(true);
		editor.SyncNavigator();
		editor.NewFile(reloading);
		editfile_repo = GetRepoKind(editfile);
	}
}

String Ide::IncludesMD5()
{ // keep track of includes for Assist++, so that we know when to save file and sync base
	int n = min(editor.GetLineCount(), 1000); // ignore big files
	Md5Stream md5;
	for(int i = 0; i < n; i++) {
		String l = editor.GetUtf8Line(i);
		try {
			CParser p(l);
			if(p.Char('#') && p.Id("include"))
				md5.Put(l);
		}
		catch(CParser::Error) {}
	}
	return md5.FinishString();
}

void Ide::EditAsHex()
{
	String path = editfile;
	if(editashex.Find(path) >= 0)
		return;
	editastext.RemoveKey(editfile);
	editashex.FindPut(editfile);
	byte cs = editor.GetCharset();
	int sc = editor.GetSpellcheckComments();
	FlushFile();
	EditFile0(path, cs, sc);
}

bool Ide::IsDesignerFile(const String& path)
{
	for(int i = 0; i < GetIdeModuleCount(); i++)
		if(GetIdeModule(i).AcceptsFile(path))
			return true;
	return false;
}

void Ide::DoEditAsText(const String& path)
{
	editastext.FindAdd(path);
	editashex.RemoveKey(editfile);
}

String GetLayItemId(const String& l)
{
	try {
		CParser p(l); // ITEM(Upp::ArrayCtrl, list, LeftPosZ(8, 296).TopPosZ(8, 448))
		if(p.Id("ITEM") || p.Id("UNTYPED")) {
			p.Char('(');
			if(p.IsId()) p.ReadId();
			p.Char2(':', ':');
			if(p.IsId()) p.ReadId();
			p.Char(',');
			if(p.IsId())
				return p.ReadId();
		}
	}
	catch(CParser::Error) {}
	return Null;
}

void Ide::EditAsText()
{
	String path = editfile;
	if(editastext.Find(path) >= 0)
		return;
//	if(!FileExists(path))
//		return;
	String layout, item;
	if(auto *l = dynamic_cast<LayDesigner *>(~designer)) {
		layout = l->GetCurrentLayout();
		item = l->GetCurrentItem();
	}
	DoEditAsText(path);
	byte cs = editor.GetCharset();
	int sc = editor.GetSpellcheckComments();
	FlushFile();
	EditFile0(path, cs, sc);
	if(layout.GetCount()) {
		layout = "LAYOUT(" + layout + ",";
		for(int i = 0; i < editor.GetLineCount(); i++)
			if(TrimBoth(editor.GetUtf8Line(i)).StartsWith(layout)) {
				editor.GotoBarLine(i);
				if(item.GetCount())
					for(int j = i + 1; j < editor.GetLineCount(); j++)
						if(GetLayItemId(editor.GetUtf8Line(j)) == item) {
							editor.GotoBarLine(j);
							break;
						}
				break;
			}
	}
}

bool Ide::GetLayoutItem(String& layout, String& item)
{
	if(ToLower(GetFileExt(editfile)) == ".lay") {
		int i = editor.GetLine(editor.GetCursor());
		item = GetLayItemId(editor.GetUtf8Line(i));
		for(; i >= 0; i--) {
			String l = editor.GetUtf8Line(i);
			try {
				CParser p(l);
				if(p.Id("LAYOUT")) {
					p.Char('(');
					layout = p.ReadId();
					return true;
				}
			}
			catch(CParser::Error) {}
		}
	}
	return false;
}

void Ide::EditUsingDesigner()
{
	String path = editfile;
	if(editastext.Find(editfile) < 0 && editashex.Find(editfile) < 0)
		return;
	editashex.RemoveKey(editfile);
	editastext.RemoveKey(editfile);
	byte cs = editor.GetCharset();
	int sc = editor.GetSpellcheckComments();
	String layout, item;
	GetLayoutItem(layout, item);
	FlushFile();
	EditFile0(path, cs, sc);
	if(layout.GetCount()) {
		auto *l = dynamic_cast<LayDesigner *>(~designer);
		if(l)
			l->FindLayout(layout, item);
	}
}

void Ide::AddEditFile(const String& path)
{
	actual.file.AddPick(Package::File(path));
	if(IsAux())
		SaveLoadPackageNS(false);
	else
		SaveLoadPackage(false);
	ShowFile(package.GetCount() - 1);
	filelist.SetCursor(filelist.GetCount() - 1);
}

void Ide::EditFile(const String& p)
{
	if(p.IsEmpty()) {
		FlushFile();
		return;
	}
	if(p == HELPNAME) {
		if(designer && designer->GetFileName() == p)
			return;
		package.FindSetCursor(METAPACKAGE);
		filelist.FindSetCursor(HELPNAME);
		return;
	}

	String path = NormalizePath(p);
	if(designer ? path == designer->GetFileName() : path == editfile)
		return;

	FlushFile();
	if(path.IsEmpty())
		return;
	
	for(int i = 0; i < package.GetCount(); i++) {
		String pkg = package[i].name;
		Package p;
		Fetch(p, pkg);
		for(int j = 0; j < p.file.GetCount(); j++)
			if(PathIsEqual(SourcePath(pkg, p.file[j]), path)) {
				package.FindSetCursor(pkg);
				ShowFile(j);
				filelist.FindSetCursor(p.file[j]);
				return;
			}
		if(GetFileExt(path) == ".tpp" && PathIsEqual(SourcePath(pkg, GetFileName(path)), path)) {
			filelist.KillCursor();
			package.KillCursor();
			package.SetCursor(i);
			AddEditFile(GetFileName(path));
			return;
		}
	}
	filelist.KillCursor();
	if(!IsAux() || IsMeta()) {
		package.KillCursor();
		package.SetCursor(package.GetCount() - 2); // use temp-aux
	}
	AddEditFile(path);
}

void Ide::CheckFileUpdate()
{
	if(editfile.IsEmpty() || !IsForeground() || designer || !FileExists(editfile)) return;
	FileTime tm = GetFileTime(editfile);
	if(tm == edittime) return;
	edittime = tm;
	if(editor.IsDirty() && !Prompt(Ctrl::GetAppName(), CtrlImg::exclamation(),
		"Current file was changed outside the IDE, but was also edited inside it.&"
		"Would you like to reload the file or to keep changes made in the IDE ?",
		"Reload", "Keep")) return;

	if(!editor.IsView() && !editor.IsReadOnly() && editor.GetUndoCount() &&
	   max((int64)editor.GetLength(), GetFileLength(editfile)) < 30*1024*1024) {
	    int c = editor.GetCursor();
	    ApplyChanges(editor, LoadFile(editfile));
		editor.SetCursor(c);
		editor.ClearDirty(); // as it is "loaded"
	}
	else
		ReloadFile();
}

typedef Index<dword> HashBase;

static void GetLineIndex(String file, HashBase& hash, Vector<String>& lines)
{
	const char *p = file;
	while(*p)
	{
		while(*p && *p != '\n' && (byte)*p <= ' ')
			p++;
		const char *b = p;
		while(*p && *p++ != '\n')
			;
		const char *e = p;
		while(e > b && (byte)e[-1] <= ' ')
			e--;
		String s(b, e);
		hash.Add(FoldHash(GetHashValue(s)));
		lines.Add(s);
	}
}

int LocateLine(String old_file, int old_line, String new_file)
{
	HashBase old_hash, new_hash;
	Vector<String> old_lines, new_lines;
	GetLineIndex(old_file, old_hash, old_lines);
	GetLineIndex(new_file, new_hash, new_lines);
	if(old_line <= 0)
		return 0;
	if(old_line >= old_lines.GetCount())
		return new_lines.GetCount();
	String line = old_lines[old_line];
	//int hash = old_hash[old_line]; Mirek: unused
	//int fore_count = old_lines.GetCount() - old_line - 1;
	int best_match = 0, best_value = 0;
	for(int r = 0; r < 10 && !best_value; r++)
	{
		int src = (r & 1 ? old_line + (r >> 1) + 1 : old_line - (r >> 1));
		if(src < 0 || src >= old_lines.GetCount())
			continue;
		dword hash = old_hash[src];
		for(int i = new_hash.Find(hash); i >= 0; i = new_hash.FindNext(i))
			if(new_lines[i] == old_lines[src])
			{
				int max_back = min(i, src);
				int max_fore = min(new_lines.GetCount() - i, old_lines.GetCount() - src) - 1;
				if(max_back + max_fore <= best_value)
					continue;
				int back = 1;
				while(back <= max_back && new_hash[i - back] == old_hash[src - back]
					&& new_lines[i - back] == old_lines[src - back])
					back++;
				int fore = 1;
				while(fore < max_fore && new_hash[i + fore] == old_hash[src + fore]
					&& new_lines[i + fore] == old_lines[src + fore])
					fore++;
				if(back + fore > best_value)
				{
					best_value = back + fore;
					best_match = minmax(i, 0, new_lines.GetCount());
				}
			}
	}
	return best_match;
}

void Ide::ReloadFile()
{
	if(editfile.IsEmpty())
		return;
	String fn = editfile;
	String data = ~editor;
	int ln = editor.GetCursorLine();
	editfile.Clear();
	int sc = filelist.GetSbPos();
	EditFile0(fn, editor.GetCharset(), editor.GetSpellcheckComments(), Null, true);
	filelist.SetSbPos(sc);
	int l = LocateLine(data, ln, ~editor);
	editor.SetCursor(editor.GetPos64(l));
}

void Ide::EditAnyFile() {
	FileSel& fs = AnySourceFs();
#if 0
	fs.Multi(false);
	if(!fs.ExecuteOpen()) return;
	EditFile(fs);
	FileSelected();
#endif
	if(fs.ExecuteOpen())
		for(int i = 0; i < fs.GetCount(); i++) {
			EditFile(fs[i]);
			FileSelected();
		}
}

void Ide::DragAndDrop(Point, PasteClip& d)
{
	if(AcceptFiles(d)) {
		Vector<String> f = GetFiles(d);
		for(int i = 0; i < f.GetCount(); i++)
			if(FileExists(f[i])) {
				EditFile(f[i]);
				FileSelected();
				editor.SetFocus();
			}
	}
}

void Ide::AddLru()
{
	if(editfile.IsEmpty() || tabi) return;
	LruAdd(tablru, editfile, 200);
}

static String sExFiles(const char *fn, const char **ext, int cnt)
{
	for(int i = 0; i < cnt; i++) {
		String f = ForceExt(fn, ext[i]);
		if(FileExists(f))
			return f;
	}
	return Null;
}

String Ide::GetOpposite()
{
	static const char *cpp[] = { ".c", ".cpp", ".cc", ".cxx" };
	static const char *hdr[] = { ".h", ".hpp", ".hh", ".hxx" };
	if(IsNull(editfile) || designer)
		return Null;
	String ext = GetFileExt(editfile);
	for(int i = 0; i < __countof(cpp); i++)
		if(ext == cpp[i])
			return sExFiles(editfile, hdr, __countof(hdr));
	for(int i = 0; i < __countof(hdr); i++)
		if(ext == hdr[i])
			return sExFiles(editfile, cpp, __countof(cpp));
	return Null;
}

void Ide::GoOpposite()
{
	String fn = GetOpposite();
	if(!IsNull(fn))
		EditFile(fn);
}

void Ide::PassEditor(AssistEditor& editor2)
{
	SetupEditor();
	String editfile2 = editfile;
	editor2.SetFont(editor.GetFont());
	editor2.Highlight(editor.GetHighlight());
	editor2.LoadHlStyles(editor.StoreHlStyles());
	editor2.NoShowReadOnly();
	byte charset = editor.GetCharset();
	editor2.CheckEdited(false);
	view_file2.Close();
	if(editor.IsView()) {
		view_file2.Open(editfile2);
		ViewFile(editor2, view_file2, editfile2, charset);
	}
	else
		editor2.Set(editor.Get(charset), charset);
	editor2.SetEditPosSb(editor.GetEditPos());
	editor2.CheckEdited();
	editor.SetFocus();
	editor.ScrollIntoCursor();
	editor2.SpellcheckComments(editor.GetSpellcheckComments());
}

void Ide::PassEditor()
{
	editorsplit.NoZoom();
	SyncEditorSplit();
	editfile2 = editfile;
	PassEditor(editor2);
}

void Ide::ClearEditedFile()
{
	editor.ClearEdited();
}

void Ide::ClearEditedAll()
{
	ClearEditedFile();
	for(int i = 0; i < filedata.GetCount(); i++) {
		LineInfo li = editor.GetLineInfo();
		LineInfoRem lir = editor.GetLineInfoRem();
		FileData& fd = Filedata(filedata.GetKey(i));
		editor.SetLineInfo(fd.lineinfo);
		editor.SetLineInfoRem(pick(fd.lineinforem));
		ClearEditedFile();
		fd.lineinfo = editor.GetLineInfo();
		fd.lineinforem = editor.GetLineInfoRem();
		editor.SetLineInfo(li);
	}
}

void Ide::SplitEditor(bool horz)
{
	if(editorsplit.GetZoom() < 0)
		CloseSplit();

	if(horz)
		editorsplit.Horz(editor2, editor_p);
	else
		editorsplit.Vert(editor2, editor_p);
	
	tabs.SetSplitColor(editfile, SYellow());
	PassEditor();
}

void Ide::SwapEditors()
{
	String f = editfile2;
	CodeEditor::EditPos p = editor2.GetEditPos();
	if(editorsplit.GetFirstChild() == &editor_p)
		if(editorsplit.IsVert())
			editorsplit.Vert(editor2, editor_p);
		else
			editorsplit.Horz(editor2, editor_p);
	else
		if(editorsplit.IsVert())
			editorsplit.Vert(editor_p, editor2);
		else
			editorsplit.Horz(editor_p, editor2);
	PassEditor();
	EditFile(f);
	editor.SetEditPos(p);
}

void Ide::CloseSplit()
{
	editorsplit.Vert(editor_p, editor2);
	editorsplit.Zoom(0);
	view_file2.Close();
	editfile2.Clear();
	tabs.ClearSplitColor();
	SyncEditorSplit();
	editor.SetFocus();
	SetupEditor();
}

void Ide::KeySplit(bool horz)
{
	if(editorsplit.GetZoom() >= 0)
		SplitEditor(horz);
	else
		CloseSplit();
}

void Ide::SyncEditorSplit()
{
	editor.topsbbutton.ScrollStyle().SetMonoImage(IdeImg::split()).Tip("Split (Ctrl+[-])");
	editor.topsbbutton <<= THISBACK1(SplitEditor, false);
	editor.topsbbutton1.ScrollStyle().SetMonoImage(IdeImg::vsplit()).Tip("Split (Ctrl+[\\])");
	editor.topsbbutton1 <<= THISBACK1(SplitEditor, true);
	editor2.topsbbutton.ScrollStyle().SetMonoImage(IdeImg::split()).Tip("Split (Ctrl+[-])");
	editor2.topsbbutton <<= THISBACK1(SplitEditor, false);
	editor2.topsbbutton1.ScrollStyle().SetMonoImage(IdeImg::vsplit()).Tip("Split (Ctrl+[\\])");
	editor2.topsbbutton1 <<= THISBACK1(SplitEditor, true);
	if(editorsplit.GetZoom() >= 0)
		return;
	if(editorsplit.IsVert()) {
		editor.topsbbutton.ScrollStyle().SetMonoImage(IdeImg::closesplit()).Tip("Close (Ctrl+[-])");
		editor.topsbbutton <<= THISBACK(CloseSplit);
		editor2.topsbbutton.ScrollStyle().SetMonoImage(IdeImg::closesplit()).Tip("Close (Ctrl+[-])");
		editor2.topsbbutton <<= THISBACK(CloseSplit);
	}
	else {
		editor.topsbbutton1.ScrollStyle().SetMonoImage(IdeImg::closesplit()).Tip("Close (Ctrl+[\\])");
		editor.topsbbutton1 <<= THISBACK(CloseSplit);
		editor2.topsbbutton1.ScrollStyle().SetMonoImage(IdeImg::closesplit()).Tip("Close (Ctrl+[\\])");
		editor2.topsbbutton1 <<= THISBACK(CloseSplit);
	}
}

bool Ide::HotKey(dword key)
{
	if(designer && designer->DesignerCtrl().HotKey(key))
		return true;
	if(designer && dynamic_cast<FileHexView *>(~designer) && Match(IdeKeys::AK_EDITASHEX, key)) {
		EditUsingDesigner();
		return true;
	}
	return TopWindow::HotKey(key);
}

String Ide::IdeGetFileName()
{
	return editfile;
}

String Ide::IdeGetNestFolder()
{
	Vector<String> w = GetUppDirs();
	for(int i = 0; i < w.GetCount(); i++)
		if(editfile.StartsWith(w[i]))
			return w[i];
	return Null;
}

void Ide::Duplicate()
{
	int l, h;
	if(editor.GetSelection(l, h)) {
		editor.NextUndo();
		editor.SetSelection(h, editor.Insert(h, editor.GetSelection()) + h);
	}
	else
		editor.DuplicateLine();
}
