1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2012-2015 - TortoiseGit
4 // Copyright (C) 2003-2008,2012-2015 - TortoiseSVN
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include "LoglistCommonResource.h"
22 #include "PathUtils.h"
23 #include "UnicodeUtils.h"
28 #include "../../TortoiseUDiff/UDiffColors.h"
30 void CSciEditContextMenuInterface::InsertMenuItems(CMenu
&, int&) {return;}
31 bool CSciEditContextMenuInterface::HandleMenuItemClick(int, CSciEdit
*) {return false;}
32 void CSciEditContextMenuInterface::HandleSnippet(int, const CString
&, CSciEdit
*) { return; }
35 #define STYLE_ISSUEBOLD 11
36 #define STYLE_ISSUEBOLDITALIC 12
38 #define STYLE_ITALIC 15
39 #define STYLE_UNDERLINED 16
41 #define INDIC_MISSPELLED 18
43 #define STYLE_MASK 0x1f
45 #define SCI_ADDWORD 2000
52 struct loc_map enc2locale
[] = {
53 {"28591","ISO8859-1"},
54 {"28592","ISO8859-2"},
55 {"28593","ISO8859-3"},
56 {"28594","ISO8859-4"},
57 {"28595","ISO8859-5"},
58 {"28596","ISO8859-6"},
59 {"28597","ISO8859-7"},
60 {"28598","ISO8859-8"},
61 {"28599","ISO8859-9"},
62 {"28605","ISO8859-15"},
65 {"1251","microsoft-cp1251"},
70 IMPLEMENT_DYNAMIC(CSciEdit
, CWnd
)
72 CSciEdit::CSciEdit(void) : m_DirectFunction(NULL
)
73 , m_DirectPointer(NULL
)
80 , m_nAutoCompleteMinChars(3)
82 m_hModule
= ::LoadLibrary(_T("SciLexer_tgit.dll"));
85 CSciEdit::~CSciEdit(void)
87 m_personalDict
.Save();
89 ::FreeLibrary(m_hModule
);
96 static LPBYTE
Icon2Image(HICON hIcon
)
102 if (!GetIconInfo(hIcon
, &iconInfo
))
106 if (!GetObject(iconInfo
.hbmColor
, sizeof(BITMAP
), &bm
))
109 int width
= bm
.bmWidth
;
110 int height
= bm
.bmHeight
;
111 int bytesPerScanLine
= (width
* 3 + 3) & 0xFFFFFFFC;
112 int size
= bytesPerScanLine
* height
;
113 BITMAPINFO infoheader
;
114 infoheader
.bmiHeader
.biSize
= sizeof(BITMAPINFOHEADER
);
115 infoheader
.bmiHeader
.biWidth
= width
;
116 infoheader
.bmiHeader
.biHeight
= height
;
117 infoheader
.bmiHeader
.biPlanes
= 1;
118 infoheader
.bmiHeader
.biBitCount
= 24;
119 infoheader
.bmiHeader
.biCompression
= BI_RGB
;
120 infoheader
.bmiHeader
.biSizeImage
= size
;
122 std::unique_ptr
<BYTE
> ptrb(new BYTE
[(size
* 2 + height
* width
* 4)]);
123 LPBYTE pixelsIconRGB
= ptrb
.get();
124 LPBYTE alphaPixels
= pixelsIconRGB
+ size
;
125 HDC hDC
= CreateCompatibleDC(nullptr);
126 HBITMAP hBmpOld
= (HBITMAP
)SelectObject(hDC
, (HGDIOBJ
)iconInfo
.hbmColor
);
127 if (!GetDIBits(hDC
, iconInfo
.hbmColor
, 0, height
, (LPVOID
)pixelsIconRGB
, &infoheader
, DIB_RGB_COLORS
))
133 SelectObject(hDC
, hBmpOld
);
134 if (!GetDIBits(hDC
, iconInfo
.hbmMask
, 0,height
, (LPVOID
)alphaPixels
, &infoheader
, DIB_RGB_COLORS
))
141 UINT
* imagePixels
= new UINT
[height
* width
];
142 int lsSrc
= width
* 3;
143 int vsDest
= height
- 1;
144 for (int y
= 0; y
< height
; y
++)
146 int linePosSrc
= (vsDest
- y
) * lsSrc
;
147 int linePosDest
= y
* width
;
148 for (int x
= 0; x
< width
; x
++)
150 int currentDestPos
= linePosDest
+ x
;
151 int currentSrcPos
= linePosSrc
+ x
* 3;
152 imagePixels
[currentDestPos
] = (((UINT
)(
154 ((pixelsIconRGB
[currentSrcPos
+ 2] /*Red*/)
155 | (pixelsIconRGB
[currentSrcPos
+ 1] << 8 /*Green*/))
156 | pixelsIconRGB
[currentSrcPos
] << 16 /*Blue*/
158 | ((alphaPixels
[currentSrcPos
] ? 0 : 0xff) << 24))) & 0xffffffff);
161 return (LPBYTE
)imagePixels
;
164 void CSciEdit::Init(LONG lLanguage
, BOOL bLoadSpellCheck
)
166 //Setup the direct access data
167 m_DirectFunction
= SendMessage(SCI_GETDIRECTFUNCTION
, 0, 0);
168 m_DirectPointer
= SendMessage(SCI_GETDIRECTPOINTER
, 0, 0);
169 Call(SCI_SETMARGINWIDTHN
, 1, 0);
170 Call(SCI_SETUSETABS
, 0); //pressing TAB inserts spaces
171 Call(SCI_SETWRAPVISUALFLAGS
, SC_WRAPVISUALFLAG_END
);
172 Call(SCI_AUTOCSETIGNORECASE
, 1);
173 Call(SCI_SETLEXER
, SCLEX_CONTAINER
);
174 Call(SCI_SETCODEPAGE
, SC_CP_UTF8
);
175 Call(SCI_AUTOCSETFILLUPS
, 0, (LPARAM
)"\t([");
176 Call(SCI_AUTOCSETMAXWIDTH
, 0);
177 //Set the default windows colors for edit controls
178 Call(SCI_STYLESETFORE
, STYLE_DEFAULT
, ::GetSysColor(COLOR_WINDOWTEXT
));
179 Call(SCI_STYLESETBACK
, STYLE_DEFAULT
, ::GetSysColor(COLOR_WINDOW
));
180 Call(SCI_SETSELFORE
, TRUE
, ::GetSysColor(COLOR_HIGHLIGHTTEXT
));
181 Call(SCI_SETSELBACK
, TRUE
, ::GetSysColor(COLOR_HIGHLIGHT
));
182 Call(SCI_SETCARETFORE
, ::GetSysColor(COLOR_WINDOWTEXT
));
183 Call(SCI_SETMODEVENTMASK
, SC_MOD_INSERTTEXT
| SC_MOD_DELETETEXT
| SC_PERFORMED_UNDO
| SC_PERFORMED_REDO
);
184 Call(SCI_INDICSETSTYLE
, INDIC_MISSPELLED
, INDIC_SQUIGGLE
);
185 Call(SCI_INDICSETFORE
, INDIC_MISSPELLED
, RGB(255,0,0));
187 CStringA sWhiteSpace
;
188 for (int i
=0; i
<255; ++i
)
190 if (i
== '\r' || i
== '\n')
192 else if (i
< 0x20 || i
== ' ')
193 sWhiteSpace
+= (char)i
;
194 else if (isalnum(i
) || i
== '\'' || i
== '_' || i
== '-')
195 sWordChars
+= (char)i
;
197 Call(SCI_SETWORDCHARS
, 0, (LPARAM
)(LPCSTR
)sWordChars
);
198 Call(SCI_SETWHITESPACECHARS
, 0, (LPARAM
)(LPCSTR
)sWhiteSpace
);
199 m_bDoStyle
= ((DWORD
)CRegStdDWORD(_T("Software\\TortoiseGit\\StyleCommitMessages"), TRUE
))==TRUE
;
200 m_nAutoCompleteMinChars
= (int)(DWORD
)CRegStdDWORD(_T("Software\\TortoiseGit\\AutoCompleteMinChars"), 3);
201 // look for dictionary files and use them if found
202 long langId
= GetUserDefaultLCID();
206 if ((lLanguage
!= 0)||(((DWORD
)CRegStdDWORD(_T("Software\\TortoiseGit\\Spellchecker"), FALSE
))==FALSE
))
208 if (!((lLanguage
)&&(!LoadDictionaries(lLanguage
))))
212 LoadDictionaries(langId
);
213 DWORD lid
= SUBLANGID(langId
);
217 langId
= MAKELANGID(PRIMARYLANGID(langId
), lid
);
219 else if (langId
== 1033)
223 } while ((langId
)&&((pChecker
==NULL
)||(pThesaur
==NULL
)));
227 Call(SCI_SETEDGEMODE
, EDGE_NONE
);
228 Call(SCI_SETWRAPMODE
, SC_WRAP_WORD
);
229 Call(SCI_ASSIGNCMDKEY
, SCK_END
, SCI_LINEENDWRAP
);
230 Call(SCI_ASSIGNCMDKEY
, SCK_END
+ (SCMOD_SHIFT
<< 16), SCI_LINEENDWRAPEXTEND
);
231 Call(SCI_ASSIGNCMDKEY
, SCK_HOME
, SCI_HOMEWRAP
);
232 Call(SCI_ASSIGNCMDKEY
, SCK_HOME
+ (SCMOD_SHIFT
<< 16), SCI_HOMEWRAPEXTEND
);
233 CRegStdDWORD
used2d(L
"Software\\TortoiseGit\\ScintillaDirect2D", FALSE
);
234 if (SysInfo::Instance().IsWin7OrLater() && DWORD(used2d
))
236 Call(SCI_SETTECHNOLOGY
, SC_TECHNOLOGY_DIRECTWRITERETAIN
);
237 Call(SCI_SETBUFFEREDDRAW
, 0);
242 void CSciEdit::Init(const ProjectProperties
& props
)
244 Init(props
.lProjectLanguage
);
245 m_sCommand
= CStringA(CUnicodeUtils::GetUTF8(props
.GetCheckRe()));
246 m_sBugID
= CStringA(CUnicodeUtils::GetUTF8(props
.GetBugIDRe()));
247 m_sUrl
= CStringA(CUnicodeUtils::GetUTF8(props
.sUrl
));
249 Call(SCI_SETMOUSEDWELLTIME
, 333);
251 if (props
.nLogWidthMarker
)
253 Call(SCI_SETWRAPMODE
, SC_WRAP_NONE
);
254 Call(SCI_SETEDGEMODE
, EDGE_LINE
);
255 Call(SCI_SETEDGECOLUMN
, props
.nLogWidthMarker
);
259 Call(SCI_SETEDGEMODE
, EDGE_NONE
);
260 Call(SCI_SETWRAPMODE
, SC_WRAP_WORD
);
264 void CSciEdit::SetIcon(const std::map
<int, UINT
> &icons
)
266 Call(SCI_RGBAIMAGESETWIDTH
, 16);
267 Call(SCI_RGBAIMAGESETHEIGHT
, 16);
268 for (auto icon
: icons
)
270 auto hIcon
= (HICON
)::LoadImage(AfxGetInstanceHandle(), MAKEINTRESOURCE(icon
.second
), IMAGE_ICON
, 16, 16, LR_DEFAULTCOLOR
);
271 std::unique_ptr
<BYTE
> bytes(Icon2Image(hIcon
));
273 Call(SCI_REGISTERRGBAIMAGE
, icon
.first
, (LPARAM
)bytes
.get());
277 BOOL
CSciEdit::LoadDictionaries(LONG lLanguageID
)
279 //Setup the spell checker and thesaurus
280 TCHAR buf
[6] = { 0 };
281 CString sFolder
= CPathUtils::GetAppDirectory();
282 CString sFolderUp
= CPathUtils::GetAppParentDirectory();
283 CString sFolderAppData
= CPathUtils::GetAppDataDirectory();
286 GetLocaleInfo(MAKELCID(lLanguageID
, SORT_DEFAULT
), LOCALE_SISO639LANGNAME
, buf
, _countof(buf
));
288 if (lLanguageID
== 2074)
289 sFile
+= _T("-Latn");
291 GetLocaleInfo(MAKELCID(lLanguageID
, SORT_DEFAULT
), LOCALE_SISO3166CTRYNAME
, buf
, _countof(buf
));
295 if ((PathFileExists(sFolderAppData
+ _T("dic\\") + sFile
+ _T(".aff"))) &&
296 (PathFileExists(sFolderAppData
+ _T("dic\\") + sFile
+ _T(".dic"))))
298 pChecker
= new Hunspell(CStringA(sFolderAppData
+ _T("dic\\") + sFile
+ _T(".aff")), CStringA(sFolderAppData
+ _T("dic\\") + sFile
+ _T(".dic")));
300 else if ((PathFileExists(sFolder
+ sFile
+ _T(".aff"))) &&
301 (PathFileExists(sFolder
+ sFile
+ _T(".dic"))))
303 pChecker
= new Hunspell(CStringA(sFolder
+ sFile
+ _T(".aff")), CStringA(sFolder
+ sFile
+ _T(".dic")));
305 else if ((PathFileExists(sFolder
+ _T("dic\\") + sFile
+ _T(".aff"))) &&
306 (PathFileExists(sFolder
+ _T("dic\\") + sFile
+ _T(".dic"))))
308 pChecker
= new Hunspell(CStringA(sFolder
+ _T("dic\\") + sFile
+ _T(".aff")), CStringA(sFolder
+ _T("dic\\") + sFile
+ _T(".dic")));
310 else if ((PathFileExists(sFolderUp
+ sFile
+ _T(".aff"))) &&
311 (PathFileExists(sFolderUp
+ sFile
+ _T(".dic"))))
313 pChecker
= new Hunspell(CStringA(sFolderUp
+ sFile
+ _T(".aff")), CStringA(sFolderUp
+ sFile
+ _T(".dic")));
315 else if ((PathFileExists(sFolderUp
+ _T("dic\\") + sFile
+ _T(".aff"))) &&
316 (PathFileExists(sFolderUp
+ _T("dic\\") + sFile
+ _T(".dic"))))
318 pChecker
= new Hunspell(CStringA(sFolderUp
+ _T("dic\\") + sFile
+ _T(".aff")), CStringA(sFolderUp
+ _T("dic\\") + sFile
+ _T(".dic")));
320 else if ((PathFileExists(sFolderUp
+ _T("Languages\\") + sFile
+ _T(".aff"))) &&
321 (PathFileExists(sFolderUp
+ _T("Languages\\") + sFile
+ _T(".dic"))))
323 pChecker
= new Hunspell(CStringA(sFolderUp
+ _T("Languages\\") + sFile
+ _T(".aff")), CStringA(sFolderUp
+ _T("Languages\\") + sFile
+ _T(".dic")));
329 if ((PathFileExists(sFolderAppData
+ _T("dic\\th_") + sFile
+ _T("_v2.idx"))) &&
330 (PathFileExists(sFolderAppData
+ _T("dic\\th_") + sFile
+ _T("_v2.dat"))))
332 pThesaur
= new MyThes(CStringA(sFolderAppData
+ _T("dic\\th_") + sFile
+ _T("_v2.idx")), CStringA(sFolderAppData
+ _T("dic\\th_") + sFile
+ _T("_v2.dat")));
334 else if ((PathFileExists(sFolder
+ _T("th_") + sFile
+ _T("_v2.idx"))) &&
335 (PathFileExists(sFolder
+ _T("th_") + sFile
+ _T("_v2.dat"))))
337 pThesaur
= new MyThes(CStringA(sFolder
+ sFile
+ _T("_v2.idx")), CStringA(sFolder
+ sFile
+ _T("_v2.dat")));
339 else if ((PathFileExists(sFolder
+ _T("dic\\th_") + sFile
+ _T("_v2.idx"))) &&
340 (PathFileExists(sFolder
+ _T("dic\\th_") + sFile
+ _T("_v2.dat"))))
342 pThesaur
= new MyThes(CStringA(sFolder
+ _T("dic\\") + sFile
+ _T("_v2.idx")), CStringA(sFolder
+ _T("dic\\") + sFile
+ _T("_v2.dat")));
344 else if ((PathFileExists(sFolderUp
+ _T("th_") + sFile
+ _T("_v2.idx"))) &&
345 (PathFileExists(sFolderUp
+ _T("th_") + sFile
+ _T("_v2.dat"))))
347 pThesaur
= new MyThes(CStringA(sFolderUp
+ _T("th_") + sFile
+ _T("_v2.idx")), CStringA(sFolderUp
+ _T("th_") + sFile
+ _T("_v2.dat")));
349 else if ((PathFileExists(sFolderUp
+ _T("dic\\th_") + sFile
+ _T("_v2.idx"))) &&
350 (PathFileExists(sFolderUp
+ _T("dic\\th_") + sFile
+ _T("_v2.dat"))))
352 pThesaur
= new MyThes(CStringA(sFolderUp
+ _T("dic\\th_") + sFile
+ _T("_v2.idx")), CStringA(sFolderUp
+ _T("dic\\th_") + sFile
+ _T("_v2.dat")));
354 else if ((PathFileExists(sFolderUp
+ _T("Languages\\th_") + sFile
+ _T("_v2.idx"))) &&
355 (PathFileExists(sFolderUp
+ _T("Languages\\th_") + sFile
+ _T("_v2.dat"))))
357 pThesaur
= new MyThes(CStringA(sFolderUp
+ _T("Languages\\th_") + sFile
+ _T("_v2.idx")), CStringA(sFolderUp
+ _T("Languages\\th_") + sFile
+ _T("_v2.dat")));
363 const char * encoding
= pChecker
->get_dic_encoding();
364 CTraceToOutputDebugString::Instance()(__FUNCTION__
": %s\n", (LPCTSTR
)encoding
);
365 int n
= _countof(enc2locale
);
367 for (int i
= 0; i
< n
; i
++)
369 if (strcmp(encoding
,enc2locale
[i
].def_enc
) == 0)
371 m_spellcodepage
= atoi(enc2locale
[i
].cp
);
374 m_personalDict
.Init(lLanguageID
);
376 if ((pThesaur
)||(pChecker
))
381 LRESULT
CSciEdit::Call(UINT message
, WPARAM wParam
, LPARAM lParam
)
383 ASSERT(::IsWindow(m_hWnd
)); //Window must be valid
384 ASSERT(m_DirectFunction
); //Direct function must be valid
385 return ((SciFnDirect
) m_DirectFunction
)(m_DirectPointer
, message
, wParam
, lParam
);
388 CString
CSciEdit::StringFromControl(const CStringA
& text
)
392 int codepage
= (int)Call(SCI_GETCODEPAGE
);
393 int reslen
= MultiByteToWideChar(codepage
, 0, text
, text
.GetLength(), 0, 0);
394 MultiByteToWideChar(codepage
, 0, text
, text
.GetLength(), sText
.GetBuffer(reslen
+1), reslen
+1);
395 sText
.ReleaseBuffer(reslen
);
402 CStringA
CSciEdit::StringForControl(const CString
& text
)
406 int codepage
= (int)SendMessage(SCI_GETCODEPAGE
);
407 int reslen
= WideCharToMultiByte(codepage
, 0, text
, text
.GetLength(), 0, 0, 0, 0);
408 WideCharToMultiByte(codepage
, 0, text
, text
.GetLength(), sTextA
.GetBuffer(reslen
), reslen
, 0, 0);
409 sTextA
.ReleaseBuffer(reslen
);
413 ATLTRACE("string length %d\n", sTextA
.GetLength());
417 void CSciEdit::SetText(const CString
& sText
)
419 CStringA sTextA
= StringForControl(sText
);
420 Call(SCI_SETTEXT
, 0, (LPARAM
)(LPCSTR
)sTextA
);
422 // Scintilla seems to have problems with strings that
423 // aren't terminated by a newline char. Once that char
424 // is there, it can be removed without problems.
425 // So we add here a newline, then remove it again.
426 Call(SCI_DOCUMENTEND
);
428 Call(SCI_DELETEBACK
);
431 void CSciEdit::InsertText(const CString
& sText
, bool bNewLine
)
433 CStringA sTextA
= StringForControl(sText
);
434 Call(SCI_REPLACESEL
, 0, (LPARAM
)(LPCSTR
)sTextA
);
436 Call(SCI_REPLACESEL
, 0, (LPARAM
)(LPCSTR
)"\n");
439 CString
CSciEdit::GetText()
441 LRESULT len
= Call(SCI_GETTEXT
, 0, 0);
443 Call(SCI_GETTEXT
, (WPARAM
)(len
+ 1), (LPARAM
)(LPCSTR
)sTextA
.GetBuffer((int)len
+ 1));
444 sTextA
.ReleaseBuffer();
445 return StringFromControl(sTextA
);
448 CString
CSciEdit::GetWordUnderCursor(bool bSelectWord
)
450 TEXTRANGEA textrange
;
451 int pos
= (int)Call(SCI_GETCURRENTPOS
);
452 textrange
.chrg
.cpMin
= (LONG
)Call(SCI_WORDSTARTPOSITION
, pos
, TRUE
);
453 if ((pos
== textrange
.chrg
.cpMin
)||(textrange
.chrg
.cpMin
< 0))
455 textrange
.chrg
.cpMax
= (LONG
)Call(SCI_WORDENDPOSITION
, textrange
.chrg
.cpMin
, TRUE
);
457 std::unique_ptr
<char[]> textbuffer(new char[textrange
.chrg
.cpMax
- textrange
.chrg
.cpMin
+ 1]);
458 textrange
.lpstrText
= textbuffer
.get();
459 Call(SCI_GETTEXTRANGE
, 0, (LPARAM
)&textrange
);
462 Call(SCI_SETSEL
, textrange
.chrg
.cpMin
, textrange
.chrg
.cpMax
);
464 CString sRet
= StringFromControl(textbuffer
.get());
468 void CSciEdit::SetFont(CString sFontName
, int iFontSizeInPoints
)
470 Call(SCI_STYLESETFONT
, STYLE_DEFAULT
, (LPARAM
)(LPCSTR
)CUnicodeUtils::GetUTF8(sFontName
).GetBuffer());
471 Call(SCI_STYLESETSIZE
, STYLE_DEFAULT
, iFontSizeInPoints
);
472 Call(SCI_STYLECLEARALL
);
474 LPARAM color
= (LPARAM
)GetSysColor(COLOR_HIGHLIGHT
);
475 // set the styles for the bug ID strings
476 Call(SCI_STYLESETBOLD
, STYLE_ISSUEBOLD
, (LPARAM
)TRUE
);
477 Call(SCI_STYLESETFORE
, STYLE_ISSUEBOLD
, color
);
478 Call(SCI_STYLESETBOLD
, STYLE_ISSUEBOLDITALIC
, (LPARAM
)TRUE
);
479 Call(SCI_STYLESETITALIC
, STYLE_ISSUEBOLDITALIC
, (LPARAM
)TRUE
);
480 Call(SCI_STYLESETFORE
, STYLE_ISSUEBOLDITALIC
, color
);
481 Call(SCI_STYLESETHOTSPOT
, STYLE_ISSUEBOLDITALIC
, (LPARAM
)TRUE
);
483 // set the formatted text styles
484 Call(SCI_STYLESETBOLD
, STYLE_BOLD
, (LPARAM
)TRUE
);
485 Call(SCI_STYLESETITALIC
, STYLE_ITALIC
, (LPARAM
)TRUE
);
486 Call(SCI_STYLESETUNDERLINE
, STYLE_UNDERLINED
, (LPARAM
)TRUE
);
488 // set the style for URLs
489 Call(SCI_STYLESETFORE
, STYLE_URL
, color
);
490 Call(SCI_STYLESETHOTSPOT
, STYLE_URL
, (LPARAM
)TRUE
);
492 Call(SCI_SETHOTSPOTACTIVEUNDERLINE
, (LPARAM
)TRUE
);
495 void CSciEdit::SetAutoCompletionList(const std::map
<CString
, int>& list
, TCHAR separator
, TCHAR typeSeparator
)
497 //copy the auto completion list.
499 //SK: instead of creating a copy of that list, we could accept a pointer
500 //to the list and use that instead. But then the caller would have to make
501 //sure that the list persists over the lifetime of the control!
504 m_separator
= separator
;
505 m_typeSeparator
= typeSeparator
;
508 BOOL
CSciEdit::IsMisspelled(const CString
& sWord
)
510 // convert the string from the control to the encoding of the spell checker module.
515 buf
= sWordA
.GetBuffer(sWord
.GetLength()*4 + 1);
516 int lengthIncTerminator
=
517 WideCharToMultiByte(m_spellcodepage
, 0, sWord
, -1, buf
, sWord
.GetLength()*4, NULL
, NULL
);
518 if (lengthIncTerminator
== 0)
519 return FALSE
; // converting to the codepage failed, assume word is spelled correctly
520 sWordA
.ReleaseBuffer(lengthIncTerminator
-1);
523 sWordA
= CStringA(sWord
);
524 sWordA
.Trim("\'\".,");
525 // words starting with a digit are treated as correctly spelled
526 if (_istdigit(sWord
.GetAt(0)))
528 // words in the personal dictionary are correct too
529 if (m_personalDict
.FindWord(sWord
))
532 // now we actually check the spelling...
533 if (!pChecker
->spell(sWordA
))
535 // the word is marked as misspelled, we now check whether the word
536 // is maybe a composite identifier
537 // a composite identifier consists of multiple words, with each word
538 // separated by a change in lower to uppercase letters
539 if (sWord
.GetLength() > 1)
543 while (wordend
< sWord
.GetLength())
545 while ((wordend
< sWord
.GetLength())&&(!_istupper(sWord
[wordend
])))
547 if ((wordstart
== 0)&&(wordend
== sWord
.GetLength()))
549 // words in the auto list are also assumed correctly spelled
550 if (m_autolist
.find(sWord
) != m_autolist
.end())
554 sWordA
= CStringA(sWord
.Mid(wordstart
, wordend
-wordstart
));
555 if ((sWordA
.GetLength() > 2)&&(!pChecker
->spell(sWordA
)))
567 void CSciEdit::CheckSpelling(int startpos
, int endpos
)
569 if (pChecker
== NULL
)
572 TEXTRANGEA textrange
;
573 textrange
.chrg
.cpMin
= startpos
;
574 textrange
.chrg
.cpMax
= (LONG
)textrange
.chrg
.cpMin
;
575 LRESULT lastpos
= endpos
;
577 lastpos
= Call(SCI_GETLENGTH
)-textrange
.chrg
.cpMin
;
578 Call(SCI_SETINDICATORCURRENT
, INDIC_MISSPELLED
);
579 while (textrange
.chrg
.cpMax
< lastpos
)
581 textrange
.chrg
.cpMin
= (LONG
)Call(SCI_WORDSTARTPOSITION
, textrange
.chrg
.cpMax
+1, TRUE
);
582 if (textrange
.chrg
.cpMin
< textrange
.chrg
.cpMax
)
584 textrange
.chrg
.cpMax
= (LONG
)Call(SCI_WORDENDPOSITION
, textrange
.chrg
.cpMin
, TRUE
);
585 if (textrange
.chrg
.cpMin
== textrange
.chrg
.cpMax
)
587 textrange
.chrg
.cpMax
++;
588 // since Scintilla squiggles to the end of the text even if told to stop one char before it,
589 // we have to clear here the squiggly lines to the end.
590 if (textrange
.chrg
.cpMin
)
591 Call(SCI_INDICATORCLEARRANGE
, textrange
.chrg
.cpMin
-1, textrange
.chrg
.cpMax
- textrange
.chrg
.cpMin
+ 1);
594 ATLASSERT(textrange
.chrg
.cpMax
>= textrange
.chrg
.cpMin
);
595 std::unique_ptr
<char[]> textbuffer(new char[textrange
.chrg
.cpMax
- textrange
.chrg
.cpMin
+ 2]);
596 SecureZeroMemory(textbuffer
.get(), textrange
.chrg
.cpMax
- textrange
.chrg
.cpMin
+ 2);
597 textrange
.lpstrText
= textbuffer
.get();
598 textrange
.chrg
.cpMax
++;
599 Call(SCI_GETTEXTRANGE
, 0, (LPARAM
)&textrange
);
600 int len
= (int)strlen(textrange
.lpstrText
);
603 textrange
.chrg
.cpMax
--;
604 Call(SCI_GETTEXTRANGE
, 0, (LPARAM
)&textrange
);
605 len
= (int)strlen(textrange
.lpstrText
);
606 textrange
.chrg
.cpMax
++;
609 if (len
&& textrange
.lpstrText
[len
- 1] == '.')
611 // Try to ignore file names from the auto list.
612 // Do do this, for each word ending with '.' we extract next word and check
613 // whether the combined string is present in auto list.
615 twoWords
.chrg
.cpMin
= textrange
.chrg
.cpMin
;
616 twoWords
.chrg
.cpMax
= (LONG
)Call(SCI_WORDENDPOSITION
, textrange
.chrg
.cpMax
+ 1, TRUE
);
617 std::unique_ptr
<char[]> twoWordsBuffer(new char[twoWords
.chrg
.cpMax
- twoWords
.chrg
.cpMin
+ 1]);
618 twoWords
.lpstrText
= twoWordsBuffer
.get();
619 SecureZeroMemory(twoWords
.lpstrText
, twoWords
.chrg
.cpMax
- twoWords
.chrg
.cpMin
+ 1);
620 Call(SCI_GETTEXTRANGE
, 0, (LPARAM
)&twoWords
);
621 CString sWord
= StringFromControl(twoWords
.lpstrText
);
622 if (m_autolist
.find(sWord
) != m_autolist
.end())
624 //mark word as correct (remove the squiggle line)
625 Call(SCI_INDICATORCLEARRANGE
, twoWords
.chrg
.cpMin
, twoWords
.chrg
.cpMax
- twoWords
.chrg
.cpMin
);
626 textrange
.chrg
.cpMax
= twoWords
.chrg
.cpMax
;
631 textrange
.lpstrText
[len
- 1] = 0;
632 textrange
.chrg
.cpMax
--;
633 if (strlen(textrange
.lpstrText
) > 0)
635 CString sWord
= StringFromControl(textrange
.lpstrText
);
636 if ((GetStyleAt(textrange
.chrg
.cpMin
) != STYLE_URL
) && IsMisspelled(sWord
))
638 //mark word as misspelled
639 Call(SCI_INDICATORFILLRANGE
, textrange
.chrg
.cpMin
, textrange
.chrg
.cpMax
- textrange
.chrg
.cpMin
);
643 //mark word as correct (remove the squiggle line)
644 Call(SCI_INDICATORCLEARRANGE
, textrange
.chrg
.cpMin
, textrange
.chrg
.cpMax
- textrange
.chrg
.cpMin
);
645 Call(SCI_INDICATORCLEARRANGE
, textrange
.chrg
.cpMin
, textrange
.chrg
.cpMax
- textrange
.chrg
.cpMin
+ 1);
651 void CSciEdit::SuggestSpellingAlternatives()
653 if (pChecker
== NULL
)
655 CString word
= GetWordUnderCursor(true);
656 Call(SCI_SETCURRENTPOS
, Call(SCI_WORDSTARTPOSITION
, Call(SCI_GETCURRENTPOS
), TRUE
));
659 char ** wlst
= nullptr;
660 int ns
= pChecker
->suggest(&wlst
, CStringA(word
));
664 for (int i
=0; i
< ns
; i
++)
666 suggestions
.AppendFormat(_T("%s%c%d%c"), (LPCTSTR
)CString(wlst
[i
]), m_typeSeparator
, AUTOCOMPLETE_SPELLING
, m_separator
);
670 suggestions
.TrimRight(m_separator
);
671 if (suggestions
.IsEmpty())
673 Call(SCI_AUTOCSETSEPARATOR
, (WPARAM
)CStringA(m_separator
).GetAt(0));
674 Call(SCI_AUTOCSETTYPESEPARATOR
, (WPARAM
)m_typeSeparator
);
675 Call(SCI_AUTOCSETDROPRESTOFWORD
, 1);
676 Call(SCI_AUTOCSHOW
, 0, (LPARAM
)(LPCSTR
)StringForControl(suggestions
));
682 void CSciEdit::DoAutoCompletion(int nMinPrefixLength
)
684 if (m_autolist
.empty())
686 if (Call(SCI_AUTOCACTIVE
))
688 CString word
= GetWordUnderCursor();
689 if (word
.GetLength() < nMinPrefixLength
)
690 return; //don't auto complete yet, word is too short
691 int pos
= (int)Call(SCI_GETCURRENTPOS
);
692 if (pos
!= Call(SCI_WORDENDPOSITION
, pos
, TRUE
))
693 return; //don't auto complete if we're not at the end of a word
694 CString sAutoCompleteList
;
696 std::vector
<CString
> words
;
698 pos
= word
.Find('-');
700 CString wordLower
= word
;
701 wordLower
.MakeLower();
702 CString wordHigher
= word
;
703 wordHigher
.MakeUpper();
705 words
.push_back(wordLower
);
706 words
.push_back(wordHigher
);
710 CString s
= wordLower
.Left(pos
);
711 if (s
.GetLength() >= nMinPrefixLength
)
713 s
= wordLower
.Mid(pos
+1);
714 if (s
.GetLength() >= nMinPrefixLength
)
716 s
= wordHigher
.Left(pos
);
717 if (s
.GetLength() >= nMinPrefixLength
)
718 words
.push_back(wordHigher
.Left(pos
));
719 s
= wordHigher
.Mid(pos
+1);
720 if (s
.GetLength() >= nMinPrefixLength
)
721 words
.push_back(wordHigher
.Mid(pos
+1));
724 std::map
<CString
, int> wordset
;
725 for (const auto& w
: words
)
727 for (auto lowerit
= m_autolist
.lower_bound(w
);
728 lowerit
!= m_autolist
.end(); ++lowerit
)
730 int compare
= w
.CompareNoCase(lowerit
->first
.Left(w
.GetLength()));
733 else if (compare
== 0)
735 wordset
.insert(std::make_pair(lowerit
->first
, lowerit
->second
));
744 for (const auto& w
: wordset
)
745 sAutoCompleteList
.AppendFormat(_T("%s%c%d%c"), (LPCTSTR
)w
.first
, m_typeSeparator
, w
.second
, m_separator
);
747 sAutoCompleteList
.TrimRight(m_separator
);
748 if (sAutoCompleteList
.IsEmpty())
751 Call(SCI_AUTOCSETSEPARATOR
, (WPARAM
)CStringA(m_separator
).GetAt(0));
752 Call(SCI_AUTOCSETTYPESEPARATOR
, (WPARAM
)m_typeSeparator
);
753 Call(SCI_AUTOCSHOW
, word
.GetLength(), (LPARAM
)(LPCSTR
)StringForControl(sAutoCompleteList
));
756 BOOL
CSciEdit::OnChildNotify(UINT message
, WPARAM wParam
, LPARAM lParam
, LRESULT
* pLResult
)
758 if (message
!= WM_NOTIFY
)
759 return CWnd::OnChildNotify(message
, wParam
, lParam
, pLResult
);
761 LPNMHDR lpnmhdr
= (LPNMHDR
) lParam
;
762 SCNotification
* lpSCN
= (SCNotification
*)lParam
;
764 if(lpnmhdr
->hwndFrom
==m_hWnd
)
766 switch(lpnmhdr
->code
)
770 if ((lpSCN
->ch
< 32)&&(lpSCN
->ch
!= 13)&&(lpSCN
->ch
!= 10))
771 Call(SCI_DELETEBACK
);
774 DoAutoCompletion(m_nAutoCompleteMinChars
);
779 case SCN_AUTOCSELECTION
:
781 CString text
= StringFromControl(lpSCN
->text
);
782 if (m_autolist
[text
] == AUTOCOMPLETE_SNIPPET
)
784 Call(SCI_AUTOCCANCEL
);
785 for (INT_PTR handlerindex
= 0; handlerindex
< m_arContextHandlers
.GetCount(); ++handlerindex
)
787 CSciEditContextMenuInterface
* pHandler
= m_arContextHandlers
.GetAt(handlerindex
);
788 pHandler
->HandleSnippet(m_autolist
[text
], text
, this);
795 if (lpSCN
->modificationType
& (SC_MOD_INSERTTEXT
| SC_MOD_DELETETEXT
))
797 LRESULT firstline
= Call(SCI_GETFIRSTVISIBLELINE
);
798 LRESULT lastline
= firstline
+ Call(SCI_LINESONSCREEN
);
799 int firstpos
= (int)Call(SCI_POSITIONFROMLINE
, firstline
);
800 int lastpos
= (int)Call(SCI_GETLINEENDPOSITION
, lastline
);
801 int pos1
= lpSCN
->position
;
802 int pos2
= pos1
+ lpSCN
->length
;
803 // always use the bigger range
804 firstpos
= min(firstpos
, pos1
);
805 lastpos
= max(lastpos
, pos2
);
806 MarkEnteredBugID(firstpos
, lastpos
);
808 StyleEnteredText(firstpos
, lastpos
);
810 int startpos
= (int)Call(SCI_WORDSTARTPOSITION
, firstpos
, true);
811 int endpos
= (int)Call(SCI_WORDENDPOSITION
, lastpos
, true);
812 StyleURLs(startpos
, endpos
);
813 CheckSpelling(startpos
, endpos
);
814 WrapLines(firstpos
, lastpos
);
815 Call(SCI_COLOURISE
, startpos
, endpos
);
820 case SCN_HOTSPOTCLICK
:
822 TEXTRANGEA textrange
;
823 textrange
.chrg
.cpMin
= lpSCN
->position
;
824 textrange
.chrg
.cpMax
= lpSCN
->position
;
825 DWORD style
= GetStyleAt(lpSCN
->position
);
826 if (style
!= STYLE_ISSUEBOLDITALIC
&& style
!= STYLE_URL
)
828 while (GetStyleAt(textrange
.chrg
.cpMin
- 1) == style
)
829 --textrange
.chrg
.cpMin
;
830 while (GetStyleAt(textrange
.chrg
.cpMax
+ 1) == style
)
831 ++textrange
.chrg
.cpMax
;
832 ++textrange
.chrg
.cpMax
;
833 std::unique_ptr
<char[]> textbuffer(new char[textrange
.chrg
.cpMax
- textrange
.chrg
.cpMin
+ 1]);
834 textrange
.lpstrText
= textbuffer
.get();
835 Call(SCI_GETTEXTRANGE
, 0, (LPARAM
)&textrange
);
837 if (style
== STYLE_URL
)
838 url
= StringFromControl(textbuffer
.get());
842 url
.Replace(L
"%BUGID%", StringFromControl(textbuffer
.get()));
846 if (lpnmhdr
->code
== SCN_HOTSPOTCLICK
)
847 ShellExecute(GetParent()->GetSafeHwnd(), _T("open"), url
, NULL
, NULL
, SW_SHOWDEFAULT
);
850 CStringA sTextA
= StringForControl(url
);
851 Call(SCI_CALLTIPSHOW
, lpSCN
->position
+ 3, (LPARAM
)(LPCSTR
)sTextA
);
857 Call(SCI_CALLTIPCANCEL
);
861 return CWnd::OnChildNotify(message
, wParam
, lParam
, pLResult
);
864 BEGIN_MESSAGE_MAP(CSciEdit
, CWnd
)
869 void CSciEdit::OnKeyDown(UINT nChar
, UINT nRepCnt
, UINT nFlags
)
875 if ((Call(SCI_AUTOCACTIVE
)==0)&&(Call(SCI_CALLTIPACTIVE
)==0))
876 ::SendMessage(GetParent()->GetSafeHwnd(), WM_CLOSE
, 0, 0);
880 CWnd::OnKeyDown(nChar
, nRepCnt
, nFlags
);
883 BOOL
CSciEdit::PreTranslateMessage(MSG
* pMsg
)
885 if (pMsg
->message
== WM_KEYDOWN
)
887 switch (pMsg
->wParam
)
891 if (GetKeyState(VK_CONTROL
) & 0x8000)
899 // The TAB cannot be handled in OnKeyDown because it is too late by then.
901 if (GetKeyState(VK_CONTROL
)&0x8000)
903 //Ctrl-Tab was pressed, this means we should provide the user with
904 //a list of possible spell checking alternatives to the word under
906 SuggestSpellingAlternatives();
909 else if (!Call(SCI_AUTOCACTIVE
))
911 ::PostMessage(GetParent()->GetSafeHwnd(), WM_NEXTDLGCTL
, GetKeyState(VK_SHIFT
)&0x8000, 0);
918 return CWnd::PreTranslateMessage(pMsg
);
921 void CSciEdit::OnContextMenu(CWnd
* /*pWnd*/, CPoint point
)
923 int anchor
= (int)Call(SCI_GETANCHOR
);
924 int currentpos
= (int)Call(SCI_GETCURRENTPOS
);
925 int selstart
= (int)Call(SCI_GETSELECTIONSTART
);
926 int selend
= (int)Call(SCI_GETSELECTIONEND
);
928 if ((point
.x
== -1) && (point
.y
== -1))
931 GetClientRect(&rect
);
932 ClientToScreen(&rect
);
933 point
= rect
.CenterPoint();
934 pointpos
= (int)Call(SCI_GETCURRENTPOS
);
938 // change the cursor position to the point where the user
940 CPoint clientpoint
= point
;
941 ScreenToClient(&clientpoint
);
942 pointpos
= (int)Call(SCI_POSITIONFROMPOINT
, clientpoint
.x
, clientpoint
.y
);
944 CString sMenuItemText
;
946 bool bRestoreCursor
= true;
947 if (popup
.CreatePopupMenu())
949 bool bCanUndo
= !!Call(SCI_CANUNDO
);
950 bool bCanRedo
= !!Call(SCI_CANREDO
);
951 bool bHasSelection
= (selend
-selstart
> 0);
952 bool bCanPaste
= !!Call(SCI_CANPASTE
);
953 bool bIsReadOnly
= !!Call(SCI_GETREADONLY
);
954 UINT uEnabledMenu
= MF_STRING
| MF_ENABLED
;
955 UINT uDisabledMenu
= MF_STRING
| MF_GRAYED
;
957 // find the word under the cursor
961 // setting the cursor clears the selection
962 Call(SCI_SETANCHOR
, pointpos
);
963 Call(SCI_SETCURRENTPOS
, pointpos
);
964 sWord
= GetWordUnderCursor();
965 // restore the selection
966 Call(SCI_SETSELECTIONSTART
, selstart
);
967 Call(SCI_SETSELECTIONEND
, selend
);
970 sWord
= GetWordUnderCursor();
971 CStringA worda
= CStringA(sWord
);
973 int nCorrections
= 1;
974 bool bSpellAdded
= false;
975 // check if the word under the cursor is spelled wrong
976 if ((pChecker
)&&(!worda
.IsEmpty()) && !bIsReadOnly
)
978 char ** wlst
= nullptr;
979 // get the spell suggestions
980 int ns
= pChecker
->suggest(&wlst
,worda
);
983 // add the suggestions to the context menu
984 for (int i
=0; i
< ns
; i
++)
987 CString sug
= CString(wlst
[i
]);
988 popup
.InsertMenu((UINT
)-1, 0, nCorrections
++, sug
);
996 // only add a separator if spelling correction suggestions were added
998 popup
.AppendMenu(MF_SEPARATOR
);
1000 // also allow the user to add the word to the custom dictionary so
1001 // it won't show up as misspelled anymore
1002 if ((sWord
.GetLength()<PDICT_MAX_WORD_LENGTH
)&&((pChecker
)&&(m_autolist
.find(sWord
) == m_autolist
.end())&&(!pChecker
->spell(worda
)))&&
1003 (!_istdigit(sWord
.GetAt(0)))&&(!m_personalDict
.FindWord(sWord
)) && !bIsReadOnly
)
1005 sMenuItemText
.Format(IDS_SCIEDIT_ADDWORD
, (LPCTSTR
)sWord
);
1006 popup
.AppendMenu(uEnabledMenu
, SCI_ADDWORD
, sMenuItemText
);
1007 // another separator
1008 popup
.AppendMenu(MF_SEPARATOR
);
1011 // add the 'default' entries
1012 sMenuItemText
.LoadString(IDS_SCIEDIT_UNDO
);
1013 popup
.AppendMenu(bCanUndo
? uEnabledMenu
: uDisabledMenu
, SCI_UNDO
, sMenuItemText
);
1014 sMenuItemText
.LoadString(IDS_SCIEDIT_REDO
);
1015 popup
.AppendMenu(bCanRedo
? uEnabledMenu
: uDisabledMenu
, SCI_REDO
, sMenuItemText
);
1017 popup
.AppendMenu(MF_SEPARATOR
);
1019 sMenuItemText
.LoadString(IDS_SCIEDIT_CUT
);
1020 popup
.AppendMenu(!bIsReadOnly
&& bHasSelection
? uEnabledMenu
: uDisabledMenu
, SCI_CUT
, sMenuItemText
);
1021 sMenuItemText
.LoadString(IDS_SCIEDIT_COPY
);
1022 popup
.AppendMenu(bHasSelection
? uEnabledMenu
: uDisabledMenu
, SCI_COPY
, sMenuItemText
);
1023 sMenuItemText
.LoadString(IDS_SCIEDIT_PASTE
);
1024 popup
.AppendMenu(bCanPaste
? uEnabledMenu
: uDisabledMenu
, SCI_PASTE
, sMenuItemText
);
1026 popup
.AppendMenu(MF_SEPARATOR
);
1028 sMenuItemText
.LoadString(IDS_SCIEDIT_SELECTALL
);
1029 popup
.AppendMenu(uEnabledMenu
, SCI_SELECTALL
, sMenuItemText
);
1031 popup
.AppendMenu(MF_SEPARATOR
);
1033 sMenuItemText
.LoadString(IDS_SCIEDIT_SPLITLINES
);
1034 popup
.AppendMenu(bHasSelection
? uEnabledMenu
: uDisabledMenu
, SCI_LINESSPLIT
, sMenuItemText
);
1036 if (m_arContextHandlers
.GetCount() > 0)
1037 popup
.AppendMenu(MF_SEPARATOR
);
1039 int nCustoms
= nCorrections
;
1040 // now add any custom context menus
1041 for (INT_PTR handlerindex
= 0; handlerindex
< m_arContextHandlers
.GetCount(); ++handlerindex
)
1043 CSciEditContextMenuInterface
* pHandler
= m_arContextHandlers
.GetAt(handlerindex
);
1044 pHandler
->InsertMenuItems(popup
, nCustoms
);
1047 if (nCustoms
> nCorrections
)
1049 // custom menu entries present, so add another separator
1050 popup
.AppendMenu(MF_SEPARATOR
);
1053 // add found thesauri to sub menu's
1056 CPtrArray menuArray
;
1057 if (thesaurs
.CreatePopupMenu())
1059 if ((pThesaur
)&&(!worda
.IsEmpty()))
1063 int count
= pThesaur
->Lookup(worda
, worda
.GetLength(),&pmean
);
1066 mentry
* pm
= pmean
;
1067 for (int i
=0; i
< count
; i
++)
1069 CMenu
* submenu
= new CMenu();
1070 menuArray
.Add(submenu
);
1071 submenu
->CreateMenu();
1072 for (int j
=0; j
< pm
->count
; j
++)
1074 CString sug
= CString(pm
->psyns
[j
]);
1075 submenu
->InsertMenu((UINT
)-1, 0, nCorrections
+ nCustoms
+ (nThesaurs
++), sug
);
1077 thesaurs
.InsertMenu((UINT
)-1, MF_POPUP
, (UINT_PTR
)(submenu
->m_hMenu
), CString(pm
->defn
));
1081 if ((count
> 0)&&(point
.x
>= 0))
1083 #ifdef IDS_SPELLEDIT_THESAURUS
1084 sMenuItemText
.LoadString(IDS_SPELLEDIT_THESAURUS
);
1085 popup
.InsertMenu((UINT
)-1, MF_POPUP
, (UINT_PTR
)thesaurs
.m_hMenu
, sMenuItemText
);
1087 popup
.InsertMenu((UINT
)-1, MF_POPUP
, (UINT_PTR
)thesaurs
.m_hMenu
, _T("Thesaurus"));
1089 nThesaurs
= nCustoms
;
1093 sMenuItemText
.LoadString(IDS_SPELLEDIT_NOTHESAURUS
);
1094 popup
.AppendMenu(MF_DISABLED
| MF_GRAYED
| MF_STRING
, 0, sMenuItemText
);
1097 pThesaur
->CleanUpAfterLookup(&pmean
, count
);
1101 sMenuItemText
.LoadString(IDS_SPELLEDIT_NOTHESAURUS
);
1102 popup
.AppendMenu(MF_DISABLED
| MF_GRAYED
| MF_STRING
, 0, sMenuItemText
);
1106 int cmd
= popup
.TrackPopupMenu(TPM_RETURNCMD
| TPM_LEFTALIGN
| TPM_NONOTIFY
, point
.x
, point
.y
, this, 0);
1110 break; // no command selected
1112 bRestoreCursor
= false;
1122 m_personalDict
.AddWord(sWord
);
1123 CheckSpelling((int)Call(SCI_POSITIONFROMLINE
, Call(SCI_GETFIRSTVISIBLELINE
)), (int)Call(SCI_POSITIONFROMLINE
, Call(SCI_GETFIRSTVISIBLELINE
) + Call(SCI_LINESONSCREEN
)));
1125 case SCI_LINESSPLIT
:
1127 int marker
= (int)(Call(SCI_GETEDGECOLUMN
) * Call(SCI_TEXTWIDTH
, 0, (LPARAM
)" "));
1130 Call(SCI_TARGETFROMSELECTION
);
1131 Call(SCI_LINESJOIN
);
1132 Call(SCI_LINESSPLIT
, marker
);
1137 if (cmd
< nCorrections
)
1139 Call(SCI_SETANCHOR
, pointpos
);
1140 Call(SCI_SETCURRENTPOS
, pointpos
);
1141 GetWordUnderCursor(true);
1143 popup
.GetMenuString(cmd
, temp
, 0);
1144 // setting the cursor clears the selection
1145 Call(SCI_REPLACESEL
, 0, (LPARAM
)(LPCSTR
)StringForControl(temp
));
1147 else if (cmd
< (nCorrections
+nCustoms
))
1149 for (INT_PTR handlerindex
= 0; handlerindex
< m_arContextHandlers
.GetCount(); ++handlerindex
)
1151 CSciEditContextMenuInterface
* pHandler
= m_arContextHandlers
.GetAt(handlerindex
);
1152 if (pHandler
->HandleMenuItemClick(cmd
, this))
1157 else if (cmd
<= (nThesaurs
+nCorrections
+nCustoms
))
1159 Call(SCI_SETANCHOR
, pointpos
);
1160 Call(SCI_SETCURRENTPOS
, pointpos
);
1161 GetWordUnderCursor(true);
1163 thesaurs
.GetMenuString(cmd
, temp
, 0);
1164 Call(SCI_REPLACESEL
, 0, (LPARAM
)(LPCSTR
)StringForControl(temp
));
1169 for (INT_PTR index
= 0; index
< menuArray
.GetCount(); ++index
)
1171 CMenu
* pMenu
= (CMenu
*)menuArray
[index
];
1178 // restore the anchor and cursor position
1179 Call(SCI_SETCURRENTPOS
, currentpos
);
1180 Call(SCI_SETANCHOR
, anchor
);
1184 bool CSciEdit::StyleEnteredText(int startstylepos
, int endstylepos
)
1186 bool bStyled
= false;
1187 const int line
= (int)Call(SCI_LINEFROMPOSITION
, startstylepos
);
1188 const int line_number_end
= (int)Call(SCI_LINEFROMPOSITION
, endstylepos
);
1189 for (int line_number
= line
; line_number
<= line_number_end
; ++line_number
)
1191 int offset
= (int)Call(SCI_POSITIONFROMLINE
, line_number
);
1192 int line_len
= (int)Call(SCI_LINELENGTH
, line_number
);
1193 std::unique_ptr
<char[]> linebuffer(new char[line_len
+1]);
1194 Call(SCI_GETLINE
, line_number
, (LPARAM
)linebuffer
.get());
1195 linebuffer
[line_len
] = 0;
1198 while (FindStyleChars(linebuffer
.get(), '*', start
, end
))
1200 Call(SCI_STARTSTYLING
, start
+ offset
, STYLE_BOLD
);
1201 Call(SCI_SETSTYLING
, end
-start
, STYLE_BOLD
);
1207 while (FindStyleChars(linebuffer
.get(), '^', start
, end
))
1209 Call(SCI_STARTSTYLING
, start
+ offset
, STYLE_ITALIC
);
1210 Call(SCI_SETSTYLING
, end
-start
, STYLE_ITALIC
);
1216 while (FindStyleChars(linebuffer
.get(), '_', start
, end
))
1218 Call(SCI_STARTSTYLING
, start
+ offset
, STYLE_UNDERLINED
);
1219 Call(SCI_SETSTYLING
, end
-start
, STYLE_UNDERLINED
);
1227 bool CSciEdit::WrapLines(int startpos
, int endpos
)
1229 int markerX
= (int)(Call(SCI_GETEDGECOLUMN
) * Call(SCI_TEXTWIDTH
, 0, (LPARAM
)" "));
1232 Call(SCI_SETTARGETSTART
, startpos
);
1233 Call(SCI_SETTARGETEND
, endpos
);
1234 Call(SCI_LINESSPLIT
, markerX
);
1240 void CSciEdit::AdvanceUTF8(const char * str
, int& pos
)
1242 if ((str
[pos
] & 0xE0)==0xC0)
1244 // utf8 2-byte sequence
1247 else if ((str
[pos
] & 0xF0)==0xE0)
1249 // utf8 3-byte sequence
1252 else if ((str
[pos
] & 0xF8)==0xF0)
1254 // utf8 4-byte sequence
1261 bool CSciEdit::FindStyleChars(const char * line
, char styler
, int& start
, int& end
)
1267 AdvanceUTF8(line
, i
);
1271 bool bFoundMarker
= false;
1272 CString sULine
= CUnicodeUtils::GetUnicode(line
);
1273 // find a starting marker
1274 while (line
[i
] != 0)
1276 if (line
[i
] == styler
)
1278 if ((line
[i
+1]!=0)&&(IsCharAlphaNumeric(sULine
[u
+1]))&&
1279 (((u
>0)&&(!IsCharAlphaNumeric(sULine
[u
-1]))) || (u
==0)))
1282 AdvanceUTF8(line
, i
);
1284 bFoundMarker
= true;
1288 AdvanceUTF8(line
, i
);
1293 // find ending marker
1294 bFoundMarker
= false;
1295 while (line
[i
] != 0)
1297 if (line
[i
] == styler
)
1299 if ((IsCharAlphaNumeric(sULine
[u
-1]))&&
1300 ((((u
+1)<sULine
.GetLength())&&(!IsCharAlphaNumeric(sULine
[u
+1]))) || ((u
+1) == sULine
.GetLength()))
1305 bFoundMarker
= true;
1309 AdvanceUTF8(line
, i
);
1312 return bFoundMarker
;
1315 BOOL
CSciEdit::MarkEnteredBugID(int startstylepos
, int endstylepos
)
1317 if (m_sCommand
.IsEmpty())
1319 // get the text between the start and end position we have to style
1320 const int line_number
= (int)Call(SCI_LINEFROMPOSITION
, startstylepos
);
1321 int start_pos
= (int)Call(SCI_POSITIONFROMLINE
, (WPARAM
)line_number
);
1322 int end_pos
= endstylepos
;
1324 if (start_pos
== end_pos
)
1326 if (start_pos
> end_pos
)
1328 int switchtemp
= start_pos
;
1329 start_pos
= end_pos
;
1330 end_pos
= switchtemp
;
1333 std::unique_ptr
<char[]> textbuffer(new char[end_pos
- start_pos
+ 2]);
1334 TEXTRANGEA textrange
;
1335 textrange
.lpstrText
= textbuffer
.get();
1336 textrange
.chrg
.cpMin
= start_pos
;
1337 textrange
.chrg
.cpMax
= end_pos
;
1338 Call(SCI_GETTEXTRANGE
, 0, (LPARAM
)&textrange
);
1339 CStringA msg
= CStringA(textbuffer
.get());
1341 Call(SCI_STARTSTYLING
, start_pos
, STYLE_MASK
);
1345 if (!m_sBugID
.IsEmpty())
1347 // match with two regex strings (without grouping!)
1348 const std::tr1::regex
regCheck(m_sCommand
);
1349 const std::tr1::regex
regBugID(m_sBugID
);
1350 const std::tr1::sregex_iterator end
;
1351 std::string s
= msg
;
1354 // if start_pos is 0, we're styling from the beginning and let the ^ char match the beginning of the line
1355 // that way, the ^ matches the very beginning of the log message and not the beginning of further lines.
1356 // problem is: this only works *while* entering log messages. If a log message is pasted in whole or
1357 // multiple lines are pasted, start_pos can be 0 and styling goes over multiple lines. In that case, those
1358 // additional line starts also match ^
1359 for (std::tr1::sregex_iterator
it(s
.begin(), s
.end(), regCheck
, start_pos
!= 0 ? std::tr1::regex_constants::match_not_bol
: std::tr1::regex_constants::match_default
); it
!= end
; ++it
)
1361 // clear the styles up to the match position
1362 Call(SCI_SETSTYLING
, it
->position(0)-pos
, STYLE_DEFAULT
);
1364 // (*it)[0] is the matched string
1365 std::string matchedString
= (*it
)[0];
1366 LONG matchedpos
= 0;
1367 for (std::tr1::sregex_iterator
it2(matchedString
.begin(), matchedString
.end(), regBugID
); it2
!= end
; ++it2
)
1369 ATLTRACE("matched id : %s\n", std::string((*it2
)[0]).c_str());
1371 // bold style up to the id match
1372 ATLTRACE("position = %ld\n", it2
->position(0));
1373 if (it2
->position(0))
1374 Call(SCI_SETSTYLING
, it2
->position(0) - matchedpos
, STYLE_ISSUEBOLD
);
1375 // bold and recursive style for the bug ID itself
1376 if ((*it2
)[0].str().size())
1377 Call(SCI_SETSTYLING
, (*it2
)[0].str().size(), STYLE_ISSUEBOLDITALIC
);
1378 matchedpos
= (LONG
)(it2
->position(0) + (*it2
)[0].str().size());
1380 if ((matchedpos
)&&(matchedpos
< (LONG
)matchedString
.size()))
1382 Call(SCI_SETSTYLING
, matchedString
.size() - matchedpos
, STYLE_ISSUEBOLD
);
1384 pos
= (LONG
)(it
->position(0) + matchedString
.size());
1386 // bold style for the rest of the string which isn't matched
1388 Call(SCI_SETSTYLING
, s
.size()-pos
, STYLE_DEFAULT
);
1392 const std::tr1::regex
regCheck(m_sCommand
);
1393 const std::tr1::sregex_iterator end
;
1394 std::string s
= msg
;
1396 for (std::tr1::sregex_iterator
it(s
.begin(), s
.end(), regCheck
); it
!= end
; ++it
)
1398 // clear the styles up to the match position
1399 if (it
->position(0) - pos
>= 0)
1400 Call(SCI_SETSTYLING
, it
->position(0) - pos
, STYLE_DEFAULT
);
1401 pos
= (LONG
)it
->position(0);
1403 const std::tr1::smatch match
= *it
;
1404 // we define group 1 as the whole issue text and
1405 // group 2 as the bug ID
1406 if (match
.size() >= 2)
1408 ATLTRACE("matched id : %s\n", std::string(match
[1]).c_str());
1409 if (match
[1].first
- s
.begin() - pos
>= 0)
1410 Call(SCI_SETSTYLING
, match
[1].first
- s
.begin() - pos
, STYLE_ISSUEBOLD
);
1411 Call(SCI_SETSTYLING
, std::string(match
[1]).size(), STYLE_ISSUEBOLDITALIC
);
1412 pos
= (LONG
)(match
[1].second
-s
.begin());
1417 catch (std::exception
) {}
1422 //similar code in AppUtils.cpp
1423 bool CSciEdit::IsValidURLChar(unsigned char ch
)
1425 return isalnum(ch
) ||
1426 ch
== '_' || ch
== '/' || ch
== ';' || ch
== '?' || ch
== '&' || ch
== '=' ||
1427 ch
== '%' || ch
== ':' || ch
== '.' || ch
== '#' || ch
== '-' || ch
== '+' ||
1428 ch
== '|' || ch
== '>' || ch
== '<';
1431 //similar code in AppUtils.cpp
1432 void CSciEdit::StyleURLs(int startstylepos
, int endstylepos
)
1434 const int line_number
= (int)Call(SCI_LINEFROMPOSITION
, startstylepos
);
1435 startstylepos
= (int)Call(SCI_POSITIONFROMLINE
, (WPARAM
)line_number
);
1437 int len
= endstylepos
- startstylepos
+ 1;
1438 std::unique_ptr
<char[]> textbuffer(new char[len
+ 1]);
1439 TEXTRANGEA textrange
;
1440 textrange
.lpstrText
= textbuffer
.get();
1441 textrange
.chrg
.cpMin
= startstylepos
;
1442 textrange
.chrg
.cpMax
= endstylepos
;
1443 Call(SCI_GETTEXTRANGE
, 0, (LPARAM
)&textrange
);
1444 // we're dealing with utf8 encoded text here, which means one glyph is
1445 // not necessarily one byte/wchar_t
1446 // that's why we use CStringA to still get a correct char index
1447 CStringA msg
= textbuffer
.get();
1450 for (int i
= 0; i
<= msg
.GetLength(); AdvanceUTF8(msg
, i
))
1452 if ((i
< len
) && IsValidURLChar(msg
[i
]))
1462 if (msg
[starturl
] == '<' && i
< len
) // try to detect and do not strip URLs put within <>
1464 while (starturl
<= i
&& msg
[starturl
] == '<') // strip leading '<'
1468 while (i
< len
&& msg
[i
] != '\r' && msg
[i
] != '\n' && msg
[i
] != '>') // find first '>' or new line after resetting i to start position
1469 AdvanceUTF8(msg
, i
);
1471 if (!IsUrl(msg
.Mid(starturl
, i
- starturl
)))
1477 int skipTrailing
= 0;
1478 while (strip
&& i
- skipTrailing
- 1 > starturl
&& (msg
[i
- skipTrailing
- 1] == '.' || msg
[i
- skipTrailing
- 1] == '-' || msg
[i
- skipTrailing
- 1] == '?' || msg
[i
- skipTrailing
- 1] == ';' || msg
[i
- skipTrailing
- 1] == ':' || msg
[i
- skipTrailing
- 1] == '>' || msg
[i
- skipTrailing
- 1] == '<'))
1480 ASSERT(startstylepos
+ i
- skipTrailing
<= endstylepos
);
1481 Call(SCI_STARTSTYLING
, startstylepos
+ starturl
, STYLE_URL
);
1482 Call(SCI_SETSTYLING
, i
- starturl
- skipTrailing
, STYLE_URL
);
1489 bool CSciEdit::IsUrl(const CStringA
& sText
)
1491 if (!PathIsURLA(sText
))
1493 CStringA prefixes
[] = { "http://", "https://", "git://", "ftp://", "file://", "mailto:" };
1494 for (const CStringA
& prefix
: prefixes
)
1496 if (sText
.Find(prefix
) == 0 && sText
.GetLength() != prefix
.GetLength())
1502 bool CSciEdit::IsUTF8(LPVOID pBuffer
, size_t cb
)
1506 UINT16
* pVal
= (UINT16
*)pBuffer
;
1507 UINT8
* pVal2
= (UINT8
*)(pVal
+1);
1508 // scan the whole buffer for a 0x0000 sequence
1509 // if found, we assume a binary file
1510 for (size_t i
=0; i
<(cb
-2); i
=i
+2)
1512 if (0x0000 == *pVal
++)
1515 pVal
= (UINT16
*)pBuffer
;
1516 if (*pVal
== 0xFEFF)
1520 if (*pVal
== 0xBBEF)
1525 // check for illegal UTF8 chars
1526 pVal2
= (UINT8
*)pBuffer
;
1527 for (size_t i
=0; i
<cb
; ++i
)
1529 if ((*pVal2
== 0xC0)||(*pVal2
== 0xC1)||(*pVal2
>= 0xF5))
1533 pVal2
= (UINT8
*)pBuffer
;
1535 for (size_t i
=0; i
<(cb
-3); ++i
)
1537 if ((*pVal2
& 0xE0)==0xC0)
1540 if ((*pVal2
& 0xC0)!=0x80)
1544 if ((*pVal2
& 0xF0)==0xE0)
1547 if ((*pVal2
& 0xC0)!=0x80)
1550 if ((*pVal2
& 0xC0)!=0x80)
1554 if ((*pVal2
& 0xF8)==0xF0)
1557 if ((*pVal2
& 0xC0)!=0x80)
1560 if ((*pVal2
& 0xC0)!=0x80)
1563 if ((*pVal2
& 0xC0)!=0x80)
1574 void CSciEdit::SetAStyle(int style
, COLORREF fore
, COLORREF back
, int size
, const char *face
)
1576 Call(SCI_STYLESETFORE
, style
, fore
);
1577 Call(SCI_STYLESETBACK
, style
, back
);
1579 Call(SCI_STYLESETSIZE
, style
, size
);
1581 Call(SCI_STYLESETFONT
, style
, reinterpret_cast<LPARAM
>(face
));
1585 void CSciEdit::SetUDiffStyle()
1588 SetAStyle(STYLE_DEFAULT
, ::GetSysColor(COLOR_WINDOWTEXT
), ::GetSysColor(COLOR_WINDOW
),
1589 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffFontSize", 10),
1590 CUnicodeUtils::StdGetUTF8(CRegStdString(L
"Software\\TortoiseGit\\UDiffFontName", L
"Courier New")).c_str());
1591 Call(SCI_SETTABWIDTH
, CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffTabSize", 4));
1593 Call(SCI_SETREADONLY
, TRUE
);
1594 //LRESULT pix = Call(SCI_TEXTWIDTH, STYLE_LINENUMBER, (LPARAM)"_99999");
1595 //Call(SCI_SETMARGINWIDTHN, 0, pix);
1596 //Call(SCI_SETMARGINWIDTHN, 1);
1597 //Call(SCI_SETMARGINWIDTHN, 2);
1598 //Set the default windows colors for edit controls
1599 Call(SCI_STYLESETFORE
, STYLE_DEFAULT
, ::GetSysColor(COLOR_WINDOWTEXT
));
1600 Call(SCI_STYLESETBACK
, STYLE_DEFAULT
, ::GetSysColor(COLOR_WINDOW
));
1601 Call(SCI_SETSELFORE
, TRUE
, ::GetSysColor(COLOR_HIGHLIGHTTEXT
));
1602 Call(SCI_SETSELBACK
, TRUE
, ::GetSysColor(COLOR_HIGHLIGHT
));
1603 Call(SCI_SETCARETFORE
, ::GetSysColor(COLOR_WINDOWTEXT
));
1605 //SendEditor(SCI_SETREADONLY, FALSE);
1607 Call(EM_EMPTYUNDOBUFFER
);
1608 Call(SCI_SETSAVEPOINT
);
1610 Call(SCI_SETUNDOCOLLECTION
, 0);
1612 Call(SCI_SETUNDOCOLLECTION
, 1);
1613 Call(SCI_SETWRAPMODE
,SC_WRAP_NONE
);
1615 //::SetFocus(m_hWndEdit);
1616 Call(EM_EMPTYUNDOBUFFER
);
1617 Call(SCI_SETSAVEPOINT
);
1618 Call(SCI_GOTOPOS
, 0);
1620 Call(SCI_CLEARDOCUMENTSTYLE
, 0, 0);
1621 Call(SCI_SETSTYLEBITS
, 5, 0);
1623 HIGHCONTRAST highContrast
= { 0 };
1624 highContrast
.cbSize
= sizeof(HIGHCONTRAST
);
1625 if (SystemParametersInfo(SPI_GETHIGHCONTRAST
, 0, &highContrast
, 0) == TRUE
&& (highContrast
.dwFlags
& HCF_HIGHCONTRASTON
))
1628 //SetAStyle(SCE_DIFF_DEFAULT, RGB(0, 0, 0));
1629 SetAStyle(SCE_DIFF_COMMAND
,
1630 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffForeCommandColor", UDIFF_COLORFORECOMMAND
),
1631 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffBackCommandColor", UDIFF_COLORBACKCOMMAND
));
1632 SetAStyle(SCE_DIFF_POSITION
,
1633 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffForePositionColor", UDIFF_COLORFOREPOSITION
),
1634 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffBackPositionColor", UDIFF_COLORBACKPOSITION
));
1635 SetAStyle(SCE_DIFF_HEADER
,
1636 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffForeHeaderColor", UDIFF_COLORFOREHEADER
),
1637 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffBackHeaderColor", UDIFF_COLORBACKHEADER
));
1638 SetAStyle(SCE_DIFF_COMMENT
,
1639 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffForeCommentColor", UDIFF_COLORFORECOMMENT
),
1640 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffBackCommentColor", UDIFF_COLORBACKCOMMENT
));
1641 Call(SCI_STYLESETBOLD
, SCE_DIFF_COMMENT
, TRUE
);
1642 SetAStyle(SCE_DIFF_ADDED
,
1643 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffForeAddedColor", UDIFF_COLORFOREADDED
),
1644 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffBackAddedColor", UDIFF_COLORBACKADDED
));
1645 SetAStyle(SCE_DIFF_DELETED
,
1646 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffForeRemovedColor", UDIFF_COLORFOREREMOVED
),
1647 CRegStdDWORD(L
"Software\\TortoiseGit\\UDiffBackRemovedColor", UDIFF_COLORBACKREMOVED
));
1649 Call(SCI_SETLEXER
, SCLEX_DIFF
);
1650 Call(SCI_SETKEYWORDS
, 0, (LPARAM
)"revision");
1651 Call(SCI_COLOURISE
, 0, -1);
1654 int CSciEdit::LoadFromFile(CString
&filename
)
1657 _tfopen_s(&fp
, filename
, _T("rb"));
1661 char data
[4096] = { 0 };
1662 size_t lenFile
= fread(data
, 1, sizeof(data
), fp
);
1663 bool bUTF8
= IsUTF8(data
, lenFile
);
1666 Call(SCI_ADDTEXT
, lenFile
,
1667 reinterpret_cast<LPARAM
>(static_cast<char *>(data
)));
1668 lenFile
= fread(data
, 1, sizeof(data
), fp
);
1671 Call(SCI_SETCODEPAGE
, bUTF8
? SC_CP_UTF8
: GetACP());
1678 void CSciEdit::RestyleBugIDs()
1680 int endstylepos
= (int)Call(SCI_GETLENGTH
);
1682 Call(SCI_STARTSTYLING
, 0, STYLE_MASK
);
1683 Call(SCI_SETSTYLING
, endstylepos
, STYLE_DEFAULT
);
1684 // style the bug IDs
1685 MarkEnteredBugID(0, endstylepos
);