Fix trace output
[TortoiseGit.git] / src / Utils / MiscUI / SciEdit.cpp
blob3068c9bd24de200960485287433412d1d2b22dc0
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2012-2014 - TortoiseGit
4 // Copyright (C) 2003-2008,2012-2014 - TortoiseSVN
5 // Copyright (C) 2012-2013 - Sven Strickroth <email@cs-ware.de>
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License
9 // as published by the Free Software Foundation; either version 2
10 // of the License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software Foundation,
19 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include "stdafx.h"
22 #include "LoglistCommonResource.h"
23 #include "..\PathUtils.h"
24 #include "..\UnicodeUtils.h"
25 #include <string>
26 #include "..\registry.h"
27 #include "SciEdit.h"
28 #include "SysInfo.h"
31 void CSciEditContextMenuInterface::InsertMenuItems(CMenu&, int&) {return;}
32 bool CSciEditContextMenuInterface::HandleMenuItemClick(int, CSciEdit *) {return false;}
35 #define STYLE_ISSUEBOLD 11
36 #define STYLE_ISSUEBOLDITALIC 12
37 #define STYLE_BOLD 14
38 #define STYLE_ITALIC 15
39 #define STYLE_UNDERLINED 16
40 #define STYLE_URL 17
42 #define STYLE_MASK 0x1f
44 #define SCI_ADDWORD 2000
46 struct loc_map {
47 const char * cp;
48 const char * def_enc;
51 struct loc_map enc2locale[] = {
52 {"28591","ISO8859-1"},
53 {"28592","ISO8859-2"},
54 {"28593","ISO8859-3"},
55 {"28594","ISO8859-4"},
56 {"28595","ISO8859-5"},
57 {"28596","ISO8859-6"},
58 {"28597","ISO8859-7"},
59 {"28598","ISO8859-8"},
60 {"28599","ISO8859-9"},
61 {"28605","ISO8859-15"},
62 {"20866","KOI8-R"},
63 {"21866","KOI8-U"},
64 {"1251","microsoft-cp1251"},
65 {"65001","UTF-8"},
69 IMPLEMENT_DYNAMIC(CSciEdit, CWnd)
71 CSciEdit::CSciEdit(void) : m_DirectFunction(NULL)
72 , m_DirectPointer(NULL)
73 , pChecker(NULL)
74 , pThesaur(NULL)
75 , m_spellcodepage(0)
76 , m_separator(0)
77 , m_bDoStyle(false)
78 , m_nAutoCompleteMinChars(3)
80 m_hModule = ::LoadLibrary(_T("SciLexer_tgit.dll"));
83 CSciEdit::~CSciEdit(void)
85 m_personalDict.Save();
86 if (m_hModule)
87 ::FreeLibrary(m_hModule);
88 if (pChecker)
89 delete pChecker;
90 if (pThesaur)
91 delete pThesaur;
94 void CSciEdit::Init(LONG lLanguage, BOOL bLoadSpellCheck)
96 //Setup the direct access data
97 m_DirectFunction = SendMessage(SCI_GETDIRECTFUNCTION, 0, 0);
98 m_DirectPointer = SendMessage(SCI_GETDIRECTPOINTER, 0, 0);
99 Call(SCI_SETMARGINWIDTHN, 1, 0);
100 Call(SCI_SETUSETABS, 0); //pressing TAB inserts spaces
101 Call(SCI_SETWRAPVISUALFLAGS, SC_WRAPVISUALFLAG_END);
102 Call(SCI_AUTOCSETIGNORECASE, 1);
103 Call(SCI_SETLEXER, SCLEX_CONTAINER);
104 Call(SCI_SETCODEPAGE, SC_CP_UTF8);
105 Call(SCI_AUTOCSETFILLUPS, 0, (LPARAM)"\t([");
106 Call(SCI_AUTOCSETMAXWIDTH, 0);
107 //Set the default windows colors for edit controls
108 Call(SCI_STYLESETFORE, STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOWTEXT));
109 Call(SCI_STYLESETBACK, STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOW));
110 Call(SCI_SETSELFORE, TRUE, ::GetSysColor(COLOR_HIGHLIGHTTEXT));
111 Call(SCI_SETSELBACK, TRUE, ::GetSysColor(COLOR_HIGHLIGHT));
112 Call(SCI_SETCARETFORE, ::GetSysColor(COLOR_WINDOWTEXT));
113 Call(SCI_SETMODEVENTMASK, SC_MOD_INSERTTEXT | SC_MOD_DELETETEXT | SC_PERFORMED_UNDO | SC_PERFORMED_REDO);
114 Call(SCI_INDICSETFORE, 1, 0x0000FF);
115 CStringA sWordChars;
116 CStringA sWhiteSpace;
117 for (int i=0; i<255; ++i)
119 if (i == '\r' || i == '\n')
120 continue;
121 else if (i < 0x20 || i == ' ')
122 sWhiteSpace += (char)i;
123 else if (isalnum(i) || i == '\'' || i == '_' || i == '-')
124 sWordChars += (char)i;
126 Call(SCI_SETWORDCHARS, 0, (LPARAM)(LPCSTR)sWordChars);
127 Call(SCI_SETWHITESPACECHARS, 0, (LPARAM)(LPCSTR)sWhiteSpace);
128 m_bDoStyle = ((DWORD)CRegStdDWORD(_T("Software\\TortoiseGit\\StyleCommitMessages"), TRUE))==TRUE;
129 m_nAutoCompleteMinChars = (int)(DWORD)CRegStdDWORD(_T("Software\\TortoiseGit\\AutoCompleteMinChars"), 3);
130 // look for dictionary files and use them if found
131 long langId = GetUserDefaultLCID();
133 if(bLoadSpellCheck)
135 if ((lLanguage != 0)||(((DWORD)CRegStdDWORD(_T("Software\\TortoiseGit\\Spellchecker"), FALSE))==FALSE))
137 if (!((lLanguage)&&(!LoadDictionaries(lLanguage))))
141 LoadDictionaries(langId);
142 DWORD lid = SUBLANGID(langId);
143 lid--;
144 if (lid > 0)
146 langId = MAKELANGID(PRIMARYLANGID(langId), lid);
148 else if (langId == 1033)
149 langId = 0;
150 else
151 langId = 1033;
152 } while ((langId)&&((pChecker==NULL)||(pThesaur==NULL)));
156 Call(SCI_SETEDGEMODE, EDGE_NONE);
157 Call(SCI_SETWRAPMODE, SC_WRAP_WORD);
158 Call(SCI_ASSIGNCMDKEY, SCK_END, SCI_LINEENDWRAP);
159 Call(SCI_ASSIGNCMDKEY, SCK_END + (SCMOD_SHIFT << 16), SCI_LINEENDWRAPEXTEND);
160 Call(SCI_ASSIGNCMDKEY, SCK_HOME, SCI_HOMEWRAP);
161 Call(SCI_ASSIGNCMDKEY, SCK_HOME + (SCMOD_SHIFT << 16), SCI_HOMEWRAPEXTEND);
162 CRegStdDWORD used2d(L"Software\\TortoiseGit\\ScintillaDirect2D", FALSE);
163 if (SysInfo::Instance().IsWin7OrLater() && DWORD(used2d))
165 Call(SCI_SETTECHNOLOGY, SC_TECHNOLOGY_DIRECTWRITE);
166 Call(SCI_SETBUFFEREDDRAW, 0);
171 void CSciEdit::Init(const ProjectProperties& props)
173 Init(props.lProjectLanguage);
174 m_sCommand = CStringA(CUnicodeUtils::GetUTF8(props.sCheckRe));
175 m_sBugID = CStringA(CUnicodeUtils::GetUTF8(props.sBugIDRe));
176 m_sUrl = CStringA(CUnicodeUtils::GetUTF8(props.sUrl));
178 if (props.nLogWidthMarker)
180 Call(SCI_SETWRAPMODE, SC_WRAP_NONE);
181 Call(SCI_SETEDGEMODE, EDGE_LINE);
182 Call(SCI_SETEDGECOLUMN, props.nLogWidthMarker);
184 else
186 Call(SCI_SETEDGEMODE, EDGE_NONE);
187 Call(SCI_SETWRAPMODE, SC_WRAP_WORD);
191 BOOL CSciEdit::LoadDictionaries(LONG lLanguageID)
193 //Setup the spell checker and thesaurus
194 TCHAR buf[6] = { 0 };
195 CString sFolder = CPathUtils::GetAppDirectory();
196 CString sFolderUp = CPathUtils::GetAppParentDirectory();
197 CString sFile;
199 GetLocaleInfo(MAKELCID(lLanguageID, SORT_DEFAULT), LOCALE_SISO639LANGNAME, buf, _countof(buf));
200 sFile = buf;
201 sFile += _T("_");
202 GetLocaleInfo(MAKELCID(lLanguageID, SORT_DEFAULT), LOCALE_SISO3166CTRYNAME, buf, _countof(buf));
203 sFile += buf;
204 if (pChecker==NULL)
206 if ((PathFileExists(sFolder + sFile + _T(".aff"))) &&
207 (PathFileExists(sFolder + sFile + _T(".dic"))))
209 pChecker = new Hunspell(CStringA(sFolder + sFile + _T(".aff")), CStringA(sFolder + sFile + _T(".dic")));
211 else if ((PathFileExists(sFolder + _T("dic\\") + sFile + _T(".aff"))) &&
212 (PathFileExists(sFolder + _T("dic\\") + sFile + _T(".dic"))))
214 pChecker = new Hunspell(CStringA(sFolder + _T("dic\\") + sFile + _T(".aff")), CStringA(sFolder + _T("dic\\") + sFile + _T(".dic")));
216 else if ((PathFileExists(sFolderUp + sFile + _T(".aff"))) &&
217 (PathFileExists(sFolderUp + sFile + _T(".dic"))))
219 pChecker = new Hunspell(CStringA(sFolderUp + sFile + _T(".aff")), CStringA(sFolderUp + sFile + _T(".dic")));
221 else if ((PathFileExists(sFolderUp + _T("dic\\") + sFile + _T(".aff"))) &&
222 (PathFileExists(sFolderUp + _T("dic\\") + sFile + _T(".dic"))))
224 pChecker = new Hunspell(CStringA(sFolderUp + _T("dic\\") + sFile + _T(".aff")), CStringA(sFolderUp + _T("dic\\") + sFile + _T(".dic")));
226 else if ((PathFileExists(sFolderUp + _T("Languages\\") + sFile + _T(".aff"))) &&
227 (PathFileExists(sFolderUp + _T("Languages\\") + sFile + _T(".dic"))))
229 pChecker = new Hunspell(CStringA(sFolderUp + _T("Languages\\") + sFile + _T(".aff")), CStringA(sFolderUp + _T("Languages\\") + sFile + _T(".dic")));
232 #if THESAURUS
233 if (pThesaur==NULL)
235 if ((PathFileExists(sFolder + _T("th_") + sFile + _T("_v2.idx"))) &&
236 (PathFileExists(sFolder + _T("th_") + sFile + _T("_v2.dat"))))
238 pThesaur = new MyThes(CStringA(sFolder + sFile + _T("_v2.idx")), CStringA(sFolder + sFile + _T("_v2.dat")));
240 else if ((PathFileExists(sFolder + _T("dic\\th_") + sFile + _T("_v2.idx"))) &&
241 (PathFileExists(sFolder + _T("dic\\th_") + sFile + _T("_v2.dat"))))
243 pThesaur = new MyThes(CStringA(sFolder + _T("dic\\") + sFile + _T("_v2.idx")), CStringA(sFolder + _T("dic\\") + sFile + _T("_v2.dat")));
245 else if ((PathFileExists(sFolderUp + _T("th_") + sFile + _T("_v2.idx"))) &&
246 (PathFileExists(sFolderUp + _T("th_") + sFile + _T("_v2.dat"))))
248 pThesaur = new MyThes(CStringA(sFolderUp + _T("th_") + sFile + _T("_v2.idx")), CStringA(sFolderUp + _T("th_") + sFile + _T("_v2.dat")));
250 else if ((PathFileExists(sFolderUp + _T("dic\\th_") + sFile + _T("_v2.idx"))) &&
251 (PathFileExists(sFolderUp + _T("dic\\th_") + sFile + _T("_v2.dat"))))
253 pThesaur = new MyThes(CStringA(sFolderUp + _T("dic\\th_") + sFile + _T("_v2.idx")), CStringA(sFolderUp + _T("dic\\th_") + sFile + _T("_v2.dat")));
255 else if ((PathFileExists(sFolderUp + _T("Languages\\th_") + sFile + _T("_v2.idx"))) &&
256 (PathFileExists(sFolderUp + _T("Languages\\th_") + sFile + _T("_v2.dat"))))
258 pThesaur = new MyThes(CStringA(sFolderUp + _T("Languages\\th_") + sFile + _T("_v2.idx")), CStringA(sFolderUp + _T("Languages\\th_") + sFile + _T("_v2.dat")));
261 #endif
262 if (pChecker)
264 const char * encoding = pChecker->get_dic_encoding();
265 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": %s\n", encoding);
266 int n = _countof(enc2locale);
267 m_spellcodepage = 0;
268 for (int i = 0; i < n; i++)
270 if (strcmp(encoding,enc2locale[i].def_enc) == 0)
272 m_spellcodepage = atoi(enc2locale[i].cp);
275 m_personalDict.Init(lLanguageID);
277 if ((pThesaur)||(pChecker))
278 return TRUE;
279 return FALSE;
282 LRESULT CSciEdit::Call(UINT message, WPARAM wParam, LPARAM lParam)
284 ASSERT(::IsWindow(m_hWnd)); //Window must be valid
285 ASSERT(m_DirectFunction); //Direct function must be valid
286 return ((SciFnDirect) m_DirectFunction)(m_DirectPointer, message, wParam, lParam);
289 CString CSciEdit::StringFromControl(const CStringA& text)
291 CString sText;
292 #ifdef UNICODE
293 int codepage = (int)Call(SCI_GETCODEPAGE);
294 int reslen = MultiByteToWideChar(codepage, 0, text, text.GetLength(), 0, 0);
295 MultiByteToWideChar(codepage, 0, text, text.GetLength(), sText.GetBuffer(reslen+1), reslen+1);
296 sText.ReleaseBuffer(reslen);
297 #else
298 sText = text;
299 #endif
300 return sText;
303 CStringA CSciEdit::StringForControl(const CString& text)
305 CStringA sTextA;
306 #ifdef UNICODE
307 int codepage = (int)SendMessage(SCI_GETCODEPAGE);
308 int reslen = WideCharToMultiByte(codepage, 0, text, text.GetLength(), 0, 0, 0, 0);
309 WideCharToMultiByte(codepage, 0, text, text.GetLength(), sTextA.GetBuffer(reslen), reslen, 0, 0);
310 sTextA.ReleaseBuffer(reslen);
311 #else
312 sTextA = text;
313 #endif
314 ATLTRACE("string length %d\n", sTextA.GetLength());
315 return sTextA;
318 void CSciEdit::SetText(const CString& sText)
320 CStringA sTextA = StringForControl(sText);
321 Call(SCI_SETTEXT, 0, (LPARAM)(LPCSTR)sTextA);
323 // Scintilla seems to have problems with strings that
324 // aren't terminated by a newline char. Once that char
325 // is there, it can be removed without problems.
326 // So we add here a newline, then remove it again.
327 Call(SCI_DOCUMENTEND);
328 Call(SCI_NEWLINE);
329 Call(SCI_DELETEBACK);
332 void CSciEdit::InsertText(const CString& sText, bool bNewLine)
334 CStringA sTextA = StringForControl(sText);
335 Call(SCI_REPLACESEL, 0, (LPARAM)(LPCSTR)sTextA);
336 if (bNewLine)
337 Call(SCI_REPLACESEL, 0, (LPARAM)(LPCSTR)"\n");
340 CString CSciEdit::GetText()
342 LRESULT len = Call(SCI_GETTEXT, 0, 0);
343 CStringA sTextA;
344 Call(SCI_GETTEXT, (WPARAM)(len + 1), (LPARAM)(LPCSTR)sTextA.GetBuffer((int)len + 1));
345 sTextA.ReleaseBuffer();
346 return StringFromControl(sTextA);
349 CString CSciEdit::GetWordUnderCursor(bool bSelectWord)
351 TEXTRANGEA textrange;
352 int pos = (int)Call(SCI_GETCURRENTPOS);
353 textrange.chrg.cpMin = (LONG)Call(SCI_WORDSTARTPOSITION, pos, TRUE);
354 if ((pos == textrange.chrg.cpMin)||(textrange.chrg.cpMin < 0))
355 return CString();
356 textrange.chrg.cpMax = (LONG)Call(SCI_WORDENDPOSITION, textrange.chrg.cpMin, TRUE);
358 std::unique_ptr<char[]> textbuffer(new char[textrange.chrg.cpMax - textrange.chrg.cpMin + 1]);
359 textrange.lpstrText = textbuffer.get();
360 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
361 if (bSelectWord)
363 Call(SCI_SETSEL, textrange.chrg.cpMin, textrange.chrg.cpMax);
365 CString sRet = StringFromControl(textbuffer.get());
366 return sRet;
369 void CSciEdit::SetFont(CString sFontName, int iFontSizeInPoints)
371 Call(SCI_STYLESETFONT, STYLE_DEFAULT, (LPARAM)(LPCSTR)CStringA(sFontName));
372 Call(SCI_STYLESETSIZE, STYLE_DEFAULT, iFontSizeInPoints);
373 Call(SCI_STYLECLEARALL);
375 LPARAM color = (LPARAM)GetSysColor(COLOR_HIGHLIGHT);
376 // set the styles for the bug ID strings
377 Call(SCI_STYLESETBOLD, STYLE_ISSUEBOLD, (LPARAM)TRUE);
378 Call(SCI_STYLESETFORE, STYLE_ISSUEBOLD, color);
379 Call(SCI_STYLESETBOLD, STYLE_ISSUEBOLDITALIC, (LPARAM)TRUE);
380 Call(SCI_STYLESETITALIC, STYLE_ISSUEBOLDITALIC, (LPARAM)TRUE);
381 Call(SCI_STYLESETFORE, STYLE_ISSUEBOLDITALIC, color);
382 Call(SCI_STYLESETHOTSPOT, STYLE_ISSUEBOLDITALIC, (LPARAM)TRUE);
384 // set the formatted text styles
385 Call(SCI_STYLESETBOLD, STYLE_BOLD, (LPARAM)TRUE);
386 Call(SCI_STYLESETITALIC, STYLE_ITALIC, (LPARAM)TRUE);
387 Call(SCI_STYLESETUNDERLINE, STYLE_UNDERLINED, (LPARAM)TRUE);
389 // set the style for URLs
390 Call(SCI_STYLESETFORE, STYLE_URL, color);
391 Call(SCI_STYLESETHOTSPOT, STYLE_URL, (LPARAM)TRUE);
393 Call(SCI_SETHOTSPOTACTIVEUNDERLINE, (LPARAM)TRUE);
396 void CSciEdit::SetAutoCompletionList(const std::set<CString>& list, const TCHAR separator)
398 //copy the auto completion list.
400 //SK: instead of creating a copy of that list, we could accept a pointer
401 //to the list and use that instead. But then the caller would have to make
402 //sure that the list persists over the lifetime of the control!
403 m_autolist.clear();
404 m_autolist = list;
405 m_separator = separator;
408 BOOL CSciEdit::IsMisspelled(const CString& sWord)
410 // convert the string from the control to the encoding of the spell checker module.
411 CStringA sWordA;
412 if (m_spellcodepage)
414 char * buf;
415 buf = sWordA.GetBuffer(sWord.GetLength()*4 + 1);
416 int lengthIncTerminator =
417 WideCharToMultiByte(m_spellcodepage, 0, sWord, -1, buf, sWord.GetLength()*4, NULL, NULL);
418 if (lengthIncTerminator == 0)
419 return FALSE; // converting to the codepage failed, assume word is spelled correctly
420 sWordA.ReleaseBuffer(lengthIncTerminator-1);
422 else
423 sWordA = CStringA(sWord);
424 sWordA.Trim("\'\".,");
425 // words starting with a digit are treated as correctly spelled
426 if (_istdigit(sWord.GetAt(0)))
427 return FALSE;
428 // words in the personal dictionary are correct too
429 if (m_personalDict.FindWord(sWord))
430 return FALSE;
432 // now we actually check the spelling...
433 if (!pChecker->spell(sWordA))
435 // the word is marked as misspelled, we now check whether the word
436 // is maybe a composite identifier
437 // a composite identifier consists of multiple words, with each word
438 // separated by a change in lower to uppercase letters
439 if (sWord.GetLength() > 1)
441 int wordstart = 0;
442 int wordend = 1;
443 while (wordend < sWord.GetLength())
445 while ((wordend < sWord.GetLength())&&(!_istupper(sWord[wordend])))
446 wordend++;
447 if ((wordstart == 0)&&(wordend == sWord.GetLength()))
449 // words in the auto list are also assumed correctly spelled
450 if (m_autolist.find(sWord) != m_autolist.end())
451 return FALSE;
452 return TRUE;
454 sWordA = CStringA(sWord.Mid(wordstart, wordend-wordstart));
455 if ((sWordA.GetLength() > 2)&&(!pChecker->spell(sWordA)))
457 return TRUE;
459 wordstart = wordend;
460 wordend++;
464 return FALSE;
467 void CSciEdit::CheckSpelling()
469 if (pChecker == NULL)
470 return;
472 TEXTRANGEA textrange;
474 LRESULT firstline = Call(SCI_GETFIRSTVISIBLELINE);
475 LRESULT lastline = firstline + Call(SCI_LINESONSCREEN);
476 textrange.chrg.cpMin = (LONG)Call(SCI_POSITIONFROMLINE, firstline);
477 textrange.chrg.cpMax = (LONG)textrange.chrg.cpMin;
478 LRESULT lastpos = Call(SCI_POSITIONFROMLINE, lastline) + Call(SCI_LINELENGTH, lastline);
479 if (lastpos < 0)
480 lastpos = Call(SCI_GETLENGTH)-textrange.chrg.cpMin;
481 while (textrange.chrg.cpMax < lastpos)
483 textrange.chrg.cpMin = (LONG)Call(SCI_WORDSTARTPOSITION, textrange.chrg.cpMax+1, TRUE);
484 if (textrange.chrg.cpMin < textrange.chrg.cpMax)
485 break;
486 textrange.chrg.cpMax = (LONG)Call(SCI_WORDENDPOSITION, textrange.chrg.cpMin, TRUE);
487 if (textrange.chrg.cpMin == textrange.chrg.cpMax)
489 textrange.chrg.cpMax++;
490 continue;
492 ATLASSERT(textrange.chrg.cpMax >= textrange.chrg.cpMin);
493 std::unique_ptr<char[]> textbuffer(new char[textrange.chrg.cpMax - textrange.chrg.cpMin + 2]);
494 SecureZeroMemory(textbuffer.get(), textrange.chrg.cpMax - textrange.chrg.cpMin + 2);
495 textrange.lpstrText = textbuffer.get();
496 textrange.chrg.cpMax++;
497 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
498 int len = (int)strlen(textrange.lpstrText);
499 if (len == 0)
501 textrange.chrg.cpMax--;
502 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
503 len = (int)strlen(textrange.lpstrText);
504 textrange.chrg.cpMax++;
505 len++;
507 if (len && textrange.lpstrText[len - 1] == '.')
509 // Try to ignore file names from the auto list.
510 // Do do this, for each word ending with '.' we extract next word and check
511 // whether the combined string is present in auto list.
512 TEXTRANGEA twoWords;
513 twoWords.chrg.cpMin = (LONG)textrange.chrg.cpMin;
514 twoWords.chrg.cpMax = (LONG)Call(SCI_WORDENDPOSITION, textrange.chrg.cpMax + 1, TRUE);
515 std::unique_ptr<char[]> twoWordsBuffer(new char[twoWords.chrg.cpMax - twoWords.chrg.cpMin + 1]);
516 twoWords.lpstrText = twoWordsBuffer.get();
517 SecureZeroMemory(twoWords.lpstrText, twoWords.chrg.cpMax - twoWords.chrg.cpMin + 1);
518 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&twoWords);
519 CString sWord = StringFromControl(twoWords.lpstrText);
520 if (m_autolist.find(sWord) != m_autolist.end())
522 //mark word as correct (remove the squiggle line)
523 Call(SCI_STARTSTYLING, twoWords.chrg.cpMin, INDICS_MASK);
524 Call(SCI_SETSTYLING, twoWords.chrg.cpMax - twoWords.chrg.cpMin, 0);
525 textrange.chrg.cpMax = twoWords.chrg.cpMax;
526 continue;
529 if (len)
530 textrange.lpstrText[len - 1] = 0;
531 textrange.chrg.cpMax--;
532 if (strlen(textrange.lpstrText) > 0)
534 CString sWord = StringFromControl(textrange.lpstrText);
535 if ((GetStyleAt(textrange.chrg.cpMin) != STYLE_URL) && IsMisspelled(sWord))
537 //mark word as misspelled
538 Call(SCI_STARTSTYLING, textrange.chrg.cpMin, INDICS_MASK);
539 Call(SCI_SETSTYLING, textrange.chrg.cpMax - textrange.chrg.cpMin, INDIC1_MASK);
541 else
543 //mark word as correct (remove the squiggle line)
544 Call(SCI_STARTSTYLING, textrange.chrg.cpMin, INDICS_MASK);
545 Call(SCI_SETSTYLING, textrange.chrg.cpMax - textrange.chrg.cpMin, 0);
551 void CSciEdit::SuggestSpellingAlternatives()
553 if (pChecker == NULL)
554 return;
555 CString word = GetWordUnderCursor(true);
556 Call(SCI_SETCURRENTPOS, Call(SCI_WORDSTARTPOSITION, Call(SCI_GETCURRENTPOS), TRUE));
557 if (word.IsEmpty())
558 return;
559 char ** wlst = nullptr;
560 int ns = pChecker->suggest(&wlst, CStringA(word));
561 if (ns > 0)
563 CString suggestions;
564 for (int i=0; i < ns; i++)
566 suggestions += CString(wlst[i]) + m_separator;
567 free(wlst[i]);
569 free(wlst);
570 suggestions.TrimRight(m_separator);
571 if (suggestions.IsEmpty())
572 return;
573 Call(SCI_AUTOCSETSEPARATOR, (WPARAM)CStringA(m_separator).GetAt(0));
574 Call(SCI_AUTOCSETDROPRESTOFWORD, 1);
575 Call(SCI_AUTOCSHOW, 0, (LPARAM)(LPCSTR)StringForControl(suggestions));
576 return;
578 free(wlst);
581 void CSciEdit::DoAutoCompletion(int nMinPrefixLength)
583 if (m_autolist.empty())
584 return;
585 if (Call(SCI_AUTOCACTIVE))
586 return;
587 CString word = GetWordUnderCursor();
588 if (word.GetLength() < nMinPrefixLength)
589 return; //don't auto complete yet, word is too short
590 int pos = (int)Call(SCI_GETCURRENTPOS);
591 if (pos != Call(SCI_WORDENDPOSITION, pos, TRUE))
592 return; //don't auto complete if we're not at the end of a word
593 CString sAutoCompleteList;
595 std::vector<CString> words;
597 pos = word.Find('-');
599 CString wordLower = word;
600 wordLower.MakeLower();
601 CString wordHigher = word;
602 wordHigher.MakeUpper();
604 words.push_back(wordLower);
605 words.push_back(wordHigher);
607 if (pos >= 0)
609 CString s = wordLower.Left(pos);
610 if (s.GetLength() >= nMinPrefixLength)
611 words.push_back(s);
612 s = wordLower.Mid(pos+1);
613 if (s.GetLength() >= nMinPrefixLength)
614 words.push_back(s);
615 s = wordHigher.Left(pos);
616 if (s.GetLength() >= nMinPrefixLength)
617 words.push_back(wordHigher.Left(pos));
618 s = wordHigher.Mid(pos+1);
619 if (s.GetLength() >= nMinPrefixLength)
620 words.push_back(wordHigher.Mid(pos+1));
623 std::set<CString> wordset;
624 for (const auto& w : words)
626 for (std::set<CString>::const_iterator lowerit = m_autolist.lower_bound(w);
627 lowerit != m_autolist.end(); ++lowerit)
629 int compare = w.CompareNoCase(lowerit->Left(w.GetLength()));
630 if (compare>0)
631 continue;
632 else if (compare == 0)
634 wordset.insert(*lowerit);
636 else
638 break;
643 for (const auto& w : wordset)
644 sAutoCompleteList += w + m_separator;
646 sAutoCompleteList.TrimRight(m_separator);
647 if (sAutoCompleteList.IsEmpty())
648 return;
650 Call(SCI_AUTOCSETSEPARATOR, (WPARAM)CStringA(m_separator).GetAt(0));
651 Call(SCI_AUTOCSHOW, word.GetLength(), (LPARAM)(LPCSTR)StringForControl(sAutoCompleteList));
654 BOOL CSciEdit::OnChildNotify(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pLResult)
656 if (message != WM_NOTIFY)
657 return CWnd::OnChildNotify(message, wParam, lParam, pLResult);
659 LPNMHDR lpnmhdr = (LPNMHDR) lParam;
660 SCNotification * lpSCN = (SCNotification *)lParam;
662 if(lpnmhdr->hwndFrom==m_hWnd)
664 switch(lpnmhdr->code)
666 case SCN_CHARADDED:
668 if ((lpSCN->ch < 32)&&(lpSCN->ch != 13)&&(lpSCN->ch != 10))
669 Call(SCI_DELETEBACK);
670 else
672 DoAutoCompletion(m_nAutoCompleteMinChars);
674 return TRUE;
676 break;
677 case SCN_STYLENEEDED:
679 int startstylepos = (int)Call(SCI_GETENDSTYLED);
680 int endstylepos = ((SCNotification *)lpnmhdr)->position;
681 MarkEnteredBugID(startstylepos, endstylepos);
682 if (m_bDoStyle)
683 StyleEnteredText(startstylepos, endstylepos);
684 StyleURLs(startstylepos, endstylepos);
685 CheckSpelling();
686 WrapLines(startstylepos, endstylepos);
687 return TRUE;
689 break;
690 case SCN_HOTSPOTCLICK:
692 TEXTRANGEA textrange;
693 textrange.chrg.cpMin = lpSCN->position;
694 textrange.chrg.cpMax = lpSCN->position;
695 DWORD style = GetStyleAt(lpSCN->position);
696 while (GetStyleAt(textrange.chrg.cpMin - 1) == style)
697 --textrange.chrg.cpMin;
698 while (GetStyleAt(textrange.chrg.cpMax + 1) == style)
699 ++textrange.chrg.cpMax;
700 ++textrange.chrg.cpMax;
701 std::unique_ptr<char[]> textbuffer(new char[textrange.chrg.cpMax - textrange.chrg.cpMin + 1]);
702 textrange.lpstrText = textbuffer.get();
703 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
704 CString url;
705 if (style == STYLE_URL)
706 url = StringFromControl(textbuffer.get());
707 else
709 url = m_sUrl;
710 url.Replace(L"%BUGID%", StringFromControl(textbuffer.get()));
712 if (!url.IsEmpty())
713 ShellExecute(GetParent()->GetSafeHwnd(), _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
715 break;
718 return CWnd::OnChildNotify(message, wParam, lParam, pLResult);
721 BEGIN_MESSAGE_MAP(CSciEdit, CWnd)
722 ON_WM_KEYDOWN()
723 ON_WM_CONTEXTMENU()
724 END_MESSAGE_MAP()
726 void CSciEdit::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
728 switch (nChar)
730 case (VK_ESCAPE):
732 if ((Call(SCI_AUTOCACTIVE)==0)&&(Call(SCI_CALLTIPACTIVE)==0))
733 ::SendMessage(GetParent()->GetSafeHwnd(), WM_CLOSE, 0, 0);
735 break;
737 CWnd::OnKeyDown(nChar, nRepCnt, nFlags);
740 BOOL CSciEdit::PreTranslateMessage(MSG* pMsg)
742 if (pMsg->message == WM_KEYDOWN)
744 switch (pMsg->wParam)
746 case VK_SPACE:
748 if (GetKeyState(VK_CONTROL) & 0x8000)
750 DoAutoCompletion(1);
751 return TRUE;
754 break;
755 case VK_TAB:
756 // The TAB cannot be handled in OnKeyDown because it is too late by then.
758 if (GetKeyState(VK_CONTROL)&0x8000)
760 //Ctrl-Tab was pressed, this means we should provide the user with
761 //a list of possible spell checking alternatives to the word under
762 //the cursor
763 SuggestSpellingAlternatives();
764 return TRUE;
766 else if (!Call(SCI_AUTOCACTIVE))
768 ::PostMessage(GetParent()->GetSafeHwnd(), WM_NEXTDLGCTL, GetKeyState(VK_SHIFT)&0x8000, 0);
769 return TRUE;
772 break;
775 return CWnd::PreTranslateMessage(pMsg);
778 void CSciEdit::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
780 int anchor = (int)Call(SCI_GETANCHOR);
781 int currentpos = (int)Call(SCI_GETCURRENTPOS);
782 int selstart = (int)Call(SCI_GETSELECTIONSTART);
783 int selend = (int)Call(SCI_GETSELECTIONEND);
784 int pointpos = 0;
785 if ((point.x == -1) && (point.y == -1))
787 CRect rect;
788 GetClientRect(&rect);
789 ClientToScreen(&rect);
790 point = rect.CenterPoint();
791 pointpos = (int)Call(SCI_GETCURRENTPOS);
793 else
795 // change the cursor position to the point where the user
796 // right-clicked.
797 CPoint clientpoint = point;
798 ScreenToClient(&clientpoint);
799 pointpos = (int)Call(SCI_POSITIONFROMPOINT, clientpoint.x, clientpoint.y);
801 CString sMenuItemText;
802 CMenu popup;
803 bool bRestoreCursor = true;
804 if (popup.CreatePopupMenu())
806 bool bCanUndo = !!Call(SCI_CANUNDO);
807 bool bCanRedo = !!Call(SCI_CANREDO);
808 bool bHasSelection = (selend-selstart > 0);
809 bool bCanPaste = !!Call(SCI_CANPASTE);
810 bool bIsReadOnly = !!Call(SCI_GETREADONLY);
811 UINT uEnabledMenu = MF_STRING | MF_ENABLED;
812 UINT uDisabledMenu = MF_STRING | MF_GRAYED;
814 // find the word under the cursor
815 CString sWord;
816 if (pointpos)
818 // setting the cursor clears the selection
819 Call(SCI_SETANCHOR, pointpos);
820 Call(SCI_SETCURRENTPOS, pointpos);
821 sWord = GetWordUnderCursor();
822 // restore the selection
823 Call(SCI_SETSELECTIONSTART, selstart);
824 Call(SCI_SETSELECTIONEND, selend);
826 else
827 sWord = GetWordUnderCursor();
828 CStringA worda = CStringA(sWord);
830 int nCorrections = 1;
831 bool bSpellAdded = false;
832 // check if the word under the cursor is spelled wrong
833 if ((pChecker)&&(!worda.IsEmpty()) && !bIsReadOnly)
835 char ** wlst = nullptr;
836 // get the spell suggestions
837 int ns = pChecker->suggest(&wlst,worda);
838 if (ns > 0)
840 // add the suggestions to the context menu
841 for (int i=0; i < ns; i++)
843 bSpellAdded = true;
844 CString sug = CString(wlst[i]);
845 popup.InsertMenu((UINT)-1, 0, nCorrections++, sug);
846 free(wlst[i]);
848 free(wlst);
850 else
851 free(wlst);
853 // only add a separator if spelling correction suggestions were added
854 if (bSpellAdded)
855 popup.AppendMenu(MF_SEPARATOR);
857 // also allow the user to add the word to the custom dictionary so
858 // it won't show up as misspelled anymore
859 if ((sWord.GetLength()<PDICT_MAX_WORD_LENGTH)&&((pChecker)&&(m_autolist.find(sWord) == m_autolist.end())&&(!pChecker->spell(worda)))&&
860 (!_istdigit(sWord.GetAt(0)))&&(!m_personalDict.FindWord(sWord)) && !bIsReadOnly)
862 sMenuItemText.Format(IDS_SCIEDIT_ADDWORD, sWord);
863 popup.AppendMenu(uEnabledMenu, SCI_ADDWORD, sMenuItemText);
864 // another separator
865 popup.AppendMenu(MF_SEPARATOR);
868 // add the 'default' entries
869 sMenuItemText.LoadString(IDS_SCIEDIT_UNDO);
870 popup.AppendMenu(bCanUndo ? uEnabledMenu : uDisabledMenu, SCI_UNDO, sMenuItemText);
871 sMenuItemText.LoadString(IDS_SCIEDIT_REDO);
872 popup.AppendMenu(bCanRedo ? uEnabledMenu : uDisabledMenu, SCI_REDO, sMenuItemText);
874 popup.AppendMenu(MF_SEPARATOR);
876 sMenuItemText.LoadString(IDS_SCIEDIT_CUT);
877 popup.AppendMenu(bHasSelection ? uEnabledMenu : uDisabledMenu, SCI_CUT, sMenuItemText);
878 sMenuItemText.LoadString(IDS_SCIEDIT_COPY);
879 popup.AppendMenu(bHasSelection ? uEnabledMenu : uDisabledMenu, SCI_COPY, sMenuItemText);
880 sMenuItemText.LoadString(IDS_SCIEDIT_PASTE);
881 popup.AppendMenu(bCanPaste ? uEnabledMenu : uDisabledMenu, SCI_PASTE, sMenuItemText);
883 popup.AppendMenu(MF_SEPARATOR);
885 sMenuItemText.LoadString(IDS_SCIEDIT_SELECTALL);
886 popup.AppendMenu(uEnabledMenu, SCI_SELECTALL, sMenuItemText);
888 popup.AppendMenu(MF_SEPARATOR);
890 sMenuItemText.LoadString(IDS_SCIEDIT_SPLITLINES);
891 popup.AppendMenu(bHasSelection ? uEnabledMenu : uDisabledMenu, SCI_LINESSPLIT, sMenuItemText);
893 if (m_arContextHandlers.GetCount() > 0)
894 popup.AppendMenu(MF_SEPARATOR);
896 int nCustoms = nCorrections;
897 // now add any custom context menus
898 for (INT_PTR handlerindex = 0; handlerindex < m_arContextHandlers.GetCount(); ++handlerindex)
900 CSciEditContextMenuInterface * pHandler = m_arContextHandlers.GetAt(handlerindex);
901 pHandler->InsertMenuItems(popup, nCustoms);
903 #if THESAURUS
904 if (nCustoms > nCorrections)
906 // custom menu entries present, so add another separator
907 popup.AppendMenu(MF_SEPARATOR);
910 // add found thesauri to sub menu's
911 CMenu thesaurs;
912 int nThesaurs = 0;
913 CPtrArray menuArray;
914 if (thesaurs.CreatePopupMenu())
916 if ((pThesaur)&&(!worda.IsEmpty()))
918 mentry * pmean;
919 worda.MakeLower();
920 int count = pThesaur->Lookup(worda, worda.GetLength(),&pmean);
921 if (count)
923 mentry * pm = pmean;
924 for (int i=0; i < count; i++)
926 CMenu * submenu = new CMenu();
927 menuArray.Add(submenu);
928 submenu->CreateMenu();
929 for (int j=0; j < pm->count; j++)
931 CString sug = CString(pm->psyns[j]);
932 submenu->InsertMenu((UINT)-1, 0, nCorrections + nCustoms + (nThesaurs++), sug);
934 thesaurs.InsertMenu((UINT)-1, MF_POPUP, (UINT_PTR)(submenu->m_hMenu), CString(pm->defn));
935 pm++;
938 if ((count > 0)&&(point.x >= 0))
940 #ifdef IDS_SPELLEDIT_THESAURUS
941 sMenuItemText.LoadString(IDS_SPELLEDIT_THESAURUS);
942 popup.InsertMenu((UINT)-1, MF_POPUP, (UINT_PTR)thesaurs.m_hMenu, sMenuItemText);
943 #else
944 popup.InsertMenu((UINT)-1, MF_POPUP, (UINT_PTR)thesaurs.m_hMenu, _T("Thesaurus"));
945 #endif
946 nThesaurs = nCustoms;
948 else
950 sMenuItemText.LoadString(IDS_SPELLEDIT_NOTHESAURUS);
951 popup.AppendMenu(MF_DISABLED | MF_GRAYED | MF_STRING, 0, sMenuItemText);
954 pThesaur->CleanUpAfterLookup(&pmean, count);
956 else
958 sMenuItemText.LoadString(IDS_SPELLEDIT_NOTHESAURUS);
959 popup.AppendMenu(MF_DISABLED | MF_GRAYED | MF_STRING, 0, sMenuItemText);
962 #endif
963 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
964 switch (cmd)
966 case 0:
967 break; // no command selected
968 case SCI_SELECTALL:
969 bRestoreCursor = false;
970 // fall through
971 case SCI_UNDO:
972 case SCI_REDO:
973 case SCI_CUT:
974 case SCI_COPY:
975 case SCI_PASTE:
976 Call(cmd);
977 break;
978 case SCI_ADDWORD:
979 m_personalDict.AddWord(sWord);
980 CheckSpelling();
981 break;
982 case SCI_LINESSPLIT:
984 int marker = (int)(Call(SCI_GETEDGECOLUMN) * Call(SCI_TEXTWIDTH, 0, (LPARAM)" "));
985 if (marker)
987 Call(SCI_TARGETFROMSELECTION);
988 Call(SCI_LINESJOIN);
989 Call(SCI_LINESSPLIT, marker);
992 break;
993 default:
994 if (cmd < nCorrections)
996 Call(SCI_SETANCHOR, pointpos);
997 Call(SCI_SETCURRENTPOS, pointpos);
998 GetWordUnderCursor(true);
999 CString temp;
1000 popup.GetMenuString(cmd, temp, 0);
1001 // setting the cursor clears the selection
1002 Call(SCI_REPLACESEL, 0, (LPARAM)(LPCSTR)StringForControl(temp));
1004 else if (cmd < (nCorrections+nCustoms))
1006 for (INT_PTR handlerindex = 0; handlerindex < m_arContextHandlers.GetCount(); ++handlerindex)
1008 CSciEditContextMenuInterface * pHandler = m_arContextHandlers.GetAt(handlerindex);
1009 if (pHandler->HandleMenuItemClick(cmd, this))
1010 break;
1013 #if THESAURUS
1014 else if (cmd <= (nThesaurs+nCorrections+nCustoms))
1016 Call(SCI_SETANCHOR, pointpos);
1017 Call(SCI_SETCURRENTPOS, pointpos);
1018 GetWordUnderCursor(true);
1019 CString temp;
1020 thesaurs.GetMenuString(cmd, temp, 0);
1021 Call(SCI_REPLACESEL, 0, (LPARAM)(LPCSTR)StringForControl(temp));
1023 #endif
1025 #ifdef THESAURUS
1026 for (INT_PTR index = 0; index < menuArray.GetCount(); ++index)
1028 CMenu * pMenu = (CMenu*)menuArray[index];
1029 delete pMenu;
1031 #endif
1033 if (bRestoreCursor)
1035 // restore the anchor and cursor position
1036 Call(SCI_SETCURRENTPOS, currentpos);
1037 Call(SCI_SETANCHOR, anchor);
1041 bool CSciEdit::StyleEnteredText(int startstylepos, int endstylepos)
1043 bool bStyled = false;
1044 const int line = (int)Call(SCI_LINEFROMPOSITION, startstylepos);
1045 const int line_number_end = (int)Call(SCI_LINEFROMPOSITION, endstylepos);
1046 for (int line_number = line; line_number <= line_number_end; ++line_number)
1048 int offset = (int)Call(SCI_POSITIONFROMLINE, line_number);
1049 int line_len = (int)Call(SCI_LINELENGTH, line_number);
1050 std::unique_ptr<char[]> linebuffer(new char[line_len+1]);
1051 Call(SCI_GETLINE, line_number, (LPARAM)linebuffer.get());
1052 linebuffer[line_len] = 0;
1053 int start = 0;
1054 int end = 0;
1055 while (FindStyleChars(linebuffer.get(), '*', start, end))
1057 Call(SCI_STARTSTYLING, start+offset, STYLE_MASK);
1058 Call(SCI_SETSTYLING, end-start, STYLE_BOLD);
1059 bStyled = true;
1060 start = end;
1062 start = 0;
1063 end = 0;
1064 while (FindStyleChars(linebuffer.get(), '^', start, end))
1066 Call(SCI_STARTSTYLING, start+offset, STYLE_MASK);
1067 Call(SCI_SETSTYLING, end-start, STYLE_ITALIC);
1068 bStyled = true;
1069 start = end;
1071 start = 0;
1072 end = 0;
1073 while (FindStyleChars(linebuffer.get(), '_', start, end))
1075 Call(SCI_STARTSTYLING, start+offset, STYLE_MASK);
1076 Call(SCI_SETSTYLING, end-start, STYLE_UNDERLINED);
1077 bStyled = true;
1078 start = end;
1081 return bStyled;
1084 bool CSciEdit::WrapLines(int startpos, int endpos)
1086 int markerX = (int)(Call(SCI_GETEDGECOLUMN) * Call(SCI_TEXTWIDTH, 0, (LPARAM)" "));
1087 if (markerX)
1089 Call(SCI_SETTARGETSTART, startpos);
1090 Call(SCI_SETTARGETEND, endpos);
1091 Call(SCI_LINESSPLIT, markerX);
1092 return true;
1094 return false;
1097 void CSciEdit::AdvanceUTF8(const char * str, int& pos)
1099 if ((str[pos] & 0xE0)==0xC0)
1101 // utf8 2-byte sequence
1102 pos += 2;
1104 else if ((str[pos] & 0xF0)==0xE0)
1106 // utf8 3-byte sequence
1107 pos += 3;
1109 else if ((str[pos] & 0xF8)==0xF0)
1111 // utf8 4-byte sequence
1112 pos += 4;
1114 else
1115 pos++;
1118 bool CSciEdit::FindStyleChars(const char * line, char styler, int& start, int& end)
1120 int i=0;
1121 int u=0;
1122 while (i < start)
1124 AdvanceUTF8(line, i);
1125 u++;
1128 bool bFoundMarker = false;
1129 CString sULine = CUnicodeUtils::GetUnicode(line);
1130 // find a starting marker
1131 while (line[i] != 0)
1133 if (line[i] == styler)
1135 if ((line[i+1]!=0)&&(IsCharAlphaNumeric(sULine[u+1]))&&
1136 (((u>0)&&(!IsCharAlphaNumeric(sULine[u-1]))) || (u==0)))
1138 start = i+1;
1139 AdvanceUTF8(line, i);
1140 u++;
1141 bFoundMarker = true;
1142 break;
1145 AdvanceUTF8(line, i);
1146 u++;
1148 if (!bFoundMarker)
1149 return false;
1150 // find ending marker
1151 bFoundMarker = false;
1152 while (line[i] != 0)
1154 if (line[i] == styler)
1156 if ((IsCharAlphaNumeric(sULine[u-1]))&&
1157 ((((u+1)<sULine.GetLength())&&(!IsCharAlphaNumeric(sULine[u+1]))) || ((u+1) == sULine.GetLength()))
1160 end = i;
1161 i++;
1162 bFoundMarker = true;
1163 break;
1166 AdvanceUTF8(line, i);
1167 u++;
1169 return bFoundMarker;
1172 BOOL CSciEdit::MarkEnteredBugID(int startstylepos, int endstylepos)
1174 if (m_sCommand.IsEmpty())
1175 return FALSE;
1176 // get the text between the start and end position we have to style
1177 const int line_number = (int)Call(SCI_LINEFROMPOSITION, startstylepos);
1178 int start_pos = (int)Call(SCI_POSITIONFROMLINE, (WPARAM)line_number);
1179 int end_pos = endstylepos;
1181 if (start_pos == end_pos)
1182 return FALSE;
1183 if (start_pos > end_pos)
1185 int switchtemp = start_pos;
1186 start_pos = end_pos;
1187 end_pos = switchtemp;
1190 std::unique_ptr<char[]> textbuffer(new char[end_pos - start_pos + 2]);
1191 TEXTRANGEA textrange;
1192 textrange.lpstrText = textbuffer.get();
1193 textrange.chrg.cpMin = start_pos;
1194 textrange.chrg.cpMax = end_pos;
1195 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
1196 CStringA msg = CStringA(textbuffer.get());
1198 Call(SCI_STARTSTYLING, start_pos, STYLE_MASK);
1202 if (!m_sBugID.IsEmpty())
1204 // match with two regex strings (without grouping!)
1205 const std::tr1::regex regCheck(m_sCommand);
1206 const std::tr1::regex regBugID(m_sBugID);
1207 const std::tr1::sregex_iterator end;
1208 std::string s = msg;
1209 LONG pos = 0;
1210 // note:
1211 // if start_pos is 0, we're styling from the beginning and let the ^ char match the beginning of the line
1212 // that way, the ^ matches the very beginning of the log message and not the beginning of further lines.
1213 // problem is: this only works *while* entering log messages. If a log message is pasted in whole or
1214 // multiple lines are pasted, start_pos can be 0 and styling goes over multiple lines. In that case, those
1215 // additional line starts also match ^
1216 for (std::tr1::sregex_iterator it(s.begin(), s.end(), regCheck, start_pos != 0 ? std::tr1::regex_constants::match_not_bol : std::tr1::regex_constants::match_default); it != end; ++it)
1218 // clear the styles up to the match position
1219 Call(SCI_SETSTYLING, it->position(0)-pos, STYLE_DEFAULT);
1221 // (*it)[0] is the matched string
1222 std::string matchedString = (*it)[0];
1223 LONG matchedpos = 0;
1224 for (std::tr1::sregex_iterator it2(matchedString.begin(), matchedString.end(), regBugID); it2 != end; ++it2)
1226 ATLTRACE("matched id : %s\n", std::string((*it2)[0]).c_str());
1228 // bold style up to the id match
1229 ATLTRACE("position = %ld\n", it2->position(0));
1230 if (it2->position(0))
1231 Call(SCI_SETSTYLING, it2->position(0) - matchedpos, STYLE_ISSUEBOLD);
1232 // bold and recursive style for the bug ID itself
1233 if ((*it2)[0].str().size())
1234 Call(SCI_SETSTYLING, (*it2)[0].str().size(), STYLE_ISSUEBOLDITALIC);
1235 matchedpos = (LONG)(it2->position(0) + (*it2)[0].str().size());
1237 if ((matchedpos)&&(matchedpos < (LONG)matchedString.size()))
1239 Call(SCI_SETSTYLING, matchedString.size() - matchedpos, STYLE_ISSUEBOLD);
1241 pos = (LONG)(it->position(0) + matchedString.size());
1243 // bold style for the rest of the string which isn't matched
1244 if (s.size()-pos)
1245 Call(SCI_SETSTYLING, s.size()-pos, STYLE_DEFAULT);
1247 else
1249 const std::tr1::regex regCheck(m_sCommand);
1250 const std::tr1::sregex_iterator end;
1251 std::string s = msg;
1252 LONG pos = 0;
1253 for (std::tr1::sregex_iterator it(s.begin(), s.end(), regCheck); it != end; ++it)
1255 // clear the styles up to the match position
1256 Call(SCI_SETSTYLING, it->position(0)-pos, STYLE_DEFAULT);
1257 pos = (LONG)it->position(0);
1259 const std::tr1::smatch match = *it;
1260 // we define group 1 as the whole issue text and
1261 // group 2 as the bug ID
1262 if (match.size() >= 2)
1264 ATLTRACE("matched id : %s\n", std::string(match[1]).c_str());
1265 Call(SCI_SETSTYLING, match[1].first-s.begin()-pos, STYLE_ISSUEBOLD);
1266 Call(SCI_SETSTYLING, std::string(match[1]).size(), STYLE_ISSUEBOLDITALIC);
1267 pos = (LONG)(match[1].second-s.begin());
1272 catch (std::exception) {}
1274 return FALSE;
1277 bool CSciEdit::IsValidURLChar(unsigned char ch)
1279 return isalnum(ch) ||
1280 ch == '_' || ch == '/' || ch == ';' || ch == '?' || ch == '&' || ch == '=' ||
1281 ch == '%' || ch == ':' || ch == '.' || ch == '#' || ch == '-' || ch == '+';
1284 void CSciEdit::StyleURLs(int startstylepos, int endstylepos)
1286 const int line_number = (int)Call(SCI_LINEFROMPOSITION, startstylepos);
1287 startstylepos = (int)Call(SCI_POSITIONFROMLINE, (WPARAM)line_number);
1289 int len = endstylepos - startstylepos + 1;
1290 std::unique_ptr<char[]> textbuffer(new char[len + 1]);
1291 TEXTRANGEA textrange;
1292 textrange.lpstrText = textbuffer.get();
1293 textrange.chrg.cpMin = startstylepos;
1294 textrange.chrg.cpMax = endstylepos;
1295 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
1296 // we're dealing with utf8 encoded text here, which means one glyph is
1297 // not necessarily one byte/wchar_t
1298 // that's why we use CStringA to still get a correct char index
1299 CStringA msg = textbuffer.get();
1301 int starturl = -1;
1302 for(int i = 0; i <= msg.GetLength(); )
1304 if ((i < len) && IsValidURLChar(msg[i]))
1306 if (starturl < 0)
1307 starturl = i;
1309 else
1311 if ((starturl >= 0) && IsUrl(msg.Mid(starturl, i - starturl)))
1313 ASSERT(startstylepos + i <= endstylepos);
1314 Call(SCI_STARTSTYLING, startstylepos + starturl, STYLE_MASK);
1315 Call(SCI_SETSTYLING, i - starturl, STYLE_URL);
1317 starturl = -1;
1319 AdvanceUTF8(msg, i);
1323 bool CSciEdit::IsUrl(const CStringA& sText)
1325 if (!PathIsURLA(sText))
1326 return false;
1327 if (sText.Find("://")>=0)
1328 return true;
1329 return false;
1332 bool CSciEdit::IsUTF8(LPVOID pBuffer, size_t cb)
1334 if (cb < 2)
1335 return true;
1336 UINT16 * pVal = (UINT16 *)pBuffer;
1337 UINT8 * pVal2 = (UINT8 *)(pVal+1);
1338 // scan the whole buffer for a 0x0000 sequence
1339 // if found, we assume a binary file
1340 for (size_t i=0; i<(cb-2); i=i+2)
1342 if (0x0000 == *pVal++)
1343 return false;
1345 pVal = (UINT16 *)pBuffer;
1346 if (*pVal == 0xFEFF)
1347 return false;
1348 if (cb < 3)
1349 return false;
1350 if (*pVal == 0xBBEF)
1352 if (*pVal2 == 0xBF)
1353 return true;
1355 // check for illegal UTF8 chars
1356 pVal2 = (UINT8 *)pBuffer;
1357 for (size_t i=0; i<cb; ++i)
1359 if ((*pVal2 == 0xC0)||(*pVal2 == 0xC1)||(*pVal2 >= 0xF5))
1360 return false;
1361 pVal2++;
1363 pVal2 = (UINT8 *)pBuffer;
1364 bool bUTF8 = false;
1365 for (size_t i=0; i<(cb-3); ++i)
1367 if ((*pVal2 & 0xE0)==0xC0)
1369 pVal2++;i++;
1370 if ((*pVal2 & 0xC0)!=0x80)
1371 return false;
1372 bUTF8 = true;
1374 if ((*pVal2 & 0xF0)==0xE0)
1376 pVal2++;i++;
1377 if ((*pVal2 & 0xC0)!=0x80)
1378 return false;
1379 pVal2++;i++;
1380 if ((*pVal2 & 0xC0)!=0x80)
1381 return false;
1382 bUTF8 = true;
1384 if ((*pVal2 & 0xF8)==0xF0)
1386 pVal2++;i++;
1387 if ((*pVal2 & 0xC0)!=0x80)
1388 return false;
1389 pVal2++;i++;
1390 if ((*pVal2 & 0xC0)!=0x80)
1391 return false;
1392 pVal2++;i++;
1393 if ((*pVal2 & 0xC0)!=0x80)
1394 return false;
1395 bUTF8 = true;
1397 pVal2++;
1399 if (bUTF8)
1400 return true;
1401 return false;
1404 void CSciEdit::SetAStyle(int style, COLORREF fore, COLORREF back, int size, const char *face)
1406 Call(SCI_STYLESETFORE, style, fore);
1407 Call(SCI_STYLESETBACK, style, back);
1408 if (size >= 1)
1409 Call(SCI_STYLESETSIZE, style, size);
1410 if (face)
1411 Call(SCI_STYLESETFONT, style, reinterpret_cast<LPARAM>(face));
1415 void CSciEdit::SetUDiffStyle()
1417 SetAStyle(STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOWTEXT), ::GetSysColor(COLOR_WINDOW),
1418 // Reusing TortoiseBlame's setting which already have an user friendly
1419 // pane in TortoiseSVN's Settings dialog, while there is no such
1420 // pane for TortoiseUDiff.
1421 CRegStdDWORD(_T("Software\\TortoiseGit\\BlameFontSize"), 10),
1422 WideToMultibyte(CRegStdString(_T("Software\\TortoiseGit\\BlameFontName"), _T("Courier New"))).c_str());
1424 Call(SCI_SETTABWIDTH, 4);
1425 Call(SCI_SETREADONLY, TRUE);
1426 //LRESULT pix = Call(SCI_TEXTWIDTH, STYLE_LINENUMBER, (LPARAM)"_99999");
1427 //Call(SCI_SETMARGINWIDTHN, 0, pix);
1428 //Call(SCI_SETMARGINWIDTHN, 1);
1429 //Call(SCI_SETMARGINWIDTHN, 2);
1430 //Set the default windows colors for edit controls
1431 Call(SCI_STYLESETFORE, STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOWTEXT));
1432 Call(SCI_STYLESETBACK, STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOW));
1433 Call(SCI_SETSELFORE, TRUE, ::GetSysColor(COLOR_HIGHLIGHTTEXT));
1434 Call(SCI_SETSELBACK, TRUE, ::GetSysColor(COLOR_HIGHLIGHT));
1435 Call(SCI_SETCARETFORE, ::GetSysColor(COLOR_WINDOWTEXT));
1437 //SendEditor(SCI_SETREADONLY, FALSE);
1438 Call(SCI_CLEARALL);
1439 Call(EM_EMPTYUNDOBUFFER);
1440 Call(SCI_SETSAVEPOINT);
1441 Call(SCI_CANCEL);
1442 Call(SCI_SETUNDOCOLLECTION, 0);
1444 Call(SCI_SETUNDOCOLLECTION, 1);
1445 Call(SCI_SETWRAPMODE,SC_WRAP_NONE);
1447 //::SetFocus(m_hWndEdit);
1448 Call(EM_EMPTYUNDOBUFFER);
1449 Call(SCI_SETSAVEPOINT);
1450 Call(SCI_GOTOPOS, 0);
1452 Call(SCI_CLEARDOCUMENTSTYLE, 0, 0);
1453 Call(SCI_SETSTYLEBITS, 5, 0);
1455 //SetAStyle(SCE_DIFF_DEFAULT, RGB(0, 0, 0));
1456 SetAStyle(SCE_DIFF_COMMAND, RGB(0x0A, 0x24, 0x36));
1457 SetAStyle(SCE_DIFF_POSITION, RGB(0xFF, 0, 0));
1458 SetAStyle(SCE_DIFF_HEADER, RGB(0x80, 0, 0), RGB(0xFF, 0xFF, 0x80));
1459 SetAStyle(SCE_DIFF_COMMENT, RGB(0, 0x80, 0));
1460 Call(SCI_STYLESETBOLD, SCE_DIFF_COMMENT, TRUE);
1461 SetAStyle(SCE_DIFF_DELETED, ::GetSysColor(COLOR_WINDOWTEXT), RGB(0xFF, 0x80, 0x80));
1462 SetAStyle(SCE_DIFF_ADDED, ::GetSysColor(COLOR_WINDOWTEXT), RGB(0x80, 0xFF, 0x80));
1464 Call(SCI_SETLEXER, SCLEX_DIFF);
1465 Call(SCI_SETKEYWORDS, 0, (LPARAM)"revision");
1466 Call(SCI_COLOURISE, 0, -1);
1469 int CSciEdit::LoadFromFile(CString &filename)
1471 FILE *fp = NULL;
1472 _tfopen_s(&fp, filename, _T("rb"));
1473 if (fp)
1475 //SetTitle();
1476 char data[4096] = { 0 };
1477 size_t lenFile = fread(data, 1, sizeof(data), fp);
1478 bool bUTF8 = IsUTF8(data, lenFile);
1479 while (lenFile > 0)
1481 Call(SCI_ADDTEXT, lenFile,
1482 reinterpret_cast<LPARAM>(static_cast<char *>(data)));
1483 lenFile = fread(data, 1, sizeof(data), fp);
1485 fclose(fp);
1486 Call(SCI_SETCODEPAGE, bUTF8 ? SC_CP_UTF8 : GetACP());
1487 return 0;
1489 else
1490 return -1;
1493 void CSciEdit::RestyleBugIDs()
1495 int endstylepos = (int)Call(SCI_GETLENGTH);
1496 // clear all styles
1497 Call(SCI_STARTSTYLING, 0, STYLE_MASK);
1498 Call(SCI_SETSTYLING, endstylepos, STYLE_DEFAULT);
1499 // style the bug IDs
1500 MarkEnteredBugID(0, endstylepos);