1 /*
2 * This file is part of the Code::Blocks IDE and licensed under the GNU Lesser General Public License, version 3
3 * http://www.gnu.org/licenses/lgpl-3.0.html
4 *
5 * $Revision: 11922 $
6 * $Id: ccmanager.cpp 11922 2019-11-16 19:30:27Z fuscated $
7 * $HeadURL: svn://svn.code.sf.net/p/codeblocks/code/branches/release-20.xx/src/sdk/ccmanager.cpp $
8 */
9
10 #include "sdk_precomp.h"
11
12 #include "ccmanager.h"
13
14 #ifndef CB_PRECOMP
15 #include <algorithm>
16
17 #include <wx/listctrl.h>
18 #include <wx/menu.h>
19 #if wxUSE_POPUPWIN
20 #include <wx/popupwin.h>
21 #endif
22 #include <wx/timer.h>
23
24 #include "cbeditor.h"
25 #include "configmanager.h"
26 #include "editormanager.h"
27 #include "logmanager.h" // for F
28 #endif
29
30 #include <wx/html/htmlwin.h>
31 #include <wx/display.h>
32
33 #include "cbcolourmanager.h"
34 #include "cbstyledtextctrl.h"
35 #include "editor_hooks.h"
36
37 namespace CCManagerHelper
38 {
39 // shift points if they are past the insertion/deletion point
RipplePts(int & ptA,int & ptB,int len,int delta)40 inline void RipplePts(int& ptA, int& ptB, int len, int delta)
41 {
42 if (ptA > len - delta)
43 ptA += delta;
44 if (ptB > len - delta)
45 ptB += delta;
46 }
47
48 // wxScintilla::FindColumn seems to be broken; re-implement:
49 // Find the position of a column on a line taking into account tabs and
50 // multi-byte characters. If beyond end of line, return line end position.
FindColumn(int line,int column,wxScintilla * stc)51 inline int FindColumn(int line, int column, wxScintilla* stc)
52 {
53 int lnEnd = stc->GetLineEndPosition(line);
54 for (int pos = stc->PositionFromLine(line); pos < lnEnd; ++pos)
55 {
56 if (stc->GetColumn(pos) == column)
57 return pos;
58 }
59 return lnEnd;
60 }
61
62 // test if an editor position is displayed
IsPosVisible(int pos,wxScintilla * stc)63 inline bool IsPosVisible(int pos, wxScintilla* stc)
64 {
65 const int dist = stc->VisibleFromDocLine(stc->LineFromPosition(pos)) - stc->GetFirstVisibleLine();
66 return !(dist < 0 || dist > stc->LinesOnScreen()); // caret is off screen
67 }
68
69 // return a hash of a calltip context (to avoid storing strings of each calltip)
70 // used in m_CallTipChoiceDict and m_CallTipFuzzyChoiceDict
CallTipToInt(const wxString & firstTip,int numPages)71 static int CallTipToInt(const wxString& firstTip, int numPages)
72 {
73 int val = 33 * firstTip.Length() ^ numPages;
74 for (wxString::const_iterator itr = firstTip.begin();
75 itr != firstTip.end(); ++itr)
76 {
77 val = 33 * val ^ static_cast<int>(*itr);
78 }
79 return val;
80 }
81
82 // (shamelessly stolen from mime handler plugin ;) )
83 // build all HTML font sizes (1..7) from the given base size
BuildFontSizes(int * sizes,int size)84 static void BuildFontSizes(int *sizes, int size)
85 {
86 // using a fixed factor (1.2, from CSS2) is a bad idea as explained at
87 // http://www.w3.org/TR/CSS21/fonts.html#font-size-props but this is by far
88 // simplest thing to do so still do it like this for now
89 sizes[0] = int(size * 0.75); // exception to 1.2 rule, otherwise too small
90 sizes[1] = int(size * 0.83);
91 sizes[2] = size;
92 sizes[3] = int(size * 1.2);
93 sizes[4] = int(size * 1.44);
94 sizes[5] = int(size * 1.73);
95 sizes[6] = int(size * 2);
96 }
97
98 // (shamelessly stolen from mime handler plugin ;) )
GetDefaultHTMLFontSize()99 static int GetDefaultHTMLFontSize()
100 {
101 // base the default font size on the size of the default system font but
102 // also ensure that we have a font of reasonable size, otherwise small HTML
103 // fonts are unreadable
104 int size = wxNORMAL_FONT->GetPointSize();
105 if ( size < 9 )
106 size = 9;
107 return size;
108 }
109 }
110
111 template<> CCManager* Mgr<CCManager>::instance = nullptr;
112 template<> bool Mgr<CCManager>::isShutdown = false;
113
114 const int idCallTipTimer = wxNewId();
115 const int idAutoLaunchTimer = wxNewId();
116 const int idAutocompSelectTimer = wxNewId();
117 const int idShowTooltip = wxNewId();
118 const int idCallTipNext = wxNewId();
119 const int idCallTipPrevious = wxNewId();
120
121 DEFINE_EVENT_TYPE(cbEVT_DEFERRED_CALLTIP_SHOW)
122 DEFINE_EVENT_TYPE(cbEVT_DEFERRED_CALLTIP_CANCEL)
123
124 // milliseconds
125 #define CALLTIP_REFRESH_DELAY 90
126 #define AUTOCOMP_SELECT_DELAY 35
127 #define SCROLL_REFRESH_DELAY 500
128
129 /** the CC tooltip options in Editor setting dialog */
130 enum TooltipMode
131 {
132 tmDisable = 0,
133 tmEnable,
134 tmForceSinglePage,
135 tmKeyboundOnly
136 };
137
138 /** FROM_TIMER means the event is automatically fired from the ccmanager, not explicitly called
139 * by the user. For example, if the code suggestion is fired by the client code, such as:
140 * @code
141 * CodeBlocksEvent evt(cbEVT_COMPLETE_CODE);
142 * Manager::Get()->ProcessEvent(evt);
143 * @endcode
144 * Then the event has int value 0.
145 */
146 #define FROM_TIMER 1
147
148 //{ Unfocusable popup
149
150 // imported with small changes from PlatWX.cpp
151 class UnfocusablePopupWindow :
152 #if wxUSE_POPUPWIN
153 public wxPopupWindow
154 #else
155 public wxFrame
156 #endif // wxUSE_POPUPWIN
157 {
158 public:
159 #if wxUSE_POPUPWIN
160 typedef wxPopupWindow BaseClass;
161
UnfocusablePopupWindow(wxWindow * parent,int style=wxBORDER_NONE)162 UnfocusablePopupWindow(wxWindow* parent, int style = wxBORDER_NONE) :
163 wxPopupWindow(parent, style)
164 #else
165 typedef wxFrame BaseClass;
166
167 UnfocusablePopupWindow(wxWindow* parent, int style = 0) :
168 wxFrame(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize,
169 style | wxFRAME_NO_TASKBAR | wxFRAME_FLOAT_ON_PARENT | wxNO_BORDER | wxFRAME_SHAPED
170 #ifdef __WXMAC__
171 | wxPOPUP_WINDOW
172 #endif // __WXMAC__
173 )
174 #endif // wxUSE_POPUPWIN
175 {
176 Hide();
177 }
178
179 bool Destroy() override;
180 void OnFocus(wxFocusEvent& event);
181 void ActivateParent();
182
183 void DoSetSize(int x, int y, int width, int height, int sizeFlags = wxSIZE_AUTO) override;
184 bool Show(bool show = true) override;
185
186 private:
187 DECLARE_EVENT_TABLE()
188 };
189
190 // On OSX and (possibly others) there can still be pending
191 // messages/events for the list control when Scintilla wants to
192 // close it, so do a pending delete of it instead of destroying
193 // immediately.
Destroy()194 bool UnfocusablePopupWindow::Destroy()
195 {
196 #ifdef __WXMAC__
197 // The bottom edge of this window is not getting properly
198 // refreshed upon deletion, so help it out...
199 wxWindow* p = GetParent();
200 wxRect r(GetPosition(), GetSize());
201 r.SetHeight(r.GetHeight()+1);
202 p->Refresh(false, &r);
203 #endif
204 if ( !wxPendingDelete.Member(this) )
205 wxPendingDelete.Append(this);
206 return true;
207 }
208
OnFocus(wxFocusEvent & event)209 void UnfocusablePopupWindow::OnFocus(wxFocusEvent& event)
210 {
211 ActivateParent();
212 GetParent()->SetFocus();
213 event.Skip();
214 }
215
ActivateParent()216 void UnfocusablePopupWindow::ActivateParent()
217 {
218 // Although we're a frame, we always want the parent to be active, so
219 // raise it whenever we get shown, focused, etc.
220 wxTopLevelWindow *frame = wxDynamicCast(
221 wxGetTopLevelParent(GetParent()), wxTopLevelWindow);
222 if (frame)
223 frame->Raise();
224 }
225
DoSetSize(int x,int y,int width,int height,int sizeFlags)226 void UnfocusablePopupWindow::DoSetSize(int x, int y,
227 int width, int height,
228 int sizeFlags)
229 {
230 // convert coords to screen coords since we're a top-level window
231 if (x != wxDefaultCoord)
232 GetParent()->ClientToScreen(&x, NULL);
233
234 if (y != wxDefaultCoord)
235 GetParent()->ClientToScreen(NULL, &y);
236
237 BaseClass::DoSetSize(x, y, width, height, sizeFlags);
238 }
239
Show(bool show)240 bool UnfocusablePopupWindow::Show(bool show)
241 {
242 bool rv = BaseClass::Show(show);
243 if (rv && show)
244 ActivateParent();
245 #ifdef __WXMAC__
246 GetParent()->Refresh(false);
247 #endif
248 return rv;
249 }
250
BEGIN_EVENT_TABLE(UnfocusablePopupWindow,UnfocusablePopupWindow::BaseClass)251 BEGIN_EVENT_TABLE(UnfocusablePopupWindow, UnfocusablePopupWindow::BaseClass)
252 EVT_SET_FOCUS(UnfocusablePopupWindow::OnFocus)
253 END_EVENT_TABLE()
254
255 //} end Unfocusable popup
256
257
258 // class constructor
259 CCManager::CCManager() :
260 m_AutocompPosition(wxSCI_INVALID_POSITION),
261 m_CallTipActive(wxSCI_INVALID_POSITION),
262 m_LastAutocompIndex(wxNOT_FOUND),
263 m_LastTipPos(wxSCI_INVALID_POSITION),
264 m_WindowBound(0),
265 m_OwnsAutocomp(true),
266 m_CallTipTimer(this, idCallTipTimer),
267 m_AutoLaunchTimer(this, idAutoLaunchTimer),
268 m_AutocompSelectTimer(this, idAutocompSelectTimer),
269 #ifdef __WXMSW__
270 m_pAutocompPopup(nullptr),
271 #endif // __WXMSW__
272 m_pLastEditor(nullptr),
273 m_pLastCCPlugin(nullptr)
274 {
275 const wxString ctChars = wxT(",;\n()"); // default set
276 m_CallTipChars[nullptr] = std::set<wxChar>(ctChars.begin(), ctChars.end());
277 const wxString alChars = wxT(".:<>\"#/"); // default set
278 m_AutoLaunchChars[nullptr] = std::set<wxChar>(alChars.begin(), alChars.end());
279
280 m_LastACLaunchState.init(wxSCI_INVALID_POSITION, wxSCI_INVALID_POSITION, 0);
281
282 // init documentation popup
283 m_pPopup = new UnfocusablePopupWindow(Manager::Get()->GetAppFrame());
284 m_pHtml = new wxHtmlWindow(m_pPopup, wxID_ANY, wxDefaultPosition,
285 wxDefaultSize, wxHW_SCROLLBAR_AUTO | wxBORDER_SIMPLE);
286 int sizes[7] = {};
287 CCManagerHelper::BuildFontSizes(sizes, CCManagerHelper::GetDefaultHTMLFontSize());
288 m_pHtml->SetFonts(wxEmptyString, wxEmptyString, &sizes[0]);
289 m_pHtml->Connect(wxEVT_COMMAND_HTML_LINK_CLICKED,
290 wxHtmlLinkEventHandler(CCManager::OnHtmlLink), nullptr, this);
291
292 // register colours
293 ColourManager* cmgr = Manager::Get()->GetColourManager();
294 cmgr->RegisterColour(_("Code completion"), _("Tooltip/Calltip background"), wxT("cc_tips_back"), *wxWHITE);
295 cmgr->RegisterColour(_("Code completion"), _("Tooltip/Calltip foreground"), wxT("cc_tips_fore"), wxColour(wxT("DIM GREY")));
296 cmgr->RegisterColour(_("Code completion"), _("Tooltip/Calltip highlight"), wxT("cc_tips_highlight"), wxColour(wxT("BLUE")));
297
298 // connect menus
299 wxFrame* mainFrame = Manager::Get()->GetAppFrame();
300 wxMenuBar* menuBar = mainFrame->GetMenuBar();
301 if (menuBar)
302 {
303 int idx = menuBar->FindMenu(_("&Edit"));
304 wxMenu* edMenu = menuBar->GetMenu(idx < 0 ? 0 : idx);
305 const wxMenuItemList& itemsList = edMenu->GetMenuItems();
306 size_t insertPos = itemsList.GetCount();
307 for (size_t i = 0; i < insertPos; ++i)
308 {
309 if (itemsList[i]->GetItemLabel() == _("Complete code"))
310 {
311 insertPos = i + 1;
312 break;
313 }
314 }
315 // insert after Edit->Complete code
316 edMenu->Insert(insertPos, idShowTooltip, _("Show tooltip\tShift-Alt-Space"));
317 edMenu->Insert(insertPos + 1, idCallTipNext, _("Next call tip"));
318 edMenu->Insert(insertPos + 2, idCallTipPrevious, _("Previous call tip"));
319 }
320 mainFrame->Connect(idShowTooltip, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(CCManager::OnMenuSelect), nullptr, this);
321 mainFrame->Connect(idCallTipNext, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(CCManager::OnMenuSelect), nullptr, this);
322 mainFrame->Connect(idCallTipPrevious, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(CCManager::OnMenuSelect), nullptr, this);
323
324 // connect events
325 typedef cbEventFunctor<CCManager, CodeBlocksEvent> CCEvent;
326 Manager::Get()->RegisterEventSink(cbEVT_APP_DEACTIVATED, new CCEvent(this, &CCManager::OnDeactivateApp));
327 Manager::Get()->RegisterEventSink(cbEVT_EDITOR_DEACTIVATED, new CCEvent(this, &CCManager::OnDeactivateEd));
328 Manager::Get()->RegisterEventSink(cbEVT_EDITOR_OPEN, new CCEvent(this, &CCManager::OnEditorOpen));
329 Manager::Get()->RegisterEventSink(cbEVT_EDITOR_CLOSE, new CCEvent(this, &CCManager::OnEditorClose));
330 Manager::Get()->RegisterEventSink(cbEVT_EDITOR_TOOLTIP, new CCEvent(this, &CCManager::OnEditorTooltip));
331 Manager::Get()->RegisterEventSink(cbEVT_SHOW_CALL_TIP, new CCEvent(this, &CCManager::OnShowCallTip));
332 Manager::Get()->RegisterEventSink(cbEVT_COMPLETE_CODE, new CCEvent(this, &CCManager::OnCompleteCode));
333 m_EditorHookID = EditorHooks::RegisterHook(new EditorHooks::HookFunctor<CCManager>(this, &CCManager::OnEditorHook));
334 Connect(idCallTipTimer, wxEVT_TIMER, wxTimerEventHandler(CCManager::OnTimer));
335 Connect(idAutoLaunchTimer, wxEVT_TIMER, wxTimerEventHandler(CCManager::OnTimer));
336 Connect(idAutocompSelectTimer, wxEVT_TIMER, wxTimerEventHandler(CCManager::OnTimer));
337 Connect(cbEVT_DEFERRED_CALLTIP_SHOW, wxCommandEventHandler(CCManager::OnDeferredCallTipShow));
338 Connect(cbEVT_DEFERRED_CALLTIP_CANCEL, wxCommandEventHandler(CCManager::OnDeferredCallTipCancel));
339 }
340
341 // class destructor
~CCManager()342 CCManager::~CCManager()
343 {
344 m_pHtml->Disconnect(wxEVT_COMMAND_HTML_LINK_CLICKED,
345 wxHtmlLinkEventHandler(CCManager::OnHtmlLink), nullptr, this);
346 m_pHtml->Destroy();
347 m_pPopup->Destroy();
348 wxFrame* mainFrame = Manager::Get()->GetAppFrame();
349 mainFrame->Disconnect(idShowTooltip, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(CCManager::OnMenuSelect), nullptr, this);
350 mainFrame->Disconnect(idCallTipNext, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(CCManager::OnMenuSelect), nullptr, this);
351 mainFrame->Disconnect(idCallTipPrevious, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(CCManager::OnMenuSelect), nullptr, this);
352 Manager::Get()->RemoveAllEventSinksFor(this);
353 EditorHooks::UnregisterHook(m_EditorHookID, true);
354 Disconnect(idCallTipTimer);
355 Disconnect(idAutoLaunchTimer);
356 Disconnect(idAutocompSelectTimer);
357 Disconnect(cbEVT_DEFERRED_CALLTIP_SHOW);
358 Disconnect(cbEVT_DEFERRED_CALLTIP_CANCEL);
359 }
360
GetProviderFor(cbEditor * ed)361 cbCodeCompletionPlugin* CCManager::GetProviderFor(cbEditor* ed)
362 {
363 if (!ed)
364 ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
365 if (ed == m_pLastEditor)
366 return m_pLastCCPlugin; // use cached
367
368 m_pLastEditor = ed;
369 m_pLastCCPlugin = nullptr;
370 m_LastACLaunchState.caretStart = wxSCI_INVALID_POSITION;
371 const PluginsArray& pa = Manager::Get()->GetPluginManager()->GetCodeCompletionOffers();
372 for (size_t i = 0; i < pa.GetCount(); ++i)
373 {
374 cbCodeCompletionPlugin::CCProviderStatus status = static_cast<cbCodeCompletionPlugin*>(pa[i])->GetProviderStatusFor(ed);
375 if (status == cbCodeCompletionPlugin::ccpsActive)
376 {
377 m_pLastCCPlugin = static_cast<cbCodeCompletionPlugin*>(pa[i]);
378 break;
379 }
380 else if (status == cbCodeCompletionPlugin::ccpsUniversal)
381 m_pLastCCPlugin = static_cast<cbCodeCompletionPlugin*>(pa[i]);
382 }
383 return m_pLastCCPlugin;
384 }
385
RegisterCallTipChars(const wxString & chars,cbCodeCompletionPlugin * registrant)386 void CCManager::RegisterCallTipChars(const wxString& chars, cbCodeCompletionPlugin* registrant)
387 {
388 if (registrant)
389 m_CallTipChars[registrant] = std::set<wxChar>(chars.begin(), chars.end());
390 }
391
RegisterAutoLaunchChars(const wxString & chars,cbCodeCompletionPlugin * registrant)392 void CCManager::RegisterAutoLaunchChars(const wxString& chars, cbCodeCompletionPlugin* registrant)
393 {
394 if (registrant)
395 m_AutoLaunchChars[registrant] = std::set<wxChar>(chars.begin(), chars.end());
396 }
397
NotifyDocumentation()398 void CCManager::NotifyDocumentation()
399 {
400 cbEditor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
401 if (ed)
402 DoShowDocumentation(ed);
403 }
404
NotifyPluginStatus()405 void CCManager::NotifyPluginStatus()
406 {
407 m_pLastEditor = nullptr;
408 m_pLastCCPlugin = nullptr;
409 }
410
InjectAutoCompShow(int lenEntered,const wxString & itemList)411 void CCManager::InjectAutoCompShow(int lenEntered, const wxString& itemList)
412 {
413 cbEditor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
414 if (ed)
415 {
416 ed->GetControl()->AutoCompShow(lenEntered, itemList);
417 m_OwnsAutocomp = false;
418 m_AutocompTokens.clear();
419 }
420 }
421
422 // Change the current call tip to be the next or the previous.
423 // Do wrapping if the end is reached in both directions.
AdvanceTip(Direction direction)424 void CCManager::AdvanceTip(Direction direction)
425 {
426 if (direction == Next)
427 {
428 ++m_CurCallTip;
429 if (m_CurCallTip == m_CallTips.end())
430 m_CurCallTip = m_CallTips.begin();
431 }
432 else
433 {
434 if (m_CurCallTip == m_CallTips.begin())
435 {
436 if (m_CallTips.size() > 1)
437 m_CurCallTip = m_CallTips.begin() + m_CallTips.size() - 1;
438 }
439 else
440 --m_CurCallTip;
441 }
442 }
443
ProcessArrow(int key)444 bool CCManager::ProcessArrow(int key)
445 {
446 bool wasProcessed = false;
447 cbEditor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
448 if (!ed)
449 return wasProcessed;
450 cbStyledTextCtrl* stc = ed->GetControl();
451 if (stc->CallTipActive() && m_CallTipActive != wxSCI_INVALID_POSITION && m_CallTips.size() > 1)
452 {
453 if (key == WXK_DOWN)
454 AdvanceTip(Next);
455 else if (key == WXK_UP)
456 AdvanceTip(Previous);
457 else
458 return wasProcessed;
459
460 DoUpdateCallTip(ed);
461 wasProcessed = true;
462 }
463 return wasProcessed;
464 }
465
466 // priority, then alphabetical
467 struct TokenSorter
468 {
469 bool& m_PureAlphabetical; // modify the passed argument(set to false) if weight are different
470 bool m_CaseSensitive;
471
TokenSorterTokenSorter472 TokenSorter(bool& alphabetical, bool caseSensitive): m_PureAlphabetical(alphabetical), m_CaseSensitive(caseSensitive)
473 {
474 m_PureAlphabetical = true;
475 }
476
operator ()TokenSorter477 bool operator()(const cbCodeCompletionPlugin::CCToken& a, const cbCodeCompletionPlugin::CCToken& b)
478 {
479 int diff = a.weight - b.weight;
480 if (diff == 0)
481 {
482 if (m_CaseSensitive)
483 diff = a.displayName.Cmp(b.displayName);
484 else
485 { // cannot use CmpNoCase() because it compares lower case but Scintilla compares upper
486 diff = a.displayName.Upper().Cmp(b.displayName.Upper());
487 if (diff == 0)
488 diff = a.displayName.Cmp(b.displayName);
489 }
490 }
491 else
492 m_PureAlphabetical = false;
493
494 return diff < 0;
495 }
496 };
497
498 // cbEVT_COMPLETE_CODE
OnCompleteCode(CodeBlocksEvent & event)499 void CCManager::OnCompleteCode(CodeBlocksEvent& event)
500 {
501 event.Skip();
502 ConfigManager* cfg = Manager::Get()->GetConfigManager(wxT("ccmanager"));
503 if (!cfg->ReadBool(wxT("/code_completion"), true))
504 return;
505
506 cbEditor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
507 if (!ed)
508 return;
509 cbCodeCompletionPlugin* ccPlugin = GetProviderFor(ed);
510 if (!ccPlugin)
511 return;
512
513 cbStyledTextCtrl* stc = ed->GetControl();
514 int tknEnd = stc->GetCurrentPos();
515 if (tknEnd == m_LastACLaunchState.caretStart && stc->GetZoom() == m_LastACLaunchState.editorZoom && !m_AutocompTokens.empty())
516 {
517 DoBufferedCC(stc);
518 return;
519 }
520 int tknStart = stc->WordStartPosition(tknEnd, true);
521
522 m_AutocompTokens = ccPlugin->GetAutocompList(event.GetInt() == FROM_TIMER,
523 ed, tknStart, tknEnd);
524 if (m_AutocompTokens.empty())
525 return;
526
527 if (m_AutocompTokens.size() == 1 && cfg->ReadBool(wxT("/auto_select_single"), false))
528 {
529 // Using stc->AutoCompSetChooseSingle() does not send wxEVT_SCI_AUTOCOMP_SELECTION,
530 // so manually emulate the behaviour.
531
532 if (!stc->CallTipActive() && !stc->AutoCompActive())
533 m_CallTipActive = wxSCI_INVALID_POSITION;
534
535 m_OwnsAutocomp = true;
536 m_LastACLaunchState.init(tknEnd, tknStart, stc->GetZoom());
537 m_LastAutocompIndex = 0;
538
539 wxScintillaEvent autoCompFinishEvt(wxEVT_SCI_AUTOCOMP_SELECTION);
540 autoCompFinishEvt.SetText(m_AutocompTokens.front().displayName);
541
542 OnEditorHook(ed, autoCompFinishEvt);
543
544 return;
545 }
546
547 bool isPureAlphabetical = true;
548 bool isCaseSensitive = cfg->ReadBool(wxT("/case_sensitive"), false);
549 TokenSorter sortFunctor(isPureAlphabetical, isCaseSensitive);
550 std::sort(m_AutocompTokens.begin(), m_AutocompTokens.end(), sortFunctor);
551 if (isPureAlphabetical)
552 stc->AutoCompSetOrder(wxSCI_ORDER_PRESORTED);
553 else
554 stc->AutoCompSetOrder(wxSCI_ORDER_CUSTOM);
555 wxString items;
556 // experimentally, the average length per token seems to be 23 for the main CC plugin
557 items.Alloc(m_AutocompTokens.size() * 20); // TODO: measure performance
558 for (size_t i = 0; i < m_AutocompTokens.size(); ++i)
559 {
560 items += m_AutocompTokens[i].displayName;
561 if (m_AutocompTokens[i].category == -1)
562 items += wxT("\r");
563 else
564 items += F(wxT("\n%d\r"), m_AutocompTokens[i].category);
565 }
566 items.RemoveLast();
567
568 if (!stc->CallTipActive() && !stc->AutoCompActive())
569 m_CallTipActive = wxSCI_INVALID_POSITION;
570
571 stc->AutoCompSetIgnoreCase(!isCaseSensitive);
572 stc->AutoCompSetMaxHeight(14);
573 stc->AutoCompSetTypeSeparator(wxT('\n'));
574 stc->AutoCompSetSeparator(wxT('\r'));
575 stc->AutoCompShow(tknEnd - tknStart, items);
576 m_OwnsAutocomp = true;
577 if (isPureAlphabetical)
578 {
579 const wxString& contextStr = stc->GetTextRange(tknStart, stc->WordEndPosition(tknEnd, true));
580 std::vector<cbCodeCompletionPlugin::CCToken>::const_iterator tknIt
581 = std::lower_bound(m_AutocompTokens.begin(), m_AutocompTokens.end(),
582 cbCodeCompletionPlugin::CCToken(-1, contextStr),
583 sortFunctor);
584 if (tknIt != m_AutocompTokens.end() && tknIt->displayName.StartsWith(contextStr))
585 stc->AutoCompSelect(tknIt->displayName);
586 }
587
588 m_LastACLaunchState.init(tknEnd, tknStart, stc->GetZoom());
589 }
590
591 // cbEVT_APP_DEACTIVATED
OnDeactivateApp(CodeBlocksEvent & event)592 void CCManager::OnDeactivateApp(CodeBlocksEvent& event)
593 {
594 DoHidePopup();
595 cbEditor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
596 if (ed)
597 {
598 cbStyledTextCtrl* stc = ed->GetControl();
599 if (stc->CallTipActive())
600 {
601 // calling 'stc->CallTipCancel()' directly can cause crashes for some users due to:
602 // http://forums.codeblocks.org/index.php/topic,19117.msg130969.html#msg130969
603 wxCommandEvent pendingCancel(cbEVT_DEFERRED_CALLTIP_CANCEL);
604 AddPendingEvent(pendingCancel);
605 }
606 m_CallTipActive = wxSCI_INVALID_POSITION;
607 }
608 event.Skip();
609 }
610
611 // cbEVT_EDITOR_DEACTIVATED
OnDeactivateEd(CodeBlocksEvent & event)612 void CCManager::OnDeactivateEd(CodeBlocksEvent& event)
613 {
614 DoHidePopup();
615 cbEditor* ed = Manager::Get()->GetEditorManager()->GetBuiltinEditor(event.GetEditor());
616 if (ed)
617 {
618 cbStyledTextCtrl* stc = ed->GetControl();
619 if (stc->CallTipActive())
620 stc->CallTipCancel();
621 m_CallTipActive = wxSCI_INVALID_POSITION;
622 }
623 event.Skip();
624 }
625
setupColours(cbEditor * editor,ColourManager * manager)626 static void setupColours(cbEditor *editor, ColourManager *manager)
627 {
628 cbStyledTextCtrl* stc = editor->GetControl();
629 stc->CallTipSetBackground(manager->GetColour(wxT("cc_tips_back")));
630 stc->CallTipSetForeground(manager->GetColour(wxT("cc_tips_fore")));
631 stc->CallTipSetForegroundHighlight(manager->GetColour(wxT("cc_tips_highlight")));
632 }
633
634 // cbEVT_EDITOR_OPEN
OnEditorOpen(CodeBlocksEvent & event)635 void CCManager::OnEditorOpen(CodeBlocksEvent& event)
636 {
637 cbEditor* ed = Manager::Get()->GetEditorManager()->GetBuiltinEditor(event.GetEditor());
638 if (ed)
639 {
640 cbStyledTextCtrl* stc = ed->GetControl();
641 stc->Connect(wxEVT_COMMAND_LIST_ITEM_SELECTED,
642 wxListEventHandler(CCManager::OnAutocompleteSelect), nullptr, this);
643
644 setupColours(ed, Manager::Get()->GetColourManager());
645 }
646 }
647
UpdateEnvSettings()648 void CCManager::UpdateEnvSettings()
649 {
650 EditorManager *editors = Manager::Get()->GetEditorManager();
651 ColourManager* cmgr = Manager::Get()->GetColourManager();
652
653 int count = editors->GetEditorsCount();
654 for (int ii = 0; ii < count; ++ii)
655 {
656 cbEditor *editor = editors->GetBuiltinEditor(editors->GetEditor(ii));
657 if (editor)
658 setupColours(editor, cmgr);
659 }
660 }
661
662 // cbEVT_EDITOR_CLOSE
OnEditorClose(CodeBlocksEvent & event)663 void CCManager::OnEditorClose(CodeBlocksEvent& event)
664 {
665 DoHidePopup();
666 cbEditor* ed = Manager::Get()->GetEditorManager()->GetBuiltinEditor(event.GetEditor());
667 if (ed == m_pLastEditor)
668 m_pLastEditor = nullptr;
669 if (ed && ed->GetControl())
670 {
671 // TODO: is this ever called?
672 ed->GetControl()->Disconnect(wxEVT_COMMAND_LIST_ITEM_SELECTED,
673 wxListEventHandler(CCManager::OnAutocompleteSelect), nullptr, this);
674 }
675 }
676
677 // cbEVT_EDITOR_TOOLTIP
OnEditorTooltip(CodeBlocksEvent & event)678 void CCManager::OnEditorTooltip(CodeBlocksEvent& event)
679 {
680 event.Skip();
681
682 int tooltipMode = Manager::Get()->GetConfigManager(wxT("ccmanager"))->ReadInt(wxT("/tooltip_mode"), 1);
683 if (tooltipMode == tmDisable) // disabled
684 return;
685
686 // if the event comes from user menu click, then its String field isn't empty
687 // @see CCManager::OnMenuSelect() for details
688 bool fromMouseDwell = event.GetString().IsEmpty();
689 if (wxGetKeyState(WXK_CONTROL) && fromMouseDwell)
690 return;
691 if (tooltipMode == tmKeyboundOnly && fromMouseDwell) // keybound only
692 return;
693
694 EditorBase* base = event.GetEditor();
695 cbEditor* ed = base && base->IsBuiltinEditor() ? static_cast<cbEditor*>(base) : nullptr;
696 if (!ed || ed->IsContextMenuOpened())
697 return;
698
699 cbStyledTextCtrl* stc = ed->GetControl();
700 cbCodeCompletionPlugin* ccPlugin = GetProviderFor(ed);
701 int pos = stc->PositionFromPointClose(event.GetX(), event.GetY());
702 if (!ccPlugin || pos < 0 || pos >= stc->GetLength())
703 {
704 if (stc->CallTipActive() && event.GetExtraLong() == 0 && m_CallTipActive == wxSCI_INVALID_POSITION)
705 static_cast<wxScintilla*>(stc)->CallTipCancel();
706 return;
707 }
708
709 int hlStart, hlEnd, argsPos;
710 hlStart = hlEnd = argsPos = wxSCI_INVALID_POSITION;
711 bool allowCallTip = true;
712 const std::vector<cbCodeCompletionPlugin::CCToken>& tokens = ccPlugin->GetTokenAt(pos, ed, allowCallTip);
713 std::set<wxString> uniqueTips;
714 for (size_t i = 0; i < tokens.size(); ++i)
715 uniqueTips.insert(tokens[i].displayName);
716 wxStringVec tips(uniqueTips.begin(), uniqueTips.end());
717
718 const int style = event.GetInt();
719 if (!tips.empty())
720 {
721 const int tknStart = stc->WordStartPosition(pos, true);
722 const int tknEnd = stc->WordEndPosition(pos, true);
723 if (tknEnd - tknStart > 2)
724 {
725 for (size_t i = 0; i < tips[0].Length(); ++i)
726 {
727 size_t hlLoc = tips[0].find(stc->GetTextRange(tknStart, tknEnd), i);
728 if (hlLoc == wxString::npos)
729 break;
730 hlStart = hlLoc;
731 hlEnd = hlStart + tknEnd - tknStart;
732 if ( (hlStart > 0 && (tips[0][hlStart - 1] == wxT('_') || wxIsalpha(tips[0][hlStart - 1])))
733 || (hlEnd < static_cast<int>(tips[0].Length()) - 1 && (tips[0][hlEnd] == wxT('_') || wxIsalpha(tips[0][hlEnd]))) )
734 {
735 i = hlEnd;
736 hlStart = hlEnd = wxSCI_INVALID_POSITION;
737 }
738 else
739 break;
740 }
741 }
742 }
743 else if ( allowCallTip
744 && !( stc->IsString(style)
745 || stc->IsComment(style)
746 || stc->IsCharacter(style)
747 || stc->IsPreprocessor(style) ) )
748 {
749 const int line = stc->LineFromPosition(pos);
750 if (pos + 4 > stc->PositionFromLine(line) + (int)ed->GetLineIndentString(line).Length())
751 {
752 const CallTipVec& cTips = ccPlugin->GetCallTips(pos, style, ed, argsPos);
753 for (size_t i = 0; i < cTips.size(); ++i)
754 tips.push_back(cTips[i].tip);
755 if (!tips.empty())
756 {
757 hlStart = cTips[0].hlStart;
758 hlEnd = cTips[0].hlEnd;
759 }
760 }
761 }
762 if (tips.empty())
763 {
764 if (stc->CallTipActive() && event.GetExtraLong() == 0 && m_CallTipActive == wxSCI_INVALID_POSITION)
765 static_cast<wxScintilla*>(stc)->CallTipCancel();
766 }
767 else
768 {
769 DoShowTips(tips, stc, pos, argsPos, hlStart, hlEnd);
770 event.SetExtraLong(1);
771 }
772 m_CallTipActive = wxSCI_INVALID_POSITION;
773 }
774
OnEditorHook(cbEditor * ed,wxScintillaEvent & event)775 void CCManager::OnEditorHook(cbEditor* ed, wxScintillaEvent& event)
776 {
777 wxEventType evtType = event.GetEventType();
778 if (evtType == wxEVT_SCI_CHARADDED)
779 {
780 const wxChar ch = event.GetKey();
781 CCPluginCharMap::const_iterator ctChars = m_CallTipChars.find(GetProviderFor(ed));
782 if (ctChars == m_CallTipChars.end())
783 ctChars = m_CallTipChars.find(nullptr); // default
784
785 // Are there any characters which could trigger the call tip?
786 if (ctChars->second.find(ch) != ctChars->second.end())
787 {
788 int tooltipMode = Manager::Get()->GetConfigManager(wxT("ccmanager"))->ReadInt(wxT("/tooltip_mode"), 1);
789 if ( tooltipMode != 3 // keybound only
790 || m_CallTipActive != wxSCI_INVALID_POSITION )
791 {
792 wxCommandEvent pendingShow(cbEVT_DEFERRED_CALLTIP_SHOW);
793 AddPendingEvent(pendingShow);
794 }
795 }
796 else
797 {
798 cbStyledTextCtrl* stc = ed->GetControl();
799 const int pos = stc->GetCurrentPos();
800 const int wordStartPos = stc->WordStartPosition(pos, true);
801 CCPluginCharMap::const_iterator alChars = m_AutoLaunchChars.find(GetProviderFor(ed));
802 if (alChars == m_AutoLaunchChars.end())
803 alChars = m_AutoLaunchChars.find(nullptr); // default
804
805 // auto suggest list can be triggered either:
806 // 1, some number of chars are entered
807 // 2, an interested char belong to alChars is entered
808 int autolaunchCt = Manager::Get()->GetConfigManager(wxT("ccmanager"))->ReadInt(wxT("/auto_launch_count"), 3);
809 if ( (pos - wordStartPos >= autolaunchCt && !stc->AutoCompActive())
810 || pos - wordStartPos == autolaunchCt + 4 )
811 {
812 CodeBlocksEvent evt(cbEVT_COMPLETE_CODE);
813 Manager::Get()->ProcessEvent(evt);
814 }
815 else if (alChars->second.find(ch) != alChars->second.end())
816 {
817 m_AutoLaunchTimer.Start(10, wxTIMER_ONE_SHOT);
818 m_AutocompPosition = pos;
819 }
820 }
821 }
822 else if (evtType == wxEVT_SCI_UPDATEUI)
823 {
824 if (event.GetUpdated() & (wxSCI_UPDATE_V_SCROLL|wxSCI_UPDATE_H_SCROLL))
825 {
826 cbStyledTextCtrl* stc = ed->GetControl();
827 if (stc->CallTipActive())
828 {
829 // force to call the wxScintilla::CallTipCancel to avoid the smart indent condition check
830 // @see cbStyledTextCtrl::CallTipCancel() for the details
831 static_cast<wxScintilla*>(stc)->CallTipCancel();
832 if (m_CallTipActive != wxSCI_INVALID_POSITION && CCManagerHelper::IsPosVisible(m_CallTipActive, stc))
833 m_CallTipTimer.Start(SCROLL_REFRESH_DELAY, wxTIMER_ONE_SHOT);
834 }
835 else if (m_CallTipTimer.IsRunning())
836 {
837 if (CCManagerHelper::IsPosVisible(stc->GetCurrentPos(), stc))
838 m_CallTipTimer.Start(SCROLL_REFRESH_DELAY, wxTIMER_ONE_SHOT);
839 else
840 {
841 m_CallTipTimer.Stop();
842 m_CallTipActive = wxSCI_INVALID_POSITION;
843 }
844 }
845 if (m_AutoLaunchTimer.IsRunning())
846 {
847 if (CCManagerHelper::IsPosVisible(stc->GetCurrentPos(), stc))
848 m_AutoLaunchTimer.Start(SCROLL_REFRESH_DELAY, wxTIMER_ONE_SHOT);
849 else
850 m_AutoLaunchTimer.Stop();
851 }
852 else if (stc->AutoCompActive())
853 {
854 stc->AutoCompCancel();
855 m_AutocompPosition = stc->GetCurrentPos();
856 if (CCManagerHelper::IsPosVisible(m_AutocompPosition, stc))
857 m_AutoLaunchTimer.Start(SCROLL_REFRESH_DELAY, wxTIMER_ONE_SHOT);
858 }
859 }
860 }
861 else if (evtType == wxEVT_SCI_MODIFIED)
862 {
863 if (event.GetModificationType() & wxSCI_PERFORMED_UNDO)
864 {
865 cbStyledTextCtrl* stc = ed->GetControl();
866 if (m_CallTipActive != wxSCI_INVALID_POSITION && stc->GetCurrentPos() >= m_CallTipActive)
867 m_CallTipTimer.Start(CALLTIP_REFRESH_DELAY, wxTIMER_ONE_SHOT);
868 else
869 static_cast<wxScintilla*>(stc)->CallTipCancel();
870 }
871 }
872 else if (evtType == wxEVT_SCI_AUTOCOMP_SELECTION)
873 {
874 DoHidePopup();
875 cbCodeCompletionPlugin* ccPlugin = GetProviderFor(ed);
876 if (ccPlugin && m_OwnsAutocomp)
877 {
878 if ( m_LastAutocompIndex != wxNOT_FOUND
879 && m_LastAutocompIndex < (int)m_AutocompTokens.size() )
880 {
881 ccPlugin->DoAutocomplete(m_AutocompTokens[m_LastAutocompIndex], ed);
882 }
883 else // this case should not normally happen
884 {
885 ccPlugin->DoAutocomplete(event.GetText(), ed);
886 }
887 CallSmartIndentCCDone(ed);
888 }
889 }
890 else if (evtType == wxEVT_SCI_AUTOCOMP_CANCELLED)
891 DoHidePopup();
892 else if (evtType == wxEVT_SCI_CALLTIP_CLICK)
893 {
894 switch (event.GetPosition())
895 {
896 case 1: // up
897 AdvanceTip(Previous);
898 DoUpdateCallTip(ed);
899 break;
900
901 case 2: // down
902 AdvanceTip(Next);
903 DoUpdateCallTip(ed);
904 break;
905
906 case 0: // elsewhere
907 default:
908 break;
909 }
910 }
911 event.Skip();
912 }
913
914 // cbEVT_SHOW_CALL_TIP
915 // There are some caches used in this function
916 // see the comment "search long term recall" and "search short term recall"
917 // They remember which page on the calltip was last selected, and show that again first next time it
918 // is requested.
919 // Short term recall caches the current calltip, so it can be displayed exactly if a calltip is
920 // re-requested in the same location.
921 // Long term recall uses two dictionaries, the first one based on the tip's content, and will
922 // display the last shown page when a new calltip is requested that has the same content.
923 // The second (fuzzy) dictionary remembers which page was shown based on the typed word prefix
924 // (this is to support FortranProject, which uses more dynamic calltips).
OnShowCallTip(CodeBlocksEvent & event)925 void CCManager::OnShowCallTip(CodeBlocksEvent& event)
926 {
927 event.Skip();
928
929 int tooltipMode = Manager::Get()->GetConfigManager(wxT("ccmanager"))->ReadInt(wxT("/tooltip_mode"), 1);
930 // 0 - disable
931 // 1 - enable
932 // 2 - force single page
933 // 3 - keybound only
934 if (tooltipMode == tmDisable)
935 return;
936
937 cbEditor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
938 if (!ed)
939 return;
940
941 cbCodeCompletionPlugin* ccPlugin = GetProviderFor(ed);
942 if (!ccPlugin)
943 return;
944
945 cbStyledTextCtrl* stc = ed->GetControl();
946 if (!stc)
947 return;
948
949 int pos = stc->GetCurrentPos();
950 int argsPos = wxSCI_INVALID_POSITION;
951 // save the current tip shown text for later recalling
952 wxString curTip;
953 // check whether m_CurCallTip is invalid(point to the end of m_CallTips)
954 if (!m_CallTips.empty() && m_CurCallTip != m_CallTips.end())
955 curTip = m_CurCallTip->tip;
956
957 m_CallTips = ccPlugin->GetCallTips(pos, stc->GetStyleAt(pos), ed, argsPos);
958 // since m_CallTips get updated, we should update the m_CurCallTip, they are done in
959 // the following if/else statement.
960 if (!m_CallTips.empty() && (event.GetInt() != FROM_TIMER || argsPos == m_CallTipActive))
961 {
962 int lnStart = stc->PositionFromLine(stc->LineFromPosition(pos));
963 while (wxIsspace(stc->GetCharAt(lnStart)))
964 ++lnStart; // do not show too far left on multi-line call tips
965 if ( m_CallTips.size() > 1
966 && tooltipMode == tmForceSinglePage ) // force single page
967 {
968 wxString tip;
969 int hlStart, hlEnd;
970 hlStart = hlEnd = wxSCI_INVALID_POSITION;
971 for (CallTipVec::const_iterator itr = m_CallTips.begin();
972 itr != m_CallTips.end(); ++itr)
973 {
974 if (hlStart == hlEnd && itr->hlStart != itr->hlEnd)
975 {
976 hlStart = tip.Length() + itr->hlStart;
977 hlEnd = tip.Length() + itr->hlEnd;
978 }
979 tip += itr->tip + wxT('\n');
980 }
981 m_CallTips.clear();
982 m_CallTips.push_back(cbCodeCompletionPlugin::CCCallTip(tip.RemoveLast(), hlStart, hlEnd));
983 }
984 m_CurCallTip = m_CallTips.begin();
985 if (m_CallTips.size() > 1)
986 {
987 // search long term recall
988 // convert the wxString to a hash value, then search the hash value map cache
989 // thus we can avoid directly lookup the cache by wxString
990 std::map<int, size_t>::const_iterator choiceItr =
991 m_CallTipChoiceDict.find(CCManagerHelper::CallTipToInt(m_CurCallTip->tip, m_CallTips.size()));
992
993 // choiceItr is an iterator in the Dict, and choiceItr->second is the zero based index in m_CallTips
994 if (choiceItr != m_CallTipChoiceDict.end() && choiceItr->second < m_CallTips.size())
995 m_CurCallTip = m_CallTips.begin() + choiceItr->second;
996
997 if (choiceItr == m_CallTipChoiceDict.end() || argsPos == m_CallTipActive)
998 {
999 int prefixEndPos = argsPos;
1000 while (prefixEndPos > 0 && wxIsspace(stc->GetCharAt(prefixEndPos - 1)))
1001 --prefixEndPos;
1002
1003 // convert the prefix string to hash, check to see whether the hash is in the cache
1004 const wxString& prefix = stc->GetTextRange(stc->WordStartPosition(prefixEndPos, true), prefixEndPos);
1005 choiceItr = m_CallTipFuzzyChoiceDict.find(CCManagerHelper::CallTipToInt(prefix, m_CallTips.size()));
1006 if (choiceItr != m_CallTipFuzzyChoiceDict.end() && choiceItr->second < m_CallTips.size())
1007 m_CurCallTip = m_CallTips.begin() + choiceItr->second;
1008 }
1009 // search short term recall
1010 for (CallTipVec::const_iterator itr = m_CallTips.begin();
1011 itr != m_CallTips.end(); ++itr)
1012 {
1013 if (itr->tip == curTip)
1014 {
1015 m_CurCallTip = itr;
1016 break;
1017 }
1018 }
1019 }
1020 m_CallTipActive = argsPos;
1021 DoUpdateCallTip(ed);
1022 }
1023 else
1024 {
1025 if (m_CallTipActive != wxSCI_INVALID_POSITION)
1026 {
1027 static_cast<wxScintilla*>(stc)->CallTipCancel();
1028 m_CallTipActive = wxSCI_INVALID_POSITION;
1029 }
1030 // Make m_CurCallTip to be valid iterator, pointing to the end.
1031 m_CurCallTip = m_CallTips.end();
1032 }
1033 }
1034
OnAutocompleteSelect(wxListEvent & event)1035 void CCManager::OnAutocompleteSelect(wxListEvent& event)
1036 {
1037 event.Skip();
1038 m_AutocompSelectTimer.Start(AUTOCOMP_SELECT_DELAY, wxTIMER_ONE_SHOT);
1039 wxObject* evtObj = event.GetEventObject();
1040 if (!evtObj)
1041 return;
1042 #ifdef __WXMSW__
1043 m_pAutocompPopup = static_cast<wxListView*>(evtObj);
1044 #endif // __WXMSW__
1045
1046 wxWindow* evtWin = static_cast<wxWindow*>(evtObj)->GetParent();
1047 if (!evtWin)
1048 return;
1049
1050 m_DocPos = m_pPopup->GetParent()->ScreenToClient(evtWin->GetScreenPosition());
1051 m_DocPos.x += evtWin->GetSize().x;
1052 cbEditor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
1053 wxRect edRect = ed->GetRect();
1054 if (!m_pPopup->IsShown())
1055 {
1056 cbStyledTextCtrl* stc = ed->GetControl();
1057 int acMaxHeight = stc->AutoCompGetMaxHeight() + 1;
1058 int textHeight = stc->TextHeight(stc->GetCurrentLine());
1059 m_DocSize.x = edRect.width * 5 / 12;
1060 m_DocSize.y = acMaxHeight * textHeight;
1061 evtWin->Connect(wxEVT_SHOW, wxShowEventHandler(CCManager::OnAutocompleteHide), nullptr, this);
1062
1063 const int idx = wxDisplay::GetFromWindow(evtWin);
1064 m_WindowBound = m_DocPos.x + m_DocSize.x;
1065 if (idx != wxNOT_FOUND)
1066 {
1067 const wxPoint& corner = m_pPopup->GetParent()->ScreenToClient(wxDisplay(idx).GetGeometry().GetBottomRight());
1068 m_DocSize.y = std::max(9 * textHeight, std::min(m_DocSize.y, corner.y - m_DocPos.y - 2));
1069 m_DocSize.x = std::max(m_DocSize.y * 2 / 3, std::min(m_DocSize.x, corner.x - m_DocPos.x - 2));
1070 m_WindowBound = std::min(corner.x - 2, m_WindowBound);
1071 }
1072 }
1073 if ((m_DocPos.x + m_DocSize.x) > m_WindowBound)
1074 m_DocPos.x -= evtWin->GetSize().x + m_DocSize.x; // show to the left instead
1075 else
1076 m_DocSize.x = std::min(m_WindowBound - m_DocPos.x, edRect.width * 5 / 12);
1077 }
1078
1079 // Note: according to documentation, this event is only available under wxMSW, wxGTK, and wxOS2
OnAutocompleteHide(wxShowEvent & event)1080 void CCManager::OnAutocompleteHide(wxShowEvent& event)
1081 {
1082 event.Skip();
1083 DoHidePopup();
1084 wxObject* evtObj = event.GetEventObject();
1085 if (evtObj)
1086 static_cast<wxWindow*>(evtObj)->Disconnect(wxEVT_SHOW, wxShowEventHandler(CCManager::OnAutocompleteHide), nullptr, this);
1087 if (m_CallTipActive != wxSCI_INVALID_POSITION && !m_AutoLaunchTimer.IsRunning())
1088 m_CallTipTimer.Start(CALLTIP_REFRESH_DELAY, wxTIMER_ONE_SHOT);
1089 }
1090
1091 // cbEVT_DEFERRED_CALLTIP_SHOW
OnDeferredCallTipShow(wxCommandEvent & event)1092 void CCManager::OnDeferredCallTipShow(wxCommandEvent& event)
1093 {
1094 // Launching this event directly seems to be a candidate for race condition
1095 // and crash in OnShowCallTip() so we attempt to serialize it. See:
1096 // http://forums.codeblocks.org/index.php/topic,20181.msg137762.html#msg137762
1097 CodeBlocksEvent evt(cbEVT_SHOW_CALL_TIP);
1098 evt.SetInt(event.GetInt());
1099 Manager::Get()->ProcessEvent(evt);
1100 }
1101
1102 // cbEVT_DEFERRED_CALLTIP_CANCEL
OnDeferredCallTipCancel(wxCommandEvent & WXUNUSED (event))1103 void CCManager::OnDeferredCallTipCancel(wxCommandEvent& WXUNUSED(event))
1104 {
1105 cbEditor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
1106 if (ed)
1107 static_cast<wxScintilla*>(ed->GetControl())->CallTipCancel();
1108 }
1109
1110 #ifdef __WXMSW__
OnPopupScroll(wxMouseEvent & event)1111 void CCManager::OnPopupScroll(wxMouseEvent& event)
1112 {
1113 const wxPoint& pos = m_pLastEditor->GetControl()->ClientToScreen(event.GetPosition());
1114 if (m_pPopup->GetScreenRect().Contains(pos))
1115 m_pHtml->GetEventHandler()->ProcessEvent(event);
1116 else if (m_pAutocompPopup && m_pAutocompPopup->GetScreenRect().Contains(pos))
1117 m_pAutocompPopup->ScrollList(0, event.GetWheelRotation() / -4); // TODO: magic number... can we hook to the actual event?
1118 else
1119 event.Skip();
1120 }
1121 #endif // __WXMSW__
1122
OnHtmlLink(wxHtmlLinkEvent & event)1123 void CCManager::OnHtmlLink(wxHtmlLinkEvent& event)
1124 {
1125 cbCodeCompletionPlugin* ccPlugin = GetProviderFor();
1126 if (!ccPlugin)
1127 return;
1128
1129 bool dismissPopup = false;
1130 const wxString& html = ccPlugin->OnDocumentationLink(event, dismissPopup);
1131 if (dismissPopup)
1132 DoHidePopup();
1133 else if (!html.IsEmpty())
1134 m_pHtml->SetPage(html);
1135 // plugins are responsible to skip this event (if they choose to)
1136 }
1137
OnTimer(wxTimerEvent & event)1138 void CCManager::OnTimer(wxTimerEvent& event)
1139 {
1140 if (event.GetId() == idCallTipTimer) // m_CallTipTimer
1141 {
1142 wxCommandEvent evt(cbEVT_DEFERRED_CALLTIP_SHOW);
1143 evt.SetInt(FROM_TIMER);
1144 AddPendingEvent(evt);
1145 }
1146 else if (event.GetId() == idAutoLaunchTimer) // m_AutoLaunchTimer
1147 {
1148 cbEditor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
1149 if (ed && ed->GetControl()->GetCurrentPos() == m_AutocompPosition)
1150 {
1151 CodeBlocksEvent evt(cbEVT_COMPLETE_CODE);
1152 evt.SetInt(FROM_TIMER);
1153 Manager::Get()->ProcessEvent(evt);
1154 }
1155 m_AutocompPosition = wxSCI_INVALID_POSITION;
1156 }
1157 else if (event.GetId() == idAutocompSelectTimer) // m_AutocompSelectTimer
1158 {
1159 cbEditor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
1160 if (ed)
1161 {
1162 cbStyledTextCtrl* stc = ed->GetControl();
1163 if (stc->AutoCompActive())
1164 {
1165 m_LastAutocompIndex = stc->AutoCompGetCurrent();
1166 DoShowDocumentation(ed);
1167 }
1168 }
1169 }
1170 else // ?!
1171 event.Skip();
1172 }
1173
OnMenuSelect(wxCommandEvent & event)1174 void CCManager::OnMenuSelect(wxCommandEvent& event)
1175 {
1176 cbEditor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
1177 if (!ed)
1178 return;
1179 if (event.GetId() == idShowTooltip)
1180 {
1181 // compare this with cbStyledTextCtrl::EmulateDwellStart()
1182 cbStyledTextCtrl* stc = ed->GetControl();
1183 CodeBlocksEvent evt(cbEVT_EDITOR_TOOLTIP);
1184 wxPoint pt = stc->PointFromPosition(stc->GetCurrentPos());
1185 evt.SetX(pt.x);
1186 evt.SetY(pt.y);
1187 evt.SetInt(stc->GetStyleAt(stc->GetCurrentPos()));
1188 evt.SetEditor(ed);
1189 evt.SetExtraLong(0);
1190 evt.SetString(wxT("evt from menu"));
1191 Manager::Get()->ProcessEvent(evt);
1192 return;
1193 }
1194 if (m_CallTips.empty() || m_CallTipActive == wxSCI_INVALID_POSITION)
1195 return;
1196 if (!ed->GetControl()->CallTipActive())
1197 return;
1198 if (event.GetId() == idCallTipNext)
1199 {
1200 AdvanceTip(Next);
1201 DoUpdateCallTip(ed);
1202 }
1203 else if (event.GetId() == idCallTipPrevious)
1204 {
1205 AdvanceTip(Previous);
1206 DoUpdateCallTip(ed);
1207 }
1208 }
1209
DoBufferedCC(cbStyledTextCtrl * stc)1210 void CCManager::DoBufferedCC(cbStyledTextCtrl* stc)
1211 {
1212 if (stc->AutoCompActive())
1213 return; // already active, no need to rebuild
1214 // fill list of suggestions
1215 wxString items;
1216 items.Alloc(m_AutocompTokens.size() * 20);
1217 for (size_t i = 0; i < m_AutocompTokens.size(); ++i)
1218 {
1219 items += m_AutocompTokens[i].displayName;
1220 if (m_AutocompTokens[i].category == -1)
1221 items += wxT("\r");
1222 else
1223 items += F(wxT("\n%d\r"), m_AutocompTokens[i].category);
1224 }
1225 items.RemoveLast();
1226 if (!stc->CallTipActive())
1227 m_CallTipActive = wxSCI_INVALID_POSITION;
1228 // display
1229 stc->AutoCompShow(m_LastACLaunchState.caretStart - m_LastACLaunchState.tokenStart, items);
1230 m_OwnsAutocomp = true;
1231
1232 // We need to check if the auto completion is active, because if there are no matches scintilla will close
1233 // the popup and any call to AutoCompSelect will result in a crash.
1234 if (stc->AutoCompActive() &&
1235 (m_LastAutocompIndex != wxNOT_FOUND && m_LastAutocompIndex < (int)m_AutocompTokens.size()))
1236 {
1237 // re-select last selected entry
1238 const cbCodeCompletionPlugin::CCToken& token = m_AutocompTokens[m_LastAutocompIndex];
1239 const int sepIdx = token.displayName.Find('\n', true);
1240 if (sepIdx == wxNOT_FOUND)
1241 stc->AutoCompSelect(token.displayName);
1242 else
1243 stc->AutoCompSelect(token.displayName.Mid(0, sepIdx));
1244 }
1245 }
1246
DoHidePopup()1247 void CCManager::DoHidePopup()
1248 {
1249 if (!m_pPopup->IsShown())
1250 return;
1251 m_pPopup->Hide();
1252 #ifdef __WXMSW__
1253 if (m_pLastEditor && m_pLastEditor->GetControl())
1254 m_pLastEditor->GetControl()->Disconnect(wxEVT_MOUSEWHEEL, wxMouseEventHandler(CCManager::OnPopupScroll), nullptr, this);
1255 #endif // __WXMSW__
1256 }
1257
DoShowDocumentation(cbEditor * ed)1258 void CCManager::DoShowDocumentation(cbEditor* ed)
1259 {
1260 if (!Manager::Get()->GetConfigManager(wxT("ccmanager"))->ReadBool(wxT("/documentation_popup"), true))
1261 return;
1262
1263 cbCodeCompletionPlugin* ccPlugin = GetProviderFor(ed);
1264 if (!ccPlugin)
1265 return;
1266 if ( m_LastAutocompIndex == wxNOT_FOUND
1267 || m_LastAutocompIndex >= (int)m_AutocompTokens.size() )
1268 {
1269 return;
1270 }
1271 const wxString& html = ccPlugin->GetDocumentation(m_AutocompTokens[m_LastAutocompIndex]);
1272 if (html.IsEmpty())
1273 {
1274 DoHidePopup();
1275 return;
1276 }
1277
1278 m_pPopup->Freeze();
1279 m_pHtml->SetSize(m_DocSize);
1280 m_pHtml->SetPage(html);
1281 m_pPopup->SetClientSize(m_DocSize);
1282 m_pPopup->SetPosition(m_DocPos);
1283 m_pPopup->Thaw();
1284 if (!m_pPopup->IsShown())
1285 {
1286 m_pPopup->Show();
1287 #ifdef __WXMSW__
1288 ed->GetControl()->Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler(CCManager::OnPopupScroll), nullptr, this);
1289 #endif // __WXMSW__
1290 }
1291 }
1292
DoUpdateCallTip(cbEditor * ed)1293 void CCManager::DoUpdateCallTip(cbEditor* ed)
1294 {
1295 wxStringVec tips;
1296 int hlStart = m_CurCallTip->hlStart;
1297 int hlEnd = m_CurCallTip->hlEnd;
1298 size_t sRange = 0;
1299 size_t eRange = m_CurCallTip->tip.find(wxT('\n'));
1300 while (eRange != wxString::npos)
1301 {
1302 tips.push_back(m_CurCallTip->tip.Mid(sRange, eRange - sRange));
1303 CCManagerHelper::RipplePts(hlStart, hlEnd, eRange, -1);
1304 sRange = eRange + 1;
1305 eRange = m_CurCallTip->tip.find(wxT('\n'), sRange);
1306 }
1307 if (sRange < m_CurCallTip->tip.Length())
1308 tips.push_back(m_CurCallTip->tip.Mid(sRange));
1309 // for multiple tips (such as tips for some overload functions)
1310 // some more text were added to the single tip, such as the up/down arrows, the x/y indicator.
1311 int offset = 0;
1312 cbStyledTextCtrl* stc = ed->GetControl();
1313 if (m_CallTips.size() > 1)
1314 {
1315 tips.front().Prepend(wxT("\001\002")); // up/down arrows
1316 offset += 2;
1317
1318 // display (curTipNumber/totalTipCount)
1319 wxString tip;
1320 tip << wxT("(") << (m_CurCallTip - m_CallTips.begin() + 1) << wxT("/") << m_CallTips.size() << wxT(")");
1321
1322 tips.push_back(tip);
1323 // store for better first choice later
1324 m_CallTipChoiceDict[CCManagerHelper::CallTipToInt(m_CallTips.front().tip, m_CallTips.size())] = m_CurCallTip - m_CallTips.begin();
1325 // fuzzy store
1326 int prefixEndPos = m_CallTipActive;
1327 while (prefixEndPos > 0 && wxIsspace(stc->GetCharAt(prefixEndPos - 1)))
1328 --prefixEndPos;
1329 const wxString& prefix = stc->GetTextRange(stc->WordStartPosition(prefixEndPos, true), prefixEndPos);
1330 m_CallTipFuzzyChoiceDict[CCManagerHelper::CallTipToInt(prefix, m_CallTips.size())] = m_CurCallTip - m_CallTips.begin();
1331 }
1332 int pos = stc->GetCurrentPos();
1333 int lnStart = stc->PositionFromLine(stc->LineFromPosition(pos));
1334 while (wxIsspace(stc->GetCharAt(lnStart)))
1335 ++lnStart;
1336 #ifdef __WXMSW__
1337 m_LastTipPos = wxSCI_INVALID_POSITION; // Windows hack to fix display update
1338 #endif // __WXMSW__
1339 DoShowTips(tips, stc, std::max(pos, lnStart), m_CallTipActive, hlStart + offset, hlEnd + offset);
1340 }
1341
DoShowTips(const wxStringVec & tips,cbStyledTextCtrl * stc,int pos,int argsPos,int hlStart,int hlEnd)1342 void CCManager::DoShowTips(const wxStringVec& tips, cbStyledTextCtrl* stc, int pos, int argsPos, int hlStart, int hlEnd)
1343 {
1344 int maxLines = std::max(stc->LinesOnScreen() / 4, 5);
1345 int marginWidth = stc->GetMarginWidth(wxSCI_MARGIN_SYMBOL) + stc->GetMarginWidth(wxSCI_MARGIN_NUMBER);
1346 int maxWidth = (stc->GetSize().x - marginWidth) / stc->TextWidth(wxSCI_STYLE_LINENUMBER, wxT("W")) - 1;
1347 maxWidth = std::min(std::max(60, maxWidth), 135);
1348 wxString tip;
1349 int lineCount = 0;
1350 wxString lineBreak = wxT('\n');
1351 if (!tips.front().IsEmpty() && tips.front()[0] <= wxT('\002'))
1352 {
1353 // indent line break as needed, if tip prefixed with up/down arrows
1354 lineBreak += wxT(' ');
1355 if (tips.front().Length() > 1 && tips.front()[1] <= wxT('\002'))
1356 lineBreak += wxT(" ");
1357 }
1358
1359 for (size_t i = 0; i < tips.size() && lineCount < maxLines; ++i)
1360 {
1361 if (tips[i].Length() > (size_t)maxWidth + 6) // line is too long, try breaking it
1362 {
1363 wxString tipLn = tips[i];
1364 while (!tipLn.IsEmpty())
1365 {
1366 wxString segment = tipLn.Mid(0, maxWidth);
1367 int index = segment.Find(wxT(' '), true); // break on a space
1368 if (index < 20) // no reasonable break?
1369 {
1370 segment = tipLn.Mid(0, maxWidth * 6 / 5); // increase search width a bit
1371 index = segment.Find(wxT(' '), true);
1372 }
1373 for (int commaIdx = index - 1; commaIdx > maxWidth / 2; --commaIdx) // check back for a comma
1374 {
1375 if (segment[commaIdx] == wxT(',') && segment[commaIdx + 1] == wxT(' '))
1376 {
1377 index = commaIdx + 1; // prefer splitting on a comma, if that does not set us back too far
1378 break;
1379 }
1380 }
1381 if (index < 20 || segment == tipLn) // end of string, or cannot split
1382 {
1383 tip += tipLn + lineBreak;
1384 CCManagerHelper::RipplePts(hlStart, hlEnd, tip.Length(), lineBreak.Length());
1385 tipLn.Clear();
1386 }
1387 else // continue splitting
1388 {
1389 tip += segment.Mid(0, index) + lineBreak + wxT(' ');
1390 CCManagerHelper::RipplePts(hlStart, hlEnd, tip.Length(), lineBreak.Length() + 1);
1391 // already starts with a space, so all subsequent lines are prefixed by two spaces
1392 tipLn = tipLn.Mid(index);
1393 }
1394 ++lineCount;
1395 }
1396 }
1397 else // just add the line
1398 {
1399 tip += tips[i] + lineBreak;
1400 CCManagerHelper::RipplePts(hlStart, hlEnd, tip.Length(), lineBreak.Length());
1401 ++lineCount;
1402 }
1403 }
1404 tip.Trim(); // trailing linefeed
1405
1406 // try to show the tip at the start of the token/arguments, or at the margin if we are scrolled right
1407 // an offset of 2 helps deal with the width of the folding bar (TODO: does an actual calculation exist?)
1408 int line = stc->LineFromPosition(pos);
1409 if (argsPos == wxSCI_INVALID_POSITION)
1410 argsPos = stc->WordStartPosition(pos, true);
1411 else
1412 argsPos = std::min(CCManagerHelper::FindColumn(line, stc->GetColumn(argsPos), stc), stc->WordStartPosition(pos, true));
1413 int offset = stc->PointFromPosition(stc->PositionFromLine(line)).x > marginWidth ? 0 : 2;
1414 pos = std::max(argsPos, stc->PositionFromPoint(wxPoint(marginWidth, stc->PointFromPosition(pos).y)) + offset);
1415 pos = std::min(pos, stc->GetLineEndPosition(line)); // do not go to next line
1416 if (stc->CallTipActive() && m_LastTipPos != pos)
1417 stc->CallTipCancel(); // force tip popup to invalidate (sometimes fails to otherwise do so on Windows)
1418 stc->CallTipShow(pos, tip);
1419 if (hlStart >= 0 && hlEnd > hlStart)
1420 stc->CallTipSetHighlight(hlStart, hlEnd);
1421 m_LastTipPos = pos;
1422 }
1423
CallSmartIndentCCDone(cbEditor * ed)1424 void CCManager::CallSmartIndentCCDone(cbEditor* ed)
1425 {
1426 CodeBlocksEvent event(cbEVT_EDITOR_CC_DONE);
1427 event.SetEditor(ed);
1428 // post event in the host's event queue
1429 Manager::Get()->ProcessEvent(event);
1430 }
1431