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