Do not check for dictionaries in too many directories
[TortoiseGit.git] / src / Utils / MiscUI / SciEdit.cpp
blob6e9647baadcca6e48fb0326a2be92f8e80424ef6
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2012-2015 - TortoiseGit
4 // Copyright (C) 2003-2008,2012-2015 - TortoiseSVN
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 "SmartHandle.h"
28 #include "../../TortoiseUDiff/UDiffColors.h"
30 void CSciEditContextMenuInterface::InsertMenuItems(CMenu&, int&) {return;}
31 bool CSciEditContextMenuInterface::HandleMenuItemClick(int, CSciEdit *) {return false;}
32 void CSciEditContextMenuInterface::HandleSnippet(int, const CString &, CSciEdit *) { return; }
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
41 #define INDIC_MISSPELLED 18
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_typeSeparator(1)
79 , m_bDoStyle(false)
80 , m_nAutoCompleteMinChars(3)
82 m_hModule = ::LoadLibrary(_T("SciLexer_tgit.dll"));
85 CSciEdit::~CSciEdit(void)
87 m_personalDict.Save();
88 if (m_hModule)
89 ::FreeLibrary(m_hModule);
90 delete pChecker;
91 delete pThesaur;
94 static std::unique_ptr<UINT[]> Icon2Image(HICON hIcon)
96 if (hIcon == nullptr)
97 return nullptr;
99 ICONINFO iconInfo;
100 if (!GetIconInfo(hIcon, &iconInfo))
101 return nullptr;
103 BITMAP bm;
104 if (!GetObject(iconInfo.hbmColor, sizeof(BITMAP), &bm))
105 return nullptr;
107 int width = bm.bmWidth;
108 int height = bm.bmHeight;
109 int bytesPerScanLine = (width * 3 + 3) & 0xFFFFFFFC;
110 int size = bytesPerScanLine * height;
111 BITMAPINFO infoheader;
112 infoheader.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
113 infoheader.bmiHeader.biWidth = width;
114 infoheader.bmiHeader.biHeight = height;
115 infoheader.bmiHeader.biPlanes = 1;
116 infoheader.bmiHeader.biBitCount = 24;
117 infoheader.bmiHeader.biCompression = BI_RGB;
118 infoheader.bmiHeader.biSizeImage = size;
120 auto ptrb = std::make_unique<BYTE[]>(size * 2 + height * width * 4);
121 LPBYTE pixelsIconRGB = ptrb.get();
122 LPBYTE alphaPixels = pixelsIconRGB + size;
123 HDC hDC = CreateCompatibleDC(nullptr);
124 HBITMAP hBmpOld = (HBITMAP)SelectObject(hDC, (HGDIOBJ)iconInfo.hbmColor);
125 if (!GetDIBits(hDC, iconInfo.hbmColor, 0, height, (LPVOID)pixelsIconRGB, &infoheader, DIB_RGB_COLORS))
127 DeleteDC(hDC);
128 return nullptr;
131 SelectObject(hDC, hBmpOld);
132 if (!GetDIBits(hDC, iconInfo.hbmMask, 0,height, (LPVOID)alphaPixels, &infoheader, DIB_RGB_COLORS))
134 DeleteDC(hDC);
135 return nullptr;
138 DeleteDC(hDC);
139 auto imagePixels = std::make_unique<UINT[]>(height * width);
140 int lsSrc = width * 3;
141 int vsDest = height - 1;
142 for (int y = 0; y < height; y++)
144 int linePosSrc = (vsDest - y) * lsSrc;
145 int linePosDest = y * width;
146 for (int x = 0; x < width; x++)
148 int currentDestPos = linePosDest + x;
149 int currentSrcPos = linePosSrc + x * 3;
150 imagePixels[currentDestPos] = (((UINT)(
152 ((pixelsIconRGB[currentSrcPos + 2] /*Red*/)
153 | (pixelsIconRGB[currentSrcPos + 1] << 8 /*Green*/))
154 | pixelsIconRGB[currentSrcPos] << 16 /*Blue*/
156 | ((alphaPixels[currentSrcPos] ? 0 : 0xff) << 24))) & 0xffffffff);
159 return imagePixels;
162 void CSciEdit::Init(LONG lLanguage)
164 //Setup the direct access data
165 m_DirectFunction = SendMessage(SCI_GETDIRECTFUNCTION, 0, 0);
166 m_DirectPointer = SendMessage(SCI_GETDIRECTPOINTER, 0, 0);
167 Call(SCI_SETMARGINWIDTHN, 1, 0);
168 Call(SCI_SETUSETABS, 0); //pressing TAB inserts spaces
169 Call(SCI_SETWRAPVISUALFLAGS, SC_WRAPVISUALFLAG_END);
170 Call(SCI_AUTOCSETIGNORECASE, 1);
171 Call(SCI_SETLEXER, SCLEX_CONTAINER);
172 Call(SCI_SETCODEPAGE, SC_CP_UTF8);
173 Call(SCI_AUTOCSETFILLUPS, 0, (LPARAM)"\t([");
174 Call(SCI_AUTOCSETMAXWIDTH, 0);
175 //Set the default windows colors for edit controls
176 Call(SCI_STYLESETFORE, STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOWTEXT));
177 Call(SCI_STYLESETBACK, STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOW));
178 Call(SCI_SETSELFORE, TRUE, ::GetSysColor(COLOR_HIGHLIGHTTEXT));
179 Call(SCI_SETSELBACK, TRUE, ::GetSysColor(COLOR_HIGHLIGHT));
180 Call(SCI_SETCARETFORE, ::GetSysColor(COLOR_WINDOWTEXT));
181 Call(SCI_SETMODEVENTMASK, SC_MOD_INSERTTEXT | SC_MOD_DELETETEXT | SC_PERFORMED_UNDO | SC_PERFORMED_REDO);
182 Call(SCI_INDICSETSTYLE, INDIC_MISSPELLED, INDIC_SQUIGGLE);
183 Call(SCI_INDICSETFORE, INDIC_MISSPELLED, RGB(255,0,0));
184 CStringA sWordChars;
185 CStringA sWhiteSpace;
186 for (int i=0; i<255; ++i)
188 if (i == '\r' || i == '\n')
189 continue;
190 else if (i < 0x20 || i == ' ')
191 sWhiteSpace += (char)i;
192 else if (isalnum(i) || i == '\'' || i == '_' || i == '-')
193 sWordChars += (char)i;
195 Call(SCI_SETWORDCHARS, 0, (LPARAM)(LPCSTR)sWordChars);
196 Call(SCI_SETWHITESPACECHARS, 0, (LPARAM)(LPCSTR)sWhiteSpace);
197 m_bDoStyle = ((DWORD)CRegStdDWORD(_T("Software\\TortoiseGit\\StyleCommitMessages"), TRUE))==TRUE;
198 m_nAutoCompleteMinChars = (int)(DWORD)CRegStdDWORD(_T("Software\\TortoiseGit\\AutoCompleteMinChars"), 3);
199 // look for dictionary files and use them if found
200 long langId = GetUserDefaultLCID();
202 if (lLanguage >= 0)
204 if ((lLanguage != 0)||(((DWORD)CRegStdDWORD(_T("Software\\TortoiseGit\\Spellchecker"), FALSE))==FALSE))
206 if (!((lLanguage)&&(!LoadDictionaries(lLanguage))))
210 LoadDictionaries(langId);
211 DWORD lid = SUBLANGID(langId);
212 lid--;
213 if (lid > 0)
215 langId = MAKELANGID(PRIMARYLANGID(langId), lid);
217 else if (langId == 1033)
218 langId = 0;
219 else
220 langId = 1033;
221 } while ((langId)&&((pChecker==NULL)||(pThesaur==NULL)));
225 Call(SCI_SETEDGEMODE, EDGE_NONE);
226 Call(SCI_SETWRAPMODE, SC_WRAP_WORD);
227 Call(SCI_ASSIGNCMDKEY, SCK_END, SCI_LINEENDWRAP);
228 Call(SCI_ASSIGNCMDKEY, SCK_END + (SCMOD_SHIFT << 16), SCI_LINEENDWRAPEXTEND);
229 Call(SCI_ASSIGNCMDKEY, SCK_HOME, SCI_HOMEWRAP);
230 Call(SCI_ASSIGNCMDKEY, SCK_HOME + (SCMOD_SHIFT << 16), SCI_HOMEWRAPEXTEND);
231 if (CRegStdDWORD(L"Software\\TortoiseGit\\ScintillaDirect2D", FALSE) != FALSE)
233 Call(SCI_SETTECHNOLOGY, SC_TECHNOLOGY_DIRECTWRITERETAIN);
234 Call(SCI_SETBUFFEREDDRAW, 0);
239 void CSciEdit::Init(const ProjectProperties& props)
241 Init(props.lProjectLanguage);
242 m_sCommand = CStringA(CUnicodeUtils::GetUTF8(props.GetCheckRe()));
243 m_sBugID = CStringA(CUnicodeUtils::GetUTF8(props.GetBugIDRe()));
244 m_sUrl = CStringA(CUnicodeUtils::GetUTF8(props.sUrl));
246 Call(SCI_SETMOUSEDWELLTIME, 333);
248 if (props.nLogWidthMarker)
250 Call(SCI_SETWRAPMODE, SC_WRAP_NONE);
251 Call(SCI_SETEDGEMODE, EDGE_LINE);
252 Call(SCI_SETEDGECOLUMN, props.nLogWidthMarker);
254 else
256 Call(SCI_SETEDGEMODE, EDGE_NONE);
257 Call(SCI_SETWRAPMODE, SC_WRAP_WORD);
261 void CSciEdit::SetIcon(const std::map<int, UINT> &icons)
263 Call(SCI_RGBAIMAGESETWIDTH, 16);
264 Call(SCI_RGBAIMAGESETHEIGHT, 16);
265 for (auto icon : icons)
267 auto hIcon = (HICON)::LoadImage(AfxGetInstanceHandle(), MAKEINTRESOURCE(icon.second), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);
268 auto bytes = Icon2Image(hIcon);
269 DestroyIcon(hIcon);
270 Call(SCI_REGISTERRGBAIMAGE, icon.first, (LPARAM)bytes.get());
274 BOOL CSciEdit::LoadDictionaries(LONG lLanguageID)
276 //Setup the spell checker and thesaurus
277 TCHAR buf[6] = { 0 };
278 CString sFolderUp = CPathUtils::GetAppParentDirectory();
279 CString sFolderAppData = CPathUtils::GetAppDataDirectory();
280 CString sFile;
282 GetLocaleInfo(MAKELCID(lLanguageID, SORT_DEFAULT), LOCALE_SISO639LANGNAME, buf, _countof(buf));
283 sFile = buf;
284 if (lLanguageID == 2074)
285 sFile += _T("-Latn");
286 sFile += _T("_");
287 GetLocaleInfo(MAKELCID(lLanguageID, SORT_DEFAULT), LOCALE_SISO3166CTRYNAME, buf, _countof(buf));
288 sFile += buf;
289 if (pChecker==NULL)
291 if ((PathFileExists(sFolderAppData + _T("dic\\") + sFile + _T(".aff"))) &&
292 (PathFileExists(sFolderAppData + _T("dic\\") + sFile + _T(".dic"))))
294 pChecker = new Hunspell(CStringA(sFolderAppData + _T("dic\\") + sFile + _T(".aff")), CStringA(sFolderAppData + _T("dic\\") + sFile + _T(".dic")));
296 else if ((PathFileExists(sFolderUp + _T("Languages\\") + sFile + _T(".aff"))) &&
297 (PathFileExists(sFolderUp + _T("Languages\\") + sFile + _T(".dic"))))
299 pChecker = new Hunspell(CStringA(sFolderUp + _T("Languages\\") + sFile + _T(".aff")), CStringA(sFolderUp + _T("Languages\\") + sFile + _T(".dic")));
302 #if THESAURUS
303 if (pThesaur==NULL)
305 if ((PathFileExists(sFolderAppData + _T("dic\\th_") + sFile + _T("_v2.idx"))) &&
306 (PathFileExists(sFolderAppData + _T("dic\\th_") + sFile + _T("_v2.dat"))))
308 pThesaur = new MyThes(CStringA(sFolderAppData + _T("dic\\th_") + sFile + _T("_v2.idx")), CStringA(sFolderAppData + _T("dic\\th_") + sFile + _T("_v2.dat")));
310 else if ((PathFileExists(sFolderUp + _T("Languages\\th_") + sFile + _T("_v2.idx"))) &&
311 (PathFileExists(sFolderUp + _T("Languages\\th_") + sFile + _T("_v2.dat"))))
313 pThesaur = new MyThes(CStringA(sFolderUp + _T("Languages\\th_") + sFile + _T("_v2.idx")), CStringA(sFolderUp + _T("Languages\\th_") + sFile + _T("_v2.dat")));
316 #endif
317 if (pChecker)
319 const char * encoding = pChecker->get_dic_encoding();
320 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": %s\n", encoding);
321 int n = _countof(enc2locale);
322 m_spellcodepage = 0;
323 for (int i = 0; i < n; i++)
325 if (strcmp(encoding,enc2locale[i].def_enc) == 0)
327 m_spellcodepage = atoi(enc2locale[i].cp);
330 m_personalDict.Init(lLanguageID);
332 if ((pThesaur)||(pChecker))
333 return TRUE;
334 return FALSE;
337 LRESULT CSciEdit::Call(UINT message, WPARAM wParam, LPARAM lParam)
339 ASSERT(::IsWindow(m_hWnd)); //Window must be valid
340 ASSERT(m_DirectFunction); //Direct function must be valid
341 return ((SciFnDirect) m_DirectFunction)(m_DirectPointer, message, wParam, lParam);
344 CString CSciEdit::StringFromControl(const CStringA& text)
346 CString sText;
347 #ifdef UNICODE
348 int codepage = (int)Call(SCI_GETCODEPAGE);
349 int reslen = MultiByteToWideChar(codepage, 0, text, text.GetLength(), 0, 0);
350 MultiByteToWideChar(codepage, 0, text, text.GetLength(), sText.GetBuffer(reslen+1), reslen+1);
351 sText.ReleaseBuffer(reslen);
352 #else
353 sText = text;
354 #endif
355 return sText;
358 CStringA CSciEdit::StringForControl(const CString& text)
360 CStringA sTextA;
361 #ifdef UNICODE
362 int codepage = (int)SendMessage(SCI_GETCODEPAGE);
363 int reslen = WideCharToMultiByte(codepage, 0, text, text.GetLength(), 0, 0, 0, 0);
364 WideCharToMultiByte(codepage, 0, text, text.GetLength(), sTextA.GetBuffer(reslen), reslen, 0, 0);
365 sTextA.ReleaseBuffer(reslen);
366 #else
367 sTextA = text;
368 #endif
369 ATLTRACE("string length %d\n", sTextA.GetLength());
370 return sTextA;
373 void CSciEdit::SetText(const CString& sText)
375 CStringA sTextA = StringForControl(sText);
376 Call(SCI_SETTEXT, 0, (LPARAM)(LPCSTR)sTextA);
378 // Scintilla seems to have problems with strings that
379 // aren't terminated by a newline char. Once that char
380 // is there, it can be removed without problems.
381 // So we add here a newline, then remove it again.
382 Call(SCI_DOCUMENTEND);
383 Call(SCI_NEWLINE);
384 Call(SCI_DELETEBACK);
387 void CSciEdit::InsertText(const CString& sText, bool bNewLine)
389 CStringA sTextA = StringForControl(sText);
390 Call(SCI_REPLACESEL, 0, (LPARAM)(LPCSTR)sTextA);
391 if (bNewLine)
392 Call(SCI_REPLACESEL, 0, (LPARAM)(LPCSTR)"\n");
395 CString CSciEdit::GetText()
397 LRESULT len = Call(SCI_GETTEXT, 0, 0);
398 CStringA sTextA;
399 Call(SCI_GETTEXT, (WPARAM)(len + 1), (LPARAM)(LPCSTR)sTextA.GetBuffer((int)len + 1));
400 sTextA.ReleaseBuffer();
401 return StringFromControl(sTextA);
404 CString CSciEdit::GetWordUnderCursor(bool bSelectWord)
406 TEXTRANGEA textrange;
407 int pos = (int)Call(SCI_GETCURRENTPOS);
408 textrange.chrg.cpMin = (LONG)Call(SCI_WORDSTARTPOSITION, pos, TRUE);
409 if ((pos == textrange.chrg.cpMin)||(textrange.chrg.cpMin < 0))
410 return CString();
411 textrange.chrg.cpMax = (LONG)Call(SCI_WORDENDPOSITION, textrange.chrg.cpMin, TRUE);
413 auto textbuffer = std::make_unique<char[]>(textrange.chrg.cpMax - textrange.chrg.cpMin + 1);
414 textrange.lpstrText = textbuffer.get();
415 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
416 if (bSelectWord)
418 Call(SCI_SETSEL, textrange.chrg.cpMin, textrange.chrg.cpMax);
420 CString sRet = StringFromControl(textbuffer.get());
421 return sRet;
424 void CSciEdit::SetFont(CString sFontName, int iFontSizeInPoints)
426 Call(SCI_STYLESETFONT, STYLE_DEFAULT, (LPARAM)(LPCSTR)CUnicodeUtils::GetUTF8(sFontName).GetBuffer());
427 Call(SCI_STYLESETSIZE, STYLE_DEFAULT, iFontSizeInPoints);
428 Call(SCI_STYLECLEARALL);
430 LPARAM color = (LPARAM)GetSysColor(COLOR_HIGHLIGHT);
431 // set the styles for the bug ID strings
432 Call(SCI_STYLESETBOLD, STYLE_ISSUEBOLD, (LPARAM)TRUE);
433 Call(SCI_STYLESETFORE, STYLE_ISSUEBOLD, color);
434 Call(SCI_STYLESETBOLD, STYLE_ISSUEBOLDITALIC, (LPARAM)TRUE);
435 Call(SCI_STYLESETITALIC, STYLE_ISSUEBOLDITALIC, (LPARAM)TRUE);
436 Call(SCI_STYLESETFORE, STYLE_ISSUEBOLDITALIC, color);
437 Call(SCI_STYLESETHOTSPOT, STYLE_ISSUEBOLDITALIC, (LPARAM)TRUE);
439 // set the formatted text styles
440 Call(SCI_STYLESETBOLD, STYLE_BOLD, (LPARAM)TRUE);
441 Call(SCI_STYLESETITALIC, STYLE_ITALIC, (LPARAM)TRUE);
442 Call(SCI_STYLESETUNDERLINE, STYLE_UNDERLINED, (LPARAM)TRUE);
444 // set the style for URLs
445 Call(SCI_STYLESETFORE, STYLE_URL, color);
446 Call(SCI_STYLESETHOTSPOT, STYLE_URL, (LPARAM)TRUE);
448 Call(SCI_SETHOTSPOTACTIVEUNDERLINE, (LPARAM)TRUE);
451 void CSciEdit::SetAutoCompletionList(const std::map<CString, int>& list, TCHAR separator, TCHAR typeSeparator)
453 //copy the auto completion list.
455 //SK: instead of creating a copy of that list, we could accept a pointer
456 //to the list and use that instead. But then the caller would have to make
457 //sure that the list persists over the lifetime of the control!
458 m_autolist.clear();
459 m_autolist = list;
460 m_separator = separator;
461 m_typeSeparator = typeSeparator;
464 BOOL CSciEdit::IsMisspelled(const CString& sWord)
466 // convert the string from the control to the encoding of the spell checker module.
467 CStringA sWordA = GetWordForSpellChecker(sWord);
469 // words starting with a digit are treated as correctly spelled
470 if (_istdigit(sWord.GetAt(0)))
471 return FALSE;
472 // words in the personal dictionary are correct too
473 if (m_personalDict.FindWord(sWord))
474 return FALSE;
476 // now we actually check the spelling...
477 if (!pChecker->spell(sWordA))
479 // the word is marked as misspelled, we now check whether the word
480 // is maybe a composite identifier
481 // a composite identifier consists of multiple words, with each word
482 // separated by a change in lower to uppercase letters
483 if (sWord.GetLength() > 1)
485 int wordstart = 0;
486 int wordend = 1;
487 while (wordend < sWord.GetLength())
489 while ((wordend < sWord.GetLength())&&(!_istupper(sWord[wordend])))
490 wordend++;
491 if ((wordstart == 0)&&(wordend == sWord.GetLength()))
493 // words in the auto list are also assumed correctly spelled
494 if (m_autolist.find(sWord) != m_autolist.end())
495 return FALSE;
496 return TRUE;
498 sWordA = GetWordForSpellChecker(sWord.Mid(wordstart, wordend - wordstart));
499 if ((sWordA.GetLength() > 2)&&(!pChecker->spell(sWordA)))
501 return TRUE;
503 wordstart = wordend;
504 wordend++;
508 return FALSE;
511 void CSciEdit::CheckSpelling(int startpos, int endpos)
513 if (pChecker == NULL)
514 return;
516 TEXTRANGEA textrange;
517 textrange.chrg.cpMin = startpos;
518 textrange.chrg.cpMax = (LONG)textrange.chrg.cpMin;
519 LRESULT lastpos = endpos;
520 if (lastpos < 0)
521 lastpos = Call(SCI_GETLENGTH)-textrange.chrg.cpMin;
522 Call(SCI_SETINDICATORCURRENT, INDIC_MISSPELLED);
523 while (textrange.chrg.cpMax < lastpos)
525 textrange.chrg.cpMin = (LONG)Call(SCI_WORDSTARTPOSITION, textrange.chrg.cpMax+1, TRUE);
526 if (textrange.chrg.cpMin < textrange.chrg.cpMax)
527 break;
528 textrange.chrg.cpMax = (LONG)Call(SCI_WORDENDPOSITION, textrange.chrg.cpMin, TRUE);
529 if (textrange.chrg.cpMin == textrange.chrg.cpMax)
531 textrange.chrg.cpMax++;
532 // since Scintilla squiggles to the end of the text even if told to stop one char before it,
533 // we have to clear here the squiggly lines to the end.
534 if (textrange.chrg.cpMin)
535 Call(SCI_INDICATORCLEARRANGE, textrange.chrg.cpMin-1, textrange.chrg.cpMax - textrange.chrg.cpMin + 1);
536 continue;
538 ATLASSERT(textrange.chrg.cpMax >= textrange.chrg.cpMin);
539 auto textbuffer = std::make_unique<char[]>(textrange.chrg.cpMax - textrange.chrg.cpMin + 2);
540 SecureZeroMemory(textbuffer.get(), textrange.chrg.cpMax - textrange.chrg.cpMin + 2);
541 textrange.lpstrText = textbuffer.get();
542 textrange.chrg.cpMax++;
543 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
544 int len = (int)strlen(textrange.lpstrText);
545 if (len == 0)
547 textrange.chrg.cpMax--;
548 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
549 len = (int)strlen(textrange.lpstrText);
550 textrange.chrg.cpMax++;
551 len++;
553 if (len && textrange.lpstrText[len - 1] == '.')
555 // Try to ignore file names from the auto list.
556 // Do do this, for each word ending with '.' we extract next word and check
557 // whether the combined string is present in auto list.
558 TEXTRANGEA twoWords;
559 twoWords.chrg.cpMin = textrange.chrg.cpMin;
560 twoWords.chrg.cpMax = (LONG)Call(SCI_WORDENDPOSITION, textrange.chrg.cpMax + 1, TRUE);
561 auto twoWordsBuffer = std::make_unique<char[]>(twoWords.chrg.cpMax - twoWords.chrg.cpMin + 1);
562 twoWords.lpstrText = twoWordsBuffer.get();
563 SecureZeroMemory(twoWords.lpstrText, twoWords.chrg.cpMax - twoWords.chrg.cpMin + 1);
564 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&twoWords);
565 CString sWord = StringFromControl(twoWords.lpstrText);
566 if (m_autolist.find(sWord) != m_autolist.end())
568 //mark word as correct (remove the squiggle line)
569 Call(SCI_INDICATORCLEARRANGE, twoWords.chrg.cpMin, twoWords.chrg.cpMax - twoWords.chrg.cpMin);
570 textrange.chrg.cpMax = twoWords.chrg.cpMax;
571 continue;
574 if (len)
575 textrange.lpstrText[len - 1] = 0;
576 textrange.chrg.cpMax--;
577 if (strlen(textrange.lpstrText) > 0)
579 CString sWord = StringFromControl(textrange.lpstrText);
580 if ((GetStyleAt(textrange.chrg.cpMin) != STYLE_URL) && IsMisspelled(sWord))
582 //mark word as misspelled
583 Call(SCI_INDICATORFILLRANGE, textrange.chrg.cpMin, textrange.chrg.cpMax - textrange.chrg.cpMin);
585 else
587 //mark word as correct (remove the squiggle line)
588 Call(SCI_INDICATORCLEARRANGE, textrange.chrg.cpMin, textrange.chrg.cpMax - textrange.chrg.cpMin);
589 Call(SCI_INDICATORCLEARRANGE, textrange.chrg.cpMin, textrange.chrg.cpMax - textrange.chrg.cpMin + 1);
595 void CSciEdit::SuggestSpellingAlternatives()
597 if (pChecker == NULL)
598 return;
599 CString word = GetWordUnderCursor(true);
600 Call(SCI_SETCURRENTPOS, Call(SCI_WORDSTARTPOSITION, Call(SCI_GETCURRENTPOS), TRUE));
601 if (word.IsEmpty())
602 return;
603 char ** wlst = nullptr;
604 int ns = pChecker->suggest(&wlst, GetWordForSpellChecker(word));
605 if (ns > 0)
607 CString suggestions;
608 for (int i=0; i < ns; i++)
610 suggestions.AppendFormat(_T("%s%c%d%c"), (LPCTSTR)GetWordFromSpellChecker(wlst[i]), m_typeSeparator, AUTOCOMPLETE_SPELLING, m_separator);
611 free(wlst[i]);
613 free(wlst);
614 suggestions.TrimRight(m_separator);
615 if (suggestions.IsEmpty())
616 return;
617 Call(SCI_AUTOCSETSEPARATOR, (WPARAM)CStringA(m_separator).GetAt(0));
618 Call(SCI_AUTOCSETTYPESEPARATOR, (WPARAM)m_typeSeparator);
619 Call(SCI_AUTOCSETDROPRESTOFWORD, 1);
620 Call(SCI_AUTOCSHOW, 0, (LPARAM)(LPCSTR)StringForControl(suggestions));
621 return;
623 free(wlst);
626 void CSciEdit::DoAutoCompletion(int nMinPrefixLength)
628 if (m_autolist.empty())
629 return;
630 if (Call(SCI_AUTOCACTIVE))
631 return;
632 CString word = GetWordUnderCursor();
633 if (word.GetLength() < nMinPrefixLength)
634 return; //don't auto complete yet, word is too short
635 int pos = (int)Call(SCI_GETCURRENTPOS);
636 if (pos != Call(SCI_WORDENDPOSITION, pos, TRUE))
637 return; //don't auto complete if we're not at the end of a word
638 CString sAutoCompleteList;
640 std::vector<CString> words;
642 pos = word.Find('-');
644 CString wordLower = word;
645 wordLower.MakeLower();
646 CString wordHigher = word;
647 wordHigher.MakeUpper();
649 words.push_back(wordLower);
650 words.push_back(wordHigher);
652 if (pos >= 0)
654 CString s = wordLower.Left(pos);
655 if (s.GetLength() >= nMinPrefixLength)
656 words.push_back(s);
657 s = wordLower.Mid(pos+1);
658 if (s.GetLength() >= nMinPrefixLength)
659 words.push_back(s);
660 s = wordHigher.Left(pos);
661 if (s.GetLength() >= nMinPrefixLength)
662 words.push_back(wordHigher.Left(pos));
663 s = wordHigher.Mid(pos+1);
664 if (s.GetLength() >= nMinPrefixLength)
665 words.push_back(wordHigher.Mid(pos+1));
668 std::map<CString, int> wordset;
669 for (const auto& w : words)
671 for (auto lowerit = m_autolist.lower_bound(w);
672 lowerit != m_autolist.end(); ++lowerit)
674 int compare = w.CompareNoCase(lowerit->first.Left(w.GetLength()));
675 if (compare>0)
676 continue;
677 else if (compare == 0)
679 wordset.emplace(lowerit->first, lowerit->second);
681 else
683 break;
688 for (const auto& w : wordset)
689 sAutoCompleteList.AppendFormat(_T("%s%c%d%c"), (LPCTSTR)w.first, m_typeSeparator, w.second, m_separator);
691 sAutoCompleteList.TrimRight(m_separator);
692 if (sAutoCompleteList.IsEmpty())
693 return;
695 Call(SCI_AUTOCSETSEPARATOR, (WPARAM)CStringA(m_separator).GetAt(0));
696 Call(SCI_AUTOCSETTYPESEPARATOR, (WPARAM)m_typeSeparator);
697 Call(SCI_AUTOCSHOW, word.GetLength(), (LPARAM)(LPCSTR)StringForControl(sAutoCompleteList));
700 BOOL CSciEdit::OnChildNotify(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pLResult)
702 if (message != WM_NOTIFY)
703 return CWnd::OnChildNotify(message, wParam, lParam, pLResult);
705 LPNMHDR lpnmhdr = (LPNMHDR) lParam;
706 SCNotification * lpSCN = (SCNotification *)lParam;
708 if(lpnmhdr->hwndFrom==m_hWnd)
710 switch(lpnmhdr->code)
712 case SCN_CHARADDED:
714 if ((lpSCN->ch < 32)&&(lpSCN->ch != 13)&&(lpSCN->ch != 10))
715 Call(SCI_DELETEBACK);
716 else
718 DoAutoCompletion(m_nAutoCompleteMinChars);
720 return TRUE;
722 break;
723 case SCN_AUTOCSELECTION:
725 CString text = StringFromControl(lpSCN->text);
726 if (m_autolist[text] == AUTOCOMPLETE_SNIPPET)
728 Call(SCI_AUTOCCANCEL);
729 for (INT_PTR handlerindex = 0; handlerindex < m_arContextHandlers.GetCount(); ++handlerindex)
731 CSciEditContextMenuInterface * pHandler = m_arContextHandlers.GetAt(handlerindex);
732 pHandler->HandleSnippet(m_autolist[text], text, this);
735 return TRUE;
737 case SCN_MODIFIED:
739 if (lpSCN->modificationType & (SC_MOD_INSERTTEXT | SC_MOD_DELETETEXT))
741 LRESULT firstline = Call(SCI_GETFIRSTVISIBLELINE);
742 LRESULT lastline = firstline + Call(SCI_LINESONSCREEN);
743 int firstpos = (int)Call(SCI_POSITIONFROMLINE, firstline);
744 int lastpos = (int)Call(SCI_GETLINEENDPOSITION, lastline);
745 int pos1 = lpSCN->position;
746 int pos2 = pos1 + lpSCN->length;
747 // always use the bigger range
748 firstpos = min(firstpos, pos1);
749 lastpos = max(lastpos, pos2);
750 MarkEnteredBugID(firstpos, lastpos);
751 if (m_bDoStyle)
752 StyleEnteredText(firstpos, lastpos);
754 int startpos = (int)Call(SCI_WORDSTARTPOSITION, firstpos, true);
755 int endpos = (int)Call(SCI_WORDENDPOSITION, lastpos, true);
756 StyleURLs(startpos, endpos);
757 CheckSpelling(startpos, endpos);
758 WrapLines(firstpos, lastpos);
759 Call(SCI_COLOURISE, startpos, endpos);
761 break;
763 case SCN_DWELLSTART:
764 case SCN_HOTSPOTCLICK:
766 TEXTRANGEA textrange;
767 textrange.chrg.cpMin = lpSCN->position;
768 textrange.chrg.cpMax = lpSCN->position;
769 DWORD style = GetStyleAt(lpSCN->position);
770 if (style != STYLE_ISSUEBOLDITALIC && style != STYLE_URL)
771 break;
772 while (GetStyleAt(textrange.chrg.cpMin - 1) == style)
773 --textrange.chrg.cpMin;
774 while (GetStyleAt(textrange.chrg.cpMax + 1) == style)
775 ++textrange.chrg.cpMax;
776 ++textrange.chrg.cpMax;
777 auto textbuffer = std::make_unique<char[]>(textrange.chrg.cpMax - textrange.chrg.cpMin + 1);
778 textrange.lpstrText = textbuffer.get();
779 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
780 CString url;
781 if (style == STYLE_URL)
782 url = StringFromControl(textbuffer.get());
783 else
785 url = m_sUrl;
786 url.Replace(L"%BUGID%", StringFromControl(textbuffer.get()));
788 if (!url.IsEmpty())
790 if (lpnmhdr->code == SCN_HOTSPOTCLICK)
791 ShellExecute(GetParent()->GetSafeHwnd(), _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
792 else
794 CStringA sTextA = StringForControl(url);
795 Call(SCI_CALLTIPSHOW, lpSCN->position + 3, (LPARAM)(LPCSTR)sTextA);
799 break;
800 case SCN_DWELLEND:
801 Call(SCI_CALLTIPCANCEL);
802 break;
805 return CWnd::OnChildNotify(message, wParam, lParam, pLResult);
808 BEGIN_MESSAGE_MAP(CSciEdit, CWnd)
809 ON_WM_KEYDOWN()
810 ON_WM_CONTEXTMENU()
811 END_MESSAGE_MAP()
813 void CSciEdit::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
815 switch (nChar)
817 case (VK_ESCAPE):
819 if ((Call(SCI_AUTOCACTIVE)==0)&&(Call(SCI_CALLTIPACTIVE)==0))
820 ::SendMessage(GetParent()->GetSafeHwnd(), WM_CLOSE, 0, 0);
822 break;
824 CWnd::OnKeyDown(nChar, nRepCnt, nFlags);
827 BOOL CSciEdit::PreTranslateMessage(MSG* pMsg)
829 if (pMsg->message == WM_KEYDOWN)
831 switch (pMsg->wParam)
833 case VK_SPACE:
835 if ((GetKeyState(VK_CONTROL) & 0x8000) && ((GetKeyState(VK_MENU) & 0x8000) == 0))
837 DoAutoCompletion(1);
838 return TRUE;
841 break;
842 case VK_TAB:
843 // The TAB cannot be handled in OnKeyDown because it is too late by then.
845 if ((GetKeyState(VK_CONTROL) & 0x8000) && ((GetKeyState(VK_MENU) & 0x8000) == 0))
847 //Ctrl-Tab was pressed, this means we should provide the user with
848 //a list of possible spell checking alternatives to the word under
849 //the cursor
850 SuggestSpellingAlternatives();
851 return TRUE;
853 else if (!Call(SCI_AUTOCACTIVE))
855 ::PostMessage(GetParent()->GetSafeHwnd(), WM_NEXTDLGCTL, GetKeyState(VK_SHIFT)&0x8000, 0);
856 return TRUE;
859 break;
862 return CWnd::PreTranslateMessage(pMsg);
865 void CSciEdit::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
867 int anchor = (int)Call(SCI_GETANCHOR);
868 int currentpos = (int)Call(SCI_GETCURRENTPOS);
869 int selstart = (int)Call(SCI_GETSELECTIONSTART);
870 int selend = (int)Call(SCI_GETSELECTIONEND);
871 int pointpos = 0;
872 if ((point.x == -1) && (point.y == -1))
874 CRect rect;
875 GetClientRect(&rect);
876 ClientToScreen(&rect);
877 point = rect.CenterPoint();
878 pointpos = (int)Call(SCI_GETCURRENTPOS);
880 else
882 // change the cursor position to the point where the user
883 // right-clicked.
884 CPoint clientpoint = point;
885 ScreenToClient(&clientpoint);
886 pointpos = (int)Call(SCI_POSITIONFROMPOINT, clientpoint.x, clientpoint.y);
888 CString sMenuItemText;
889 CMenu popup;
890 bool bRestoreCursor = true;
891 if (popup.CreatePopupMenu())
893 bool bCanUndo = !!Call(SCI_CANUNDO);
894 bool bCanRedo = !!Call(SCI_CANREDO);
895 bool bHasSelection = (selend-selstart > 0);
896 bool bCanPaste = !!Call(SCI_CANPASTE);
897 bool bIsReadOnly = !!Call(SCI_GETREADONLY);
898 UINT uEnabledMenu = MF_STRING | MF_ENABLED;
899 UINT uDisabledMenu = MF_STRING | MF_GRAYED;
901 // find the word under the cursor
902 CString sWord;
903 if (pointpos)
905 // setting the cursor clears the selection
906 Call(SCI_SETANCHOR, pointpos);
907 Call(SCI_SETCURRENTPOS, pointpos);
908 sWord = GetWordUnderCursor();
909 // restore the selection
910 Call(SCI_SETSELECTIONSTART, selstart);
911 Call(SCI_SETSELECTIONEND, selend);
913 else
914 sWord = GetWordUnderCursor();
915 CStringA worda = GetWordForSpellChecker(sWord);
917 int nCorrections = 1;
918 bool bSpellAdded = false;
919 // check if the word under the cursor is spelled wrong
920 if ((pChecker)&&(!worda.IsEmpty()) && !bIsReadOnly)
922 char ** wlst = nullptr;
923 // get the spell suggestions
924 int ns = pChecker->suggest(&wlst,worda);
925 if (ns > 0)
927 // add the suggestions to the context menu
928 for (int i=0; i < ns; i++)
930 bSpellAdded = true;
931 CString sug = GetWordFromSpellChecker(wlst[i]);
932 popup.InsertMenu((UINT)-1, 0, nCorrections++, sug);
933 free(wlst[i]);
935 free(wlst);
937 else
938 free(wlst);
940 // only add a separator if spelling correction suggestions were added
941 if (bSpellAdded)
942 popup.AppendMenu(MF_SEPARATOR);
944 // also allow the user to add the word to the custom dictionary so
945 // it won't show up as misspelled anymore
946 if ((sWord.GetLength()<PDICT_MAX_WORD_LENGTH)&&((pChecker)&&(m_autolist.find(sWord) == m_autolist.end())&&(!pChecker->spell(worda)))&&
947 (!_istdigit(sWord.GetAt(0)))&&(!m_personalDict.FindWord(sWord)) && !bIsReadOnly)
949 sMenuItemText.Format(IDS_SCIEDIT_ADDWORD, (LPCTSTR)sWord);
950 popup.AppendMenu(uEnabledMenu, SCI_ADDWORD, sMenuItemText);
951 // another separator
952 popup.AppendMenu(MF_SEPARATOR);
955 // add the 'default' entries
956 sMenuItemText.LoadString(IDS_SCIEDIT_UNDO);
957 popup.AppendMenu(bCanUndo ? uEnabledMenu : uDisabledMenu, SCI_UNDO, sMenuItemText);
958 sMenuItemText.LoadString(IDS_SCIEDIT_REDO);
959 popup.AppendMenu(bCanRedo ? uEnabledMenu : uDisabledMenu, SCI_REDO, sMenuItemText);
961 popup.AppendMenu(MF_SEPARATOR);
963 sMenuItemText.LoadString(IDS_SCIEDIT_CUT);
964 popup.AppendMenu(!bIsReadOnly && bHasSelection ? uEnabledMenu : uDisabledMenu, SCI_CUT, sMenuItemText);
965 sMenuItemText.LoadString(IDS_SCIEDIT_COPY);
966 popup.AppendMenu(bHasSelection ? uEnabledMenu : uDisabledMenu, SCI_COPY, sMenuItemText);
967 sMenuItemText.LoadString(IDS_SCIEDIT_PASTE);
968 popup.AppendMenu(bCanPaste ? uEnabledMenu : uDisabledMenu, SCI_PASTE, sMenuItemText);
970 popup.AppendMenu(MF_SEPARATOR);
972 sMenuItemText.LoadString(IDS_SCIEDIT_SELECTALL);
973 popup.AppendMenu(uEnabledMenu, SCI_SELECTALL, sMenuItemText);
975 popup.AppendMenu(MF_SEPARATOR);
977 sMenuItemText.LoadString(IDS_SCIEDIT_SPLITLINES);
978 popup.AppendMenu(bHasSelection ? uEnabledMenu : uDisabledMenu, SCI_LINESSPLIT, sMenuItemText);
980 if (m_arContextHandlers.GetCount() > 0)
981 popup.AppendMenu(MF_SEPARATOR);
983 int nCustoms = nCorrections;
984 // now add any custom context menus
985 for (INT_PTR handlerindex = 0; handlerindex < m_arContextHandlers.GetCount(); ++handlerindex)
987 CSciEditContextMenuInterface * pHandler = m_arContextHandlers.GetAt(handlerindex);
988 pHandler->InsertMenuItems(popup, nCustoms);
990 #if THESAURUS
991 if (nCustoms > nCorrections)
993 // custom menu entries present, so add another separator
994 popup.AppendMenu(MF_SEPARATOR);
997 // add found thesauri to sub menu's
998 CMenu thesaurs;
999 int nThesaurs = 0;
1000 CPtrArray menuArray;
1001 if (thesaurs.CreatePopupMenu())
1003 if ((pThesaur)&&(!worda.IsEmpty()))
1005 mentry * pmean;
1006 worda.MakeLower();
1007 int count = pThesaur->Lookup(worda, worda.GetLength(),&pmean);
1008 if (count)
1010 mentry * pm = pmean;
1011 for (int i=0; i < count; i++)
1013 CMenu * submenu = new CMenu();
1014 menuArray.Add(submenu);
1015 submenu->CreateMenu();
1016 for (int j=0; j < pm->count; j++)
1018 CString sug = CString(pm->psyns[j]);
1019 submenu->InsertMenu((UINT)-1, 0, nCorrections + nCustoms + (nThesaurs++), sug);
1021 thesaurs.InsertMenu((UINT)-1, MF_POPUP, (UINT_PTR)(submenu->m_hMenu), CString(pm->defn));
1022 pm++;
1025 if ((count > 0)&&(point.x >= 0))
1027 #ifdef IDS_SPELLEDIT_THESAURUS
1028 sMenuItemText.LoadString(IDS_SPELLEDIT_THESAURUS);
1029 popup.InsertMenu((UINT)-1, MF_POPUP, (UINT_PTR)thesaurs.m_hMenu, sMenuItemText);
1030 #else
1031 popup.InsertMenu((UINT)-1, MF_POPUP, (UINT_PTR)thesaurs.m_hMenu, _T("Thesaurus"));
1032 #endif
1033 nThesaurs = nCustoms;
1035 else
1037 sMenuItemText.LoadString(IDS_SPELLEDIT_NOTHESAURUS);
1038 popup.AppendMenu(MF_DISABLED | MF_GRAYED | MF_STRING, 0, sMenuItemText);
1041 pThesaur->CleanUpAfterLookup(&pmean, count);
1043 else
1045 sMenuItemText.LoadString(IDS_SPELLEDIT_NOTHESAURUS);
1046 popup.AppendMenu(MF_DISABLED | MF_GRAYED | MF_STRING, 0, sMenuItemText);
1049 #endif
1050 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
1051 switch (cmd)
1053 case 0:
1054 break; // no command selected
1055 case SCI_SELECTALL:
1056 bRestoreCursor = false;
1057 // fall through
1058 case SCI_UNDO:
1059 case SCI_REDO:
1060 case SCI_CUT:
1061 case SCI_COPY:
1062 case SCI_PASTE:
1063 Call(cmd);
1064 break;
1065 case SCI_ADDWORD:
1066 m_personalDict.AddWord(sWord);
1067 CheckSpelling((int)Call(SCI_POSITIONFROMLINE, Call(SCI_GETFIRSTVISIBLELINE)), (int)Call(SCI_POSITIONFROMLINE, Call(SCI_GETFIRSTVISIBLELINE) + Call(SCI_LINESONSCREEN)));
1068 break;
1069 case SCI_LINESSPLIT:
1071 int marker = (int)(Call(SCI_GETEDGECOLUMN) * Call(SCI_TEXTWIDTH, 0, (LPARAM)" "));
1072 if (marker)
1074 Call(SCI_TARGETFROMSELECTION);
1075 Call(SCI_LINESJOIN);
1076 Call(SCI_LINESSPLIT, marker);
1079 break;
1080 default:
1081 if (cmd < nCorrections)
1083 Call(SCI_SETANCHOR, pointpos);
1084 Call(SCI_SETCURRENTPOS, pointpos);
1085 GetWordUnderCursor(true);
1086 CString temp;
1087 popup.GetMenuString(cmd, temp, 0);
1088 // setting the cursor clears the selection
1089 Call(SCI_REPLACESEL, 0, (LPARAM)(LPCSTR)StringForControl(temp));
1091 else if (cmd < (nCorrections+nCustoms))
1093 for (INT_PTR handlerindex = 0; handlerindex < m_arContextHandlers.GetCount(); ++handlerindex)
1095 CSciEditContextMenuInterface * pHandler = m_arContextHandlers.GetAt(handlerindex);
1096 if (pHandler->HandleMenuItemClick(cmd, this))
1097 break;
1100 #if THESAURUS
1101 else if (cmd <= (nThesaurs+nCorrections+nCustoms))
1103 Call(SCI_SETANCHOR, pointpos);
1104 Call(SCI_SETCURRENTPOS, pointpos);
1105 GetWordUnderCursor(true);
1106 CString temp;
1107 thesaurs.GetMenuString(cmd, temp, 0);
1108 Call(SCI_REPLACESEL, 0, (LPARAM)(LPCSTR)StringForControl(temp));
1110 #endif
1112 #ifdef THESAURUS
1113 for (INT_PTR index = 0; index < menuArray.GetCount(); ++index)
1115 CMenu * pMenu = (CMenu*)menuArray[index];
1116 delete pMenu;
1118 #endif
1120 if (bRestoreCursor)
1122 // restore the anchor and cursor position
1123 Call(SCI_SETCURRENTPOS, currentpos);
1124 Call(SCI_SETANCHOR, anchor);
1128 bool CSciEdit::StyleEnteredText(int startstylepos, int endstylepos)
1130 bool bStyled = false;
1131 const int line = (int)Call(SCI_LINEFROMPOSITION, startstylepos);
1132 const int line_number_end = (int)Call(SCI_LINEFROMPOSITION, endstylepos);
1133 for (int line_number = line; line_number <= line_number_end; ++line_number)
1135 int offset = (int)Call(SCI_POSITIONFROMLINE, line_number);
1136 int line_len = (int)Call(SCI_LINELENGTH, line_number);
1137 auto linebuffer = std::make_unique<char[]>(line_len + 1);
1138 Call(SCI_GETLINE, line_number, (LPARAM)linebuffer.get());
1139 linebuffer[line_len] = 0;
1140 int start = 0;
1141 int end = 0;
1142 while (FindStyleChars(linebuffer.get(), '*', start, end))
1144 Call(SCI_STARTSTYLING, start + offset, STYLE_BOLD);
1145 Call(SCI_SETSTYLING, end-start, STYLE_BOLD);
1146 bStyled = true;
1147 start = end;
1149 start = 0;
1150 end = 0;
1151 while (FindStyleChars(linebuffer.get(), '^', start, end))
1153 Call(SCI_STARTSTYLING, start + offset, STYLE_ITALIC);
1154 Call(SCI_SETSTYLING, end-start, STYLE_ITALIC);
1155 bStyled = true;
1156 start = end;
1158 start = 0;
1159 end = 0;
1160 while (FindStyleChars(linebuffer.get(), '_', start, end))
1162 Call(SCI_STARTSTYLING, start + offset, STYLE_UNDERLINED);
1163 Call(SCI_SETSTYLING, end-start, STYLE_UNDERLINED);
1164 bStyled = true;
1165 start = end;
1168 return bStyled;
1171 bool CSciEdit::WrapLines(int startpos, int endpos)
1173 int markerX = (int)(Call(SCI_GETEDGECOLUMN) * Call(SCI_TEXTWIDTH, 0, (LPARAM)" "));
1174 if (markerX)
1176 Call(SCI_SETTARGETSTART, startpos);
1177 Call(SCI_SETTARGETEND, endpos);
1178 Call(SCI_LINESSPLIT, markerX);
1179 return true;
1181 return false;
1184 void CSciEdit::AdvanceUTF8(const char * str, int& pos)
1186 if ((str[pos] & 0xE0)==0xC0)
1188 // utf8 2-byte sequence
1189 pos += 2;
1191 else if ((str[pos] & 0xF0)==0xE0)
1193 // utf8 3-byte sequence
1194 pos += 3;
1196 else if ((str[pos] & 0xF8)==0xF0)
1198 // utf8 4-byte sequence
1199 pos += 4;
1201 else
1202 pos++;
1205 bool CSciEdit::FindStyleChars(const char * line, char styler, int& start, int& end)
1207 int i=0;
1208 int u=0;
1209 while (i < start)
1211 AdvanceUTF8(line, i);
1212 u++;
1215 bool bFoundMarker = false;
1216 CString sULine = CUnicodeUtils::GetUnicode(line);
1217 // find a starting marker
1218 while (line[i] != 0)
1220 if (line[i] == styler)
1222 if ((line[i+1]!=0)&&(IsCharAlphaNumeric(sULine[u+1]))&&
1223 (((u>0)&&(!IsCharAlphaNumeric(sULine[u-1]))) || (u==0)))
1225 start = i+1;
1226 AdvanceUTF8(line, i);
1227 u++;
1228 bFoundMarker = true;
1229 break;
1232 AdvanceUTF8(line, i);
1233 u++;
1235 if (!bFoundMarker)
1236 return false;
1237 // find ending marker
1238 bFoundMarker = false;
1239 while (line[i] != 0)
1241 if (line[i] == styler)
1243 if ((IsCharAlphaNumeric(sULine[u-1]))&&
1244 ((((u+1)<sULine.GetLength())&&(!IsCharAlphaNumeric(sULine[u+1]))) || ((u+1) == sULine.GetLength()))
1247 end = i;
1248 i++;
1249 bFoundMarker = true;
1250 break;
1253 AdvanceUTF8(line, i);
1254 u++;
1256 return bFoundMarker;
1259 BOOL CSciEdit::MarkEnteredBugID(int startstylepos, int endstylepos)
1261 if (m_sCommand.IsEmpty())
1262 return FALSE;
1263 // get the text between the start and end position we have to style
1264 const int line_number = (int)Call(SCI_LINEFROMPOSITION, startstylepos);
1265 int start_pos = (int)Call(SCI_POSITIONFROMLINE, (WPARAM)line_number);
1266 int end_pos = endstylepos;
1268 if (start_pos == end_pos)
1269 return FALSE;
1270 if (start_pos > end_pos)
1272 int switchtemp = start_pos;
1273 start_pos = end_pos;
1274 end_pos = switchtemp;
1277 auto textbuffer = std::make_unique<char[]>(end_pos - start_pos + 2);
1278 TEXTRANGEA textrange;
1279 textrange.lpstrText = textbuffer.get();
1280 textrange.chrg.cpMin = start_pos;
1281 textrange.chrg.cpMax = end_pos;
1282 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
1283 CStringA msg = CStringA(textbuffer.get());
1285 Call(SCI_STARTSTYLING, start_pos, STYLE_MASK);
1289 if (!m_sBugID.IsEmpty())
1291 // match with two regex strings (without grouping!)
1292 const std::tr1::regex regCheck(m_sCommand);
1293 const std::tr1::regex regBugID(m_sBugID);
1294 const std::tr1::sregex_iterator end;
1295 std::string s = msg;
1296 LONG pos = 0;
1297 // note:
1298 // if start_pos is 0, we're styling from the beginning and let the ^ char match the beginning of the line
1299 // that way, the ^ matches the very beginning of the log message and not the beginning of further lines.
1300 // problem is: this only works *while* entering log messages. If a log message is pasted in whole or
1301 // multiple lines are pasted, start_pos can be 0 and styling goes over multiple lines. In that case, those
1302 // additional line starts also match ^
1303 for (std::tr1::sregex_iterator it(s.cbegin(), s.cend(), regCheck, start_pos != 0 ? std::tr1::regex_constants::match_not_bol : std::tr1::regex_constants::match_default); it != end; ++it)
1305 // clear the styles up to the match position
1306 Call(SCI_SETSTYLING, it->position(0)-pos, STYLE_DEFAULT);
1308 // (*it)[0] is the matched string
1309 std::string matchedString = (*it)[0];
1310 LONG matchedpos = 0;
1311 for (std::tr1::sregex_iterator it2(matchedString.cbegin(), matchedString.cend(), regBugID); it2 != end; ++it2)
1313 ATLTRACE("matched id : %s\n", std::string((*it2)[0]).c_str());
1315 // bold style up to the id match
1316 ATLTRACE("position = %ld\n", it2->position(0));
1317 if (it2->position(0))
1318 Call(SCI_SETSTYLING, it2->position(0) - matchedpos, STYLE_ISSUEBOLD);
1319 // bold and recursive style for the bug ID itself
1320 if ((*it2)[0].str().size())
1321 Call(SCI_SETSTYLING, (*it2)[0].str().size(), STYLE_ISSUEBOLDITALIC);
1322 matchedpos = (LONG)(it2->position(0) + (*it2)[0].str().size());
1324 if ((matchedpos)&&(matchedpos < (LONG)matchedString.size()))
1326 Call(SCI_SETSTYLING, matchedString.size() - matchedpos, STYLE_ISSUEBOLD);
1328 pos = (LONG)(it->position(0) + matchedString.size());
1330 // bold style for the rest of the string which isn't matched
1331 if (s.size()-pos)
1332 Call(SCI_SETSTYLING, s.size()-pos, STYLE_DEFAULT);
1334 else
1336 const std::tr1::regex regCheck(m_sCommand);
1337 const std::tr1::sregex_iterator end;
1338 std::string s = msg;
1339 LONG pos = 0;
1340 for (std::tr1::sregex_iterator it(s.cbegin(), s.cend(), regCheck); it != end; ++it)
1342 // clear the styles up to the match position
1343 if (it->position(0) - pos >= 0)
1344 Call(SCI_SETSTYLING, it->position(0) - pos, STYLE_DEFAULT);
1345 pos = (LONG)it->position(0);
1347 const std::tr1::smatch match = *it;
1348 // we define group 1 as the whole issue text and
1349 // group 2 as the bug ID
1350 if (match.size() >= 2)
1352 ATLTRACE("matched id : %s\n", std::string(match[1]).c_str());
1353 if (match[1].first - s.cbegin() - pos >= 0)
1354 Call(SCI_SETSTYLING, match[1].first - s.cbegin() - pos, STYLE_ISSUEBOLD);
1355 Call(SCI_SETSTYLING, std::string(match[1]).size(), STYLE_ISSUEBOLDITALIC);
1356 pos = (LONG)(match[1].second - s.cbegin());
1361 catch (std::exception) {}
1363 return FALSE;
1366 //similar code in AppUtils.cpp
1367 bool CSciEdit::IsValidURLChar(unsigned char ch)
1369 return isalnum(ch) ||
1370 ch == '_' || ch == '/' || ch == ';' || ch == '?' || ch == '&' || ch == '=' ||
1371 ch == '%' || ch == ':' || ch == '.' || ch == '#' || ch == '-' || ch == '+' ||
1372 ch == '|' || ch == '>' || ch == '<';
1375 //similar code in AppUtils.cpp
1376 void CSciEdit::StyleURLs(int startstylepos, int endstylepos)
1378 const int line_number = (int)Call(SCI_LINEFROMPOSITION, startstylepos);
1379 startstylepos = (int)Call(SCI_POSITIONFROMLINE, (WPARAM)line_number);
1381 int len = endstylepos - startstylepos + 1;
1382 auto textbuffer = std::make_unique<char[]>(len + 1);
1383 TEXTRANGEA textrange;
1384 textrange.lpstrText = textbuffer.get();
1385 textrange.chrg.cpMin = startstylepos;
1386 textrange.chrg.cpMax = endstylepos;
1387 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
1388 // we're dealing with utf8 encoded text here, which means one glyph is
1389 // not necessarily one byte/wchar_t
1390 // that's why we use CStringA to still get a correct char index
1391 CStringA msg = textbuffer.get();
1393 int starturl = -1;
1394 for (int i = 0; i <= msg.GetLength(); AdvanceUTF8(msg, i))
1396 if ((i < len) && IsValidURLChar(msg[i]))
1398 if (starturl < 0)
1399 starturl = i;
1401 else
1403 if (starturl >= 0)
1405 bool strip = true;
1406 if (msg[starturl] == '<' && i < len) // try to detect and do not strip URLs put within <>
1408 while (starturl <= i && msg[starturl] == '<') // strip leading '<'
1409 ++starturl;
1410 strip = false;
1411 i = starturl;
1412 while (i < len && msg[i] != '\r' && msg[i] != '\n' && msg[i] != '>') // find first '>' or new line after resetting i to start position
1413 AdvanceUTF8(msg, i);
1415 if (!IsUrl(msg.Mid(starturl, i - starturl)))
1417 starturl = -1;
1418 continue;
1421 int skipTrailing = 0;
1422 while (strip && i - skipTrailing - 1 > starturl && (msg[i - skipTrailing - 1] == '.' || msg[i - skipTrailing - 1] == '-' || msg[i - skipTrailing - 1] == '?' || msg[i - skipTrailing - 1] == ';' || msg[i - skipTrailing - 1] == ':' || msg[i - skipTrailing - 1] == '>' || msg[i - skipTrailing - 1] == '<'))
1423 ++skipTrailing;
1424 ASSERT(startstylepos + i - skipTrailing <= endstylepos);
1425 Call(SCI_STARTSTYLING, startstylepos + starturl, STYLE_URL);
1426 Call(SCI_SETSTYLING, i - starturl - skipTrailing, STYLE_URL);
1428 starturl = -1;
1433 bool CSciEdit::IsUrl(const CStringA& sText)
1435 if (!PathIsURLA(sText))
1436 return false;
1437 for (const CStringA& prefix : { "http://", "https://", "git://", "ftp://", "file://", "mailto:" })
1439 if (sText.Find(prefix) == 0 && sText.GetLength() != prefix.GetLength())
1440 return true;
1442 return false;
1445 CStringA CSciEdit::GetWordForSpellChecker(const CString& sWord)
1447 // convert the string from the control to the encoding of the spell checker module.
1448 CStringA sWordA;
1449 if (m_spellcodepage)
1451 char * buf = sWordA.GetBuffer(sWord.GetLength() * 4 + 1);
1452 int lengthIncTerminator = WideCharToMultiByte(m_spellcodepage, 0, sWord, -1, buf, sWord.GetLength() * 4, NULL, NULL);
1453 if (lengthIncTerminator == 0)
1454 return ""; // converting to the codepage failed
1455 sWordA.ReleaseBuffer(lengthIncTerminator - 1);
1457 else
1458 sWordA = CStringA(sWord);
1460 sWordA.Trim("\'\".,");
1462 return sWordA;
1465 CString CSciEdit::GetWordFromSpellChecker(const CStringA& sWordA)
1467 CString sWord;
1468 if (m_spellcodepage)
1470 wchar_t * buf = sWord.GetBuffer(sWordA.GetLength() * 2);
1471 int lengthIncTerminator = MultiByteToWideChar(m_spellcodepage, 0, sWordA, -1, buf, sWordA.GetLength() * 2);
1472 if (lengthIncTerminator == 0)
1473 return L"";
1474 sWord.ReleaseBuffer(lengthIncTerminator - 1);
1476 else
1477 sWord = CString(sWordA);
1479 sWord.Trim(L"\'\".,");
1481 return sWord;
1484 bool CSciEdit::IsUTF8(LPVOID pBuffer, size_t cb)
1486 if (cb < 2)
1487 return true;
1488 UINT16 * pVal = (UINT16 *)pBuffer;
1489 UINT8 * pVal2 = (UINT8 *)(pVal+1);
1490 // scan the whole buffer for a 0x0000 sequence
1491 // if found, we assume a binary file
1492 for (size_t i=0; i<(cb-2); i=i+2)
1494 if (0x0000 == *pVal++)
1495 return false;
1497 pVal = (UINT16 *)pBuffer;
1498 if (*pVal == 0xFEFF)
1499 return false;
1500 if (cb < 3)
1501 return false;
1502 if (*pVal == 0xBBEF)
1504 if (*pVal2 == 0xBF)
1505 return true;
1507 // check for illegal UTF8 chars
1508 pVal2 = (UINT8 *)pBuffer;
1509 for (size_t i=0; i<cb; ++i)
1511 if ((*pVal2 == 0xC0)||(*pVal2 == 0xC1)||(*pVal2 >= 0xF5))
1512 return false;
1513 pVal2++;
1515 pVal2 = (UINT8 *)pBuffer;
1516 bool bUTF8 = false;
1517 for (size_t i=0; i<(cb-3); ++i)
1519 if ((*pVal2 & 0xE0)==0xC0)
1521 pVal2++;i++;
1522 if ((*pVal2 & 0xC0)!=0x80)
1523 return false;
1524 bUTF8 = true;
1526 if ((*pVal2 & 0xF0)==0xE0)
1528 pVal2++;i++;
1529 if ((*pVal2 & 0xC0)!=0x80)
1530 return false;
1531 pVal2++;i++;
1532 if ((*pVal2 & 0xC0)!=0x80)
1533 return false;
1534 bUTF8 = true;
1536 if ((*pVal2 & 0xF8)==0xF0)
1538 pVal2++;i++;
1539 if ((*pVal2 & 0xC0)!=0x80)
1540 return false;
1541 pVal2++;i++;
1542 if ((*pVal2 & 0xC0)!=0x80)
1543 return false;
1544 pVal2++;i++;
1545 if ((*pVal2 & 0xC0)!=0x80)
1546 return false;
1547 bUTF8 = true;
1549 pVal2++;
1551 if (bUTF8)
1552 return true;
1553 return false;
1556 void CSciEdit::SetAStyle(int style, COLORREF fore, COLORREF back, int size, const char *face)
1558 Call(SCI_STYLESETFORE, style, fore);
1559 Call(SCI_STYLESETBACK, style, back);
1560 if (size >= 1)
1561 Call(SCI_STYLESETSIZE, style, size);
1562 if (face)
1563 Call(SCI_STYLESETFONT, style, reinterpret_cast<LPARAM>(face));
1567 void CSciEdit::SetUDiffStyle()
1569 m_bDoStyle = false;
1570 SetAStyle(STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOWTEXT), ::GetSysColor(COLOR_WINDOW),
1571 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffFontSize", 10),
1572 CUnicodeUtils::StdGetUTF8(CRegStdString(L"Software\\TortoiseGit\\UDiffFontName", L"Courier New")).c_str());
1573 Call(SCI_SETTABWIDTH, CRegStdDWORD(L"Software\\TortoiseGit\\UDiffTabSize", 4));
1575 Call(SCI_SETREADONLY, TRUE);
1576 //LRESULT pix = Call(SCI_TEXTWIDTH, STYLE_LINENUMBER, (LPARAM)"_99999");
1577 //Call(SCI_SETMARGINWIDTHN, 0, pix);
1578 //Call(SCI_SETMARGINWIDTHN, 1);
1579 //Call(SCI_SETMARGINWIDTHN, 2);
1580 //Set the default windows colors for edit controls
1581 Call(SCI_STYLESETFORE, STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOWTEXT));
1582 Call(SCI_STYLESETBACK, STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOW));
1583 Call(SCI_SETSELFORE, TRUE, ::GetSysColor(COLOR_HIGHLIGHTTEXT));
1584 Call(SCI_SETSELBACK, TRUE, ::GetSysColor(COLOR_HIGHLIGHT));
1585 Call(SCI_SETCARETFORE, ::GetSysColor(COLOR_WINDOWTEXT));
1587 //SendEditor(SCI_SETREADONLY, FALSE);
1588 Call(SCI_CLEARALL);
1589 Call(EM_EMPTYUNDOBUFFER);
1590 Call(SCI_SETSAVEPOINT);
1591 Call(SCI_CANCEL);
1592 Call(SCI_SETUNDOCOLLECTION, 0);
1594 Call(SCI_SETUNDOCOLLECTION, 1);
1595 Call(SCI_SETWRAPMODE,SC_WRAP_NONE);
1597 //::SetFocus(m_hWndEdit);
1598 Call(EM_EMPTYUNDOBUFFER);
1599 Call(SCI_SETSAVEPOINT);
1600 Call(SCI_GOTOPOS, 0);
1602 Call(SCI_CLEARDOCUMENTSTYLE, 0, 0);
1603 Call(SCI_SETSTYLEBITS, 5, 0);
1605 HIGHCONTRAST highContrast = { 0 };
1606 highContrast.cbSize = sizeof(HIGHCONTRAST);
1607 if (SystemParametersInfo(SPI_GETHIGHCONTRAST, 0, &highContrast, 0) == TRUE && (highContrast.dwFlags & HCF_HIGHCONTRASTON))
1608 return;
1610 //SetAStyle(SCE_DIFF_DEFAULT, RGB(0, 0, 0));
1611 SetAStyle(SCE_DIFF_COMMAND,
1612 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffForeCommandColor", UDIFF_COLORFORECOMMAND),
1613 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffBackCommandColor", UDIFF_COLORBACKCOMMAND));
1614 SetAStyle(SCE_DIFF_POSITION,
1615 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffForePositionColor", UDIFF_COLORFOREPOSITION),
1616 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffBackPositionColor", UDIFF_COLORBACKPOSITION));
1617 SetAStyle(SCE_DIFF_HEADER,
1618 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffForeHeaderColor", UDIFF_COLORFOREHEADER),
1619 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffBackHeaderColor", UDIFF_COLORBACKHEADER));
1620 SetAStyle(SCE_DIFF_COMMENT,
1621 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffForeCommentColor", UDIFF_COLORFORECOMMENT),
1622 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffBackCommentColor", UDIFF_COLORBACKCOMMENT));
1623 Call(SCI_STYLESETBOLD, SCE_DIFF_COMMENT, TRUE);
1624 SetAStyle(SCE_DIFF_ADDED,
1625 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffForeAddedColor", UDIFF_COLORFOREADDED),
1626 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffBackAddedColor", UDIFF_COLORBACKADDED));
1627 SetAStyle(SCE_DIFF_DELETED,
1628 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffForeRemovedColor", UDIFF_COLORFOREREMOVED),
1629 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffBackRemovedColor", UDIFF_COLORBACKREMOVED));
1631 Call(SCI_SETLEXER, SCLEX_DIFF);
1632 Call(SCI_SETKEYWORDS, 0, (LPARAM)"revision");
1633 Call(SCI_COLOURISE, 0, -1);
1636 int CSciEdit::LoadFromFile(CString &filename)
1638 CAutoFILE fp = _tfsopen(filename, _T("rb"), _SH_DENYWR);
1639 if (!fp)
1640 return -1;
1642 char data[4096] = { 0 };
1643 size_t lenFile = fread(data, 1, sizeof(data), fp);
1644 bool bUTF8 = IsUTF8(data, lenFile);
1645 while (lenFile > 0)
1647 Call(SCI_ADDTEXT, lenFile, reinterpret_cast<LPARAM>(static_cast<char *>(data)));
1648 lenFile = fread(data, 1, sizeof(data), fp);
1650 Call(SCI_SETCODEPAGE, bUTF8 ? SC_CP_UTF8 : GetACP());
1651 return 0;
1654 void CSciEdit::RestyleBugIDs()
1656 int endstylepos = (int)Call(SCI_GETLENGTH);
1657 // clear all styles
1658 Call(SCI_STARTSTYLING, 0, STYLE_MASK);
1659 Call(SCI_SETSTYLING, endstylepos, STYLE_DEFAULT);
1660 // style the bug IDs
1661 MarkEnteredBugID(0, endstylepos);