mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-16 06:05:58 -06:00
442 lines
No EOL
12 KiB
C++
442 lines
No EOL
12 KiB
C++
#include "TextDiffCtrl.h"
|
|
|
|
namespace Upp {
|
|
|
|
DirDiffDlg::DirDiffDlg()
|
|
{
|
|
int div = HorzLayoutZoom(4);
|
|
int cy = dir1.GetStdSize().cy;
|
|
|
|
int bcx = GetTextSize(t_("Compare"), StdFont()).cx * 12 / 10 + 2 * div;
|
|
|
|
hidden.SetLabel(t_("Hidden"));
|
|
split_lines.SetLabel(t_("Split long lines"));
|
|
ignore_indentation.SetLabel(t_("Ignore indentation"));
|
|
|
|
added.SetColor(Green()).SetLabel(t_("New"));
|
|
modified.SetLabel(t_("Modified"));
|
|
removed.SetColor(Red()).SetLabel(t_("Removed"));
|
|
|
|
recent <<= Null;
|
|
recent.Add(Null, "All");
|
|
recent.Add(1, "1 Day");
|
|
recent.Add(3, "3 Days");
|
|
recent.Add(7, "7 Days");
|
|
recent.Add(14, "14 Days");
|
|
recent.Add(28, "28 Days");
|
|
recent.Add(60, "3 Months");
|
|
recent.Add(180, "6 Months");
|
|
recent.Add(365, "1 Year");
|
|
|
|
compare.SetLabel(t_("Compare"));
|
|
int bcy = max(cy, compare.GetStdSize().cy);
|
|
|
|
files_pane.Add(dir1.TopPos(0, cy).HSizePos());
|
|
files_pane.Add(dir2.TopPos(cy + div, cy).HSizePos());
|
|
files_pane.Add(hidden.TopPos(2 * cy + 2 * div, bcy).LeftPos(0, bcx));
|
|
files_pane.Add(split_lines.TopPos(2 * cy + 2 * div, bcy).LeftPosZ(55, 100));
|
|
files_pane.Add(ignore_indentation.TopPos(2 * cy + 2 * div, bcy).LeftPosZ(152, 120));
|
|
|
|
files_pane.Add(added.TopPos(3 * cy + 3 * div, bcy).LeftPosZ(2, 60));
|
|
files_pane.Add(modified.TopPos(3 * cy + 3 * div, bcy).LeftPosZ(52, 70));
|
|
files_pane.Add(removed.TopPos(3 * cy + 3 * div, bcy).LeftPosZ(128, 80));
|
|
files_pane.Add(recent.TopPos(3 * cy + 3 * div, bcy).RightPos(0, bcx + Zx(8)));
|
|
files_pane.Add(extension.TopPos(3 * cy + 3 * div, bcy).RightPos(bcx + Zx(8) + DPI(8), bcx));
|
|
|
|
removed = 1;
|
|
added = 1;
|
|
modified = 1;
|
|
find.NullText(t_("Find (Ctrl+F)"));
|
|
clearFind.SetLabel("X");
|
|
clearFind.RightPosZ(1, 16).VSizePosZ(1, 1);
|
|
find.AddChild(&clearFind);
|
|
|
|
files_pane.Add(compare.TopPos(2 * cy + 2 * div, bcy).RightPos(0, bcx));
|
|
files_pane.Add(files.VSizePos(3 * cy + bcy + 4 * div, Zy(24)).HSizePos());
|
|
files_pane.Add(find.BottomPosZ(4, 19).HSizePosZ());
|
|
|
|
Add(files_diff.SizePos());
|
|
files_diff.Set(files_pane, diff);
|
|
files_diff.SetPos(2000);
|
|
files_diff.SetMinPixels(0, Zx(256));
|
|
|
|
Sizeable().Zoomable();
|
|
|
|
seldir1.Attach(dir1);
|
|
seldir2.Attach(dir2);
|
|
|
|
seldir1.Title("First directory to compare");
|
|
seldir2.Title("Second directory to compare");
|
|
|
|
compare <<= THISBACK(Compare);
|
|
dir1 <<= THISBACK(ClearFiles);
|
|
dir2 <<= THISBACK(ClearFiles);
|
|
|
|
modified << [=] { ShowResult(); };
|
|
removed << [=] { ShowResult(); };
|
|
added << [=] { ShowResult(); };
|
|
find << [=] { ShowResult(); };
|
|
extension << [=] { ShowResult(); };
|
|
recent << [=] { ShowResult(); };
|
|
clearFind << [=] { find.Clear(); ShowResult();};
|
|
|
|
files.WhenSel = THISBACK(File);
|
|
|
|
diff.InsertFrameLeft(left);
|
|
diff.InsertFrameRight(right);
|
|
|
|
left.Height(EditField::GetStdHeight());
|
|
lfile.SetReadOnly();
|
|
left.Add(lfile.VSizePos().HSizePosZ(0, 222));
|
|
left.Add(copyright.VSizePos().RightPosZ(0, 70));
|
|
left.Add(removeleft.VSizePos().RightPosZ(74, 70));
|
|
left.Add(revertleft.VSizePos().RightPosZ(148, 70));
|
|
|
|
right.Height(EditField::GetStdHeight());
|
|
rfile.SetReadOnly();
|
|
right.Add(rfile.VSizePos().HSizePosZ(222, 0));
|
|
right.Add(copyleft.VSizePos().LeftPosZ(0, 70));
|
|
right.Add(removeright.VSizePos().LeftPosZ(74, 70));
|
|
right.Add(revertright.VSizePos().LeftPosZ(148, 70));
|
|
|
|
auto SetupCopy = [=](Button& copy, bool left) {
|
|
copy.SetImage(left ? DiffImg::CopyLeft() : DiffImg::CopyRight());
|
|
copy.SetLabel("Copy");
|
|
copy.Tip("F5");
|
|
copy.Disable();
|
|
copy << [=] { Copy(left); };
|
|
};
|
|
|
|
SetupCopy(copyleft, true);
|
|
SetupCopy(copyright, false);
|
|
|
|
auto SetupRevert = [=](Button& revert, EditString *dir) {
|
|
revert.Disable();
|
|
revert.SetLabel("Revert");
|
|
revert.SetImage(CtrlImg::undo());
|
|
revert << [=] {
|
|
String path = AppendFileName(~*dir, files.GetCurrentName());
|
|
int q = backup.Find(path);
|
|
if(q >= 0 && PromptYesNo("Revert changes?")) {
|
|
SaveFile(path, ZDecompress(backup[q]));
|
|
backup.Remove(q);
|
|
Refresh();
|
|
}
|
|
};
|
|
};
|
|
|
|
SetupRevert(revertleft, &dir1);
|
|
SetupRevert(revertright, &dir2);
|
|
|
|
auto SetupRemove = [=](Button& remove, TextCompareCtrl *text, EditString *dir)
|
|
{
|
|
remove.SetLabel("Remove");
|
|
remove.Tip("F8");
|
|
remove.SetImage(CtrlImg::remove());
|
|
remove.Disable();
|
|
|
|
remove << [=] {
|
|
String path = AppendFileName(~*dir, files.GetCurrentName());
|
|
Backup(path);
|
|
SaveFile(path, text->RemoveSelected(HasCrs(path)));
|
|
Refresh();
|
|
};
|
|
|
|
text->WhenSel << [=, &remove] {
|
|
remove.Enable(text->IsSelection());
|
|
};
|
|
};
|
|
|
|
SetupRemove(removeleft, &diff.left, &dir1);
|
|
SetupRemove(removeright, &diff.right, &dir2);
|
|
|
|
split_lines << [=] { File(); };
|
|
ignore_indentation << [=] { File(); };
|
|
|
|
Icon(DiffImg::DirDiff());
|
|
|
|
WhenIcon = [](const char *path) -> Image { return NativePathIcon(path); };
|
|
|
|
Title("Compare directories");
|
|
};
|
|
|
|
void DirDiffDlg::GatherFilesDeep(VectorMap<String, Time>& files, const String& base, const String& path)
|
|
{
|
|
FindFile ff(AppendFileName(AppendFileName(base, path), "*.*"));
|
|
while(ff) {
|
|
String p = (path.GetCount() ? path + '/' : String()) + ff.GetName();
|
|
if(hidden || !ff.IsHidden()) {
|
|
if(ff.IsFile()) {
|
|
Time ftm = ff.GetLastWriteTime();
|
|
Time& tm = files.GetAdd(p, ftm);
|
|
tm = max(tm, ftm);
|
|
}
|
|
else
|
|
if(ff.IsFolder())
|
|
GatherFilesDeep(files, base, p);
|
|
}
|
|
ff.Next();
|
|
}
|
|
}
|
|
|
|
bool DirDiffDlg::FileEqual(const String& f1, const String& f2, int& kind)
|
|
{
|
|
FileIn in1(f1);
|
|
FileIn in2(f2);
|
|
if(in1 && in2) {
|
|
kind = NORMAL_FILE;
|
|
if(in1.GetSize() != in2.GetSize())
|
|
return false;
|
|
|
|
while(!in1.IsEof() && !in2.IsEof()) {
|
|
String a = in1.Get(64*1024);
|
|
String b = in2.Get(64*1024);
|
|
if(a != b)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
kind = in1 ? DELETED_FILE : NEW_FILE;
|
|
|
|
return false;
|
|
}
|
|
|
|
void DirDiffDlg::Compare()
|
|
{
|
|
VectorMap<String, Time> fs;
|
|
GatherFilesDeep(fs, ~dir1, Null);
|
|
GatherFilesDeep(fs, ~dir2, Null);
|
|
|
|
copyleft.Disable();
|
|
copyright.Disable();
|
|
|
|
removeleft.Disable();
|
|
removeright.Disable();
|
|
|
|
files.Clear();
|
|
SortByKey(fs);
|
|
Progress pi(t_("Comparing.."));
|
|
pi.SetTotal(fs.GetCount());
|
|
|
|
list.Clear();
|
|
Index<String> exts;
|
|
for(int i = 0; i < fs.GetCount(); i++) {
|
|
if(pi.StepCanceled())
|
|
break;
|
|
String p = fs.GetKey(i);
|
|
String p1 = AppendFileName(~dir1, p);
|
|
String p2 = AppendFileName(~dir2, p);
|
|
int kind = NORMAL_FILE;
|
|
auto IsGit = [&](const String& path) {
|
|
return path.Find("/.git/") >= 0 || path.Find("\\.git/") >= 0 || path.Find("\\.git\\") >= 0 || path.Find("/.git\\") >= 0;
|
|
};
|
|
if(!FileEqual(p1, p2, kind) && !IsGit(p1) && !IsGit(p2)) {
|
|
exts.FindAdd(GetFileExt(p1));
|
|
FileInfo& m = list.Add();
|
|
m.file = p;
|
|
m.path1 = p1;
|
|
m.path2 = p2;
|
|
m.time = fs[i];
|
|
m.kind = kind;
|
|
}
|
|
}
|
|
|
|
extension.Clear();
|
|
extension.Add(Null, "*.*");
|
|
for(int ii : GetSortOrder(exts))
|
|
extension.Add(exts[ii], "*" + exts[ii]);
|
|
extension.Enable();
|
|
|
|
ShowResult();
|
|
}
|
|
|
|
FileList::File DirDiffDlg::MakeFile(int i)
|
|
{
|
|
static Color cs[] = { SColorText(), SRed(), SGreen(), SRed(), SLtBlue() };
|
|
FileList::File m;
|
|
m.isdir = false;
|
|
m.unixexe = false;
|
|
m.hidden = false;
|
|
Image icn = WhenIcon(FileExists(list[i].path1) ? list[i].path1 : list[i].path2);
|
|
int k = list[i].kind;
|
|
if(IsNull(icn))
|
|
icn = CtrlImg::File();
|
|
m.icon = decode(k, FAILED_FILE, AdjustImage(icn, [](const Image& m) { return GetOver(m, DiffImg::Failed()); }),
|
|
PATCHED_FILE, AdjustImage(icn, [](const Image& m) { return GetOver(m, DiffImg::Patched()); }),
|
|
icn);
|
|
m.name = list[i].file;
|
|
m.font = decode(k, FAILED_FILE, StdFont().Strikeout().Italic(),
|
|
PATCHED_FILE, StdFont().Italic(), StdFont());
|
|
m.ink = cs[k];
|
|
m.length = 0;
|
|
m.time = Null;
|
|
m.extink = m.ink;
|
|
m.data = i;
|
|
return m;
|
|
}
|
|
|
|
void DirDiffDlg::ShowResult()
|
|
{
|
|
files.Clear();
|
|
String sFind = ToLower(~~find);
|
|
String ext = ToLower(~~extension);
|
|
Date dlim = IsNull(recent) ? Null : GetSysDate() - (int)~recent;
|
|
for(int i = 0; i < list.GetCount(); i++) {
|
|
const FileInfo& fi = list[i];
|
|
int n = fi.kind;
|
|
String fn = ToLower(list[i].file);
|
|
if((IsNull(dlim) || fi.time >= dlim) &&
|
|
(n == NORMAL_FILE && modified ||
|
|
n == DELETED_FILE && removed ||
|
|
n == NEW_FILE && added ||
|
|
n == FAILED_FILE ||
|
|
n == PATCHED_FILE) &&
|
|
fn.Find(sFind) >= 0 &&
|
|
fn.EndsWith(ext))
|
|
files.Add(MakeFile(i));
|
|
}
|
|
Title(AsString(files.GetCount()) + " files");
|
|
clearFind.Show(!IsNull(find));
|
|
}
|
|
|
|
void DirDiffDlg::ClearFiles()
|
|
{
|
|
files.Clear();
|
|
compare.Enable(!IsNull(dir1) && !IsNull(dir2));
|
|
}
|
|
|
|
WString ExpandTabs(const wchar *text)
|
|
{
|
|
WString out;
|
|
for(wchar c; (c = *text++);)
|
|
if(c == '\t')
|
|
out.Cat(' ', 4 - out.GetLength() % 4);
|
|
else
|
|
out.Cat(c);
|
|
return out;
|
|
}
|
|
|
|
String SplitLines(const String& s)
|
|
{
|
|
StringStream ss(s);
|
|
WString result;
|
|
while(!ss.IsEof()) {
|
|
WString l = ExpandTabs(ss.GetLine().ToWString());
|
|
int q = 0;
|
|
while(l.GetCount() - q > 80) {
|
|
result.Cat(~l + q, 80);
|
|
result.Cat('\n');
|
|
q += 80;
|
|
}
|
|
result.Cat(~l + q, l.GetCount() - q);
|
|
result.Cat('\n');
|
|
}
|
|
return result.ToString();
|
|
}
|
|
|
|
void DirDiffDlg::File()
|
|
{
|
|
String fn = files.GetCurrentName();
|
|
String p1 = AppendFileName(~dir1, fn);
|
|
String p2 = AppendFileName(~dir2, fn);
|
|
|
|
diff.right.WhenHighlight = diff.left.WhenHighlight = [=](Vector<LineEdit::Highlight>& hl, const WString& ln) {
|
|
DiffDlg::WhenHighlight(AppendFileName(p1, files.GetCurrentName()), hl, ln);
|
|
};
|
|
|
|
diff.Set(Null, Null);
|
|
f1 = LoadFile(p1);
|
|
f2 = LoadFile(p2);
|
|
if(split_lines) {
|
|
f1 = SplitLines(f1);
|
|
f2 = SplitLines(f2);
|
|
}
|
|
if(GetFileLength(p1) < 4 * 1024 * 1024 && GetFileLength(p2) < 4 * 1024 * 1024)
|
|
diff.Set(f1, f2);
|
|
if(lmid < p1.GetCount())
|
|
p1 = p1.Mid(lmid);
|
|
lfile <<= p1;
|
|
if(rmid < p2.GetCount())
|
|
p2 = p2.Mid(rmid);
|
|
rfile <<= p2;
|
|
copyleft.Enable(editable_left);
|
|
copyright.Enable(editable_right);
|
|
removeleft.Enable(editable_left);
|
|
removeright.Enable(editable_right);
|
|
revertleft.Enable(backup.Find(p1) >= 0 && editable_left);
|
|
revertright.Enable(backup.Find(p2) >= 0 && editable_right);
|
|
}
|
|
|
|
void DirDiffDlg::Refresh()
|
|
{
|
|
int sc = diff.GetSc();
|
|
File();
|
|
diff.Sc(sc);
|
|
}
|
|
|
|
void DirDiffDlg::Backup(const String& path)
|
|
{
|
|
int q = backup.Find(path);
|
|
if(q < 0 && GetFileLength(path) < 4 * 1024 * 1024 && backup.GetCount() < 100)
|
|
backup.Add(path, ZCompress(LoadFile(path)));
|
|
}
|
|
|
|
void DirDiffDlg::Copy(bool left)
|
|
{
|
|
String src = ~lfile;
|
|
String dst = ~rfile;
|
|
if(left)
|
|
Swap(src, dst);
|
|
if(left ? diff.right.IsSelection() : diff.left.IsSelection()) {
|
|
Backup(dst);
|
|
SaveFile(dst, diff.Merge(left, HasCrs(dst)));
|
|
Refresh();
|
|
return;
|
|
}
|
|
if(PromptYesNo("Copy [* \1" + src + "\1]&to [* \1" + dst + "\1] ?")) {
|
|
Backup(dst);
|
|
String src_data = f1;
|
|
String dst_data = f2;
|
|
if(left)
|
|
Swap(src_data, dst_data);
|
|
SaveFile(dst, src_data);
|
|
Refresh();
|
|
}
|
|
}
|
|
|
|
bool Upp::DirDiffDlg::HotKey(dword key)
|
|
{
|
|
if(key == K_CTRL_F) {
|
|
ActiveFocus(find);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool DirDiffDlg::Key(dword key, int count)
|
|
{
|
|
bool left;
|
|
if(diff.left.HasFocus())
|
|
left = true;
|
|
else
|
|
if(diff.right.HasFocus())
|
|
left = false;
|
|
else
|
|
return TopWindow::Key(key, count);
|
|
switch(key) {
|
|
case K_F5:
|
|
case K_INSERT:
|
|
case K_ENTER:
|
|
case K_SPACE:
|
|
(left ? copyright : copyleft).WhenAction();
|
|
return true;
|
|
case K_F8:
|
|
case K_DELETE:
|
|
(left ? removeleft : removeright).WhenAction();
|
|
return true;
|
|
}
|
|
return TopWindow::Key(key, count);
|
|
}
|
|
|
|
}; |