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