1
// TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2009-2022 - 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"
34 void CSciEditContextMenuInterface::InsertMenuItems(CMenu
&, int&) {return;}
35 bool CSciEditContextMenuInterface::HandleMenuItemClick(int, CSciEdit
*) {return false;}
36 void CSciEditContextMenuInterface::HandleSnippet(int, const CString
&, CSciEdit
*) { return; }
39 #define STYLE_ISSUEBOLD 11
40 #define STYLE_ISSUEBOLDITALIC 12
42 #define STYLE_ITALIC 15
43 #define STYLE_UNDERLINED 16
45 #define INDIC_MISSPELLED 18
47 #define STYLE_MASK 0x1f
49 #define SCI_ADDWORD 2000
56 struct loc_map enc2locale
[] = {
57 {"28591","ISO8859-1"},
58 {"28592","ISO8859-2"},
59 {"28593","ISO8859-3"},
60 {"28594","ISO8859-4"},
61 {"28595","ISO8859-5"},
62 {"28596","ISO8859-6"},
63 {"28597","ISO8859-7"},
64 {"28598","ISO8859-8"},
65 {"28599","ISO8859-9"},
66 {"28605","ISO8859-15"},
69 {"1251","microsoft-cp1251"},
74 IMPLEMENT_DYNAMIC(CSciEdit
, CWnd
)
76 CSciEdit::CSciEdit() : m_DirectFunction(NULL
)
77 , m_DirectPointer(NULL
)
79 , m_spellCheckerFactory(nullptr)
80 , m_SpellChecker(nullptr)
84 , m_nAutoCompleteMinChars(3)
85 , m_SpellingCache(2000)
86 , m_blockModifiedHandler(false)
88 , m_themeCallbackId(0)
90 m_hModule
= ::LoadLibrary(L
"SciLexer_tgit.dll");
95 CTheme::Instance().RemoveRegisteredCallback(m_themeCallbackId
);
96 m_personalDict
.Save();
99 static std::unique_ptr
<UINT
[]> Icon2Image(HICON hIcon
)
101 if (hIcon
== nullptr)
105 if (!GetIconInfo(hIcon
, &iconInfo
))
109 if (!GetObject(iconInfo
.hbmColor
, sizeof(BITMAP
), &bm
))
112 int width
= bm
.bmWidth
;
113 int height
= bm
.bmHeight
;
114 int bytesPerScanLine
= (width
* 3 + 3) & 0xFFFFFFFC;
115 int size
= bytesPerScanLine
* height
;
116 BITMAPINFO infoheader
;
117 infoheader
.bmiHeader
.biSize
= sizeof(BITMAPINFOHEADER
);
118 infoheader
.bmiHeader
.biWidth
= width
;
119 infoheader
.bmiHeader
.biHeight
= height
;
120 infoheader
.bmiHeader
.biPlanes
= 1;
121 infoheader
.bmiHeader
.biBitCount
= 24;
122 infoheader
.bmiHeader
.biCompression
= BI_RGB
;
123 infoheader
.bmiHeader
.biSizeImage
= size
;
125 auto ptrb
= std::make_unique
<BYTE
[]>(size
* 2 + height
* width
* 4);
126 LPBYTE pixelsIconRGB
= ptrb
.get();
127 LPBYTE alphaPixels
= pixelsIconRGB
+ size
;
128 HDC hDC
= CreateCompatibleDC(nullptr);
129 SCOPE_EXIT
{ DeleteDC(hDC
); };
130 HBITMAP hBmpOld
= static_cast<HBITMAP
>(SelectObject(hDC
, iconInfo
.hbmColor
));
131 if (!GetDIBits(hDC
, iconInfo
.hbmColor
, 0, height
, static_cast<LPVOID
>(pixelsIconRGB
), &infoheader
, DIB_RGB_COLORS
))
134 SelectObject(hDC
, hBmpOld
);
135 if (!GetDIBits(hDC
, iconInfo
.hbmMask
, 0, height
, static_cast<LPVOID
>(alphaPixels
), &infoheader
, DIB_RGB_COLORS
))
138 auto imagePixels
= std::make_unique
<UINT
[]>(height
* width
);
139 int lsSrc
= width
* 3;
140 int vsDest
= height
- 1;
141 for (int y
= 0; y
< height
; y
++)
143 int linePosSrc
= (vsDest
- y
) * lsSrc
;
144 int linePosDest
= y
* width
;
145 for (int x
= 0; x
< width
; x
++)
147 int currentDestPos
= linePosDest
+ x
;
148 int currentSrcPos
= linePosSrc
+ x
* 3;
149 imagePixels
[currentDestPos
] = ((static_cast<UINT
>(
151 ((pixelsIconRGB
[currentSrcPos
+ 2] /*Red*/)
152 | (pixelsIconRGB
[currentSrcPos
+ 1] << 8 /*Green*/))
153 | pixelsIconRGB
[currentSrcPos
] << 16 /*Blue*/
155 | ((alphaPixels
[currentSrcPos
] ? 0 : 0xff) << 24))) & 0xffffffff);
161 void CSciEdit::SetColors(bool recolorize
)
163 if (CTheme::Instance().IsDarkTheme())
165 SetClassLongPtr(GetSafeHwnd(), GCLP_HBRBACKGROUND
, reinterpret_cast<LONG_PTR
>(GetStockObject(BLACK_BRUSH
)));
166 Call(SCI_STYLESETFORE
, STYLE_DEFAULT
, !IsWindowEnabled() ? ::CTheme::darkDisabledTextColor
: CTheme::darkTextColor
);
167 Call(SCI_STYLESETBACK
, STYLE_DEFAULT
, !IsWindowEnabled() ? ::GetSysColor(COLOR_BTNFACE
) : CTheme::darkBkColor
);
168 Call(SCI_SETCARETFORE
, CTheme::darkTextColor
);
170 Call(SCI_SETELEMENTCOLOUR
, SC_ELEMENT_LIST
, RGB(187, 187, 187));
171 Call(SCI_SETELEMENTCOLOUR
, SC_ELEMENT_LIST_BACK
, RGB(15, 15, 15));
172 Call(SCI_SETELEMENTCOLOUR
, SC_ELEMENT_LIST_SELECTED
, RGB(187, 187, 187));
173 Call(SCI_SETELEMENTCOLOUR
, SC_ELEMENT_LIST_SELECTED_BACK
, RGB(80, 80, 80));
177 SetClassLongPtr(GetSafeHwnd(), GCLP_HBRBACKGROUND
, reinterpret_cast<LONG_PTR
>(GetSysColorBrush(COLOR_3DFACE
)));
178 Call(SCI_STYLESETFORE
, STYLE_DEFAULT
, ::GetSysColor(!IsWindowEnabled() ? COLOR_GRAYTEXT
: COLOR_WINDOWTEXT
));
179 Call(SCI_STYLESETBACK
, STYLE_DEFAULT
, ::GetSysColor(!IsWindowEnabled() ? COLOR_BTNFACE
: COLOR_WINDOW
));
180 Call(SCI_SETCARETFORE
, ::GetSysColor(COLOR_WINDOWTEXT
));
182 Call(SCI_RESETELEMENTCOLOUR
, SC_ELEMENT_LIST
);
183 Call(SCI_RESETELEMENTCOLOUR
, SC_ELEMENT_LIST_BACK
);
184 Call(SCI_RESETELEMENTCOLOUR
, SC_ELEMENT_LIST_SELECTED
);
185 Call(SCI_RESETELEMENTCOLOUR
, SC_ELEMENT_LIST_SELECTED_BACK
);
187 Call(SCI_SETSELFORE
, TRUE
, CTheme::Instance().GetThemeColor(::GetSysColor(COLOR_HIGHLIGHTTEXT
)));
188 Call(SCI_SETSELBACK
, TRUE
, CTheme::Instance().GetThemeColor(::GetSysColor(COLOR_HIGHLIGHT
)));
190 if (!m_bNoAutomaticStyling
)
192 Call(SCI_STYLECLEARALL
);
194 LPARAM color
= ::GetSysColor(COLOR_HOTLIGHT
);
195 // set the styles for the bug ID strings
196 Call(SCI_STYLESETBOLD
, STYLE_ISSUEBOLD
, TRUE
);
197 Call(SCI_STYLESETFORE
, STYLE_ISSUEBOLD
, color
);
198 Call(SCI_STYLESETBOLD
, STYLE_ISSUEBOLDITALIC
, TRUE
);
199 Call(SCI_STYLESETITALIC
, STYLE_ISSUEBOLDITALIC
, TRUE
);
200 Call(SCI_STYLESETFORE
, STYLE_ISSUEBOLDITALIC
, color
);
201 Call(SCI_STYLESETHOTSPOT
, STYLE_ISSUEBOLDITALIC
, TRUE
);
203 // set the formatted text styles
204 Call(SCI_STYLESETBOLD
, STYLE_BOLD
, TRUE
);
205 Call(SCI_STYLESETITALIC
, STYLE_ITALIC
, TRUE
);
206 Call(SCI_STYLESETUNDERLINE
, STYLE_UNDERLINED
, TRUE
);
208 // set the style for URLs
209 Call(SCI_STYLESETFORE
, STYLE_URL
, color
);
210 Call(SCI_STYLESETHOTSPOT
, STYLE_URL
, TRUE
);
212 Call(SCI_SETHOTSPOTACTIVEUNDERLINE
, TRUE
);
216 Call(SCI_COLOURISE
, 0, -1);
219 void CSciEdit::Init(LONG lLanguage
)
221 //Setup the direct access data
222 m_DirectFunction
= SendMessage(SCI_GETDIRECTFUNCTION
, 0, 0);
223 m_DirectPointer
= SendMessage(SCI_GETDIRECTPOINTER
, 0, 0);
224 Call(SCI_SETMARGINWIDTHN
, 1, 0);
225 Call(SCI_SETUSETABS
, 0); //pressing TAB inserts spaces
226 Call(SCI_SETWRAPVISUALFLAGS
, SC_WRAPVISUALFLAG_END
);
227 Call(SCI_AUTOCSETIGNORECASE
, 1);
228 Call(SCI_SETILEXER
, 0, reinterpret_cast<sptr_t
>(nullptr));
229 Call(SCI_SETCODEPAGE
, SC_CP_UTF8
);
230 Call(SCI_AUTOCSETFILLUPS
, 0, reinterpret_cast<LPARAM
>("\t(["));
231 Call(SCI_AUTOCSETMAXWIDTH
, 0);
232 //Set the default windows colors for edit controls
234 Call(SCI_SETMODEVENTMASK
, SC_MOD_INSERTTEXT
| SC_MOD_DELETETEXT
| SC_PERFORMED_UNDO
| SC_PERFORMED_REDO
);
235 Call(SCI_INDICSETSTYLE
, INDIC_MISSPELLED
, INDIC_SQUIGGLE
);
236 Call(SCI_INDICSETFORE
, INDIC_MISSPELLED
, RGB(255,0,0));
238 CStringA sWhiteSpace
;
239 for (int i
=0; i
<255; ++i
)
241 if (i
== '\r' || i
== '\n')
243 else if (i
< 0x20 || i
== ' ')
244 sWhiteSpace
+= static_cast<char>(i
);
245 else if (isalnum(i
) || i
== '\'' || i
== '_' || i
== '-')
246 sWordChars
+= static_cast<char>(i
);
248 Call(SCI_SETWORDCHARS
, 0, reinterpret_cast<LPARAM
>(static_cast<LPCSTR
>(sWordChars
)));
249 Call(SCI_SETWHITESPACECHARS
, 0, reinterpret_cast<LPARAM
>(static_cast<LPCSTR
>(sWhiteSpace
)));
250 m_bDoStyle
= static_cast<DWORD
>(CRegStdDWORD(L
"Software\\TortoiseGit\\StyleCommitMessages", TRUE
)) == TRUE
;
251 m_nAutoCompleteMinChars
= static_cast<int>(CRegStdDWORD(L
"Software\\TortoiseGit\\AutoCompleteMinChars", 3));
252 // look for dictionary files and use them if found
253 if (lLanguage
>= 0 && static_cast<DWORD
>(CRegStdDWORD(L
"Software\\TortoiseGit\\Spellchecker", TRUE
)) == TRUE
)
255 long langId
= GetUserDefaultLCID();
256 long origLangId
= langId
;
259 // if a specific language is requested, then use that
261 origLangId
= lLanguage
;
264 // first try the Win8 spell checker
265 BOOL supported
= FALSE
;
266 HRESULT hr
= CoCreateInstance(__uuidof(SpellCheckerFactory
), nullptr, CLSCTX_INPROC_SERVER
, IID_PPV_ARGS(&m_spellCheckerFactory
));
267 bool bFallbackUsed
= false;
268 if (SUCCEEDED(hr
) && static_cast<DWORD
>(CRegDWORD(L
"Software\\TortoiseGit\\Win8SpellChecker", FALSE
)) == TRUE
)
270 wchar_t localename
[LOCALE_NAME_MAX_LENGTH
] = { 0 };
273 LCIDToLocaleName(langId
, localename
, _countof(localename
), 0);
275 hr
= m_spellCheckerFactory
->IsSupported(localename
, &supported
);
278 hr
= m_spellCheckerFactory
->CreateSpellChecker(localename
, &m_SpellChecker
);
281 m_personalDict
.Init(langId
);
285 DWORD lid
= SUBLANGID(langId
);
288 langId
= MAKELANGID(PRIMARYLANGID(langId
), lid
);
289 else if (langId
== 1033)
294 bFallbackUsed
= true;
296 } while (langId
&& (!supported
|| FAILED(hr
)));
298 if (FAILED(hr
) || !supported
|| bFallbackUsed
)
302 if ((lLanguage
== 0) || (lLanguage
&& !LoadDictionaries(lLanguage
)))
306 LoadDictionaries(langId
);
307 DWORD lid
= SUBLANGID(langId
);
310 langId
= MAKELANGID(PRIMARYLANGID(langId
), lid
);
311 else if (langId
== 1033)
315 if (bFallbackUsed
&& supported
)
320 } while (langId
&& !pChecker
);
322 if (bFallbackUsed
&& pChecker
)
323 m_SpellChecker
= nullptr;
327 Call(SCI_SETEDGEMODE
, EDGE_NONE
);
328 Call(SCI_SETWRAPMODE
, SC_WRAP_WORD
);
329 Call(SCI_ASSIGNCMDKEY
, SCK_END
, SCI_LINEENDWRAP
);
330 Call(SCI_ASSIGNCMDKEY
, SCK_END
+ (SCMOD_SHIFT
<< 16), SCI_LINEENDWRAPEXTEND
);
331 Call(SCI_ASSIGNCMDKEY
, SCK_HOME
, SCI_HOMEWRAP
);
332 Call(SCI_ASSIGNCMDKEY
, SCK_HOME
+ (SCMOD_SHIFT
<< 16), SCI_HOMEWRAPEXTEND
);
333 if (CRegStdDWORD(L
"Software\\TortoiseGit\\ScintillaDirect2D", FALSE
) != FALSE
)
335 // set font quality for the popup window, since that window does not use D2D
336 Call(SCI_SETFONTQUALITY
, SC_EFF_QUALITY_LCD_OPTIMIZED
);
338 Call(SCI_SETTECHNOLOGY
, SC_TECHNOLOGY_DIRECTWRITERETAIN
);
339 Call(SCI_SETBUFFEREDDRAW
, 0);
341 m_themeCallbackId
= CTheme::Instance().RegisterThemeChangeCallback([this]() { OnSysColorChange(); });
345 void CSciEdit::Init(const ProjectProperties
& props
)
347 Init(props
.lProjectLanguage
);
348 m_sCommand
= CUnicodeUtils::GetUTF8(props
.GetCheckRe());
349 m_sBugID
= CUnicodeUtils::GetUTF8(props
.GetBugIDRe());
350 m_sUrl
= CUnicodeUtils::GetUTF8(props
.sUrl
);
352 Call(SCI_SETMOUSEDWELLTIME
, 333);
354 if (props
.nLogWidthMarker
)
356 Call(SCI_SETWRAPMODE
, SC_WRAP_NONE
);
357 Call(SCI_SETEDGEMODE
, EDGE_LINE
);
358 Call(SCI_SETEDGECOLUMN
, props
.nLogWidthMarker
);
359 Call(SCI_SETSCROLLWIDTHTRACKING
, TRUE
);
360 Call(SCI_SETSCROLLWIDTH
, 1);
364 Call(SCI_SETEDGEMODE
, EDGE_NONE
);
365 Call(SCI_SETWRAPMODE
, SC_WRAP_WORD
);
369 void CSciEdit::SetIcon(const std::map
<int, UINT
> &icons
)
371 int iconWidth
= GetSystemMetrics(SM_CXSMICON
);
372 int iconHeight
= GetSystemMetrics(SM_CYSMICON
);
373 Call(SCI_RGBAIMAGESETWIDTH
, iconWidth
);
374 Call(SCI_RGBAIMAGESETHEIGHT
, iconHeight
);
375 for (auto icon
: icons
)
377 CAutoIcon hIcon
= LoadIconEx(AfxGetInstanceHandle(), MAKEINTRESOURCE(icon
.second
), iconWidth
, iconHeight
);
378 auto bytes
= Icon2Image(hIcon
);
379 Call(SCI_REGISTERRGBAIMAGE
, icon
.first
, reinterpret_cast<LPARAM
>(bytes
.get()));
383 BOOL
CSciEdit::LoadDictionaries(LONG lLanguageID
)
385 // Setup the spell checker
386 wchar_t buf
[6] = { 0 };
387 CString sFolderUp
= CPathUtils::GetAppParentDirectory();
388 CString sFolderAppData
= CPathUtils::GetAppDataDirectory();
391 GetLocaleInfo(MAKELCID(lLanguageID
, SORT_DEFAULT
), LOCALE_SISO639LANGNAME
, buf
, _countof(buf
));
393 if (lLanguageID
== 2074)
396 GetLocaleInfo(MAKELCID(lLanguageID
, SORT_DEFAULT
), LOCALE_SISO3166CTRYNAME
, buf
, _countof(buf
));
400 if ((PathFileExists(sFolderAppData
+ L
"dic\\" + sFile
+ L
".aff")) &&
401 (PathFileExists(sFolderAppData
+ L
"dic\\" + sFile
+ L
".dic")))
403 pChecker
= std::make_unique
<Hunspell
>(CStringA(sFolderAppData
+ L
"dic\\" + sFile
+ L
".aff"), CStringA(sFolderAppData
+ L
"dic\\" + sFile
+ L
".dic"));
405 else if ((PathFileExists(sFolderUp
+ L
"Languages\\" + sFile
+ L
".aff")) &&
406 (PathFileExists(sFolderUp
+ L
"Languages\\" + sFile
+ L
".dic")))
408 pChecker
= std::make_unique
<Hunspell
>(CStringA(sFolderUp
+ L
"Languages\\" + sFile
+ L
".aff"), CStringA(sFolderUp
+ L
"Languages\\" + sFile
+ L
".dic"));
412 const char* encoding
= pChecker
->get_dic_encoding();
413 CTraceToOutputDebugString::Instance()(__FUNCTION__
": %s\n", encoding
);
415 for (int i
= 0; i
< _countof(enc2locale
); ++i
)
417 if (strcmp(encoding
, enc2locale
[i
].def_enc
) == 0)
418 m_spellcodepage
= atoi(enc2locale
[i
].cp
);
420 m_personalDict
.Init(lLanguageID
);
428 LRESULT
CSciEdit::Call(UINT message
, WPARAM wParam
, LPARAM lParam
)
430 ASSERT(::IsWindow(m_hWnd
)); //Window must be valid
431 ASSERT(m_DirectFunction
); //Direct function must be valid
432 return reinterpret_cast<SciFnDirect
>(m_DirectFunction
)(m_DirectPointer
, message
, wParam
, lParam
);
435 CString
CSciEdit::StringFromControl(const CStringA
& text
)
439 int codepage
= static_cast<int>(Call(SCI_GETCODEPAGE
));
440 int reslen
= MultiByteToWideChar(codepage
, 0, text
, text
.GetLength(), 0, 0);
441 MultiByteToWideChar(codepage
, 0, text
, text
.GetLength(), sText
.GetBuffer(reslen
+1), reslen
+1);
442 sText
.ReleaseBuffer(reslen
);
449 CStringA
CSciEdit::StringForControl(const CString
& text
)
453 int codepage
= static_cast<int>(SendMessage(SCI_GETCODEPAGE
));
454 int reslen
= WideCharToMultiByte(codepage
, 0, text
, text
.GetLength(), 0, 0, 0, 0);
455 WideCharToMultiByte(codepage
, 0, text
, text
.GetLength(), sTextA
.GetBuffer(reslen
), reslen
, 0, 0);
456 sTextA
.ReleaseBuffer(reslen
);
460 ATLTRACE("string length %d\n", sTextA
.GetLength());
464 void CSciEdit::SetText(const CString
& sText
)
466 auto readonly
= m_bReadOnly
;
467 SCOPE_EXIT
{ SetReadOnly(readonly
); };
470 CStringA sTextA
= StringForControl(sText
);
471 Call(SCI_SETTEXT
, 0, reinterpret_cast<LPARAM
>(static_cast<LPCSTR
>(sTextA
)));
473 if (Call(SCI_GETSCROLLWIDTHTRACKING
) != 0)
474 Call(SCI_SETSCROLLWIDTH
, 1);
476 // Scintilla seems to have problems with strings that
477 // aren't terminated by a newline char. Once that char
478 // is there, it can be removed without problems.
479 // So we add here a newline, then remove it again.
480 Call(SCI_DOCUMENTEND
);
482 Call(SCI_DELETEBACK
);
485 void CSciEdit::InsertText(const CString
& sText
, bool bNewLine
)
487 auto readonly
= m_bReadOnly
;
488 SCOPE_EXIT
{ SetReadOnly(readonly
); };
491 CStringA sTextA
= StringForControl(sText
);
492 Call(SCI_REPLACESEL
, 0, reinterpret_cast<LPARAM
>(static_cast<LPCSTR
>(sTextA
)));
494 Call(SCI_REPLACESEL
, 0, reinterpret_cast<LPARAM
>("\n"));
497 CString
CSciEdit::GetText()
499 auto len
= static_cast<int>(Call(SCI_GETTEXT
, 0, 0));
501 Call(SCI_GETTEXT
, len
+ 1, reinterpret_cast<LPARAM
>(static_cast<LPCSTR
>(CStrBufA(sTextA
, len
+ 1))));
502 return StringFromControl(sTextA
);
505 CString
CSciEdit::GetWordUnderCursor(bool bSelectWord
, bool allchars
)
507 Sci_TextRange textrange
;
508 auto pos
= static_cast<Sci_Position
>(Call(SCI_GETCURRENTPOS
));
509 textrange
.chrg
.cpMin
= static_cast<int>(Call(SCI_WORDSTARTPOSITION
, pos
, TRUE
));
510 if ((pos
== textrange
.chrg
.cpMin
)||(textrange
.chrg
.cpMin
< 0))
512 textrange
.chrg
.cpMax
= static_cast<int>(Call(SCI_WORDENDPOSITION
, textrange
.chrg
.cpMin
, TRUE
));
514 auto textbuffer
= std::make_unique
<char[]>(textrange
.chrg
.cpMax
- textrange
.chrg
.cpMin
+ 1);
515 textrange
.lpstrText
= textbuffer
.get();
516 Call(SCI_GETTEXTRANGE
, 0, reinterpret_cast<LPARAM
>(&textrange
));
517 CString sRet
= StringFromControl(textbuffer
.get());
518 if (m_bDoStyle
&& !allchars
)
520 for (const auto styleindicator
: { '*', '_', '^' })
524 if (sRet
[sRet
.GetLength() - 1] == styleindicator
)
526 --textrange
.chrg
.cpMax
;
527 sRet
.Truncate(sRet
.GetLength() - 1);
531 if (sRet
[0] == styleindicator
)
533 ++textrange
.chrg
.cpMin
;
534 sRet
= sRet
.Right(sRet
.GetLength() - 1);
539 Call(SCI_SETSEL
, textrange
.chrg
.cpMin
, textrange
.chrg
.cpMax
);
543 void CSciEdit::SetFont(CString sFontName
, int iFontSizeInPoints
)
545 Call(SCI_STYLESETFONT
, STYLE_DEFAULT
, reinterpret_cast<LPARAM
>(CUnicodeUtils::GetUTF8(sFontName
).GetBuffer()));
546 Call(SCI_STYLESETSIZE
, STYLE_DEFAULT
, iFontSizeInPoints
);
547 Call(SCI_STYLECLEARALL
);
549 LPARAM color
= GetSysColor(COLOR_HOTLIGHT
);
550 // set the styles for the bug ID strings
551 Call(SCI_STYLESETBOLD
, STYLE_ISSUEBOLD
, TRUE
);
552 Call(SCI_STYLESETFORE
, STYLE_ISSUEBOLD
, color
);
553 Call(SCI_STYLESETBOLD
, STYLE_ISSUEBOLDITALIC
, TRUE
);
554 Call(SCI_STYLESETITALIC
, STYLE_ISSUEBOLDITALIC
, TRUE
);
555 Call(SCI_STYLESETFORE
, STYLE_ISSUEBOLDITALIC
, color
);
556 Call(SCI_STYLESETHOTSPOT
, STYLE_ISSUEBOLDITALIC
, TRUE
);
558 // set the formatted text styles
559 Call(SCI_STYLESETBOLD
, STYLE_BOLD
, TRUE
);
560 Call(SCI_STYLESETITALIC
, STYLE_ITALIC
, TRUE
);
561 Call(SCI_STYLESETUNDERLINE
, STYLE_UNDERLINED
, TRUE
);
563 // set the style for URLs
564 Call(SCI_STYLESETFORE
, STYLE_URL
, color
);
565 Call(SCI_STYLESETHOTSPOT
, STYLE_URL
, TRUE
);
567 Call(SCI_SETHOTSPOTACTIVEUNDERLINE
, TRUE
);
570 void CSciEdit::SetAutoCompletionList(std::map
<CString
, int>&& list
, wchar_t separator
, wchar_t typeSeparator
)
572 //copy the auto completion list.
574 //SK: instead of creating a copy of that list, we could accept a pointer
575 //to the list and use that instead. But then the caller would have to make
576 //sure that the list persists over the lifetime of the control!
578 m_autolist
= std::move(list
);
579 m_separator
= separator
;
580 m_typeSeparator
= typeSeparator
;
583 // Helper for CSciEdit::IsMisspelled()
584 // Returns TRUE if sWord has spelling errors.
585 BOOL
CSciEdit::CheckWordSpelling(const CString
& sWord
)
589 IEnumSpellingErrorPtr enumSpellingError
= nullptr;
590 if (SUCCEEDED(m_SpellChecker
->Check(sWord
, &enumSpellingError
)))
592 ISpellingErrorPtr spellingError
= nullptr;
593 if (enumSpellingError
->Next(&spellingError
) == S_OK
)
595 CORRECTIVE_ACTION action
= CORRECTIVE_ACTION_NONE
;
596 spellingError
->get_CorrectiveAction(&action
);
597 if (action
!= CORRECTIVE_ACTION_NONE
)
604 // convert the string from the control to the encoding of the spell checker module.
605 auto sWordA
= GetWordForSpellChecker(sWord
);
606 if (!pChecker
->spell(sWordA
))
613 BOOL
CSciEdit::IsMisspelled(const CString
& sWord
)
615 // words starting with a digit are treated as correctly spelled
616 if (_istdigit(sWord
.GetAt(0)))
618 // words in the personal dictionary are correct too
619 if (m_personalDict
.FindWord(sWord
))
622 // Check spell checking cache first.
623 const BOOL
*cacheResult
= m_SpellingCache
.try_get(std::wstring(sWord
, sWord
.GetLength()));
627 // convert the string from the control to the encoding of the spell checker module.
628 auto sWordA
= GetWordForSpellChecker(sWord
);
630 // now we actually check the spelling...
631 BOOL misspelled
= CheckWordSpelling(sWord
);
634 // the word is marked as misspelled, we now check whether the word
635 // is maybe a composite identifier
636 // a composite identifier consists of multiple words, with each word
637 // separated by a change in lower to uppercase letters
639 if (sWord
.GetLength() > 1)
643 while (wordend
< sWord
.GetLength())
645 while ((wordend
< sWord
.GetLength())&&(!_istupper(sWord
[wordend
])))
647 if ((wordstart
== 0)&&(wordend
== sWord
.GetLength()))
649 // words in the auto list are also assumed correctly spelled
650 if (m_autolist
.find(sWord
) != m_autolist
.end())
656 CString
token(sWord
.Mid(wordstart
, wordend
- wordstart
));
657 if (token
.GetLength() > 2 && CheckWordSpelling(token
))
669 m_SpellingCache
.insert_or_assign(std::wstring(sWord
, sWord
.GetLength()), misspelled
);
673 void CSciEdit::CheckSpelling(Sci_Position startpos
, Sci_Position endpos
)
678 if (!pChecker
&& !m_SpellChecker
)
681 Sci_TextRange textrange
;
682 textrange
.chrg
.cpMin
= static_cast<Sci_PositionCR
>(startpos
);
683 textrange
.chrg
.cpMax
= textrange
.chrg
.cpMin
;
684 auto lastpos
= endpos
;
686 lastpos
= static_cast<int>(Call(SCI_GETLENGTH
)) - textrange
.chrg
.cpMin
;
687 Call(SCI_SETINDICATORCURRENT
, INDIC_MISSPELLED
);
688 while (textrange
.chrg
.cpMax
< lastpos
)
690 textrange
.chrg
.cpMin
= static_cast<int>(Call(SCI_WORDSTARTPOSITION
, textrange
.chrg
.cpMax
+ 1, TRUE
));
691 if (textrange
.chrg
.cpMin
< textrange
.chrg
.cpMax
)
693 textrange
.chrg
.cpMax
= static_cast<int>(Call(SCI_WORDENDPOSITION
, textrange
.chrg
.cpMin
, TRUE
));
694 if (textrange
.chrg
.cpMin
== textrange
.chrg
.cpMax
)
696 textrange
.chrg
.cpMax
++;
697 // since Scintilla squiggles to the end of the text even if told to stop one char before it,
698 // we have to clear here the squiggly lines to the end.
699 if (textrange
.chrg
.cpMin
)
700 Call(SCI_INDICATORCLEARRANGE
, textrange
.chrg
.cpMin
-1, textrange
.chrg
.cpMax
- textrange
.chrg
.cpMin
+ 1);
703 ATLASSERT(textrange
.chrg
.cpMax
>= textrange
.chrg
.cpMin
);
704 auto textbuffer
= std::make_unique
<char[]>(textrange
.chrg
.cpMax
- textrange
.chrg
.cpMin
+ 2);
705 textrange
.lpstrText
= textbuffer
.get();
706 textrange
.chrg
.cpMax
++;
707 Call(SCI_GETTEXTRANGE
, 0, reinterpret_cast<LPARAM
>(&textrange
));
708 auto len
= static_cast<int>(strlen(textrange
.lpstrText
));
711 textrange
.chrg
.cpMax
--;
712 Call(SCI_GETTEXTRANGE
, 0, reinterpret_cast<LPARAM
>(&textrange
));
713 len
= static_cast<int>(strlen(textrange
.lpstrText
));
714 textrange
.chrg
.cpMax
++;
717 if (len
&& textrange
.lpstrText
[len
- 1] == '.')
719 // Try to ignore file names from the auto list.
720 // Do do this, for each word ending with '.' we extract next word and check
721 // whether the combined string is present in auto list.
722 Sci_TextRange twoWords
;
723 twoWords
.chrg
.cpMin
= textrange
.chrg
.cpMin
;
724 twoWords
.chrg
.cpMax
= static_cast<int>(Call(SCI_WORDENDPOSITION
, textrange
.chrg
.cpMax
+ 1, TRUE
));
725 auto twoWordsBuffer
= std::make_unique
<char[]>(twoWords
.chrg
.cpMax
- twoWords
.chrg
.cpMin
+ 1);
726 twoWords
.lpstrText
= twoWordsBuffer
.get();
727 Call(SCI_GETTEXTRANGE
, 0, reinterpret_cast<LPARAM
>(&twoWords
));
728 CString sWord
= StringFromControl(twoWords
.lpstrText
);
729 if (m_autolist
.find(sWord
) != m_autolist
.end())
731 //mark word as correct (remove the squiggle line)
732 Call(SCI_INDICATORCLEARRANGE
, twoWords
.chrg
.cpMin
, twoWords
.chrg
.cpMax
- twoWords
.chrg
.cpMin
);
733 textrange
.chrg
.cpMax
= twoWords
.chrg
.cpMax
;
738 textrange
.lpstrText
[len
- 1] = '\0';
739 textrange
.chrg
.cpMax
--;
740 if (textrange
.lpstrText
[0])
742 CString sWord
= StringFromControl(textrange
.lpstrText
);
743 if ((GetStyleAt(textrange
.chrg
.cpMin
) != STYLE_URL
) && IsMisspelled(sWord
))
745 //mark word as misspelled
746 Call(SCI_INDICATORFILLRANGE
, textrange
.chrg
.cpMin
, textrange
.chrg
.cpMax
- textrange
.chrg
.cpMin
);
750 //mark word as correct (remove the squiggle line)
751 Call(SCI_INDICATORCLEARRANGE
, textrange
.chrg
.cpMin
, textrange
.chrg
.cpMax
- textrange
.chrg
.cpMin
);
752 Call(SCI_INDICATORCLEARRANGE
, textrange
.chrg
.cpMin
, textrange
.chrg
.cpMax
- textrange
.chrg
.cpMin
+ 1);
758 void CSciEdit::SuggestSpellingAlternatives()
760 if (!pChecker
&& !m_SpellChecker
)
762 CString word
= GetWordUnderCursor(true);
763 Call(SCI_SETCURRENTPOS
, Call(SCI_WORDSTARTPOSITION
, Call(SCI_GETCURRENTPOS
), TRUE
));
770 IEnumStringPtr enumSpellingSuggestions
= nullptr;
771 if (SUCCEEDED(m_SpellChecker
->Suggest(word
, &enumSpellingSuggestions
)))
773 LPOLESTR string
= nullptr;
774 while (enumSpellingSuggestions
->Next(1, &string
, nullptr) == S_OK
)
776 suggestions
.AppendFormat(L
"%s%c%d%c", (LPCWSTR
)CString(string
), m_typeSeparator
, AUTOCOMPLETE_SPELLING
, m_separator
);
777 CoTaskMemFree(string
);
783 auto wlst
= pChecker
->suggest(GetWordForSpellChecker(word
));
786 for (const auto& alternative
: wlst
)
787 suggestions
.AppendFormat(L
"%s%c%d%c", static_cast<LPCWSTR
>(GetWordFromSpellChecker(alternative
)), m_typeSeparator
, AUTOCOMPLETE_SPELLING
, m_separator
);
791 suggestions
.TrimRight(m_separator
);
792 if (suggestions
.IsEmpty())
794 Call(SCI_AUTOCSETSEPARATOR
, CStringA(m_separator
).GetAt(0));
795 Call(SCI_AUTOCSETTYPESEPARATOR
, m_typeSeparator
);
796 Call(SCI_AUTOCSETDROPRESTOFWORD
, 1);
797 Call(SCI_AUTOCSHOW
, 0, reinterpret_cast<LPARAM
>(static_cast<LPCSTR
>(StringForControl(suggestions
))));
798 SetWindowStylesForAutocompletionPopup();
801 void CSciEdit::DoAutoCompletion(Sci_Position nMinPrefixLength
)
803 if (m_autolist
.empty())
805 auto pos
= static_cast<int>(static_cast<Sci_Position
>(Call(SCI_GETCURRENTPOS
)));
806 if (pos
!= static_cast<int>(Call(SCI_WORDENDPOSITION
, pos
, TRUE
)))
807 return; // don't auto complete if we're not at the end of a word
808 CString word
= GetWordUnderCursor();
809 if (word
.GetLength() < nMinPrefixLength
)
811 word
= GetWordUnderCursor(false, true);
812 if (word
.GetLength() < nMinPrefixLength
)
813 return; // don't auto complete yet, word is too short
815 CString sAutoCompleteList
;
817 for (int i
= 0; i
< 2; ++i
)
819 std::vector
<CString
> words
;
821 pos
= word
.Find('-');
823 CString wordLower
= word
;
824 wordLower
.MakeLower();
825 CString wordHigher
= word
;
826 wordHigher
.MakeUpper();
828 words
.push_back(word
);
829 words
.push_back(wordLower
);
830 words
.push_back(wordHigher
);
834 CString s
= wordLower
.Left(pos
);
835 if (s
.GetLength() >= nMinPrefixLength
)
837 s
= wordLower
.Mid(pos
+ 1);
838 if (s
.GetLength() >= nMinPrefixLength
)
840 s
= wordHigher
.Left(pos
);
841 if (s
.GetLength() >= nMinPrefixLength
)
842 words
.push_back(wordHigher
.Left(pos
));
843 s
= wordHigher
.Mid(pos
+ 1);
844 if (s
.GetLength() >= nMinPrefixLength
)
845 words
.push_back(wordHigher
.Mid(pos
+1));
848 // note: the m_autolist is case-sensitive because
849 // its contents are also used to mark words in it
850 // as correctly spelled. If it would be case-insensitive,
851 // case spelling mistakes would not show up as misspelled.
852 std::map
<CString
, int> wordset
;
853 for (const auto& w
: words
)
855 for (auto lowerit
= m_autolist
.lower_bound(w
);
856 lowerit
!= m_autolist
.end(); ++lowerit
)
858 int compare
= w
.CompareNoCase(lowerit
->first
.Left(w
.GetLength()));
861 else if (compare
== 0)
862 wordset
.emplace(lowerit
->first
, lowerit
->second
);
868 for (const auto& w
: wordset
)
869 sAutoCompleteList
.AppendFormat(L
"%s%c%d%c", static_cast<LPCWSTR
>(w
.first
), m_typeSeparator
, w
.second
, m_separator
);
871 sAutoCompleteList
.TrimRight(m_separator
);
875 if (sAutoCompleteList
.IsEmpty())
877 // retry with all chars
878 word
= GetWordUnderCursor(false, true);
885 if (sAutoCompleteList
.IsEmpty())
890 Call(SCI_AUTOCSETSEPARATOR
, CStringA(m_separator
).GetAt(0));
891 Call(SCI_AUTOCSETTYPESEPARATOR
, (m_typeSeparator
));
892 auto sForControl
= StringForControl(sAutoCompleteList
);
893 Call(SCI_AUTOCSHOW
, StringForControl(word
).GetLength(), reinterpret_cast<LPARAM
>(static_cast<LPCSTR
>(sForControl
)));
894 SetWindowStylesForAutocompletionPopup();
897 BOOL
CSciEdit::OnChildNotify(UINT message
, WPARAM wParam
, LPARAM lParam
, LRESULT
* pLResult
)
899 if (message
!= WM_NOTIFY
)
900 return CWnd::OnChildNotify(message
, wParam
, lParam
, pLResult
);
902 auto lpnmhdr
= reinterpret_cast<LPNMHDR
>(lParam
);
903 auto lpSCN
= reinterpret_cast<SCNotification
*>(lParam
);
905 if(lpnmhdr
->hwndFrom
==m_hWnd
)
907 switch(lpnmhdr
->code
)
911 if ((lpSCN
->ch
< 32)&&(lpSCN
->ch
!= 13)&&(lpSCN
->ch
!= 10))
912 Call(SCI_DELETEBACK
);
914 DoAutoCompletion(m_nAutoCompleteMinChars
);
918 case SCN_AUTOCSELECTION
:
920 CString text
= StringFromControl(lpSCN
->text
);
921 if (m_autolist
[text
] == AUTOCOMPLETE_SNIPPET
)
923 Call(SCI_AUTOCCANCEL
);
924 for (INT_PTR handlerindex
= 0; handlerindex
< m_arContextHandlers
.GetCount(); ++handlerindex
)
926 CSciEditContextMenuInterface
* pHandler
= m_arContextHandlers
.GetAt(handlerindex
);
927 pHandler
->HandleSnippet(m_autolist
[text
], text
, this);
932 case SCN_STYLENEEDED
:
934 auto startpos
= static_cast<Sci_Position
>(Call(SCI_GETENDSTYLED
));
935 auto endpos
= reinterpret_cast<SCNotification
*>(lpnmhdr
)->position
;
937 auto startwordpos
= static_cast<int>(Call(SCI_WORDSTARTPOSITION
, startpos
, true));
938 auto endwordpos
= static_cast<int>(Call(SCI_WORDENDPOSITION
, endpos
, true));
940 MarkEnteredBugID(startwordpos
, endwordpos
);
942 StyleEnteredText(startwordpos
, endwordpos
);
944 StyleURLs(startwordpos
, endwordpos
);
945 CheckSpelling(startwordpos
, endwordpos
);
947 // Tell scintilla editor that we styled all requested range.
948 Call(SCI_STARTSTYLING
, endwordpos
);
949 Call(SCI_SETSTYLING
, 0, 0);
954 if (!m_blockModifiedHandler
&& (lpSCN
->modificationType
& (SC_MOD_INSERTTEXT
| SC_MOD_DELETETEXT
)))
956 auto firstline
= static_cast<int>(Call(SCI_GETFIRSTVISIBLELINE
));
957 auto lastline
= firstline
+ static_cast<int>(Call(SCI_LINESONSCREEN
));
958 auto firstpos
= static_cast<Sci_Position
>(Call(SCI_POSITIONFROMLINE
, firstline
));
959 auto lastpos
= static_cast<Sci_Position
>(Call(SCI_GETLINEENDPOSITION
, lastline
));
960 auto pos1
= lpSCN
->position
;
961 auto pos2
= pos1
+ lpSCN
->length
;
962 // always use the bigger range
963 firstpos
= min(firstpos
, pos1
);
964 lastpos
= max(lastpos
, pos2
);
966 WrapLines(firstpos
, lastpos
);
971 case SCN_HOTSPOTRELEASECLICK
:
973 Sci_TextRange textrange
;
974 textrange
.chrg
.cpMin
= static_cast<Sci_PositionCR
>(lpSCN
->position
);
975 textrange
.chrg
.cpMax
= static_cast<Sci_PositionCR
>(lpSCN
->position
);
976 auto style
= GetStyleAt(lpSCN
->position
);
977 if (style
!= STYLE_ISSUEBOLDITALIC
&& style
!= STYLE_URL
)
979 while (GetStyleAt(textrange
.chrg
.cpMin
- 1) == style
)
980 --textrange
.chrg
.cpMin
;
981 while (GetStyleAt(textrange
.chrg
.cpMax
+ 1) == style
)
982 ++textrange
.chrg
.cpMax
;
983 ++textrange
.chrg
.cpMax
;
984 auto textbuffer
= std::make_unique
<char[]>(textrange
.chrg
.cpMax
- textrange
.chrg
.cpMin
+ 1);
985 textrange
.lpstrText
= textbuffer
.get();
986 Call(SCI_GETTEXTRANGE
, 0, reinterpret_cast<LPARAM
>(&textrange
));
988 if (style
== STYLE_URL
)
990 url
= StringFromControl(textbuffer
.get());
991 if (url
.Find(L
'@') > 0 && !PathIsURL(url
))
992 url
= L
"mailto:" + url
;
997 ProjectProperties::ReplaceBugIDPlaceholder(url
, StringFromControl(textbuffer
.get()));
1001 if (lpnmhdr
->code
== SCN_HOTSPOTRELEASECLICK
)
1002 ShellExecute(GetParent()->GetSafeHwnd(), L
"open", url
, nullptr, nullptr, SW_SHOWDEFAULT
);
1005 CStringA sTextA
= StringForControl(url
);
1006 Call(SCI_CALLTIPSHOW
, lpSCN
->position
+ 3, reinterpret_cast<LPARAM
>(static_cast<LPCSTR
>(sTextA
)));
1012 Call(SCI_CALLTIPCANCEL
);
1016 return CWnd::OnChildNotify(message
, wParam
, lParam
, pLResult
);
1019 BEGIN_MESSAGE_MAP(CSciEdit
, CWnd
)
1022 ON_WM_SYSCOLORCHANGE()
1025 void CSciEdit::OnSysColorChange()
1027 __super::OnSysColorChange();
1028 CTheme::Instance().OnSysColorChanged();
1034 void CSciEdit::OnKeyDown(UINT nChar
, UINT nRepCnt
, UINT nFlags
)
1040 if ((Call(SCI_AUTOCACTIVE
)==0)&&(Call(SCI_CALLTIPACTIVE
)==0))
1041 ::SendMessage(GetParent()->GetSafeHwnd(), WM_CLOSE
, 0, 0);
1045 CWnd::OnKeyDown(nChar
, nRepCnt
, nFlags
);
1048 BOOL
CSciEdit::PreTranslateMessage(MSG
* pMsg
)
1050 if (pMsg
->message
== WM_KEYDOWN
)
1052 switch (pMsg
->wParam
)
1056 if ((GetKeyState(VK_CONTROL
) & 0x8000) && ((GetKeyState(VK_MENU
) & 0x8000) == 0))
1058 DoAutoCompletion(1);
1064 // The TAB cannot be handled in OnKeyDown because it is too late by then.
1066 if ((GetKeyState(VK_CONTROL
) & 0x8000) && ((GetKeyState(VK_MENU
) & 0x8000) == 0))
1068 //Ctrl-Tab was pressed, this means we should provide the user with
1069 //a list of possible spell checking alternatives to the word under
1071 SuggestSpellingAlternatives();
1074 else if (!Call(SCI_AUTOCACTIVE
))
1076 ::PostMessage(GetParent()->GetSafeHwnd(), WM_NEXTDLGCTL
, GetKeyState(VK_SHIFT
)&0x8000, 0);
1083 return CWnd::PreTranslateMessage(pMsg
);
1086 void CSciEdit::OnContextMenu(CWnd
* /*pWnd*/, CPoint point
)
1088 auto anchor
= static_cast<Sci_Position
>(Call(SCI_GETANCHOR
));
1089 auto currentpos
= static_cast<Sci_Position
>(Call(SCI_GETCURRENTPOS
));
1090 auto selstart
= static_cast<int>(static_cast<Sci_Position
>(Call(SCI_GETSELECTIONSTART
)));
1091 auto selend
= static_cast<int>(static_cast<Sci_Position
>(Call(SCI_GETSELECTIONEND
)));
1092 Sci_Position pointpos
= 0;
1093 if ((point
.x
== -1) && (point
.y
== -1))
1096 GetClientRect(&rect
);
1097 ClientToScreen(&rect
);
1098 point
= rect
.CenterPoint();
1099 pointpos
= static_cast<Sci_Position
>(Call(SCI_GETCURRENTPOS
));
1103 // change the cursor position to the point where the user
1105 CPoint clientpoint
= point
;
1106 ScreenToClient(&clientpoint
);
1107 pointpos
= static_cast<Sci_Position
>(Call(SCI_POSITIONFROMPOINT
, clientpoint
.x
, clientpoint
.y
));
1109 CString sMenuItemText
;
1111 bool bRestoreCursor
= true;
1112 if (popup
.CreatePopupMenu())
1114 bool bCanUndo
= !!Call(SCI_CANUNDO
);
1115 bool bCanRedo
= !!Call(SCI_CANREDO
);
1116 bool bHasSelection
= (selend
-selstart
> 0);
1117 bool bCanPaste
= !!Call(SCI_CANPASTE
);
1118 bool bIsReadOnly
= !!Call(SCI_GETREADONLY
);
1119 UINT uEnabledMenu
= MF_STRING
| MF_ENABLED
;
1120 UINT uDisabledMenu
= MF_STRING
| MF_GRAYED
;
1122 // find the word under the cursor
1126 // setting the cursor clears the selection
1127 Call(SCI_SETANCHOR
, pointpos
);
1128 Call(SCI_SETCURRENTPOS
, pointpos
);
1129 sWord
= GetWordUnderCursor();
1130 // restore the selection
1131 Call(SCI_SETSELECTIONSTART
, selstart
);
1132 Call(SCI_SETSELECTIONEND
, selend
);
1135 sWord
= GetWordUnderCursor();
1136 auto worda
= GetWordForSpellChecker(sWord
);
1138 int nCorrections
= 1;
1139 // check if the word under the cursor is spelled wrong
1140 if (!bIsReadOnly
&& (pChecker
|| m_SpellChecker
) && !worda
.empty())
1144 IEnumStringPtr enumSpellingSuggestions
= nullptr;
1145 if (SUCCEEDED(m_SpellChecker
->Suggest(sWord
, &enumSpellingSuggestions
)))
1147 LPOLESTR string
= nullptr;
1148 while (enumSpellingSuggestions
->Next(1, &string
, nullptr) == S_OK
)
1150 popup
.InsertMenu((UINT
)-1, 0, nCorrections
++, string
);
1151 CoTaskMemFree(string
);
1153 popup
.AppendMenu(MF_SEPARATOR
);
1158 // get the spell suggestions
1159 auto wlst
= pChecker
->suggest(worda
);
1162 // add the suggestions to the context menu
1163 for (const auto& alternative
: wlst
)
1165 CString sug
= GetWordFromSpellChecker(alternative
);
1166 popup
.InsertMenu(static_cast<UINT
>(-1), 0, nCorrections
++, sug
);
1168 popup
.AppendMenu(MF_SEPARATOR
);
1173 // also allow the user to add the word to the custom dictionary so
1174 // it won't show up as misspelled anymore
1175 if (!bIsReadOnly
&& (sWord
.GetLength() < PDICT_MAX_WORD_LENGTH
) && (m_autolist
.find(sWord
) == m_autolist
.end() && IsMisspelled(sWord
)) &&
1176 (!_istdigit(sWord
.GetAt(0))) && (!m_personalDict
.FindWord(sWord
)))
1178 sMenuItemText
.Format(IDS_SCIEDIT_ADDWORD
, static_cast<LPCWSTR
>(sWord
));
1179 popup
.AppendMenu(uEnabledMenu
, SCI_ADDWORD
, sMenuItemText
);
1180 // another separator
1181 popup
.AppendMenu(MF_SEPARATOR
);
1184 // add the 'default' entries
1185 sMenuItemText
.LoadString(IDS_SCIEDIT_UNDO
);
1186 popup
.AppendMenu(bCanUndo
? uEnabledMenu
: uDisabledMenu
, SCI_UNDO
, sMenuItemText
);
1187 sMenuItemText
.LoadString(IDS_SCIEDIT_REDO
);
1188 popup
.AppendMenu(bCanRedo
? uEnabledMenu
: uDisabledMenu
, SCI_REDO
, sMenuItemText
);
1190 popup
.AppendMenu(MF_SEPARATOR
);
1192 sMenuItemText
.LoadString(IDS_SCIEDIT_CUT
);
1193 popup
.AppendMenu(!bIsReadOnly
&& bHasSelection
? uEnabledMenu
: uDisabledMenu
, SCI_CUT
, sMenuItemText
);
1194 sMenuItemText
.LoadString(IDS_SCIEDIT_COPY
);
1195 popup
.AppendMenu(bHasSelection
? uEnabledMenu
: uDisabledMenu
, SCI_COPY
, sMenuItemText
);
1196 sMenuItemText
.LoadString(IDS_SCIEDIT_PASTE
);
1197 popup
.AppendMenu(bCanPaste
? uEnabledMenu
: uDisabledMenu
, SCI_PASTE
, sMenuItemText
);
1199 popup
.AppendMenu(MF_SEPARATOR
);
1201 sMenuItemText
.LoadString(IDS_SCIEDIT_SELECTALL
);
1202 popup
.AppendMenu(uEnabledMenu
, SCI_SELECTALL
, sMenuItemText
);
1204 if (!bIsReadOnly
&& Call(SCI_GETEDGECOLUMN
))
1206 popup
.AppendMenu(MF_SEPARATOR
);
1208 sMenuItemText
.LoadString(IDS_SCIEDIT_SPLITLINES
);
1209 popup
.AppendMenu(bHasSelection
? uEnabledMenu
: uDisabledMenu
, SCI_LINESSPLIT
, sMenuItemText
);
1212 if (m_arContextHandlers
.GetCount() > 0)
1213 popup
.AppendMenu(MF_SEPARATOR
);
1215 int nCustoms
= nCorrections
;
1216 // now add any custom context menus
1217 for (INT_PTR handlerindex
= 0; handlerindex
< m_arContextHandlers
.GetCount(); ++handlerindex
)
1219 CSciEditContextMenuInterface
* pHandler
= m_arContextHandlers
.GetAt(handlerindex
);
1220 pHandler
->InsertMenuItems(popup
, nCustoms
);
1223 int cmd
= popup
.TrackPopupMenu(TPM_RETURNCMD
| TPM_LEFTALIGN
| TPM_NONOTIFY
, point
.x
, point
.y
, this);
1227 break; // no command selected
1229 bRestoreCursor
= false;
1239 m_personalDict
.AddWord(sWord
);
1240 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
)))));
1242 case SCI_LINESSPLIT
:
1244 auto marker
= static_cast<int>(Call(SCI_GETEDGECOLUMN
) * static_cast<int>(Call(SCI_TEXTWIDTH
, 0, reinterpret_cast<LPARAM
>(" "))));
1247 m_blockModifiedHandler
= true;
1248 SCOPE_EXIT
{ m_blockModifiedHandler
= false; };
1249 Call(SCI_TARGETFROMSELECTION
);
1250 Call(SCI_LINESJOIN
);
1251 Call(SCI_LINESSPLIT
, marker
);
1256 if (cmd
< nCorrections
)
1258 Call(SCI_SETANCHOR
, pointpos
);
1259 Call(SCI_SETCURRENTPOS
, pointpos
);
1260 GetWordUnderCursor(true);
1262 popup
.GetMenuString(cmd
, temp
, 0);
1263 // setting the cursor clears the selection
1264 Call(SCI_REPLACESEL
, 0, reinterpret_cast<LPARAM
>(static_cast<LPCSTR
>(StringForControl(temp
))));
1266 else if (cmd
< (nCorrections
+nCustoms
))
1268 for (INT_PTR handlerindex
= 0; handlerindex
< m_arContextHandlers
.GetCount(); ++handlerindex
)
1270 CSciEditContextMenuInterface
* pHandler
= m_arContextHandlers
.GetAt(handlerindex
);
1271 if (pHandler
->HandleMenuItemClick(cmd
, this))
1279 // restore the anchor and cursor position
1280 Call(SCI_SETCURRENTPOS
, currentpos
);
1281 Call(SCI_SETANCHOR
, anchor
);
1285 bool CSciEdit::StyleEnteredText(Sci_Position startstylepos
, Sci_Position endstylepos
)
1287 bool bStyled
= false;
1288 const auto line
= static_cast<int>(Call(SCI_LINEFROMPOSITION
, startstylepos
));
1289 const auto line_number_end
= static_cast<int>(Call(SCI_LINEFROMPOSITION
, endstylepos
));
1290 for (auto line_number
= line
; line_number
<= line_number_end
; ++line_number
)
1292 auto offset
= static_cast<Sci_Position
>(Call(SCI_POSITIONFROMLINE
, line_number
));
1293 auto line_len
= static_cast<int>(Call(SCI_LINELENGTH
, line_number
));
1294 auto linebuffer
= std::make_unique
<char[]>(line_len
+ 1);
1295 Call(SCI_GETLINE
, line_number
, reinterpret_cast<LPARAM
>(linebuffer
.get()));
1296 linebuffer
[line_len
] = '\0';
1297 Sci_Position start
= 0;
1298 Sci_Position end
= 0;
1299 while (FindStyleChars(linebuffer
.get(), '*', start
, end
))
1301 Call(SCI_STARTSTYLING
, start
+ offset
, STYLE_BOLD
);
1302 Call(SCI_SETSTYLING
, end
-start
, STYLE_BOLD
);
1308 while (FindStyleChars(linebuffer
.get(), '^', start
, end
))
1310 Call(SCI_STARTSTYLING
, start
+ offset
, STYLE_ITALIC
);
1311 Call(SCI_SETSTYLING
, end
-start
, STYLE_ITALIC
);
1317 while (FindStyleChars(linebuffer
.get(), '_', start
, end
))
1319 Call(SCI_STARTSTYLING
, start
+ offset
, STYLE_UNDERLINED
);
1320 Call(SCI_SETSTYLING
, end
-start
, STYLE_UNDERLINED
);
1328 bool CSciEdit::WrapLines(Sci_Position startpos
, Sci_Position endpos
)
1330 auto markerX
= static_cast<Sci_Position
>(Call(SCI_GETEDGECOLUMN
) * static_cast<int>(Call(SCI_TEXTWIDTH
, 0, reinterpret_cast<LPARAM
>(" "))));
1333 Call(SCI_SETTARGETSTART
, startpos
);
1334 Call(SCI_SETTARGETEND
, endpos
);
1335 Call(SCI_LINESSPLIT
, markerX
);
1341 void CSciEdit::AdvanceUTF8(const char * str
, int& pos
)
1343 if ((str
[pos
] & 0xE0)==0xC0)
1345 // utf8 2-byte sequence
1348 else if ((str
[pos
] & 0xF0)==0xE0)
1350 // utf8 3-byte sequence
1353 else if ((str
[pos
] & 0xF8)==0xF0)
1355 // utf8 4-byte sequence
1362 bool CSciEdit::FindStyleChars(const char* line
, char styler
, Sci_Position
& start
, Sci_Position
& end
)
1368 AdvanceUTF8(line
, i
);
1372 bool bFoundMarker
= false;
1373 CString sULine
= CUnicodeUtils::GetUnicode(line
);
1374 // find a starting marker
1375 while (line
[i
] != 0)
1377 if (line
[i
] == styler
)
1379 if ((line
[i
+1]!=0)&&(IsCharAlphaNumeric(sULine
[u
+1]))&&
1380 (((u
>0)&&(!IsCharAlphaNumeric(sULine
[u
-1]))) || (u
==0)))
1383 AdvanceUTF8(line
, i
);
1385 bFoundMarker
= true;
1389 AdvanceUTF8(line
, i
);
1394 // find ending marker
1395 bFoundMarker
= false;
1398 if (line
[i
] == styler
)
1400 if ((IsCharAlphaNumeric(sULine
[u
-1]))&&
1401 ((((u
+1)<sULine
.GetLength())&&(!IsCharAlphaNumeric(sULine
[u
+1]))) || ((u
+1) == sULine
.GetLength()))
1406 bFoundMarker
= true;
1410 AdvanceUTF8(line
, i
);
1413 return bFoundMarker
;
1416 BOOL
CSciEdit::MarkEnteredBugID(Sci_Position startstylepos
, Sci_Position endstylepos
)
1418 if (m_sCommand
.IsEmpty())
1420 // get the text between the start and end position we have to style
1421 const auto line_number
= static_cast<int>(Call(SCI_LINEFROMPOSITION
, startstylepos
));
1422 auto start_pos
= static_cast<Sci_Position
>(Call(SCI_POSITIONFROMLINE
, line_number
));
1423 auto end_pos
= endstylepos
;
1425 if (start_pos
== end_pos
)
1427 if (start_pos
> end_pos
)
1429 auto switchtemp
= start_pos
;
1430 start_pos
= end_pos
;
1431 end_pos
= switchtemp
;
1434 auto textbuffer
= std::make_unique
<char[]>(end_pos
- start_pos
+ 2);
1435 Sci_TextRange textrange
;
1436 textrange
.lpstrText
= textbuffer
.get();
1437 textrange
.chrg
.cpMin
= static_cast<Sci_PositionCR
>(start_pos
);
1438 textrange
.chrg
.cpMax
= static_cast<Sci_PositionCR
>(end_pos
);
1439 Call(SCI_GETTEXTRANGE
, 0, reinterpret_cast<LPARAM
>(&textrange
));
1440 CStringA msg
= CStringA(textbuffer
.get());
1442 Call(SCI_STARTSTYLING
, start_pos
, STYLE_MASK
);
1446 if (!m_sBugID
.IsEmpty())
1448 // match with two regex strings (without grouping!)
1449 const std::regex
regCheck(m_sCommand
);
1450 const std::regex
regBugID(m_sBugID
);
1451 const std::sregex_iterator end
;
1452 std::string s
{ static_cast<LPCSTR
>(msg
) };
1455 // if start_pos is 0, we're styling from the beginning and let the ^ char match the beginning of the line
1456 // that way, the ^ matches the very beginning of the log message and not the beginning of further lines.
1457 // problem is: this only works *while* entering log messages. If a log message is pasted in whole or
1458 // multiple lines are pasted, start_pos can be 0 and styling goes over multiple lines. In that case, those
1459 // additional line starts also match ^
1460 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
)
1462 // clear the styles up to the match position
1463 Call(SCI_SETSTYLING
, it
->position(0)-pos
, STYLE_DEFAULT
);
1465 // (*it)[0] is the matched string
1466 std::string matchedString
= (*it
)[0];
1467 LONG matchedpos
= 0;
1468 for (std::sregex_iterator
it2(matchedString
.cbegin(), matchedString
.cend(), regBugID
); it2
!= end
; ++it2
)
1470 ATLTRACE("matched id : %s\n", std::string((*it2
)[0]).c_str());
1472 // bold style up to the id match
1473 ATLTRACE("position = %ld\n", it2
->position(0));
1474 if (it2
->position(0))
1475 Call(SCI_SETSTYLING
, it2
->position(0) - matchedpos
, STYLE_ISSUEBOLD
);
1476 // bold and recursive style for the bug ID itself
1477 if ((*it2
)[0].str().size())
1478 Call(SCI_SETSTYLING
, (*it2
)[0].str().size(), STYLE_ISSUEBOLDITALIC
);
1479 matchedpos
= static_cast<LONG
>(it2
->position(0) + (*it2
)[0].str().size());
1481 if (matchedpos
&& matchedpos
< static_cast<LONG
>(matchedString
.size()))
1483 Call(SCI_SETSTYLING
, matchedString
.size() - matchedpos
, STYLE_ISSUEBOLD
);
1485 pos
= static_cast<LONG
>(it
->position(0) + matchedString
.size());
1487 // bold style for the rest of the string which isn't matched
1489 Call(SCI_SETSTYLING
, s
.size()-pos
, STYLE_DEFAULT
);
1493 const std::regex
regCheck(m_sCommand
);
1494 const std::sregex_iterator end
;
1495 std::string s
{ static_cast<LPCSTR
>(msg
) };
1497 for (std::sregex_iterator
it(s
.cbegin(), s
.cend(), regCheck
); it
!= end
; ++it
)
1499 // clear the styles up to the match position
1500 if (it
->position(0) - pos
>= 0)
1501 Call(SCI_SETSTYLING
, it
->position(0) - pos
, STYLE_DEFAULT
);
1502 pos
= static_cast<LONG
>(it
->position(0));
1504 const std::smatch match
= *it
;
1505 // we define group 1 as the whole issue text and
1506 // group 2 as the bug ID
1507 if (match
.size() >= 2)
1509 ATLTRACE("matched id : %s\n", std::string(match
[1]).c_str());
1510 if (match
[1].first
- s
.cbegin() - pos
>= 0)
1511 Call(SCI_SETSTYLING
, match
[1].first
- s
.cbegin() - pos
, STYLE_ISSUEBOLD
);
1512 Call(SCI_SETSTYLING
, std::string(match
[1]).size(), STYLE_ISSUEBOLDITALIC
);
1513 pos
= static_cast<LONG
>(match
[1].second
- s
.cbegin());
1518 catch (std::exception
&) {}
1523 //similar code in AppUtils.cpp
1524 bool CSciEdit::IsValidURLChar(unsigned char ch
)
1526 return isalnum(ch
) ||
1527 ch
== '_' || ch
== '/' || ch
== ';' || ch
== '?' || ch
== '&' || ch
== '=' ||
1528 ch
== '%' || ch
== ':' || ch
== '.' || ch
== '#' || ch
== '-' || ch
== '+' ||
1529 ch
== '|' || ch
== '>' || ch
== '<' || ch
== '!' || ch
== '@' || ch
== '~';
1532 //similar code in AppUtils.cpp
1533 void CSciEdit::StyleURLs(Sci_Position startstylepos
, Sci_Position endstylepos
)
1535 const auto line_number
= static_cast<int>(Call(SCI_LINEFROMPOSITION
, startstylepos
));
1536 startstylepos
= static_cast<Sci_Position
>(Call(SCI_POSITIONFROMLINE
, line_number
));
1538 auto len
= endstylepos
- startstylepos
+ 1;
1539 auto textbuffer
= std::make_unique
<char[]>(len
+ 1);
1540 Sci_TextRange textrange
;
1541 textrange
.lpstrText
= textbuffer
.get();
1542 textrange
.chrg
.cpMin
= static_cast<Sci_PositionCR
>(startstylepos
);
1543 textrange
.chrg
.cpMax
= static_cast<Sci_PositionCR
>(endstylepos
);
1544 Call(SCI_GETTEXTRANGE
, 0, reinterpret_cast<LPARAM
>(&textrange
));
1545 // we're dealing with utf8 encoded text here, which means one glyph is
1546 // not necessarily one byte/wchar_t
1547 // that's why we use CStringA to still get a correct char index
1548 CStringA msg
= textbuffer
.get();
1551 for (int i
= 0; i
<= msg
.GetLength(); AdvanceUTF8(msg
, i
))
1553 if ((i
< len
) && IsValidURLChar(msg
[i
]))
1563 if (msg
[starturl
] == '<' && i
< len
) // try to detect and do not strip URLs put within <>
1565 while (starturl
<= i
&& msg
[starturl
] == '<') // strip leading '<'
1569 while (i
< len
&& msg
[i
] != '\r' && msg
[i
] != '\n' && msg
[i
] != '>') // find first '>' or new line after resetting i to start position
1570 AdvanceUTF8(msg
, i
);
1573 int skipTrailing
= 0;
1574 while (strip
&& i
- skipTrailing
- 1 > starturl
&& (msg
[i
- skipTrailing
- 1] == '.' || msg
[i
- skipTrailing
- 1] == '-' || msg
[i
- skipTrailing
- 1] == '?' || msg
[i
- skipTrailing
- 1] == ';' || msg
[i
- skipTrailing
- 1] == ':' || msg
[i
- skipTrailing
- 1] == '>' || msg
[i
- skipTrailing
- 1] == '<' || msg
[i
- skipTrailing
- 1] == '!'))
1577 if (!IsUrlOrEmail(msg
.Mid(starturl
, i
- starturl
- skipTrailing
)))
1583 ASSERT(startstylepos
+ i
- skipTrailing
<= endstylepos
);
1584 Call(SCI_STARTSTYLING
, startstylepos
+ starturl
, STYLE_URL
);
1585 Call(SCI_SETSTYLING
, i
- starturl
- skipTrailing
, STYLE_URL
);
1592 bool CSciEdit::IsUrlOrEmail(const CStringA
& sText
)
1594 if (!PathIsURLA(sText
))
1596 auto atpos
= sText
.Find('@');
1599 if (sText
.Find('.', atpos
) <= atpos
+ 1) // a dot must follow after the @, but not directly after it
1601 if (sText
.Find(':', atpos
) < 0) // do not detect git@example.com:something as an email address
1605 for (const CStringA
& prefix
: { "http://", "https://", "git://", "ftp://", "file://", "mailto:" })
1607 if (strncmp(sText
, prefix
, prefix
.GetLength()) == 0 && sText
.GetLength() != prefix
.GetLength())
1613 std::string
CSciEdit::GetWordForSpellChecker(const CString
& sWord
)
1615 // convert the string from the control to the encoding of the spell checker module.
1617 if (m_spellcodepage
)
1619 int lengthIncTerminator
= WideCharToMultiByte(m_spellcodepage
, 0, sWord
, -1, nullptr, 0, nullptr, nullptr);
1620 if (lengthIncTerminator
<= 1)
1621 return ""; // converting to the codepage failed
1622 sWordA
.resize(lengthIncTerminator
- 1);
1623 WideCharToMultiByte(m_spellcodepage
, 0, sWord
, -1, sWordA
.data(), lengthIncTerminator
- 1, nullptr, nullptr);
1626 sWordA
= std::string(reinterpret_cast<LPCSTR
>(static_cast<LPCWSTR
>(sWord
)));
1628 sWordA
.erase(sWordA
.find_last_not_of("\'\".,") + 1);
1629 sWordA
.erase(0, sWordA
.find_first_not_of("\'\".,"));
1633 for (const auto styleindicator
: { '*', '_', '^' })
1637 if (sWordA
[sWordA
.size() - 1] == styleindicator
)
1638 sWordA
.resize(sWordA
.size() - 1);
1641 if (sWordA
[0] == styleindicator
)
1642 sWordA
= sWordA
.substr(sWordA
.size() - 1);
1649 CString
CSciEdit::GetWordFromSpellChecker(const std::string
& sWordA
)
1652 if (m_spellcodepage
)
1654 wchar_t* buf
= sWord
.GetBuffer(static_cast<int>(sWordA
.size()) * 2);
1655 int lengthIncTerminator
= MultiByteToWideChar(m_spellcodepage
, 0, sWordA
.c_str(), -1, buf
, static_cast<int>(sWordA
.size()) * 2);
1656 if (lengthIncTerminator
== 0)
1658 sWord
.ReleaseBuffer(lengthIncTerminator
- 1);
1661 sWord
= CString(sWordA
.c_str());
1663 sWord
.Trim(L
"\'\".,");
1668 bool CSciEdit::IsUTF8(LPVOID pBuffer
, size_t cb
)
1672 auto pVal
= static_cast<UINT16
*>(pBuffer
);
1673 auto pVal2
= reinterpret_cast<UINT8
*>(pVal
+ 1);
1674 // scan the whole buffer for a 0x0000 sequence
1675 // if found, we assume a binary file
1676 for (size_t i
=0; i
<(cb
-2); i
=i
+2)
1678 if (0x0000 == *pVal
++)
1681 pVal
= static_cast<UINT16
*>(pBuffer
);
1682 if (*pVal
== 0xFEFF)
1686 if (*pVal
== 0xBBEF)
1691 // check for illegal UTF8 chars
1692 pVal2
= static_cast<UINT8
*>(pBuffer
);
1693 for (size_t i
=0; i
<cb
; ++i
)
1695 if ((*pVal2
== 0xC0)||(*pVal2
== 0xC1)||(*pVal2
>= 0xF5))
1699 pVal2
= static_cast<UINT8
*>(pBuffer
);
1701 for (size_t i
=0; i
<(cb
-3); ++i
)
1703 if ((*pVal2
& 0xE0)==0xC0)
1706 if ((*pVal2
& 0xC0)!=0x80)
1710 if ((*pVal2
& 0xF0)==0xE0)
1713 if ((*pVal2
& 0xC0)!=0x80)
1716 if ((*pVal2
& 0xC0)!=0x80)
1720 if ((*pVal2
& 0xF8)==0xF0)
1723 if ((*pVal2
& 0xC0)!=0x80)
1726 if ((*pVal2
& 0xC0)!=0x80)
1729 if ((*pVal2
& 0xC0)!=0x80)
1740 void CSciEdit::SetAStyle(int style
, COLORREF fore
, COLORREF back
, int size
, const char *face
)
1742 Call(SCI_STYLESETFORE
, style
, fore
);
1743 Call(SCI_STYLESETBACK
, style
, back
);
1745 Call(SCI_STYLESETSIZE
, style
, size
);
1747 Call(SCI_STYLESETFONT
, style
, reinterpret_cast<LPARAM
>(face
));
1750 void CSciEdit::SetUDiffStyle()
1752 m_bUDiffmode
= true;
1754 Call(SCI_CLEARDOCUMENTSTYLE
);
1755 Call(SCI_SETTABWIDTH
, CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffTabSize", 4));
1759 //Set the default windows colors for edit controls
1762 //SendEditor(SCI_SETREADONLY, FALSE);
1764 Call(SCI_EMPTYUNDOBUFFER
);
1765 Call(SCI_SETSAVEPOINT
);
1767 Call(SCI_SETUNDOCOLLECTION
, 0);
1769 Call(SCI_SETWRAPMODE
,SC_WRAP_NONE
);
1771 Call(SCI_GOTOPOS
, 0);
1773 Call(SCI_SETVIEWWS
, 1);
1774 Call(SCI_SETWHITESPACESIZE
, 2);
1775 Call(SCI_SETWHITESPACEFORE
, true, ::GetSysColor(COLOR_3DSHADOW
));
1776 Call(SCI_STYLESETVISIBLE
, STYLE_CONTROLCHAR
, TRUE
);
1778 Call(SCI_CLEARDOCUMENTSTYLE
, 0, 0);
1780 if (CTheme::Instance().IsDarkTheme())
1782 Call(SCI_STYLESETFORE
, STYLE_DEFAULT
, UDiffTextColorDark
);
1783 Call(SCI_STYLESETBACK
, STYLE_DEFAULT
, UDiffBackColorDark
);
1784 Call(SCI_SETSELFORE
, TRUE
, CTheme::Instance().GetThemeColor(::GetSysColor(COLOR_HIGHLIGHTTEXT
)));
1785 Call(SCI_SETSELBACK
, TRUE
, CTheme::Instance().GetThemeColor(::GetSysColor(COLOR_HIGHLIGHT
)));
1786 Call(SCI_SETCARETFORE
, UDiffTextColorDark
);
1787 Call(SCI_SETWHITESPACEFORE
, true, RGB(180, 180, 180));
1788 SetAStyle(STYLE_DEFAULT
, UDiffTextColorDark
, UDiffBackColorDark
,
1789 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffFontSize", 10),
1790 CUnicodeUtils::StdGetUTF8(CRegStdString(L
"Software\\TortoiseGit\\UDiffFontName", L
"Consolas")).c_str());
1791 SetAStyle(SCE_DIFF_DEFAULT
, UDiffTextColorDark
, UDiffBackColorDark
,
1792 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffFontSize", 10),
1793 CUnicodeUtils::StdGetUTF8(CRegStdString(L
"Software\\TortoiseGit\\UDiffFontName", L
"Consolas")).c_str());
1794 Call(SCI_STYLECLEARALL
);
1795 SetAStyle(SCE_DIFF_COMMAND
,
1796 CRegStdDWORD(L
"Software\\TortoiseGit\\DarkUDiffForeCommandColor", UDIFF_COLORFORECOMMAND_DARK
),
1797 CRegStdDWORD(L
"Software\\TortoiseGit\\DarkUDiffBackCommandColor", UDIFF_COLORBACKCOMMAND_DARK
));
1798 SetAStyle(SCE_DIFF_POSITION
,
1799 CRegStdDWORD(L
"Software\\TortoiseGit\\DarkUDiffForePositionColor", UDIFF_COLORFOREPOSITION_DARK
),
1800 CRegStdDWORD(L
"Software\\TortoiseGit\\DarkUDiffBackPositionColor", UDIFF_COLORBACKPOSITION_DARK
));
1801 SetAStyle(SCE_DIFF_HEADER
,
1802 CRegStdDWORD(L
"Software\\TortoiseGit\\DarkUDiffForeHeaderColor", UDIFF_COLORFOREHEADER_DARK
),
1803 CRegStdDWORD(L
"Software\\TortoiseGit\\DarkUDiffBackHeaderColor", UDIFF_COLORBACKHEADER_DARK
));
1804 SetAStyle(SCE_DIFF_COMMENT
,
1805 CRegStdDWORD(L
"Software\\TortoiseGit\\DarkUDiffForeCommentColor", UDIFF_COLORFORECOMMENT_DARK
),
1806 CRegStdDWORD(L
"Software\\TortoiseGit\\DarkUDiffBackCommentColor", UDIFF_COLORBACKCOMMENT_DARK
));
1807 for (int style
: { SCE_DIFF_ADDED
, SCE_DIFF_PATCH_ADD
, SCE_DIFF_PATCH_DELETE
})
1810 CRegStdDWORD(L
"Software\\TortoiseGit\\DarkUDiffForeAddedColor", UDIFF_COLORFOREADDED_DARK
),
1811 CRegStdDWORD(L
"Software\\TortoiseGit\\DarkUDiffBackAddedColor", UDIFF_COLORBACKADDED_DARK
));
1813 for (int style
: { SCE_DIFF_DELETED
, SCE_DIFF_REMOVED_PATCH_ADD
, SCE_DIFF_REMOVED_PATCH_DELETE
})
1816 CRegStdDWORD(L
"Software\\TortoiseGit\\DarkUDiffForeRemovedColor", UDIFF_COLORFOREREMOVED_DARK
),
1817 CRegStdDWORD(L
"Software\\TortoiseGit\\DarkUDiffBackRemovedColor", UDIFF_COLORBACKREMOVED_DARK
));
1822 Call(SCI_STYLESETFORE
, STYLE_DEFAULT
, ::GetSysColor(COLOR_WINDOWTEXT
));
1823 Call(SCI_STYLESETBACK
, STYLE_DEFAULT
, ::GetSysColor(COLOR_WINDOW
));
1824 Call(SCI_SETSELFORE
, TRUE
, ::GetSysColor(COLOR_HIGHLIGHTTEXT
));
1825 Call(SCI_SETSELBACK
, TRUE
, ::GetSysColor(COLOR_HIGHLIGHT
));
1826 Call(SCI_SETCARETFORE
, ::GetSysColor(COLOR_WINDOWTEXT
));
1827 Call(SCI_SETWHITESPACEFORE
, true, ::GetSysColor(COLOR_3DSHADOW
));
1828 SetAStyle(STYLE_DEFAULT
, ::GetSysColor(COLOR_WINDOWTEXT
), ::GetSysColor(COLOR_WINDOW
),
1829 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffFontSize", 10),
1830 CUnicodeUtils::StdGetUTF8(CRegStdString(L
"Software\\TortoiseGit\\UDiffFontName", L
"Consolas")).c_str());
1831 SetAStyle(SCE_DIFF_DEFAULT
, ::GetSysColor(COLOR_WINDOWTEXT
), ::GetSysColor(COLOR_WINDOW
),
1832 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffFontSize", 10),
1833 CUnicodeUtils::StdGetUTF8(CRegStdString(L
"Software\\TortoiseGit\\UDiffFontName", L
"Consolas")).c_str());
1834 Call(SCI_STYLECLEARALL
);
1835 SetAStyle(SCE_DIFF_COMMAND
,
1836 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffForeCommandColor", UDIFF_COLORFORECOMMAND
),
1837 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffBackCommandColor", UDIFF_COLORBACKCOMMAND
));
1838 SetAStyle(SCE_DIFF_POSITION
,
1839 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffForePositionColor", UDIFF_COLORFOREPOSITION
),
1840 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffBackPositionColor", UDIFF_COLORBACKPOSITION
));
1841 SetAStyle(SCE_DIFF_HEADER
,
1842 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffForeHeaderColor", UDIFF_COLORFOREHEADER
),
1843 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffBackHeaderColor", UDIFF_COLORBACKHEADER
));
1844 SetAStyle(SCE_DIFF_COMMENT
,
1845 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffForeCommentColor", UDIFF_COLORFORECOMMENT
),
1846 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffBackCommentColor", UDIFF_COLORBACKCOMMENT
));
1847 for (int style
: { SCE_DIFF_ADDED
, SCE_DIFF_PATCH_ADD
, SCE_DIFF_PATCH_DELETE
})
1850 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffForeAddedColor", UDIFF_COLORFOREADDED
),
1851 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffBackAddedColor", UDIFF_COLORBACKADDED
));
1853 for (int style
: { SCE_DIFF_DELETED
, SCE_DIFF_REMOVED_PATCH_ADD
, SCE_DIFF_REMOVED_PATCH_DELETE
})
1856 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffForeRemovedColor", UDIFF_COLORFOREREMOVED
),
1857 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffBackRemovedColor", UDIFF_COLORBACKREMOVED
));
1860 Call(SCI_SETFOLDMARGINCOLOUR
, true, RGB(240, 240, 240));
1861 Call(SCI_SETFOLDMARGINHICOLOUR
, true, RGB(255, 255, 255));
1862 Call(SCI_STYLESETFORE
, STYLE_LINENUMBER
, RGB(109, 109, 109));
1863 Call(SCI_STYLESETBACK
, STYLE_LINENUMBER
, RGB(230, 230, 230));
1865 Call(SCI_STYLESETFORE
, STYLE_BRACELIGHT
, RGB(0, 150, 0));
1866 Call(SCI_STYLESETBOLD
, STYLE_BRACELIGHT
, 1);
1867 Call(SCI_STYLESETFORE
, STYLE_BRACEBAD
, RGB(255, 0, 0));
1868 Call(SCI_STYLESETBOLD
, STYLE_BRACEBAD
, 1);
1869 if (CTheme::Instance().IsDarkTheme() || CTheme::Instance().IsHighContrastModeDark())
1871 Call(SCI_SETFOLDMARGINCOLOUR
, true, UDiffTextColorDark
);
1872 Call(SCI_SETFOLDMARGINHICOLOUR
, true, CTheme::darkBkColor
);
1873 Call(SCI_STYLESETFORE
, STYLE_LINENUMBER
, RGB(140, 140, 140));
1874 Call(SCI_STYLESETBACK
, STYLE_LINENUMBER
, UDiffBackColorDark
);
1877 auto curlexer
= Call(SCI_GETLEXER
);
1878 if (CTheme::Instance().IsHighContrastMode() && curlexer
!= SCLEX_NULL
)
1880 Call(SCI_CLEARDOCUMENTSTYLE
, 0, 0);
1881 Call(SCI_SETILEXER
, reinterpret_cast<sptr_t
>(nullptr));
1882 Call(SCI_COLOURISE
, 0, -1);
1884 else if (!CTheme::Instance().IsHighContrastMode() && curlexer
!= SCLEX_DIFF
)
1886 Call(SCI_CLEARDOCUMENTSTYLE
, 0, 0);
1887 Call(SCI_STYLESETBOLD
, SCE_DIFF_COMMENT
, TRUE
);
1888 Call(SCI_SETILEXER
, 0, reinterpret_cast<sptr_t
>(CreateLexer("diff")));
1889 Call(SCI_STYLESETBOLD
, SCE_DIFF_COMMENT
, TRUE
);
1890 Call(SCI_SETKEYWORDS
, 0, reinterpret_cast<LPARAM
>("revision"));
1891 Call(SCI_COLOURISE
, 0, -1);
1895 int CSciEdit::LoadFromFile(CString
&filename
)
1897 CAutoFILE fp
= _wfsopen(filename
, L
"rb", _SH_DENYWR
);
1901 auto readonly
= m_bReadOnly
;
1902 SCOPE_EXIT
{ SetReadOnly(readonly
); };
1908 char data
[4096] = { 0 };
1909 size_t lenFile
= fread(data
, 1, sizeof(data
), fp
);
1910 bool bUTF8
= IsUTF8(data
, lenFile
);
1913 Call(SCI_ADDTEXT
, lenFile
, reinterpret_cast<LPARAM
>(static_cast<char *>(data
)));
1914 lenFile
= fread(data
, 1, sizeof(data
), fp
);
1916 Call(SCI_SETCODEPAGE
, bUTF8
? SC_CP_UTF8
: GetACP());
1920 void CSciEdit::RestyleBugIDs()
1922 auto endstylepos
= static_cast<int>(Call(SCI_GETLENGTH
));
1924 Call(SCI_STARTSTYLING
, 0, STYLE_MASK
);
1925 Call(SCI_SETSTYLING
, endstylepos
, STYLE_DEFAULT
);
1926 // style the bug IDs
1927 MarkEnteredBugID(0, endstylepos
);
1930 ULONG
CSciEdit::GetGestureStatus(CPoint
/*ptTouch*/)
1935 BOOL
CSciEdit::EnableWindow(BOOL bEnable
/*= TRUE*/)
1937 auto ret
= __super::EnableWindow(bEnable
);
1942 void CSciEdit::SetReadOnly(bool bReadOnly
)
1944 m_bReadOnly
= bReadOnly
;
1945 Call(SCI_SETREADONLY
, m_bReadOnly
);
1948 void CSciEdit::ClearUndoBuffer()
1950 Call(SCI_EMPTYUNDOBUFFER
);
1951 Call(SCI_SETSAVEPOINT
);
1954 void CSciEdit::SetWindowStylesForAutocompletionPopup()
1956 if (CTheme::Instance().IsDarkTheme())
1958 EnumThreadWindows(GetCurrentThreadId(), AdjustThemeProc
, 0);
1962 BOOL
CSciEdit::AdjustThemeProc(HWND hwnd
, LPARAM
/*lParam*/)
1964 wchar_t szWndClassName
[MAX_PATH
] = { 0 };
1965 GetClassName(hwnd
, szWndClassName
, _countof(szWndClassName
));
1966 if ((wcscmp(szWndClassName
, L
"ListBoxX") == 0) ||
1967 (wcscmp(szWndClassName
, WC_LISTBOX
) == 0))
1969 // in dark mode, the resizing border is visible at the top
1970 // of the popup, and it's white and ugly.
1971 // this removes the border, but that also means that the
1972 // popup is not resizable anymore - which I think is not
1973 // really necessary anyway.
1974 auto dwCurStyle
= static_cast<DWORD
>(GetWindowLongPtr(hwnd
, GWL_STYLE
));
1975 dwCurStyle
&= ~WS_THICKFRAME
;
1976 dwCurStyle
|= WS_BORDER
;
1977 SetWindowLongPtr(hwnd
, GWL_STYLE
, dwCurStyle
);
1979 DarkModeHelper::Instance().AllowDarkModeForWindow(hwnd
, TRUE
);
1980 SetWindowTheme(hwnd
, L
"Explorer", nullptr);
1981 EnumChildWindows(hwnd
, AdjustThemeProc
, 0);