Sync TortoiseIDiff and TortoiseUDiff from TortoiseSVN
[TortoiseGit.git] / src / Utils / MiscUI / SciEdit.cpp
blob98cbb8f7ca04e9465958c17f6b8ee002a25c52b9
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2008,2012-2013 - TortoiseSVN
4 // Copyright (C) 2012 - Sven Strickroth <email@cs-ware.de>
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "stdafx.h"
21 #include "LoglistCommonResource.h"
22 #include "..\PathUtils.h"
23 #include "..\UnicodeUtils.h"
24 #include <string>
25 #include "..\registry.h"
26 #include ".\sciedit.h"
27 #include "SysInfo.h"
29 using namespace std;
32 void CSciEditContextMenuInterface::InsertMenuItems(CMenu&, int&) {return;}
33 bool CSciEditContextMenuInterface::HandleMenuItemClick(int, CSciEdit *) {return false;}
36 #define STYLE_ISSUEBOLD 11
37 #define STYLE_ISSUEBOLDITALIC 12
38 #define STYLE_BOLD 14
39 #define STYLE_ITALIC 15
40 #define STYLE_UNDERLINED 16
41 #define STYLE_URL 17
43 #define STYLE_MASK 0x1f
45 #define SCI_ADDWORD 2000
47 struct loc_map {
48 const char * cp;
49 const char * def_enc;
52 struct loc_map enc2locale[] = {
53 {"28591","ISO8859-1"},
54 {"28592","ISO8859-2"},
55 {"28593","ISO8859-3"},
56 {"28594","ISO8859-4"},
57 {"28595","ISO8859-5"},
58 {"28596","ISO8859-6"},
59 {"28597","ISO8859-7"},
60 {"28598","ISO8859-8"},
61 {"28599","ISO8859-9"},
62 {"28605","ISO8859-15"},
63 {"20866","KOI8-R"},
64 {"21866","KOI8-U"},
65 {"1251","microsoft-cp1251"},
66 {"65001","UTF-8"},
70 IMPLEMENT_DYNAMIC(CSciEdit, CWnd)
72 CSciEdit::CSciEdit(void) : m_DirectFunction(NULL)
73 , m_DirectPointer(NULL)
74 , pChecker(NULL)
75 , pThesaur(NULL)
76 , m_spellcodepage(0)
77 , m_separator(0)
78 , m_bDoStyle(false)
79 , m_nAutoCompleteMinChars(3)
81 m_hModule = ::LoadLibrary(_T("SciLexer.DLL"));
84 CSciEdit::~CSciEdit(void)
86 m_personalDict.Save();
87 if (m_hModule)
88 ::FreeLibrary(m_hModule);
89 if (pChecker)
90 delete pChecker;
91 if (pThesaur)
92 delete pThesaur;
95 void CSciEdit::Init(LONG lLanguage, BOOL bLoadSpellCheck)
97 //Setup the direct access data
98 m_DirectFunction = SendMessage(SCI_GETDIRECTFUNCTION, 0, 0);
99 m_DirectPointer = SendMessage(SCI_GETDIRECTPOINTER, 0, 0);
100 Call(SCI_SETMARGINWIDTHN, 1, 0);
101 Call(SCI_SETUSETABS, 0); //pressing TAB inserts spaces
102 Call(SCI_SETWRAPVISUALFLAGS, SC_WRAPVISUALFLAG_END);
103 Call(SCI_AUTOCSETIGNORECASE, 1);
104 Call(SCI_SETLEXER, SCLEX_CONTAINER);
105 Call(SCI_SETCODEPAGE, SC_CP_UTF8);
106 Call(SCI_AUTOCSETFILLUPS, 0, (LPARAM)"\t([");
107 Call(SCI_AUTOCSETMAXWIDTH, 0);
108 //Set the default windows colors for edit controls
109 Call(SCI_STYLESETFORE, STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOWTEXT));
110 Call(SCI_STYLESETBACK, STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOW));
111 Call(SCI_SETSELFORE, TRUE, ::GetSysColor(COLOR_HIGHLIGHTTEXT));
112 Call(SCI_SETSELBACK, TRUE, ::GetSysColor(COLOR_HIGHLIGHT));
113 Call(SCI_SETCARETFORE, ::GetSysColor(COLOR_WINDOWTEXT));
114 Call(SCI_SETMODEVENTMASK, SC_MOD_INSERTTEXT | SC_MOD_DELETETEXT | SC_PERFORMED_UNDO | SC_PERFORMED_REDO);
115 Call(SCI_INDICSETFORE, 1, 0x0000FF);
116 CStringA sWordChars;
117 CStringA sWhiteSpace;
118 for (int i=0; i<255; ++i)
120 if (i == '\r' || i == '\n')
121 continue;
122 else if (i < 0x20 || i == ' ')
123 sWhiteSpace += (char)i;
124 else if (isalnum(i) || i == '\'')
125 sWordChars += (char)i;
127 Call(SCI_SETWORDCHARS, 0, (LPARAM)(LPCSTR)sWordChars);
128 Call(SCI_SETWHITESPACECHARS, 0, (LPARAM)(LPCSTR)sWhiteSpace);
129 m_bDoStyle = ((DWORD)CRegStdDWORD(_T("Software\\TortoiseGit\\StyleCommitMessages"), TRUE))==TRUE;
130 m_nAutoCompleteMinChars = (int)(DWORD)CRegStdDWORD(_T("Software\\TortoiseGit\\AutoCompleteMinChars"), 3);
131 // look for dictionary files and use them if found
132 long langId = GetUserDefaultLCID();
134 if(bLoadSpellCheck)
136 if ((lLanguage != 0)||(((DWORD)CRegStdDWORD(_T("Software\\TortoiseGit\\Spellchecker"), FALSE))==FALSE))
138 if (!((lLanguage)&&(!LoadDictionaries(lLanguage))))
142 LoadDictionaries(langId);
143 DWORD lid = SUBLANGID(langId);
144 lid--;
145 if (lid > 0)
147 langId = MAKELANGID(PRIMARYLANGID(langId), lid);
149 else if (langId == 1033)
150 langId = 0;
151 else
152 langId = 1033;
153 } while ((langId)&&((pChecker==NULL)||(pThesaur==NULL)));
157 Call(SCI_SETEDGEMODE, EDGE_NONE);
158 Call(SCI_SETWRAPMODE, SC_WRAP_WORD);
159 Call(SCI_ASSIGNCMDKEY, SCK_END, SCI_LINEENDWRAP);
160 Call(SCI_ASSIGNCMDKEY, SCK_END + (SCMOD_SHIFT << 16), SCI_LINEENDWRAPEXTEND);
161 Call(SCI_ASSIGNCMDKEY, SCK_HOME, SCI_HOMEWRAP);
162 Call(SCI_ASSIGNCMDKEY, SCK_HOME + (SCMOD_SHIFT << 16), SCI_HOMEWRAPEXTEND);
163 CRegStdDWORD used2d(L"Software\\TortoiseGit\\ScintillaDirect2D", FALSE);
164 if (SysInfo::Instance().IsWin7OrLater() && DWORD(used2d))
166 Call(SCI_SETTECHNOLOGY, SC_TECHNOLOGY_DIRECTWRITE);
167 Call(SCI_SETBUFFEREDDRAW, 0);
169 Call(SCI_SETFONTQUALITY, SC_EFF_QUALITY_DEFAULT);
173 void CSciEdit::Init(const ProjectProperties& props)
175 Init(props.lProjectLanguage);
176 m_sCommand = CStringA(CUnicodeUtils::GetUTF8(props.sCheckRe));
177 m_sBugID = CStringA(CUnicodeUtils::GetUTF8(props.sBugIDRe));
178 m_sUrl = CStringA(CUnicodeUtils::GetUTF8(props.sUrl));
180 if (props.nLogWidthMarker)
182 Call(SCI_SETWRAPMODE, SC_WRAP_NONE);
183 Call(SCI_SETEDGEMODE, EDGE_LINE);
184 Call(SCI_SETEDGECOLUMN, props.nLogWidthMarker);
186 else
188 Call(SCI_SETEDGEMODE, EDGE_NONE);
189 Call(SCI_SETWRAPMODE, SC_WRAP_WORD);
191 SetText(props.sLogTemplate);
194 BOOL CSciEdit::LoadDictionaries(LONG lLanguageID)
196 //Setup the spell checker and thesaurus
197 TCHAR buf[6];
198 CString sFolder = CPathUtils::GetAppDirectory();
199 CString sFolderUp = CPathUtils::GetAppParentDirectory();
200 CString sFile;
202 GetLocaleInfo(MAKELCID(lLanguageID, SORT_DEFAULT), LOCALE_SISO639LANGNAME, buf, _countof(buf));
203 sFile = buf;
204 sFile += _T("_");
205 GetLocaleInfo(MAKELCID(lLanguageID, SORT_DEFAULT), LOCALE_SISO3166CTRYNAME, buf, _countof(buf));
206 sFile += buf;
207 if (pChecker==NULL)
209 if ((PathFileExists(sFolder + sFile + _T(".aff"))) &&
210 (PathFileExists(sFolder + sFile + _T(".dic"))))
212 pChecker = new Hunspell(CStringA(sFolder + sFile + _T(".aff")), CStringA(sFolder + sFile + _T(".dic")));
214 else if ((PathFileExists(sFolder + _T("dic\\") + sFile + _T(".aff"))) &&
215 (PathFileExists(sFolder + _T("dic\\") + sFile + _T(".dic"))))
217 pChecker = new Hunspell(CStringA(sFolder + _T("dic\\") + sFile + _T(".aff")), CStringA(sFolder + _T("dic\\") + sFile + _T(".dic")));
219 else if ((PathFileExists(sFolderUp + sFile + _T(".aff"))) &&
220 (PathFileExists(sFolderUp + sFile + _T(".dic"))))
222 pChecker = new Hunspell(CStringA(sFolderUp + sFile + _T(".aff")), CStringA(sFolderUp + sFile + _T(".dic")));
224 else if ((PathFileExists(sFolderUp + _T("dic\\") + sFile + _T(".aff"))) &&
225 (PathFileExists(sFolderUp + _T("dic\\") + sFile + _T(".dic"))))
227 pChecker = new Hunspell(CStringA(sFolderUp + _T("dic\\") + sFile + _T(".aff")), CStringA(sFolderUp + _T("dic\\") + sFile + _T(".dic")));
229 else if ((PathFileExists(sFolderUp + _T("Languages\\") + sFile + _T(".aff"))) &&
230 (PathFileExists(sFolderUp + _T("Languages\\") + sFile + _T(".dic"))))
232 pChecker = new Hunspell(CStringA(sFolderUp + _T("Languages\\") + sFile + _T(".aff")), CStringA(sFolderUp + _T("Languages\\") + sFile + _T(".dic")));
235 #if THESAURUS
236 if (pThesaur==NULL)
238 if ((PathFileExists(sFolder + _T("th_") + sFile + _T("_v2.idx"))) &&
239 (PathFileExists(sFolder + _T("th_") + sFile + _T("_v2.dat"))))
241 pThesaur = new MyThes(CStringA(sFolder + sFile + _T("_v2.idx")), CStringA(sFolder + sFile + _T("_v2.dat")));
243 else if ((PathFileExists(sFolder + _T("dic\\th_") + sFile + _T("_v2.idx"))) &&
244 (PathFileExists(sFolder + _T("dic\\th_") + sFile + _T("_v2.dat"))))
246 pThesaur = new MyThes(CStringA(sFolder + _T("dic\\") + sFile + _T("_v2.idx")), CStringA(sFolder + _T("dic\\") + sFile + _T("_v2.dat")));
248 else if ((PathFileExists(sFolderUp + _T("th_") + sFile + _T("_v2.idx"))) &&
249 (PathFileExists(sFolderUp + _T("th_") + sFile + _T("_v2.dat"))))
251 pThesaur = new MyThes(CStringA(sFolderUp + _T("th_") + sFile + _T("_v2.idx")), CStringA(sFolderUp + _T("th_") + sFile + _T("_v2.dat")));
253 else if ((PathFileExists(sFolderUp + _T("dic\\th_") + sFile + _T("_v2.idx"))) &&
254 (PathFileExists(sFolderUp + _T("dic\\th_") + sFile + _T("_v2.dat"))))
256 pThesaur = new MyThes(CStringA(sFolderUp + _T("dic\\th_") + sFile + _T("_v2.idx")), CStringA(sFolderUp + _T("dic\\th_") + sFile + _T("_v2.dat")));
258 else if ((PathFileExists(sFolderUp + _T("Languages\\th_") + sFile + _T("_v2.idx"))) &&
259 (PathFileExists(sFolderUp + _T("Languages\\th_") + sFile + _T("_v2.dat"))))
261 pThesaur = new MyThes(CStringA(sFolderUp + _T("Languages\\th_") + sFile + _T("_v2.idx")), CStringA(sFolderUp + _T("Languages\\th_") + sFile + _T("_v2.dat")));
264 #endif
265 if (pChecker)
267 const char * encoding = pChecker->get_dic_encoding();
268 ATLTRACE(encoding);
269 int n = _countof(enc2locale);
270 m_spellcodepage = 0;
271 for (int i = 0; i < n; i++)
273 if (strcmp(encoding,enc2locale[i].def_enc) == 0)
275 m_spellcodepage = atoi(enc2locale[i].cp);
278 m_personalDict.Init(lLanguageID);
280 if ((pThesaur)||(pChecker))
281 return TRUE;
282 return FALSE;
285 LRESULT CSciEdit::Call(UINT message, WPARAM wParam, LPARAM lParam)
287 ASSERT(::IsWindow(m_hWnd)); //Window must be valid
288 ASSERT(m_DirectFunction); //Direct function must be valid
289 return ((SciFnDirect) m_DirectFunction)(m_DirectPointer, message, wParam, lParam);
292 CString CSciEdit::StringFromControl(const CStringA& text)
294 CString sText;
295 #ifdef UNICODE
296 int codepage = (int)Call(SCI_GETCODEPAGE);
297 int reslen = MultiByteToWideChar(codepage, 0, text, text.GetLength(), 0, 0);
298 MultiByteToWideChar(codepage, 0, text, text.GetLength(), sText.GetBuffer(reslen+1), reslen+1);
299 sText.ReleaseBuffer(reslen);
300 #else
301 sText = text;
302 #endif
303 return sText;
306 CStringA CSciEdit::StringForControl(const CString& text)
308 CStringA sTextA;
309 #ifdef UNICODE
310 int codepage = (int)SendMessage(SCI_GETCODEPAGE);
311 int reslen = WideCharToMultiByte(codepage, 0, text, text.GetLength(), 0, 0, 0, 0);
312 WideCharToMultiByte(codepage, 0, text, text.GetLength(), sTextA.GetBuffer(reslen), reslen, 0, 0);
313 sTextA.ReleaseBuffer(reslen);
314 #else
315 sTextA = text;
316 #endif
317 ATLTRACE("string length %d\n", sTextA.GetLength());
318 return sTextA;
321 void CSciEdit::SetText(const CString& sText)
323 CStringA sTextA = StringForControl(sText);
324 Call(SCI_SETTEXT, 0, (LPARAM)(LPCSTR)sTextA);
326 // Scintilla seems to have problems with strings that
327 // aren't terminated by a newline char. Once that char
328 // is there, it can be removed without problems.
329 // So we add here a newline, then remove it again.
330 Call(SCI_DOCUMENTEND);
331 Call(SCI_NEWLINE);
332 Call(SCI_DELETEBACK);
335 void CSciEdit::InsertText(const CString& sText, bool bNewLine)
337 CStringA sTextA = StringForControl(sText);
338 Call(SCI_REPLACESEL, 0, (LPARAM)(LPCSTR)sTextA);
339 if (bNewLine)
340 Call(SCI_REPLACESEL, 0, (LPARAM)(LPCSTR)"\n");
343 CString CSciEdit::GetText()
345 LRESULT len = Call(SCI_GETTEXT, 0, 0);
346 CStringA sTextA;
347 Call(SCI_GETTEXT, (WPARAM)(len + 1), (LPARAM)(LPCSTR)sTextA.GetBuffer((int)len + 1));
348 sTextA.ReleaseBuffer();
349 return StringFromControl(sTextA);
352 CString CSciEdit::GetWordUnderCursor(bool bSelectWord)
354 TEXTRANGEA textrange;
355 int pos = (int)Call(SCI_GETCURRENTPOS);
356 textrange.chrg.cpMin = (LONG)Call(SCI_WORDSTARTPOSITION, pos, TRUE);
357 if ((pos == textrange.chrg.cpMin)||(textrange.chrg.cpMin < 0))
358 return CString();
359 textrange.chrg.cpMax = (LONG)Call(SCI_WORDENDPOSITION, textrange.chrg.cpMin, TRUE);
361 char * textbuffer = new char[textrange.chrg.cpMax - textrange.chrg.cpMin + 1];
363 textrange.lpstrText = textbuffer;
364 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
365 if (bSelectWord)
367 Call(SCI_SETSEL, textrange.chrg.cpMin, textrange.chrg.cpMax);
369 CString sRet = StringFromControl(textbuffer);
370 delete [] textbuffer;
371 return sRet;
374 void CSciEdit::SetFont(CString sFontName, int iFontSizeInPoints)
376 Call(SCI_STYLESETFONT, STYLE_DEFAULT, (LPARAM)(LPCSTR)CStringA(sFontName));
377 Call(SCI_STYLESETSIZE, STYLE_DEFAULT, iFontSizeInPoints);
378 Call(SCI_STYLECLEARALL);
380 LPARAM color = (LPARAM)GetSysColor(COLOR_HIGHLIGHT);
381 // set the styles for the bug ID strings
382 Call(SCI_STYLESETBOLD, STYLE_ISSUEBOLD, (LPARAM)TRUE);
383 Call(SCI_STYLESETFORE, STYLE_ISSUEBOLD, color);
384 Call(SCI_STYLESETBOLD, STYLE_ISSUEBOLDITALIC, (LPARAM)TRUE);
385 Call(SCI_STYLESETITALIC, STYLE_ISSUEBOLDITALIC, (LPARAM)TRUE);
386 Call(SCI_STYLESETFORE, STYLE_ISSUEBOLDITALIC, color);
387 Call(SCI_STYLESETHOTSPOT, STYLE_ISSUEBOLDITALIC, (LPARAM)TRUE);
389 // set the formatted text styles
390 Call(SCI_STYLESETBOLD, STYLE_BOLD, (LPARAM)TRUE);
391 Call(SCI_STYLESETITALIC, STYLE_ITALIC, (LPARAM)TRUE);
392 Call(SCI_STYLESETUNDERLINE, STYLE_UNDERLINED, (LPARAM)TRUE);
394 // set the style for URLs
395 Call(SCI_STYLESETFORE, STYLE_URL, color);
396 Call(SCI_STYLESETHOTSPOT, STYLE_URL, (LPARAM)TRUE);
398 Call(SCI_SETHOTSPOTACTIVEUNDERLINE, (LPARAM)TRUE);
401 void CSciEdit::SetAutoCompletionList(const std::set<CString>& list, const TCHAR separator)
403 //copy the auto completion list.
405 //SK: instead of creating a copy of that list, we could accept a pointer
406 //to the list and use that instead. But then the caller would have to make
407 //sure that the list persists over the lifetime of the control!
408 m_autolist.clear();
409 m_autolist = list;
410 m_separator = separator;
413 BOOL CSciEdit::IsMisspelled(const CString& sWord)
415 // convert the string from the control to the encoding of the spell checker module.
416 CStringA sWordA;
417 if (m_spellcodepage)
419 char * buf;
420 buf = sWordA.GetBuffer(sWord.GetLength()*4 + 1);
421 int lengthIncTerminator =
422 WideCharToMultiByte(m_spellcodepage, 0, sWord, -1, buf, sWord.GetLength()*4, NULL, NULL);
423 sWordA.ReleaseBuffer(lengthIncTerminator-1);
425 else
426 sWordA = CStringA(sWord);
427 sWordA.Trim("\'\".,");
428 // words starting with a digit are treated as correctly spelled
429 if (_istdigit(sWord.GetAt(0)))
430 return FALSE;
431 // words in the personal dictionary are correct too
432 if (m_personalDict.FindWord(sWord))
433 return FALSE;
435 // now we actually check the spelling...
436 if (!pChecker->spell(sWordA))
438 // the word is marked as misspelled, we now check whether the word
439 // is maybe a composite identifier
440 // a composite identifier consists of multiple words, with each word
441 // separated by a change in lower to uppercase letters
442 if (sWord.GetLength() > 1)
444 int wordstart = 0;
445 int wordend = 1;
446 while (wordend < sWord.GetLength())
448 while ((wordend < sWord.GetLength())&&(!_istupper(sWord[wordend])))
449 wordend++;
450 if ((wordstart == 0)&&(wordend == sWord.GetLength()))
452 // words in the auto list are also assumed correctly spelled
453 if (m_autolist.find(sWord) != m_autolist.end())
454 return FALSE;
455 return TRUE;
457 sWordA = CStringA(sWord.Mid(wordstart, wordend-wordstart));
458 if ((sWordA.GetLength() > 2)&&(!pChecker->spell(sWordA)))
460 return TRUE;
462 wordstart = wordend;
463 wordend++;
467 return FALSE;
470 void CSciEdit::CheckSpelling()
472 if (pChecker == NULL)
473 return;
475 TEXTRANGEA textrange;
477 LRESULT firstline = Call(SCI_GETFIRSTVISIBLELINE);
478 LRESULT lastline = firstline + Call(SCI_LINESONSCREEN);
479 textrange.chrg.cpMin = (LONG)Call(SCI_POSITIONFROMLINE, firstline);
480 textrange.chrg.cpMax = (LONG)textrange.chrg.cpMin;
481 LRESULT lastpos = Call(SCI_POSITIONFROMLINE, lastline) + Call(SCI_LINELENGTH, lastline);
482 if (lastpos < 0)
483 lastpos = Call(SCI_GETLENGTH)-textrange.chrg.cpMin;
484 while (textrange.chrg.cpMax < lastpos)
486 textrange.chrg.cpMin = (LONG)Call(SCI_WORDSTARTPOSITION, textrange.chrg.cpMax+1, TRUE);
487 if (textrange.chrg.cpMin < textrange.chrg.cpMax)
488 break;
489 textrange.chrg.cpMax = (LONG)Call(SCI_WORDENDPOSITION, textrange.chrg.cpMin, TRUE);
490 if (textrange.chrg.cpMin == textrange.chrg.cpMax)
492 textrange.chrg.cpMax++;
493 continue;
495 ATLASSERT(textrange.chrg.cpMax >= textrange.chrg.cpMin);
496 char * textbuffer = new char[textrange.chrg.cpMax - textrange.chrg.cpMin + 2];
497 SecureZeroMemory(textbuffer, textrange.chrg.cpMax - textrange.chrg.cpMin + 2);
498 textrange.lpstrText = textbuffer;
499 textrange.chrg.cpMax++;
500 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
501 int len = (int)strlen(textrange.lpstrText);
502 if (len == 0)
504 textrange.chrg.cpMax--;
505 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
506 len = (int)strlen(textrange.lpstrText);
507 textrange.chrg.cpMax++;
508 len++;
510 if (len && textrange.lpstrText[len - 1] == '.')
512 // Try to ignore file names from the auto list.
513 // Do do this, for each word ending with '.' we extract next word and check
514 // whether the combined string is present in auto list.
515 TEXTRANGEA twoWords;
516 twoWords.chrg.cpMin = (LONG)textrange.chrg.cpMin;
517 twoWords.chrg.cpMax = (LONG)Call(SCI_WORDENDPOSITION, textrange.chrg.cpMax + 1, TRUE);
518 twoWords.lpstrText = new char[twoWords.chrg.cpMax - twoWords.chrg.cpMin + 1];
519 SecureZeroMemory(twoWords.lpstrText, twoWords.chrg.cpMax - twoWords.chrg.cpMin + 1);
520 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&twoWords);
521 CString sWord = StringFromControl(twoWords.lpstrText);
522 delete [] twoWords.lpstrText;
523 if (m_autolist.find(sWord) != m_autolist.end())
525 //mark word as correct (remove the squiggle line)
526 Call(SCI_STARTSTYLING, twoWords.chrg.cpMin, INDICS_MASK);
527 Call(SCI_SETSTYLING, twoWords.chrg.cpMax - twoWords.chrg.cpMin, 0);
528 textrange.chrg.cpMax = twoWords.chrg.cpMax;
529 delete [] textbuffer;
530 continue;
533 if (len)
534 textrange.lpstrText[len - 1] = 0;
535 textrange.chrg.cpMax--;
536 if (strlen(textrange.lpstrText) > 0)
538 CString sWord = StringFromControl(textrange.lpstrText);
539 if ((GetStyleAt(textrange.chrg.cpMin) != STYLE_URL) && IsMisspelled(sWord))
541 //mark word as misspelled
542 Call(SCI_STARTSTYLING, textrange.chrg.cpMin, INDICS_MASK);
543 Call(SCI_SETSTYLING, textrange.chrg.cpMax - textrange.chrg.cpMin, INDIC1_MASK);
545 else
547 //mark word as correct (remove the squiggle line)
548 Call(SCI_STARTSTYLING, textrange.chrg.cpMin, INDICS_MASK);
549 Call(SCI_SETSTYLING, textrange.chrg.cpMax - textrange.chrg.cpMin, 0);
552 delete [] textbuffer;
556 void CSciEdit::SuggestSpellingAlternatives()
558 if (pChecker == NULL)
559 return;
560 CString word = GetWordUnderCursor(true);
561 Call(SCI_SETCURRENTPOS, Call(SCI_WORDSTARTPOSITION, Call(SCI_GETCURRENTPOS), TRUE));
562 if (word.IsEmpty())
563 return;
564 char ** wlst;
565 int ns = pChecker->suggest(&wlst, CStringA(word));
566 if (ns > 0)
568 CString suggestions;
569 for (int i=0; i < ns; i++)
571 suggestions += CString(wlst[i]) + m_separator;
572 free(wlst[i]);
574 free(wlst);
575 suggestions.TrimRight(m_separator);
576 if (suggestions.IsEmpty())
577 return;
578 Call(SCI_AUTOCSETSEPARATOR, (WPARAM)CStringA(m_separator).GetAt(0));
579 Call(SCI_AUTOCSETDROPRESTOFWORD, 1);
580 Call(SCI_AUTOCSHOW, 0, (LPARAM)(LPCSTR)StringForControl(suggestions));
585 void CSciEdit::DoAutoCompletion(int nMinPrefixLength)
587 if (m_autolist.empty())
588 return;
589 if (Call(SCI_AUTOCACTIVE))
590 return;
591 CString word = GetWordUnderCursor();
592 if (word.GetLength() < nMinPrefixLength)
593 return; //don't auto complete yet, word is too short
594 int pos = (int)Call(SCI_GETCURRENTPOS);
595 if (pos != Call(SCI_WORDENDPOSITION, pos, TRUE))
596 return; //don't auto complete if we're not at the end of a word
597 CString sAutoCompleteList;
599 word.MakeUpper();
600 for (std::set<CString>::const_iterator lowerit = m_autolist.lower_bound(word);
601 lowerit != m_autolist.end(); ++lowerit)
603 int compare = word.CompareNoCase(lowerit->Left(word.GetLength()));
604 if (compare>0)
605 continue;
606 else if (compare == 0)
608 sAutoCompleteList += *lowerit + m_separator;
610 else
612 break;
615 sAutoCompleteList.TrimRight(m_separator);
616 if (sAutoCompleteList.IsEmpty())
617 return;
619 Call(SCI_AUTOCSETSEPARATOR, (WPARAM)CStringA(m_separator).GetAt(0));
620 Call(SCI_AUTOCSHOW, word.GetLength(), (LPARAM)(LPCSTR)StringForControl(sAutoCompleteList));
623 BOOL CSciEdit::OnChildNotify(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pLResult)
625 if (message != WM_NOTIFY)
626 return CWnd::OnChildNotify(message, wParam, lParam, pLResult);
628 LPNMHDR lpnmhdr = (LPNMHDR) lParam;
629 SCNotification * lpSCN = (SCNotification *)lParam;
631 if(lpnmhdr->hwndFrom==m_hWnd)
633 switch(lpnmhdr->code)
635 case SCN_CHARADDED:
637 if ((lpSCN->ch < 32)&&(lpSCN->ch != 13)&&(lpSCN->ch != 10))
638 Call(SCI_DELETEBACK);
639 else
641 DoAutoCompletion(m_nAutoCompleteMinChars);
643 return TRUE;
645 break;
646 case SCN_STYLENEEDED:
648 int startstylepos = (int)Call(SCI_GETENDSTYLED);
649 int endstylepos = ((SCNotification *)lpnmhdr)->position;
650 MarkEnteredBugID(startstylepos, endstylepos);
651 if (m_bDoStyle)
652 StyleEnteredText(startstylepos, endstylepos);
653 StyleURLs(startstylepos, endstylepos);
654 CheckSpelling();
655 WrapLines(startstylepos, endstylepos);
656 return TRUE;
658 break;
659 case SCN_HOTSPOTCLICK:
661 TEXTRANGEA textrange;
662 textrange.chrg.cpMin = lpSCN->position;
663 textrange.chrg.cpMax = lpSCN->position;
664 DWORD style = GetStyleAt(lpSCN->position);
665 while (GetStyleAt(textrange.chrg.cpMin - 1) == style)
666 --textrange.chrg.cpMin;
667 while (GetStyleAt(textrange.chrg.cpMax + 1) == style)
668 ++textrange.chrg.cpMax;
669 ++textrange.chrg.cpMax;
670 char * textbuffer = new char[textrange.chrg.cpMax - textrange.chrg.cpMin + 1];
671 textrange.lpstrText = textbuffer;
672 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
673 CString url;
674 if (style == STYLE_URL)
675 url = StringFromControl(textbuffer);
676 else
678 url = m_sUrl;
679 url.Replace(_T("%BUGID%"), StringFromControl(textbuffer));
681 delete [] textbuffer;
682 if (!url.IsEmpty())
683 ShellExecute(GetParent()->GetSafeHwnd(), _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
685 break;
688 return CWnd::OnChildNotify(message, wParam, lParam, pLResult);
691 BEGIN_MESSAGE_MAP(CSciEdit, CWnd)
692 ON_WM_KEYDOWN()
693 ON_WM_CONTEXTMENU()
694 END_MESSAGE_MAP()
696 void CSciEdit::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
698 switch (nChar)
700 case (VK_ESCAPE):
702 if ((Call(SCI_AUTOCACTIVE)==0)&&(Call(SCI_CALLTIPACTIVE)==0))
703 ::SendMessage(GetParent()->GetSafeHwnd(), WM_CLOSE, 0, 0);
705 break;
707 CWnd::OnKeyDown(nChar, nRepCnt, nFlags);
710 BOOL CSciEdit::PreTranslateMessage(MSG* pMsg)
712 if (pMsg->message == WM_KEYDOWN)
714 switch (pMsg->wParam)
716 case VK_SPACE:
718 if (GetKeyState(VK_CONTROL) & 0x8000)
720 DoAutoCompletion(1);
721 return TRUE;
724 break;
725 case VK_TAB:
726 // The TAB cannot be handled in OnKeyDown because it is too late by then.
728 if (GetKeyState(VK_CONTROL)&0x8000)
730 //Ctrl-Tab was pressed, this means we should provide the user with
731 //a list of possible spell checking alternatives to the word under
732 //the cursor
733 SuggestSpellingAlternatives();
734 return TRUE;
736 else if (!Call(SCI_AUTOCACTIVE))
738 ::PostMessage(GetParent()->GetSafeHwnd(), WM_NEXTDLGCTL, GetKeyState(VK_SHIFT)&0x8000, 0);
739 return TRUE;
742 break;
745 return CWnd::PreTranslateMessage(pMsg);
748 void CSciEdit::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
750 int anchor = (int)Call(SCI_GETANCHOR);
751 int currentpos = (int)Call(SCI_GETCURRENTPOS);
752 int selstart = (int)Call(SCI_GETSELECTIONSTART);
753 int selend = (int)Call(SCI_GETSELECTIONEND);
754 int pointpos = 0;
755 if ((point.x == -1) && (point.y == -1))
757 CRect rect;
758 GetClientRect(&rect);
759 ClientToScreen(&rect);
760 point = rect.CenterPoint();
761 pointpos = (int)Call(SCI_GETCURRENTPOS);
763 else
765 // change the cursor position to the point where the user
766 // right-clicked.
767 CPoint clientpoint = point;
768 ScreenToClient(&clientpoint);
769 pointpos = (int)Call(SCI_POSITIONFROMPOINT, clientpoint.x, clientpoint.y);
771 CString sMenuItemText;
772 CMenu popup;
773 bool bRestoreCursor = true;
774 if (popup.CreatePopupMenu())
776 bool bCanUndo = !!Call(SCI_CANUNDO);
777 bool bCanRedo = !!Call(SCI_CANREDO);
778 bool bHasSelection = (selend-selstart > 0);
779 bool bCanPaste = !!Call(SCI_CANPASTE);
780 bool bIsReadOnly = !!Call(SCI_GETREADONLY);
781 UINT uEnabledMenu = MF_STRING | MF_ENABLED;
782 UINT uDisabledMenu = MF_STRING | MF_GRAYED;
784 // find the word under the cursor
785 CString sWord;
786 if (pointpos)
788 // setting the cursor clears the selection
789 Call(SCI_SETANCHOR, pointpos);
790 Call(SCI_SETCURRENTPOS, pointpos);
791 sWord = GetWordUnderCursor();
792 // restore the selection
793 Call(SCI_SETSELECTIONSTART, selstart);
794 Call(SCI_SETSELECTIONEND, selend);
796 else
797 sWord = GetWordUnderCursor();
798 CStringA worda = CStringA(sWord);
800 int nCorrections = 1;
801 bool bSpellAdded = false;
802 // check if the word under the cursor is spelled wrong
803 if ((pChecker)&&(!worda.IsEmpty()) && !bIsReadOnly)
805 char ** wlst;
806 // get the spell suggestions
807 int ns = pChecker->suggest(&wlst,worda);
808 if (ns > 0)
810 // add the suggestions to the context menu
811 for (int i=0; i < ns; i++)
813 bSpellAdded = true;
814 CString sug = CString(wlst[i]);
815 popup.InsertMenu((UINT)-1, 0, nCorrections++, sug);
816 free(wlst[i]);
818 free(wlst);
821 // only add a separator if spelling correction suggestions were added
822 if (bSpellAdded)
823 popup.AppendMenu(MF_SEPARATOR);
825 // also allow the user to add the word to the custom dictionary so
826 // it won't show up as misspelled anymore
827 if ((sWord.GetLength()<PDICT_MAX_WORD_LENGTH)&&((pChecker)&&(m_autolist.find(sWord) == m_autolist.end())&&(!pChecker->spell(worda)))&&
828 (!_istdigit(sWord.GetAt(0)))&&(!m_personalDict.FindWord(sWord)) && !bIsReadOnly)
830 sMenuItemText.Format(IDS_SCIEDIT_ADDWORD, sWord);
831 popup.AppendMenu(uEnabledMenu, SCI_ADDWORD, sMenuItemText);
832 // another separator
833 popup.AppendMenu(MF_SEPARATOR);
836 // add the 'default' entries
837 sMenuItemText.LoadString(IDS_SCIEDIT_UNDO);
838 popup.AppendMenu(bCanUndo ? uEnabledMenu : uDisabledMenu, SCI_UNDO, sMenuItemText);
839 sMenuItemText.LoadString(IDS_SCIEDIT_REDO);
840 popup.AppendMenu(bCanRedo ? uEnabledMenu : uDisabledMenu, SCI_REDO, sMenuItemText);
842 popup.AppendMenu(MF_SEPARATOR);
844 sMenuItemText.LoadString(IDS_SCIEDIT_CUT);
845 popup.AppendMenu(bHasSelection ? uEnabledMenu : uDisabledMenu, SCI_CUT, sMenuItemText);
846 sMenuItemText.LoadString(IDS_SCIEDIT_COPY);
847 popup.AppendMenu(bHasSelection ? uEnabledMenu : uDisabledMenu, SCI_COPY, sMenuItemText);
848 sMenuItemText.LoadString(IDS_SCIEDIT_PASTE);
849 popup.AppendMenu(bCanPaste ? uEnabledMenu : uDisabledMenu, SCI_PASTE, sMenuItemText);
851 popup.AppendMenu(MF_SEPARATOR);
853 sMenuItemText.LoadString(IDS_SCIEDIT_SELECTALL);
854 popup.AppendMenu(uEnabledMenu, SCI_SELECTALL, sMenuItemText);
856 popup.AppendMenu(MF_SEPARATOR);
858 sMenuItemText.LoadString(IDS_SCIEDIT_SPLITLINES);
859 popup.AppendMenu(bHasSelection ? uEnabledMenu : uDisabledMenu, SCI_LINESSPLIT, sMenuItemText);
861 popup.AppendMenu(MF_SEPARATOR);
863 int nCustoms = nCorrections;
864 // now add any custom context menus
865 for (INT_PTR handlerindex = 0; handlerindex < m_arContextHandlers.GetCount(); ++handlerindex)
867 CSciEditContextMenuInterface * pHandler = m_arContextHandlers.GetAt(handlerindex);
868 pHandler->InsertMenuItems(popup, nCustoms);
870 if (nCustoms > nCorrections)
872 // custom menu entries present, so add another separator
873 popup.AppendMenu(MF_SEPARATOR);
876 #if THESAURUS
877 // add found thesauri to sub menu's
878 CMenu thesaurs;
879 int nThesaurs = 0;
880 CPtrArray menuArray;
881 if (thesaurs.CreatePopupMenu())
883 if ((pThesaur)&&(!worda.IsEmpty()))
885 mentry * pmean;
886 worda.MakeLower();
887 int count = pThesaur->Lookup(worda, worda.GetLength(),&pmean);
888 if (count)
890 mentry * pm = pmean;
891 for (int i=0; i < count; i++)
893 CMenu * submenu = new CMenu();
894 menuArray.Add(submenu);
895 submenu->CreateMenu();
896 for (int j=0; j < pm->count; j++)
898 CString sug = CString(pm->psyns[j]);
899 submenu->InsertMenu((UINT)-1, 0, nThesaurs++, sug);
901 thesaurs.InsertMenu((UINT)-1, MF_POPUP, (UINT_PTR)(submenu->m_hMenu), CString(pm->defn));
902 pm++;
905 if ((count > 0)&&(point.x >= 0))
907 #ifdef IDS_SPELLEDIT_THESAURUS
908 sMenuItemText.LoadString(IDS_SPELLEDIT_THESAURUS);
909 popup.InsertMenu((UINT)-1, MF_POPUP, (UINT_PTR)thesaurs.m_hMenu, sMenuItemText);
910 #else
911 popup.InsertMenu((UINT)-1, MF_POPUP, (UINT_PTR)thesaurs.m_hMenu, _T("Thesaurus"));
912 #endif
913 nThesaurs = nCustoms;
915 else
917 sMenuItemText.LoadString(IDS_SPELLEDIT_NOTHESAURUS);
918 popup.AppendMenu(MF_DISABLED | MF_GRAYED | MF_STRING, 0, sMenuItemText);
921 pThesaur->CleanUpAfterLookup(&pmean, count);
923 else
925 sMenuItemText.LoadString(IDS_SPELLEDIT_NOTHESAURUS);
926 popup.AppendMenu(MF_DISABLED | MF_GRAYED | MF_STRING, 0, sMenuItemText);
929 #endif
930 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
931 switch (cmd)
933 case 0:
934 break; // no command selected
935 case SCI_SELECTALL:
936 bRestoreCursor = false;
937 // fall through
938 case SCI_UNDO:
939 case SCI_REDO:
940 case SCI_CUT:
941 case SCI_COPY:
942 case SCI_PASTE:
943 Call(cmd);
944 break;
945 case SCI_ADDWORD:
946 m_personalDict.AddWord(sWord);
947 CheckSpelling();
948 break;
949 case SCI_LINESSPLIT:
951 int marker = (int)(Call(SCI_GETEDGECOLUMN) * Call(SCI_TEXTWIDTH, 0, (LPARAM)" "));
952 if (marker)
954 Call(SCI_TARGETFROMSELECTION);
955 Call(SCI_LINESJOIN);
956 Call(SCI_LINESSPLIT, marker);
959 break;
960 default:
961 if (cmd < nCorrections)
963 Call(SCI_SETANCHOR, pointpos);
964 Call(SCI_SETCURRENTPOS, pointpos);
965 GetWordUnderCursor(true);
966 CString temp;
967 popup.GetMenuString(cmd, temp, 0);
968 // setting the cursor clears the selection
969 Call(SCI_REPLACESEL, 0, (LPARAM)(LPCSTR)StringForControl(temp));
971 else if (cmd < (nCorrections+nCustoms))
973 for (INT_PTR handlerindex = 0; handlerindex < m_arContextHandlers.GetCount(); ++handlerindex)
975 CSciEditContextMenuInterface * pHandler = m_arContextHandlers.GetAt(handlerindex);
976 if (pHandler->HandleMenuItemClick(cmd, this))
977 break;
980 #if THESAURUS
981 else if (cmd <= (nThesaurs+nCorrections+nCustoms))
983 Call(SCI_SETANCHOR, pointpos);
984 Call(SCI_SETCURRENTPOS, pointpos);
985 GetWordUnderCursor(true);
986 CString temp;
987 thesaurs.GetMenuString(cmd, temp, 0);
988 Call(SCI_REPLACESEL, 0, (LPARAM)(LPCSTR)StringForControl(temp));
990 #endif
992 #ifdef THESAURUS
993 for (INT_PTR index = 0; index < menuArray.GetCount(); ++index)
995 CMenu * pMenu = (CMenu*)menuArray[index];
996 delete pMenu;
998 #endif
1000 if (bRestoreCursor)
1002 // restore the anchor and cursor position
1003 Call(SCI_SETCURRENTPOS, currentpos);
1004 Call(SCI_SETANCHOR, anchor);
1008 bool CSciEdit::StyleEnteredText(int startstylepos, int endstylepos)
1010 bool bStyled = false;
1011 const int line = (int)Call(SCI_LINEFROMPOSITION, startstylepos);
1012 const int line_number_end = (int)Call(SCI_LINEFROMPOSITION, endstylepos);
1013 for (int line_number = line; line_number <= line_number_end; ++line_number)
1015 int offset = (int)Call(SCI_POSITIONFROMLINE, line_number);
1016 int line_len = (int)Call(SCI_LINELENGTH, line_number);
1017 char * linebuffer = new char[line_len+1];
1018 Call(SCI_GETLINE, line_number, (LPARAM)linebuffer);
1019 linebuffer[line_len] = 0;
1020 int start = 0;
1021 int end = 0;
1022 while (FindStyleChars(linebuffer, '*', start, end))
1024 Call(SCI_STARTSTYLING, start+offset, STYLE_MASK);
1025 Call(SCI_SETSTYLING, end-start, STYLE_BOLD);
1026 bStyled = true;
1027 start = end;
1029 start = 0;
1030 end = 0;
1031 while (FindStyleChars(linebuffer, '^', start, end))
1033 Call(SCI_STARTSTYLING, start+offset, STYLE_MASK);
1034 Call(SCI_SETSTYLING, end-start, STYLE_ITALIC);
1035 bStyled = true;
1036 start = end;
1038 start = 0;
1039 end = 0;
1040 while (FindStyleChars(linebuffer, '_', start, end))
1042 Call(SCI_STARTSTYLING, start+offset, STYLE_MASK);
1043 Call(SCI_SETSTYLING, end-start, STYLE_UNDERLINED);
1044 bStyled = true;
1045 start = end;
1047 delete [] linebuffer;
1049 return bStyled;
1052 bool CSciEdit::WrapLines(int startpos, int endpos)
1054 int markerX = (int)(Call(SCI_GETEDGECOLUMN) * Call(SCI_TEXTWIDTH, 0, (LPARAM)" "));
1055 if (markerX)
1057 Call(SCI_SETTARGETSTART, startpos);
1058 Call(SCI_SETTARGETEND, endpos);
1059 Call(SCI_LINESSPLIT, markerX);
1060 return true;
1062 return false;
1065 void CSciEdit::AdvanceUTF8(const char * str, int& pos)
1067 if ((str[pos] & 0xE0)==0xC0)
1069 // utf8 2-byte sequence
1070 pos += 2;
1072 else if ((str[pos] & 0xF0)==0xE0)
1074 // utf8 3-byte sequence
1075 pos += 3;
1077 else if ((str[pos] & 0xF8)==0xF0)
1079 // utf8 4-byte sequence
1080 pos += 4;
1082 else
1083 pos++;
1086 bool CSciEdit::FindStyleChars(const char * line, char styler, int& start, int& end)
1088 int i=0;
1089 int u=0;
1090 while (i < start)
1092 AdvanceUTF8(line, i);
1093 u++;
1096 bool bFoundMarker = false;
1097 CString sULine = CUnicodeUtils::GetUnicode(line);
1098 // find a starting marker
1099 while (line[i] != 0)
1101 if (line[i] == styler)
1103 if ((line[i+1]!=0)&&(IsCharAlphaNumeric(sULine[u+1]))&&
1104 (((u>0)&&(!IsCharAlphaNumeric(sULine[u-1]))) || (u==0)))
1106 start = i+1;
1107 AdvanceUTF8(line, i);
1108 u++;
1109 bFoundMarker = true;
1110 break;
1113 AdvanceUTF8(line, i);
1114 u++;
1116 if (!bFoundMarker)
1117 return false;
1118 // find ending marker
1119 bFoundMarker = false;
1120 while (line[i] != 0)
1122 if (line[i] == styler)
1124 if ((IsCharAlphaNumeric(sULine[u-1]))&&
1125 ((((u+1)<sULine.GetLength())&&(!IsCharAlphaNumeric(sULine[u+1]))) || ((u+1) == sULine.GetLength()))
1128 end = i;
1129 i++;
1130 bFoundMarker = true;
1131 break;
1134 AdvanceUTF8(line, i);
1135 u++;
1137 return bFoundMarker;
1140 BOOL CSciEdit::MarkEnteredBugID(int startstylepos, int endstylepos)
1142 if (m_sCommand.IsEmpty())
1143 return FALSE;
1144 // get the text between the start and end position we have to style
1145 const int line_number = (int)Call(SCI_LINEFROMPOSITION, startstylepos);
1146 int start_pos = (int)Call(SCI_POSITIONFROMLINE, (WPARAM)line_number);
1147 int end_pos = endstylepos;
1149 if (start_pos == end_pos)
1150 return FALSE;
1151 if (start_pos > end_pos)
1153 int switchtemp = start_pos;
1154 start_pos = end_pos;
1155 end_pos = switchtemp;
1158 char * textbuffer = new char[end_pos - start_pos + 2];
1159 TEXTRANGEA textrange;
1160 textrange.lpstrText = textbuffer;
1161 textrange.chrg.cpMin = start_pos;
1162 textrange.chrg.cpMax = end_pos;
1163 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
1164 CStringA msg = CStringA(textbuffer);
1166 Call(SCI_STARTSTYLING, start_pos, STYLE_MASK);
1168 if (!m_sBugID.IsEmpty())
1170 // match with two regex strings (without grouping!)
1173 const tr1::regex regCheck(m_sCommand);
1174 const tr1::regex regBugID(m_sBugID);
1175 const tr1::sregex_iterator end;
1176 string s = msg;
1177 LONG pos = 0;
1178 for (tr1::sregex_iterator it(s.begin(), s.end(), regCheck); it != end; ++it)
1180 // clear the styles up to the match position
1181 Call(SCI_SETSTYLING, it->position(0)-pos, STYLE_DEFAULT);
1182 pos = (LONG)it->position(0);
1184 // (*it)[0] is the matched string
1185 string matchedString = (*it)[0];
1186 for (tr1::sregex_iterator it2(matchedString.begin(), matchedString.end(), regBugID); it2 != end; ++it2)
1188 ATLTRACE(_T("matched id : %s\n"), (*it2)[0].str().c_str());
1190 // bold style up to the id match
1191 ATLTRACE("position = %ld\n", it2->position(0));
1192 if (it2->position(0))
1193 Call(SCI_SETSTYLING, it2->position(0), STYLE_ISSUEBOLD);
1194 // bold and recursive style for the bug ID itself
1195 if ((*it2)[0].str().size())
1196 Call(SCI_SETSTYLING, (*it2)[0].str().size(), STYLE_ISSUEBOLDITALIC);
1198 pos = (LONG)(it->position(0) + matchedString.size());
1200 // bold style for the rest of the string which isn't matched
1201 if (s.size()-pos)
1202 Call(SCI_SETSTYLING, s.size()-pos, STYLE_DEFAULT);
1204 catch (exception) {}
1206 else
1210 const tr1::regex regCheck(m_sCommand);
1211 const tr1::sregex_iterator end;
1212 string s = msg;
1213 LONG pos = 0;
1214 for (tr1::sregex_iterator it(s.begin(), s.end(), regCheck); it != end; ++it)
1216 // clear the styles up to the match position
1217 Call(SCI_SETSTYLING, it->position(0)-pos, STYLE_DEFAULT);
1218 pos = (LONG)it->position(0);
1220 const tr1::smatch match = *it;
1221 // we define group 1 as the whole issue text and
1222 // group 2 as the bug ID
1223 if (match.size() >= 2)
1225 ATLTRACE(_T("matched id : %s\n"), string(match[1]).c_str());
1226 Call(SCI_SETSTYLING, match[1].first-s.begin()-pos, STYLE_ISSUEBOLD);
1227 Call(SCI_SETSTYLING, string(match[1]).size(), STYLE_ISSUEBOLDITALIC);
1228 pos = (LONG)(match[1].second-s.begin());
1232 catch (exception) {}
1234 delete [] textbuffer;
1236 return FALSE;
1239 bool CSciEdit::IsValidURLChar(unsigned char ch)
1241 return isalnum(ch) ||
1242 ch == '_' || ch == '/' || ch == ';' || ch == '?' || ch == '&' || ch == '=' ||
1243 ch == '%' || ch == ':' || ch == '.' || ch == '#' || ch == '-' || ch == '+';
1246 void CSciEdit::StyleURLs(int startstylepos, int endstylepos)
1248 const int line_number = (int)Call(SCI_LINEFROMPOSITION, startstylepos);
1249 startstylepos = (int)Call(SCI_POSITIONFROMLINE, (WPARAM)line_number);
1251 int len = endstylepos - startstylepos + 1;
1252 char* textbuffer = new char[len + 1];
1253 TEXTRANGEA textrange;
1254 textrange.lpstrText = textbuffer;
1255 textrange.chrg.cpMin = startstylepos;
1256 textrange.chrg.cpMax = endstylepos;
1257 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
1258 // we're dealing with utf8 encoded text here, which means one glyph is
1259 // not necessarily one byte/wchar_t
1260 // that's why we use CStringA to still get a correct char index
1261 CStringA msg = textbuffer;
1262 delete [] textbuffer;
1264 int starturl = -1;
1265 for(int i = 0; i <= msg.GetLength(); )
1267 if ((i < len) && IsValidURLChar(msg[i]))
1269 if (starturl < 0)
1270 starturl = i;
1272 else
1274 if ((starturl >= 0) && IsUrl(msg.Mid(starturl, i - starturl)))
1276 ASSERT(startstylepos + i <= endstylepos);
1277 Call(SCI_STARTSTYLING, startstylepos + starturl, STYLE_MASK);
1278 Call(SCI_SETSTYLING, i - starturl, STYLE_URL);
1280 starturl = -1;
1282 AdvanceUTF8(msg, i);
1286 bool CSciEdit::IsUrl(const CStringA& sText)
1288 if (!PathIsURLA(sText))
1289 return false;
1290 if (sText.Find("://")>=0)
1291 return true;
1292 return false;
1295 bool CSciEdit::IsUTF8(LPVOID pBuffer, size_t cb)
1297 if (cb < 2)
1298 return true;
1299 UINT16 * pVal = (UINT16 *)pBuffer;
1300 UINT8 * pVal2 = (UINT8 *)(pVal+1);
1301 // scan the whole buffer for a 0x0000 sequence
1302 // if found, we assume a binary file
1303 for (size_t i=0; i<(cb-2); i=i+2)
1305 if (0x0000 == *pVal++)
1306 return false;
1308 pVal = (UINT16 *)pBuffer;
1309 if (*pVal == 0xFEFF)
1310 return false;
1311 if (cb < 3)
1312 return false;
1313 if (*pVal == 0xBBEF)
1315 if (*pVal2 == 0xBF)
1316 return true;
1318 // check for illegal UTF8 chars
1319 pVal2 = (UINT8 *)pBuffer;
1320 for (size_t i=0; i<cb; ++i)
1322 if ((*pVal2 == 0xC0)||(*pVal2 == 0xC1)||(*pVal2 >= 0xF5))
1323 return false;
1324 pVal2++;
1326 pVal2 = (UINT8 *)pBuffer;
1327 bool bUTF8 = false;
1328 for (size_t i=0; i<(cb-3); ++i)
1330 if ((*pVal2 & 0xE0)==0xC0)
1332 pVal2++;i++;
1333 if ((*pVal2 & 0xC0)!=0x80)
1334 return false;
1335 bUTF8 = true;
1337 if ((*pVal2 & 0xF0)==0xE0)
1339 pVal2++;i++;
1340 if ((*pVal2 & 0xC0)!=0x80)
1341 return false;
1342 pVal2++;i++;
1343 if ((*pVal2 & 0xC0)!=0x80)
1344 return false;
1345 bUTF8 = true;
1347 if ((*pVal2 & 0xF8)==0xF0)
1349 pVal2++;i++;
1350 if ((*pVal2 & 0xC0)!=0x80)
1351 return false;
1352 pVal2++;i++;
1353 if ((*pVal2 & 0xC0)!=0x80)
1354 return false;
1355 pVal2++;i++;
1356 if ((*pVal2 & 0xC0)!=0x80)
1357 return false;
1358 bUTF8 = true;
1360 pVal2++;
1362 if (bUTF8)
1363 return true;
1364 return false;
1367 void CSciEdit::SetAStyle(int style, COLORREF fore, COLORREF back, int size, const char *face)
1369 Call(SCI_STYLESETFORE, style, fore);
1370 Call(SCI_STYLESETBACK, style, back);
1371 if (size >= 1)
1372 Call(SCI_STYLESETSIZE, style, size);
1373 if (face)
1374 Call(SCI_STYLESETFONT, style, reinterpret_cast<LPARAM>(face));
1378 void CSciEdit::SetUDiffStyle()
1380 SetAStyle(STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOWTEXT), ::GetSysColor(COLOR_WINDOW),
1381 // Reusing TortoiseBlame's setting which already have an user friendly
1382 // pane in TortoiseSVN's Settings dialog, while there is no such
1383 // pane for TortoiseUDiff.
1384 CRegStdDWORD(_T("Software\\TortoiseGit\\BlameFontSize"), 10),
1385 WideToMultibyte(CRegStdString(_T("Software\\TortoiseGit\\BlameFontName"), _T("Courier New"))).c_str());
1387 Call(SCI_SETTABWIDTH, 4);
1388 Call(SCI_SETREADONLY, TRUE);
1389 //LRESULT pix = Call(SCI_TEXTWIDTH, STYLE_LINENUMBER, (LPARAM)"_99999");
1390 //Call(SCI_SETMARGINWIDTHN, 0, pix);
1391 //Call(SCI_SETMARGINWIDTHN, 1);
1392 //Call(SCI_SETMARGINWIDTHN, 2);
1393 //Set the default windows colors for edit controls
1394 Call(SCI_STYLESETFORE, STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOWTEXT));
1395 Call(SCI_STYLESETBACK, STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOW));
1396 Call(SCI_SETSELFORE, TRUE, ::GetSysColor(COLOR_HIGHLIGHTTEXT));
1397 Call(SCI_SETSELBACK, TRUE, ::GetSysColor(COLOR_HIGHLIGHT));
1398 Call(SCI_SETCARETFORE, ::GetSysColor(COLOR_WINDOWTEXT));
1400 //SendEditor(SCI_SETREADONLY, FALSE);
1401 Call(SCI_CLEARALL);
1402 Call(EM_EMPTYUNDOBUFFER);
1403 Call(SCI_SETSAVEPOINT);
1404 Call(SCI_CANCEL);
1405 Call(SCI_SETUNDOCOLLECTION, 0);
1407 Call(SCI_SETUNDOCOLLECTION, 1);
1408 Call(SCI_SETWRAPMODE,SC_WRAP_NONE);
1410 //::SetFocus(m_hWndEdit);
1411 Call(EM_EMPTYUNDOBUFFER);
1412 Call(SCI_SETSAVEPOINT);
1413 Call(SCI_GOTOPOS, 0);
1415 Call(SCI_CLEARDOCUMENTSTYLE, 0, 0);
1416 Call(SCI_SETSTYLEBITS, 5, 0);
1418 //SetAStyle(SCE_DIFF_DEFAULT, RGB(0, 0, 0));
1419 SetAStyle(SCE_DIFF_COMMAND, RGB(0x0A, 0x24, 0x36));
1420 SetAStyle(SCE_DIFF_POSITION, RGB(0xFF, 0, 0));
1421 SetAStyle(SCE_DIFF_HEADER, RGB(0x80, 0, 0), RGB(0xFF, 0xFF, 0x80));
1422 SetAStyle(SCE_DIFF_COMMENT, RGB(0, 0x80, 0));
1423 Call(SCI_STYLESETBOLD, SCE_DIFF_COMMENT, TRUE);
1424 SetAStyle(SCE_DIFF_DELETED, ::GetSysColor(COLOR_WINDOWTEXT), RGB(0xFF, 0x80, 0x80));
1425 SetAStyle(SCE_DIFF_ADDED, ::GetSysColor(COLOR_WINDOWTEXT), RGB(0x80, 0xFF, 0x80));
1427 Call(SCI_SETLEXER, SCLEX_DIFF);
1428 Call(SCI_SETKEYWORDS, 0, (LPARAM)"revision");
1429 Call(SCI_COLOURISE, 0, -1);
1432 int CSciEdit::LoadFromFile(CString &filename)
1434 FILE *fp = NULL;
1435 _tfopen_s(&fp, filename, _T("rb"));
1436 if (fp)
1438 //SetTitle();
1439 char data[4096];
1440 size_t lenFile = fread(data, 1, sizeof(data), fp);
1441 bool bUTF8 = IsUTF8(data, lenFile);
1442 while (lenFile > 0)
1444 Call(SCI_ADDTEXT, lenFile,
1445 reinterpret_cast<LPARAM>(static_cast<char *>(data)));
1446 lenFile = fread(data, 1, sizeof(data), fp);
1448 fclose(fp);
1449 Call(SCI_SETCODEPAGE, bUTF8 ? SC_CP_UTF8 : GetACP());
1450 return 0;
1452 else
1453 return -1;