Merge branch 'bare-repo'
[TortoiseGit.git] / src / Utils / MiscUI / SciEdit.cpp
blob70b3b2ee4023f05ca0831f7705bbf2eec1085fc7
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2008 - TortoiseSVN
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 #include "StdAfx.h"
20 #include "CommonResource.h"
21 //#include "AppUtils.h"
22 #include "..\PathUtils.h"
23 #include "..\UnicodeUtils.h"
24 #include <string>
25 #include "..\registry.h"
26 #include ".\sciedit.h"
28 using namespace std;
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"},
68 IMPLEMENT_DYNAMIC(CSciEdit, CWnd)
70 CSciEdit::CSciEdit(void) : m_DirectFunction(NULL)
71 , m_DirectPointer(NULL)
72 , pChecker(NULL)
73 , pThesaur(NULL)
75 m_hModule = ::LoadLibrary(_T("SciLexer.DLL"));
78 CSciEdit::~CSciEdit(void)
80 m_personalDict.Save();
81 if (m_hModule)
82 ::FreeLibrary(m_hModule);
83 if (pChecker)
84 delete pChecker;
85 if (pThesaur)
86 delete pThesaur;
89 void CSciEdit::Init(LONG lLanguage, BOOL bLoadSpellCheck)
91 //Setup the direct access data
92 m_DirectFunction = SendMessage(SCI_GETDIRECTFUNCTION, 0, 0);
93 m_DirectPointer = SendMessage(SCI_GETDIRECTPOINTER, 0, 0);
94 Call(SCI_SETMARGINWIDTHN, 1, 0);
95 Call(SCI_SETUSETABS, 0); //pressing TAB inserts spaces
96 Call(SCI_SETWRAPVISUALFLAGS, SC_WRAPVISUALFLAG_END);
97 Call(SCI_AUTOCSETIGNORECASE, 1);
98 Call(SCI_SETLEXER, SCLEX_CONTAINER);
99 Call(SCI_SETCODEPAGE, SC_CP_UTF8);
100 Call(SCI_AUTOCSETFILLUPS, 0, (LPARAM)"\t([");
101 Call(SCI_AUTOCSETMAXWIDTH, 0);
102 //Set the default windows colors for edit controls
103 Call(SCI_STYLESETFORE, STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOWTEXT));
104 Call(SCI_STYLESETBACK, STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOW));
105 Call(SCI_SETSELFORE, TRUE, ::GetSysColor(COLOR_HIGHLIGHTTEXT));
106 Call(SCI_SETSELBACK, TRUE, ::GetSysColor(COLOR_HIGHLIGHT));
107 Call(SCI_SETCARETFORE, ::GetSysColor(COLOR_WINDOWTEXT));
108 Call(SCI_SETMODEVENTMASK, SC_MOD_INSERTTEXT | SC_MOD_DELETETEXT | SC_PERFORMED_UNDO | SC_PERFORMED_REDO);
109 Call(SCI_INDICSETFORE, 1, 0x0000FF);
110 CStringA sWordChars;
111 CStringA sWhiteSpace;
112 for (int i=0; i<255; ++i)
114 if (i == '\r' || i == '\n')
115 continue;
116 else if (i < 0x20 || i == ' ')
117 sWhiteSpace += (char)i;
118 else if (isalnum(i) || i == '\'')
119 sWordChars += (char)i;
121 Call(SCI_SETWORDCHARS, 0, (LPARAM)(LPCSTR)sWordChars);
122 Call(SCI_SETWHITESPACECHARS, 0, (LPARAM)(LPCSTR)sWhiteSpace);
123 // look for dictionary files and use them if found
124 long langId = GetUserDefaultLCID();
126 if(bLoadSpellCheck)
128 if ((lLanguage != 0)||(((DWORD)CRegStdDWORD(_T("Software\\TortoiseGit\\Spellchecker"), FALSE))==FALSE))
130 if (!((lLanguage)&&(!LoadDictionaries(lLanguage))))
134 LoadDictionaries(langId);
135 DWORD lid = SUBLANGID(langId);
136 lid--;
137 if (lid > 0)
139 langId = MAKELANGID(PRIMARYLANGID(langId), lid);
141 else if (langId == 1033)
142 langId = 0;
143 else
144 langId = 1033;
145 } while ((langId)&&((pChecker==NULL)||(pThesaur==NULL)));
149 Call(SCI_SETEDGEMODE, EDGE_NONE);
150 Call(SCI_SETWRAPMODE, SC_WRAP_WORD);
151 Call(SCI_ASSIGNCMDKEY, SCK_END, SCI_LINEENDWRAP);
152 Call(SCI_ASSIGNCMDKEY, SCK_END + (SCMOD_SHIFT << 16), SCI_LINEENDWRAPEXTEND);
153 Call(SCI_ASSIGNCMDKEY, SCK_HOME, SCI_HOMEWRAP);
154 Call(SCI_ASSIGNCMDKEY, SCK_HOME + (SCMOD_SHIFT << 16), SCI_HOMEWRAPEXTEND);
158 void CSciEdit::Init(const ProjectProperties& props)
160 Init(props.lProjectLanguage);
161 m_sCommand = CStringA(CUnicodeUtils::GetUTF8(props.sCheckRe));
162 m_sBugID = CStringA(CUnicodeUtils::GetUTF8(props.sBugIDRe));
163 m_sUrl = CStringA(CUnicodeUtils::GetUTF8(props.sUrl));
165 if (props.nLogWidthMarker)
167 Call(SCI_SETWRAPMODE, SC_WRAP_NONE);
168 Call(SCI_SETEDGEMODE, EDGE_LINE);
169 Call(SCI_SETEDGECOLUMN, props.nLogWidthMarker);
171 else
173 Call(SCI_SETEDGEMODE, EDGE_NONE);
174 Call(SCI_SETWRAPMODE, SC_WRAP_WORD);
176 SetText(props.sLogTemplate);
179 BOOL CSciEdit::LoadDictionaries(LONG lLanguageID)
181 //Setup the spell checker and thesaurus
182 TCHAR buf[6];
183 CString sFolder = CPathUtils::GetAppDirectory();
184 CString sFolderUp = CPathUtils::GetAppParentDirectory();
185 CString sFile;
187 GetLocaleInfo(MAKELCID(lLanguageID, SORT_DEFAULT), LOCALE_SISO639LANGNAME, buf, _countof(buf));
188 sFile = buf;
189 sFile += _T("_");
190 GetLocaleInfo(MAKELCID(lLanguageID, SORT_DEFAULT), LOCALE_SISO3166CTRYNAME, buf, _countof(buf));
191 sFile += buf;
192 if (pChecker==NULL)
194 if ((PathFileExists(sFolder + sFile + _T(".aff"))) &&
195 (PathFileExists(sFolder + sFile + _T(".dic"))))
197 pChecker = new Hunspell(CStringA(sFolder + sFile + _T(".aff")), CStringA(sFolder + sFile + _T(".dic")));
199 else if ((PathFileExists(sFolder + _T("dic\\") + sFile + _T(".aff"))) &&
200 (PathFileExists(sFolder + _T("dic\\") + sFile + _T(".dic"))))
202 pChecker = new Hunspell(CStringA(sFolder + _T("dic\\") + sFile + _T(".aff")), CStringA(sFolder + _T("dic\\") + sFile + _T(".dic")));
204 else if ((PathFileExists(sFolderUp + sFile + _T(".aff"))) &&
205 (PathFileExists(sFolderUp + sFile + _T(".dic"))))
207 pChecker = new Hunspell(CStringA(sFolderUp + sFile + _T(".aff")), CStringA(sFolderUp + sFile + _T(".dic")));
209 else if ((PathFileExists(sFolderUp + _T("dic\\") + sFile + _T(".aff"))) &&
210 (PathFileExists(sFolderUp + _T("dic\\") + sFile + _T(".dic"))))
212 pChecker = new Hunspell(CStringA(sFolderUp + _T("dic\\") + sFile + _T(".aff")), CStringA(sFolderUp + _T("dic\\") + sFile + _T(".dic")));
214 else if ((PathFileExists(sFolderUp + _T("Languages\\") + sFile + _T(".aff"))) &&
215 (PathFileExists(sFolderUp + _T("Languages\\") + sFile + _T(".dic"))))
217 pChecker = new Hunspell(CStringA(sFolderUp + _T("Languages\\") + sFile + _T(".aff")), CStringA(sFolderUp + _T("Languages\\") + sFile + _T(".dic")));
220 #if THESAURUS
221 if (pThesaur==NULL)
223 if ((PathFileExists(sFolder + _T("th_") + sFile + _T("_v2.idx"))) &&
224 (PathFileExists(sFolder + _T("th_") + sFile + _T("_v2.dat"))))
226 pThesaur = new MyThes(CStringA(sFolder + sFile + _T("_v2.idx")), CStringA(sFolder + sFile + _T("_v2.dat")));
228 else if ((PathFileExists(sFolder + _T("dic\\th_") + sFile + _T("_v2.idx"))) &&
229 (PathFileExists(sFolder + _T("dic\\th_") + sFile + _T("_v2.dat"))))
231 pThesaur = new MyThes(CStringA(sFolder + _T("dic\\") + sFile + _T("_v2.idx")), CStringA(sFolder + _T("dic\\") + sFile + _T("_v2.dat")));
233 else if ((PathFileExists(sFolderUp + _T("th_") + sFile + _T("_v2.idx"))) &&
234 (PathFileExists(sFolderUp + _T("th_") + sFile + _T("_v2.dat"))))
236 pThesaur = new MyThes(CStringA(sFolderUp + _T("th_") + sFile + _T("_v2.idx")), CStringA(sFolderUp + _T("th_") + sFile + _T("_v2.dat")));
238 else if ((PathFileExists(sFolderUp + _T("dic\\th_") + sFile + _T("_v2.idx"))) &&
239 (PathFileExists(sFolderUp + _T("dic\\th_") + sFile + _T("_v2.dat"))))
241 pThesaur = new MyThes(CStringA(sFolderUp + _T("dic\\th_") + sFile + _T("_v2.idx")), CStringA(sFolderUp + _T("dic\\th_") + sFile + _T("_v2.dat")));
243 else if ((PathFileExists(sFolderUp + _T("Languages\\th_") + sFile + _T("_v2.idx"))) &&
244 (PathFileExists(sFolderUp + _T("Languages\\th_") + sFile + _T("_v2.dat"))))
246 pThesaur = new MyThes(CStringA(sFolderUp + _T("Languages\\th_") + sFile + _T("_v2.idx")), CStringA(sFolderUp + _T("Languages\\th_") + sFile + _T("_v2.dat")));
249 #endif
250 if (pChecker)
252 const char * encoding = pChecker->get_dic_encoding();
253 ATLTRACE(encoding);
254 int n = _countof(enc2locale);
255 m_spellcodepage = 0;
256 for (int i = 0; i < n; i++)
258 if (strcmp(encoding,enc2locale[i].def_enc) == 0)
260 m_spellcodepage = atoi(enc2locale[i].cp);
263 m_personalDict.Init(lLanguageID);
265 if ((pThesaur)||(pChecker))
266 return TRUE;
267 return FALSE;
270 LRESULT CSciEdit::Call(UINT message, WPARAM wParam, LPARAM lParam)
272 ASSERT(::IsWindow(m_hWnd)); //Window must be valid
273 ASSERT(m_DirectFunction); //Direct function must be valid
274 return ((SciFnDirect) m_DirectFunction)(m_DirectPointer, message, wParam, lParam);
277 CString CSciEdit::StringFromControl(const CStringA& text)
279 CString sText;
280 #ifdef UNICODE
281 int codepage = Call(SCI_GETCODEPAGE);
282 int reslen = MultiByteToWideChar(codepage, 0, text, text.GetLength(), 0, 0);
283 MultiByteToWideChar(codepage, 0, text, text.GetLength(), sText.GetBuffer(reslen+1), reslen+1);
284 sText.ReleaseBuffer(reslen);
285 #else
286 sText = text;
287 #endif
288 return sText;
291 CStringA CSciEdit::StringForControl(const CString& text)
293 CStringA sTextA;
294 #ifdef UNICODE
295 int codepage = SendMessage(SCI_GETCODEPAGE);
296 int reslen = WideCharToMultiByte(codepage, 0, text, text.GetLength(), 0, 0, 0, 0);
297 WideCharToMultiByte(codepage, 0, text, text.GetLength(), sTextA.GetBuffer(reslen), reslen, 0, 0);
298 sTextA.ReleaseBuffer(reslen);
299 #else
300 sTextA = text;
301 #endif
302 ATLTRACE("string length %d\n", sTextA.GetLength());
303 return sTextA;
306 void CSciEdit::SetText(const CString& sText)
308 CStringA sTextA = StringForControl(sText);
309 Call(SCI_SETTEXT, 0, (LPARAM)(LPCSTR)sTextA);
311 // Scintilla seems to have problems with strings that
312 // aren't terminated by a newline char. Once that char
313 // is there, it can be removed without problems.
314 // So we add here a newline, then remove it again.
315 Call(SCI_DOCUMENTEND);
316 Call(SCI_NEWLINE);
317 Call(SCI_DELETEBACK);
320 void CSciEdit::InsertText(const CString& sText, bool bNewLine)
322 CStringA sTextA = StringForControl(sText);
323 Call(SCI_REPLACESEL, 0, (LPARAM)(LPCSTR)sTextA);
324 if (bNewLine)
325 Call(SCI_REPLACESEL, 0, (LPARAM)(LPCSTR)"\n");
328 CString CSciEdit::GetText()
330 LRESULT len = Call(SCI_GETTEXT, 0, 0);
331 CStringA sTextA;
332 Call(SCI_GETTEXT, len+1, (LPARAM)(LPCSTR)sTextA.GetBuffer(len+1));
333 sTextA.ReleaseBuffer();
334 return StringFromControl(sTextA);
337 CString CSciEdit::GetWordUnderCursor(bool bSelectWord)
339 TEXTRANGEA textrange;
340 int pos = Call(SCI_GETCURRENTPOS);
341 textrange.chrg.cpMin = Call(SCI_WORDSTARTPOSITION, pos, TRUE);
342 if ((pos == textrange.chrg.cpMin)||(textrange.chrg.cpMin < 0))
343 return CString();
344 textrange.chrg.cpMax = Call(SCI_WORDENDPOSITION, textrange.chrg.cpMin, TRUE);
346 char * textbuffer = new char[textrange.chrg.cpMax - textrange.chrg.cpMin + 1];
348 textrange.lpstrText = textbuffer;
349 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
350 if (bSelectWord)
352 Call(SCI_SETSEL, textrange.chrg.cpMin, textrange.chrg.cpMax);
354 CString sRet = StringFromControl(textbuffer);
355 delete [] textbuffer;
356 return sRet;
359 void CSciEdit::SetFont(CString sFontName, int iFontSizeInPoints)
361 Call(SCI_STYLESETFONT, STYLE_DEFAULT, (LPARAM)(LPCSTR)CStringA(sFontName));
362 Call(SCI_STYLESETSIZE, STYLE_DEFAULT, iFontSizeInPoints);
363 Call(SCI_STYLECLEARALL);
365 LPARAM color = (LPARAM)GetSysColor(COLOR_HIGHLIGHT);
366 // set the styles for the bug ID strings
367 Call(SCI_STYLESETBOLD, STYLE_ISSUEBOLD, (LPARAM)TRUE);
368 Call(SCI_STYLESETFORE, STYLE_ISSUEBOLD, color);
369 Call(SCI_STYLESETBOLD, STYLE_ISSUEBOLDITALIC, (LPARAM)TRUE);
370 Call(SCI_STYLESETITALIC, STYLE_ISSUEBOLDITALIC, (LPARAM)TRUE);
371 Call(SCI_STYLESETFORE, STYLE_ISSUEBOLDITALIC, color);
372 Call(SCI_STYLESETHOTSPOT, STYLE_ISSUEBOLDITALIC, (LPARAM)TRUE);
374 // set the formatted text styles
375 Call(SCI_STYLESETBOLD, STYLE_BOLD, (LPARAM)TRUE);
376 Call(SCI_STYLESETITALIC, STYLE_ITALIC, (LPARAM)TRUE);
377 Call(SCI_STYLESETUNDERLINE, STYLE_UNDERLINED, (LPARAM)TRUE);
379 // set the style for URLs
380 Call(SCI_STYLESETFORE, STYLE_URL, color);
381 Call(SCI_STYLESETHOTSPOT, STYLE_URL, (LPARAM)TRUE);
383 Call(SCI_SETHOTSPOTACTIVEUNDERLINE, (LPARAM)TRUE);
386 void CSciEdit::SetAutoCompletionList(const std::set<CString>& list, const TCHAR separator)
388 //copy the auto completion list.
390 //SK: instead of creating a copy of that list, we could accept a pointer
391 //to the list and use that instead. But then the caller would have to make
392 //sure that the list persists over the lifetime of the control!
393 m_autolist.clear();
394 m_autolist = list;
395 m_separator = separator;
398 BOOL CSciEdit::IsMisspelled(const CString& sWord)
400 // convert the string from the control to the encoding of the spell checker module.
401 CStringA sWordA;
402 if (m_spellcodepage)
404 char * buf;
405 buf = sWordA.GetBuffer(sWord.GetLength()*4 + 1);
406 int lengthIncTerminator =
407 WideCharToMultiByte(m_spellcodepage, 0, sWord, -1, buf, sWord.GetLength()*4, NULL, NULL);
408 sWordA.ReleaseBuffer(lengthIncTerminator-1);
410 else
411 sWordA = CStringA(sWord);
412 sWordA.Trim("\'\".,");
413 // words starting with a digit are treated as correctly spelled
414 if (_istdigit(sWord.GetAt(0)))
415 return FALSE;
416 // words in the personal dictionary are correct too
417 if (m_personalDict.FindWord(sWord))
418 return FALSE;
420 // now we actually check the spelling...
421 if (!pChecker->spell(sWordA))
423 // the word is marked as misspelled, we now check whether the word
424 // is maybe a composite identifier
425 // a composite identifier consists of multiple words, with each word
426 // separated by a change in lower to uppercase letters
427 if (sWord.GetLength() > 1)
429 int wordstart = 0;
430 int wordend = 1;
431 while (wordend < sWord.GetLength())
433 while ((wordend < sWord.GetLength())&&(!_istupper(sWord[wordend])))
434 wordend++;
435 if ((wordstart == 0)&&(wordend == sWord.GetLength()))
437 // words in the auto list are also assumed correctly spelled
438 if (m_autolist.find(sWord) != m_autolist.end())
439 return FALSE;
440 return TRUE;
442 sWordA = CStringA(sWord.Mid(wordstart, wordend-wordstart));
443 if ((sWordA.GetLength() > 2)&&(!pChecker->spell(sWordA)))
445 return TRUE;
447 wordstart = wordend;
448 wordend++;
452 return FALSE;
455 void CSciEdit::CheckSpelling()
457 if (pChecker == NULL)
458 return;
460 TEXTRANGEA textrange;
462 LRESULT firstline = Call(SCI_GETFIRSTVISIBLELINE);
463 LRESULT lastline = firstline + Call(SCI_LINESONSCREEN);
464 textrange.chrg.cpMin = Call(SCI_POSITIONFROMLINE, firstline);
465 textrange.chrg.cpMax = textrange.chrg.cpMin;
466 LRESULT lastpos = Call(SCI_POSITIONFROMLINE, lastline) + Call(SCI_LINELENGTH, lastline);
467 if (lastpos < 0)
468 lastpos = Call(SCI_GETLENGTH)-textrange.chrg.cpMin;
469 while (textrange.chrg.cpMax < lastpos)
471 textrange.chrg.cpMin = Call(SCI_WORDSTARTPOSITION, textrange.chrg.cpMax+1, TRUE);
472 if (textrange.chrg.cpMin < textrange.chrg.cpMax)
473 break;
474 textrange.chrg.cpMax = Call(SCI_WORDENDPOSITION, textrange.chrg.cpMin, TRUE);
475 if (textrange.chrg.cpMin == textrange.chrg.cpMax)
477 textrange.chrg.cpMax++;
478 continue;
480 ATLASSERT(textrange.chrg.cpMax >= textrange.chrg.cpMin);
481 char * textbuffer = new char[textrange.chrg.cpMax - textrange.chrg.cpMin + 2];
482 SecureZeroMemory(textbuffer, textrange.chrg.cpMax - textrange.chrg.cpMin + 2);
483 textrange.lpstrText = textbuffer;
484 textrange.chrg.cpMax++;
485 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
486 int len = strlen(textrange.lpstrText);
487 if (len == 0)
489 textrange.chrg.cpMax--;
490 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
491 len = strlen(textrange.lpstrText);
492 textrange.chrg.cpMax++;
493 len++;
495 if (len && textrange.lpstrText[len - 1] == '.')
497 // Try to ignore file names from the auto list.
498 // Do do this, for each word ending with '.' we extract next word and check
499 // whether the combined string is present in auto list.
500 TEXTRANGEA twoWords;
501 twoWords.chrg.cpMin = textrange.chrg.cpMin;
502 twoWords.chrg.cpMax = Call(SCI_WORDENDPOSITION, textrange.chrg.cpMax + 1, TRUE);
503 twoWords.lpstrText = new char[twoWords.chrg.cpMax - twoWords.chrg.cpMin + 1];
504 SecureZeroMemory(twoWords.lpstrText, twoWords.chrg.cpMax - twoWords.chrg.cpMin + 1);
505 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&twoWords);
506 CString sWord = StringFromControl(twoWords.lpstrText);
507 delete [] twoWords.lpstrText;
508 if (m_autolist.find(sWord) != m_autolist.end())
510 //mark word as correct (remove the squiggle line)
511 Call(SCI_STARTSTYLING, twoWords.chrg.cpMin, INDICS_MASK);
512 Call(SCI_SETSTYLING, twoWords.chrg.cpMax - twoWords.chrg.cpMin, 0);
513 textrange.chrg.cpMax = twoWords.chrg.cpMax;
514 delete [] textbuffer;
515 continue;
518 if (len)
519 textrange.lpstrText[len - 1] = 0;
520 textrange.chrg.cpMax--;
521 if (strlen(textrange.lpstrText) > 0)
523 CString sWord = StringFromControl(textrange.lpstrText);
524 if ((GetStyleAt(textrange.chrg.cpMin) != STYLE_URL) && IsMisspelled(sWord))
526 //mark word as misspelled
527 Call(SCI_STARTSTYLING, textrange.chrg.cpMin, INDICS_MASK);
528 Call(SCI_SETSTYLING, textrange.chrg.cpMax - textrange.chrg.cpMin, INDIC1_MASK);
530 else
532 //mark word as correct (remove the squiggle line)
533 Call(SCI_STARTSTYLING, textrange.chrg.cpMin, INDICS_MASK);
534 Call(SCI_SETSTYLING, textrange.chrg.cpMax - textrange.chrg.cpMin, 0);
537 delete [] textbuffer;
541 void CSciEdit::SuggestSpellingAlternatives()
543 if (pChecker == NULL)
544 return;
545 CString word = GetWordUnderCursor(true);
546 Call(SCI_SETCURRENTPOS, Call(SCI_WORDSTARTPOSITION, Call(SCI_GETCURRENTPOS), TRUE));
547 if (word.IsEmpty())
548 return;
549 char ** wlst;
550 int ns = pChecker->suggest(&wlst, CStringA(word));
551 if (ns > 0)
553 CString suggestions;
554 for (int i=0; i < ns; i++)
556 suggestions += CString(wlst[i]) + m_separator;
557 free(wlst[i]);
559 free(wlst);
560 suggestions.TrimRight(m_separator);
561 if (suggestions.IsEmpty())
562 return;
563 Call(SCI_AUTOCSETSEPARATOR, (WPARAM)CStringA(m_separator).GetAt(0));
564 Call(SCI_AUTOCSETDROPRESTOFWORD, 1);
565 Call(SCI_AUTOCSHOW, 0, (LPARAM)(LPCSTR)StringForControl(suggestions));
570 void CSciEdit::DoAutoCompletion(int nMinPrefixLength)
572 if (m_autolist.size()==0)
573 return;
574 if (Call(SCI_AUTOCACTIVE))
575 return;
576 CString word = GetWordUnderCursor();
577 if (word.GetLength() < nMinPrefixLength)
578 return; //don't auto complete yet, word is too short
579 int pos = Call(SCI_GETCURRENTPOS);
580 if (pos != Call(SCI_WORDENDPOSITION, pos, TRUE))
581 return; //don't auto complete if we're not at the end of a word
582 CString sAutoCompleteList;
584 word.MakeUpper();
585 for (std::set<CString>::const_iterator lowerit = m_autolist.lower_bound(word);
586 lowerit != m_autolist.end(); ++lowerit)
588 int compare = word.CompareNoCase(lowerit->Left(word.GetLength()));
589 if (compare>0)
590 continue;
591 else if (compare == 0)
593 sAutoCompleteList += *lowerit + m_separator;
595 else
597 break;
600 sAutoCompleteList.TrimRight(m_separator);
601 if (sAutoCompleteList.IsEmpty())
602 return;
604 Call(SCI_AUTOCSETSEPARATOR, (WPARAM)CStringA(m_separator).GetAt(0));
605 Call(SCI_AUTOCSHOW, word.GetLength(), (LPARAM)(LPCSTR)StringForControl(sAutoCompleteList));
608 BOOL CSciEdit::OnChildNotify(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pLResult)
610 if (message != WM_NOTIFY)
611 return CWnd::OnChildNotify(message, wParam, lParam, pLResult);
613 LPNMHDR lpnmhdr = (LPNMHDR) lParam;
614 SCNotification * lpSCN = (SCNotification *)lParam;
616 if(lpnmhdr->hwndFrom==m_hWnd)
618 switch(lpnmhdr->code)
620 case SCN_CHARADDED:
622 if ((lpSCN->ch < 32)&&(lpSCN->ch != 13)&&(lpSCN->ch != 10))
623 Call(SCI_DELETEBACK);
624 else
626 DoAutoCompletion(3);
628 return TRUE;
630 break;
631 case SCN_STYLENEEDED:
633 int startstylepos = Call(SCI_GETENDSTYLED);
634 int endstylepos = ((SCNotification *)lpnmhdr)->position;
635 MarkEnteredBugID(startstylepos, endstylepos);
636 StyleEnteredText(startstylepos, endstylepos);
637 StyleURLs(startstylepos, endstylepos);
638 CheckSpelling();
639 WrapLines(startstylepos, endstylepos);
640 return TRUE;
642 break;
643 case SCN_HOTSPOTCLICK:
645 TEXTRANGEA textrange;
646 textrange.chrg.cpMin = lpSCN->position;
647 textrange.chrg.cpMax = lpSCN->position;
648 DWORD style = GetStyleAt(lpSCN->position);
649 while (GetStyleAt(textrange.chrg.cpMin - 1) == style)
650 --textrange.chrg.cpMin;
651 while (GetStyleAt(textrange.chrg.cpMax + 1) == style)
652 ++textrange.chrg.cpMax;
653 ++textrange.chrg.cpMax;
654 char * textbuffer = new char[textrange.chrg.cpMax - textrange.chrg.cpMin + 1];
655 textrange.lpstrText = textbuffer;
656 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
657 CString url;
658 if (style == STYLE_URL)
659 url = StringFromControl(textbuffer);
660 else
662 url = m_sUrl;
663 url.Replace(_T("%BUGID%"), StringFromControl(textbuffer));
665 delete [] textbuffer;
666 if (!url.IsEmpty())
667 ShellExecute(GetParent()->GetSafeHwnd(), _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
669 break;
672 return CWnd::OnChildNotify(message, wParam, lParam, pLResult);
675 BEGIN_MESSAGE_MAP(CSciEdit, CWnd)
676 ON_WM_KEYDOWN()
677 ON_WM_CONTEXTMENU()
678 END_MESSAGE_MAP()
680 void CSciEdit::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
682 switch (nChar)
684 case (VK_ESCAPE):
686 if ((Call(SCI_AUTOCACTIVE)==0)&&(Call(SCI_CALLTIPACTIVE)==0))
687 ::SendMessage(GetParent()->GetSafeHwnd(), WM_CLOSE, 0, 0);
689 break;
691 CWnd::OnKeyDown(nChar, nRepCnt, nFlags);
694 BOOL CSciEdit::PreTranslateMessage(MSG* pMsg)
696 if (pMsg->message == WM_KEYDOWN)
698 switch (pMsg->wParam)
700 case VK_SPACE:
702 if (GetKeyState(VK_CONTROL) & 0x8000)
704 DoAutoCompletion(1);
705 return TRUE;
708 break;
709 case VK_TAB:
710 // The TAB cannot be handled in OnKeyDown because it is too late by then.
712 if (GetKeyState(VK_CONTROL)&0x8000)
714 //Ctrl-Tab was pressed, this means we should provide the user with
715 //a list of possible spell checking alternatives to the word under
716 //the cursor
717 SuggestSpellingAlternatives();
718 return TRUE;
720 else if (!Call(SCI_AUTOCACTIVE))
722 ::PostMessage(GetParent()->GetSafeHwnd(), WM_NEXTDLGCTL, GetKeyState(VK_SHIFT)&0x8000, 0);
723 return TRUE;
726 break;
729 return CWnd::PreTranslateMessage(pMsg);
732 void CSciEdit::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
734 int anchor = Call(SCI_GETANCHOR);
735 int currentpos = Call(SCI_GETCURRENTPOS);
736 int selstart = Call(SCI_GETSELECTIONSTART);
737 int selend = Call(SCI_GETSELECTIONEND);
738 int pointpos = 0;
739 if ((point.x == -1) && (point.y == -1))
741 CRect rect;
742 GetClientRect(&rect);
743 ClientToScreen(&rect);
744 point = rect.CenterPoint();
745 pointpos = Call(SCI_GETCURRENTPOS);
747 else
749 // change the cursor position to the point where the user
750 // right-clicked.
751 CPoint clientpoint = point;
752 ScreenToClient(&clientpoint);
753 pointpos = Call(SCI_POSITIONFROMPOINT, clientpoint.x, clientpoint.y);
755 CString sMenuItemText;
756 CMenu popup;
757 bool bRestoreCursor = true;
758 if (popup.CreatePopupMenu())
760 bool bCanUndo = !!Call(SCI_CANUNDO);
761 bool bCanRedo = !!Call(SCI_CANREDO);
762 bool bHasSelection = (selend-selstart > 0);
763 bool bCanPaste = !!Call(SCI_CANPASTE);
764 UINT uEnabledMenu = MF_STRING | MF_ENABLED;
765 UINT uDisabledMenu = MF_STRING | MF_GRAYED;
767 // find the word under the cursor
768 CString sWord;
769 if (pointpos)
771 // setting the cursor clears the selection
772 Call(SCI_SETANCHOR, pointpos);
773 Call(SCI_SETCURRENTPOS, pointpos);
774 sWord = GetWordUnderCursor();
775 // restore the selection
776 Call(SCI_SETSELECTIONSTART, selstart);
777 Call(SCI_SETSELECTIONEND, selend);
779 else
780 sWord = GetWordUnderCursor();
781 CStringA worda = CStringA(sWord);
783 int nCorrections = 1;
784 bool bSpellAdded = false;
785 // check if the word under the cursor is spelled wrong
786 if ((pChecker)&&(!worda.IsEmpty()))
788 char ** wlst;
789 // get the spell suggestions
790 int ns = pChecker->suggest(&wlst,worda);
791 if (ns > 0)
793 // add the suggestions to the context menu
794 for (int i=0; i < ns; i++)
796 bSpellAdded = true;
797 CString sug = CString(wlst[i]);
798 popup.InsertMenu((UINT)-1, 0, nCorrections++, sug);
799 free(wlst[i]);
801 free(wlst);
804 // only add a separator if spelling correction suggestions were added
805 if (bSpellAdded)
806 popup.AppendMenu(MF_SEPARATOR);
808 // also allow the user to add the word to the custom dictionary so
809 // it won't show up as misspelled anymore
810 if ((sWord.GetLength()<PDICT_MAX_WORD_LENGTH)&&((pChecker)&&(m_autolist.find(sWord) == m_autolist.end())&&(!pChecker->spell(worda)))&&
811 (!_istdigit(sWord.GetAt(0)))&&(!m_personalDict.FindWord(sWord)))
813 sMenuItemText.Format(IDS_SCIEDIT_ADDWORD, sWord);
814 popup.AppendMenu(uEnabledMenu, SCI_ADDWORD, sMenuItemText);
815 // another separator
816 popup.AppendMenu(MF_SEPARATOR);
819 // add the 'default' entries
820 sMenuItemText.LoadString(IDS_SCIEDIT_UNDO);
821 popup.AppendMenu(bCanUndo ? uEnabledMenu : uDisabledMenu, SCI_UNDO, sMenuItemText);
822 sMenuItemText.LoadString(IDS_SCIEDIT_REDO);
823 popup.AppendMenu(bCanRedo ? uEnabledMenu : uDisabledMenu, SCI_REDO, sMenuItemText);
825 popup.AppendMenu(MF_SEPARATOR);
827 sMenuItemText.LoadString(IDS_SCIEDIT_CUT);
828 popup.AppendMenu(bHasSelection ? uEnabledMenu : uDisabledMenu, SCI_CUT, sMenuItemText);
829 sMenuItemText.LoadString(IDS_SCIEDIT_COPY);
830 popup.AppendMenu(bHasSelection ? uEnabledMenu : uDisabledMenu, SCI_COPY, sMenuItemText);
831 sMenuItemText.LoadString(IDS_SCIEDIT_PASTE);
832 popup.AppendMenu(bCanPaste ? uEnabledMenu : uDisabledMenu, SCI_PASTE, sMenuItemText);
834 popup.AppendMenu(MF_SEPARATOR);
836 sMenuItemText.LoadString(IDS_SCIEDIT_SELECTALL);
837 popup.AppendMenu(uEnabledMenu, SCI_SELECTALL, sMenuItemText);
839 popup.AppendMenu(MF_SEPARATOR);
841 sMenuItemText.LoadString(IDS_SCIEDIT_SPLITLINES);
842 popup.AppendMenu(bHasSelection ? uEnabledMenu : uDisabledMenu, SCI_LINESSPLIT, sMenuItemText);
844 popup.AppendMenu(MF_SEPARATOR);
846 int nCustoms = nCorrections;
847 // now add any custom context menus
848 for (INT_PTR handlerindex = 0; handlerindex < m_arContextHandlers.GetCount(); ++handlerindex)
850 CSciEditContextMenuInterface * pHandler = m_arContextHandlers.GetAt(handlerindex);
851 pHandler->InsertMenuItems(popup, nCustoms);
853 if (nCustoms > nCorrections)
855 // custom menu entries present, so add another separator
856 popup.AppendMenu(MF_SEPARATOR);
859 #if THESAURUS
860 // add found thesauri to sub menu's
861 CMenu thesaurs;
862 thesaurs.CreatePopupMenu();
863 int nThesaurs = 0;
864 CPtrArray menuArray;
865 if ((pThesaur)&&(!worda.IsEmpty()))
867 mentry * pmean;
868 worda.MakeLower();
869 int count = pThesaur->Lookup(worda, worda.GetLength(),&pmean);
870 if (count)
872 mentry * pm = pmean;
873 for (int i=0; i < count; i++)
875 CMenu * submenu = new CMenu();
876 menuArray.Add(submenu);
877 submenu->CreateMenu();
878 for (int j=0; j < pm->count; j++)
880 CString sug = CString(pm->psyns[j]);
881 submenu->InsertMenu((UINT)-1, 0, nThesaurs++, sug);
883 thesaurs.InsertMenu((UINT)-1, MF_POPUP, (UINT_PTR)(submenu->m_hMenu), CString(pm->defn));
884 pm++;
887 if ((count > 0)&&(point.x >= 0))
889 #ifdef IDS_SPELLEDIT_THESAURUS
890 sMenuItemText.LoadString(IDS_SPELLEDIT_THESAURUS);
891 popup.InsertMenu((UINT)-1, MF_POPUP, (UINT_PTR)thesaurs.m_hMenu, sMenuItemText);
892 #else
893 popup.InsertMenu((UINT)-1, MF_POPUP, (UINT_PTR)thesaurs.m_hMenu, _T("Thesaurus"));
894 #endif
895 nThesaurs = nCustoms;
897 else
899 sMenuItemText.LoadString(IDS_SPELLEDIT_NOTHESAURUS);
900 popup.AppendMenu(MF_DISABLED | MF_GRAYED | MF_STRING, 0, sMenuItemText);
903 pThesaur->CleanUpAfterLookup(&pmean, count);
905 else
907 sMenuItemText.LoadString(IDS_SPELLEDIT_NOTHESAURUS);
908 popup.AppendMenu(MF_DISABLED | MF_GRAYED | MF_STRING, 0, sMenuItemText);
910 #endif
911 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
912 switch (cmd)
914 case 0:
915 break; // no command selected
916 case SCI_SELECTALL:
917 bRestoreCursor = false;
918 // fall through
919 case SCI_UNDO:
920 case SCI_REDO:
921 case SCI_CUT:
922 case SCI_COPY:
923 case SCI_PASTE:
924 Call(cmd);
925 break;
926 case SCI_ADDWORD:
927 m_personalDict.AddWord(sWord);
928 CheckSpelling();
929 break;
930 case SCI_LINESSPLIT:
932 int marker = Call(SCI_GETEDGECOLUMN) * Call(SCI_TEXTWIDTH, 0, (LPARAM)" ");
933 if (marker)
935 Call(SCI_TARGETFROMSELECTION);
936 Call(SCI_LINESJOIN);
937 Call(SCI_LINESSPLIT, marker);
940 break;
941 default:
942 if (cmd < nCorrections)
944 Call(SCI_SETANCHOR, pointpos);
945 Call(SCI_SETCURRENTPOS, pointpos);
946 GetWordUnderCursor(true);
947 CString temp;
948 popup.GetMenuString(cmd, temp, 0);
949 // setting the cursor clears the selection
950 Call(SCI_REPLACESEL, 0, (LPARAM)(LPCSTR)StringForControl(temp));
952 else if (cmd < (nCorrections+nCustoms))
954 for (INT_PTR handlerindex = 0; handlerindex < m_arContextHandlers.GetCount(); ++handlerindex)
956 CSciEditContextMenuInterface * pHandler = m_arContextHandlers.GetAt(handlerindex);
957 if (pHandler->HandleMenuItemClick(cmd, this))
958 break;
961 #if THESAURUS
962 else if (cmd <= (nThesaurs+nCorrections+nCustoms))
964 Call(SCI_SETANCHOR, pointpos);
965 Call(SCI_SETCURRENTPOS, pointpos);
966 GetWordUnderCursor(true);
967 CString temp;
968 thesaurs.GetMenuString(cmd, temp, 0);
969 Call(SCI_REPLACESEL, 0, (LPARAM)(LPCSTR)StringForControl(temp));
971 #endif
973 #ifdef THESAURUS
974 for (INT_PTR index = 0; index < menuArray.GetCount(); ++index)
976 CMenu * pMenu = (CMenu*)menuArray[index];
977 delete pMenu;
979 #endif
981 if (bRestoreCursor)
983 // restore the anchor and cursor position
984 Call(SCI_SETCURRENTPOS, currentpos);
985 Call(SCI_SETANCHOR, anchor);
989 bool CSciEdit::StyleEnteredText(int startstylepos, int endstylepos)
991 bool bStyled = false;
992 const int line = Call(SCI_LINEFROMPOSITION, startstylepos);
993 const int line_number_end = Call(SCI_LINEFROMPOSITION, endstylepos);
994 for (int line_number = line; line_number <= line_number_end; ++line_number)
996 int offset = Call(SCI_POSITIONFROMLINE, line_number);
997 int line_len = Call(SCI_LINELENGTH, line_number);
998 char * linebuffer = new char[line_len+1];
999 Call(SCI_GETLINE, line_number, (LPARAM)linebuffer);
1000 linebuffer[line_len] = 0;
1001 int start = 0;
1002 int end = 0;
1003 while (FindStyleChars(linebuffer, '*', start, end))
1005 Call(SCI_STARTSTYLING, start+offset, STYLE_MASK);
1006 Call(SCI_SETSTYLING, end-start, STYLE_BOLD);
1007 bStyled = true;
1008 start = end;
1010 start = 0;
1011 end = 0;
1012 while (FindStyleChars(linebuffer, '^', start, end))
1014 Call(SCI_STARTSTYLING, start+offset, STYLE_MASK);
1015 Call(SCI_SETSTYLING, end-start, STYLE_ITALIC);
1016 bStyled = true;
1017 start = end;
1019 start = 0;
1020 end = 0;
1021 while (FindStyleChars(linebuffer, '_', start, end))
1023 Call(SCI_STARTSTYLING, start+offset, STYLE_MASK);
1024 Call(SCI_SETSTYLING, end-start, STYLE_UNDERLINED);
1025 bStyled = true;
1026 start = end;
1028 delete [] linebuffer;
1030 return bStyled;
1033 bool CSciEdit::WrapLines(int startpos, int endpos)
1035 int markerX = Call(SCI_GETEDGECOLUMN) * Call(SCI_TEXTWIDTH, 0, (LPARAM)" ");
1036 if (markerX)
1038 Call(SCI_SETTARGETSTART, startpos);
1039 Call(SCI_SETTARGETEND, endpos);
1040 Call(SCI_LINESSPLIT, markerX);
1041 return true;
1043 return false;
1046 void CSciEdit::AdvanceUTF8(const char * str, int& pos)
1048 if ((str[pos] & 0xE0)==0xC0)
1050 // utf8 2-byte sequence
1051 pos += 2;
1053 else if ((str[pos] & 0xF0)==0xE0)
1055 // utf8 3-byte sequence
1056 pos += 3;
1058 else if ((str[pos] & 0xF8)==0xF0)
1060 // utf8 4-byte sequence
1061 pos += 4;
1063 else
1064 pos++;
1067 bool CSciEdit::FindStyleChars(const char * line, char styler, int& start, int& end)
1069 int i=0;
1070 int u=0;
1071 while (i < start)
1073 AdvanceUTF8(line, i);
1074 u++;
1077 bool bFoundMarker = false;
1078 CString sULine = CUnicodeUtils::GetUnicode(line);
1079 // find a starting marker
1080 while (line[i] != 0)
1082 if (line[i] == styler)
1084 if ((line[i+1]!=0)&&(IsCharAlphaNumeric(sULine[u+1]))&&
1085 (((u>0)&&(!IsCharAlphaNumeric(sULine[u-1]))) || (u==0)))
1087 start = i+1;
1088 AdvanceUTF8(line, i);
1089 u++;
1090 bFoundMarker = true;
1091 break;
1094 AdvanceUTF8(line, i);
1095 u++;
1097 if (!bFoundMarker)
1098 return false;
1099 // find ending marker
1100 bFoundMarker = false;
1101 while (line[i] != 0)
1103 if (line[i] == styler)
1105 if ((IsCharAlphaNumeric(sULine[u-1]))&&
1106 ((((u+1)<sULine.GetLength())&&(!IsCharAlphaNumeric(sULine[u+1]))) || ((u+1) == sULine.GetLength()))
1109 end = i;
1110 i++;
1111 bFoundMarker = true;
1112 break;
1115 AdvanceUTF8(line, i);
1116 u++;
1118 return bFoundMarker;
1121 BOOL CSciEdit::MarkEnteredBugID(int startstylepos, int endstylepos)
1123 if (m_sCommand.IsEmpty())
1124 return FALSE;
1125 // get the text between the start and end position we have to style
1126 const int line_number = Call(SCI_LINEFROMPOSITION, startstylepos);
1127 int start_pos = Call(SCI_POSITIONFROMLINE, (WPARAM)line_number);
1128 int end_pos = endstylepos;
1130 if (start_pos == end_pos)
1131 return FALSE;
1132 if (start_pos > end_pos)
1134 int switchtemp = start_pos;
1135 start_pos = end_pos;
1136 end_pos = switchtemp;
1139 char * textbuffer = new char[end_pos - start_pos + 2];
1140 TEXTRANGEA textrange;
1141 textrange.lpstrText = textbuffer;
1142 textrange.chrg.cpMin = start_pos;
1143 textrange.chrg.cpMax = end_pos;
1144 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
1145 CStringA msg = CStringA(textbuffer);
1147 Call(SCI_STARTSTYLING, start_pos, STYLE_MASK);
1149 if (!m_sBugID.IsEmpty())
1151 // match with two regex strings (without grouping!)
1154 const tr1::regex regCheck(m_sCommand);
1155 const tr1::regex regBugID(m_sBugID);
1156 const tr1::sregex_iterator end;
1157 string s = msg;
1158 LONG pos = 0;
1159 for (tr1::sregex_iterator it(s.begin(), s.end(), regCheck); it != end; ++it)
1161 // clear the styles up to the match position
1162 Call(SCI_SETSTYLING, it->position(0)-pos, STYLE_DEFAULT);
1163 pos = it->position(0);
1165 // (*it)[0] is the matched string
1166 string matchedString = (*it)[0];
1167 for (tr1::sregex_iterator it2(matchedString.begin(), matchedString.end(), regBugID); it2 != end; ++it2)
1169 ATLTRACE(_T("matched id : %s\n"), (*it2)[0].str().c_str());
1171 // bold style up to the id match
1172 ATLTRACE("position = %ld\n", it2->position(0));
1173 if (it2->position(0))
1174 Call(SCI_SETSTYLING, it2->position(0), STYLE_ISSUEBOLD);
1175 // bold and recursive style for the bug ID itself
1176 if ((*it2)[0].str().size())
1177 Call(SCI_SETSTYLING, (*it2)[0].str().size(), STYLE_ISSUEBOLDITALIC);
1179 pos = it->position(0) + matchedString.size();
1181 // bold style for the rest of the string which isn't matched
1182 if (s.size()-pos)
1183 Call(SCI_SETSTYLING, s.size()-pos, STYLE_DEFAULT);
1185 catch (exception) {}
1187 else
1191 const tr1::regex regCheck(m_sCommand);
1192 const tr1::sregex_iterator end;
1193 string s = msg;
1194 LONG pos = 0;
1195 for (tr1::sregex_iterator it(s.begin(), s.end(), regCheck); it != end; ++it)
1197 // clear the styles up to the match position
1198 Call(SCI_SETSTYLING, it->position(0)-pos, STYLE_DEFAULT);
1199 pos = it->position(0);
1201 const tr1::smatch match = *it;
1202 // we define group 1 as the whole issue text and
1203 // group 2 as the bug ID
1204 if (match.size() >= 2)
1206 ATLTRACE(_T("matched id : %s\n"), string(match[1]).c_str());
1207 Call(SCI_SETSTYLING, match[1].first-s.begin()-pos, STYLE_ISSUEBOLD);
1208 Call(SCI_SETSTYLING, string(match[1]).size(), STYLE_ISSUEBOLDITALIC);
1209 pos = match[1].second-s.begin();
1213 catch (exception) {}
1215 delete [] textbuffer;
1217 return FALSE;
1220 bool CSciEdit::IsValidURLChar(unsigned char ch)
1222 return isalnum(ch) ||
1223 ch == '_' || ch == '/' || ch == ';' || ch == '?' || ch == '&' || ch == '=' ||
1224 ch == '%' || ch == ':' || ch == '.' || ch == '#' || ch == '-' || ch == '+';
1227 void CSciEdit::StyleURLs(int startstylepos, int endstylepos)
1229 const int line_number = Call(SCI_LINEFROMPOSITION, startstylepos);
1230 startstylepos = Call(SCI_POSITIONFROMLINE, (WPARAM)line_number);
1232 int len = endstylepos - startstylepos + 1;
1233 char* textbuffer = new char[len + 1];
1234 TEXTRANGEA textrange;
1235 textrange.lpstrText = textbuffer;
1236 textrange.chrg.cpMin = startstylepos;
1237 textrange.chrg.cpMax = endstylepos;
1238 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
1239 // we're dealing with utf8 encoded text here, which means one glyph is
1240 // not necessarily one byte/wchar_t
1241 // that's why we use CStringA to still get a correct char index
1242 CStringA msg = textbuffer;
1243 delete [] textbuffer;
1245 int starturl = -1;
1246 for(int i = 0; i <= msg.GetLength(); )
1248 if ((i < len) && IsValidURLChar(msg[i]))
1250 if (starturl < 0)
1251 starturl = i;
1253 else
1255 if ((starturl >= 0) && IsUrl(msg.Mid(starturl, i - starturl)))
1257 ASSERT(startstylepos + i <= endstylepos);
1258 Call(SCI_STARTSTYLING, startstylepos + starturl, STYLE_MASK);
1259 Call(SCI_SETSTYLING, i - starturl, STYLE_URL);
1261 starturl = -1;
1263 AdvanceUTF8(msg, i);
1267 bool CSciEdit::IsUrl(const CStringA& sText)
1269 if (!PathIsURLA(sText))
1270 return false;
1271 if (sText.Find("://")>=0)
1272 return true;
1273 return false;
1276 bool CSciEdit::IsUTF8(LPVOID pBuffer, size_t cb)
1278 if (cb < 2)
1279 return true;
1280 UINT16 * pVal = (UINT16 *)pBuffer;
1281 UINT8 * pVal2 = (UINT8 *)(pVal+1);
1282 // scan the whole buffer for a 0x0000 sequence
1283 // if found, we assume a binary file
1284 for (size_t i=0; i<(cb-2); i=i+2)
1286 if (0x0000 == *pVal++)
1287 return false;
1289 pVal = (UINT16 *)pBuffer;
1290 if (*pVal == 0xFEFF)
1291 return false;
1292 if (cb < 3)
1293 return false;
1294 if (*pVal == 0xBBEF)
1296 if (*pVal2 == 0xBF)
1297 return true;
1299 // check for illegal UTF8 chars
1300 pVal2 = (UINT8 *)pBuffer;
1301 for (size_t i=0; i<cb; ++i)
1303 if ((*pVal2 == 0xC0)||(*pVal2 == 0xC1)||(*pVal2 >= 0xF5))
1304 return false;
1305 pVal2++;
1307 pVal2 = (UINT8 *)pBuffer;
1308 bool bUTF8 = false;
1309 for (size_t i=0; i<(cb-3); ++i)
1311 if ((*pVal2 & 0xE0)==0xC0)
1313 pVal2++;i++;
1314 if ((*pVal2 & 0xC0)!=0x80)
1315 return false;
1316 bUTF8 = true;
1318 if ((*pVal2 & 0xF0)==0xE0)
1320 pVal2++;i++;
1321 if ((*pVal2 & 0xC0)!=0x80)
1322 return false;
1323 pVal2++;i++;
1324 if ((*pVal2 & 0xC0)!=0x80)
1325 return false;
1326 bUTF8 = true;
1328 if ((*pVal2 & 0xF8)==0xF0)
1330 pVal2++;i++;
1331 if ((*pVal2 & 0xC0)!=0x80)
1332 return false;
1333 pVal2++;i++;
1334 if ((*pVal2 & 0xC0)!=0x80)
1335 return false;
1336 pVal2++;i++;
1337 if ((*pVal2 & 0xC0)!=0x80)
1338 return false;
1339 bUTF8 = true;
1341 pVal2++;
1343 if (bUTF8)
1344 return true;
1345 return false;
1348 void CSciEdit::SetAStyle(int style, COLORREF fore, COLORREF back, int size, const char *face)
1350 Call(SCI_STYLESETFORE, style, fore);
1351 Call(SCI_STYLESETBACK, style, back);
1352 if (size >= 1)
1353 Call(SCI_STYLESETSIZE, style, size);
1354 if (face)
1355 Call(SCI_STYLESETFONT, style, reinterpret_cast<LPARAM>(face));
1359 void CSciEdit::SetUDiffStyle()
1361 SetAStyle(STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOWTEXT), ::GetSysColor(COLOR_WINDOW),
1362 // Reusing TortoiseBlame's setting which already have an user friendly
1363 // pane in TortoiseSVN's Settings dialog, while there is no such
1364 // pane for TortoiseUDiff.
1365 CRegStdDWORD(_T("Software\\TortoiseGit\\BlameFontSize"), 10),
1366 WideToMultibyte(CRegStdString(_T("Software\\TortoiseGit\\BlameFontName"), _T("Courier New"))).c_str());
1368 Call(SCI_SETTABWIDTH, 4);
1369 Call(SCI_SETREADONLY, TRUE);
1370 //LRESULT pix = Call(SCI_TEXTWIDTH, STYLE_LINENUMBER, (LPARAM)"_99999");
1371 //Call(SCI_SETMARGINWIDTHN, 0, pix);
1372 //Call(SCI_SETMARGINWIDTHN, 1);
1373 //Call(SCI_SETMARGINWIDTHN, 2);
1374 //Set the default windows colors for edit controls
1375 Call(SCI_STYLESETFORE, STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOWTEXT));
1376 Call(SCI_STYLESETBACK, STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOW));
1377 Call(SCI_SETSELFORE, TRUE, ::GetSysColor(COLOR_HIGHLIGHTTEXT));
1378 Call(SCI_SETSELBACK, TRUE, ::GetSysColor(COLOR_HIGHLIGHT));
1379 Call(SCI_SETCARETFORE, ::GetSysColor(COLOR_WINDOWTEXT));
1381 //SendEditor(SCI_SETREADONLY, FALSE);
1382 Call(SCI_CLEARALL);
1383 Call(EM_EMPTYUNDOBUFFER);
1384 Call(SCI_SETSAVEPOINT);
1385 Call(SCI_CANCEL);
1386 Call(SCI_SETUNDOCOLLECTION, 0);
1388 Call(SCI_SETUNDOCOLLECTION, 1);
1389 Call(SCI_SETWRAPMODE,SC_WRAP_NONE);
1391 //::SetFocus(m_hWndEdit);
1392 Call(EM_EMPTYUNDOBUFFER);
1393 Call(SCI_SETSAVEPOINT);
1394 Call(SCI_GOTOPOS, 0);
1396 Call(SCI_CLEARDOCUMENTSTYLE, 0, 0);
1397 Call(SCI_SETSTYLEBITS, 5, 0);
1399 //SetAStyle(SCE_DIFF_DEFAULT, RGB(0, 0, 0));
1400 SetAStyle(SCE_DIFF_COMMAND, RGB(0x0A, 0x24, 0x36));
1401 SetAStyle(SCE_DIFF_POSITION, RGB(0xFF, 0, 0));
1402 SetAStyle(SCE_DIFF_HEADER, RGB(0x80, 0, 0), RGB(0xFF, 0xFF, 0x80));
1403 SetAStyle(SCE_DIFF_COMMENT, RGB(0, 0x80, 0));
1404 Call(SCI_STYLESETBOLD, SCE_DIFF_COMMENT, TRUE);
1405 SetAStyle(SCE_DIFF_DELETED, ::GetSysColor(COLOR_WINDOWTEXT), RGB(0xFF, 0x80, 0x80));
1406 SetAStyle(SCE_DIFF_ADDED, ::GetSysColor(COLOR_WINDOWTEXT), RGB(0x80, 0xFF, 0x80));
1408 Call(SCI_SETLEXER, SCLEX_DIFF);
1409 Call(SCI_SETKEYWORDS, 0, (LPARAM)"revision");
1410 Call(SCI_COLOURISE, 0, -1);
1413 int CSciEdit::LoadFromFile(CString &filename)
1415 FILE *fp = NULL;
1416 _tfopen_s(&fp, filename, _T("rb"));
1417 if (fp)
1419 //SetTitle();
1420 char data[4096];
1421 size_t lenFile = fread(data, 1, sizeof(data), fp);
1422 bool bUTF8 = IsUTF8(data, lenFile);
1423 while (lenFile > 0)
1425 Call(SCI_ADDTEXT, lenFile,
1426 reinterpret_cast<LPARAM>(static_cast<char *>(data)));
1427 lenFile = fread(data, 1, sizeof(data), fp);
1429 fclose(fp);
1430 Call(SCI_SETCODEPAGE, bUTF8 ? SC_CP_UTF8 : GetACP());
1431 return 0;
1433 else
1434 return -1;