1
// TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2024 - TortoiseGit
4 // Copyright (C) 2003-2008, 2012-2020 - 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.
21 #include "LoglistCommonResource.h"
22 #include "PathUtils.h"
23 #include "UnicodeUtils.h"
27 #include "SmartHandle.h"
28 #include "../../TortoiseUDiff/UDiffColors.h"
29 #include "LoadIconEx.h"
32 #include "DarkModeHelper.h"
33 #include "URLFinder.h"
35 void CSciEditContextMenuInterface::InsertMenuItems(CMenu
&, int&) {return;}
36 bool CSciEditContextMenuInterface::HandleMenuItemClick(int, CSciEdit
*) {return false;}
37 void CSciEditContextMenuInterface::HandleSnippet(int, const CString
&, CSciEdit
*) { return; }
40 #define STYLE_ISSUEBOLD 11
41 #define STYLE_ISSUEBOLDITALIC 12
43 #define STYLE_ITALIC 15
44 #define STYLE_UNDERLINED 16
46 #define INDIC_MISSPELLED 18
48 #define STYLE_MASK 0x1f
50 #define SCI_ADDWORD 2000
57 struct loc_map enc2locale
[] = {
58 {"28591","ISO8859-1"},
59 {"28592","ISO8859-2"},
60 {"28593","ISO8859-3"},
61 {"28594","ISO8859-4"},
62 {"28595","ISO8859-5"},
63 {"28596","ISO8859-6"},
64 {"28597","ISO8859-7"},
65 {"28598","ISO8859-8"},
66 {"28599","ISO8859-9"},
67 {"28605","ISO8859-15"},
70 {"1251","microsoft-cp1251"},
75 IMPLEMENT_DYNAMIC(CSciEdit
, CWnd
)
79 m_hModule
= ::LoadLibrary(L
"SciLexer_tgit.dll");
84 CTheme::Instance().RemoveRegisteredCallback(m_themeCallbackId
);
85 m_personalDict
.Save();
88 static std::unique_ptr
<UINT
[]> Icon2Image(HICON hIcon
)
94 if (!GetIconInfo(hIcon
, &iconInfo
))
98 DeleteObject(iconInfo
.hbmColor
);
99 DeleteObject(iconInfo
.hbmMask
);
103 if (!GetObject(iconInfo
.hbmColor
, sizeof(BITMAP
), &bm
))
106 int width
= bm
.bmWidth
;
107 int height
= bm
.bmHeight
;
108 int bytesPerScanLine
= (width
* 3 + 3) & 0xFFFFFFFC;
109 int size
= bytesPerScanLine
* height
;
110 BITMAPINFO infoheader
;
111 infoheader
.bmiHeader
.biSize
= sizeof(BITMAPINFOHEADER
);
112 infoheader
.bmiHeader
.biWidth
= width
;
113 infoheader
.bmiHeader
.biHeight
= height
;
114 infoheader
.bmiHeader
.biPlanes
= 1;
115 infoheader
.bmiHeader
.biBitCount
= 24;
116 infoheader
.bmiHeader
.biCompression
= BI_RGB
;
117 infoheader
.bmiHeader
.biSizeImage
= size
;
119 auto ptrb
= std::make_unique
<BYTE
[]>(size
* 2 + height
* width
* 4);
120 LPBYTE pixelsIconRGB
= ptrb
.get();
121 LPBYTE alphaPixels
= pixelsIconRGB
+ size
;
122 HDC hDC
= CreateCompatibleDC(nullptr);
123 SCOPE_EXIT
{ DeleteDC(hDC
); };
124 HBITMAP hBmpOld
= static_cast<HBITMAP
>(SelectObject(hDC
, iconInfo
.hbmColor
));
125 if (!GetDIBits(hDC
, iconInfo
.hbmColor
, 0, height
, static_cast<LPVOID
>(pixelsIconRGB
), &infoheader
, DIB_RGB_COLORS
))
128 SelectObject(hDC
, hBmpOld
);
129 if (!GetDIBits(hDC
, iconInfo
.hbmMask
, 0, height
, static_cast<LPVOID
>(alphaPixels
), &infoheader
, DIB_RGB_COLORS
))
132 auto imagePixels
= std::make_unique
<UINT
[]>(height
* width
);
133 int lsSrc
= width
* 3;
134 int vsDest
= height
- 1;
135 for (int y
= 0; y
< height
; y
++)
137 int linePosSrc
= (vsDest
- y
) * lsSrc
;
138 int linePosDest
= y
* width
;
139 for (int x
= 0; x
< width
; x
++)
141 int currentDestPos
= linePosDest
+ x
;
142 int currentSrcPos
= linePosSrc
+ x
* 3;
143 imagePixels
[currentDestPos
] = ((static_cast<UINT
>(
145 ((pixelsIconRGB
[currentSrcPos
+ 2] /*Red*/)
146 | (pixelsIconRGB
[currentSrcPos
+ 1] << 8 /*Green*/))
147 | pixelsIconRGB
[currentSrcPos
] << 16 /*Blue*/
149 | ((alphaPixels
[currentSrcPos
] ? 0 : 0xff) << 24))) & 0xffffffff);
155 void CSciEdit::SetColors(bool recolorize
)
157 if (CTheme::Instance().IsDarkTheme())
159 SetClassLongPtr(GetSafeHwnd(), GCLP_HBRBACKGROUND
, reinterpret_cast<LONG_PTR
>(GetStockObject(BLACK_BRUSH
)));
160 Call(SCI_STYLESETFORE
, STYLE_DEFAULT
, !IsWindowEnabled() ? ::CTheme::darkDisabledTextColor
: CTheme::darkTextColor
);
161 Call(SCI_STYLESETBACK
, STYLE_DEFAULT
, !IsWindowEnabled() ? ::GetSysColor(COLOR_BTNFACE
) : CTheme::darkBkColor
);
162 Call(SCI_SETCARETFORE
, CTheme::darkTextColor
);
164 Call(SCI_SETELEMENTCOLOUR
, SC_ELEMENT_LIST
, RGB(187, 187, 187));
165 Call(SCI_SETELEMENTCOLOUR
, SC_ELEMENT_LIST_BACK
, RGB(15, 15, 15));
166 Call(SCI_SETELEMENTCOLOUR
, SC_ELEMENT_LIST_SELECTED
, RGB(187, 187, 187));
167 Call(SCI_SETELEMENTCOLOUR
, SC_ELEMENT_LIST_SELECTED_BACK
, RGB(80, 80, 80));
171 SetClassLongPtr(GetSafeHwnd(), GCLP_HBRBACKGROUND
, reinterpret_cast<LONG_PTR
>(GetSysColorBrush(COLOR_3DFACE
)));
172 Call(SCI_STYLESETFORE
, STYLE_DEFAULT
, ::GetSysColor(!IsWindowEnabled() ? COLOR_GRAYTEXT
: COLOR_WINDOWTEXT
));
173 Call(SCI_STYLESETBACK
, STYLE_DEFAULT
, ::GetSysColor(!IsWindowEnabled() ? COLOR_BTNFACE
: COLOR_WINDOW
));
174 Call(SCI_SETCARETFORE
, ::GetSysColor(COLOR_WINDOWTEXT
));
176 Call(SCI_RESETELEMENTCOLOUR
, SC_ELEMENT_LIST
);
177 Call(SCI_RESETELEMENTCOLOUR
, SC_ELEMENT_LIST_BACK
);
178 Call(SCI_RESETELEMENTCOLOUR
, SC_ELEMENT_LIST_SELECTED
);
179 Call(SCI_RESETELEMENTCOLOUR
, SC_ELEMENT_LIST_SELECTED_BACK
);
181 Call(SCI_SETSELFORE
, TRUE
, CTheme::Instance().GetThemeColor(::GetSysColor(COLOR_HIGHLIGHTTEXT
)));
182 Call(SCI_SETSELBACK
, TRUE
, CTheme::Instance().GetThemeColor(::GetSysColor(COLOR_HIGHLIGHT
)));
184 if (!m_bNoAutomaticStyling
)
186 Call(SCI_STYLECLEARALL
);
188 LPARAM color
= ::GetSysColor(COLOR_HOTLIGHT
);
189 // set the styles for the bug ID strings
190 Call(SCI_STYLESETBOLD
, STYLE_ISSUEBOLD
, TRUE
);
191 Call(SCI_STYLESETFORE
, STYLE_ISSUEBOLD
, color
);
192 Call(SCI_STYLESETBOLD
, STYLE_ISSUEBOLDITALIC
, TRUE
);
193 Call(SCI_STYLESETITALIC
, STYLE_ISSUEBOLDITALIC
, TRUE
);
194 Call(SCI_STYLESETFORE
, STYLE_ISSUEBOLDITALIC
, color
);
195 Call(SCI_STYLESETHOTSPOT
, STYLE_ISSUEBOLDITALIC
, TRUE
);
197 // set the formatted text styles
198 Call(SCI_STYLESETBOLD
, STYLE_BOLD
, TRUE
);
199 Call(SCI_STYLESETITALIC
, STYLE_ITALIC
, TRUE
);
200 Call(SCI_STYLESETUNDERLINE
, STYLE_UNDERLINED
, TRUE
);
202 // set the style for URLs
203 Call(SCI_STYLESETFORE
, STYLE_URL
, color
);
204 Call(SCI_STYLESETHOTSPOT
, STYLE_URL
, TRUE
);
206 Call(SCI_SETHOTSPOTACTIVEUNDERLINE
, TRUE
);
210 Call(SCI_COLOURISE
, 0, -1);
213 void CSciEdit::Init(LONG lLanguage
)
215 //Setup the direct access data
216 m_DirectFunction
= SendMessage(SCI_GETDIRECTFUNCTION
, 0, 0);
217 m_DirectPointer
= SendMessage(SCI_GETDIRECTPOINTER
, 0, 0);
218 Call(SCI_SETMARGINWIDTHN
, 1, 0);
219 Call(SCI_SETUSETABS
, 0); //pressing TAB inserts spaces
220 Call(SCI_SETWRAPVISUALFLAGS
, SC_WRAPVISUALFLAG_END
);
221 Call(SCI_AUTOCSETIGNORECASE
, 1);
222 Call(SCI_SETILEXER
, 0, reinterpret_cast<sptr_t
>(nullptr));
223 Call(SCI_SETCODEPAGE
, SC_CP_UTF8
);
224 Call(SCI_AUTOCSETFILLUPS
, 0, reinterpret_cast<LPARAM
>("\t(["));
225 Call(SCI_AUTOCSETMAXWIDTH
, 0);
226 //Set the default windows colors for edit controls
228 Call(SCI_SETMODEVENTMASK
, SC_MOD_INSERTTEXT
| SC_MOD_DELETETEXT
| SC_PERFORMED_UNDO
| SC_PERFORMED_REDO
);
229 Call(SCI_INDICSETSTYLE
, INDIC_MISSPELLED
, INDIC_SQUIGGLE
);
230 Call(SCI_INDICSETFORE
, INDIC_MISSPELLED
, RGB(255,0,0));
232 CStringA sWhiteSpace
;
233 for (int i
=0; i
<255; ++i
)
235 if (i
== '\r' || i
== '\n')
237 else if (i
< 0x20 || i
== ' ')
238 sWhiteSpace
+= static_cast<char>(i
);
239 else if (isalnum(i
) || i
== '\'' || i
== '_' || i
== '-')
240 sWordChars
+= static_cast<char>(i
);
242 Call(SCI_SETWORDCHARS
, 0, reinterpret_cast<LPARAM
>(static_cast<LPCSTR
>(sWordChars
)));
243 Call(SCI_SETWHITESPACECHARS
, 0, reinterpret_cast<LPARAM
>(static_cast<LPCSTR
>(sWhiteSpace
)));
244 m_bDoStyle
= static_cast<DWORD
>(CRegStdDWORD(L
"Software\\TortoiseGit\\StyleCommitMessages", TRUE
)) == TRUE
;
245 m_nAutoCompleteMinChars
= static_cast<int>(CRegStdDWORD(L
"Software\\TortoiseGit\\AutoCompleteMinChars", 3));
246 // look for dictionary files and use them if found
247 if (lLanguage
>= 0 && static_cast<DWORD
>(CRegStdDWORD(L
"Software\\TortoiseGit\\Spellchecker", TRUE
)) == TRUE
)
249 long langId
= GetUserDefaultLCID();
250 long origLangId
= langId
;
253 // if a specific language is requested, then use that
255 origLangId
= lLanguage
;
258 // first try the Win8 spell checker
259 BOOL supported
= FALSE
;
260 HRESULT hr
= CoCreateInstance(__uuidof(SpellCheckerFactory
), nullptr, CLSCTX_INPROC_SERVER
, IID_PPV_ARGS(&m_spellCheckerFactory
));
261 bool bFallbackUsed
= false;
262 if (SUCCEEDED(hr
) && static_cast<DWORD
>(CRegDWORD(L
"Software\\TortoiseGit\\Win8SpellChecker", FALSE
)) == TRUE
)
264 wchar_t localename
[LOCALE_NAME_MAX_LENGTH
] = { 0 };
267 LCIDToLocaleName(langId
, localename
, _countof(localename
), 0);
269 hr
= m_spellCheckerFactory
->IsSupported(localename
, &supported
);
272 hr
= m_spellCheckerFactory
->CreateSpellChecker(localename
, &m_SpellChecker
);
275 m_personalDict
.Init(langId
);
279 DWORD lid
= SUBLANGID(langId
);
282 langId
= MAKELANGID(PRIMARYLANGID(langId
), lid
);
283 else if (langId
== 1033)
288 bFallbackUsed
= true;
290 } while (langId
&& (!supported
|| FAILED(hr
)));
292 if (FAILED(hr
) || !supported
|| bFallbackUsed
)
296 if ((lLanguage
== 0) || (lLanguage
&& !LoadDictionaries(lLanguage
)))
300 LoadDictionaries(langId
);
301 DWORD lid
= SUBLANGID(langId
);
304 langId
= MAKELANGID(PRIMARYLANGID(langId
), lid
);
305 else if (langId
== 1033)
309 if (bFallbackUsed
&& supported
)
314 } while (langId
&& !pChecker
);
316 if (bFallbackUsed
&& pChecker
)
317 m_SpellChecker
= nullptr;
321 Call(SCI_SETEDGEMODE
, EDGE_NONE
);
322 Call(SCI_SETWRAPMODE
, SC_WRAP_WORD
);
323 Call(SCI_ASSIGNCMDKEY
, SCK_END
, SCI_LINEENDWRAP
);
324 Call(SCI_ASSIGNCMDKEY
, SCK_END
+ (SCMOD_SHIFT
<< 16), SCI_LINEENDWRAPEXTEND
);
325 Call(SCI_ASSIGNCMDKEY
, SCK_HOME
, SCI_HOMEWRAP
);
326 Call(SCI_ASSIGNCMDKEY
, SCK_HOME
+ (SCMOD_SHIFT
<< 16), SCI_HOMEWRAPEXTEND
);
327 if (CRegStdDWORD(L
"Software\\TortoiseGit\\ScintillaDirect2D", FALSE
) != FALSE
)
329 // set font quality for the popup window, since that window does not use D2D
330 Call(SCI_SETFONTQUALITY
, SC_EFF_QUALITY_LCD_OPTIMIZED
);
332 Call(SCI_SETTECHNOLOGY
, SC_TECHNOLOGY_DIRECTWRITERETAIN
);
333 Call(SCI_SETBUFFEREDDRAW
, 0);
335 m_themeCallbackId
= CTheme::Instance().RegisterThemeChangeCallback([this]() { OnSysColorChange(); });
339 void CSciEdit::Init(const ProjectProperties
& props
)
341 Init(props
.lProjectLanguage
);
342 m_sCommand
= CUnicodeUtils::GetUTF8(props
.GetCheckRe());
343 m_sBugID
= CUnicodeUtils::GetUTF8(props
.GetBugIDRe());
344 m_sUrl
= CUnicodeUtils::GetUTF8(props
.sUrl
);
346 Call(SCI_SETMOUSEDWELLTIME
, 333);
348 if (props
.nLogWidthMarker
)
350 Call(SCI_SETWRAPMODE
, SC_WRAP_NONE
);
351 Call(SCI_SETEDGEMODE
, EDGE_LINE
);
352 Call(SCI_SETEDGECOLUMN
, props
.nLogWidthMarker
);
353 Call(SCI_SETSCROLLWIDTHTRACKING
, TRUE
);
354 Call(SCI_SETSCROLLWIDTH
, 1);
358 Call(SCI_SETEDGEMODE
, EDGE_NONE
);
359 Call(SCI_SETWRAPMODE
, SC_WRAP_WORD
);
363 void CSciEdit::SetIcon(const std::map
<int, UINT
> &icons
)
365 int iconWidth
= GetSystemMetrics(SM_CXSMICON
);
366 int iconHeight
= GetSystemMetrics(SM_CYSMICON
);
367 Call(SCI_RGBAIMAGESETWIDTH
, iconWidth
);
368 Call(SCI_RGBAIMAGESETHEIGHT
, iconHeight
);
369 for (auto icon
: icons
)
371 CAutoIcon hIcon
= LoadIconEx(AfxGetInstanceHandle(), MAKEINTRESOURCE(icon
.second
), iconWidth
, iconHeight
);
372 auto bytes
= Icon2Image(hIcon
);
373 Call(SCI_REGISTERRGBAIMAGE
, icon
.first
, reinterpret_cast<LPARAM
>(bytes
.get()));
377 BOOL
CSciEdit::LoadDictionaries(LONG lLanguageID
)
379 // Setup the spell checker
380 wchar_t buf
[6] = { 0 };
381 CString sFolderUp
= CPathUtils::GetAppParentDirectory();
382 CString sFolderAppData
= CPathUtils::GetAppDataDirectory();
385 GetLocaleInfo(MAKELCID(lLanguageID
, SORT_DEFAULT
), LOCALE_SISO639LANGNAME
, buf
, _countof(buf
));
387 if (lLanguageID
== 2074)
390 GetLocaleInfo(MAKELCID(lLanguageID
, SORT_DEFAULT
), LOCALE_SISO3166CTRYNAME
, buf
, _countof(buf
));
394 if ((PathFileExists(sFolderAppData
+ L
"dic\\" + sFile
+ L
".aff")) &&
395 (PathFileExists(sFolderAppData
+ L
"dic\\" + sFile
+ L
".dic")))
397 pChecker
= std::make_unique
<Hunspell
>(CStringA(sFolderAppData
+ L
"dic\\" + sFile
+ L
".aff"), CStringA(sFolderAppData
+ L
"dic\\" + sFile
+ L
".dic"));
399 else if ((PathFileExists(sFolderUp
+ L
"Languages\\" + sFile
+ L
".aff")) &&
400 (PathFileExists(sFolderUp
+ L
"Languages\\" + sFile
+ L
".dic")))
402 pChecker
= std::make_unique
<Hunspell
>(CStringA(sFolderUp
+ L
"Languages\\" + sFile
+ L
".aff"), CStringA(sFolderUp
+ L
"Languages\\" + sFile
+ L
".dic"));
406 const char* encoding
= pChecker
->get_dic_encoding();
407 CTraceToOutputDebugString::Instance()(__FUNCTION__
": %s\n", encoding
);
409 for (int i
= 0; i
< _countof(enc2locale
); ++i
)
411 if (strcmp(encoding
, enc2locale
[i
].def_enc
) == 0)
412 m_spellcodepage
= atoi(enc2locale
[i
].cp
);
414 m_personalDict
.Init(lLanguageID
);
422 LRESULT
CSciEdit::Call(UINT message
, WPARAM wParam
, LPARAM lParam
)
424 ASSERT(::IsWindow(m_hWnd
)); //Window must be valid
425 ASSERT(m_DirectFunction
); //Direct function must be valid
426 return reinterpret_cast<SciFnDirect
>(m_DirectFunction
)(m_DirectPointer
, message
, wParam
, lParam
);
429 CString
CSciEdit::StringFromControl(const CStringA
& text
)
433 int codepage
= static_cast<int>(Call(SCI_GETCODEPAGE
));
434 int reslen
= MultiByteToWideChar(codepage
, 0, text
, text
.GetLength(), 0, 0);
435 MultiByteToWideChar(codepage
, 0, text
, text
.GetLength(), sText
.GetBuffer(reslen
+1), reslen
+1);
436 sText
.ReleaseBuffer(reslen
);
443 CStringA
CSciEdit::StringForControl(const CString
& text
)
447 int codepage
= static_cast<int>(SendMessage(SCI_GETCODEPAGE
));
448 int reslen
= WideCharToMultiByte(codepage
, 0, text
, text
.GetLength(), 0, 0, 0, 0);
449 WideCharToMultiByte(codepage
, 0, text
, text
.GetLength(), sTextA
.GetBuffer(reslen
), reslen
, 0, 0);
450 sTextA
.ReleaseBuffer(reslen
);
454 ATLTRACE("string length %d\n", sTextA
.GetLength());
458 void CSciEdit::SetText(const CString
& sText
)
460 auto readonly
= m_bReadOnly
;
461 SCOPE_EXIT
{ SetReadOnly(readonly
); };
464 CStringA sTextA
= StringForControl(sText
);
465 Call(SCI_SETTEXT
, 0, reinterpret_cast<LPARAM
>(static_cast<LPCSTR
>(sTextA
)));
467 if (Call(SCI_GETSCROLLWIDTHTRACKING
) != 0)
468 Call(SCI_SETSCROLLWIDTH
, 1);
470 // Scintilla seems to have problems with strings that
471 // aren't terminated by a newline char. Once that char
472 // is there, it can be removed without problems.
473 // So we add here a newline, then remove it again.
474 Call(SCI_DOCUMENTEND
);
476 Call(SCI_DELETEBACK
);
479 void CSciEdit::InsertText(const CString
& sText
, bool bNewLine
)
481 auto readonly
= m_bReadOnly
;
482 SCOPE_EXIT
{ SetReadOnly(readonly
); };
485 CStringA sTextA
= StringForControl(sText
);
486 Call(SCI_REPLACESEL
, 0, reinterpret_cast<LPARAM
>(static_cast<LPCSTR
>(sTextA
)));
488 Call(SCI_REPLACESEL
, 0, reinterpret_cast<LPARAM
>("\n"));
491 CString
CSciEdit::GetText()
493 auto len
= static_cast<int>(Call(SCI_GETTEXT
, 0, 0));
495 Call(SCI_GETTEXT
, len
+ 1, reinterpret_cast<LPARAM
>(static_cast<LPCSTR
>(CStrBufA(sTextA
, len
, 0))));
496 return StringFromControl(sTextA
);
499 CString
CSciEdit::GetWordUnderCursor(bool bSelectWord
, bool allchars
)
501 Sci_TextRange textrange
;
502 auto pos
= static_cast<Sci_Position
>(Call(SCI_GETCURRENTPOS
));
503 textrange
.chrg
.cpMin
= static_cast<int>(Call(SCI_WORDSTARTPOSITION
, pos
, TRUE
));
504 if ((pos
== textrange
.chrg
.cpMin
)||(textrange
.chrg
.cpMin
< 0))
506 textrange
.chrg
.cpMax
= static_cast<int>(Call(SCI_WORDENDPOSITION
, textrange
.chrg
.cpMin
, TRUE
));
509 textrange
.lpstrText
= textbuffer
.GetBuffer(textrange
.chrg
.cpMax
- textrange
.chrg
.cpMin
);
510 Call(SCI_GETTEXTRANGE
, 0, reinterpret_cast<LPARAM
>(&textrange
));
511 textbuffer
.ReleaseBufferSetLength(textrange
.chrg
.cpMax
- textrange
.chrg
.cpMin
);
512 CString sRet
= StringFromControl(textbuffer
);
513 if (m_bDoStyle
&& !allchars
)
515 for (const auto styleindicator
: { '*', '_', '^' })
519 if (sRet
[sRet
.GetLength() - 1] == styleindicator
)
521 --textrange
.chrg
.cpMax
;
522 sRet
.Truncate(sRet
.GetLength() - 1);
526 if (sRet
[0] == styleindicator
)
528 ++textrange
.chrg
.cpMin
;
529 sRet
= sRet
.Right(sRet
.GetLength() - 1);
534 Call(SCI_SETSEL
, textrange
.chrg
.cpMin
, textrange
.chrg
.cpMax
);
538 void CSciEdit::SetFont(CString sFontName
, int iFontSizeInPoints
)
540 Call(SCI_STYLESETFONT
, STYLE_DEFAULT
, reinterpret_cast<LPARAM
>(CUnicodeUtils::GetUTF8(sFontName
).GetBuffer()));
541 Call(SCI_STYLESETSIZE
, STYLE_DEFAULT
, iFontSizeInPoints
);
542 Call(SCI_STYLECLEARALL
);
544 LPARAM color
= GetSysColor(COLOR_HOTLIGHT
);
545 // set the styles for the bug ID strings
546 Call(SCI_STYLESETBOLD
, STYLE_ISSUEBOLD
, TRUE
);
547 Call(SCI_STYLESETFORE
, STYLE_ISSUEBOLD
, color
);
548 Call(SCI_STYLESETBOLD
, STYLE_ISSUEBOLDITALIC
, TRUE
);
549 Call(SCI_STYLESETITALIC
, STYLE_ISSUEBOLDITALIC
, TRUE
);
550 Call(SCI_STYLESETFORE
, STYLE_ISSUEBOLDITALIC
, color
);
551 Call(SCI_STYLESETHOTSPOT
, STYLE_ISSUEBOLDITALIC
, TRUE
);
553 // set the formatted text styles
554 Call(SCI_STYLESETBOLD
, STYLE_BOLD
, TRUE
);
555 Call(SCI_STYLESETITALIC
, STYLE_ITALIC
, TRUE
);
556 Call(SCI_STYLESETUNDERLINE
, STYLE_UNDERLINED
, TRUE
);
558 // set the style for URLs
559 Call(SCI_STYLESETFORE
, STYLE_URL
, color
);
560 Call(SCI_STYLESETHOTSPOT
, STYLE_URL
, TRUE
);
562 Call(SCI_SETHOTSPOTACTIVEUNDERLINE
, TRUE
);
565 void CSciEdit::SetAutoCompletionList(std::map
<CString
, int>&& list
, wchar_t separator
, wchar_t typeSeparator
)
567 //copy the auto completion list.
569 //SK: instead of creating a copy of that list, we could accept a pointer
570 //to the list and use that instead. But then the caller would have to make
571 //sure that the list persists over the lifetime of the control!
573 m_autolist
= std::move(list
);
574 m_separator
= separator
;
575 m_typeSeparator
= typeSeparator
;
578 // Helper for CSciEdit::IsMisspelled()
579 // Returns TRUE if sWord has spelling errors.
580 BOOL
CSciEdit::CheckWordSpelling(const CString
& sWord
)
584 IEnumSpellingErrorPtr enumSpellingError
= nullptr;
585 if (SUCCEEDED(m_SpellChecker
->Check(sWord
, &enumSpellingError
)))
587 ISpellingErrorPtr spellingError
= nullptr;
588 if (enumSpellingError
->Next(&spellingError
) == S_OK
)
590 CORRECTIVE_ACTION action
= CORRECTIVE_ACTION_NONE
;
591 spellingError
->get_CorrectiveAction(&action
);
592 if (action
!= CORRECTIVE_ACTION_NONE
)
599 // convert the string from the control to the encoding of the spell checker module.
600 auto sWordA
= GetWordForSpellChecker(sWord
);
601 if (!pChecker
->spell(sWordA
))
608 BOOL
CSciEdit::IsMisspelled(const CString
& sWord
)
610 // words starting with a digit are treated as correctly spelled
611 if (_istdigit(sWord
.GetAt(0)))
613 // words in the personal dictionary are correct too
614 if (m_personalDict
.FindWord(sWord
))
617 // Check spell checking cache first.
618 const BOOL
*cacheResult
= m_SpellingCache
.try_get(std::wstring(sWord
, sWord
.GetLength()));
622 // convert the string from the control to the encoding of the spell checker module.
623 auto sWordA
= GetWordForSpellChecker(sWord
);
625 // now we actually check the spelling...
626 BOOL misspelled
= CheckWordSpelling(sWord
);
629 // the word is marked as misspelled, we now check whether the word
630 // is maybe a composite identifier
631 // a composite identifier consists of multiple words, with each word
632 // separated by a change in lower to uppercase letters
634 if (sWord
.GetLength() > 1)
638 while (wordend
< sWord
.GetLength())
640 while ((wordend
< sWord
.GetLength())&&(!_istupper(sWord
[wordend
])))
642 if ((wordstart
== 0)&&(wordend
== sWord
.GetLength()))
644 // words in the auto list are also assumed correctly spelled
645 if (m_autolist
.find(sWord
) != m_autolist
.end())
651 CString
token(sWord
.Mid(wordstart
, wordend
- wordstart
));
652 if (token
.GetLength() > 2 && CheckWordSpelling(token
))
664 m_SpellingCache
.insert_or_assign(std::wstring(sWord
, sWord
.GetLength()), misspelled
);
668 void CSciEdit::CheckSpelling(Sci_Position startpos
, Sci_Position endpos
)
673 if (!pChecker
&& !m_SpellChecker
)
676 Sci_TextRange textrange
;
677 textrange
.chrg
.cpMin
= static_cast<Sci_PositionCR
>(startpos
);
678 textrange
.chrg
.cpMax
= textrange
.chrg
.cpMin
;
679 auto lastpos
= endpos
;
681 lastpos
= static_cast<int>(Call(SCI_GETLENGTH
)) - textrange
.chrg
.cpMin
;
682 Call(SCI_SETINDICATORCURRENT
, INDIC_MISSPELLED
);
683 while (textrange
.chrg
.cpMax
< lastpos
)
685 textrange
.chrg
.cpMin
= static_cast<int>(Call(SCI_WORDSTARTPOSITION
, textrange
.chrg
.cpMax
+ 1, TRUE
));
686 if (textrange
.chrg
.cpMin
< textrange
.chrg
.cpMax
)
688 textrange
.chrg
.cpMax
= static_cast<int>(Call(SCI_WORDENDPOSITION
, textrange
.chrg
.cpMin
, TRUE
));
689 if (textrange
.chrg
.cpMin
== textrange
.chrg
.cpMax
)
691 textrange
.chrg
.cpMax
++;
692 // since Scintilla squiggles to the end of the text even if told to stop one char before it,
693 // we have to clear here the squiggly lines to the end.
694 if (textrange
.chrg
.cpMin
)
695 Call(SCI_INDICATORCLEARRANGE
, textrange
.chrg
.cpMin
-1, textrange
.chrg
.cpMax
- textrange
.chrg
.cpMin
+ 1);
698 ATLASSERT(textrange
.chrg
.cpMax
>= textrange
.chrg
.cpMin
);
699 auto textbuffer
= std::make_unique
<char[]>(textrange
.chrg
.cpMax
- textrange
.chrg
.cpMin
+ 2);
700 textrange
.lpstrText
= textbuffer
.get();
701 textrange
.chrg
.cpMax
++;
702 Call(SCI_GETTEXTRANGE
, 0, reinterpret_cast<LPARAM
>(&textrange
));
703 auto len
= static_cast<int>(strlen(textrange
.lpstrText
));
706 textrange
.chrg
.cpMax
--;
707 Call(SCI_GETTEXTRANGE
, 0, reinterpret_cast<LPARAM
>(&textrange
));
708 len
= static_cast<int>(strlen(textrange
.lpstrText
));
709 textrange
.chrg
.cpMax
++;
712 if (len
&& textrange
.lpstrText
[len
- 1] == '.')
714 // Try to ignore file names from the auto list.
715 // Do do this, for each word ending with '.' we extract next word and check
716 // whether the combined string is present in auto list.
717 Sci_TextRange twoWords
;
718 twoWords
.chrg
.cpMin
= textrange
.chrg
.cpMin
;
719 twoWords
.chrg
.cpMax
= static_cast<int>(Call(SCI_WORDENDPOSITION
, textrange
.chrg
.cpMax
+ 1, TRUE
));
720 CStringA twoWordsBuffer
;
721 twoWords
.lpstrText
= twoWordsBuffer
.GetBuffer(twoWords
.chrg
.cpMax
- twoWords
.chrg
.cpMin
);
722 Call(SCI_GETTEXTRANGE
, 0, reinterpret_cast<LPARAM
>(&twoWords
));
723 twoWordsBuffer
.ReleaseBufferSetLength(twoWords
.chrg
.cpMax
- twoWords
.chrg
.cpMin
);
724 CString sWord
= StringFromControl(twoWords
.lpstrText
);
725 if (m_autolist
.find(sWord
) != m_autolist
.end())
727 //mark word as correct (remove the squiggle line)
728 Call(SCI_INDICATORCLEARRANGE
, twoWords
.chrg
.cpMin
, twoWords
.chrg
.cpMax
- twoWords
.chrg
.cpMin
);
729 textrange
.chrg
.cpMax
= twoWords
.chrg
.cpMax
;
734 textrange
.lpstrText
[len
- 1] = '\0';
735 textrange
.chrg
.cpMax
--;
736 if (textrange
.lpstrText
[0])
738 CString sWord
= StringFromControl(textrange
.lpstrText
);
739 if ((GetStyleAt(textrange
.chrg
.cpMin
) != STYLE_URL
) && IsMisspelled(sWord
))
741 //mark word as misspelled
742 Call(SCI_INDICATORFILLRANGE
, textrange
.chrg
.cpMin
, textrange
.chrg
.cpMax
- textrange
.chrg
.cpMin
);
746 //mark word as correct (remove the squiggle line)
747 Call(SCI_INDICATORCLEARRANGE
, textrange
.chrg
.cpMin
, textrange
.chrg
.cpMax
- textrange
.chrg
.cpMin
);
748 Call(SCI_INDICATORCLEARRANGE
, textrange
.chrg
.cpMin
, textrange
.chrg
.cpMax
- textrange
.chrg
.cpMin
+ 1);
754 void CSciEdit::SuggestSpellingAlternatives()
756 if (!pChecker
&& !m_SpellChecker
)
758 CString word
= GetWordUnderCursor(true);
759 Call(SCI_SETCURRENTPOS
, Call(SCI_WORDSTARTPOSITION
, Call(SCI_GETCURRENTPOS
), TRUE
));
766 IEnumStringPtr enumSpellingSuggestions
= nullptr;
767 if (SUCCEEDED(m_SpellChecker
->Suggest(word
, &enumSpellingSuggestions
)))
769 LPOLESTR string
= nullptr;
770 while (enumSpellingSuggestions
->Next(1, &string
, nullptr) == S_OK
)
772 suggestions
.AppendFormat(L
"%s%c%d%c", (LPCWSTR
)CString(string
), m_typeSeparator
, AUTOCOMPLETE_SPELLING
, m_separator
);
773 CoTaskMemFree(string
);
779 auto wlst
= pChecker
->suggest(GetWordForSpellChecker(word
));
782 for (const auto& alternative
: wlst
)
783 suggestions
.AppendFormat(L
"%s%c%d%c", static_cast<LPCWSTR
>(GetWordFromSpellChecker(alternative
)), m_typeSeparator
, AUTOCOMPLETE_SPELLING
, m_separator
);
787 suggestions
.TrimRight(m_separator
);
788 if (suggestions
.IsEmpty())
790 Call(SCI_AUTOCSETSEPARATOR
, CStringA(m_separator
).GetAt(0));
791 Call(SCI_AUTOCSETTYPESEPARATOR
, m_typeSeparator
);
792 Call(SCI_AUTOCSETDROPRESTOFWORD
, 1);
793 Call(SCI_AUTOCSHOW
, 0, reinterpret_cast<LPARAM
>(static_cast<LPCSTR
>(StringForControl(suggestions
))));
794 SetWindowStylesForAutocompletionPopup();
797 void CSciEdit::DoAutoCompletion(Sci_Position nMinPrefixLength
)
799 if (m_autolist
.empty())
801 auto pos
= static_cast<int>(static_cast<Sci_Position
>(Call(SCI_GETCURRENTPOS
)));
802 if (pos
!= static_cast<int>(Call(SCI_WORDENDPOSITION
, pos
, TRUE
)))
803 return; // don't auto complete if we're not at the end of a word
804 CString word
= GetWordUnderCursor();
805 if (word
.GetLength() < nMinPrefixLength
)
807 word
= GetWordUnderCursor(false, true);
808 if (word
.GetLength() < nMinPrefixLength
)
809 return; // don't auto complete yet, word is too short
811 CString sAutoCompleteList
;
813 for (int i
= 0; i
< 2; ++i
)
815 std::vector
<CString
> words
;
817 pos
= word
.Find('-');
819 CString wordLower
= word
;
820 wordLower
.MakeLower();
821 CString wordHigher
= word
;
822 wordHigher
.MakeUpper();
824 words
.push_back(word
);
825 words
.push_back(wordLower
);
826 words
.push_back(wordHigher
);
830 CString s
= wordLower
.Left(pos
);
831 if (s
.GetLength() >= nMinPrefixLength
)
833 s
= wordLower
.Mid(pos
+ 1);
834 if (s
.GetLength() >= nMinPrefixLength
)
836 s
= wordHigher
.Left(pos
);
837 if (s
.GetLength() >= nMinPrefixLength
)
838 words
.push_back(wordHigher
.Left(pos
));
839 s
= wordHigher
.Mid(pos
+ 1);
840 if (s
.GetLength() >= nMinPrefixLength
)
841 words
.push_back(wordHigher
.Mid(pos
+1));
844 // note: the m_autolist is case-sensitive because
845 // its contents are also used to mark words in it
846 // as correctly spelled. If it would be case-insensitive,
847 // case spelling mistakes would not show up as misspelled.
848 std::map
<CString
, int> wordset
;
849 for (const auto& w
: words
)
851 for (auto lowerit
= m_autolist
.lower_bound(w
);
852 lowerit
!= m_autolist
.end(); ++lowerit
)
854 int compare
= w
.CompareNoCase(lowerit
->first
.Left(w
.GetLength()));
857 else if (compare
== 0)
858 wordset
.emplace(lowerit
->first
, lowerit
->second
);
864 for (const auto& w
: wordset
)
865 sAutoCompleteList
.AppendFormat(L
"%s%c%d%c", static_cast<LPCWSTR
>(w
.first
), m_typeSeparator
, w
.second
, m_separator
);
867 sAutoCompleteList
.TrimRight(m_separator
);
871 if (sAutoCompleteList
.IsEmpty())
873 // retry with all chars
874 word
= GetWordUnderCursor(false, true);
881 if (sAutoCompleteList
.IsEmpty())
886 Call(SCI_AUTOCSETSEPARATOR
, CStringA(m_separator
).GetAt(0));
887 Call(SCI_AUTOCSETTYPESEPARATOR
, (m_typeSeparator
));
888 auto sForControl
= StringForControl(sAutoCompleteList
);
889 Call(SCI_AUTOCSHOW
, StringForControl(word
).GetLength(), reinterpret_cast<LPARAM
>(static_cast<LPCSTR
>(sForControl
)));
890 SetWindowStylesForAutocompletionPopup();
893 BOOL
CSciEdit::OnChildNotify(UINT message
, WPARAM wParam
, LPARAM lParam
, LRESULT
* pLResult
)
895 if (message
!= WM_NOTIFY
)
896 return CWnd::OnChildNotify(message
, wParam
, lParam
, pLResult
);
898 auto lpnmhdr
= reinterpret_cast<LPNMHDR
>(lParam
);
899 auto lpSCN
= reinterpret_cast<SCNotification
*>(lParam
);
901 if(lpnmhdr
->hwndFrom
==m_hWnd
)
903 switch(lpnmhdr
->code
)
907 if ((lpSCN
->ch
< 32)&&(lpSCN
->ch
!= 13)&&(lpSCN
->ch
!= 10))
908 Call(SCI_DELETEBACK
);
910 DoAutoCompletion(m_nAutoCompleteMinChars
);
914 case SCN_AUTOCSELECTION
:
916 CString text
= StringFromControl(lpSCN
->text
);
917 if (m_autolist
[text
] == AUTOCOMPLETE_SNIPPET
)
919 Call(SCI_AUTOCCANCEL
);
920 for (INT_PTR handlerindex
= 0; handlerindex
< m_arContextHandlers
.GetCount(); ++handlerindex
)
922 CSciEditContextMenuInterface
* pHandler
= m_arContextHandlers
.GetAt(handlerindex
);
923 pHandler
->HandleSnippet(m_autolist
[text
], text
, this);
928 case SCN_STYLENEEDED
:
930 auto startpos
= static_cast<Sci_Position
>(Call(SCI_GETENDSTYLED
));
931 auto endpos
= reinterpret_cast<SCNotification
*>(lpnmhdr
)->position
;
933 auto startwordpos
= static_cast<int>(Call(SCI_WORDSTARTPOSITION
, startpos
, true));
934 auto endwordpos
= static_cast<int>(Call(SCI_WORDENDPOSITION
, endpos
, true));
936 MarkEnteredBugID(startwordpos
, endwordpos
);
938 StyleEnteredText(startwordpos
, endwordpos
);
940 StyleURLs(startwordpos
, endwordpos
);
941 CheckSpelling(startwordpos
, endwordpos
);
943 // Tell scintilla editor that we styled all requested range.
944 Call(SCI_STARTSTYLING
, endwordpos
);
945 Call(SCI_SETSTYLING
, 0, 0);
950 if (!m_blockModifiedHandler
&& (lpSCN
->modificationType
& (SC_MOD_INSERTTEXT
| SC_MOD_DELETETEXT
)))
952 auto firstline
= static_cast<int>(Call(SCI_GETFIRSTVISIBLELINE
));
953 auto lastline
= firstline
+ static_cast<int>(Call(SCI_LINESONSCREEN
));
954 auto firstpos
= static_cast<Sci_Position
>(Call(SCI_POSITIONFROMLINE
, firstline
));
955 auto lastpos
= static_cast<Sci_Position
>(Call(SCI_GETLINEENDPOSITION
, lastline
));
956 auto pos1
= lpSCN
->position
;
957 auto pos2
= pos1
+ lpSCN
->length
;
958 // always use the bigger range
959 firstpos
= min(firstpos
, pos1
);
960 lastpos
= max(lastpos
, pos2
);
962 WrapLines(firstpos
, lastpos
);
967 case SCN_HOTSPOTRELEASECLICK
:
969 Sci_TextRange textrange
;
970 textrange
.chrg
.cpMin
= static_cast<Sci_PositionCR
>(lpSCN
->position
);
971 textrange
.chrg
.cpMax
= static_cast<Sci_PositionCR
>(lpSCN
->position
);
972 auto style
= GetStyleAt(lpSCN
->position
);
973 if (style
!= STYLE_ISSUEBOLDITALIC
&& style
!= STYLE_URL
)
975 while (GetStyleAt(textrange
.chrg
.cpMin
- 1) == style
)
976 --textrange
.chrg
.cpMin
;
977 while (GetStyleAt(textrange
.chrg
.cpMax
+ 1) == style
)
978 ++textrange
.chrg
.cpMax
;
979 ++textrange
.chrg
.cpMax
;
981 textrange
.lpstrText
= textbuffer
.GetBuffer(textrange
.chrg
.cpMax
- textrange
.chrg
.cpMin
);
982 Call(SCI_GETTEXTRANGE
, 0, reinterpret_cast<LPARAM
>(&textrange
));
983 textbuffer
.ReleaseBufferSetLength(textrange
.chrg
.cpMax
- textrange
.chrg
.cpMin
);
985 if (style
== STYLE_URL
)
987 url
= StringFromControl(textbuffer
);
988 if (url
.Find(L
'@') > 0 && !PathIsURL(url
))
989 url
= L
"mailto:" + url
;
994 ProjectProperties::ReplaceBugIDPlaceholder(url
, StringFromControl(textbuffer
));
998 if (lpnmhdr
->code
== SCN_HOTSPOTRELEASECLICK
)
999 ShellExecute(GetParent()->GetSafeHwnd(), L
"open", url
, nullptr, nullptr, SW_SHOWDEFAULT
);
1002 CStringA sTextA
= StringForControl(url
);
1003 Call(SCI_CALLTIPSHOW
, lpSCN
->position
+ 3, reinterpret_cast<LPARAM
>(static_cast<LPCSTR
>(sTextA
)));
1009 Call(SCI_CALLTIPCANCEL
);
1013 return CWnd::OnChildNotify(message
, wParam
, lParam
, pLResult
);
1016 BEGIN_MESSAGE_MAP(CSciEdit
, CWnd
)
1019 ON_WM_SYSCOLORCHANGE()
1022 void CSciEdit::OnSysColorChange()
1024 __super::OnSysColorChange();
1025 CTheme::Instance().OnSysColorChanged();
1031 void CSciEdit::OnKeyDown(UINT nChar
, UINT nRepCnt
, UINT nFlags
)
1037 if ((Call(SCI_AUTOCACTIVE
)==0)&&(Call(SCI_CALLTIPACTIVE
)==0))
1038 ::SendMessage(GetParent()->GetSafeHwnd(), WM_CLOSE
, 0, 0);
1042 CWnd::OnKeyDown(nChar
, nRepCnt
, nFlags
);
1045 BOOL
CSciEdit::PreTranslateMessage(MSG
* pMsg
)
1047 if (pMsg
->message
== WM_KEYDOWN
)
1049 switch (pMsg
->wParam
)
1053 if ((GetKeyState(VK_CONTROL
) & 0x8000) && ((GetKeyState(VK_MENU
) & 0x8000) == 0))
1055 DoAutoCompletion(1);
1061 // The TAB cannot be handled in OnKeyDown because it is too late by then.
1063 if ((GetKeyState(VK_CONTROL
) & 0x8000) && ((GetKeyState(VK_MENU
) & 0x8000) == 0))
1065 //Ctrl-Tab was pressed, this means we should provide the user with
1066 //a list of possible spell checking alternatives to the word under
1068 SuggestSpellingAlternatives();
1071 else if (!Call(SCI_AUTOCACTIVE
))
1073 ::PostMessage(GetParent()->GetSafeHwnd(), WM_NEXTDLGCTL
, GetKeyState(VK_SHIFT
)&0x8000, 0);
1080 return CWnd::PreTranslateMessage(pMsg
);
1083 void CSciEdit::OnContextMenu(CWnd
* /*pWnd*/, CPoint point
)
1085 auto anchor
= static_cast<Sci_Position
>(Call(SCI_GETANCHOR
));
1086 auto currentpos
= static_cast<Sci_Position
>(Call(SCI_GETCURRENTPOS
));
1087 auto selstart
= static_cast<int>(static_cast<Sci_Position
>(Call(SCI_GETSELECTIONSTART
)));
1088 auto selend
= static_cast<int>(static_cast<Sci_Position
>(Call(SCI_GETSELECTIONEND
)));
1089 Sci_Position pointpos
= 0;
1090 if ((point
.x
== -1) && (point
.y
== -1))
1093 GetClientRect(&rect
);
1094 ClientToScreen(&rect
);
1095 point
= rect
.CenterPoint();
1096 pointpos
= static_cast<Sci_Position
>(Call(SCI_GETCURRENTPOS
));
1100 // change the cursor position to the point where the user
1102 CPoint clientpoint
= point
;
1103 ScreenToClient(&clientpoint
);
1104 pointpos
= static_cast<Sci_Position
>(Call(SCI_POSITIONFROMPOINT
, clientpoint
.x
, clientpoint
.y
));
1106 CString sMenuItemText
;
1108 bool bRestoreCursor
= true;
1109 if (popup
.CreatePopupMenu())
1111 bool bCanUndo
= !!Call(SCI_CANUNDO
);
1112 bool bCanRedo
= !!Call(SCI_CANREDO
);
1113 bool bHasSelection
= (selend
-selstart
> 0);
1114 bool bCanPaste
= !!Call(SCI_CANPASTE
);
1115 bool bIsReadOnly
= !!Call(SCI_GETREADONLY
);
1116 UINT uEnabledMenu
= MF_STRING
| MF_ENABLED
;
1117 UINT uDisabledMenu
= MF_STRING
| MF_GRAYED
;
1119 // find the word under the cursor
1123 // setting the cursor clears the selection
1124 Call(SCI_SETANCHOR
, pointpos
);
1125 Call(SCI_SETCURRENTPOS
, pointpos
);
1126 sWord
= GetWordUnderCursor();
1127 // restore the selection
1128 Call(SCI_SETSELECTIONSTART
, selstart
);
1129 Call(SCI_SETSELECTIONEND
, selend
);
1132 sWord
= GetWordUnderCursor();
1133 auto worda
= GetWordForSpellChecker(sWord
);
1135 int nCorrections
= 1;
1136 // check if the word under the cursor is spelled wrong
1137 if (!bIsReadOnly
&& (pChecker
|| m_SpellChecker
) && !worda
.empty())
1141 IEnumStringPtr enumSpellingSuggestions
= nullptr;
1142 if (SUCCEEDED(m_SpellChecker
->Suggest(sWord
, &enumSpellingSuggestions
)))
1144 LPOLESTR string
= nullptr;
1145 while (enumSpellingSuggestions
->Next(1, &string
, nullptr) == S_OK
)
1147 popup
.InsertMenu((UINT
)-1, 0, nCorrections
++, string
);
1148 CoTaskMemFree(string
);
1150 popup
.AppendMenu(MF_SEPARATOR
);
1155 // get the spell suggestions
1156 auto wlst
= pChecker
->suggest(worda
);
1159 // add the suggestions to the context menu
1160 for (const auto& alternative
: wlst
)
1162 CString sug
= GetWordFromSpellChecker(alternative
);
1163 popup
.InsertMenu(static_cast<UINT
>(-1), 0, nCorrections
++, sug
);
1165 popup
.AppendMenu(MF_SEPARATOR
);
1170 // also allow the user to add the word to the custom dictionary so
1171 // it won't show up as misspelled anymore
1172 if (!bIsReadOnly
&& (sWord
.GetLength() < PDICT_MAX_WORD_LENGTH
) && (m_autolist
.find(sWord
) == m_autolist
.end() && IsMisspelled(sWord
)) &&
1173 (!_istdigit(sWord
.GetAt(0))) && (!m_personalDict
.FindWord(sWord
)))
1175 sMenuItemText
.Format(IDS_SCIEDIT_ADDWORD
, static_cast<LPCWSTR
>(sWord
));
1176 popup
.AppendMenu(uEnabledMenu
, SCI_ADDWORD
, sMenuItemText
);
1177 // another separator
1178 popup
.AppendMenu(MF_SEPARATOR
);
1181 // add the 'default' entries
1182 sMenuItemText
.LoadString(IDS_SCIEDIT_UNDO
);
1183 popup
.AppendMenu(bCanUndo
? uEnabledMenu
: uDisabledMenu
, SCI_UNDO
, sMenuItemText
);
1184 sMenuItemText
.LoadString(IDS_SCIEDIT_REDO
);
1185 popup
.AppendMenu(bCanRedo
? uEnabledMenu
: uDisabledMenu
, SCI_REDO
, sMenuItemText
);
1187 popup
.AppendMenu(MF_SEPARATOR
);
1189 sMenuItemText
.LoadString(IDS_SCIEDIT_CUT
);
1190 popup
.AppendMenu(!bIsReadOnly
&& bHasSelection
? uEnabledMenu
: uDisabledMenu
, SCI_CUT
, sMenuItemText
);
1191 sMenuItemText
.LoadString(IDS_SCIEDIT_COPY
);
1192 popup
.AppendMenu(bHasSelection
? uEnabledMenu
: uDisabledMenu
, SCI_COPY
, sMenuItemText
);
1193 sMenuItemText
.LoadString(IDS_SCIEDIT_PASTE
);
1194 popup
.AppendMenu(bCanPaste
? uEnabledMenu
: uDisabledMenu
, SCI_PASTE
, sMenuItemText
);
1196 popup
.AppendMenu(MF_SEPARATOR
);
1198 sMenuItemText
.LoadString(IDS_SCIEDIT_SELECTALL
);
1199 popup
.AppendMenu(uEnabledMenu
, SCI_SELECTALL
, sMenuItemText
);
1201 if (!bIsReadOnly
&& Call(SCI_GETEDGECOLUMN
))
1203 popup
.AppendMenu(MF_SEPARATOR
);
1205 sMenuItemText
.LoadString(IDS_SCIEDIT_SPLITLINES
);
1206 popup
.AppendMenu(bHasSelection
? uEnabledMenu
: uDisabledMenu
, SCI_LINESSPLIT
, sMenuItemText
);
1209 if (m_arContextHandlers
.GetCount() > 0)
1210 popup
.AppendMenu(MF_SEPARATOR
);
1212 int nCustoms
= nCorrections
;
1213 // now add any custom context menus
1214 for (INT_PTR handlerindex
= 0; handlerindex
< m_arContextHandlers
.GetCount(); ++handlerindex
)
1216 CSciEditContextMenuInterface
* pHandler
= m_arContextHandlers
.GetAt(handlerindex
);
1217 pHandler
->InsertMenuItems(popup
, nCustoms
);
1220 int cmd
= popup
.TrackPopupMenu(TPM_RETURNCMD
| TPM_LEFTALIGN
| TPM_NONOTIFY
, point
.x
, point
.y
, this);
1224 break; // no command selected
1226 bRestoreCursor
= false;
1236 m_personalDict
.AddWord(sWord
);
1237 CheckSpelling(static_cast<Sci_Position
>(Call(SCI_POSITIONFROMLINE
, static_cast<int>(Call(SCI_GETFIRSTVISIBLELINE
)))), static_cast<Sci_Position
>(Call(SCI_POSITIONFROMLINE
, static_cast<int>(Call(SCI_GETFIRSTVISIBLELINE
)) + static_cast<int>(Call(SCI_LINESONSCREEN
)))));
1239 case SCI_LINESSPLIT
:
1241 auto marker
= static_cast<int>(Call(SCI_GETEDGECOLUMN
) * static_cast<int>(Call(SCI_TEXTWIDTH
, 0, reinterpret_cast<LPARAM
>(" "))));
1244 m_blockModifiedHandler
= true;
1245 SCOPE_EXIT
{ m_blockModifiedHandler
= false; };
1246 Call(SCI_TARGETFROMSELECTION
);
1247 Call(SCI_LINESJOIN
);
1248 Call(SCI_LINESSPLIT
, marker
);
1253 if (cmd
< nCorrections
)
1255 Call(SCI_SETANCHOR
, pointpos
);
1256 Call(SCI_SETCURRENTPOS
, pointpos
);
1257 GetWordUnderCursor(true);
1259 popup
.GetMenuString(cmd
, temp
, 0);
1260 // setting the cursor clears the selection
1261 Call(SCI_REPLACESEL
, 0, reinterpret_cast<LPARAM
>(static_cast<LPCSTR
>(StringForControl(temp
))));
1263 else if (cmd
< (nCorrections
+nCustoms
))
1265 for (INT_PTR handlerindex
= 0; handlerindex
< m_arContextHandlers
.GetCount(); ++handlerindex
)
1267 CSciEditContextMenuInterface
* pHandler
= m_arContextHandlers
.GetAt(handlerindex
);
1268 if (pHandler
->HandleMenuItemClick(cmd
, this))
1276 // restore the anchor and cursor position
1277 Call(SCI_SETCURRENTPOS
, currentpos
);
1278 Call(SCI_SETANCHOR
, anchor
);
1282 bool CSciEdit::StyleEnteredText(Sci_Position startstylepos
, Sci_Position endstylepos
)
1284 bool bStyled
= false;
1285 const auto line
= static_cast<int>(Call(SCI_LINEFROMPOSITION
, startstylepos
));
1286 const auto line_number_end
= static_cast<int>(Call(SCI_LINEFROMPOSITION
, endstylepos
));
1287 for (auto line_number
= line
; line_number
<= line_number_end
; ++line_number
)
1289 auto offset
= static_cast<Sci_Position
>(Call(SCI_POSITIONFROMLINE
, line_number
));
1290 auto line_len
= static_cast<int>(Call(SCI_LINELENGTH
, line_number
));
1291 auto linebuffer
= std::make_unique
<char[]>(line_len
+ 1);
1292 Call(SCI_GETLINE
, line_number
, reinterpret_cast<LPARAM
>(linebuffer
.get()));
1293 linebuffer
[line_len
] = '\0';
1294 Sci_Position start
= 0;
1295 Sci_Position end
= 0;
1296 while (FindStyleChars(linebuffer
.get(), '*', start
, end
))
1298 Call(SCI_STARTSTYLING
, start
+ offset
, STYLE_BOLD
);
1299 Call(SCI_SETSTYLING
, end
-start
, STYLE_BOLD
);
1305 while (FindStyleChars(linebuffer
.get(), '^', start
, end
))
1307 Call(SCI_STARTSTYLING
, start
+ offset
, STYLE_ITALIC
);
1308 Call(SCI_SETSTYLING
, end
-start
, STYLE_ITALIC
);
1314 while (FindStyleChars(linebuffer
.get(), '_', start
, end
))
1316 Call(SCI_STARTSTYLING
, start
+ offset
, STYLE_UNDERLINED
);
1317 Call(SCI_SETSTYLING
, end
-start
, STYLE_UNDERLINED
);
1325 bool CSciEdit::WrapLines(Sci_Position startpos
, Sci_Position endpos
)
1327 auto markerX
= static_cast<Sci_Position
>(Call(SCI_GETEDGECOLUMN
) * static_cast<int>(Call(SCI_TEXTWIDTH
, 0, reinterpret_cast<LPARAM
>(" "))));
1330 Call(SCI_SETTARGETSTART
, startpos
);
1331 Call(SCI_SETTARGETEND
, endpos
);
1332 Call(SCI_LINESSPLIT
, markerX
);
1338 void CSciEdit::AdvanceUTF8(const char * str
, int& pos
)
1340 if ((str
[pos
] & 0xE0)==0xC0)
1342 // utf8 2-byte sequence
1345 else if ((str
[pos
] & 0xF0)==0xE0)
1347 // utf8 3-byte sequence
1350 else if ((str
[pos
] & 0xF8)==0xF0)
1352 // utf8 4-byte sequence
1359 bool CSciEdit::FindStyleChars(const char* line
, char styler
, Sci_Position
& start
, Sci_Position
& end
)
1365 AdvanceUTF8(line
, i
);
1369 bool bFoundMarker
= false;
1370 CString sULine
= CUnicodeUtils::GetUnicode(line
);
1371 // find a starting marker
1372 while (line
[i
] != 0)
1374 if (line
[i
] == styler
)
1376 if ((line
[i
+1]!=0)&&(IsCharAlphaNumeric(sULine
[u
+1]))&&
1377 (((u
>0)&&(!IsCharAlphaNumeric(sULine
[u
-1]))) || (u
==0)))
1380 AdvanceUTF8(line
, i
);
1382 bFoundMarker
= true;
1386 AdvanceUTF8(line
, i
);
1391 // find ending marker
1392 bFoundMarker
= false;
1395 if (line
[i
] == styler
)
1397 if ((IsCharAlphaNumeric(sULine
[u
-1]))&&
1398 ((((u
+1)<sULine
.GetLength())&&(!IsCharAlphaNumeric(sULine
[u
+1]))) || ((u
+1) == sULine
.GetLength()))
1403 bFoundMarker
= true;
1407 AdvanceUTF8(line
, i
);
1410 return bFoundMarker
;
1413 BOOL
CSciEdit::MarkEnteredBugID(Sci_Position startstylepos
, Sci_Position endstylepos
)
1415 if (m_sCommand
.IsEmpty())
1417 // get the text between the start and end position we have to style
1418 const auto line_number
= static_cast<int>(Call(SCI_LINEFROMPOSITION
, startstylepos
));
1419 auto start_pos
= static_cast<Sci_Position
>(Call(SCI_POSITIONFROMLINE
, line_number
));
1420 auto end_pos
= endstylepos
;
1422 if (start_pos
== end_pos
)
1424 if (start_pos
> end_pos
)
1426 auto switchtemp
= start_pos
;
1427 start_pos
= end_pos
;
1428 end_pos
= switchtemp
;
1432 const auto len
= SafeSizeToInt(end_pos
- start_pos
);
1433 Sci_TextRange textrange
;
1434 textrange
.lpstrText
= msg
.GetBuffer(len
);
1435 textrange
.chrg
.cpMin
= static_cast<Sci_PositionCR
>(start_pos
);
1436 textrange
.chrg
.cpMax
= static_cast<Sci_PositionCR
>(end_pos
);
1437 Call(SCI_GETTEXTRANGE
, 0, reinterpret_cast<LPARAM
>(&textrange
));
1438 msg
.ReleaseBufferSetLength(len
);
1440 Call(SCI_STARTSTYLING
, start_pos
, STYLE_MASK
);
1444 if (!m_sBugID
.IsEmpty())
1446 // match with two regex strings (without grouping!)
1447 const std::regex
regCheck(m_sCommand
);
1448 const std::regex
regBugID(m_sBugID
);
1449 const std::sregex_iterator end
;
1450 std::string s
{ static_cast<LPCSTR
>(msg
) };
1453 // if start_pos is 0, we're styling from the beginning and let the ^ char match the beginning of the line
1454 // that way, the ^ matches the very beginning of the log message and not the beginning of further lines.
1455 // problem is: this only works *while* entering log messages. If a log message is pasted in whole or
1456 // multiple lines are pasted, start_pos can be 0 and styling goes over multiple lines. In that case, those
1457 // additional line starts also match ^
1458 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
)
1460 // clear the styles up to the match position
1461 Call(SCI_SETSTYLING
, it
->position(0)-pos
, STYLE_DEFAULT
);
1463 // (*it)[0] is the matched string
1464 std::string matchedString
= (*it
)[0];
1465 LONG matchedpos
= 0;
1466 for (std::sregex_iterator
it2(matchedString
.cbegin(), matchedString
.cend(), regBugID
); it2
!= end
; ++it2
)
1468 ATLTRACE("matched id : %s\n", std::string((*it2
)[0]).c_str());
1470 // bold style up to the id match
1471 ATLTRACE("position = %ld\n", it2
->position(0));
1472 if (it2
->position(0))
1473 Call(SCI_SETSTYLING
, it2
->position(0) - matchedpos
, STYLE_ISSUEBOLD
);
1474 // bold and recursive style for the bug ID itself
1475 if ((*it2
)[0].str().size())
1476 Call(SCI_SETSTYLING
, (*it2
)[0].str().size(), STYLE_ISSUEBOLDITALIC
);
1477 matchedpos
= static_cast<LONG
>(it2
->position(0) + (*it2
)[0].str().size());
1479 if (matchedpos
&& matchedpos
< static_cast<LONG
>(matchedString
.size()))
1481 Call(SCI_SETSTYLING
, matchedString
.size() - matchedpos
, STYLE_ISSUEBOLD
);
1483 pos
= static_cast<LONG
>(it
->position(0) + matchedString
.size());
1485 // bold style for the rest of the string which isn't matched
1487 Call(SCI_SETSTYLING
, s
.size()-pos
, STYLE_DEFAULT
);
1491 const std::regex
regCheck(m_sCommand
);
1492 const std::sregex_iterator end
;
1493 std::string s
{ static_cast<LPCSTR
>(msg
) };
1495 for (std::sregex_iterator
it(s
.cbegin(), s
.cend(), regCheck
); it
!= end
; ++it
)
1497 // clear the styles up to the match position
1498 if (it
->position(0) - pos
>= 0)
1499 Call(SCI_SETSTYLING
, it
->position(0) - pos
, STYLE_DEFAULT
);
1500 pos
= static_cast<LONG
>(it
->position(0));
1502 const std::smatch match
= *it
;
1503 // we define group 1 as the whole issue text and
1504 // group 2 as the bug ID
1505 if (match
.size() >= 2)
1507 ATLTRACE("matched id : %s\n", std::string(match
[1]).c_str());
1508 if (match
[1].first
- s
.cbegin() - pos
>= 0)
1509 Call(SCI_SETSTYLING
, match
[1].first
- s
.cbegin() - pos
, STYLE_ISSUEBOLD
);
1510 Call(SCI_SETSTYLING
, std::string(match
[1]).size(), STYLE_ISSUEBOLDITALIC
);
1511 pos
= static_cast<LONG
>(match
[1].second
- s
.cbegin());
1516 catch (std::exception
&) {}
1521 void CSciEdit::StyleURLs(Sci_Position startstylepos
, Sci_Position endstylepos
)
1523 const auto line_number
= static_cast<int>(Call(SCI_LINEFROMPOSITION
, startstylepos
));
1524 startstylepos
= static_cast<Sci_Position
>(Call(SCI_POSITIONFROMLINE
, line_number
));
1526 const auto len
= SafeSizeToInt(endstylepos
- startstylepos
);
1528 Sci_TextRange textrange
;
1529 textrange
.lpstrText
= msg
.GetBuffer(len
);
1530 textrange
.chrg
.cpMin
= static_cast<Sci_PositionCR
>(startstylepos
);
1531 textrange
.chrg
.cpMax
= static_cast<Sci_PositionCR
>(endstylepos
);
1532 Call(SCI_GETTEXTRANGE
, 0, reinterpret_cast<LPARAM
>(&textrange
));
1533 // we're dealing with utf8 encoded text here, which means one glyph is
1534 // not necessarily one byte/wchar_t
1535 // that's why we use CStringA to still get a correct char index
1536 msg
.ReleaseBufferSetLength(len
);
1538 ::FindURLMatches(msg
, AdvanceUTF8
, [&](int start
, int end
) {
1539 ASSERT(startstylepos
+ end
<= endstylepos
);
1540 Call(SCI_STARTSTYLING
, startstylepos
+ start
, STYLE_URL
);
1541 Call(SCI_SETSTYLING
, end
- start
, STYLE_URL
); });
1544 std::string
CSciEdit::GetWordForSpellChecker(const CString
& sWord
)
1546 // convert the string from the control to the encoding of the spell checker module.
1548 if (m_spellcodepage
)
1550 int lengthIncTerminator
= WideCharToMultiByte(m_spellcodepage
, 0, sWord
, -1, nullptr, 0, nullptr, nullptr);
1551 if (lengthIncTerminator
<= 1)
1552 return ""; // converting to the codepage failed
1553 sWordA
.resize(lengthIncTerminator
- 1);
1554 WideCharToMultiByte(m_spellcodepage
, 0, sWord
, -1, sWordA
.data(), lengthIncTerminator
- 1, nullptr, nullptr);
1557 sWordA
= std::string(reinterpret_cast<LPCSTR
>(static_cast<LPCWSTR
>(sWord
)));
1559 sWordA
.erase(sWordA
.find_last_not_of("\'\".,") + 1);
1560 sWordA
.erase(0, sWordA
.find_first_not_of("\'\".,"));
1564 for (const auto styleindicator
: { '*', '_', '^' })
1568 if (sWordA
[sWordA
.size() - 1] == styleindicator
)
1569 sWordA
.resize(sWordA
.size() - 1);
1572 if (sWordA
[0] == styleindicator
)
1573 sWordA
= sWordA
.substr(sWordA
.size() - 1);
1580 CString
CSciEdit::GetWordFromSpellChecker(const std::string
& sWordA
)
1583 if (m_spellcodepage
)
1585 wchar_t* buf
= sWord
.GetBuffer(static_cast<int>(sWordA
.size()) * 2);
1586 int lengthIncTerminator
= MultiByteToWideChar(m_spellcodepage
, 0, sWordA
.c_str(), -1, buf
, static_cast<int>(sWordA
.size()) * 2);
1587 if (lengthIncTerminator
== 0)
1589 sWord
.ReleaseBuffer(lengthIncTerminator
- 1);
1592 sWord
= CString(sWordA
.c_str());
1594 sWord
.Trim(L
"\'\".,");
1599 bool CSciEdit::IsUTF8(LPVOID pBuffer
, size_t cb
)
1603 auto pVal
= static_cast<UINT16
*>(pBuffer
);
1604 auto pVal2
= reinterpret_cast<UINT8
*>(pVal
+ 1);
1605 // scan the whole buffer for a 0x0000 sequence
1606 // if found, we assume a binary file
1607 for (size_t i
=0; i
<(cb
-2); i
=i
+2)
1609 if (0x0000 == *pVal
++)
1612 pVal
= static_cast<UINT16
*>(pBuffer
);
1613 if (*pVal
== 0xFEFF)
1617 if (*pVal
== 0xBBEF)
1622 // check for illegal UTF8 chars
1623 pVal2
= static_cast<UINT8
*>(pBuffer
);
1624 for (size_t i
=0; i
<cb
; ++i
)
1626 if ((*pVal2
== 0xC0)||(*pVal2
== 0xC1)||(*pVal2
>= 0xF5))
1630 pVal2
= static_cast<UINT8
*>(pBuffer
);
1632 for (size_t i
=0; i
<(cb
-3); ++i
)
1634 if ((*pVal2
& 0xE0)==0xC0)
1637 if ((*pVal2
& 0xC0)!=0x80)
1641 if ((*pVal2
& 0xF0)==0xE0)
1644 if ((*pVal2
& 0xC0)!=0x80)
1647 if ((*pVal2
& 0xC0)!=0x80)
1651 if ((*pVal2
& 0xF8)==0xF0)
1654 if ((*pVal2
& 0xC0)!=0x80)
1657 if ((*pVal2
& 0xC0)!=0x80)
1660 if ((*pVal2
& 0xC0)!=0x80)
1671 void CSciEdit::SetAStyle(int style
, COLORREF fore
, COLORREF back
, int size
, const char *face
)
1673 Call(SCI_STYLESETFORE
, style
, fore
);
1674 Call(SCI_STYLESETBACK
, style
, back
);
1676 Call(SCI_STYLESETSIZE
, style
, size
);
1678 Call(SCI_STYLESETFONT
, style
, reinterpret_cast<LPARAM
>(face
));
1681 void CSciEdit::SetUDiffStyle()
1683 m_bUDiffmode
= true;
1685 Call(SCI_CLEARDOCUMENTSTYLE
);
1686 Call(SCI_SETTABWIDTH
, CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffTabSize", 4));
1690 //Set the default windows colors for edit controls
1693 //SendEditor(SCI_SETREADONLY, FALSE);
1695 Call(SCI_EMPTYUNDOBUFFER
);
1696 Call(SCI_SETSAVEPOINT
);
1698 Call(SCI_SETUNDOCOLLECTION
, 0);
1700 Call(SCI_SETWRAPMODE
,SC_WRAP_NONE
);
1702 Call(SCI_GOTOPOS
, 0);
1704 Call(SCI_SETVIEWWS
, 1);
1705 Call(SCI_SETWHITESPACESIZE
, 2);
1706 Call(SCI_SETWHITESPACEFORE
, true, ::GetSysColor(COLOR_3DSHADOW
));
1707 Call(SCI_STYLESETVISIBLE
, STYLE_CONTROLCHAR
, TRUE
);
1709 Call(SCI_CLEARDOCUMENTSTYLE
, 0, 0);
1711 if (CTheme::Instance().IsDarkTheme())
1713 Call(SCI_STYLESETFORE
, STYLE_DEFAULT
, UDiffTextColorDark
);
1714 Call(SCI_STYLESETBACK
, STYLE_DEFAULT
, UDiffBackColorDark
);
1715 Call(SCI_SETSELFORE
, TRUE
, CTheme::Instance().GetThemeColor(::GetSysColor(COLOR_HIGHLIGHTTEXT
)));
1716 Call(SCI_SETSELBACK
, TRUE
, CTheme::Instance().GetThemeColor(::GetSysColor(COLOR_HIGHLIGHT
)));
1717 Call(SCI_SETCARETFORE
, UDiffTextColorDark
);
1718 Call(SCI_SETWHITESPACEFORE
, true, RGB(180, 180, 180));
1719 SetAStyle(STYLE_DEFAULT
, UDiffTextColorDark
, UDiffBackColorDark
,
1720 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffFontSize", 10),
1721 CUnicodeUtils::StdGetUTF8(CRegStdString(L
"Software\\TortoiseGit\\UDiffFontName", L
"Consolas")).c_str());
1722 SetAStyle(SCE_DIFF_DEFAULT
, UDiffTextColorDark
, UDiffBackColorDark
,
1723 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffFontSize", 10),
1724 CUnicodeUtils::StdGetUTF8(CRegStdString(L
"Software\\TortoiseGit\\UDiffFontName", L
"Consolas")).c_str());
1725 Call(SCI_STYLECLEARALL
);
1726 SetAStyle(SCE_DIFF_COMMAND
,
1727 CRegStdDWORD(L
"Software\\TortoiseGit\\DarkUDiffForeCommandColor", UDIFF_COLORFORECOMMAND_DARK
),
1728 CRegStdDWORD(L
"Software\\TortoiseGit\\DarkUDiffBackCommandColor", UDIFF_COLORBACKCOMMAND_DARK
));
1729 SetAStyle(SCE_DIFF_POSITION
,
1730 CRegStdDWORD(L
"Software\\TortoiseGit\\DarkUDiffForePositionColor", UDIFF_COLORFOREPOSITION_DARK
),
1731 CRegStdDWORD(L
"Software\\TortoiseGit\\DarkUDiffBackPositionColor", UDIFF_COLORBACKPOSITION_DARK
));
1732 SetAStyle(SCE_DIFF_HEADER
,
1733 CRegStdDWORD(L
"Software\\TortoiseGit\\DarkUDiffForeHeaderColor", UDIFF_COLORFOREHEADER_DARK
),
1734 CRegStdDWORD(L
"Software\\TortoiseGit\\DarkUDiffBackHeaderColor", UDIFF_COLORBACKHEADER_DARK
));
1735 SetAStyle(SCE_DIFF_COMMENT
,
1736 CRegStdDWORD(L
"Software\\TortoiseGit\\DarkUDiffForeCommentColor", UDIFF_COLORFORECOMMENT_DARK
),
1737 CRegStdDWORD(L
"Software\\TortoiseGit\\DarkUDiffBackCommentColor", UDIFF_COLORBACKCOMMENT_DARK
));
1738 for (int style
: { SCE_DIFF_ADDED
, SCE_DIFF_PATCH_ADD
, SCE_DIFF_PATCH_DELETE
})
1741 CRegStdDWORD(L
"Software\\TortoiseGit\\DarkUDiffForeAddedColor", UDIFF_COLORFOREADDED_DARK
),
1742 CRegStdDWORD(L
"Software\\TortoiseGit\\DarkUDiffBackAddedColor", UDIFF_COLORBACKADDED_DARK
));
1744 for (int style
: { SCE_DIFF_DELETED
, SCE_DIFF_REMOVED_PATCH_ADD
, SCE_DIFF_REMOVED_PATCH_DELETE
})
1747 CRegStdDWORD(L
"Software\\TortoiseGit\\DarkUDiffForeRemovedColor", UDIFF_COLORFOREREMOVED_DARK
),
1748 CRegStdDWORD(L
"Software\\TortoiseGit\\DarkUDiffBackRemovedColor", UDIFF_COLORBACKREMOVED_DARK
));
1753 Call(SCI_STYLESETFORE
, STYLE_DEFAULT
, ::GetSysColor(COLOR_WINDOWTEXT
));
1754 Call(SCI_STYLESETBACK
, STYLE_DEFAULT
, ::GetSysColor(COLOR_WINDOW
));
1755 Call(SCI_SETSELFORE
, TRUE
, ::GetSysColor(COLOR_HIGHLIGHTTEXT
));
1756 Call(SCI_SETSELBACK
, TRUE
, ::GetSysColor(COLOR_HIGHLIGHT
));
1757 Call(SCI_SETCARETFORE
, ::GetSysColor(COLOR_WINDOWTEXT
));
1758 Call(SCI_SETWHITESPACEFORE
, true, ::GetSysColor(COLOR_3DSHADOW
));
1759 SetAStyle(STYLE_DEFAULT
, ::GetSysColor(COLOR_WINDOWTEXT
), ::GetSysColor(COLOR_WINDOW
),
1760 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffFontSize", 10),
1761 CUnicodeUtils::StdGetUTF8(CRegStdString(L
"Software\\TortoiseGit\\UDiffFontName", L
"Consolas")).c_str());
1762 SetAStyle(SCE_DIFF_DEFAULT
, ::GetSysColor(COLOR_WINDOWTEXT
), ::GetSysColor(COLOR_WINDOW
),
1763 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffFontSize", 10),
1764 CUnicodeUtils::StdGetUTF8(CRegStdString(L
"Software\\TortoiseGit\\UDiffFontName", L
"Consolas")).c_str());
1765 Call(SCI_STYLECLEARALL
);
1766 SetAStyle(SCE_DIFF_COMMAND
,
1767 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffForeCommandColor", UDIFF_COLORFORECOMMAND
),
1768 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffBackCommandColor", UDIFF_COLORBACKCOMMAND
));
1769 SetAStyle(SCE_DIFF_POSITION
,
1770 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffForePositionColor", UDIFF_COLORFOREPOSITION
),
1771 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffBackPositionColor", UDIFF_COLORBACKPOSITION
));
1772 SetAStyle(SCE_DIFF_HEADER
,
1773 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffForeHeaderColor", UDIFF_COLORFOREHEADER
),
1774 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffBackHeaderColor", UDIFF_COLORBACKHEADER
));
1775 SetAStyle(SCE_DIFF_COMMENT
,
1776 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffForeCommentColor", UDIFF_COLORFORECOMMENT
),
1777 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffBackCommentColor", UDIFF_COLORBACKCOMMENT
));
1778 for (int style
: { SCE_DIFF_ADDED
, SCE_DIFF_PATCH_ADD
, SCE_DIFF_PATCH_DELETE
})
1781 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffForeAddedColor", UDIFF_COLORFOREADDED
),
1782 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffBackAddedColor", UDIFF_COLORBACKADDED
));
1784 for (int style
: { SCE_DIFF_DELETED
, SCE_DIFF_REMOVED_PATCH_ADD
, SCE_DIFF_REMOVED_PATCH_DELETE
})
1787 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffForeRemovedColor", UDIFF_COLORFOREREMOVED
),
1788 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffBackRemovedColor", UDIFF_COLORBACKREMOVED
));
1791 Call(SCI_SETFOLDMARGINCOLOUR
, true, RGB(240, 240, 240));
1792 Call(SCI_SETFOLDMARGINHICOLOUR
, true, RGB(255, 255, 255));
1793 Call(SCI_STYLESETFORE
, STYLE_LINENUMBER
, RGB(109, 109, 109));
1794 Call(SCI_STYLESETBACK
, STYLE_LINENUMBER
, RGB(230, 230, 230));
1796 Call(SCI_STYLESETFORE
, STYLE_BRACELIGHT
, RGB(0, 150, 0));
1797 Call(SCI_STYLESETBOLD
, STYLE_BRACELIGHT
, 1);
1798 Call(SCI_STYLESETFORE
, STYLE_BRACEBAD
, RGB(255, 0, 0));
1799 Call(SCI_STYLESETBOLD
, STYLE_BRACEBAD
, 1);
1800 if (CTheme::Instance().IsDarkTheme() || CTheme::Instance().IsHighContrastModeDark())
1802 Call(SCI_SETFOLDMARGINCOLOUR
, true, UDiffTextColorDark
);
1803 Call(SCI_SETFOLDMARGINHICOLOUR
, true, CTheme::darkBkColor
);
1804 Call(SCI_STYLESETFORE
, STYLE_LINENUMBER
, RGB(140, 140, 140));
1805 Call(SCI_STYLESETBACK
, STYLE_LINENUMBER
, UDiffBackColorDark
);
1808 auto curlexer
= Call(SCI_GETLEXER
);
1809 if (CTheme::Instance().IsHighContrastMode() && curlexer
!= SCLEX_NULL
)
1811 Call(SCI_CLEARDOCUMENTSTYLE
, 0, 0);
1812 Call(SCI_SETILEXER
, reinterpret_cast<sptr_t
>(nullptr));
1813 Call(SCI_COLOURISE
, 0, -1);
1815 else if (!CTheme::Instance().IsHighContrastMode() && curlexer
!= SCLEX_DIFF
)
1817 Call(SCI_CLEARDOCUMENTSTYLE
, 0, 0);
1818 Call(SCI_STYLESETBOLD
, SCE_DIFF_COMMENT
, TRUE
);
1819 Call(SCI_SETILEXER
, 0, reinterpret_cast<sptr_t
>(CreateLexer("diff")));
1820 Call(SCI_STYLESETBOLD
, SCE_DIFF_COMMENT
, TRUE
);
1821 Call(SCI_SETKEYWORDS
, 0, reinterpret_cast<LPARAM
>("revision"));
1822 Call(SCI_COLOURISE
, 0, -1);
1826 int CSciEdit::LoadFromFile(const CString
& filename
, UINT errorMsgId
)
1828 CAutoFile hfile
= CreateFile(filename
, GENERIC_READ
, FILE_SHARE_READ
| FILE_SHARE_DELETE
, nullptr, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, nullptr);
1831 MessageBox(CFormatMessageWrapper(), L
"TortoiseGit", MB_ICONEXCLAMATION
);
1835 if (LARGE_INTEGER fileSize
; !::GetFileSizeEx(hfile
, &fileSize
))
1837 MessageBox(static_cast<LPCWSTR
>(CFormatMessageWrapper()), L
"TortoiseGit", MB_ICONEXCLAMATION
);
1840 else if (fileSize
.QuadPart
>= 250 * 1024 * 1024) // styling gets really slow and Scintilla requires special initialization for large files
1843 error
.LoadString(errorMsgId
);
1844 MessageBox(error
, L
"TortoiseGit", MB_ICONEXCLAMATION
);
1850 if (!ReadFile(hfile
, data
, sizeof(data
), &size
, nullptr))
1852 MessageBox(CFormatMessageWrapper(), L
"TortoiseGit", MB_ICONEXCLAMATION
);
1856 auto readonly
= m_bReadOnly
;
1857 SCOPE_EXIT
{ SetReadOnly(readonly
); };
1860 int wasUndoCollection
= static_cast<int>(Call(SCI_GETUNDOCOLLECTION
));
1861 SCOPE_EXIT
{ Call(SCI_SETUNDOCOLLECTION
, wasUndoCollection
); };
1862 Call(SCI_SETUNDOCOLLECTION
, 0);
1867 bool bUTF8
= IsUTF8(data
, size
);
1870 Call(SCI_ADDTEXT
, size
, reinterpret_cast<LPARAM
>(data
));
1871 if (auto sciStatus
= static_cast<int>(Call(SCI_GETSTATUS
)); sciStatus
> SC_STATUS_OK
&& sciStatus
< SC_STATUS_WARN_START
)
1873 if (sciStatus
== SC_STATUS_BADALLOC
)
1874 MessageBox(static_cast<LPCWSTR
>(CFormatMessageWrapper(static_cast<DWORD
>(E_OUTOFMEMORY
))), L
"TortoiseGit", MB_ICONEXCLAMATION
);
1876 MessageBox(static_cast<LPCWSTR
>(CFormatMessageWrapper(static_cast<DWORD
>(E_FAIL
))), L
"TortoiseGit", MB_ICONEXCLAMATION
);
1882 if (!ReadFile(hfile
, data
, sizeof(data
), &size
, nullptr))
1886 MessageBox(CFormatMessageWrapper(), L
"TortoiseGit", MB_ICONEXCLAMATION
);
1890 Call(SCI_SETCODEPAGE
, bUTF8
? SC_CP_UTF8
: GetACP());
1894 void CSciEdit::RestyleBugIDs()
1896 auto endstylepos
= static_cast<int>(Call(SCI_GETLENGTH
));
1898 Call(SCI_STARTSTYLING
, 0, STYLE_MASK
);
1899 Call(SCI_SETSTYLING
, endstylepos
, STYLE_DEFAULT
);
1900 // style the bug IDs
1901 MarkEnteredBugID(0, endstylepos
);
1904 ULONG
CSciEdit::GetGestureStatus(CPoint
/*ptTouch*/)
1909 BOOL
CSciEdit::EnableWindow(BOOL bEnable
/*= TRUE*/)
1911 auto ret
= __super::EnableWindow(bEnable
);
1916 void CSciEdit::SetReadOnly(bool bReadOnly
)
1918 m_bReadOnly
= bReadOnly
;
1919 Call(SCI_SETREADONLY
, m_bReadOnly
);
1922 void CSciEdit::ClearUndoBuffer()
1924 Call(SCI_EMPTYUNDOBUFFER
);
1925 Call(SCI_SETSAVEPOINT
);
1928 void CSciEdit::SetWindowStylesForAutocompletionPopup()
1930 if (CTheme::Instance().IsDarkTheme())
1932 EnumThreadWindows(GetCurrentThreadId(), AdjustThemeProc
, 0);
1936 BOOL
CSciEdit::AdjustThemeProc(HWND hwnd
, LPARAM
/*lParam*/)
1938 wchar_t szWndClassName
[MAX_PATH
] = { 0 };
1939 GetClassName(hwnd
, szWndClassName
, _countof(szWndClassName
));
1940 if ((wcscmp(szWndClassName
, L
"ListBoxX") == 0) ||
1941 (wcscmp(szWndClassName
, WC_LISTBOX
) == 0))
1943 // in dark mode, the resizing border is visible at the top
1944 // of the popup, and it's white and ugly.
1945 // this removes the border, but that also means that the
1946 // popup is not resizable anymore - which I think is not
1947 // really necessary anyway.
1948 auto dwCurStyle
= static_cast<DWORD
>(GetWindowLongPtr(hwnd
, GWL_STYLE
));
1949 dwCurStyle
&= ~WS_THICKFRAME
;
1950 dwCurStyle
|= WS_BORDER
;
1951 SetWindowLongPtr(hwnd
, GWL_STYLE
, dwCurStyle
);
1953 DarkModeHelper::Instance().AllowDarkModeForWindow(hwnd
, TRUE
);
1954 SetWindowTheme(hwnd
, L
"Explorer", nullptr);
1955 EnumChildWindows(hwnd
, AdjustThemeProc
, 0);