Optimize SCN_MODIFIED handling
[TortoiseGit.git] / src / Utils / MiscUI / SciEdit.cpp
bloba5dfe8d944339d3ca8650379beb364c0ea49b7c2
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 "SysInfo.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 if (pChecker)
91 delete pChecker;
92 if (pThesaur)
93 delete pThesaur;
96 static LPBYTE Icon2Image(HICON hIcon)
98 if (hIcon == nullptr)
99 return nullptr;
101 ICONINFO iconInfo;
102 if (!GetIconInfo(hIcon, &iconInfo))
103 return nullptr;
105 BITMAP bm;
106 if (!GetObject(iconInfo.hbmColor, sizeof(BITMAP), &bm))
107 return nullptr;
109 int width = bm.bmWidth;
110 int height = bm.bmHeight;
111 int bytesPerScanLine = (width * 3 + 3) & 0xFFFFFFFC;
112 int size = bytesPerScanLine * height;
113 BITMAPINFO infoheader;
114 infoheader.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
115 infoheader.bmiHeader.biWidth = width;
116 infoheader.bmiHeader.biHeight = height;
117 infoheader.bmiHeader.biPlanes = 1;
118 infoheader.bmiHeader.biBitCount = 24;
119 infoheader.bmiHeader.biCompression = BI_RGB;
120 infoheader.bmiHeader.biSizeImage = size;
122 std::unique_ptr<BYTE> ptrb(new BYTE[(size * 2 + height * width * 4)]);
123 LPBYTE pixelsIconRGB = ptrb.get();
124 LPBYTE alphaPixels = pixelsIconRGB + size;
125 HDC hDC = CreateCompatibleDC(nullptr);
126 HBITMAP hBmpOld = (HBITMAP)SelectObject(hDC, (HGDIOBJ)iconInfo.hbmColor);
127 if (!GetDIBits(hDC, iconInfo.hbmColor, 0, height, (LPVOID)pixelsIconRGB, &infoheader, DIB_RGB_COLORS))
129 DeleteDC(hDC);
130 return nullptr;
133 SelectObject(hDC, hBmpOld);
134 if (!GetDIBits(hDC, iconInfo.hbmMask, 0,height, (LPVOID)alphaPixels, &infoheader, DIB_RGB_COLORS))
136 DeleteDC(hDC);
137 return nullptr;
140 DeleteDC(hDC);
141 UINT* imagePixels = new UINT[height * width];
142 int lsSrc = width * 3;
143 int vsDest = height - 1;
144 for (int y = 0; y < height; y++)
146 int linePosSrc = (vsDest - y) * lsSrc;
147 int linePosDest = y * width;
148 for (int x = 0; x < width; x++)
150 int currentDestPos = linePosDest + x;
151 int currentSrcPos = linePosSrc + x * 3;
152 imagePixels[currentDestPos] = (((UINT)(
154 ((pixelsIconRGB[currentSrcPos + 2] /*Red*/)
155 | (pixelsIconRGB[currentSrcPos + 1] << 8 /*Green*/))
156 | pixelsIconRGB[currentSrcPos] << 16 /*Blue*/
158 | ((alphaPixels[currentSrcPos] ? 0 : 0xff) << 24))) & 0xffffffff);
161 return (LPBYTE)imagePixels;
164 void CSciEdit::Init(LONG lLanguage, BOOL bLoadSpellCheck)
166 //Setup the direct access data
167 m_DirectFunction = SendMessage(SCI_GETDIRECTFUNCTION, 0, 0);
168 m_DirectPointer = SendMessage(SCI_GETDIRECTPOINTER, 0, 0);
169 Call(SCI_SETMARGINWIDTHN, 1, 0);
170 Call(SCI_SETUSETABS, 0); //pressing TAB inserts spaces
171 Call(SCI_SETWRAPVISUALFLAGS, SC_WRAPVISUALFLAG_END);
172 Call(SCI_AUTOCSETIGNORECASE, 1);
173 Call(SCI_SETLEXER, SCLEX_CONTAINER);
174 Call(SCI_SETCODEPAGE, SC_CP_UTF8);
175 Call(SCI_AUTOCSETFILLUPS, 0, (LPARAM)"\t([");
176 Call(SCI_AUTOCSETMAXWIDTH, 0);
177 //Set the default windows colors for edit controls
178 Call(SCI_STYLESETFORE, STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOWTEXT));
179 Call(SCI_STYLESETBACK, STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOW));
180 Call(SCI_SETSELFORE, TRUE, ::GetSysColor(COLOR_HIGHLIGHTTEXT));
181 Call(SCI_SETSELBACK, TRUE, ::GetSysColor(COLOR_HIGHLIGHT));
182 Call(SCI_SETCARETFORE, ::GetSysColor(COLOR_WINDOWTEXT));
183 Call(SCI_SETMODEVENTMASK, SC_MOD_INSERTTEXT | SC_MOD_DELETETEXT | SC_PERFORMED_UNDO | SC_PERFORMED_REDO);
184 Call(SCI_INDICSETSTYLE, INDIC_MISSPELLED, INDIC_SQUIGGLE);
185 Call(SCI_INDICSETFORE, INDIC_MISSPELLED, RGB(255,0,0));
186 CStringA sWordChars;
187 CStringA sWhiteSpace;
188 for (int i=0; i<255; ++i)
190 if (i == '\r' || i == '\n')
191 continue;
192 else if (i < 0x20 || i == ' ')
193 sWhiteSpace += (char)i;
194 else if (isalnum(i) || i == '\'' || i == '_' || i == '-')
195 sWordChars += (char)i;
197 Call(SCI_SETWORDCHARS, 0, (LPARAM)(LPCSTR)sWordChars);
198 Call(SCI_SETWHITESPACECHARS, 0, (LPARAM)(LPCSTR)sWhiteSpace);
199 m_bDoStyle = ((DWORD)CRegStdDWORD(_T("Software\\TortoiseGit\\StyleCommitMessages"), TRUE))==TRUE;
200 m_nAutoCompleteMinChars = (int)(DWORD)CRegStdDWORD(_T("Software\\TortoiseGit\\AutoCompleteMinChars"), 3);
201 // look for dictionary files and use them if found
202 long langId = GetUserDefaultLCID();
204 if(bLoadSpellCheck)
206 if ((lLanguage != 0)||(((DWORD)CRegStdDWORD(_T("Software\\TortoiseGit\\Spellchecker"), FALSE))==FALSE))
208 if (!((lLanguage)&&(!LoadDictionaries(lLanguage))))
212 LoadDictionaries(langId);
213 DWORD lid = SUBLANGID(langId);
214 lid--;
215 if (lid > 0)
217 langId = MAKELANGID(PRIMARYLANGID(langId), lid);
219 else if (langId == 1033)
220 langId = 0;
221 else
222 langId = 1033;
223 } while ((langId)&&((pChecker==NULL)||(pThesaur==NULL)));
227 Call(SCI_SETEDGEMODE, EDGE_NONE);
228 Call(SCI_SETWRAPMODE, SC_WRAP_WORD);
229 Call(SCI_ASSIGNCMDKEY, SCK_END, SCI_LINEENDWRAP);
230 Call(SCI_ASSIGNCMDKEY, SCK_END + (SCMOD_SHIFT << 16), SCI_LINEENDWRAPEXTEND);
231 Call(SCI_ASSIGNCMDKEY, SCK_HOME, SCI_HOMEWRAP);
232 Call(SCI_ASSIGNCMDKEY, SCK_HOME + (SCMOD_SHIFT << 16), SCI_HOMEWRAPEXTEND);
233 CRegStdDWORD used2d(L"Software\\TortoiseGit\\ScintillaDirect2D", FALSE);
234 if (SysInfo::Instance().IsWin7OrLater() && DWORD(used2d))
236 Call(SCI_SETTECHNOLOGY, SC_TECHNOLOGY_DIRECTWRITERETAIN);
237 Call(SCI_SETBUFFEREDDRAW, 0);
242 void CSciEdit::Init(const ProjectProperties& props)
244 Init(props.lProjectLanguage);
245 m_sCommand = CStringA(CUnicodeUtils::GetUTF8(props.GetCheckRe()));
246 m_sBugID = CStringA(CUnicodeUtils::GetUTF8(props.GetBugIDRe()));
247 m_sUrl = CStringA(CUnicodeUtils::GetUTF8(props.sUrl));
249 Call(SCI_SETMOUSEDWELLTIME, 333);
251 if (props.nLogWidthMarker)
253 Call(SCI_SETWRAPMODE, SC_WRAP_NONE);
254 Call(SCI_SETEDGEMODE, EDGE_LINE);
255 Call(SCI_SETEDGECOLUMN, props.nLogWidthMarker);
257 else
259 Call(SCI_SETEDGEMODE, EDGE_NONE);
260 Call(SCI_SETWRAPMODE, SC_WRAP_WORD);
264 void CSciEdit::SetIcon(const std::map<int, UINT> &icons)
266 Call(SCI_RGBAIMAGESETWIDTH, 16);
267 Call(SCI_RGBAIMAGESETHEIGHT, 16);
268 for (auto icon : icons)
270 auto hIcon = (HICON)::LoadImage(AfxGetInstanceHandle(), MAKEINTRESOURCE(icon.second), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);
271 std::unique_ptr<BYTE> bytes(Icon2Image(hIcon));
272 DestroyIcon(hIcon);
273 Call(SCI_REGISTERRGBAIMAGE, icon.first, (LPARAM)bytes.get());
277 BOOL CSciEdit::LoadDictionaries(LONG lLanguageID)
279 //Setup the spell checker and thesaurus
280 TCHAR buf[6] = { 0 };
281 CString sFolder = CPathUtils::GetAppDirectory();
282 CString sFolderUp = CPathUtils::GetAppParentDirectory();
283 CString sFolderAppData = CPathUtils::GetAppDataDirectory();
284 CString sFile;
286 GetLocaleInfo(MAKELCID(lLanguageID, SORT_DEFAULT), LOCALE_SISO639LANGNAME, buf, _countof(buf));
287 sFile = buf;
288 if (lLanguageID == 2074)
289 sFile += _T("-Latn");
290 sFile += _T("_");
291 GetLocaleInfo(MAKELCID(lLanguageID, SORT_DEFAULT), LOCALE_SISO3166CTRYNAME, buf, _countof(buf));
292 sFile += buf;
293 if (pChecker==NULL)
295 if ((PathFileExists(sFolderAppData + _T("dic\\") + sFile + _T(".aff"))) &&
296 (PathFileExists(sFolderAppData + _T("dic\\") + sFile + _T(".dic"))))
298 pChecker = new Hunspell(CStringA(sFolderAppData + _T("dic\\") + sFile + _T(".aff")), CStringA(sFolderAppData + _T("dic\\") + sFile + _T(".dic")));
300 else if ((PathFileExists(sFolder + sFile + _T(".aff"))) &&
301 (PathFileExists(sFolder + sFile + _T(".dic"))))
303 pChecker = new Hunspell(CStringA(sFolder + sFile + _T(".aff")), CStringA(sFolder + sFile + _T(".dic")));
305 else if ((PathFileExists(sFolder + _T("dic\\") + sFile + _T(".aff"))) &&
306 (PathFileExists(sFolder + _T("dic\\") + sFile + _T(".dic"))))
308 pChecker = new Hunspell(CStringA(sFolder + _T("dic\\") + sFile + _T(".aff")), CStringA(sFolder + _T("dic\\") + sFile + _T(".dic")));
310 else if ((PathFileExists(sFolderUp + sFile + _T(".aff"))) &&
311 (PathFileExists(sFolderUp + sFile + _T(".dic"))))
313 pChecker = new Hunspell(CStringA(sFolderUp + sFile + _T(".aff")), CStringA(sFolderUp + sFile + _T(".dic")));
315 else if ((PathFileExists(sFolderUp + _T("dic\\") + sFile + _T(".aff"))) &&
316 (PathFileExists(sFolderUp + _T("dic\\") + sFile + _T(".dic"))))
318 pChecker = new Hunspell(CStringA(sFolderUp + _T("dic\\") + sFile + _T(".aff")), CStringA(sFolderUp + _T("dic\\") + sFile + _T(".dic")));
320 else if ((PathFileExists(sFolderUp + _T("Languages\\") + sFile + _T(".aff"))) &&
321 (PathFileExists(sFolderUp + _T("Languages\\") + sFile + _T(".dic"))))
323 pChecker = new Hunspell(CStringA(sFolderUp + _T("Languages\\") + sFile + _T(".aff")), CStringA(sFolderUp + _T("Languages\\") + sFile + _T(".dic")));
326 #if THESAURUS
327 if (pThesaur==NULL)
329 if ((PathFileExists(sFolderAppData + _T("dic\\th_") + sFile + _T("_v2.idx"))) &&
330 (PathFileExists(sFolderAppData + _T("dic\\th_") + sFile + _T("_v2.dat"))))
332 pThesaur = new MyThes(CStringA(sFolderAppData + _T("dic\\th_") + sFile + _T("_v2.idx")), CStringA(sFolderAppData + _T("dic\\th_") + sFile + _T("_v2.dat")));
334 else if ((PathFileExists(sFolder + _T("th_") + sFile + _T("_v2.idx"))) &&
335 (PathFileExists(sFolder + _T("th_") + sFile + _T("_v2.dat"))))
337 pThesaur = new MyThes(CStringA(sFolder + sFile + _T("_v2.idx")), CStringA(sFolder + sFile + _T("_v2.dat")));
339 else if ((PathFileExists(sFolder + _T("dic\\th_") + sFile + _T("_v2.idx"))) &&
340 (PathFileExists(sFolder + _T("dic\\th_") + sFile + _T("_v2.dat"))))
342 pThesaur = new MyThes(CStringA(sFolder + _T("dic\\") + sFile + _T("_v2.idx")), CStringA(sFolder + _T("dic\\") + sFile + _T("_v2.dat")));
344 else if ((PathFileExists(sFolderUp + _T("th_") + sFile + _T("_v2.idx"))) &&
345 (PathFileExists(sFolderUp + _T("th_") + sFile + _T("_v2.dat"))))
347 pThesaur = new MyThes(CStringA(sFolderUp + _T("th_") + sFile + _T("_v2.idx")), CStringA(sFolderUp + _T("th_") + sFile + _T("_v2.dat")));
349 else if ((PathFileExists(sFolderUp + _T("dic\\th_") + sFile + _T("_v2.idx"))) &&
350 (PathFileExists(sFolderUp + _T("dic\\th_") + sFile + _T("_v2.dat"))))
352 pThesaur = new MyThes(CStringA(sFolderUp + _T("dic\\th_") + sFile + _T("_v2.idx")), CStringA(sFolderUp + _T("dic\\th_") + sFile + _T("_v2.dat")));
354 else if ((PathFileExists(sFolderUp + _T("Languages\\th_") + sFile + _T("_v2.idx"))) &&
355 (PathFileExists(sFolderUp + _T("Languages\\th_") + sFile + _T("_v2.dat"))))
357 pThesaur = new MyThes(CStringA(sFolderUp + _T("Languages\\th_") + sFile + _T("_v2.idx")), CStringA(sFolderUp + _T("Languages\\th_") + sFile + _T("_v2.dat")));
360 #endif
361 if (pChecker)
363 const char * encoding = pChecker->get_dic_encoding();
364 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": %s\n", encoding);
365 int n = _countof(enc2locale);
366 m_spellcodepage = 0;
367 for (int i = 0; i < n; i++)
369 if (strcmp(encoding,enc2locale[i].def_enc) == 0)
371 m_spellcodepage = atoi(enc2locale[i].cp);
374 m_personalDict.Init(lLanguageID);
376 if ((pThesaur)||(pChecker))
377 return TRUE;
378 return FALSE;
381 LRESULT CSciEdit::Call(UINT message, WPARAM wParam, LPARAM lParam)
383 ASSERT(::IsWindow(m_hWnd)); //Window must be valid
384 ASSERT(m_DirectFunction); //Direct function must be valid
385 return ((SciFnDirect) m_DirectFunction)(m_DirectPointer, message, wParam, lParam);
388 CString CSciEdit::StringFromControl(const CStringA& text)
390 CString sText;
391 #ifdef UNICODE
392 int codepage = (int)Call(SCI_GETCODEPAGE);
393 int reslen = MultiByteToWideChar(codepage, 0, text, text.GetLength(), 0, 0);
394 MultiByteToWideChar(codepage, 0, text, text.GetLength(), sText.GetBuffer(reslen+1), reslen+1);
395 sText.ReleaseBuffer(reslen);
396 #else
397 sText = text;
398 #endif
399 return sText;
402 CStringA CSciEdit::StringForControl(const CString& text)
404 CStringA sTextA;
405 #ifdef UNICODE
406 int codepage = (int)SendMessage(SCI_GETCODEPAGE);
407 int reslen = WideCharToMultiByte(codepage, 0, text, text.GetLength(), 0, 0, 0, 0);
408 WideCharToMultiByte(codepage, 0, text, text.GetLength(), sTextA.GetBuffer(reslen), reslen, 0, 0);
409 sTextA.ReleaseBuffer(reslen);
410 #else
411 sTextA = text;
412 #endif
413 ATLTRACE("string length %d\n", sTextA.GetLength());
414 return sTextA;
417 void CSciEdit::SetText(const CString& sText)
419 CStringA sTextA = StringForControl(sText);
420 Call(SCI_SETTEXT, 0, (LPARAM)(LPCSTR)sTextA);
422 // Scintilla seems to have problems with strings that
423 // aren't terminated by a newline char. Once that char
424 // is there, it can be removed without problems.
425 // So we add here a newline, then remove it again.
426 Call(SCI_DOCUMENTEND);
427 Call(SCI_NEWLINE);
428 Call(SCI_DELETEBACK);
431 void CSciEdit::InsertText(const CString& sText, bool bNewLine)
433 CStringA sTextA = StringForControl(sText);
434 Call(SCI_REPLACESEL, 0, (LPARAM)(LPCSTR)sTextA);
435 if (bNewLine)
436 Call(SCI_REPLACESEL, 0, (LPARAM)(LPCSTR)"\n");
439 CString CSciEdit::GetText()
441 LRESULT len = Call(SCI_GETTEXT, 0, 0);
442 CStringA sTextA;
443 Call(SCI_GETTEXT, (WPARAM)(len + 1), (LPARAM)(LPCSTR)sTextA.GetBuffer((int)len + 1));
444 sTextA.ReleaseBuffer();
445 return StringFromControl(sTextA);
448 CString CSciEdit::GetWordUnderCursor(bool bSelectWord)
450 TEXTRANGEA textrange;
451 int pos = (int)Call(SCI_GETCURRENTPOS);
452 textrange.chrg.cpMin = (LONG)Call(SCI_WORDSTARTPOSITION, pos, TRUE);
453 if ((pos == textrange.chrg.cpMin)||(textrange.chrg.cpMin < 0))
454 return CString();
455 textrange.chrg.cpMax = (LONG)Call(SCI_WORDENDPOSITION, textrange.chrg.cpMin, TRUE);
457 std::unique_ptr<char[]> textbuffer(new char[textrange.chrg.cpMax - textrange.chrg.cpMin + 1]);
458 textrange.lpstrText = textbuffer.get();
459 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
460 if (bSelectWord)
462 Call(SCI_SETSEL, textrange.chrg.cpMin, textrange.chrg.cpMax);
464 CString sRet = StringFromControl(textbuffer.get());
465 return sRet;
468 void CSciEdit::SetFont(CString sFontName, int iFontSizeInPoints)
470 Call(SCI_STYLESETFONT, STYLE_DEFAULT, (LPARAM)(LPCSTR)CUnicodeUtils::GetUTF8(sFontName).GetBuffer());
471 Call(SCI_STYLESETSIZE, STYLE_DEFAULT, iFontSizeInPoints);
472 Call(SCI_STYLECLEARALL);
474 LPARAM color = (LPARAM)GetSysColor(COLOR_HIGHLIGHT);
475 // set the styles for the bug ID strings
476 Call(SCI_STYLESETBOLD, STYLE_ISSUEBOLD, (LPARAM)TRUE);
477 Call(SCI_STYLESETFORE, STYLE_ISSUEBOLD, color);
478 Call(SCI_STYLESETBOLD, STYLE_ISSUEBOLDITALIC, (LPARAM)TRUE);
479 Call(SCI_STYLESETITALIC, STYLE_ISSUEBOLDITALIC, (LPARAM)TRUE);
480 Call(SCI_STYLESETFORE, STYLE_ISSUEBOLDITALIC, color);
481 Call(SCI_STYLESETHOTSPOT, STYLE_ISSUEBOLDITALIC, (LPARAM)TRUE);
483 // set the formatted text styles
484 Call(SCI_STYLESETBOLD, STYLE_BOLD, (LPARAM)TRUE);
485 Call(SCI_STYLESETITALIC, STYLE_ITALIC, (LPARAM)TRUE);
486 Call(SCI_STYLESETUNDERLINE, STYLE_UNDERLINED, (LPARAM)TRUE);
488 // set the style for URLs
489 Call(SCI_STYLESETFORE, STYLE_URL, color);
490 Call(SCI_STYLESETHOTSPOT, STYLE_URL, (LPARAM)TRUE);
492 Call(SCI_SETHOTSPOTACTIVEUNDERLINE, (LPARAM)TRUE);
495 void CSciEdit::SetAutoCompletionList(const std::map<CString, int>& list, TCHAR separator, TCHAR typeSeparator)
497 //copy the auto completion list.
499 //SK: instead of creating a copy of that list, we could accept a pointer
500 //to the list and use that instead. But then the caller would have to make
501 //sure that the list persists over the lifetime of the control!
502 m_autolist.clear();
503 m_autolist = list;
504 m_separator = separator;
505 m_typeSeparator = typeSeparator;
508 BOOL CSciEdit::IsMisspelled(const CString& sWord)
510 // convert the string from the control to the encoding of the spell checker module.
511 CStringA sWordA;
512 if (m_spellcodepage)
514 char * buf;
515 buf = sWordA.GetBuffer(sWord.GetLength()*4 + 1);
516 int lengthIncTerminator =
517 WideCharToMultiByte(m_spellcodepage, 0, sWord, -1, buf, sWord.GetLength()*4, NULL, NULL);
518 if (lengthIncTerminator == 0)
519 return FALSE; // converting to the codepage failed, assume word is spelled correctly
520 sWordA.ReleaseBuffer(lengthIncTerminator-1);
522 else
523 sWordA = CStringA(sWord);
524 sWordA.Trim("\'\".,");
525 // words starting with a digit are treated as correctly spelled
526 if (_istdigit(sWord.GetAt(0)))
527 return FALSE;
528 // words in the personal dictionary are correct too
529 if (m_personalDict.FindWord(sWord))
530 return FALSE;
532 // now we actually check the spelling...
533 if (!pChecker->spell(sWordA))
535 // the word is marked as misspelled, we now check whether the word
536 // is maybe a composite identifier
537 // a composite identifier consists of multiple words, with each word
538 // separated by a change in lower to uppercase letters
539 if (sWord.GetLength() > 1)
541 int wordstart = 0;
542 int wordend = 1;
543 while (wordend < sWord.GetLength())
545 while ((wordend < sWord.GetLength())&&(!_istupper(sWord[wordend])))
546 wordend++;
547 if ((wordstart == 0)&&(wordend == sWord.GetLength()))
549 // words in the auto list are also assumed correctly spelled
550 if (m_autolist.find(sWord) != m_autolist.end())
551 return FALSE;
552 return TRUE;
554 sWordA = CStringA(sWord.Mid(wordstart, wordend-wordstart));
555 if ((sWordA.GetLength() > 2)&&(!pChecker->spell(sWordA)))
557 return TRUE;
559 wordstart = wordend;
560 wordend++;
564 return FALSE;
567 void CSciEdit::CheckSpelling(int startpos, int endpos)
569 if (pChecker == NULL)
570 return;
572 TEXTRANGEA textrange;
573 textrange.chrg.cpMin = startpos;
574 textrange.chrg.cpMax = (LONG)textrange.chrg.cpMin;
575 LRESULT lastpos = endpos;
576 if (lastpos < 0)
577 lastpos = Call(SCI_GETLENGTH)-textrange.chrg.cpMin;
578 Call(SCI_SETINDICATORCURRENT, INDIC_MISSPELLED);
579 while (textrange.chrg.cpMax < lastpos)
581 textrange.chrg.cpMin = (LONG)Call(SCI_WORDSTARTPOSITION, textrange.chrg.cpMax+1, TRUE);
582 if (textrange.chrg.cpMin < textrange.chrg.cpMax)
583 break;
584 textrange.chrg.cpMax = (LONG)Call(SCI_WORDENDPOSITION, textrange.chrg.cpMin, TRUE);
585 if (textrange.chrg.cpMin == textrange.chrg.cpMax)
587 textrange.chrg.cpMax++;
588 // since Scintilla squiggles to the end of the text even if told to stop one char before it,
589 // we have to clear here the squiggly lines to the end.
590 if (textrange.chrg.cpMin)
591 Call(SCI_INDICATORCLEARRANGE, textrange.chrg.cpMin-1, textrange.chrg.cpMax - textrange.chrg.cpMin + 1);
592 continue;
594 ATLASSERT(textrange.chrg.cpMax >= textrange.chrg.cpMin);
595 std::unique_ptr<char[]> textbuffer(new char[textrange.chrg.cpMax - textrange.chrg.cpMin + 2]);
596 SecureZeroMemory(textbuffer.get(), textrange.chrg.cpMax - textrange.chrg.cpMin + 2);
597 textrange.lpstrText = textbuffer.get();
598 textrange.chrg.cpMax++;
599 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
600 int len = (int)strlen(textrange.lpstrText);
601 if (len == 0)
603 textrange.chrg.cpMax--;
604 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
605 len = (int)strlen(textrange.lpstrText);
606 textrange.chrg.cpMax++;
607 len++;
609 if (len && textrange.lpstrText[len - 1] == '.')
611 // Try to ignore file names from the auto list.
612 // Do do this, for each word ending with '.' we extract next word and check
613 // whether the combined string is present in auto list.
614 TEXTRANGEA twoWords;
615 twoWords.chrg.cpMin = textrange.chrg.cpMin;
616 twoWords.chrg.cpMax = (LONG)Call(SCI_WORDENDPOSITION, textrange.chrg.cpMax + 1, TRUE);
617 std::unique_ptr<char[]> twoWordsBuffer(new char[twoWords.chrg.cpMax - twoWords.chrg.cpMin + 1]);
618 twoWords.lpstrText = twoWordsBuffer.get();
619 SecureZeroMemory(twoWords.lpstrText, twoWords.chrg.cpMax - twoWords.chrg.cpMin + 1);
620 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&twoWords);
621 CString sWord = StringFromControl(twoWords.lpstrText);
622 if (m_autolist.find(sWord) != m_autolist.end())
624 //mark word as correct (remove the squiggle line)
625 Call(SCI_INDICATORCLEARRANGE, twoWords.chrg.cpMin, twoWords.chrg.cpMax - twoWords.chrg.cpMin);
626 textrange.chrg.cpMax = twoWords.chrg.cpMax;
627 continue;
630 if (len)
631 textrange.lpstrText[len - 1] = 0;
632 textrange.chrg.cpMax--;
633 if (strlen(textrange.lpstrText) > 0)
635 CString sWord = StringFromControl(textrange.lpstrText);
636 if ((GetStyleAt(textrange.chrg.cpMin) != STYLE_URL) && IsMisspelled(sWord))
638 //mark word as misspelled
639 Call(SCI_INDICATORFILLRANGE, textrange.chrg.cpMin, textrange.chrg.cpMax - textrange.chrg.cpMin);
641 else
643 //mark word as correct (remove the squiggle line)
644 Call(SCI_INDICATORCLEARRANGE, textrange.chrg.cpMin, textrange.chrg.cpMax - textrange.chrg.cpMin);
645 Call(SCI_INDICATORCLEARRANGE, textrange.chrg.cpMin, textrange.chrg.cpMax - textrange.chrg.cpMin + 1);
651 void CSciEdit::SuggestSpellingAlternatives()
653 if (pChecker == NULL)
654 return;
655 CString word = GetWordUnderCursor(true);
656 Call(SCI_SETCURRENTPOS, Call(SCI_WORDSTARTPOSITION, Call(SCI_GETCURRENTPOS), TRUE));
657 if (word.IsEmpty())
658 return;
659 char ** wlst = nullptr;
660 int ns = pChecker->suggest(&wlst, CStringA(word));
661 if (ns > 0)
663 CString suggestions;
664 for (int i=0; i < ns; i++)
666 suggestions.AppendFormat(_T("%s%c%d%c"), CString(wlst[i]), m_typeSeparator, AUTOCOMPLETE_SPELLING, m_separator);
667 free(wlst[i]);
669 free(wlst);
670 suggestions.TrimRight(m_separator);
671 if (suggestions.IsEmpty())
672 return;
673 Call(SCI_AUTOCSETSEPARATOR, (WPARAM)CStringA(m_separator).GetAt(0));
674 Call(SCI_AUTOCSETTYPESEPARATOR, (WPARAM)m_typeSeparator);
675 Call(SCI_AUTOCSETDROPRESTOFWORD, 1);
676 Call(SCI_AUTOCSHOW, 0, (LPARAM)(LPCSTR)StringForControl(suggestions));
677 return;
679 free(wlst);
682 void CSciEdit::DoAutoCompletion(int nMinPrefixLength)
684 if (m_autolist.empty())
685 return;
686 if (Call(SCI_AUTOCACTIVE))
687 return;
688 CString word = GetWordUnderCursor();
689 if (word.GetLength() < nMinPrefixLength)
690 return; //don't auto complete yet, word is too short
691 int pos = (int)Call(SCI_GETCURRENTPOS);
692 if (pos != Call(SCI_WORDENDPOSITION, pos, TRUE))
693 return; //don't auto complete if we're not at the end of a word
694 CString sAutoCompleteList;
696 std::vector<CString> words;
698 pos = word.Find('-');
700 CString wordLower = word;
701 wordLower.MakeLower();
702 CString wordHigher = word;
703 wordHigher.MakeUpper();
705 words.push_back(wordLower);
706 words.push_back(wordHigher);
708 if (pos >= 0)
710 CString s = wordLower.Left(pos);
711 if (s.GetLength() >= nMinPrefixLength)
712 words.push_back(s);
713 s = wordLower.Mid(pos+1);
714 if (s.GetLength() >= nMinPrefixLength)
715 words.push_back(s);
716 s = wordHigher.Left(pos);
717 if (s.GetLength() >= nMinPrefixLength)
718 words.push_back(wordHigher.Left(pos));
719 s = wordHigher.Mid(pos+1);
720 if (s.GetLength() >= nMinPrefixLength)
721 words.push_back(wordHigher.Mid(pos+1));
724 std::map<CString, int> wordset;
725 for (const auto& w : words)
727 for (auto lowerit = m_autolist.lower_bound(w);
728 lowerit != m_autolist.end(); ++lowerit)
730 int compare = w.CompareNoCase(lowerit->first.Left(w.GetLength()));
731 if (compare>0)
732 continue;
733 else if (compare == 0)
735 wordset.insert(std::make_pair(lowerit->first, lowerit->second));
737 else
739 break;
744 for (const auto& w : wordset)
745 sAutoCompleteList.AppendFormat(_T("%s%c%d%c"), w.first, m_typeSeparator, w.second, m_separator);
747 sAutoCompleteList.TrimRight(m_separator);
748 if (sAutoCompleteList.IsEmpty())
749 return;
751 Call(SCI_AUTOCSETSEPARATOR, (WPARAM)CStringA(m_separator).GetAt(0));
752 Call(SCI_AUTOCSETTYPESEPARATOR, (WPARAM)m_typeSeparator);
753 Call(SCI_AUTOCSHOW, word.GetLength(), (LPARAM)(LPCSTR)StringForControl(sAutoCompleteList));
756 BOOL CSciEdit::OnChildNotify(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pLResult)
758 if (message != WM_NOTIFY)
759 return CWnd::OnChildNotify(message, wParam, lParam, pLResult);
761 LPNMHDR lpnmhdr = (LPNMHDR) lParam;
762 SCNotification * lpSCN = (SCNotification *)lParam;
764 if(lpnmhdr->hwndFrom==m_hWnd)
766 switch(lpnmhdr->code)
768 case SCN_CHARADDED:
770 if ((lpSCN->ch < 32)&&(lpSCN->ch != 13)&&(lpSCN->ch != 10))
771 Call(SCI_DELETEBACK);
772 else
774 DoAutoCompletion(m_nAutoCompleteMinChars);
776 return TRUE;
778 break;
779 case SCN_AUTOCSELECTION:
781 CString text = StringFromControl(lpSCN->text);
782 if (m_autolist[text] == AUTOCOMPLETE_SNIPPET)
784 Call(SCI_AUTOCCANCEL);
785 for (INT_PTR handlerindex = 0; handlerindex < m_arContextHandlers.GetCount(); ++handlerindex)
787 CSciEditContextMenuInterface * pHandler = m_arContextHandlers.GetAt(handlerindex);
788 pHandler->HandleSnippet(m_autolist[text], text, this);
791 return TRUE;
793 case SCN_MODIFIED:
795 if (lpSCN->modificationType & (SC_MOD_INSERTTEXT | SC_MOD_DELETETEXT))
797 LRESULT firstline = Call(SCI_GETFIRSTVISIBLELINE);
798 LRESULT lastline = firstline + Call(SCI_LINESONSCREEN);
799 int firstpos = (int)Call(SCI_POSITIONFROMLINE, firstline);
800 int lastpos = (int)Call(SCI_GETLINEENDPOSITION, lastline);
801 int pos1 = lpSCN->position;
802 int pos2 = pos1 + lpSCN->length;
803 // always use the bigger range
804 firstpos = min(firstpos, pos1);
805 lastpos = max(lastpos, pos2);
806 MarkEnteredBugID(firstpos, lastpos);
807 if (m_bDoStyle)
808 StyleEnteredText(firstpos, lastpos);
810 int startpos = (int)Call(SCI_WORDSTARTPOSITION, firstpos, true);
811 int endpos = (int)Call(SCI_WORDENDPOSITION, lastpos, true);
812 StyleURLs(startpos, endpos);
813 CheckSpelling(startpos, endpos);
814 WrapLines(firstpos, lastpos);
815 Call(SCI_COLOURISE, startpos, endpos);
817 break;
819 case SCN_DWELLSTART:
820 case SCN_HOTSPOTCLICK:
822 TEXTRANGEA textrange;
823 textrange.chrg.cpMin = lpSCN->position;
824 textrange.chrg.cpMax = lpSCN->position;
825 DWORD style = GetStyleAt(lpSCN->position);
826 if (style != STYLE_ISSUEBOLDITALIC && style != STYLE_URL)
827 break;
828 while (GetStyleAt(textrange.chrg.cpMin - 1) == style)
829 --textrange.chrg.cpMin;
830 while (GetStyleAt(textrange.chrg.cpMax + 1) == style)
831 ++textrange.chrg.cpMax;
832 ++textrange.chrg.cpMax;
833 std::unique_ptr<char[]> textbuffer(new char[textrange.chrg.cpMax - textrange.chrg.cpMin + 1]);
834 textrange.lpstrText = textbuffer.get();
835 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
836 CString url;
837 if (style == STYLE_URL)
838 url = StringFromControl(textbuffer.get());
839 else
841 url = m_sUrl;
842 url.Replace(L"%BUGID%", StringFromControl(textbuffer.get()));
844 if (!url.IsEmpty())
846 if (lpnmhdr->code == SCN_HOTSPOTCLICK)
847 ShellExecute(GetParent()->GetSafeHwnd(), _T("open"), url, NULL, NULL, SW_SHOWDEFAULT);
848 else
850 CStringA sTextA = StringForControl(url);
851 Call(SCI_CALLTIPSHOW, lpSCN->position + 3, (LPARAM)(LPCSTR)sTextA);
855 break;
856 case SCN_DWELLEND:
857 Call(SCI_CALLTIPCANCEL);
858 break;
861 return CWnd::OnChildNotify(message, wParam, lParam, pLResult);
864 BEGIN_MESSAGE_MAP(CSciEdit, CWnd)
865 ON_WM_KEYDOWN()
866 ON_WM_CONTEXTMENU()
867 END_MESSAGE_MAP()
869 void CSciEdit::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
871 switch (nChar)
873 case (VK_ESCAPE):
875 if ((Call(SCI_AUTOCACTIVE)==0)&&(Call(SCI_CALLTIPACTIVE)==0))
876 ::SendMessage(GetParent()->GetSafeHwnd(), WM_CLOSE, 0, 0);
878 break;
880 CWnd::OnKeyDown(nChar, nRepCnt, nFlags);
883 BOOL CSciEdit::PreTranslateMessage(MSG* pMsg)
885 if (pMsg->message == WM_KEYDOWN)
887 switch (pMsg->wParam)
889 case VK_SPACE:
891 if (GetKeyState(VK_CONTROL) & 0x8000)
893 DoAutoCompletion(1);
894 return TRUE;
897 break;
898 case VK_TAB:
899 // The TAB cannot be handled in OnKeyDown because it is too late by then.
901 if (GetKeyState(VK_CONTROL)&0x8000)
903 //Ctrl-Tab was pressed, this means we should provide the user with
904 //a list of possible spell checking alternatives to the word under
905 //the cursor
906 SuggestSpellingAlternatives();
907 return TRUE;
909 else if (!Call(SCI_AUTOCACTIVE))
911 ::PostMessage(GetParent()->GetSafeHwnd(), WM_NEXTDLGCTL, GetKeyState(VK_SHIFT)&0x8000, 0);
912 return TRUE;
915 break;
918 return CWnd::PreTranslateMessage(pMsg);
921 void CSciEdit::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
923 int anchor = (int)Call(SCI_GETANCHOR);
924 int currentpos = (int)Call(SCI_GETCURRENTPOS);
925 int selstart = (int)Call(SCI_GETSELECTIONSTART);
926 int selend = (int)Call(SCI_GETSELECTIONEND);
927 int pointpos = 0;
928 if ((point.x == -1) && (point.y == -1))
930 CRect rect;
931 GetClientRect(&rect);
932 ClientToScreen(&rect);
933 point = rect.CenterPoint();
934 pointpos = (int)Call(SCI_GETCURRENTPOS);
936 else
938 // change the cursor position to the point where the user
939 // right-clicked.
940 CPoint clientpoint = point;
941 ScreenToClient(&clientpoint);
942 pointpos = (int)Call(SCI_POSITIONFROMPOINT, clientpoint.x, clientpoint.y);
944 CString sMenuItemText;
945 CMenu popup;
946 bool bRestoreCursor = true;
947 if (popup.CreatePopupMenu())
949 bool bCanUndo = !!Call(SCI_CANUNDO);
950 bool bCanRedo = !!Call(SCI_CANREDO);
951 bool bHasSelection = (selend-selstart > 0);
952 bool bCanPaste = !!Call(SCI_CANPASTE);
953 bool bIsReadOnly = !!Call(SCI_GETREADONLY);
954 UINT uEnabledMenu = MF_STRING | MF_ENABLED;
955 UINT uDisabledMenu = MF_STRING | MF_GRAYED;
957 // find the word under the cursor
958 CString sWord;
959 if (pointpos)
961 // setting the cursor clears the selection
962 Call(SCI_SETANCHOR, pointpos);
963 Call(SCI_SETCURRENTPOS, pointpos);
964 sWord = GetWordUnderCursor();
965 // restore the selection
966 Call(SCI_SETSELECTIONSTART, selstart);
967 Call(SCI_SETSELECTIONEND, selend);
969 else
970 sWord = GetWordUnderCursor();
971 CStringA worda = CStringA(sWord);
973 int nCorrections = 1;
974 bool bSpellAdded = false;
975 // check if the word under the cursor is spelled wrong
976 if ((pChecker)&&(!worda.IsEmpty()) && !bIsReadOnly)
978 char ** wlst = nullptr;
979 // get the spell suggestions
980 int ns = pChecker->suggest(&wlst,worda);
981 if (ns > 0)
983 // add the suggestions to the context menu
984 for (int i=0; i < ns; i++)
986 bSpellAdded = true;
987 CString sug = CString(wlst[i]);
988 popup.InsertMenu((UINT)-1, 0, nCorrections++, sug);
989 free(wlst[i]);
991 free(wlst);
993 else
994 free(wlst);
996 // only add a separator if spelling correction suggestions were added
997 if (bSpellAdded)
998 popup.AppendMenu(MF_SEPARATOR);
1000 // also allow the user to add the word to the custom dictionary so
1001 // it won't show up as misspelled anymore
1002 if ((sWord.GetLength()<PDICT_MAX_WORD_LENGTH)&&((pChecker)&&(m_autolist.find(sWord) == m_autolist.end())&&(!pChecker->spell(worda)))&&
1003 (!_istdigit(sWord.GetAt(0)))&&(!m_personalDict.FindWord(sWord)) && !bIsReadOnly)
1005 sMenuItemText.Format(IDS_SCIEDIT_ADDWORD, sWord);
1006 popup.AppendMenu(uEnabledMenu, SCI_ADDWORD, sMenuItemText);
1007 // another separator
1008 popup.AppendMenu(MF_SEPARATOR);
1011 // add the 'default' entries
1012 sMenuItemText.LoadString(IDS_SCIEDIT_UNDO);
1013 popup.AppendMenu(bCanUndo ? uEnabledMenu : uDisabledMenu, SCI_UNDO, sMenuItemText);
1014 sMenuItemText.LoadString(IDS_SCIEDIT_REDO);
1015 popup.AppendMenu(bCanRedo ? uEnabledMenu : uDisabledMenu, SCI_REDO, sMenuItemText);
1017 popup.AppendMenu(MF_SEPARATOR);
1019 sMenuItemText.LoadString(IDS_SCIEDIT_CUT);
1020 popup.AppendMenu(!bIsReadOnly && bHasSelection ? uEnabledMenu : uDisabledMenu, SCI_CUT, sMenuItemText);
1021 sMenuItemText.LoadString(IDS_SCIEDIT_COPY);
1022 popup.AppendMenu(bHasSelection ? uEnabledMenu : uDisabledMenu, SCI_COPY, sMenuItemText);
1023 sMenuItemText.LoadString(IDS_SCIEDIT_PASTE);
1024 popup.AppendMenu(bCanPaste ? uEnabledMenu : uDisabledMenu, SCI_PASTE, sMenuItemText);
1026 popup.AppendMenu(MF_SEPARATOR);
1028 sMenuItemText.LoadString(IDS_SCIEDIT_SELECTALL);
1029 popup.AppendMenu(uEnabledMenu, SCI_SELECTALL, sMenuItemText);
1031 popup.AppendMenu(MF_SEPARATOR);
1033 sMenuItemText.LoadString(IDS_SCIEDIT_SPLITLINES);
1034 popup.AppendMenu(bHasSelection ? uEnabledMenu : uDisabledMenu, SCI_LINESSPLIT, sMenuItemText);
1036 if (m_arContextHandlers.GetCount() > 0)
1037 popup.AppendMenu(MF_SEPARATOR);
1039 int nCustoms = nCorrections;
1040 // now add any custom context menus
1041 for (INT_PTR handlerindex = 0; handlerindex < m_arContextHandlers.GetCount(); ++handlerindex)
1043 CSciEditContextMenuInterface * pHandler = m_arContextHandlers.GetAt(handlerindex);
1044 pHandler->InsertMenuItems(popup, nCustoms);
1046 #if THESAURUS
1047 if (nCustoms > nCorrections)
1049 // custom menu entries present, so add another separator
1050 popup.AppendMenu(MF_SEPARATOR);
1053 // add found thesauri to sub menu's
1054 CMenu thesaurs;
1055 int nThesaurs = 0;
1056 CPtrArray menuArray;
1057 if (thesaurs.CreatePopupMenu())
1059 if ((pThesaur)&&(!worda.IsEmpty()))
1061 mentry * pmean;
1062 worda.MakeLower();
1063 int count = pThesaur->Lookup(worda, worda.GetLength(),&pmean);
1064 if (count)
1066 mentry * pm = pmean;
1067 for (int i=0; i < count; i++)
1069 CMenu * submenu = new CMenu();
1070 menuArray.Add(submenu);
1071 submenu->CreateMenu();
1072 for (int j=0; j < pm->count; j++)
1074 CString sug = CString(pm->psyns[j]);
1075 submenu->InsertMenu((UINT)-1, 0, nCorrections + nCustoms + (nThesaurs++), sug);
1077 thesaurs.InsertMenu((UINT)-1, MF_POPUP, (UINT_PTR)(submenu->m_hMenu), CString(pm->defn));
1078 pm++;
1081 if ((count > 0)&&(point.x >= 0))
1083 #ifdef IDS_SPELLEDIT_THESAURUS
1084 sMenuItemText.LoadString(IDS_SPELLEDIT_THESAURUS);
1085 popup.InsertMenu((UINT)-1, MF_POPUP, (UINT_PTR)thesaurs.m_hMenu, sMenuItemText);
1086 #else
1087 popup.InsertMenu((UINT)-1, MF_POPUP, (UINT_PTR)thesaurs.m_hMenu, _T("Thesaurus"));
1088 #endif
1089 nThesaurs = nCustoms;
1091 else
1093 sMenuItemText.LoadString(IDS_SPELLEDIT_NOTHESAURUS);
1094 popup.AppendMenu(MF_DISABLED | MF_GRAYED | MF_STRING, 0, sMenuItemText);
1097 pThesaur->CleanUpAfterLookup(&pmean, count);
1099 else
1101 sMenuItemText.LoadString(IDS_SPELLEDIT_NOTHESAURUS);
1102 popup.AppendMenu(MF_DISABLED | MF_GRAYED | MF_STRING, 0, sMenuItemText);
1105 #endif
1106 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this, 0);
1107 switch (cmd)
1109 case 0:
1110 break; // no command selected
1111 case SCI_SELECTALL:
1112 bRestoreCursor = false;
1113 // fall through
1114 case SCI_UNDO:
1115 case SCI_REDO:
1116 case SCI_CUT:
1117 case SCI_COPY:
1118 case SCI_PASTE:
1119 Call(cmd);
1120 break;
1121 case SCI_ADDWORD:
1122 m_personalDict.AddWord(sWord);
1123 CheckSpelling((int)Call(SCI_POSITIONFROMLINE, Call(SCI_GETFIRSTVISIBLELINE)), (int)Call(SCI_POSITIONFROMLINE, Call(SCI_GETFIRSTVISIBLELINE) + Call(SCI_LINESONSCREEN)));
1124 break;
1125 case SCI_LINESSPLIT:
1127 int marker = (int)(Call(SCI_GETEDGECOLUMN) * Call(SCI_TEXTWIDTH, 0, (LPARAM)" "));
1128 if (marker)
1130 Call(SCI_TARGETFROMSELECTION);
1131 Call(SCI_LINESJOIN);
1132 Call(SCI_LINESSPLIT, marker);
1135 break;
1136 default:
1137 if (cmd < nCorrections)
1139 Call(SCI_SETANCHOR, pointpos);
1140 Call(SCI_SETCURRENTPOS, pointpos);
1141 GetWordUnderCursor(true);
1142 CString temp;
1143 popup.GetMenuString(cmd, temp, 0);
1144 // setting the cursor clears the selection
1145 Call(SCI_REPLACESEL, 0, (LPARAM)(LPCSTR)StringForControl(temp));
1147 else if (cmd < (nCorrections+nCustoms))
1149 for (INT_PTR handlerindex = 0; handlerindex < m_arContextHandlers.GetCount(); ++handlerindex)
1151 CSciEditContextMenuInterface * pHandler = m_arContextHandlers.GetAt(handlerindex);
1152 if (pHandler->HandleMenuItemClick(cmd, this))
1153 break;
1156 #if THESAURUS
1157 else if (cmd <= (nThesaurs+nCorrections+nCustoms))
1159 Call(SCI_SETANCHOR, pointpos);
1160 Call(SCI_SETCURRENTPOS, pointpos);
1161 GetWordUnderCursor(true);
1162 CString temp;
1163 thesaurs.GetMenuString(cmd, temp, 0);
1164 Call(SCI_REPLACESEL, 0, (LPARAM)(LPCSTR)StringForControl(temp));
1166 #endif
1168 #ifdef THESAURUS
1169 for (INT_PTR index = 0; index < menuArray.GetCount(); ++index)
1171 CMenu * pMenu = (CMenu*)menuArray[index];
1172 delete pMenu;
1174 #endif
1176 if (bRestoreCursor)
1178 // restore the anchor and cursor position
1179 Call(SCI_SETCURRENTPOS, currentpos);
1180 Call(SCI_SETANCHOR, anchor);
1184 bool CSciEdit::StyleEnteredText(int startstylepos, int endstylepos)
1186 bool bStyled = false;
1187 const int line = (int)Call(SCI_LINEFROMPOSITION, startstylepos);
1188 const int line_number_end = (int)Call(SCI_LINEFROMPOSITION, endstylepos);
1189 for (int line_number = line; line_number <= line_number_end; ++line_number)
1191 int offset = (int)Call(SCI_POSITIONFROMLINE, line_number);
1192 int line_len = (int)Call(SCI_LINELENGTH, line_number);
1193 std::unique_ptr<char[]> linebuffer(new char[line_len+1]);
1194 Call(SCI_GETLINE, line_number, (LPARAM)linebuffer.get());
1195 linebuffer[line_len] = 0;
1196 int start = 0;
1197 int end = 0;
1198 while (FindStyleChars(linebuffer.get(), '*', start, end))
1200 Call(SCI_STARTSTYLING, start + offset, STYLE_BOLD);
1201 Call(SCI_SETSTYLING, end-start, STYLE_BOLD);
1202 bStyled = true;
1203 start = end;
1205 start = 0;
1206 end = 0;
1207 while (FindStyleChars(linebuffer.get(), '^', start, end))
1209 Call(SCI_STARTSTYLING, start + offset, STYLE_ITALIC);
1210 Call(SCI_SETSTYLING, end-start, STYLE_ITALIC);
1211 bStyled = true;
1212 start = end;
1214 start = 0;
1215 end = 0;
1216 while (FindStyleChars(linebuffer.get(), '_', start, end))
1218 Call(SCI_STARTSTYLING, start + offset, STYLE_UNDERLINED);
1219 Call(SCI_SETSTYLING, end-start, STYLE_UNDERLINED);
1220 bStyled = true;
1221 start = end;
1224 return bStyled;
1227 bool CSciEdit::WrapLines(int startpos, int endpos)
1229 int markerX = (int)(Call(SCI_GETEDGECOLUMN) * Call(SCI_TEXTWIDTH, 0, (LPARAM)" "));
1230 if (markerX)
1232 Call(SCI_SETTARGETSTART, startpos);
1233 Call(SCI_SETTARGETEND, endpos);
1234 Call(SCI_LINESSPLIT, markerX);
1235 return true;
1237 return false;
1240 void CSciEdit::AdvanceUTF8(const char * str, int& pos)
1242 if ((str[pos] & 0xE0)==0xC0)
1244 // utf8 2-byte sequence
1245 pos += 2;
1247 else if ((str[pos] & 0xF0)==0xE0)
1249 // utf8 3-byte sequence
1250 pos += 3;
1252 else if ((str[pos] & 0xF8)==0xF0)
1254 // utf8 4-byte sequence
1255 pos += 4;
1257 else
1258 pos++;
1261 bool CSciEdit::FindStyleChars(const char * line, char styler, int& start, int& end)
1263 int i=0;
1264 int u=0;
1265 while (i < start)
1267 AdvanceUTF8(line, i);
1268 u++;
1271 bool bFoundMarker = false;
1272 CString sULine = CUnicodeUtils::GetUnicode(line);
1273 // find a starting marker
1274 while (line[i] != 0)
1276 if (line[i] == styler)
1278 if ((line[i+1]!=0)&&(IsCharAlphaNumeric(sULine[u+1]))&&
1279 (((u>0)&&(!IsCharAlphaNumeric(sULine[u-1]))) || (u==0)))
1281 start = i+1;
1282 AdvanceUTF8(line, i);
1283 u++;
1284 bFoundMarker = true;
1285 break;
1288 AdvanceUTF8(line, i);
1289 u++;
1291 if (!bFoundMarker)
1292 return false;
1293 // find ending marker
1294 bFoundMarker = false;
1295 while (line[i] != 0)
1297 if (line[i] == styler)
1299 if ((IsCharAlphaNumeric(sULine[u-1]))&&
1300 ((((u+1)<sULine.GetLength())&&(!IsCharAlphaNumeric(sULine[u+1]))) || ((u+1) == sULine.GetLength()))
1303 end = i;
1304 i++;
1305 bFoundMarker = true;
1306 break;
1309 AdvanceUTF8(line, i);
1310 u++;
1312 return bFoundMarker;
1315 BOOL CSciEdit::MarkEnteredBugID(int startstylepos, int endstylepos)
1317 if (m_sCommand.IsEmpty())
1318 return FALSE;
1319 // get the text between the start and end position we have to style
1320 const int line_number = (int)Call(SCI_LINEFROMPOSITION, startstylepos);
1321 int start_pos = (int)Call(SCI_POSITIONFROMLINE, (WPARAM)line_number);
1322 int end_pos = endstylepos;
1324 if (start_pos == end_pos)
1325 return FALSE;
1326 if (start_pos > end_pos)
1328 int switchtemp = start_pos;
1329 start_pos = end_pos;
1330 end_pos = switchtemp;
1333 std::unique_ptr<char[]> textbuffer(new char[end_pos - start_pos + 2]);
1334 TEXTRANGEA textrange;
1335 textrange.lpstrText = textbuffer.get();
1336 textrange.chrg.cpMin = start_pos;
1337 textrange.chrg.cpMax = end_pos;
1338 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
1339 CStringA msg = CStringA(textbuffer.get());
1341 Call(SCI_STARTSTYLING, start_pos, STYLE_MASK);
1345 if (!m_sBugID.IsEmpty())
1347 // match with two regex strings (without grouping!)
1348 const std::tr1::regex regCheck(m_sCommand);
1349 const std::tr1::regex regBugID(m_sBugID);
1350 const std::tr1::sregex_iterator end;
1351 std::string s = msg;
1352 LONG pos = 0;
1353 // note:
1354 // if start_pos is 0, we're styling from the beginning and let the ^ char match the beginning of the line
1355 // that way, the ^ matches the very beginning of the log message and not the beginning of further lines.
1356 // problem is: this only works *while* entering log messages. If a log message is pasted in whole or
1357 // multiple lines are pasted, start_pos can be 0 and styling goes over multiple lines. In that case, those
1358 // additional line starts also match ^
1359 for (std::tr1::sregex_iterator it(s.begin(), s.end(), regCheck, start_pos != 0 ? std::tr1::regex_constants::match_not_bol : std::tr1::regex_constants::match_default); it != end; ++it)
1361 // clear the styles up to the match position
1362 Call(SCI_SETSTYLING, it->position(0)-pos, STYLE_DEFAULT);
1364 // (*it)[0] is the matched string
1365 std::string matchedString = (*it)[0];
1366 LONG matchedpos = 0;
1367 for (std::tr1::sregex_iterator it2(matchedString.begin(), matchedString.end(), regBugID); it2 != end; ++it2)
1369 ATLTRACE("matched id : %s\n", std::string((*it2)[0]).c_str());
1371 // bold style up to the id match
1372 ATLTRACE("position = %ld\n", it2->position(0));
1373 if (it2->position(0))
1374 Call(SCI_SETSTYLING, it2->position(0) - matchedpos, STYLE_ISSUEBOLD);
1375 // bold and recursive style for the bug ID itself
1376 if ((*it2)[0].str().size())
1377 Call(SCI_SETSTYLING, (*it2)[0].str().size(), STYLE_ISSUEBOLDITALIC);
1378 matchedpos = (LONG)(it2->position(0) + (*it2)[0].str().size());
1380 if ((matchedpos)&&(matchedpos < (LONG)matchedString.size()))
1382 Call(SCI_SETSTYLING, matchedString.size() - matchedpos, STYLE_ISSUEBOLD);
1384 pos = (LONG)(it->position(0) + matchedString.size());
1386 // bold style for the rest of the string which isn't matched
1387 if (s.size()-pos)
1388 Call(SCI_SETSTYLING, s.size()-pos, STYLE_DEFAULT);
1390 else
1392 const std::tr1::regex regCheck(m_sCommand);
1393 const std::tr1::sregex_iterator end;
1394 std::string s = msg;
1395 LONG pos = 0;
1396 for (std::tr1::sregex_iterator it(s.begin(), s.end(), regCheck); it != end; ++it)
1398 // clear the styles up to the match position
1399 if (it->position(0) - pos >= 0)
1400 Call(SCI_SETSTYLING, it->position(0) - pos, STYLE_DEFAULT);
1401 pos = (LONG)it->position(0);
1403 const std::tr1::smatch match = *it;
1404 // we define group 1 as the whole issue text and
1405 // group 2 as the bug ID
1406 if (match.size() >= 2)
1408 ATLTRACE("matched id : %s\n", std::string(match[1]).c_str());
1409 if (match[1].first - s.begin() - pos >= 0)
1410 Call(SCI_SETSTYLING, match[1].first - s.begin() - pos, STYLE_ISSUEBOLD);
1411 Call(SCI_SETSTYLING, std::string(match[1]).size(), STYLE_ISSUEBOLDITALIC);
1412 pos = (LONG)(match[1].second-s.begin());
1417 catch (std::exception) {}
1419 return FALSE;
1422 //similar code in AppUtils.cpp
1423 bool CSciEdit::IsValidURLChar(unsigned char ch)
1425 return isalnum(ch) ||
1426 ch == '_' || ch == '/' || ch == ';' || ch == '?' || ch == '&' || ch == '=' ||
1427 ch == '%' || ch == ':' || ch == '.' || ch == '#' || ch == '-' || ch == '+' ||
1428 ch == '|' || ch == '>' || ch == '<';
1431 //similar code in AppUtils.cpp
1432 void CSciEdit::StyleURLs(int startstylepos, int endstylepos)
1434 const int line_number = (int)Call(SCI_LINEFROMPOSITION, startstylepos);
1435 startstylepos = (int)Call(SCI_POSITIONFROMLINE, (WPARAM)line_number);
1437 int len = endstylepos - startstylepos + 1;
1438 std::unique_ptr<char[]> textbuffer(new char[len + 1]);
1439 TEXTRANGEA textrange;
1440 textrange.lpstrText = textbuffer.get();
1441 textrange.chrg.cpMin = startstylepos;
1442 textrange.chrg.cpMax = endstylepos;
1443 Call(SCI_GETTEXTRANGE, 0, (LPARAM)&textrange);
1444 // we're dealing with utf8 encoded text here, which means one glyph is
1445 // not necessarily one byte/wchar_t
1446 // that's why we use CStringA to still get a correct char index
1447 CStringA msg = textbuffer.get();
1449 int starturl = -1;
1450 for (int i = 0; i <= msg.GetLength(); AdvanceUTF8(msg, i))
1452 if ((i < len) && IsValidURLChar(msg[i]))
1454 if (starturl < 0)
1455 starturl = i;
1457 else
1459 if (starturl >= 0)
1461 bool strip = true;
1462 if (msg[starturl] == '<' && i < len) // try to detect and do not strip URLs put within <>
1464 while (starturl <= i && msg[starturl] == '<') // strip leading '<'
1465 ++starturl;
1466 strip = false;
1467 i = starturl;
1468 while (i < len && msg[i] != '\r' && msg[i] != '\n' && msg[i] != '>') // find first '>' or new line after resetting i to start position
1469 AdvanceUTF8(msg, i);
1471 if (!IsUrl(msg.Mid(starturl, i - starturl)))
1473 starturl = -1;
1474 continue;
1477 int skipTrailing = 0;
1478 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] == '<'))
1479 ++skipTrailing;
1480 ASSERT(startstylepos + i - skipTrailing <= endstylepos);
1481 Call(SCI_STARTSTYLING, startstylepos + starturl, STYLE_URL);
1482 Call(SCI_SETSTYLING, i - starturl - skipTrailing, STYLE_URL);
1484 starturl = -1;
1489 bool CSciEdit::IsUrl(const CStringA& sText)
1491 if (!PathIsURLA(sText))
1492 return false;
1493 CStringA prefixes[] = { "http://", "https://", "git://", "ftp://", "file://", "mailto:" };
1494 for (const CStringA& prefix : prefixes)
1496 if (sText.Find(prefix) == 0 && sText.GetLength() != prefix.GetLength())
1497 return true;
1499 return false;
1502 bool CSciEdit::IsUTF8(LPVOID pBuffer, size_t cb)
1504 if (cb < 2)
1505 return true;
1506 UINT16 * pVal = (UINT16 *)pBuffer;
1507 UINT8 * pVal2 = (UINT8 *)(pVal+1);
1508 // scan the whole buffer for a 0x0000 sequence
1509 // if found, we assume a binary file
1510 for (size_t i=0; i<(cb-2); i=i+2)
1512 if (0x0000 == *pVal++)
1513 return false;
1515 pVal = (UINT16 *)pBuffer;
1516 if (*pVal == 0xFEFF)
1517 return false;
1518 if (cb < 3)
1519 return false;
1520 if (*pVal == 0xBBEF)
1522 if (*pVal2 == 0xBF)
1523 return true;
1525 // check for illegal UTF8 chars
1526 pVal2 = (UINT8 *)pBuffer;
1527 for (size_t i=0; i<cb; ++i)
1529 if ((*pVal2 == 0xC0)||(*pVal2 == 0xC1)||(*pVal2 >= 0xF5))
1530 return false;
1531 pVal2++;
1533 pVal2 = (UINT8 *)pBuffer;
1534 bool bUTF8 = false;
1535 for (size_t i=0; i<(cb-3); ++i)
1537 if ((*pVal2 & 0xE0)==0xC0)
1539 pVal2++;i++;
1540 if ((*pVal2 & 0xC0)!=0x80)
1541 return false;
1542 bUTF8 = true;
1544 if ((*pVal2 & 0xF0)==0xE0)
1546 pVal2++;i++;
1547 if ((*pVal2 & 0xC0)!=0x80)
1548 return false;
1549 pVal2++;i++;
1550 if ((*pVal2 & 0xC0)!=0x80)
1551 return false;
1552 bUTF8 = true;
1554 if ((*pVal2 & 0xF8)==0xF0)
1556 pVal2++;i++;
1557 if ((*pVal2 & 0xC0)!=0x80)
1558 return false;
1559 pVal2++;i++;
1560 if ((*pVal2 & 0xC0)!=0x80)
1561 return false;
1562 pVal2++;i++;
1563 if ((*pVal2 & 0xC0)!=0x80)
1564 return false;
1565 bUTF8 = true;
1567 pVal2++;
1569 if (bUTF8)
1570 return true;
1571 return false;
1574 void CSciEdit::SetAStyle(int style, COLORREF fore, COLORREF back, int size, const char *face)
1576 Call(SCI_STYLESETFORE, style, fore);
1577 Call(SCI_STYLESETBACK, style, back);
1578 if (size >= 1)
1579 Call(SCI_STYLESETSIZE, style, size);
1580 if (face)
1581 Call(SCI_STYLESETFONT, style, reinterpret_cast<LPARAM>(face));
1585 void CSciEdit::SetUDiffStyle()
1587 m_bDoStyle = false;
1588 SetAStyle(STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOWTEXT), ::GetSysColor(COLOR_WINDOW),
1589 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffFontSize", 10),
1590 CUnicodeUtils::StdGetUTF8(CRegStdString(L"Software\\TortoiseGit\\UDiffFontName", L"Courier New")).c_str());
1591 Call(SCI_SETTABWIDTH, CRegStdDWORD(L"Software\\TortoiseGit\\UDiffTabSize", 4));
1593 Call(SCI_SETREADONLY, TRUE);
1594 //LRESULT pix = Call(SCI_TEXTWIDTH, STYLE_LINENUMBER, (LPARAM)"_99999");
1595 //Call(SCI_SETMARGINWIDTHN, 0, pix);
1596 //Call(SCI_SETMARGINWIDTHN, 1);
1597 //Call(SCI_SETMARGINWIDTHN, 2);
1598 //Set the default windows colors for edit controls
1599 Call(SCI_STYLESETFORE, STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOWTEXT));
1600 Call(SCI_STYLESETBACK, STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOW));
1601 Call(SCI_SETSELFORE, TRUE, ::GetSysColor(COLOR_HIGHLIGHTTEXT));
1602 Call(SCI_SETSELBACK, TRUE, ::GetSysColor(COLOR_HIGHLIGHT));
1603 Call(SCI_SETCARETFORE, ::GetSysColor(COLOR_WINDOWTEXT));
1605 //SendEditor(SCI_SETREADONLY, FALSE);
1606 Call(SCI_CLEARALL);
1607 Call(EM_EMPTYUNDOBUFFER);
1608 Call(SCI_SETSAVEPOINT);
1609 Call(SCI_CANCEL);
1610 Call(SCI_SETUNDOCOLLECTION, 0);
1612 Call(SCI_SETUNDOCOLLECTION, 1);
1613 Call(SCI_SETWRAPMODE,SC_WRAP_NONE);
1615 //::SetFocus(m_hWndEdit);
1616 Call(EM_EMPTYUNDOBUFFER);
1617 Call(SCI_SETSAVEPOINT);
1618 Call(SCI_GOTOPOS, 0);
1620 Call(SCI_CLEARDOCUMENTSTYLE, 0, 0);
1621 Call(SCI_SETSTYLEBITS, 5, 0);
1623 //SetAStyle(SCE_DIFF_DEFAULT, RGB(0, 0, 0));
1624 SetAStyle(SCE_DIFF_COMMAND,
1625 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffForeCommandColor", UDIFF_COLORFORECOMMAND),
1626 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffBackCommandColor", UDIFF_COLORBACKCOMMAND));
1627 SetAStyle(SCE_DIFF_POSITION,
1628 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffForePositionColor", UDIFF_COLORFOREPOSITION),
1629 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffBackPositionColor", UDIFF_COLORBACKPOSITION));
1630 SetAStyle(SCE_DIFF_HEADER,
1631 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffForeHeaderColor", UDIFF_COLORFOREHEADER),
1632 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffBackHeaderColor", UDIFF_COLORBACKHEADER));
1633 SetAStyle(SCE_DIFF_COMMENT,
1634 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffForeCommentColor", UDIFF_COLORFORECOMMENT),
1635 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffBackCommentColor", UDIFF_COLORBACKCOMMENT));
1636 Call(SCI_STYLESETBOLD, SCE_DIFF_COMMENT, TRUE);
1637 SetAStyle(SCE_DIFF_ADDED,
1638 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffForeAddedColor", UDIFF_COLORFOREADDED),
1639 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffBackAddedColor", UDIFF_COLORBACKADDED));
1640 SetAStyle(SCE_DIFF_DELETED,
1641 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffForeRemovedColor", UDIFF_COLORFOREREMOVED),
1642 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffBackRemovedColor", UDIFF_COLORBACKREMOVED));
1644 Call(SCI_SETLEXER, SCLEX_DIFF);
1645 Call(SCI_SETKEYWORDS, 0, (LPARAM)"revision");
1646 Call(SCI_COLOURISE, 0, -1);
1649 int CSciEdit::LoadFromFile(CString &filename)
1651 FILE *fp = NULL;
1652 _tfopen_s(&fp, filename, _T("rb"));
1653 if (fp)
1655 //SetTitle();
1656 char data[4096] = { 0 };
1657 size_t lenFile = fread(data, 1, sizeof(data), fp);
1658 bool bUTF8 = IsUTF8(data, lenFile);
1659 while (lenFile > 0)
1661 Call(SCI_ADDTEXT, lenFile,
1662 reinterpret_cast<LPARAM>(static_cast<char *>(data)));
1663 lenFile = fread(data, 1, sizeof(data), fp);
1665 fclose(fp);
1666 Call(SCI_SETCODEPAGE, bUTF8 ? SC_CP_UTF8 : GetACP());
1667 return 0;
1669 else
1670 return -1;
1673 void CSciEdit::RestyleBugIDs()
1675 int endstylepos = (int)Call(SCI_GETLENGTH);
1676 // clear all styles
1677 Call(SCI_STARTSTYLING, 0, STYLE_MASK);
1678 Call(SCI_SETSTYLING, endstylepos, STYLE_DEFAULT);
1679 // style the bug IDs
1680 MarkEnteredBugID(0, endstylepos);