Fixed issue #3668: "Revert to revision" fails for added files
[TortoiseGit.git] / src / TortoiseProc / ProgressDlg.cpp
blob89752c569786dad535493d9b206df05a69a406c5
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2020 - TortoiseGit
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 // ProgressDlg.cpp : implementation file
22 #include "stdafx.h"
23 #include "TortoiseProc.h"
24 #include "ProgressDlg.h"
25 #include "UnicodeUtils.h"
26 #include "IconMenu.h"
27 #include "LoglistCommonResource.h"
28 #include <Tlhelp32.h>
29 #include "AppUtils.h"
30 #include "SmartHandle.h"
31 #include "../TGitCache/CacheInterface.h"
32 #include "LoglistUtils.h"
33 #include "MessageBox.h"
34 #include "LogFile.h"
35 #include "CmdLineParser.h"
36 #include "StringUtils.h"
38 // CProgressDlg dialog
40 IMPLEMENT_DYNAMIC(CProgressDlg, CResizableStandAloneDialog)
42 const int CProgressDlg::s_iProgressLinesLimit = max(50, static_cast<int>(CRegDWORD(L"Software\\TortoiseGit\\ProgressDlgLinesLimit", 50000)));
44 CProgressDlg::CProgressDlg(CWnd* pParent /*=nullptr*/)
45 : CResizableStandAloneDialog(CProgressDlg::IDD, pParent)
46 , m_bShowCommand(true)
47 , m_bAbort(false)
48 , m_bDone(false)
49 , m_startTick(GetTickCount64())
50 , m_BufStart(0)
51 , m_Git(&g_Git)
52 , m_hAccel(nullptr)
53 , m_pThread(nullptr)
54 , m_bBufferAll(false)
55 , m_iBufferAllImmediateLines(0)
56 , m_GitStatus(DWORD(-1))
58 int autoClose = CRegDWORD(L"Software\\TortoiseGit\\AutoCloseGitProgress", 0);
59 CCmdLineParser parser(AfxGetApp()->m_lpCmdLine);
60 if (parser.HasKey(L"closeonend"))
61 autoClose = parser.GetLongVal(L"closeonend");
62 switch (autoClose)
64 case 1:
65 m_AutoClose = AUTOCLOSE_IF_NO_OPTIONS;
66 break;
67 case 2:
68 m_AutoClose = AUTOCLOSE_IF_NO_ERRORS;
69 break;
70 default:
71 m_AutoClose = AUTOCLOSE_NO;
72 break;
76 CProgressDlg::~CProgressDlg()
78 if (m_hAccel)
79 DestroyAcceleratorTable(m_hAccel);
80 delete m_pThread;
83 void CProgressDlg::DoDataExchange(CDataExchange* pDX)
85 CDialog::DoDataExchange(pDX);
86 DDX_Control(pDX, IDC_CURRENT, this->m_CurrentWork);
87 DDX_Control(pDX, IDC_TITLE_ANIMATE, this->m_Animate);
88 DDX_Control(pDX, IDC_RUN_PROGRESS, this->m_Progress);
89 DDX_Control(pDX, IDC_LOG, this->m_Log);
90 DDX_Control(pDX, IDC_PROGRESS_BUTTON1, this->m_ctrlPostCmd);
93 BEGIN_MESSAGE_MAP(CProgressDlg, CResizableStandAloneDialog)
94 ON_WM_CLOSE()
95 ON_MESSAGE(MSG_PROGRESSDLG_UPDATE_UI, OnProgressUpdateUI)
96 ON_BN_CLICKED(IDOK, &CProgressDlg::OnBnClickedOk)
97 ON_BN_CLICKED(IDC_PROGRESS_BUTTON1, &CProgressDlg::OnBnClickedButton1)
98 ON_REGISTERED_MESSAGE(TaskBarButtonCreated, OnTaskbarBtnCreated)
99 ON_NOTIFY(EN_LINK, IDC_LOG, OnEnLinkLog)
100 ON_EN_VSCROLL(IDC_LOG, OnEnscrollLog)
101 ON_EN_HSCROLL(IDC_LOG, OnEnscrollLog)
102 END_MESSAGE_MAP()
104 BOOL CProgressDlg::OnInitDialog()
106 CResizableStandAloneDialog::OnInitDialog();
108 // Let the TaskbarButtonCreated message through the UIPI filter. If we don't
109 // do this, Explorer would be unable to send that message to our window if we
110 // were running elevated. It's OK to make the call all the time, since if we're
111 // not elevated, this is a no-op.
112 CHANGEFILTERSTRUCT cfs = { sizeof(CHANGEFILTERSTRUCT) };
113 typedef BOOL STDAPICALLTYPE ChangeWindowMessageFilterExDFN(HWND hWnd, UINT message, DWORD action, PCHANGEFILTERSTRUCT pChangeFilterStruct);
114 CAutoLibrary hUser = AtlLoadSystemLibraryUsingFullPath(L"user32.dll");
115 if (hUser)
117 auto pfnChangeWindowMessageFilterEx = reinterpret_cast<ChangeWindowMessageFilterExDFN*>(GetProcAddress(hUser, "ChangeWindowMessageFilterEx"));
118 if (pfnChangeWindowMessageFilterEx)
119 pfnChangeWindowMessageFilterEx(m_hWnd, TaskBarButtonCreated, MSGFLT_ALLOW, &cfs);
121 m_pTaskbarList.Release();
122 if (FAILED(m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList)))
123 m_pTaskbarList = nullptr;
125 AddAnchor(IDC_TITLE_ANIMATE, TOP_LEFT, TOP_RIGHT);
126 AddAnchor(IDC_RUN_PROGRESS, TOP_LEFT, TOP_RIGHT);
127 AddAnchor(IDC_LOG, TOP_LEFT, BOTTOM_RIGHT);
129 AddAnchor(IDOK, BOTTOM_RIGHT);
130 AddAnchor(IDCANCEL, BOTTOM_RIGHT);
131 AddAnchor(IDC_PROGRESS_BUTTON1, BOTTOM_LEFT);
132 AddAnchor(IDC_CURRENT, TOP_LEFT);
134 this->GetDlgItem(IDC_PROGRESS_BUTTON1)->ShowWindow(SW_HIDE);
135 m_Animate.Open(IDR_DOWNLOAD);
137 SetupLogMessageViewControl();
139 CString InitialText;
140 if (!m_PreText.IsEmpty())
141 InitialText = m_PreText + L"\r\n";
142 #if 0
143 if (m_bShowCommand && (!m_GitCmd.IsEmpty() ))
144 InitialText += m_GitCmd + L"\r\n\r\n";
145 #endif
146 m_Log.SetWindowTextW(InitialText);
147 m_CurrentWork.SetWindowText(L"");
149 if (!m_PreFailText.IsEmpty())
150 InsertColorText(this->m_Log, m_PreFailText, CTheme::Instance().IsDarkTheme() ? RGB(207, 47, 47) : RGB(255, 0, 0));
152 EnableSaveRestore(L"ProgressDlg");
154 m_pThread = AfxBeginThread(ProgressThreadEntry, this, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
155 if (!m_pThread)
157 CMessageBox::Show(this->m_hWnd, IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
158 DialogEnableWindow(IDCANCEL, TRUE);
160 else
162 m_pThread->m_bAutoDelete = FALSE;
163 m_pThread->ResumeThread();
166 CString sWindowTitle;
167 GetWindowText(sWindowTitle);
168 CAppUtils::SetWindowTitle(m_hWnd, m_Git->m_CurrentDir, sWindowTitle);
170 // Make sure this dialog is shown in foreground (see issue #1536)
171 SetForegroundWindow();
173 return TRUE;
176 static void EnsurePostMessage(CWnd* pWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
178 redo:
179 if (!pWnd->PostMessage(Msg, wParam, lParam))
181 if (GetLastError() == ERROR_NOT_ENOUGH_QUOTA)
183 Sleep(20);
184 goto redo;
186 else
187 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": Message %d-%d could not be sent (error %d; %s)\n", wParam, lParam, GetLastError(), static_cast<LPCTSTR>(CFormatMessageWrapper()));
191 UINT CProgressDlg::ProgressThreadEntry(LPVOID pVoid)
193 return static_cast<CProgressDlg*>(pVoid)->ProgressThread();
196 //static function, Share with SyncDialog
197 UINT CProgressDlg::RunCmdList(CWnd* pWnd, STRING_VECTOR& cmdlist, STRING_VECTOR& dirlist, bool bShowCommand, CString* pfilename, volatile bool* bAbort, CGitGuardedByteArray* pdata, CGit* git)
199 UINT ret = 0;
201 std::vector<std::unique_ptr<CBlockCacheForPath>> cacheBlockList;
202 std::vector<std::unique_ptr<CGit>> gitList;
203 if (dirlist.empty())
204 cacheBlockList.push_back(std::make_unique<CBlockCacheForPath>(git->m_CurrentDir));
205 else
207 for (const auto& dir : dirlist)
209 auto pGit = std::make_unique<CGit>();
210 pGit->m_CurrentDir = dir;
211 gitList.push_back(std::move(pGit));
212 cacheBlockList.push_back(std::make_unique<CBlockCacheForPath>(dir));
216 EnsurePostMessage(pWnd, MSG_PROGRESSDLG_UPDATE_UI, MSG_PROGRESSDLG_START, 0);
218 if (pdata)
219 pdata->clear();
221 for (size_t i = 0; i < cmdlist.size(); ++i)
223 if (cmdlist[i].IsEmpty())
224 continue;
226 if (bShowCommand)
228 CStringA str;
229 if (gitList.empty() || gitList.size() == 1 && gitList[0]->m_CurrentDir == git->m_CurrentDir)
230 str = CUnicodeUtils::GetUTF8((i > 0 ? L"\r\n" : L"") + cmdlist[i].Trim() + L"\r\n");
231 else
232 str = CUnicodeUtils::GetUTF8((i > 0 ? L"\r\n" : L"") + gitList[i]->m_CurrentDir + L"\r\n" + cmdlist[i].Trim() + L"\r\n");
233 for (int j = 0; j < str.GetLength(); ++j)
235 if (pdata)
237 pdata->m_critSec.Lock();
238 pdata->push_back(str[j]);
239 pdata->m_critSec.Unlock();
241 else
242 pWnd->PostMessage(MSG_PROGRESSDLG_UPDATE_UI, MSG_PROGRESSDLG_RUN, str[j]);
244 if (pdata)
245 pWnd->PostMessage(MSG_PROGRESSDLG_UPDATE_UI, MSG_PROGRESSDLG_RUN, 0);
248 PROCESS_INFORMATION pi;
249 CAutoGeneralHandle hRead;
250 int runAsyncRet = -1;
251 if (gitList.empty())
252 runAsyncRet = git->RunAsync(cmdlist[i].Trim(), &pi, hRead.GetPointer(), nullptr, pfilename);
253 else
254 runAsyncRet = gitList[i]->RunAsync(cmdlist[i].Trim(), &pi, hRead.GetPointer(), nullptr, pfilename);
255 if (runAsyncRet)
257 EnsurePostMessage(pWnd, MSG_PROGRESSDLG_UPDATE_UI, MSG_PROGRESSDLG_FAILED, -1 * runAsyncRet);
258 return runAsyncRet;
261 CAutoGeneralHandle piProcess(std::move(pi.hProcess));
262 CAutoGeneralHandle piThread(std::move(pi.hThread));
263 DWORD readnumber;
264 char lastByte = '\0';
265 char byte;
266 CString output;
267 while (ReadFile(hRead, &byte, 1, &readnumber, nullptr))
269 if (pdata)
271 if (byte == 0)
272 byte = '\n';
274 pdata->m_critSec.Lock();
275 if (byte == '\n' && lastByte != '\r')
276 pdata->push_back('\r');
277 pdata->push_back(byte);
278 lastByte = byte;
279 pdata->m_critSec.Unlock();
281 if (byte == '\r' || byte == '\n')
282 pWnd->PostMessage(MSG_PROGRESSDLG_UPDATE_UI, MSG_PROGRESSDLG_RUN, 0);
284 else
285 pWnd->PostMessage(MSG_PROGRESSDLG_UPDATE_UI, MSG_PROGRESSDLG_RUN, byte);
287 if (pdata)
289 pdata->m_critSec.Lock();
290 bool post = !pdata->empty();
291 pdata->m_critSec.Unlock();
292 if (post)
293 EnsurePostMessage(pWnd, MSG_PROGRESSDLG_UPDATE_UI, MSG_PROGRESSDLG_RUN, 0);
296 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": waiting for process to finish (%s), aborted: %d\n", static_cast<LPCTSTR>(cmdlist[i]), *bAbort);
298 WaitForSingleObject(pi.hProcess, INFINITE);
300 DWORD status = 0;
301 if (!GetExitCodeProcess(pi.hProcess, &status) || *bAbort)
303 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": process %s finished, status code could not be fetched, (error %d; %s), aborted: %d\n", static_cast<LPCTSTR>(cmdlist[i]), GetLastError(), static_cast<LPCTSTR>(CFormatMessageWrapper()), *bAbort);
305 EnsurePostMessage(pWnd, MSG_PROGRESSDLG_UPDATE_UI, MSG_PROGRESSDLG_FAILED, status);
306 return TGIT_GIT_ERROR_GET_EXIT_CODE;
308 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": process %s finished with code %d\n", static_cast<LPCTSTR>(cmdlist[i]), status);
309 ret |= status;
312 EnsurePostMessage(pWnd, MSG_PROGRESSDLG_UPDATE_UI, MSG_PROGRESSDLG_END, ret);
314 return ret;
317 UINT CProgressDlg::ProgressThread()
319 if (!m_GitCmd.IsEmpty())
320 m_GitCmdList.push_back(m_GitCmd);
322 CString* pfilename;
324 if (m_LogFile.IsEmpty())
325 pfilename = nullptr;
326 else
327 pfilename = &m_LogFile;
329 m_startTick = GetTickCount64();
330 m_GitStatus = RunCmdList(this, m_GitCmdList, m_GitDirList, m_bShowCommand, pfilename, &m_bAbort, &this->m_Databuf, m_Git);
331 return 0;
334 LRESULT CProgressDlg::OnProgressUpdateUI(WPARAM wParam, LPARAM lParam)
336 if (wParam == MSG_PROGRESSDLG_START)
338 m_BufStart = 0;
339 if (CRegDWORD(L"Software\\TortoiseGit\\DownloadAnimation", TRUE) == TRUE)
340 m_Animate.Play(0, INT_MAX, INT_MAX);
341 DialogEnableWindow(IDCANCEL, TRUE);
342 if (m_pTaskbarList)
344 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
345 m_pTaskbarList->SetProgressValue(m_hWnd, 0, 100);
348 if (wParam == MSG_PROGRESSDLG_END || wParam == MSG_PROGRESSDLG_FAILED)
350 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": got message: %d\n", wParam);
351 ULONGLONG tickSpent = GetTickCount64() - m_startTick;
352 CString strEndTime = CLoglistUtils::FormatDateAndTime(CTime::GetCurrentTime(), DATE_SHORTDATE, true, false);
354 if (m_bBufferAll)
356 m_Databuf.m_critSec.Lock();
357 m_Databuf.push_back(0);
358 m_Log.SetWindowText(Convert2UnionCode(reinterpret_cast<char*>(m_Databuf.data())));
359 m_Databuf.m_critSec.Unlock();
361 m_BufStart = 0;
362 m_Databuf.m_critSec.Lock();
363 this->m_Databuf.clear();
364 m_Databuf.m_critSec.Unlock();
366 m_bDone = true;
367 m_Progress.SetPos(100);
368 this->DialogEnableWindow(IDOK, TRUE);
370 m_GitStatus = static_cast<DWORD>(lParam);
371 if (m_GitCmd.IsEmpty() && m_GitCmdList.empty())
372 m_GitStatus = DWORD(-1);
374 // detect crashes of perl when performing git svn actions
375 if (m_GitStatus == 0 && m_GitCmd.Find(L" svn ") > 1)
377 CString log;
378 m_Log.GetWindowText(log);
379 if (log.GetLength() > 18 && log.Mid(log.GetLength() - 18) == L"perl.exe.stackdump")
380 m_GitStatus = DWORD(-1);
383 if (m_PostExecCallback)
385 CString extraMsg;
386 m_PostExecCallback(m_GitStatus, extraMsg);
387 if (!extraMsg.IsEmpty())
389 int start = m_Log.GetTextLength();
390 m_Log.SetSel(start, start);
391 m_Log.ReplaceSel(extraMsg);
395 CString text;
396 m_Log.GetWindowText(text);
397 text.Remove('\r');
398 CAppUtils::StyleURLs(text, &m_Log);
401 if (m_Animate.IsPlaying() && !m_GitStatus)
402 m_Animate.Play(28, 29, 1); // IDR_DOWNLOAD Frame No. Range 0 ~ 29 (Memo: -1 does not work for last frame, but 32767(WORD_MAX/2) does.)
403 else
404 m_Animate.Stop();
406 if (this->m_GitStatus)
408 m_Progress.SetState(PBST_ERROR);
409 if (m_pTaskbarList)
411 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_ERROR);
412 m_pTaskbarList->SetProgressValue(m_hWnd, 100, 100);
414 CString log;
415 log.Format(IDS_PROC_PROGRESS_GITUNCLEANEXIT, m_GitStatus);
416 CString err;
417 if (CRegDWORD(L"Software\\TortoiseGit\\ShowGitexeTimings", TRUE))
418 err.Format(L"\r\n\r\n%s (%I64u ms @ %s)\r\n", static_cast<LPCTSTR>(log), tickSpent, static_cast<LPCTSTR>(strEndTime));
419 else
420 err.Format(L"\r\n\r\n%s\r\n", static_cast<LPCTSTR>(log));
421 if (!m_GitCmd.IsEmpty() || !m_GitCmdList.empty())
422 InsertColorText(this->m_Log, err, CTheme::Instance().IsDarkTheme() ? RGB(207, 47, 47) : RGB(255, 0, 0));
423 if (CRegDWORD(L"Software\\TortoiseGit\\NoSounds", FALSE) == FALSE)
424 PlaySound(reinterpret_cast<LPCTSTR>(SND_ALIAS_SYSTEMEXCLAMATION), nullptr, SND_ALIAS_ID | SND_ASYNC);
426 else {
427 if (m_pTaskbarList)
428 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NOPROGRESS);
429 CString temp;
430 temp.LoadString(IDS_SUCCESS);
431 CString log;
432 if (CRegDWORD(L"Software\\TortoiseGit\\ShowGitexeTimings", TRUE))
433 log.Format(L"\r\n%s (%I64u ms @ %s)\r\n", static_cast<LPCTSTR>(temp), tickSpent, static_cast<LPCTSTR>(strEndTime));
434 else
435 log.Format(L"\r\n%s\r\n", static_cast<LPCTSTR>(temp));
436 if (CTheme::Instance().IsHighContrastMode())
437 InsertColorText(this->m_Log, log, ::GetSysColor(COLOR_WINDOWTEXT));
438 else
439 InsertColorText(this->m_Log, log, CTheme::Instance().IsDarkTheme() ? RGB(0, 178, 255) : RGB(0, 0, 255));
440 this->DialogEnableWindow(IDCANCEL, FALSE);
443 m_Log.PostMessage(WM_VSCROLL, SB_BOTTOM, 0);
445 if (wParam == MSG_PROGRESSDLG_END)
447 if (m_PostCmdCallback) // new handling method using callback
449 m_PostCmdCallback(m_GitStatus, m_PostCmdList);
451 if (!m_PostCmdList.empty())
453 int i = 0;
454 for (const auto& entry : m_PostCmdList)
456 ++i;
457 m_ctrlPostCmd.AddEntry(entry.label, entry.icon);
458 TCHAR accellerator = CStringUtils::GetAccellerator(entry.label);
459 if (accellerator == L'\0')
460 continue;
461 ++m_accellerators[accellerator].cnt;
462 if (m_accellerators[accellerator].cnt > 1)
463 m_accellerators[accellerator].id = -1;
464 else
465 m_accellerators[accellerator].id = i - 1;
468 if (m_accellerators.size())
470 auto lpaccelNew = static_cast<LPACCEL>(LocalAlloc(LPTR, m_accellerators.size() * sizeof(ACCEL)));
471 SCOPE_EXIT { LocalFree(lpaccelNew); };
472 i = 0;
473 for (auto& entry : m_accellerators)
475 lpaccelNew[i].cmd = static_cast<WORD>(WM_USER + 1 + entry.second.id);
476 lpaccelNew[i].fVirt = FVIRTKEY | FALT;
477 lpaccelNew[i].key = entry.first;
478 entry.second.wmid = lpaccelNew[i].cmd;
479 ++i;
481 m_hAccel = CreateAcceleratorTable(lpaccelNew, static_cast<int>(m_accellerators.size()));
483 GetDlgItem(IDC_PROGRESS_BUTTON1)->ShowWindow(SW_SHOW);
488 if (wParam == MSG_PROGRESSDLG_END && m_GitStatus == 0)
490 if (m_AutoClose == AUTOCLOSE_IF_NO_OPTIONS && m_PostCmdList.empty() || m_AutoClose == AUTOCLOSE_IF_NO_ERRORS)
491 PostMessage(WM_COMMAND, 1, reinterpret_cast<LPARAM>(GetDlgItem(IDOK)->m_hWnd));
495 #define IMMEDIATELINES_LIMIT 10
496 if (!m_bBufferAll || m_iBufferAllImmediateLines < IMMEDIATELINES_LIMIT)
498 if (lParam == 0)
500 m_Databuf.m_critSec.Lock();
501 for (size_t i = this->m_BufStart; i < this->m_Databuf.size(); ++i)
503 char c = this->m_Databuf[m_BufStart];
504 ++m_BufStart;
505 m_Databuf.m_critSec.Unlock();
506 ParserCmdOutput(c);
507 if (m_bBufferAll && (c == '\r' || c == '\n'))
509 ++m_iBufferAllImmediateLines;
510 if (m_iBufferAllImmediateLines >= IMMEDIATELINES_LIMIT)
511 return 0;
514 m_Databuf.m_critSec.Lock();
517 if(m_BufStart > 1000)
519 m_Databuf.erase(m_Databuf.cbegin(), m_Databuf.cbegin() + m_BufStart);
520 m_BufStart =0;
522 m_Databuf.m_critSec.Unlock();
525 else
526 ParserCmdOutput(static_cast<char>(lParam));
528 return 0;
531 //static function, Share with SyncDialog
532 int CProgressDlg::ParsePercentage(CString& log, int s1)
534 int s2 = s1 - 1;
535 for (int i = s1 - 1; i >= 0; i--)
537 if (log[i] >= L'0' && log[i] <= L'9')
538 s2 = i;
539 else
540 break;
542 return _wtol(log.Mid(s2, s1 - s2));
545 void CProgressDlg::ParserCmdOutput(char ch)
547 ParserCmdOutput(this->m_Log, this->m_Progress, this->m_hWnd, this->m_pTaskbarList, this->m_LogTextA, ch, &this->m_CurrentWork);
549 void CProgressDlg::ClearESC(CString& str)
551 // see http://ascii-table.com/ansi-escape-sequences.php and http://tldp.org/HOWTO/Bash-Prompt-HOWTO/c327.html
552 str.Replace(L"\033[K", L""); // erase until end of line; no need to care for this, because we always clear the whole line
554 // drop colors
555 while (true)
557 int escapePosition = str.Find(L'\033');
558 if (escapePosition >= 0 && str.GetLength() >= escapePosition + 3)
560 if (str.Mid(escapePosition, 2) == L"\033[")
562 int colorEnd = str.Find(L'm', escapePosition + 2);
563 if (colorEnd > 0)
565 bool found = true;
566 for (int i = escapePosition + 2; i < colorEnd; ++i)
568 if (str[i] != L';' && (str[i] < L'0' && str[i] > L'9'))
570 found = false;
571 break;
574 if (found)
576 if (escapePosition > 0)
577 str = str.Left(escapePosition) + str.Mid(colorEnd + 1);
578 else
579 str = str.Mid(colorEnd);
580 continue;
585 break;
588 void CProgressDlg::ParserCmdOutput(CRichEditCtrl& log, CProgressCtrl& progressctrl, HWND m_hWnd, CComPtr<ITaskbarList3> m_pTaskbarList, CStringA& oneline, char ch, CWnd* CurrentWork)
590 //TRACE(L"%c",ch);
591 if (ch == ('\r') || ch == ('\n'))
593 CString str = CUnicodeUtils::GetUnicode(oneline);
595 // TRACE(L"End Char %s \r\n", ch == L'\r' ? L"lf" : L"");
596 // TRACE(L"End Char %s \r\n", ch == L'\n' ? L"cr" : L"");
598 int lines = log.GetLineCount();
599 str.Trim();
600 // TRACE(L"%s", str);
602 ClearESC(str);
604 if (ch == ('\r'))
606 int start = log.LineIndex(lines - 1);
607 log.SetSel(start, log.GetTextLength());
608 log.ReplaceSel(str);
610 else
612 int length = log.GetWindowTextLength();
613 log.SetSel(length, length);
614 if (length > 0)
615 log.ReplaceSel(L"\r\n" + str);
616 else
617 log.ReplaceSel(str);
620 if (lines > s_iProgressLinesLimit) //limited log length
622 int end = log.LineIndex(1);
623 log.SetSel(0, end);
624 log.ReplaceSel(L"");
626 log.PostMessage(WM_VSCROLL, SB_BOTTOM, 0);
628 int s1 = str.ReverseFind(L':');
629 int s2 = str.Find(L'%');
630 if (s1 > 0 && s2 > 0)
632 if (CurrentWork)
633 CurrentWork->SetWindowTextW(str.Left(s1));
635 int pos = ParsePercentage(str, s2);
636 TRACE(L"Pos %d\r\n", pos);
637 if (pos > 0)
639 progressctrl.SetPos(pos);
640 if (m_pTaskbarList)
642 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
643 m_pTaskbarList->SetProgressValue(m_hWnd, pos, 100);
648 oneline.Empty();
650 else
651 oneline += ch;
653 // CProgressDlg message handlers
655 void CProgressDlg::WriteLog() const
657 CLogFile logfile(g_Git.m_CurrentDir);
658 if (logfile.Open())
660 logfile.AddTimeLine();
661 CString text = GetLogText();
662 LPCTSTR psz_string = text;
663 while (*psz_string)
665 size_t i_len = wcscspn(psz_string, L"\r\n");
666 logfile.AddLine(CString(psz_string, static_cast<int>(i_len)));
667 psz_string += i_len;
668 if (*psz_string == '\r')
670 ++psz_string;
671 if (*psz_string == '\n')
672 ++psz_string;
674 else if (*psz_string == '\n')
675 ++psz_string;
677 if (m_bAbort)
679 CString canceled;
680 canceled.LoadString(IDS_USERCANCELLED);
681 logfile.AddLine(canceled);
683 logfile.Close();
687 void CProgressDlg::OnBnClickedOk()
689 if (m_pThread) // added here because Close-button is "called" from thread by PostMessage
690 ::WaitForSingleObject(m_pThread->m_hThread, 5000);
691 m_Log.GetWindowText(this->m_LogText);
692 WriteLog();
693 OnOK();
696 void CProgressDlg::OnBnClickedButton1()
698 WriteLog();
699 ShowWindow(SW_HIDE);
700 m_PostCmdList.at(m_ctrlPostCmd.GetCurrentEntry()).action();
701 EndDialog(IDOK);
704 void CProgressDlg::OnClose()
706 DialogEnableWindow(IDCANCEL, TRUE);
707 __super::OnClose();
710 void CProgressDlg::OnCancel()
712 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": User canceled\n");
713 m_bAbort = true;
714 if (m_bDone)
716 WriteLog();
717 CResizableStandAloneDialog::OnCancel();
718 return;
721 if (m_Git->m_CurrentGitPi.hProcess)
723 DWORD dwConfirmKillProcess = CRegDWORD(L"Software\\TortoiseGit\\ConfirmKillProcess");
724 if (dwConfirmKillProcess && CMessageBox::Show(m_hWnd, IDS_PROC_CONFIRMKILLPROCESS, IDS_APPNAME, MB_YESNO | MB_ICONQUESTION) != IDYES)
725 return;
726 if (::GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0))
728 ::WaitForSingleObject(m_Git->m_CurrentGitPi.hProcess, 10000);
731 KillProcessTree(m_Git->m_CurrentGitPi.dwProcessId);
734 ::WaitForSingleObject(m_Git->m_CurrentGitPi.hProcess, 10000);
735 if (m_pThread)
737 if (::WaitForSingleObject(m_pThread->m_hThread, 5000) == WAIT_TIMEOUT)
738 g_Git.KillRelatedThreads(m_pThread);
741 WriteLog();
742 CResizableStandAloneDialog::OnCancel();
745 void CProgressDlg::KillProcessTree(DWORD dwProcessId, unsigned int depth)
747 // recursively kills a process tree
748 // This is not optimized, but works and isn't called very often ;)
750 if (!dwProcessId || depth > 20)
751 return;
753 PROCESSENTRY32 pe = { 0 };
754 pe.dwSize = sizeof(PROCESSENTRY32);
756 CAutoGeneralHandle hSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
758 if (::Process32First(hSnap, &pe))
762 if (pe.th32ParentProcessID == dwProcessId)
763 KillProcessTree(pe.th32ProcessID, depth + 1);
764 } while (::Process32Next(hSnap, &pe));
766 CAutoGeneralHandle hProc = ::OpenProcess(PROCESS_TERMINATE, FALSE, dwProcessId);
767 if (hProc)
768 ::TerminateProcess(hProc, 1);
772 void CProgressDlg::InsertColorText(CRichEditCtrl& edit, CString text, COLORREF rgb)
774 CHARFORMAT old, cf;
775 edit.GetDefaultCharFormat(cf);
776 old = cf;
777 cf.dwMask |= CFM_COLOR;
778 cf.crTextColor = rgb;
779 cf.dwEffects |= CFE_BOLD;
780 cf.dwEffects &= ~CFE_AUTOCOLOR;
781 edit.SetSel(edit.GetTextLength() - 1, edit.GetTextLength());
782 edit.ReplaceSel(text);
783 edit.SetSel(edit.LineIndex(edit.GetLineCount() - 2), edit.GetTextLength());
784 edit.SetSelectionCharFormat(cf);
785 edit.SetSel(edit.GetTextLength(), edit.GetTextLength());
786 edit.SetDefaultCharFormat(old);
789 CString CCommitProgressDlg::Convert2UnionCode(char* buff)
791 int start = 0;
792 int size = static_cast<int>(strlen(buff));
794 CString str;
795 if (g_Git.m_LogEncode != CP_UTF8)
797 // git.exe commit output begings with "[BRANCH] SUBJECT" whereas SUBJECT is encoded using m_LogEncode
798 for (int i = 0; i < size; ++i)
800 if (buff[i] == ']')
801 start = i;
802 else if (start > 0 && buff[i] == '\n')
804 start = i;
805 break;
809 CGit::StringAppend(&str, buff, g_Git.m_LogEncode, start);
812 CGit::StringAppend(&str, buff + start, CP_UTF8, size - start);
814 ClearESC(str);
816 return str;
819 LRESULT CProgressDlg::OnTaskbarBtnCreated(WPARAM wParam, LPARAM lParam)
821 m_pTaskbarList.Release();
822 m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList);
823 return __super::OnTaskbarButtonCreated(wParam, lParam);
826 BOOL CProgressDlg::PreTranslateMessage(MSG* pMsg)
828 if (m_hAccel && TranslateAccelerator(m_hWnd, m_hAccel, pMsg))
829 return TRUE;
830 if (pMsg->message == WM_KEYDOWN)
832 if (pMsg->wParam == VK_ESCAPE)
834 // pressing the ESC key should close the dialog. But since we disabled the escape
835 // key (so the user doesn't get the idea that he could simply undo an e.g. update)
836 // this won't work.
837 // So if the user presses the ESC key, change it to VK_RETURN so the dialog gets
838 // the impression that the OK button was pressed.
839 if ((!GetDlgItem(IDCANCEL)->IsWindowEnabled())
840 &&(GetDlgItem(IDOK)->IsWindowEnabled())&&(GetDlgItem(IDOK)->IsWindowVisible()))
842 // since we convert ESC to RETURN, make sure the OK button has the focus.
843 GetDlgItem(IDOK)->SetFocus();
844 // make sure the RETURN is not handled by the RichEdit
845 pMsg->hwnd = GetSafeHwnd();
846 pMsg->wParam = VK_RETURN;
850 else if (pMsg->message == WM_CONTEXTMENU || pMsg->message == WM_RBUTTONDOWN)
852 auto pWnd = static_cast<CWnd*>(GetDlgItem(IDC_LOG));
853 if (pWnd == GetFocus())
855 CIconMenu popup;
856 if (popup.CreatePopupMenu())
858 long start = -1, end = -1;
859 auto pEdit = static_cast<CRichEditCtrl*>(GetDlgItem(IDC_LOG));
860 pEdit->GetSel(start, end);
861 popup.AppendMenuIcon(WM_COPY, IDS_SCIEDIT_COPY, IDI_COPYCLIP);
862 if (start >= end)
863 popup.EnableMenuItem(WM_COPY, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
864 popup.AppendMenu(MF_SEPARATOR);
865 popup.AppendMenuIcon(EM_SETSEL, IDS_STATUSLIST_CONTEXT_COPYEXT, IDI_COPYCLIP);
866 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, pMsg->pt.x, pMsg->pt.y, this);
867 switch (cmd)
869 case 0: // no command selected
870 break;
871 case EM_SETSEL:
873 pEdit->SetRedraw(FALSE);
874 int oldLine = pEdit->GetFirstVisibleLine();
875 pEdit->SetSel(0, -1);
876 pEdit->Copy();
877 pEdit->SetSel(start, end);
878 int newLine = pEdit->GetFirstVisibleLine();
879 pEdit->LineScroll(oldLine - newLine);
880 pEdit->SetRedraw(TRUE);
881 pEdit->RedrawWindow();
883 break;
884 case WM_COPY:
885 ::SendMessage(GetDlgItem(IDC_LOG)->GetSafeHwnd(), cmd, 0, -1);
886 break;
888 return TRUE;
892 return __super::PreTranslateMessage(pMsg);
895 LRESULT CProgressDlg::DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam)
897 if (m_hAccel && message == WM_COMMAND && LOWORD(wParam) >= WM_USER && LOWORD(wParam) <= WM_USER + m_accellerators.size())
899 for (const auto& entry : m_accellerators)
901 if (entry.second.wmid != LOWORD(wParam))
902 continue;
903 if (entry.second.id == -1)
904 m_ctrlPostCmd.PostMessage(WM_KEYDOWN, VK_F4, NULL);
905 else
907 m_ctrlPostCmd.SetCurrentEntry(entry.second.id);
908 OnBnClickedButton1();
910 return 0;
914 return __super::DefWindowProc(message, wParam, lParam);
917 void CProgressDlg::OnEnLinkLog(NMHDR* pNMHDR, LRESULT* pResult)
919 // similar code in SyncDlg.cpp and LogDlg.cpp
920 ENLINK* pEnLink = reinterpret_cast<ENLINK*>(pNMHDR);
921 if ((pEnLink->msg == WM_LBUTTONUP) || (pEnLink->msg == WM_SETCURSOR))
923 CString msg;
924 GetDlgItemText(IDC_LOG, msg);
925 msg.Replace(L"\r\n", L"\n");
926 CString url = msg.Mid(pEnLink->chrg.cpMin, pEnLink->chrg.cpMax - pEnLink->chrg.cpMin);
927 // check if it's an email address
928 auto atpos = url.Find(L'@');
929 if ((atpos > 0) && (url.ReverseFind(L'.') > atpos) && !::PathIsURL(url))
930 url = L"mailto:" + url;
931 if (::PathIsURL(url))
933 if (pEnLink->msg == WM_LBUTTONUP)
934 ShellExecute(GetSafeHwnd(), L"open", url, nullptr, nullptr, SW_SHOWDEFAULT);
935 else
937 static RECT prevRect = { 0 };
938 CWnd* pMsgView = GetDlgItem(IDC_LOG);
939 if (pMsgView)
941 RECT rc;
942 POINTL pt;
943 pMsgView->SendMessage(EM_POSFROMCHAR, reinterpret_cast<WPARAM>(&pt), pEnLink->chrg.cpMin);
944 rc.left = pt.x;
945 rc.top = pt.y;
946 pMsgView->SendMessage(EM_POSFROMCHAR, reinterpret_cast<WPARAM>(&pt), pEnLink->chrg.cpMax);
947 rc.right = pt.x;
948 rc.bottom = pt.y + 12;
949 if ((prevRect.left != rc.left) || (prevRect.top != rc.top))
951 m_tooltips.DelTool(pMsgView, 1);
952 m_tooltips.AddTool(pMsgView, url, &rc, 1);
953 prevRect = rc;
959 *pResult = 0;
962 void CProgressDlg::OnEnscrollLog()
964 m_tooltips.DelTool(GetDlgItem(IDC_LOG), 1);
967 void CProgressDlg::SetupLogMessageViewControl()
969 CFont logFont;
970 CAppUtils::CreateFontForLogs(logFont);
971 m_Log.SetFont(&logFont);
972 // make the log message rich edit control send a message when the mouse pointer is over a link
973 m_Log.SendMessage(EM_SETEVENTMASK, NULL, ENM_LINK | ENM_SCROLL);
975 CHARFORMAT2 format = { 0 };
976 format.cbSize = sizeof(CHARFORMAT2);
977 format.dwMask = CFM_COLOR | CFM_BACKCOLOR;
978 if (CTheme::Instance().IsDarkTheme())
980 format.crTextColor = CTheme::darkTextColor;
981 format.crBackColor = CTheme::darkBkColor;
983 else
985 format.crTextColor = ::GetSysColor(COLOR_WINDOWTEXT);
986 format.crBackColor = ::GetSysColor(COLOR_WINDOW);
988 m_Log.SendMessage(EM_SETCHARFORMAT, SCF_ALL, reinterpret_cast<LPARAM>(&format));
989 m_Log.SendMessage(EM_SETBKGNDCOLOR, 0, format.crBackColor);