1 // Scintilla source code edit control
2 /** @file ScintillaBase.cxx
3 ** An enhanced subclass of Editor with calltips, autocomplete and context menu.
5 // Copyright 1998-2003 by Neil Hodgson <neilh@scintilla.org>
6 // The License.txt file describes the conditions under which this software may be distributed.
15 #include "Scintilla.h"
20 #include "DocumentAccessor.h"
23 #include "ContractionState.h"
25 #include "CellBuffer.h"
28 #include "Indicator.h"
30 #include "LineMarker.h"
32 #include "ViewStyle.h"
33 #include "AutoComplete.h"
34 #include "CharClassify.h"
37 #include "ScintillaBase.h"
39 ScintillaBase::ScintillaBase() {
40 displayPopupMenu
= true;
44 lexLanguage
= SCLEX_CONTAINER
;
45 performingStyle
= false;
47 for (int wl
= 0;wl
< numWordLists
;wl
++)
48 keyWordLists
[wl
] = new WordList
;
49 keyWordLists
[numWordLists
] = 0;
53 ScintillaBase::~ScintillaBase() {
55 for (int wl
= 0;wl
< numWordLists
;wl
++)
56 delete keyWordLists
[wl
];
60 void ScintillaBase::Finalise() {
65 void ScintillaBase::RefreshColourPalette(Palette
&pal
, bool want
) {
66 Editor::RefreshColourPalette(pal
, want
);
67 ct
.RefreshColourPalette(pal
, want
);
70 void ScintillaBase::AddCharUTF(char *s
, unsigned int len
, bool treatAsDBCS
) {
71 bool isFillUp
= ac
.Active() && ac
.IsFillUpChar(*s
);
73 Editor::AddCharUTF(s
, len
, treatAsDBCS
);
76 AutoCompleteCharacterAdded(s
[0]);
77 // For fill ups add the character after the autocompletion has
78 // triggered so containers see the key so can display a calltip.
80 Editor::AddCharUTF(s
, len
, treatAsDBCS
);
85 void ScintillaBase::Command(int cmdId
) {
89 case idAutoComplete
: // Nothing to do
93 case idCallTip
: // Nothing to do
98 WndProc(SCI_UNDO
, 0, 0);
102 WndProc(SCI_REDO
, 0, 0);
106 WndProc(SCI_CUT
, 0, 0);
110 WndProc(SCI_COPY
, 0, 0);
114 WndProc(SCI_PASTE
, 0, 0);
118 WndProc(SCI_CLEAR
, 0, 0);
122 WndProc(SCI_SELECTALL
, 0, 0);
127 int ScintillaBase::KeyCommand(unsigned int iMessage
) {
128 // Most key commands cancel autocompletion mode
136 AutoCompleteMove( -1);
142 AutoCompleteMove( -5);
145 AutoCompleteMove( -5000);
148 AutoCompleteMove(5000);
152 AutoCompleteCharacterDeleted();
153 EnsureCaretVisible();
155 case SCI_DELETEBACKNOTLINE
:
157 AutoCompleteCharacterDeleted();
158 EnsureCaretVisible();
161 AutoCompleteCompleted();
164 AutoCompleteCompleted();
172 if (ct
.inCallTipMode
) {
174 (iMessage
!= SCI_CHARLEFT
) &&
175 (iMessage
!= SCI_CHARLEFTEXTEND
) &&
176 (iMessage
!= SCI_CHARRIGHT
) &&
177 (iMessage
!= SCI_CHARLEFTEXTEND
) &&
178 (iMessage
!= SCI_EDITTOGGLEOVERTYPE
) &&
179 (iMessage
!= SCI_DELETEBACK
) &&
180 (iMessage
!= SCI_DELETEBACKNOTLINE
)
184 if ((iMessage
== SCI_DELETEBACK
) || (iMessage
== SCI_DELETEBACKNOTLINE
)) {
185 if (currentPos
<= ct
.posStartCallTip
) {
190 return Editor::KeyCommand(iMessage
);
193 void ScintillaBase::AutoCompleteDoubleClick(void* p
) {
194 ScintillaBase
* sci
= reinterpret_cast<ScintillaBase
*>(p
);
195 sci
->AutoCompleteCompleted();
198 void ScintillaBase::AutoCompleteStart(int lenEntered
, const char *list
) {
199 //Platform::DebugPrintf("AutoComplete %s\n", list);
202 if (ac
.chooseSingle
&& (listType
== 0)) {
203 if (list
&& !strchr(list
, ac
.GetSeparator())) {
204 const char *typeSep
= strchr(list
, ac
.GetTypesep());
205 size_t lenInsert
= (typeSep
) ? (typeSep
-list
) : strlen(list
);
207 SetEmptySelection(currentPos
- lenEntered
);
208 pdoc
->DeleteChars(currentPos
, lenEntered
);
209 SetEmptySelection(currentPos
);
210 pdoc
->InsertString(currentPos
, list
, lenInsert
);
211 SetEmptySelection(currentPos
+ lenInsert
);
213 SetEmptySelection(currentPos
);
214 pdoc
->InsertString(currentPos
, list
+ lenEntered
, lenInsert
- lenEntered
);
215 SetEmptySelection(currentPos
+ lenInsert
- lenEntered
);
220 ac
.Start(wMain
, idAutoComplete
, currentPos
, LocationFromPosition(currentPos
),
221 lenEntered
, vs
.lineHeight
, IsUnicodeMode());
223 PRectangle rcClient
= GetClientRectangle();
224 Point pt
= LocationFromPosition(currentPos
- lenEntered
);
228 if (pt
.x
>= rcClient
.right
- widthLB
) {
229 HorizontalScrollTo(xOffset
+ pt
.x
- rcClient
.right
+ widthLB
);
231 pt
= LocationFromPosition(currentPos
);
234 rcac
.left
= pt
.x
- ac
.lb
->CaretFromEdge();
235 if (pt
.y
>= rcClient
.bottom
- heightLB
&& // Wont fit below.
236 pt
.y
>= (rcClient
.bottom
+ rcClient
.top
) / 2) { // and there is more room above.
237 rcac
.top
= pt
.y
- heightLB
;
239 heightLB
+= rcac
.top
;
243 rcac
.top
= pt
.y
+ vs
.lineHeight
;
245 rcac
.right
= rcac
.left
+ widthLB
;
246 rcac
.bottom
= Platform::Minimum(rcac
.top
+ heightLB
, rcClient
.bottom
);
247 ac
.lb
->SetPositionRelative(rcac
, wMain
);
248 ac
.lb
->SetFont(vs
.styles
[STYLE_DEFAULT
].font
);
249 unsigned int aveCharWidth
= vs
.styles
[STYLE_DEFAULT
].aveCharWidth
;
250 ac
.lb
->SetAverageCharWidth(aveCharWidth
);
251 ac
.lb
->SetDoubleClickAction(AutoCompleteDoubleClick
, this);
255 // Fiddle the position of the list so it is right next to the target and wide enough for all its strings
256 PRectangle rcList
= ac
.lb
->GetDesiredRect();
257 int heightAlloced
= rcList
.bottom
- rcList
.top
;
258 widthLB
= Platform::Maximum(widthLB
, rcList
.right
- rcList
.left
);
259 if (maxListWidth
!= 0)
260 widthLB
= Platform::Minimum(widthLB
, aveCharWidth
*maxListWidth
);
261 // Make an allowance for large strings in list
262 rcList
.left
= pt
.x
- ac
.lb
->CaretFromEdge();
263 rcList
.right
= rcList
.left
+ widthLB
;
264 if (((pt
.y
+ vs
.lineHeight
) >= (rcClient
.bottom
- heightAlloced
)) && // Wont fit below.
265 ((pt
.y
+ vs
.lineHeight
/ 2) >= (rcClient
.bottom
+ rcClient
.top
) / 2)) { // and there is more room above.
266 rcList
.top
= pt
.y
- heightAlloced
;
268 rcList
.top
= pt
.y
+ vs
.lineHeight
;
270 rcList
.bottom
= rcList
.top
+ heightAlloced
;
271 ac
.lb
->SetPositionRelative(rcList
, wMain
);
273 if (lenEntered
!= 0) {
274 AutoCompleteMoveToCurrentWord();
278 void ScintillaBase::AutoCompleteCancel() {
282 void ScintillaBase::AutoCompleteMove(int delta
) {
286 void ScintillaBase::AutoCompleteMoveToCurrentWord() {
287 char wordCurrent
[1000];
289 int startWord
= ac
.posStart
- ac
.startLen
;
290 for (i
= startWord
; i
< currentPos
&& i
- startWord
< 1000; i
++)
291 wordCurrent
[i
- startWord
] = pdoc
->CharAt(i
);
292 wordCurrent
[Platform::Minimum(i
- startWord
, 999)] = '\0';
293 ac
.Select(wordCurrent
);
296 void ScintillaBase::AutoCompleteCharacterAdded(char ch
) {
297 if (ac
.IsFillUpChar(ch
)) {
298 AutoCompleteCompleted();
299 } else if (ac
.IsStopChar(ch
)) {
302 AutoCompleteMoveToCurrentWord();
306 void ScintillaBase::AutoCompleteCharacterDeleted() {
307 if (currentPos
< ac
.posStart
- ac
.startLen
) {
309 } else if (ac
.cancelAtStartPos
&& (currentPos
<= ac
.posStart
)) {
312 AutoCompleteMoveToCurrentWord();
316 void ScintillaBase::AutoCompleteCompleted() {
317 int item
= ac
.lb
->GetSelection();
321 ac
.lb
->GetValue(item
, selected
, sizeof(selected
));
329 listSelected
= selected
;
330 SCNotification scn
= {0};
331 scn
.nmhdr
.code
= listType
> 0 ? SCN_USERLISTSELECTION
: SCN_AUTOCSELECTION
;
333 scn
.wParam
= listType
;
334 scn
.listType
= listType
;
335 Position firstPos
= ac
.posStart
- ac
.startLen
;
336 scn
.lParam
= firstPos
;
337 scn
.text
= listSelected
.c_str();
347 Position endPos
= currentPos
;
348 if (ac
.dropRestOfWord
)
349 endPos
= pdoc
->ExtendWordSelect(endPos
, 1, true);
350 if (endPos
< firstPos
)
352 pdoc
->BeginUndoAction();
353 if (endPos
!= firstPos
) {
354 pdoc
->DeleteChars(firstPos
, endPos
- firstPos
);
356 SetEmptySelection(ac
.posStart
);
358 SString piece
= selected
;
359 pdoc
->InsertString(firstPos
, piece
.c_str());
360 SetEmptySelection(firstPos
+ static_cast<int>(piece
.length()));
362 pdoc
->EndUndoAction();
365 int ScintillaBase::AutoCompleteGetCurrent() {
366 return ac
.lb
->GetSelection();
369 void ScintillaBase::CallTipShow(Point pt
, const char *defn
) {
370 AutoCompleteCancel();
371 pt
.y
+= vs
.lineHeight
;
372 // If container knows about STYLE_CALLTIP then use it in place of the
373 // STYLE_DEFAULT for the face name, size and character set. Also use it
374 // for the foreground and background colour.
375 int ctStyle
= ct
.UseStyleCallTip() ? STYLE_CALLTIP
: STYLE_DEFAULT
;
376 if (ct
.UseStyleCallTip()) {
377 ct
.SetForeBack(vs
.styles
[STYLE_CALLTIP
].fore
, vs
.styles
[STYLE_CALLTIP
].back
);
379 PRectangle rc
= ct
.CallTipStart(currentPos
, pt
,
381 vs
.styles
[ctStyle
].fontName
,
382 vs
.styles
[ctStyle
].sizeZoomed
,
384 vs
.styles
[ctStyle
].characterSet
,
386 // If the call-tip window would be out of the client
387 // space, adjust so it displays above the text.
388 PRectangle rcClient
= GetClientRectangle();
389 if (rc
.bottom
> rcClient
.bottom
) {
390 int offset
= vs
.lineHeight
+ rc
.Height();
394 // Now display the window.
395 CreateCallTipWindow(rc
);
396 ct
.wCallTip
.SetPositionRelative(rc
, wMain
);
400 void ScintillaBase::CallTipClick() {
401 SCNotification scn
= {0};
402 scn
.nmhdr
.code
= SCN_CALLTIPCLICK
;
403 scn
.position
= ct
.clickPlace
;
407 void ScintillaBase::ContextMenu(Point pt
) {
408 if (displayPopupMenu
) {
409 bool writable
= !WndProc(SCI_GETREADONLY
, 0, 0);
411 AddToPopUp("Undo", idcmdUndo
, writable
&& pdoc
->CanUndo());
412 AddToPopUp("Redo", idcmdRedo
, writable
&& pdoc
->CanRedo());
414 AddToPopUp("Cut", idcmdCut
, writable
&& currentPos
!= anchor
);
415 AddToPopUp("Copy", idcmdCopy
, currentPos
!= anchor
);
416 AddToPopUp("Paste", idcmdPaste
, writable
&& WndProc(SCI_CANPASTE
, 0, 0));
417 AddToPopUp("Delete", idcmdDelete
, writable
&& currentPos
!= anchor
);
419 AddToPopUp("Select All", idcmdSelectAll
);
420 popup
.Show(pt
, wMain
);
424 void ScintillaBase::CancelModes() {
425 AutoCompleteCancel();
427 Editor::CancelModes();
430 void ScintillaBase::ButtonDown(Point pt
, unsigned int curTime
, bool shift
, bool ctrl
, bool alt
) {
432 Editor::ButtonDown(pt
, curTime
, shift
, ctrl
, alt
);
436 void ScintillaBase::SetLexer(uptr_t wParam
) {
437 lexLanguage
= wParam
;
438 lexCurrent
= LexerModule::Find(lexLanguage
);
440 lexCurrent
= LexerModule::Find(SCLEX_NULL
);
443 void ScintillaBase::SetLexerLanguage(const char *languageName
) {
444 lexLanguage
= SCLEX_CONTAINER
;
445 lexCurrent
= LexerModule::Find(languageName
);
447 lexCurrent
= LexerModule::Find(SCLEX_NULL
);
449 lexLanguage
= lexCurrent
->GetLanguage();
452 void ScintillaBase::Colourise(int start
, int end
) {
453 if (!performingStyle
) {
454 // Protect against reentrance, which may occur, for example, when
455 // fold points are discovered while performing styling and the folding
456 // code looks for child lines which may trigger styling.
457 performingStyle
= true;
459 int lengthDoc
= pdoc
->Length();
462 int len
= end
- start
;
464 PLATFORM_ASSERT(len
>= 0);
465 PLATFORM_ASSERT(start
+ len
<= lengthDoc
);
467 //WindowAccessor styler(wMain.GetID(), props);
468 DocumentAccessor
styler(pdoc
, props
, wMain
.GetID());
472 styleStart
= styler
.StyleAt(start
- 1) & pdoc
->stylingBitsMask
;
473 styler
.SetCodePage(pdoc
->dbcsCodePage
);
475 if (lexCurrent
&& (len
> 0)) { // Should always succeed as null lexer should always be available
476 lexCurrent
->Lex(start
, len
, styleStart
, keyWordLists
, styler
);
478 if (styler
.GetPropertyInt("fold")) {
479 lexCurrent
->Fold(start
, len
, styleStart
, keyWordLists
, styler
);
484 performingStyle
= false;
489 void ScintillaBase::NotifyStyleToNeeded(int endStyleNeeded
) {
491 if (lexLanguage
!= SCLEX_CONTAINER
) {
492 int endStyled
= WndProc(SCI_GETENDSTYLED
, 0, 0);
493 int lineEndStyled
= WndProc(SCI_LINEFROMPOSITION
, endStyled
, 0);
494 endStyled
= WndProc(SCI_POSITIONFROMLINE
, lineEndStyled
, 0);
495 Colourise(endStyled
, endStyleNeeded
);
499 Editor::NotifyStyleToNeeded(endStyleNeeded
);
502 sptr_t
ScintillaBase::WndProc(unsigned int iMessage
, uptr_t wParam
, sptr_t lParam
) {
506 AutoCompleteStart(wParam
, reinterpret_cast<const char *>(lParam
));
509 case SCI_AUTOCCANCEL
:
510 AutoCompleteCancel();
513 case SCI_AUTOCACTIVE
:
516 case SCI_AUTOCPOSSTART
:
519 case SCI_AUTOCCOMPLETE
:
520 AutoCompleteCompleted();
523 case SCI_AUTOCSETSEPARATOR
:
524 ac
.SetSeparator(static_cast<char>(wParam
));
527 case SCI_AUTOCGETSEPARATOR
:
528 return ac
.GetSeparator();
531 ac
.SetStopChars(reinterpret_cast<char *>(lParam
));
534 case SCI_AUTOCSELECT
:
535 ac
.Select(reinterpret_cast<char *>(lParam
));
538 case SCI_AUTOCGETCURRENT
:
539 return AutoCompleteGetCurrent();
541 case SCI_AUTOCSETCANCELATSTART
:
542 ac
.cancelAtStartPos
= wParam
!= 0;
545 case SCI_AUTOCGETCANCELATSTART
:
546 return ac
.cancelAtStartPos
;
548 case SCI_AUTOCSETFILLUPS
:
549 ac
.SetFillUpChars(reinterpret_cast<char *>(lParam
));
552 case SCI_AUTOCSETCHOOSESINGLE
:
553 ac
.chooseSingle
= wParam
!= 0;
556 case SCI_AUTOCGETCHOOSESINGLE
:
557 return ac
.chooseSingle
;
559 case SCI_AUTOCSETIGNORECASE
:
560 ac
.ignoreCase
= wParam
!= 0;
563 case SCI_AUTOCGETIGNORECASE
:
564 return ac
.ignoreCase
;
566 case SCI_USERLISTSHOW
:
568 AutoCompleteStart(0, reinterpret_cast<const char *>(lParam
));
571 case SCI_AUTOCSETAUTOHIDE
:
572 ac
.autoHide
= wParam
!= 0;
575 case SCI_AUTOCGETAUTOHIDE
:
578 case SCI_AUTOCSETDROPRESTOFWORD
:
579 ac
.dropRestOfWord
= wParam
!= 0;
582 case SCI_AUTOCGETDROPRESTOFWORD
:
583 return ac
.dropRestOfWord
;
585 case SCI_AUTOCSETMAXHEIGHT
:
586 ac
.lb
->SetVisibleRows(wParam
);
589 case SCI_AUTOCGETMAXHEIGHT
:
590 return ac
.lb
->GetVisibleRows();
592 case SCI_AUTOCSETMAXWIDTH
:
593 maxListWidth
= wParam
;
596 case SCI_AUTOCGETMAXWIDTH
:
599 case SCI_REGISTERIMAGE
:
600 ac
.lb
->RegisterImage(wParam
, reinterpret_cast<const char *>(lParam
));
603 case SCI_CLEARREGISTEREDIMAGES
:
604 ac
.lb
->ClearRegisteredImages();
607 case SCI_AUTOCSETTYPESEPARATOR
:
608 ac
.SetTypesep(static_cast<char>(wParam
));
611 case SCI_AUTOCGETTYPESEPARATOR
:
612 return ac
.GetTypesep();
614 case SCI_CALLTIPSHOW
:
615 CallTipShow(LocationFromPosition(wParam
),
616 reinterpret_cast<const char *>(lParam
));
619 case SCI_CALLTIPCANCEL
:
623 case SCI_CALLTIPACTIVE
:
624 return ct
.inCallTipMode
;
626 case SCI_CALLTIPPOSSTART
:
627 return ct
.posStartCallTip
;
629 case SCI_CALLTIPSETHLT
:
630 ct
.SetHighlight(wParam
, lParam
);
633 case SCI_CALLTIPSETBACK
:
634 ct
.colourBG
= ColourDesired(wParam
);
635 vs
.styles
[STYLE_CALLTIP
].fore
= ct
.colourBG
;
636 InvalidateStyleRedraw();
639 case SCI_CALLTIPSETFORE
:
640 ct
.colourUnSel
= ColourDesired(wParam
);
641 vs
.styles
[STYLE_CALLTIP
].fore
= ct
.colourUnSel
;
642 InvalidateStyleRedraw();
645 case SCI_CALLTIPSETFOREHLT
:
646 ct
.colourSel
= ColourDesired(wParam
);
647 InvalidateStyleRedraw();
650 case SCI_CALLTIPUSESTYLE
:
651 ct
.SetTabSize((int)wParam
);
652 InvalidateStyleRedraw();
656 displayPopupMenu
= wParam
!= 0;
662 lexLanguage
= wParam
;
669 if (lexLanguage
== SCLEX_CONTAINER
) {
670 pdoc
->ModifiedAt(wParam
);
671 NotifyStyleToNeeded((lParam
== -1) ? pdoc
->Length() : lParam
);
673 Colourise(wParam
, lParam
);
678 case SCI_SETPROPERTY
:
679 props
.Set(reinterpret_cast<const char *>(wParam
),
680 reinterpret_cast<const char *>(lParam
));
683 case SCI_GETPROPERTY
: {
684 SString val
= props
.Get(reinterpret_cast<const char *>(wParam
));
685 const int n
= val
.length();
687 char *ptr
= reinterpret_cast<char *>(lParam
);
688 memcpy(ptr
, val
.c_str(), n
);
689 ptr
[n
] = '\0'; // terminate
691 return n
; // Not including NUL
694 case SCI_GETPROPERTYEXPANDED
: {
695 SString val
= props
.GetExpanded(reinterpret_cast<const char *>(wParam
));
696 const int n
= val
.length();
698 char *ptr
= reinterpret_cast<char *>(lParam
);
699 memcpy(ptr
, val
.c_str(), n
);
700 ptr
[n
] = '\0'; // terminate
702 return n
; // Not including NUL
705 case SCI_GETPROPERTYINT
:
706 return props
.GetInt(reinterpret_cast<const char *>(wParam
), lParam
);
708 case SCI_SETKEYWORDS
:
709 if (wParam
< numWordLists
) {
710 keyWordLists
[wParam
]->Clear();
711 keyWordLists
[wParam
]->Set(reinterpret_cast<const char *>(lParam
));
715 case SCI_SETLEXERLANGUAGE
:
716 SetLexerLanguage(reinterpret_cast<const char *>(lParam
));
719 case SCI_GETSTYLEBITSNEEDED
:
720 return lexCurrent
? lexCurrent
->GetStyleBitsNeeded() : 5;
724 return Editor::WndProc(iMessage
, wParam
, lParam
);