From c3903c842d129998cab60fb06d0d6693ef8d6c03 Mon Sep 17 00:00:00 2001 From: Sven Strickroth Date: Sat, 22 Oct 2016 16:48:02 +0200 Subject: [PATCH] Detect email addresses in log messages and make them clickable Inspired by TortoiseSVN rev. 27517. Signed-off-by: Sven Strickroth --- src/TortoiseProc/AppUtils.cpp | 13 ++++++++++--- src/TortoiseProc/LogDlg.cpp | 4 ++++ src/Utils/MiscUI/SciEdit.cpp | 17 ++++++++++++++--- src/Utils/MiscUI/SciEdit.h | 2 +- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/TortoiseProc/AppUtils.cpp b/src/TortoiseProc/AppUtils.cpp index 1192c7b59..e539f3575 100644 --- a/src/TortoiseProc/AppUtils.cpp +++ b/src/TortoiseProc/AppUtils.cpp @@ -884,13 +884,20 @@ namespace { return iswalnum(ch) || ch == L'_' || ch == L'/' || ch == L';' || ch == L'?' || ch == L'&' || ch == L'=' || ch == L'%' || ch == L':' || ch == L'.' || ch == L'#' || ch == L'-' || ch == L'+' || - ch == L'|' || ch == L'>' || ch == L'<' || ch == L'!'; + ch == L'|' || ch == L'>' || ch == L'<' || ch == L'!' || ch == L'@'; } - bool IsUrl(const CString& sText) + bool IsUrlOrEmail(const CString& sText) { if (!PathIsURLW(sText)) + { + auto atpos = sText.Find(L'@'); + if (atpos <= 0) + return false; + if (sText.ReverseFind(L'.') > atpos) + return true; return false; + } for (const CString& prefix : { L"http://", L"https://", L"git://", L"ftp://", L"file://", L"mailto:" }) { if (CStringUtils::StartsWith(sText, prefix) && sText.GetLength() != prefix.GetLength()) @@ -939,7 +946,7 @@ std::vector CAppUtils::FindURLMatches(const CString& msg) while (i < len && msg[i] != '\r' && msg[i] != '\n' && msg[i] != '>') // find first '>' or new line after resetting i to start position ++i; } - if (!IsUrl(msg.Mid(starturl, i - starturl))) + if (!IsUrlOrEmail(msg.Mid(starturl, i - starturl))) { starturl = -1; continue; diff --git a/src/TortoiseProc/LogDlg.cpp b/src/TortoiseProc/LogDlg.cpp index c557817bd..bb6891c63 100644 --- a/src/TortoiseProc/LogDlg.cpp +++ b/src/TortoiseProc/LogDlg.cpp @@ -1829,6 +1829,10 @@ void CLogDlg::OnEnLinkMsgview(NMHDR *pNMHDR, LRESULT *pResult) url = m_LogList.m_ProjectProperties.GetBugIDUrl(url); url = GetAbsoluteUrlFromRelativeUrl(url); } + // check if it's an email address + auto atpos = url.Find(L'@'); + if ((atpos > 0) && (url.ReverseFind(L'.') > atpos) && !::PathIsURL(url)) + url = L"mailto:" + url; if (::PathIsURL(url)) { if (pEnLink->msg == WM_LBUTTONUP) diff --git a/src/Utils/MiscUI/SciEdit.cpp b/src/Utils/MiscUI/SciEdit.cpp index 822fc07a9..f0e84cd9f 100644 --- a/src/Utils/MiscUI/SciEdit.cpp +++ b/src/Utils/MiscUI/SciEdit.cpp @@ -784,7 +784,11 @@ BOOL CSciEdit::OnChildNotify(UINT message, WPARAM wParam, LPARAM lParam, LRESULT Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange); CString url; if (style == STYLE_URL) + { url = StringFromControl(textbuffer.get()); + if (url.Find(L'@') > 0 && !PathIsURL(url)) + url = L"mailto:" + url; + } else { url = m_sUrl; @@ -1374,7 +1378,7 @@ bool CSciEdit::IsValidURLChar(unsigned char ch) return isalnum(ch) || ch == '_' || ch == '/' || ch == ';' || ch == '?' || ch == '&' || ch == '=' || ch == '%' || ch == ':' || ch == '.' || ch == '#' || ch == '-' || ch == '+' || - ch == '|' || ch == '>' || ch == '<' || ch == '!'; + ch == '|' || ch == '>' || ch == '<' || ch == '!' || ch == '@'; } //similar code in AppUtils.cpp @@ -1417,7 +1421,7 @@ void CSciEdit::StyleURLs(int startstylepos, int endstylepos) while (i < len && msg[i] != '\r' && msg[i] != '\n' && msg[i] != '>') // find first '>' or new line after resetting i to start position AdvanceUTF8(msg, i); } - if (!IsUrl(msg.Mid(starturl, i - starturl))) + if (!IsUrlOrEmail(msg.Mid(starturl, i - starturl))) { starturl = -1; continue; @@ -1435,10 +1439,17 @@ void CSciEdit::StyleURLs(int startstylepos, int endstylepos) } } -bool CSciEdit::IsUrl(const CStringA& sText) +bool CSciEdit::IsUrlOrEmail(const CStringA& sText) { if (!PathIsURLA(sText)) + { + auto atpos = sText.Find('@'); + if (atpos <= 0) + return false; + if (sText.ReverseFind('.') > atpos) + return true; return false; + } for (const CStringA& prefix : { "http://", "https://", "git://", "ftp://", "file://", "mailto:" }) { if (strncmp(sText, prefix, prefix.GetLength()) == 0 && sText.GetLength() != prefix.GetLength()) diff --git a/src/Utils/MiscUI/SciEdit.h b/src/Utils/MiscUI/SciEdit.h index 8ca84d5ef..1f6908dcb 100644 --- a/src/Utils/MiscUI/SciEdit.h +++ b/src/Utils/MiscUI/SciEdit.h @@ -166,7 +166,7 @@ protected: void AdvanceUTF8(const char * str, int& pos); BOOL IsMisspelled(const CString& sWord); DWORD GetStyleAt(int pos) { return (DWORD)Call(SCI_GETSTYLEAT, pos) & 0x1f; } - bool IsUrl(const CStringA& sText); + bool IsUrlOrEmail(const CStringA& sText); CStringA GetWordForSpellChecker(const CString& sWord); CString GetWordFromSpellChecker(const CStringA& sWordA); -- 2.11.4.GIT