Replace all calls to LoadIcon and LoadImage with calls to LoadIconWithScaleDown
[TortoiseGit.git] / src / Utils / MiscUI / SciEdit.cpp
blob98f351dac07635f15e16de031a857670e2dfc4dc
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2018 - TortoiseGit
4 // Copyright (C) 2003-2008, 2012-2018 - 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"
29 #include "LoadIconEx.h"
31 void CSciEditContextMenuInterface::InsertMenuItems(CMenu&, int&) {return;}
32 bool CSciEditContextMenuInterface::HandleMenuItemClick(int, CSciEdit *) {return false;}
33 void CSciEditContextMenuInterface::HandleSnippet(int, const CString &, CSciEdit *) { return; }
36 #define STYLE_ISSUEBOLD 11
37 #define STYLE_ISSUEBOLDITALIC 12
38 #define STYLE_BOLD 14
39 #define STYLE_ITALIC 15
40 #define STYLE_UNDERLINED 16
41 #define STYLE_URL 17
42 #define INDIC_MISSPELLED 18
44 #define STYLE_MASK 0x1f
46 #define SCI_ADDWORD 2000
48 struct loc_map {
49 const char * cp;
50 const char * def_enc;
53 struct loc_map enc2locale[] = {
54 {"28591","ISO8859-1"},
55 {"28592","ISO8859-2"},
56 {"28593","ISO8859-3"},
57 {"28594","ISO8859-4"},
58 {"28595","ISO8859-5"},
59 {"28596","ISO8859-6"},
60 {"28597","ISO8859-7"},
61 {"28598","ISO8859-8"},
62 {"28599","ISO8859-9"},
63 {"28605","ISO8859-15"},
64 {"20866","KOI8-R"},
65 {"21866","KOI8-U"},
66 {"1251","microsoft-cp1251"},
67 {"65001","UTF-8"},
71 IMPLEMENT_DYNAMIC(CSciEdit, CWnd)
73 CSciEdit::CSciEdit(void) : m_DirectFunction(NULL)
74 , m_DirectPointer(NULL)
75 , m_spellcodepage(0)
76 , m_separator(0)
77 , m_typeSeparator(1)
78 , m_bDoStyle(false)
79 , m_nAutoCompleteMinChars(3)
80 , m_SpellingCache(2000)
81 , m_blockModifiedHandler(false)
83 m_hModule = ::LoadLibrary(L"SciLexer_tgit.dll");
86 CSciEdit::~CSciEdit(void)
88 m_personalDict.Save();
91 static std::unique_ptr<UINT[]> Icon2Image(HICON hIcon)
93 if (hIcon == nullptr)
94 return nullptr;
96 ICONINFO iconInfo;
97 if (!GetIconInfo(hIcon, &iconInfo))
98 return nullptr;
100 BITMAP bm;
101 if (!GetObject(iconInfo.hbmColor, sizeof(BITMAP), &bm))
102 return nullptr;
104 int width = bm.bmWidth;
105 int height = bm.bmHeight;
106 int bytesPerScanLine = (width * 3 + 3) & 0xFFFFFFFC;
107 int size = bytesPerScanLine * height;
108 BITMAPINFO infoheader;
109 infoheader.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
110 infoheader.bmiHeader.biWidth = width;
111 infoheader.bmiHeader.biHeight = height;
112 infoheader.bmiHeader.biPlanes = 1;
113 infoheader.bmiHeader.biBitCount = 24;
114 infoheader.bmiHeader.biCompression = BI_RGB;
115 infoheader.bmiHeader.biSizeImage = size;
117 auto ptrb = std::make_unique<BYTE[]>(size * 2 + height * width * 4);
118 LPBYTE pixelsIconRGB = ptrb.get();
119 LPBYTE alphaPixels = pixelsIconRGB + size;
120 HDC hDC = CreateCompatibleDC(nullptr);
121 SCOPE_EXIT { DeleteDC(hDC); };
122 HBITMAP hBmpOld = (HBITMAP)SelectObject(hDC, (HGDIOBJ)iconInfo.hbmColor);
123 if (!GetDIBits(hDC, iconInfo.hbmColor, 0, height, (LPVOID)pixelsIconRGB, &infoheader, DIB_RGB_COLORS))
124 return nullptr;
126 SelectObject(hDC, hBmpOld);
127 if (!GetDIBits(hDC, iconInfo.hbmMask, 0,height, (LPVOID)alphaPixels, &infoheader, DIB_RGB_COLORS))
128 return nullptr;
130 auto imagePixels = std::make_unique<UINT[]>(height * width);
131 int lsSrc = width * 3;
132 int vsDest = height - 1;
133 for (int y = 0; y < height; y++)
135 int linePosSrc = (vsDest - y) * lsSrc;
136 int linePosDest = y * width;
137 for (int x = 0; x < width; x++)
139 int currentDestPos = linePosDest + x;
140 int currentSrcPos = linePosSrc + x * 3;
141 imagePixels[currentDestPos] = (((UINT)(
143 ((pixelsIconRGB[currentSrcPos + 2] /*Red*/)
144 | (pixelsIconRGB[currentSrcPos + 1] << 8 /*Green*/))
145 | pixelsIconRGB[currentSrcPos] << 16 /*Blue*/
147 | ((alphaPixels[currentSrcPos] ? 0 : 0xff) << 24))) & 0xffffffff);
150 return imagePixels;
153 void CSciEdit::SetColors(bool recolorize)
155 Call(SCI_STYLESETFORE, STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOWTEXT));
156 Call(SCI_STYLESETBACK, STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOW));
157 Call(SCI_SETSELFORE, TRUE, ::GetSysColor(COLOR_HIGHLIGHTTEXT));
158 Call(SCI_SETSELBACK, TRUE, ::GetSysColor(COLOR_HIGHLIGHT));
159 Call(SCI_SETCARETFORE, ::GetSysColor(COLOR_WINDOWTEXT));
161 if (recolorize)
162 Call(SCI_COLOURISE, 0, -1);
165 void CSciEdit::Init(LONG lLanguage)
167 //Setup the direct access data
168 m_DirectFunction = SendMessage(SCI_GETDIRECTFUNCTION, 0, 0);
169 m_DirectPointer = SendMessage(SCI_GETDIRECTPOINTER, 0, 0);
170 Call(SCI_SETMARGINWIDTHN, 1, 0);
171 Call(SCI_SETUSETABS, 0); //pressing TAB inserts spaces
172 Call(SCI_SETWRAPVISUALFLAGS, SC_WRAPVISUALFLAG_END);
173 Call(SCI_AUTOCSETIGNORECASE, 1);
174 Call(SCI_SETLEXER, SCLEX_CONTAINER);
175 Call(SCI_SETCODEPAGE, SC_CP_UTF8);
176 Call(SCI_AUTOCSETFILLUPS, 0, (LPARAM)"\t([");
177 Call(SCI_AUTOCSETMAXWIDTH, 0);
178 //Set the default windows colors for edit controls
179 SetColors(false);
180 Call(SCI_SETMODEVENTMASK, SC_MOD_INSERTTEXT | SC_MOD_DELETETEXT | SC_PERFORMED_UNDO | SC_PERFORMED_REDO);
181 Call(SCI_INDICSETSTYLE, INDIC_MISSPELLED, INDIC_SQUIGGLE);
182 Call(SCI_INDICSETFORE, INDIC_MISSPELLED, RGB(255,0,0));
183 CStringA sWordChars;
184 CStringA sWhiteSpace;
185 for (int i=0; i<255; ++i)
187 if (i == '\r' || i == '\n')
188 continue;
189 else if (i < 0x20 || i == ' ')
190 sWhiteSpace += (char)i;
191 else if (isalnum(i) || i == '\'' || i == '_' || i == '-')
192 sWordChars += (char)i;
194 Call(SCI_SETWORDCHARS, 0, (LPARAM)(LPCSTR)sWordChars);
195 Call(SCI_SETWHITESPACECHARS, 0, (LPARAM)(LPCSTR)sWhiteSpace);
196 m_bDoStyle = ((DWORD)CRegStdDWORD(L"Software\\TortoiseGit\\StyleCommitMessages", TRUE)) == TRUE;
197 m_nAutoCompleteMinChars = (int)(DWORD)CRegStdDWORD(L"Software\\TortoiseGit\\AutoCompleteMinChars", 3);
198 // look for dictionary files and use them if found
199 if ((lLanguage >= 0) && (((DWORD)CRegStdDWORD(L"Software\\TortoiseGit\\Spellchecker", TRUE)) == TRUE))
201 if (!lLanguage || (lLanguage && !LoadDictionaries(lLanguage)))
203 long langId = GetUserDefaultLCID();
206 LoadDictionaries(langId);
207 DWORD lid = SUBLANGID(langId);
208 lid--;
209 if (lid > 0)
210 langId = MAKELANGID(PRIMARYLANGID(langId), lid);
211 else if (langId == 1033)
212 langId = 0;
213 else
214 langId = 1033;
215 } while (langId && (!pChecker || !pThesaur));
219 Call(SCI_SETEDGEMODE, EDGE_NONE);
220 Call(SCI_SETWRAPMODE, SC_WRAP_WORD);
221 Call(SCI_ASSIGNCMDKEY, SCK_END, SCI_LINEENDWRAP);
222 Call(SCI_ASSIGNCMDKEY, SCK_END + (SCMOD_SHIFT << 16), SCI_LINEENDWRAPEXTEND);
223 Call(SCI_ASSIGNCMDKEY, SCK_HOME, SCI_HOMEWRAP);
224 Call(SCI_ASSIGNCMDKEY, SCK_HOME + (SCMOD_SHIFT << 16), SCI_HOMEWRAPEXTEND);
225 if (CRegStdDWORD(L"Software\\TortoiseGit\\ScintillaDirect2D", FALSE) != FALSE)
227 // set font quality for the popup window, since that window does not use D2D
228 Call(SCI_SETFONTQUALITY, SC_EFF_QUALITY_LCD_OPTIMIZED);
229 // now enable D2D
230 Call(SCI_SETTECHNOLOGY, SC_TECHNOLOGY_DIRECTWRITERETAIN);
231 Call(SCI_SETBUFFEREDDRAW, 0);
236 void CSciEdit::Init(const ProjectProperties& props)
238 Init(props.lProjectLanguage);
239 m_sCommand = CUnicodeUtils::GetUTF8(props.GetCheckRe());
240 m_sBugID = CUnicodeUtils::GetUTF8(props.GetBugIDRe());
241 m_sUrl = CUnicodeUtils::GetUTF8(props.sUrl);
243 Call(SCI_SETMOUSEDWELLTIME, 333);
245 if (props.nLogWidthMarker)
247 Call(SCI_SETWRAPMODE, SC_WRAP_NONE);
248 Call(SCI_SETEDGEMODE, EDGE_LINE);
249 Call(SCI_SETEDGECOLUMN, props.nLogWidthMarker);
250 Call(SCI_SETSCROLLWIDTHTRACKING, TRUE);
251 Call(SCI_SETSCROLLWIDTH, 1);
253 else
255 Call(SCI_SETEDGEMODE, EDGE_NONE);
256 Call(SCI_SETWRAPMODE, SC_WRAP_WORD);
260 void CSciEdit::SetIcon(const std::map<int, UINT> &icons)
262 int iconWidth = GetSystemMetrics(SM_CXSMICON);
263 int iconHeight = GetSystemMetrics(SM_CYSMICON);
264 Call(SCI_RGBAIMAGESETWIDTH, iconWidth);
265 Call(SCI_RGBAIMAGESETHEIGHT, iconHeight);
266 for (auto icon : icons)
268 auto hIcon = LoadIconEx(AfxGetInstanceHandle(), MAKEINTRESOURCE(icon.second), iconWidth, iconHeight);
269 auto bytes = Icon2Image(hIcon);
270 DestroyIcon(hIcon);
271 Call(SCI_REGISTERRGBAIMAGE, icon.first, (LPARAM)bytes.get());
275 BOOL CSciEdit::LoadDictionaries(LONG lLanguageID)
277 //Setup the spell checker and thesaurus
278 TCHAR buf[6] = { 0 };
279 CString sFolderUp = CPathUtils::GetAppParentDirectory();
280 CString sFolderAppData = CPathUtils::GetAppDataDirectory();
281 CString sFile;
283 GetLocaleInfo(MAKELCID(lLanguageID, SORT_DEFAULT), LOCALE_SISO639LANGNAME, buf, _countof(buf));
284 sFile = buf;
285 if (lLanguageID == 2074)
286 sFile += L"-Latn";
287 sFile += L'_';
288 GetLocaleInfo(MAKELCID(lLanguageID, SORT_DEFAULT), LOCALE_SISO3166CTRYNAME, buf, _countof(buf));
289 sFile += buf;
290 if (!pChecker)
292 if ((PathFileExists(sFolderAppData + L"dic\\" + sFile + L".aff")) &&
293 (PathFileExists(sFolderAppData + L"dic\\" + sFile + L".dic")))
295 pChecker = std::make_unique<Hunspell>(CStringA(sFolderAppData + L"dic\\" + sFile + L".aff"), CStringA(sFolderAppData + L"dic\\" + sFile + L".dic"));
297 else if ((PathFileExists(sFolderUp + L"Languages\\" + sFile + L".aff")) &&
298 (PathFileExists(sFolderUp + L"Languages\\" + sFile + L".dic")))
300 pChecker = std::make_unique<Hunspell>(CStringA(sFolderUp + L"Languages\\" + sFile + L".aff"), CStringA(sFolderUp + L"Languages\\" + sFile + L".dic"));
302 if (pChecker)
304 const char* encoding = pChecker->get_dic_encoding();
305 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": %s\n", encoding);
306 m_spellcodepage = 0;
307 for (int i = 0; i < _countof(enc2locale); ++i)
309 if (strcmp(encoding, enc2locale[i].def_enc) == 0)
310 m_spellcodepage = atoi(enc2locale[i].cp);
312 m_personalDict.Init(lLanguageID);
315 #if THESAURUS
316 if (!pThesaur)
318 if ((PathFileExists(sFolderAppData + L"dic\\th_" + sFile + L"_v2.idx")) &&
319 (PathFileExists(sFolderAppData + L"dic\\th_" + sFile + L"_v2.dat")))
321 pThesaur = std::make_unique<MyThes>(CStringA(sFolderAppData + L"dic\\th_" + sFile + L"_v2.idx"), CStringA(sFolderAppData + L"dic\\th_" + sFile + L"_v2.dat"));
323 else if ((PathFileExists(sFolderUp + L"Languages\\th_" + sFile + L"_v2.idx")) &&
324 (PathFileExists(sFolderUp + L"Languages\\th_" + sFile + L"_v2.dat")))
326 pThesaur = std::make_unique<MyThes>(CStringA(sFolderUp + L"Languages\\th_" + sFile + L"_v2.idx"), CStringA(sFolderUp + L"Languages\\th_" + sFile + L"_v2.dat"));
329 #endif
330 if ((pThesaur)||(pChecker))
331 return TRUE;
332 return FALSE;
335 LRESULT CSciEdit::Call(UINT message, WPARAM wParam, LPARAM lParam)
337 ASSERT(::IsWindow(m_hWnd)); //Window must be valid
338 ASSERT(m_DirectFunction); //Direct function must be valid
339 return ((SciFnDirect) m_DirectFunction)(m_DirectPointer, message, wParam, lParam);
342 CString CSciEdit::StringFromControl(const CStringA& text)
344 CString sText;
345 #ifdef UNICODE
346 int codepage = (int)Call(SCI_GETCODEPAGE);
347 int reslen = MultiByteToWideChar(codepage, 0, text, text.GetLength(), 0, 0);
348 MultiByteToWideChar(codepage, 0, text, text.GetLength(), sText.GetBuffer(reslen+1), reslen+1);
349 sText.ReleaseBuffer(reslen);
350 #else
351 sText = text;
352 #endif
353 return sText;
356 CStringA CSciEdit::StringForControl(const CString& text)
358 CStringA sTextA;
359 #ifdef UNICODE
360 int codepage = (int)SendMessage(SCI_GETCODEPAGE);
361 int reslen = WideCharToMultiByte(codepage, 0, text, text.GetLength(), 0, 0, 0, 0);
362 WideCharToMultiByte(codepage, 0, text, text.GetLength(), sTextA.GetBuffer(reslen), reslen, 0, 0);
363 sTextA.ReleaseBuffer(reslen);
364 #else
365 sTextA = text;
366 #endif
367 ATLTRACE("string length %d\n", sTextA.GetLength());
368 return sTextA;
371 void CSciEdit::SetText(const CString& sText)
373 CStringA sTextA = StringForControl(sText);
374 Call(SCI_SETTEXT, 0, (LPARAM)(LPCSTR)sTextA);
376 if (Call(SCI_GETSCROLLWIDTHTRACKING) != 0)
377 Call(SCI_SETSCROLLWIDTH, 1);
379 // Scintilla seems to have problems with strings that
380 // aren't terminated by a newline char. Once that char
381 // is there, it can be removed without problems.
382 // So we add here a newline, then remove it again.
383 Call(SCI_DOCUMENTEND);
384 Call(SCI_NEWLINE);
385 Call(SCI_DELETEBACK);
388 void CSciEdit::InsertText(const CString& sText, bool bNewLine)
390 CStringA sTextA = StringForControl(sText);
391 Call(SCI_REPLACESEL, 0, (LPARAM)(LPCSTR)sTextA);
392 if (bNewLine)
393 Call(SCI_REPLACESEL, 0, (LPARAM)(LPCSTR)"\n");
396 CString CSciEdit::GetText()
398 auto len = (int)Call(SCI_GETTEXT, 0, 0);
399 CStringA sTextA;
400 Call(SCI_GETTEXT, (WPARAM)(len + 1), (LPARAM)(LPCSTR)CStrBufA(sTextA, len + 1));
401 return StringFromControl(sTextA);
404 CString CSciEdit::GetWordUnderCursor(bool bSelectWord, bool allchars)
406 Sci_TextRange textrange;
407 auto pos = (Sci_Position)Call(SCI_GETCURRENTPOS);
408 textrange.chrg.cpMin = (int)Call(SCI_WORDSTARTPOSITION, pos, TRUE);
409 if ((pos == textrange.chrg.cpMin)||(textrange.chrg.cpMin < 0))
410 return CString();
411 textrange.chrg.cpMax = (int)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 CString sRet = StringFromControl(textbuffer.get());
417 if (m_bDoStyle && !allchars)
419 for (const auto styleindicator : { '*', '_', '^' })
421 if (sRet.IsEmpty())
422 break;
423 if (sRet[sRet.GetLength() - 1] == styleindicator)
425 --textrange.chrg.cpMax;
426 sRet.Truncate(sRet.GetLength() - 1);
428 if (sRet.IsEmpty())
429 break;
430 if (sRet[0] == styleindicator)
432 ++textrange.chrg.cpMin;
433 sRet = sRet.Right(sRet.GetLength() - 1);
437 if (bSelectWord)
438 Call(SCI_SETSEL, textrange.chrg.cpMin, textrange.chrg.cpMax);
439 return sRet;
442 void CSciEdit::SetFont(CString sFontName, int iFontSizeInPoints)
444 Call(SCI_STYLESETFONT, STYLE_DEFAULT, (LPARAM)(LPCSTR)CUnicodeUtils::GetUTF8(sFontName).GetBuffer());
445 Call(SCI_STYLESETSIZE, STYLE_DEFAULT, iFontSizeInPoints);
446 Call(SCI_STYLECLEARALL);
448 LPARAM color = (LPARAM)GetSysColor(COLOR_HOTLIGHT);
449 // set the styles for the bug ID strings
450 Call(SCI_STYLESETBOLD, STYLE_ISSUEBOLD, (LPARAM)TRUE);
451 Call(SCI_STYLESETFORE, STYLE_ISSUEBOLD, color);
452 Call(SCI_STYLESETBOLD, STYLE_ISSUEBOLDITALIC, (LPARAM)TRUE);
453 Call(SCI_STYLESETITALIC, STYLE_ISSUEBOLDITALIC, (LPARAM)TRUE);
454 Call(SCI_STYLESETFORE, STYLE_ISSUEBOLDITALIC, color);
455 Call(SCI_STYLESETHOTSPOT, STYLE_ISSUEBOLDITALIC, (LPARAM)TRUE);
457 // set the formatted text styles
458 Call(SCI_STYLESETBOLD, STYLE_BOLD, (LPARAM)TRUE);
459 Call(SCI_STYLESETITALIC, STYLE_ITALIC, (LPARAM)TRUE);
460 Call(SCI_STYLESETUNDERLINE, STYLE_UNDERLINED, (LPARAM)TRUE);
462 // set the style for URLs
463 Call(SCI_STYLESETFORE, STYLE_URL, color);
464 Call(SCI_STYLESETHOTSPOT, STYLE_URL, (LPARAM)TRUE);
466 Call(SCI_SETHOTSPOTACTIVEUNDERLINE, (LPARAM)TRUE);
469 void CSciEdit::SetAutoCompletionList(std::map<CString, int>&& list, TCHAR separator, TCHAR typeSeparator)
471 //copy the auto completion list.
473 //SK: instead of creating a copy of that list, we could accept a pointer
474 //to the list and use that instead. But then the caller would have to make
475 //sure that the list persists over the lifetime of the control!
476 m_autolist.clear();
477 m_autolist = std::move(list);
478 m_separator = separator;
479 m_typeSeparator = typeSeparator;
482 BOOL CSciEdit::IsMisspelled(const CString& sWord)
484 // convert the string from the control to the encoding of the spell checker module.
485 CStringA sWordA = GetWordForSpellChecker(sWord);
487 // words starting with a digit are treated as correctly spelled
488 if (_istdigit(sWord.GetAt(0)))
489 return FALSE;
490 // words in the personal dictionary are correct too
491 if (m_personalDict.FindWord(sWord))
492 return FALSE;
494 // Check spell checking cache first.
495 const BOOL *cacheResult = m_SpellingCache.try_get(std::wstring(sWord, sWord.GetLength()));
496 if (cacheResult)
497 return *cacheResult;
499 // now we actually check the spelling...
500 BOOL misspelled = !pChecker->spell(sWordA);
501 if (misspelled)
503 // the word is marked as misspelled, we now check whether the word
504 // is maybe a composite identifier
505 // a composite identifier consists of multiple words, with each word
506 // separated by a change in lower to uppercase letters
507 misspelled = FALSE;
508 if (sWord.GetLength() > 1)
510 int wordstart = 0;
511 int wordend = 1;
512 while (wordend < sWord.GetLength())
514 while ((wordend < sWord.GetLength())&&(!_istupper(sWord[wordend])))
515 wordend++;
516 if ((wordstart == 0)&&(wordend == sWord.GetLength()))
518 // words in the auto list are also assumed correctly spelled
519 if (m_autolist.find(sWord) != m_autolist.end())
520 misspelled = FALSE;
521 else
522 misspelled = TRUE;
523 break;
525 sWordA = GetWordForSpellChecker(sWord.Mid(wordstart, wordend - wordstart));
526 if ((sWordA.GetLength() > 2) && (!pChecker->spell(sWordA)))
528 misspelled = TRUE;
529 break;
531 wordstart = wordend;
532 wordend++;
537 // Update cache.
538 m_SpellingCache.insert_or_assign(std::wstring(sWord, sWord.GetLength()), misspelled);
539 return misspelled;
542 void CSciEdit::CheckSpelling(Sci_Position startpos, Sci_Position endpos)
544 if (!pChecker)
545 return;
547 Sci_TextRange textrange;
548 textrange.chrg.cpMin = static_cast<Sci_PositionCR>(startpos);
549 textrange.chrg.cpMax = textrange.chrg.cpMin;
550 auto lastpos = endpos;
551 if (lastpos < 0)
552 lastpos = (int)Call(SCI_GETLENGTH) - textrange.chrg.cpMin;
553 Call(SCI_SETINDICATORCURRENT, INDIC_MISSPELLED);
554 while (textrange.chrg.cpMax < lastpos)
556 textrange.chrg.cpMin = (int)Call(SCI_WORDSTARTPOSITION, textrange.chrg.cpMax+1, TRUE);
557 if (textrange.chrg.cpMin < textrange.chrg.cpMax)
558 break;
559 textrange.chrg.cpMax = (int)Call(SCI_WORDENDPOSITION, textrange.chrg.cpMin, TRUE);
560 if (textrange.chrg.cpMin == textrange.chrg.cpMax)
562 textrange.chrg.cpMax++;
563 // since Scintilla squiggles to the end of the text even if told to stop one char before it,
564 // we have to clear here the squiggly lines to the end.
565 if (textrange.chrg.cpMin)
566 Call(SCI_INDICATORCLEARRANGE, textrange.chrg.cpMin-1, textrange.chrg.cpMax - textrange.chrg.cpMin + 1);
567 continue;
569 ATLASSERT(textrange.chrg.cpMax >= textrange.chrg.cpMin);
570 auto textbuffer = std::make_unique<char[]>(textrange.chrg.cpMax - textrange.chrg.cpMin + 2);
571 SecureZeroMemory(textbuffer.get(), textrange.chrg.cpMax - textrange.chrg.cpMin + 2);
572 textrange.lpstrText = textbuffer.get();
573 textrange.chrg.cpMax++;
574 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
575 auto len = (int)strlen(textrange.lpstrText);
576 if (len == 0)
578 textrange.chrg.cpMax--;
579 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
580 len = (int)strlen(textrange.lpstrText);
581 textrange.chrg.cpMax++;
582 len++;
584 if (len && textrange.lpstrText[len - 1] == '.')
586 // Try to ignore file names from the auto list.
587 // Do do this, for each word ending with '.' we extract next word and check
588 // whether the combined string is present in auto list.
589 Sci_TextRange twoWords;
590 twoWords.chrg.cpMin = textrange.chrg.cpMin;
591 twoWords.chrg.cpMax = (int)Call(SCI_WORDENDPOSITION, textrange.chrg.cpMax + 1, TRUE);
592 auto twoWordsBuffer = std::make_unique<char[]>(twoWords.chrg.cpMax - twoWords.chrg.cpMin + 1);
593 twoWords.lpstrText = twoWordsBuffer.get();
594 SecureZeroMemory(twoWords.lpstrText, twoWords.chrg.cpMax - twoWords.chrg.cpMin + 1);
595 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&twoWords);
596 CString sWord = StringFromControl(twoWords.lpstrText);
597 if (m_autolist.find(sWord) != m_autolist.end())
599 //mark word as correct (remove the squiggle line)
600 Call(SCI_INDICATORCLEARRANGE, twoWords.chrg.cpMin, twoWords.chrg.cpMax - twoWords.chrg.cpMin);
601 textrange.chrg.cpMax = twoWords.chrg.cpMax;
602 continue;
605 if (len)
606 textrange.lpstrText[len - 1] = '\0';
607 textrange.chrg.cpMax--;
608 if (textrange.lpstrText[0])
610 CString sWord = StringFromControl(textrange.lpstrText);
611 if ((GetStyleAt(textrange.chrg.cpMin) != STYLE_URL) && IsMisspelled(sWord))
613 //mark word as misspelled
614 Call(SCI_INDICATORFILLRANGE, textrange.chrg.cpMin, textrange.chrg.cpMax - textrange.chrg.cpMin);
616 else
618 //mark word as correct (remove the squiggle line)
619 Call(SCI_INDICATORCLEARRANGE, textrange.chrg.cpMin, textrange.chrg.cpMax - textrange.chrg.cpMin);
620 Call(SCI_INDICATORCLEARRANGE, textrange.chrg.cpMin, textrange.chrg.cpMax - textrange.chrg.cpMin + 1);
626 void CSciEdit::SuggestSpellingAlternatives()
628 if (!pChecker)
629 return;
630 CString word = GetWordUnderCursor(true);
631 Call(SCI_SETCURRENTPOS, Call(SCI_WORDSTARTPOSITION, Call(SCI_GETCURRENTPOS), TRUE));
632 if (word.IsEmpty())
633 return;
634 char ** wlst = nullptr;
635 int ns = pChecker->suggest(&wlst, GetWordForSpellChecker(word));
636 if (ns > 0)
638 CString suggestions;
639 for (int i=0; i < ns; i++)
641 suggestions.AppendFormat(L"%s%c%d%c", (LPCTSTR)GetWordFromSpellChecker(wlst[i]), m_typeSeparator, AUTOCOMPLETE_SPELLING, m_separator);
642 free(wlst[i]);
644 free(wlst);
645 suggestions.TrimRight(m_separator);
646 if (suggestions.IsEmpty())
647 return;
648 Call(SCI_AUTOCSETSEPARATOR, (WPARAM)CStringA(m_separator).GetAt(0));
649 Call(SCI_AUTOCSETTYPESEPARATOR, (WPARAM)m_typeSeparator);
650 Call(SCI_AUTOCSETDROPRESTOFWORD, 1);
651 Call(SCI_AUTOCSHOW, 0, (LPARAM)(LPCSTR)StringForControl(suggestions));
652 return;
654 free(wlst);
657 void CSciEdit::DoAutoCompletion(Sci_Position nMinPrefixLength)
659 if (m_autolist.empty())
660 return;
661 auto pos = (int)(Sci_Position)Call(SCI_GETCURRENTPOS);
662 if (pos != (int)Call(SCI_WORDENDPOSITION, pos, TRUE))
663 return; // don't auto complete if we're not at the end of a word
664 CString word = GetWordUnderCursor();
665 if (word.GetLength() < nMinPrefixLength)
667 word = GetWordUnderCursor(false, true);
668 if (word.GetLength() < nMinPrefixLength)
669 return; // don't auto complete yet, word is too short
671 CString sAutoCompleteList;
673 for (int i = 0; i < 2; ++i)
675 std::vector<CString> words;
677 pos = word.Find('-');
679 CString wordLower = word;
680 wordLower.MakeLower();
681 CString wordHigher = word;
682 wordHigher.MakeUpper();
684 words.push_back(word);
685 words.push_back(wordLower);
686 words.push_back(wordHigher);
688 if (pos >= 0)
690 CString s = wordLower.Left(pos);
691 if (s.GetLength() >= nMinPrefixLength)
692 words.push_back(s);
693 s = wordLower.Mid(pos + 1);
694 if (s.GetLength() >= nMinPrefixLength)
695 words.push_back(s);
696 s = wordHigher.Left(pos);
697 if (s.GetLength() >= nMinPrefixLength)
698 words.push_back(wordHigher.Left(pos));
699 s = wordHigher.Mid(pos + 1);
700 if (s.GetLength() >= nMinPrefixLength)
701 words.push_back(wordHigher.Mid(pos+1));
704 // note: the m_autolist is case-sensitive because
705 // its contents are also used to mark words in it
706 // as correctly spelled. If it would be case-insensitive,
707 // case spelling mistakes would not show up as misspelled.
708 std::map<CString, int> wordset;
709 for (const auto& w : words)
711 for (auto lowerit = m_autolist.lower_bound(w);
712 lowerit != m_autolist.end(); ++lowerit)
714 int compare = w.CompareNoCase(lowerit->first.Left(w.GetLength()));
715 if (compare > 0)
716 continue;
717 else if (compare == 0)
718 wordset.emplace(lowerit->first, lowerit->second);
719 else
720 break;
724 for (const auto& w : wordset)
725 sAutoCompleteList.AppendFormat(L"%s%c%d%c", (LPCTSTR)w.first, m_typeSeparator, w.second, m_separator);
727 sAutoCompleteList.TrimRight(m_separator);
729 if (i == 0)
731 if (sAutoCompleteList.IsEmpty())
733 // retry with all chars
734 word = GetWordUnderCursor(false, true);
736 else
737 break;
739 if (i == 1)
741 if (sAutoCompleteList.IsEmpty())
742 return;
746 Call(SCI_AUTOCSETSEPARATOR, (WPARAM)CStringA(m_separator).GetAt(0));
747 Call(SCI_AUTOCSETTYPESEPARATOR, (WPARAM)m_typeSeparator);
748 auto sForControl = StringForControl(sAutoCompleteList);
749 Call(SCI_AUTOCSHOW, StringForControl(word).GetLength(), (LPARAM)(LPCSTR)sForControl);
752 BOOL CSciEdit::OnChildNotify(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pLResult)
754 if (message != WM_NOTIFY)
755 return CWnd::OnChildNotify(message, wParam, lParam, pLResult);
757 LPNMHDR lpnmhdr = (LPNMHDR) lParam;
758 SCNotification * lpSCN = (SCNotification *)lParam;
760 if(lpnmhdr->hwndFrom==m_hWnd)
762 switch(lpnmhdr->code)
764 case SCN_CHARADDED:
766 if ((lpSCN->ch < 32)&&(lpSCN->ch != 13)&&(lpSCN->ch != 10))
767 Call(SCI_DELETEBACK);
768 else
769 DoAutoCompletion(m_nAutoCompleteMinChars);
770 return TRUE;
772 break;
773 case SCN_AUTOCSELECTION:
775 CString text = StringFromControl(lpSCN->text);
776 if (m_autolist[text] == AUTOCOMPLETE_SNIPPET)
778 Call(SCI_AUTOCCANCEL);
779 for (INT_PTR handlerindex = 0; handlerindex < m_arContextHandlers.GetCount(); ++handlerindex)
781 CSciEditContextMenuInterface * pHandler = m_arContextHandlers.GetAt(handlerindex);
782 pHandler->HandleSnippet(m_autolist[text], text, this);
785 return TRUE;
787 case SCN_STYLENEEDED:
789 auto startpos = (Sci_Position)Call(SCI_GETENDSTYLED);
790 auto endpos = ((SCNotification*)lpnmhdr)->position;
792 auto startwordpos = (int)Call(SCI_WORDSTARTPOSITION, startpos, true);
793 auto endwordpos = (int)Call(SCI_WORDENDPOSITION, endpos, true);
795 MarkEnteredBugID(startwordpos, endwordpos);
796 if (m_bDoStyle)
797 StyleEnteredText(startwordpos, endwordpos);
799 StyleURLs(startwordpos, endwordpos);
800 CheckSpelling(startwordpos, endwordpos);
802 // Tell scintilla editor that we styled all requested range.
803 Call(SCI_STARTSTYLING, endwordpos);
804 Call(SCI_SETSTYLING, 0, 0);
806 break;
807 case SCN_MODIFIED:
809 if (!m_blockModifiedHandler && (lpSCN->modificationType & (SC_MOD_INSERTTEXT | SC_MOD_DELETETEXT)))
811 auto firstline = (int)Call(SCI_GETFIRSTVISIBLELINE);
812 auto lastline = firstline + (int)Call(SCI_LINESONSCREEN);
813 auto firstpos = (Sci_Position)Call(SCI_POSITIONFROMLINE, firstline);
814 auto lastpos = (Sci_Position)Call(SCI_GETLINEENDPOSITION, lastline);
815 auto pos1 = lpSCN->position;
816 auto pos2 = pos1 + lpSCN->length;
817 // always use the bigger range
818 firstpos = min(firstpos, pos1);
819 lastpos = max(lastpos, pos2);
821 WrapLines(firstpos, lastpos);
823 break;
825 case SCN_DWELLSTART:
826 case SCN_HOTSPOTRELEASECLICK:
828 Sci_TextRange textrange;
829 textrange.chrg.cpMin = static_cast<Sci_PositionCR>(lpSCN->position);
830 textrange.chrg.cpMax = static_cast<Sci_PositionCR>(lpSCN->position);
831 auto style = GetStyleAt(lpSCN->position);
832 if (style != STYLE_ISSUEBOLDITALIC && style != STYLE_URL)
833 break;
834 while (GetStyleAt(textrange.chrg.cpMin - 1) == style)
835 --textrange.chrg.cpMin;
836 while (GetStyleAt(textrange.chrg.cpMax + 1) == style)
837 ++textrange.chrg.cpMax;
838 ++textrange.chrg.cpMax;
839 auto textbuffer = std::make_unique<char[]>(textrange.chrg.cpMax - textrange.chrg.cpMin + 1);
840 textrange.lpstrText = textbuffer.get();
841 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
842 CString url;
843 if (style == STYLE_URL)
845 url = StringFromControl(textbuffer.get());
846 if (url.Find(L'@') > 0 && !PathIsURL(url))
847 url = L"mailto:" + url;
849 else
851 url = m_sUrl;
852 url.Replace(L"%BUGID%", StringFromControl(textbuffer.get()));
854 if (!url.IsEmpty())
856 if (lpnmhdr->code == SCN_HOTSPOTRELEASECLICK)
857 ShellExecute(GetParent()->GetSafeHwnd(), L"open", url, nullptr, nullptr, SW_SHOWDEFAULT);
858 else
860 CStringA sTextA = StringForControl(url);
861 Call(SCI_CALLTIPSHOW, lpSCN->position + 3, (LPARAM)(LPCSTR)sTextA);
865 break;
866 case SCN_DWELLEND:
867 Call(SCI_CALLTIPCANCEL);
868 break;
871 return CWnd::OnChildNotify(message, wParam, lParam, pLResult);
874 BEGIN_MESSAGE_MAP(CSciEdit, CWnd)
875 ON_WM_KEYDOWN()
876 ON_WM_CONTEXTMENU()
877 END_MESSAGE_MAP()
879 void CSciEdit::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
881 switch (nChar)
883 case (VK_ESCAPE):
885 if ((Call(SCI_AUTOCACTIVE)==0)&&(Call(SCI_CALLTIPACTIVE)==0))
886 ::SendMessage(GetParent()->GetSafeHwnd(), WM_CLOSE, 0, 0);
888 break;
890 CWnd::OnKeyDown(nChar, nRepCnt, nFlags);
893 BOOL CSciEdit::PreTranslateMessage(MSG* pMsg)
895 if (pMsg->message == WM_KEYDOWN)
897 switch (pMsg->wParam)
899 case VK_SPACE:
901 if ((GetKeyState(VK_CONTROL) & 0x8000) && ((GetKeyState(VK_MENU) & 0x8000) == 0))
903 DoAutoCompletion(1);
904 return TRUE;
907 break;
908 case VK_TAB:
909 // The TAB cannot be handled in OnKeyDown because it is too late by then.
911 if ((GetKeyState(VK_CONTROL) & 0x8000) && ((GetKeyState(VK_MENU) & 0x8000) == 0))
913 //Ctrl-Tab was pressed, this means we should provide the user with
914 //a list of possible spell checking alternatives to the word under
915 //the cursor
916 SuggestSpellingAlternatives();
917 return TRUE;
919 else if (!Call(SCI_AUTOCACTIVE))
921 ::PostMessage(GetParent()->GetSafeHwnd(), WM_NEXTDLGCTL, GetKeyState(VK_SHIFT)&0x8000, 0);
922 return TRUE;
925 break;
928 return CWnd::PreTranslateMessage(pMsg);
931 void CSciEdit::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
933 auto anchor = (Sci_Position)Call(SCI_GETANCHOR);
934 auto currentpos = (Sci_Position)Call(SCI_GETCURRENTPOS);
935 auto selstart = (int)(Sci_Position)Call(SCI_GETSELECTIONSTART);
936 auto selend = (int)(Sci_Position)Call(SCI_GETSELECTIONEND);
937 Sci_Position pointpos = 0;
938 if ((point.x == -1) && (point.y == -1))
940 CRect rect;
941 GetClientRect(&rect);
942 ClientToScreen(&rect);
943 point = rect.CenterPoint();
944 pointpos = (Sci_Position)Call(SCI_GETCURRENTPOS);
946 else
948 // change the cursor position to the point where the user
949 // right-clicked.
950 CPoint clientpoint = point;
951 ScreenToClient(&clientpoint);
952 pointpos = (Sci_Position)Call(SCI_POSITIONFROMPOINT, clientpoint.x, clientpoint.y);
954 CString sMenuItemText;
955 CMenu popup;
956 bool bRestoreCursor = true;
957 if (popup.CreatePopupMenu())
959 bool bCanUndo = !!Call(SCI_CANUNDO);
960 bool bCanRedo = !!Call(SCI_CANREDO);
961 bool bHasSelection = (selend-selstart > 0);
962 bool bCanPaste = !!Call(SCI_CANPASTE);
963 bool bIsReadOnly = !!Call(SCI_GETREADONLY);
964 UINT uEnabledMenu = MF_STRING | MF_ENABLED;
965 UINT uDisabledMenu = MF_STRING | MF_GRAYED;
967 // find the word under the cursor
968 CString sWord;
969 if (pointpos)
971 // setting the cursor clears the selection
972 Call(SCI_SETANCHOR, pointpos);
973 Call(SCI_SETCURRENTPOS, pointpos);
974 sWord = GetWordUnderCursor();
975 // restore the selection
976 Call(SCI_SETSELECTIONSTART, selstart);
977 Call(SCI_SETSELECTIONEND, selend);
979 else
980 sWord = GetWordUnderCursor();
981 CStringA worda = GetWordForSpellChecker(sWord);
983 int nCorrections = 1;
984 bool bSpellAdded = false;
985 // check if the word under the cursor is spelled wrong
986 if ((pChecker)&&(!worda.IsEmpty()) && !bIsReadOnly)
988 char ** wlst = nullptr;
989 // get the spell suggestions
990 int ns = pChecker->suggest(&wlst,worda);
991 if (ns > 0)
993 // add the suggestions to the context menu
994 for (int i=0; i < ns; i++)
996 bSpellAdded = true;
997 CString sug = GetWordFromSpellChecker(wlst[i]);
998 popup.InsertMenu((UINT)-1, 0, nCorrections++, sug);
999 free(wlst[i]);
1001 free(wlst);
1003 else
1004 free(wlst);
1006 // only add a separator if spelling correction suggestions were added
1007 if (bSpellAdded)
1008 popup.AppendMenu(MF_SEPARATOR);
1010 // also allow the user to add the word to the custom dictionary so
1011 // it won't show up as misspelled anymore
1012 if ((sWord.GetLength()<PDICT_MAX_WORD_LENGTH)&&((pChecker)&&(m_autolist.find(sWord) == m_autolist.end())&&(!pChecker->spell(worda)))&&
1013 (!_istdigit(sWord.GetAt(0)))&&(!m_personalDict.FindWord(sWord)) && !bIsReadOnly)
1015 sMenuItemText.Format(IDS_SCIEDIT_ADDWORD, (LPCTSTR)sWord);
1016 popup.AppendMenu(uEnabledMenu, SCI_ADDWORD, sMenuItemText);
1017 // another separator
1018 popup.AppendMenu(MF_SEPARATOR);
1021 // add the 'default' entries
1022 sMenuItemText.LoadString(IDS_SCIEDIT_UNDO);
1023 popup.AppendMenu(bCanUndo ? uEnabledMenu : uDisabledMenu, SCI_UNDO, sMenuItemText);
1024 sMenuItemText.LoadString(IDS_SCIEDIT_REDO);
1025 popup.AppendMenu(bCanRedo ? uEnabledMenu : uDisabledMenu, SCI_REDO, sMenuItemText);
1027 popup.AppendMenu(MF_SEPARATOR);
1029 sMenuItemText.LoadString(IDS_SCIEDIT_CUT);
1030 popup.AppendMenu(!bIsReadOnly && bHasSelection ? uEnabledMenu : uDisabledMenu, SCI_CUT, sMenuItemText);
1031 sMenuItemText.LoadString(IDS_SCIEDIT_COPY);
1032 popup.AppendMenu(bHasSelection ? uEnabledMenu : uDisabledMenu, SCI_COPY, sMenuItemText);
1033 sMenuItemText.LoadString(IDS_SCIEDIT_PASTE);
1034 popup.AppendMenu(bCanPaste ? uEnabledMenu : uDisabledMenu, SCI_PASTE, sMenuItemText);
1036 popup.AppendMenu(MF_SEPARATOR);
1038 sMenuItemText.LoadString(IDS_SCIEDIT_SELECTALL);
1039 popup.AppendMenu(uEnabledMenu, SCI_SELECTALL, sMenuItemText);
1041 if (!bIsReadOnly && Call(SCI_GETEDGECOLUMN))
1043 popup.AppendMenu(MF_SEPARATOR);
1045 sMenuItemText.LoadString(IDS_SCIEDIT_SPLITLINES);
1046 popup.AppendMenu(bHasSelection ? uEnabledMenu : uDisabledMenu, SCI_LINESSPLIT, sMenuItemText);
1049 if (m_arContextHandlers.GetCount() > 0)
1050 popup.AppendMenu(MF_SEPARATOR);
1052 int nCustoms = nCorrections;
1053 // now add any custom context menus
1054 for (INT_PTR handlerindex = 0; handlerindex < m_arContextHandlers.GetCount(); ++handlerindex)
1056 CSciEditContextMenuInterface * pHandler = m_arContextHandlers.GetAt(handlerindex);
1057 pHandler->InsertMenuItems(popup, nCustoms);
1059 #if THESAURUS
1060 // add found thesauri to sub menu's
1061 CMenu thesaurs;
1062 int nThesaurs = 0;
1063 CPtrArray menuArray;
1064 if (thesaurs.CreatePopupMenu())
1066 if ((nCustoms > nCorrections || m_arContextHandlers.IsEmpty()) && !bIsReadOnly)
1067 popup.AppendMenu(MF_SEPARATOR);
1068 if (pThesaur && !worda.IsEmpty() && !bIsReadOnly)
1070 mentry * pmean;
1071 worda.MakeLower();
1072 int count = pThesaur->Lookup(worda, worda.GetLength(),&pmean);
1073 if (count)
1075 mentry * pm = pmean;
1076 for (int i=0; i < count; i++)
1078 CMenu * submenu = new CMenu();
1079 menuArray.Add(submenu);
1080 submenu->CreateMenu();
1081 for (int j=0; j < pm->count; j++)
1083 CString sug = CString(pm->psyns[j]);
1084 submenu->InsertMenu((UINT)-1, 0, nCorrections + nCustoms + (nThesaurs++), sug);
1086 thesaurs.InsertMenu((UINT)-1, MF_POPUP, (UINT_PTR)(submenu->m_hMenu), CString(pm->defn));
1087 pm++;
1090 if ((count > 0)&&(point.x >= 0))
1092 #ifdef IDS_SPELLEDIT_THESAURUS
1093 sMenuItemText.LoadString(IDS_SPELLEDIT_THESAURUS);
1094 popup.InsertMenu((UINT)-1, MF_POPUP, (UINT_PTR)thesaurs.m_hMenu, sMenuItemText);
1095 #else
1096 popup.InsertMenu((UINT)-1, MF_POPUP, (UINT_PTR)thesaurs.m_hMenu, L"Thesaurus");
1097 #endif
1098 nThesaurs = nCustoms;
1100 else
1102 sMenuItemText.LoadString(IDS_SPELLEDIT_NOTHESAURUS);
1103 popup.AppendMenu(MF_DISABLED | MF_GRAYED | MF_STRING, 0, sMenuItemText);
1106 pThesaur->CleanUpAfterLookup(&pmean, count);
1108 else if (!bIsReadOnly)
1110 sMenuItemText.LoadString(IDS_SPELLEDIT_NOTHESAURUS);
1111 popup.AppendMenu(MF_DISABLED | MF_GRAYED | MF_STRING, 0, sMenuItemText);
1114 #endif
1115 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this);
1116 switch (cmd)
1118 case 0:
1119 break; // no command selected
1120 case SCI_SELECTALL:
1121 bRestoreCursor = false;
1122 // fall through
1123 case SCI_UNDO:
1124 case SCI_REDO:
1125 case SCI_CUT:
1126 case SCI_COPY:
1127 case SCI_PASTE:
1128 Call(cmd);
1129 break;
1130 case SCI_ADDWORD:
1131 m_personalDict.AddWord(sWord);
1132 CheckSpelling((Sci_Position)Call(SCI_POSITIONFROMLINE, (int)Call(SCI_GETFIRSTVISIBLELINE)), (Sci_Position)Call(SCI_POSITIONFROMLINE, (int)Call(SCI_GETFIRSTVISIBLELINE) + (int)Call(SCI_LINESONSCREEN)));
1133 break;
1134 case SCI_LINESSPLIT:
1136 auto marker = (int)(Call(SCI_GETEDGECOLUMN) * (int)Call(SCI_TEXTWIDTH, 0, (LPARAM)" "));
1137 if (marker)
1139 m_blockModifiedHandler = true;
1140 SCOPE_EXIT{ m_blockModifiedHandler = false; };
1141 Call(SCI_TARGETFROMSELECTION);
1142 Call(SCI_LINESJOIN);
1143 Call(SCI_LINESSPLIT, marker);
1146 break;
1147 default:
1148 if (cmd < nCorrections)
1150 Call(SCI_SETANCHOR, pointpos);
1151 Call(SCI_SETCURRENTPOS, pointpos);
1152 GetWordUnderCursor(true);
1153 CString temp;
1154 popup.GetMenuString(cmd, temp, 0);
1155 // setting the cursor clears the selection
1156 Call(SCI_REPLACESEL, 0, (LPARAM)(LPCSTR)StringForControl(temp));
1158 else if (cmd < (nCorrections+nCustoms))
1160 for (INT_PTR handlerindex = 0; handlerindex < m_arContextHandlers.GetCount(); ++handlerindex)
1162 CSciEditContextMenuInterface * pHandler = m_arContextHandlers.GetAt(handlerindex);
1163 if (pHandler->HandleMenuItemClick(cmd, this))
1164 break;
1167 #if THESAURUS
1168 else if (cmd <= (nThesaurs+nCorrections+nCustoms))
1170 Call(SCI_SETANCHOR, pointpos);
1171 Call(SCI_SETCURRENTPOS, pointpos);
1172 GetWordUnderCursor(true);
1173 CString temp;
1174 thesaurs.GetMenuString(cmd, temp, 0);
1175 Call(SCI_REPLACESEL, 0, (LPARAM)(LPCSTR)StringForControl(temp));
1177 #endif
1179 #ifdef THESAURUS
1180 for (INT_PTR index = 0; index < menuArray.GetCount(); ++index)
1182 CMenu * pMenu = (CMenu*)menuArray[index];
1183 delete pMenu;
1185 #endif
1187 if (bRestoreCursor)
1189 // restore the anchor and cursor position
1190 Call(SCI_SETCURRENTPOS, currentpos);
1191 Call(SCI_SETANCHOR, anchor);
1195 bool CSciEdit::StyleEnteredText(Sci_Position startstylepos, Sci_Position endstylepos)
1197 bool bStyled = false;
1198 const auto line = (int)Call(SCI_LINEFROMPOSITION, startstylepos);
1199 const auto line_number_end = (int)Call(SCI_LINEFROMPOSITION, endstylepos);
1200 for (auto line_number = line; line_number <= line_number_end; ++line_number)
1202 auto offset = (Sci_Position)Call(SCI_POSITIONFROMLINE, line_number);
1203 auto line_len = (int)Call(SCI_LINELENGTH, line_number);
1204 auto linebuffer = std::make_unique<char[]>(line_len + 1);
1205 Call(SCI_GETLINE, line_number, (LPARAM)linebuffer.get());
1206 linebuffer[line_len] = '\0';
1207 Sci_Position start = 0;
1208 Sci_Position end = 0;
1209 while (FindStyleChars(linebuffer.get(), '*', start, end))
1211 Call(SCI_STARTSTYLING, start + offset, STYLE_BOLD);
1212 Call(SCI_SETSTYLING, end-start, STYLE_BOLD);
1213 bStyled = true;
1214 start = end;
1216 start = 0;
1217 end = 0;
1218 while (FindStyleChars(linebuffer.get(), '^', start, end))
1220 Call(SCI_STARTSTYLING, start + offset, STYLE_ITALIC);
1221 Call(SCI_SETSTYLING, end-start, STYLE_ITALIC);
1222 bStyled = true;
1223 start = end;
1225 start = 0;
1226 end = 0;
1227 while (FindStyleChars(linebuffer.get(), '_', start, end))
1229 Call(SCI_STARTSTYLING, start + offset, STYLE_UNDERLINED);
1230 Call(SCI_SETSTYLING, end-start, STYLE_UNDERLINED);
1231 bStyled = true;
1232 start = end;
1235 return bStyled;
1238 bool CSciEdit::WrapLines(Sci_Position startpos, Sci_Position endpos)
1240 auto markerX = (Sci_Position)(Call(SCI_GETEDGECOLUMN) * (int)Call(SCI_TEXTWIDTH, 0, (LPARAM)" "));
1241 if (markerX)
1243 Call(SCI_SETTARGETSTART, startpos);
1244 Call(SCI_SETTARGETEND, endpos);
1245 Call(SCI_LINESSPLIT, markerX);
1246 return true;
1248 return false;
1251 void CSciEdit::AdvanceUTF8(const char * str, int& pos)
1253 if ((str[pos] & 0xE0)==0xC0)
1255 // utf8 2-byte sequence
1256 pos += 2;
1258 else if ((str[pos] & 0xF0)==0xE0)
1260 // utf8 3-byte sequence
1261 pos += 3;
1263 else if ((str[pos] & 0xF8)==0xF0)
1265 // utf8 4-byte sequence
1266 pos += 4;
1268 else
1269 pos++;
1272 bool CSciEdit::FindStyleChars(const char* line, char styler, Sci_Position& start, Sci_Position& end)
1274 int i=0;
1275 int u=0;
1276 while (i < start)
1278 AdvanceUTF8(line, i);
1279 u++;
1282 bool bFoundMarker = false;
1283 CString sULine = CUnicodeUtils::GetUnicode(line);
1284 // find a starting marker
1285 while (line[i] != 0)
1287 if (line[i] == styler)
1289 if ((line[i+1]!=0)&&(IsCharAlphaNumeric(sULine[u+1]))&&
1290 (((u>0)&&(!IsCharAlphaNumeric(sULine[u-1]))) || (u==0)))
1292 start = i+1;
1293 AdvanceUTF8(line, i);
1294 u++;
1295 bFoundMarker = true;
1296 break;
1299 AdvanceUTF8(line, i);
1300 u++;
1302 if (!bFoundMarker)
1303 return false;
1304 // find ending marker
1305 bFoundMarker = false;
1306 while (line[i])
1308 if (line[i] == styler)
1310 if ((IsCharAlphaNumeric(sULine[u-1]))&&
1311 ((((u+1)<sULine.GetLength())&&(!IsCharAlphaNumeric(sULine[u+1]))) || ((u+1) == sULine.GetLength()))
1314 end = i;
1315 i++;
1316 bFoundMarker = true;
1317 break;
1320 AdvanceUTF8(line, i);
1321 u++;
1323 return bFoundMarker;
1326 BOOL CSciEdit::MarkEnteredBugID(Sci_Position startstylepos, Sci_Position endstylepos)
1328 if (m_sCommand.IsEmpty())
1329 return FALSE;
1330 // get the text between the start and end position we have to style
1331 const auto line_number = (int)Call(SCI_LINEFROMPOSITION, startstylepos);
1332 auto start_pos = (Sci_Position)Call(SCI_POSITIONFROMLINE, (WPARAM)line_number);
1333 auto end_pos = endstylepos;
1335 if (start_pos == end_pos)
1336 return FALSE;
1337 if (start_pos > end_pos)
1339 auto switchtemp = start_pos;
1340 start_pos = end_pos;
1341 end_pos = switchtemp;
1344 auto textbuffer = std::make_unique<char[]>(end_pos - start_pos + 2);
1345 Sci_TextRange textrange;
1346 textrange.lpstrText = textbuffer.get();
1347 textrange.chrg.cpMin = static_cast<Sci_PositionCR>(start_pos);
1348 textrange.chrg.cpMax = static_cast<Sci_PositionCR>(end_pos);
1349 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
1350 CStringA msg = CStringA(textbuffer.get());
1352 Call(SCI_STARTSTYLING, start_pos, STYLE_MASK);
1356 if (!m_sBugID.IsEmpty())
1358 // match with two regex strings (without grouping!)
1359 const std::regex regCheck(m_sCommand);
1360 const std::regex regBugID(m_sBugID);
1361 const std::sregex_iterator end;
1362 std::string s = msg;
1363 LONG pos = 0;
1364 // note:
1365 // if start_pos is 0, we're styling from the beginning and let the ^ char match the beginning of the line
1366 // that way, the ^ matches the very beginning of the log message and not the beginning of further lines.
1367 // problem is: this only works *while* entering log messages. If a log message is pasted in whole or
1368 // multiple lines are pasted, start_pos can be 0 and styling goes over multiple lines. In that case, those
1369 // additional line starts also match ^
1370 for (std::sregex_iterator it(s.cbegin(), s.cend(), regCheck, start_pos != 0 ? std::regex_constants::match_not_bol : std::regex_constants::match_default); it != end; ++it)
1372 // clear the styles up to the match position
1373 Call(SCI_SETSTYLING, it->position(0)-pos, STYLE_DEFAULT);
1375 // (*it)[0] is the matched string
1376 std::string matchedString = (*it)[0];
1377 LONG matchedpos = 0;
1378 for (std::sregex_iterator it2(matchedString.cbegin(), matchedString.cend(), regBugID); it2 != end; ++it2)
1380 ATLTRACE("matched id : %s\n", std::string((*it2)[0]).c_str());
1382 // bold style up to the id match
1383 ATLTRACE("position = %ld\n", it2->position(0));
1384 if (it2->position(0))
1385 Call(SCI_SETSTYLING, it2->position(0) - matchedpos, STYLE_ISSUEBOLD);
1386 // bold and recursive style for the bug ID itself
1387 if ((*it2)[0].str().size())
1388 Call(SCI_SETSTYLING, (*it2)[0].str().size(), STYLE_ISSUEBOLDITALIC);
1389 matchedpos = (LONG)(it2->position(0) + (*it2)[0].str().size());
1391 if ((matchedpos)&&(matchedpos < (LONG)matchedString.size()))
1393 Call(SCI_SETSTYLING, matchedString.size() - matchedpos, STYLE_ISSUEBOLD);
1395 pos = (LONG)(it->position(0) + matchedString.size());
1397 // bold style for the rest of the string which isn't matched
1398 if (s.size()-pos)
1399 Call(SCI_SETSTYLING, s.size()-pos, STYLE_DEFAULT);
1401 else
1403 const std::regex regCheck(m_sCommand);
1404 const std::sregex_iterator end;
1405 std::string s = msg;
1406 LONG pos = 0;
1407 for (std::sregex_iterator it(s.cbegin(), s.cend(), regCheck); it != end; ++it)
1409 // clear the styles up to the match position
1410 if (it->position(0) - pos >= 0)
1411 Call(SCI_SETSTYLING, it->position(0) - pos, STYLE_DEFAULT);
1412 pos = (LONG)it->position(0);
1414 const std::smatch match = *it;
1415 // we define group 1 as the whole issue text and
1416 // group 2 as the bug ID
1417 if (match.size() >= 2)
1419 ATLTRACE("matched id : %s\n", std::string(match[1]).c_str());
1420 if (match[1].first - s.cbegin() - pos >= 0)
1421 Call(SCI_SETSTYLING, match[1].first - s.cbegin() - pos, STYLE_ISSUEBOLD);
1422 Call(SCI_SETSTYLING, std::string(match[1]).size(), STYLE_ISSUEBOLDITALIC);
1423 pos = (LONG)(match[1].second - s.cbegin());
1428 catch (std::exception&) {}
1430 return FALSE;
1433 //similar code in AppUtils.cpp
1434 bool CSciEdit::IsValidURLChar(unsigned char ch)
1436 return isalnum(ch) ||
1437 ch == '_' || ch == '/' || ch == ';' || ch == '?' || ch == '&' || ch == '=' ||
1438 ch == '%' || ch == ':' || ch == '.' || ch == '#' || ch == '-' || ch == '+' ||
1439 ch == '|' || ch == '>' || ch == '<' || ch == '!' || ch == '@' || ch == '~';
1442 //similar code in AppUtils.cpp
1443 void CSciEdit::StyleURLs(Sci_Position startstylepos, Sci_Position endstylepos)
1445 const auto line_number = (int)Call(SCI_LINEFROMPOSITION, startstylepos);
1446 startstylepos = (Sci_Position)Call(SCI_POSITIONFROMLINE, (WPARAM)line_number);
1448 auto len = endstylepos - startstylepos + 1;
1449 auto textbuffer = std::make_unique<char[]>(len + 1);
1450 Sci_TextRange textrange;
1451 textrange.lpstrText = textbuffer.get();
1452 textrange.chrg.cpMin = static_cast<Sci_PositionCR>(startstylepos);
1453 textrange.chrg.cpMax = static_cast<Sci_PositionCR>(endstylepos);
1454 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
1455 // we're dealing with utf8 encoded text here, which means one glyph is
1456 // not necessarily one byte/wchar_t
1457 // that's why we use CStringA to still get a correct char index
1458 CStringA msg = textbuffer.get();
1460 int starturl = -1;
1461 for (int i = 0; i <= msg.GetLength(); AdvanceUTF8(msg, i))
1463 if ((i < len) && IsValidURLChar(msg[i]))
1465 if (starturl < 0)
1466 starturl = i;
1468 else
1470 if (starturl >= 0)
1472 bool strip = true;
1473 if (msg[starturl] == '<' && i < len) // try to detect and do not strip URLs put within <>
1475 while (starturl <= i && msg[starturl] == '<') // strip leading '<'
1476 ++starturl;
1477 strip = false;
1478 i = starturl;
1479 while (i < len && msg[i] != '\r' && msg[i] != '\n' && msg[i] != '>') // find first '>' or new line after resetting i to start position
1480 AdvanceUTF8(msg, i);
1483 int skipTrailing = 0;
1484 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] == '<' || msg[i - skipTrailing - 1] == '!'))
1485 ++skipTrailing;
1487 if (!IsUrlOrEmail(msg.Mid(starturl, i - starturl - skipTrailing)))
1489 starturl = -1;
1490 continue;
1493 ASSERT(startstylepos + i - skipTrailing <= endstylepos);
1494 Call(SCI_STARTSTYLING, startstylepos + starturl, STYLE_URL);
1495 Call(SCI_SETSTYLING, i - starturl - skipTrailing, STYLE_URL);
1497 starturl = -1;
1502 bool CSciEdit::IsUrlOrEmail(const CStringA& sText)
1504 if (!PathIsURLA(sText))
1506 auto atpos = sText.Find('@');
1507 if (atpos <= 0)
1508 return false;
1509 if (sText.Find('.', atpos) <= atpos + 1) // a dot must follow after the @, but not directly after it
1510 return false;
1511 if (sText.Find(':', atpos) < 0) // do not detect git@example.com:something as an email address
1512 return true;
1513 return false;
1515 for (const CStringA& prefix : { "http://", "https://", "git://", "ftp://", "file://", "mailto:" })
1517 if (strncmp(sText, prefix, prefix.GetLength()) == 0 && sText.GetLength() != prefix.GetLength())
1518 return true;
1520 return false;
1523 CStringA CSciEdit::GetWordForSpellChecker(const CString& sWord)
1525 // convert the string from the control to the encoding of the spell checker module.
1526 CStringA sWordA;
1527 if (m_spellcodepage)
1529 char * buf = sWordA.GetBuffer(sWord.GetLength() * 4 + 1);
1530 int lengthIncTerminator = WideCharToMultiByte(m_spellcodepage, 0, sWord, -1, buf, sWord.GetLength() * 4, nullptr, nullptr);
1531 if (lengthIncTerminator == 0)
1532 return ""; // converting to the codepage failed
1533 sWordA.ReleaseBuffer(lengthIncTerminator - 1);
1535 else
1536 sWordA = CStringA(sWord);
1538 sWordA.Trim("\'\".,");
1540 if (m_bDoStyle)
1542 for (const auto styleindicator : { '*', '_', '^' })
1544 if (sWordA.IsEmpty())
1545 break;
1546 if (sWordA[sWordA.GetLength() - 1] == styleindicator)
1547 sWordA.Truncate(sWordA.GetLength() - 1);
1548 if (sWordA.IsEmpty())
1549 break;
1550 if (sWordA[0] == styleindicator)
1551 sWordA = sWordA.Right(sWordA.GetLength() - 1);
1555 return sWordA;
1558 CString CSciEdit::GetWordFromSpellChecker(const CStringA& sWordA)
1560 CString sWord;
1561 if (m_spellcodepage)
1563 wchar_t * buf = sWord.GetBuffer(sWordA.GetLength() * 2);
1564 int lengthIncTerminator = MultiByteToWideChar(m_spellcodepage, 0, sWordA, -1, buf, sWordA.GetLength() * 2);
1565 if (lengthIncTerminator == 0)
1566 return L"";
1567 sWord.ReleaseBuffer(lengthIncTerminator - 1);
1569 else
1570 sWord = CString(sWordA);
1572 sWord.Trim(L"\'\".,");
1574 return sWord;
1577 bool CSciEdit::IsUTF8(LPVOID pBuffer, size_t cb)
1579 if (cb < 2)
1580 return true;
1581 UINT16 * pVal = (UINT16 *)pBuffer;
1582 UINT8 * pVal2 = (UINT8 *)(pVal+1);
1583 // scan the whole buffer for a 0x0000 sequence
1584 // if found, we assume a binary file
1585 for (size_t i=0; i<(cb-2); i=i+2)
1587 if (0x0000 == *pVal++)
1588 return false;
1590 pVal = (UINT16 *)pBuffer;
1591 if (*pVal == 0xFEFF)
1592 return false;
1593 if (cb < 3)
1594 return false;
1595 if (*pVal == 0xBBEF)
1597 if (*pVal2 == 0xBF)
1598 return true;
1600 // check for illegal UTF8 chars
1601 pVal2 = (UINT8 *)pBuffer;
1602 for (size_t i=0; i<cb; ++i)
1604 if ((*pVal2 == 0xC0)||(*pVal2 == 0xC1)||(*pVal2 >= 0xF5))
1605 return false;
1606 pVal2++;
1608 pVal2 = (UINT8 *)pBuffer;
1609 bool bUTF8 = false;
1610 for (size_t i=0; i<(cb-3); ++i)
1612 if ((*pVal2 & 0xE0)==0xC0)
1614 pVal2++;i++;
1615 if ((*pVal2 & 0xC0)!=0x80)
1616 return false;
1617 bUTF8 = true;
1619 if ((*pVal2 & 0xF0)==0xE0)
1621 pVal2++;i++;
1622 if ((*pVal2 & 0xC0)!=0x80)
1623 return false;
1624 pVal2++;i++;
1625 if ((*pVal2 & 0xC0)!=0x80)
1626 return false;
1627 bUTF8 = true;
1629 if ((*pVal2 & 0xF8)==0xF0)
1631 pVal2++;i++;
1632 if ((*pVal2 & 0xC0)!=0x80)
1633 return false;
1634 pVal2++;i++;
1635 if ((*pVal2 & 0xC0)!=0x80)
1636 return false;
1637 pVal2++;i++;
1638 if ((*pVal2 & 0xC0)!=0x80)
1639 return false;
1640 bUTF8 = true;
1642 pVal2++;
1644 if (bUTF8)
1645 return true;
1646 return false;
1649 void CSciEdit::SetAStyle(int style, COLORREF fore, COLORREF back, int size, const char *face)
1651 Call(SCI_STYLESETFORE, style, fore);
1652 Call(SCI_STYLESETBACK, style, back);
1653 if (size >= 1)
1654 Call(SCI_STYLESETSIZE, style, size);
1655 if (face)
1656 Call(SCI_STYLESETFONT, style, reinterpret_cast<LPARAM>(face));
1659 void CSciEdit::SetUDiffStyle()
1661 m_bDoStyle = false;
1662 SetAStyle(STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOWTEXT), ::GetSysColor(COLOR_WINDOW),
1663 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffFontSize", 10),
1664 CUnicodeUtils::StdGetUTF8(CRegStdString(L"Software\\TortoiseGit\\UDiffFontName", L"Consolas")).c_str());
1665 Call(SCI_SETTABWIDTH, CRegStdDWORD(L"Software\\TortoiseGit\\UDiffTabSize", 4));
1667 Call(SCI_SETREADONLY, TRUE);
1668 //LRESULT pix = Call(SCI_TEXTWIDTH, STYLE_LINENUMBER, (LPARAM)"_99999");
1669 //Call(SCI_SETMARGINWIDTHN, 0, pix);
1670 //Call(SCI_SETMARGINWIDTHN, 1);
1671 //Call(SCI_SETMARGINWIDTHN, 2);
1672 //Set the default windows colors for edit controls
1673 SetColors(false);
1675 //SendEditor(SCI_SETREADONLY, FALSE);
1676 Call(SCI_CLEARALL);
1677 Call(EM_EMPTYUNDOBUFFER);
1678 Call(SCI_SETSAVEPOINT);
1679 Call(SCI_CANCEL);
1680 Call(SCI_SETUNDOCOLLECTION, 0);
1682 Call(SCI_SETUNDOCOLLECTION, 1);
1683 Call(SCI_SETWRAPMODE,SC_WRAP_NONE);
1685 //::SetFocus(m_hWndEdit);
1686 Call(EM_EMPTYUNDOBUFFER);
1687 Call(SCI_SETSAVEPOINT);
1688 Call(SCI_GOTOPOS, 0);
1690 Call(SCI_CLEARDOCUMENTSTYLE, 0, 0);
1692 HIGHCONTRAST highContrast = { 0 };
1693 highContrast.cbSize = sizeof(HIGHCONTRAST);
1694 if (SystemParametersInfo(SPI_GETHIGHCONTRAST, 0, &highContrast, 0) == TRUE && (highContrast.dwFlags & HCF_HIGHCONTRASTON))
1696 Call(SCI_SETLEXER, SCLEX_NULL);
1697 return;
1700 //SetAStyle(SCE_DIFF_DEFAULT, RGB(0, 0, 0));
1701 SetAStyle(SCE_DIFF_COMMAND,
1702 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffForeCommandColor", UDIFF_COLORFORECOMMAND),
1703 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffBackCommandColor", UDIFF_COLORBACKCOMMAND));
1704 SetAStyle(SCE_DIFF_POSITION,
1705 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffForePositionColor", UDIFF_COLORFOREPOSITION),
1706 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffBackPositionColor", UDIFF_COLORBACKPOSITION));
1707 SetAStyle(SCE_DIFF_HEADER,
1708 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffForeHeaderColor", UDIFF_COLORFOREHEADER),
1709 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffBackHeaderColor", UDIFF_COLORBACKHEADER));
1710 SetAStyle(SCE_DIFF_COMMENT,
1711 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffForeCommentColor", UDIFF_COLORFORECOMMENT),
1712 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffBackCommentColor", UDIFF_COLORBACKCOMMENT));
1713 Call(SCI_STYLESETBOLD, SCE_DIFF_COMMENT, TRUE);
1714 SetAStyle(SCE_DIFF_ADDED,
1715 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffForeAddedColor", UDIFF_COLORFOREADDED),
1716 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffBackAddedColor", UDIFF_COLORBACKADDED));
1717 SetAStyle(SCE_DIFF_DELETED,
1718 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffForeRemovedColor", UDIFF_COLORFOREREMOVED),
1719 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffBackRemovedColor", UDIFF_COLORBACKREMOVED));
1721 Call(SCI_SETLEXER, SCLEX_DIFF);
1722 Call(SCI_SETKEYWORDS, 0, (LPARAM)"revision");
1723 Call(SCI_COLOURISE, 0, -1);
1726 int CSciEdit::LoadFromFile(CString &filename)
1728 CAutoFILE fp = _wfsopen(filename, L"rb", _SH_DENYWR);
1729 if (!fp)
1730 return -1;
1732 char data[4096] = { 0 };
1733 size_t lenFile = fread(data, 1, sizeof(data), fp);
1734 bool bUTF8 = IsUTF8(data, lenFile);
1735 while (lenFile > 0)
1737 Call(SCI_ADDTEXT, lenFile, reinterpret_cast<LPARAM>(static_cast<char *>(data)));
1738 lenFile = fread(data, 1, sizeof(data), fp);
1740 Call(SCI_SETCODEPAGE, bUTF8 ? SC_CP_UTF8 : GetACP());
1741 return 0;
1744 void CSciEdit::RestyleBugIDs()
1746 auto endstylepos = (int)Call(SCI_GETLENGTH);
1747 // clear all styles
1748 Call(SCI_STARTSTYLING, 0, STYLE_MASK);
1749 Call(SCI_SETSTYLING, endstylepos, STYLE_DEFAULT);
1750 // style the bug IDs
1751 MarkEnteredBugID(0, endstylepos);
1754 ULONG CSciEdit::GetGestureStatus(CPoint /*ptTouch*/)
1756 return 0;