1 /*
2 * This file is part of the Code::Blocks IDE and licensed under the GNU General Public License, version 3
3 * http://www.gnu.org/licenses/gpl-3.0.html
4 *
5 * $Revision: 11838 $
6 * $Id: codecompletion.cpp 11838 2019-09-02 19:27:23Z pecanh $
7 * $HeadURL: svn://svn.code.sf.net/p/codeblocks/code/branches/release-20.xx/src/plugins/codecompletion/codecompletion.cpp $
8 */
9
10 #include <sdk.h>
11
12 #ifndef CB_PRECOMP
13 #include <algorithm>
14 #include <iterator>
15 #include <set> // for handling unique items in some places
16
17 #include <wx/choicdlg.h>
18 #include <wx/choice.h>
19 #include <wx/dir.h>
20 #include <wx/filename.h>
21 #include <wx/fs_zip.h>
22 #include <wx/menu.h>
23 #include <wx/mimetype.h>
24 #include <wx/msgdlg.h>
25 #include <wx/regex.h>
26 #include <wx/tipwin.h>
27 #include <wx/toolbar.h>
28 #include <wx/utils.h>
29 #include <wx/xrc/xmlres.h>
30 #include <wx/wxscintilla.h>
31
32 #include <cbeditor.h>
33 #include <configmanager.h>
34 #include <editorcolourset.h>
35 #include <editormanager.h>
36 #include <globals.h>
37 #include <logmanager.h>
38 #include <macrosmanager.h>
39 #include <manager.h>
40 #include <projectmanager.h>
41 #include <sdk_events.h>
42 #endif
43
44 #include <wx/tokenzr.h>
45 #include <wx/html/htmlwin.h>
46
47 #include <cbstyledtextctrl.h>
48 #include <editor_hooks.h>
49 #include <filegroupsandmasks.h>
50 #include <multiselectdlg.h>
51
52 #include "codecompletion.h"
53
54 #include "ccoptionsdlg.h"
55 #include "ccoptionsprjdlg.h"
56 #include "insertclassmethoddlg.h"
57 #include "selectincludefile.h"
58 #include "parser/ccdebuginfo.h"
59 #include "parser/cclogger.h"
60 #include "parser/parser.h"
61 #include "parser/tokenizer.h"
62 #include "doxygen_parser.h" // for DocumentationPopup and DoxygenParser
63 #include "gotofunctiondlg.h"
64
65 #define CC_CODECOMPLETION_DEBUG_OUTPUT 0
66
67 // let the global debug macro overwrite the local debug macro value
68 #if defined(CC_GLOBAL_DEBUG_OUTPUT)
69 #undef CC_CODECOMPLETION_DEBUG_OUTPUT
70 #define CC_CODECOMPLETION_DEBUG_OUTPUT CC_GLOBAL_DEBUG_OUTPUT
71 #endif
72
73 #if CC_CODECOMPLETION_DEBUG_OUTPUT == 1
74 #define TRACE(format, args...) \
75 CCLogger::Get()->DebugLog(F(format, ##args))
76 #define TRACE2(format, args...)
77 #elif CC_CODECOMPLETION_DEBUG_OUTPUT == 2
78 #define TRACE(format, args...) \
79 do \
80 { \
81 if (g_EnableDebugTrace) \
82 CCLogger::Get()->DebugLog(F(format, ##args)); \
83 } \
84 while (false)
85 #define TRACE2(format, args...) \
86 CCLogger::Get()->DebugLog(F(format, ##args))
87 #else
88 #define TRACE(format, args...)
89 #define TRACE2(format, args...)
90 #endif
91
92 /// Scopes choice name for global functions in CC's toolbar.
93 static wxString g_GlobalScope(_T("<global>"));
94
95 // this auto-registers the plugin
96 namespace
97 {
98 PluginRegistrant<CodeCompletion> reg(_T("CodeCompletion"));
99 }
100
101 namespace CodeCompletionHelper
102 {
103 // compare method for the sort algorithm for our FunctionScope struct
LessFunctionScope(const CodeCompletion::FunctionScope & fs1,const CodeCompletion::FunctionScope & fs2)104 inline bool LessFunctionScope(const CodeCompletion::FunctionScope& fs1, const CodeCompletion::FunctionScope& fs2)
105 {
106 int result = wxStricmp(fs1.Scope, fs2.Scope);
107 if (result == 0)
108 {
109 result = wxStricmp(fs1.Name, fs2.Name);
110 if (result == 0)
111 result = fs1.StartLine - fs2.StartLine;
112 }
113
114 return result < 0;
115 }
116
EqualFunctionScope(const CodeCompletion::FunctionScope & fs1,const CodeCompletion::FunctionScope & fs2)117 inline bool EqualFunctionScope(const CodeCompletion::FunctionScope& fs1, const CodeCompletion::FunctionScope& fs2)
118 {
119 int result = wxStricmp(fs1.Scope, fs2.Scope);
120 if (result == 0)
121 result = wxStricmp(fs1.Name, fs2.Name);
122
123 return result == 0;
124 }
125
LessNameSpace(const NameSpace & ns1,const NameSpace & ns2)126 inline bool LessNameSpace(const NameSpace& ns1, const NameSpace& ns2)
127 {
128 return ns1.Name < ns2.Name;
129 }
130
EqualNameSpace(const NameSpace & ns1,const NameSpace & ns2)131 inline bool EqualNameSpace(const NameSpace& ns1, const NameSpace& ns2)
132 {
133 return ns1.Name == ns2.Name;
134 }
135
136 /// for OnGotoFunction(), search backward
137 /// @code
138 /// xxxxx /* yyy */
139 /// ^ ^
140 /// result begin
141 /// @endcode
GetLastNonWhitespaceChar(cbStyledTextCtrl * control,int position)142 inline wxChar GetLastNonWhitespaceChar(cbStyledTextCtrl* control, int position)
143 {
144 if (!control)
145 return 0;
146
147 while (--position > 0)
148 {
149 const int style = control->GetStyleAt(position);
150 if (control->IsComment(style))
151 continue;
152
153 const wxChar ch = control->GetCharAt(position);
154 if (ch <= _T(' '))
155 continue;
156
157 return ch;
158 }
159
160 return 0;
161 }
162
163 /// for OnGotoFunction(), search forward
164 /// /* yyy */ xxxxx
165 /// ^ ^
166 /// begin result
GetNextNonWhitespaceChar(cbStyledTextCtrl * control,int position)167 inline wxChar GetNextNonWhitespaceChar(cbStyledTextCtrl* control, int position)
168 {
169 if (!control)
170 return 0;
171
172 const int totalLength = control->GetLength();
173 --position;
174 while (++position < totalLength)
175 {
176 const int style = control->GetStyleAt(position);
177 if (control->IsComment(style))
178 continue;
179
180 const wxChar ch = control->GetCharAt(position);
181 if (ch <= _T(' '))
182 continue;
183
184 return ch;
185 }
186
187 return 0;
188 }
189
190 /** Sorting in GetLocalIncludeDirs() */
CompareStringLen(const wxString & first,const wxString & second)191 inline int CompareStringLen(const wxString& first, const wxString& second)
192 {
193 return second.Len() - first.Len();
194 }
195
196 /** for CodeCompleteIncludes()
197 * a line has some pattern like below
198 @code
199 # [space or tab] include
200 @endcode
201 */
TestIncludeLine(wxString const & line)202 inline bool TestIncludeLine(wxString const &line)
203 {
204 size_t index = line.find(_T('#'));
205 if (index == wxString::npos)
206 return false;
207 ++index;
208
209 for (; index < line.length(); ++index)
210 {
211 if (line[index] != _T(' ') && line[index] != _T('\t'))
212 {
213 if (line.Mid(index, 7) == _T("include"))
214 return true;
215 break;
216 }
217 }
218 return false;
219 }
220
221 /** return identifier like token string under the current cursor pointer
222 * @param[out] NameUnderCursor the identifier like token string
223 * @param[out] IsInclude true if it is a #include command
224 * @return true if the underlining text is a #include command, or a normal identifier
225 */
EditorHasNameUnderCursor(wxString & NameUnderCursor,bool & IsInclude)226 inline bool EditorHasNameUnderCursor(wxString& NameUnderCursor, bool& IsInclude)
227 {
228 bool ReturnValue = false;
229 if (cbEditor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor())
230 {
231 cbStyledTextCtrl* control = ed->GetControl();
232 const int pos = control->GetCurrentPos();
233 const wxString line = control->GetLine(control->LineFromPosition(pos));
234 const wxRegEx reg(_T("^[ \t]*#[ \t]*include[ \t]+[\"<]([^\">]+)[\">]"));
235 wxString inc;
236 if (reg.Matches(line))
237 inc = reg.GetMatch(line, 1);
238
239 if (!inc.IsEmpty())
240 {
241 NameUnderCursor = inc;
242 ReturnValue = true;
243 IsInclude = true;
244 }
245 else
246 {
247 const int start = control->WordStartPosition(pos, true);
248 const int end = control->WordEndPosition(pos, true);
249 const wxString word = control->GetTextRange(start, end);
250 if (!word.IsEmpty())
251 {
252 NameUnderCursor.Clear();
253 NameUnderCursor << word;
254 ReturnValue = true;
255 IsInclude = false;
256 }
257 }
258 }
259 return ReturnValue;
260 }
261 /** used to record the position of a token when user click find declaration or implementation */
262 struct GotoDeclarationItem
263 {
264 wxString filename;
265 unsigned line;
266 };
267
268 /** when user select one item in the suggestion list, the selected contains the full display
269 * name, for example, "function_name():function_return_type", and we only need to insert the
270 * "function_name" to the editor, so this function just get the actual inserted text.
271 * @param selected a full display name of the selected token in the suggestion list
272 * @return the stripped text which are used to insert to the editor
273 */
AutocompGetName(const wxString & selected)274 static wxString AutocompGetName(const wxString& selected)
275 {
276 size_t nameEnd = selected.find_first_of(_T("(: "));
277 return selected.substr(0,nameEnd);
278 }
279
280 }//namespace CodeCompletionHelper
281
282 // menu IDs
283 // just because we don't know other plugins' used identifiers,
284 // we use wxNewId() to generate a guaranteed unique ID ;), instead of enum
285 // (don't forget that, especially in a plugin)
286 // used in the wxFrame's main menu
287 int idMenuGotoFunction = wxNewId();
288 int idMenuGotoPrevFunction = wxNewId();
289 int idMenuGotoNextFunction = wxNewId();
290 int idMenuGotoDeclaration = wxNewId();
291 int idMenuGotoImplementation = wxNewId();
292 int idMenuOpenIncludeFile = wxNewId();
293 int idMenuFindReferences = wxNewId();
294 int idMenuRenameSymbols = wxNewId();
295 int idViewClassBrowser = wxNewId();
296 // used in context menu
297 int idCurrentProjectReparse = wxNewId();
298 int idSelectedProjectReparse = wxNewId();
299 int idSelectedFileReparse = wxNewId();
300 int idEditorSubMenu = wxNewId();
301 int idClassMethod = wxNewId();
302 int idUnimplementedClassMethods = wxNewId();
303 int idGotoDeclaration = wxNewId();
304 int idGotoImplementation = wxNewId();
305 int idOpenIncludeFile = wxNewId();
306
307 int idRealtimeParsingTimer = wxNewId();
308 int idToolbarTimer = wxNewId();
309 int idProjectSavedTimer = wxNewId();
310 int idReparsingTimer = wxNewId();
311 int idEditorActivatedTimer = wxNewId();
312
313 // all the below delay time is in milliseconds units
314 // when the user enables the parsing while typing option, this is the time delay when parsing
315 // would happen after the editor has changed.
316 #define REALTIME_PARSING_DELAY 500
317
318 // there are many reasons to trigger the refreshing of CC toolbar. But to avoid refreshing
319 // the toolbar too often, we add a timer to delay the refresh, this is just like a mouse dwell
320 // event, which means we do the real job when the editor is stable for a while (no event
321 // happens in the delay time period).
322 #define TOOLBAR_REFRESH_DELAY 150
323
324 // the time delay between an editor activated event and the updating of the CC toolbar.
325 // Note that we are only interest in a stable activated editor, so if another editor is activated
326 // during the time delay, the timer will be restarted.
327 #define EDITOR_ACTIVATED_DELAY 300
328
BEGIN_EVENT_TABLE(CodeCompletion,cbCodeCompletionPlugin)329 BEGIN_EVENT_TABLE(CodeCompletion, cbCodeCompletionPlugin)
330 EVT_UPDATE_UI_RANGE(idMenuGotoFunction, idCurrentProjectReparse, CodeCompletion::OnUpdateUI)
331
332 EVT_MENU(idMenuGotoFunction, CodeCompletion::OnGotoFunction )
333 EVT_MENU(idMenuGotoPrevFunction, CodeCompletion::OnGotoPrevFunction )
334 EVT_MENU(idMenuGotoNextFunction, CodeCompletion::OnGotoNextFunction )
335 EVT_MENU(idMenuGotoDeclaration, CodeCompletion::OnGotoDeclaration )
336 EVT_MENU(idMenuGotoImplementation, CodeCompletion::OnGotoDeclaration )
337 EVT_MENU(idMenuFindReferences, CodeCompletion::OnFindReferences )
338 EVT_MENU(idMenuRenameSymbols, CodeCompletion::OnRenameSymbols )
339 EVT_MENU(idClassMethod, CodeCompletion::OnClassMethod )
340 EVT_MENU(idUnimplementedClassMethods, CodeCompletion::OnUnimplementedClassMethods)
341 EVT_MENU(idGotoDeclaration, CodeCompletion::OnGotoDeclaration )
342 EVT_MENU(idGotoImplementation, CodeCompletion::OnGotoDeclaration )
343 EVT_MENU(idOpenIncludeFile, CodeCompletion::OnOpenIncludeFile )
344 EVT_MENU(idMenuOpenIncludeFile, CodeCompletion::OnOpenIncludeFile )
345
346 EVT_MENU(idViewClassBrowser, CodeCompletion::OnViewClassBrowser )
347 EVT_MENU(idCurrentProjectReparse, CodeCompletion::OnCurrentProjectReparse )
348 EVT_MENU(idSelectedProjectReparse, CodeCompletion::OnSelectedProjectReparse)
349 EVT_MENU(idSelectedFileReparse, CodeCompletion::OnSelectedFileReparse )
350
351 // CC's toolbar
352 EVT_CHOICE(XRCID("chcCodeCompletionScope"), CodeCompletion::OnScope )
353 EVT_CHOICE(XRCID("chcCodeCompletionFunction"), CodeCompletion::OnFunction)
354 END_EVENT_TABLE()
355
356 CodeCompletion::CodeCompletion() :
357 m_InitDone(false),
358 m_CodeRefactoring(m_NativeParser),
359 m_EditorHookId(0),
360 m_TimerRealtimeParsing(this, idRealtimeParsingTimer),
361 m_TimerToolbar(this, idToolbarTimer),
362 m_TimerProjectSaved(this, idProjectSavedTimer),
363 m_TimerReparsing(this, idReparsingTimer),
364 m_TimerEditorActivated(this, idEditorActivatedTimer),
365 m_LastEditor(0),
366 m_ToolBar(0),
367 m_Function(0),
368 m_Scope(0),
369 m_ToolbarNeedRefresh(true),
370 m_ToolbarNeedReparse(false),
371 m_CurrentLine(0),
372 m_NeedReparse(false),
373 m_CurrentLength(-1),
374 m_NeedsBatchColour(true),
375 m_CCMaxMatches(16384),
376 m_CCAutoAddParentheses(true),
377 m_CCDetectImplementation(false),
378 m_CCEnableHeaders(false),
379 m_CCEnablePlatformCheck(true),
380 m_SystemHeadersThreadCS(),
381 m_DocHelper(this)
382 {
383 // CCLogger are the log event bridges, those events were finally handled by its parent, here
384 // it is the CodeCompletion plugin ifself.
385 CCLogger::Get()->Init(this, g_idCCLogger, g_idCCDebugLogger);
386
387 if (!Manager::LoadResource(_T("codecompletion.zip")))
388 NotifyMissingFile(_T("codecompletion.zip"));
389
390 // handling events send from CCLogger
391 Connect(g_idCCLogger, wxEVT_COMMAND_MENU_SELECTED, CodeBlocksThreadEventHandler(CodeCompletion::OnCCLogger) );
392 Connect(g_idCCDebugLogger, wxEVT_COMMAND_MENU_SELECTED, CodeBlocksThreadEventHandler(CodeCompletion::OnCCDebugLogger));
393
394 // the two events below were generated from NativeParser, as currently, CodeCompletionPlugin is
395 // set as the next event handler for m_NativeParser, so it get chance to handle them.
396 Connect(ParserCommon::idParserStart, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(CodeCompletion::OnParserStart) );
397 Connect(ParserCommon::idParserEnd, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(CodeCompletion::OnParserEnd) );
398
399 Connect(idRealtimeParsingTimer, wxEVT_TIMER, wxTimerEventHandler(CodeCompletion::OnRealtimeParsingTimer));
400 Connect(idToolbarTimer, wxEVT_TIMER, wxTimerEventHandler(CodeCompletion::OnToolbarTimer) );
401 Connect(idProjectSavedTimer, wxEVT_TIMER, wxTimerEventHandler(CodeCompletion::OnProjectSavedTimer) );
402 Connect(idReparsingTimer, wxEVT_TIMER, wxTimerEventHandler(CodeCompletion::OnReparsingTimer) );
403 Connect(idEditorActivatedTimer, wxEVT_TIMER, wxTimerEventHandler(CodeCompletion::OnEditorActivatedTimer));
404
405 Connect(idSystemHeadersThreadMessage, wxEVT_COMMAND_MENU_SELECTED,
406 CodeBlocksThreadEventHandler(CodeCompletion::OnSystemHeadersThreadMessage));
407 Connect(idSystemHeadersThreadFinish, wxEVT_COMMAND_MENU_SELECTED,
408 CodeBlocksThreadEventHandler(CodeCompletion::OnSystemHeadersThreadFinish));
409 }
410
~CodeCompletion()411 CodeCompletion::~CodeCompletion()
412 {
413 Disconnect(g_idCCLogger, wxEVT_COMMAND_MENU_SELECTED, CodeBlocksThreadEventHandler(CodeCompletion::OnCCLogger));
414 Disconnect(g_idCCDebugLogger, wxEVT_COMMAND_MENU_SELECTED, CodeBlocksThreadEventHandler(CodeCompletion::OnCCDebugLogger));
415 Disconnect(ParserCommon::idParserStart, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(CodeCompletion::OnParserStart));
416 Disconnect(ParserCommon::idParserEnd, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(CodeCompletion::OnParserEnd));
417
418 Disconnect(idRealtimeParsingTimer, wxEVT_TIMER, wxTimerEventHandler(CodeCompletion::OnRealtimeParsingTimer));
419 Disconnect(idToolbarTimer, wxEVT_TIMER, wxTimerEventHandler(CodeCompletion::OnToolbarTimer) );
420 Disconnect(idProjectSavedTimer, wxEVT_TIMER, wxTimerEventHandler(CodeCompletion::OnProjectSavedTimer) );
421 Disconnect(idReparsingTimer, wxEVT_TIMER, wxTimerEventHandler(CodeCompletion::OnReparsingTimer) );
422 Disconnect(idEditorActivatedTimer, wxEVT_TIMER, wxTimerEventHandler(CodeCompletion::OnEditorActivatedTimer));
423
424 Disconnect(idSystemHeadersThreadMessage, wxEVT_COMMAND_MENU_SELECTED,
425 CodeBlocksThreadEventHandler(CodeCompletion::OnSystemHeadersThreadMessage));
426 Disconnect(idSystemHeadersThreadFinish, wxEVT_COMMAND_MENU_SELECTED,
427 CodeBlocksThreadEventHandler(CodeCompletion::OnSystemHeadersThreadFinish));
428
429 // clean up all the running thread
430 while (!m_SystemHeadersThreads.empty())
431 {
432 SystemHeadersThread* thread = m_SystemHeadersThreads.front();
433 thread->Wait();
434 delete thread;
435 m_SystemHeadersThreads.pop_front();
436 }
437 }
438
OnAttach()439 void CodeCompletion::OnAttach()
440 {
441 m_EditMenu = 0;
442 m_SearchMenu = 0;
443 m_ViewMenu = 0;
444 m_ProjectMenu = 0;
445 // toolbar related variables
446 m_ToolBar = 0;
447 m_Function = 0;
448 m_Scope = 0;
449 m_FunctionsScope.clear();
450 m_NameSpaces.clear();
451 m_AllFunctionsScopes.clear();
452 m_ToolbarNeedRefresh = true; // by default
453
454 m_LastFile.clear();
455
456 // read options from configure file
457 RereadOptions();
458
459 // Events which m_NativeParser does not handle will go to the the next event
460 // handler which is the instance of a CodeCompletion.
461 m_NativeParser.SetNextHandler(this);
462
463 m_NativeParser.CreateClassBrowser();
464
465 // hook to editors
466 // both ccmanager and cc have hooks, but they don't conflict. ccmanager are mainly
467 // hooking to the event such as key stroke or mouse dwell events, so the code completion, call tip
468 // and tool tip will be handled in ccmanager. The other cases such as caret movement triggers
469 // updating the CC's toolbar, modifying the editor causing the real time content reparse will be
470 // handled inside cc's own editor hook.
471 EditorHooks::HookFunctorBase* myhook = new EditorHooks::HookFunctor<CodeCompletion>(this, &CodeCompletion::EditorEventHook);
472 m_EditorHookId = EditorHooks::RegisterHook(myhook);
473
474 // register event sinks
475 Manager* pm = Manager::Get();
476
477 pm->RegisterEventSink(cbEVT_APP_STARTUP_DONE, new cbEventFunctor<CodeCompletion, CodeBlocksEvent>(this, &CodeCompletion::OnAppDoneStartup));
478
479 pm->RegisterEventSink(cbEVT_WORKSPACE_CHANGED, new cbEventFunctor<CodeCompletion, CodeBlocksEvent>(this, &CodeCompletion::OnWorkspaceChanged));
480
481 pm->RegisterEventSink(cbEVT_PROJECT_ACTIVATE, new cbEventFunctor<CodeCompletion, CodeBlocksEvent>(this, &CodeCompletion::OnProjectActivated));
482 pm->RegisterEventSink(cbEVT_PROJECT_CLOSE, new cbEventFunctor<CodeCompletion, CodeBlocksEvent>(this, &CodeCompletion::OnProjectClosed));
483 pm->RegisterEventSink(cbEVT_PROJECT_SAVE, new cbEventFunctor<CodeCompletion, CodeBlocksEvent>(this, &CodeCompletion::OnProjectSaved));
484 pm->RegisterEventSink(cbEVT_PROJECT_FILE_ADDED, new cbEventFunctor<CodeCompletion, CodeBlocksEvent>(this, &CodeCompletion::OnProjectFileAdded));
485 pm->RegisterEventSink(cbEVT_PROJECT_FILE_REMOVED, new cbEventFunctor<CodeCompletion, CodeBlocksEvent>(this, &CodeCompletion::OnProjectFileRemoved));
486 pm->RegisterEventSink(cbEVT_PROJECT_FILE_CHANGED, new cbEventFunctor<CodeCompletion, CodeBlocksEvent>(this, &CodeCompletion::OnProjectFileChanged));
487
488 pm->RegisterEventSink(cbEVT_EDITOR_SAVE, new cbEventFunctor<CodeCompletion, CodeBlocksEvent>(this, &CodeCompletion::OnEditorSave));
489 pm->RegisterEventSink(cbEVT_EDITOR_OPEN, new cbEventFunctor<CodeCompletion, CodeBlocksEvent>(this, &CodeCompletion::OnEditorOpen));
490 pm->RegisterEventSink(cbEVT_EDITOR_ACTIVATED, new cbEventFunctor<CodeCompletion, CodeBlocksEvent>(this, &CodeCompletion::OnEditorActivated));
491 pm->RegisterEventSink(cbEVT_EDITOR_CLOSE, new cbEventFunctor<CodeCompletion, CodeBlocksEvent>(this, &CodeCompletion::OnEditorClosed));
492
493 m_DocHelper.OnAttach();
494 }
495
OnRelease(bool appShutDown)496 void CodeCompletion::OnRelease(bool appShutDown)
497 {
498 m_NativeParser.RemoveClassBrowser(appShutDown);
499 m_NativeParser.ClearParsers();
500
501 // remove chained handler
502 m_NativeParser.SetNextHandler(nullptr);
503
504 // unregister hook
505 // 'true' will delete the functor too
506 EditorHooks::UnregisterHook(m_EditorHookId, true);
507
508 // remove registered event sinks
509 Manager::Get()->RemoveAllEventSinksFor(this);
510
511 m_FunctionsScope.clear();
512 m_NameSpaces.clear();
513 m_AllFunctionsScopes.clear();
514 m_ToolbarNeedRefresh = false;
515
516 /* TODO (mandrav#1#): Delete separator line too... */
517 if (m_EditMenu)
518 m_EditMenu->Delete(idMenuRenameSymbols);
519
520 if (m_SearchMenu)
521 {
522 m_SearchMenu->Delete(idMenuGotoFunction);
523 m_SearchMenu->Delete(idMenuGotoPrevFunction);
524 m_SearchMenu->Delete(idMenuGotoNextFunction);
525 m_SearchMenu->Delete(idMenuGotoDeclaration);
526 m_SearchMenu->Delete(idMenuGotoImplementation);
527 m_SearchMenu->Delete(idMenuFindReferences);
528 m_SearchMenu->Delete(idMenuOpenIncludeFile);
529 }
530
531 m_DocHelper.OnRelease();
532 }
533
GetConfigurationPanel(wxWindow * parent)534 cbConfigurationPanel* CodeCompletion::GetConfigurationPanel(wxWindow* parent)
535 {
536 return new CCOptionsDlg(parent, &m_NativeParser, this, &m_DocHelper);
537 }
538
GetProjectConfigurationPanel(wxWindow * parent,cbProject * project)539 cbConfigurationPanel* CodeCompletion::GetProjectConfigurationPanel(wxWindow* parent, cbProject* project)
540 {
541 return new CCOptionsProjectDlg(parent, project, &m_NativeParser);
542 }
543
BuildMenu(wxMenuBar * menuBar)544 void CodeCompletion::BuildMenu(wxMenuBar* menuBar)
545 {
546 // if not attached, exit
547 if (!IsAttached())
548 return;
549
550 int pos = menuBar->FindMenu(_("&Edit"));
551 if (pos != wxNOT_FOUND)
552 {
553 m_EditMenu = menuBar->GetMenu(pos);
554 m_EditMenu->AppendSeparator();
555 m_EditMenu->Append(idMenuRenameSymbols, _("Rename symbols\tAlt-N"));
556 }
557 else
558 CCLogger::Get()->DebugLog(_T("Could not find Edit menu!"));
559
560 pos = menuBar->FindMenu(_("Sea&rch"));
561 if (pos != wxNOT_FOUND)
562 {
563 m_SearchMenu = menuBar->GetMenu(pos);
564 m_SearchMenu->Append(idMenuGotoFunction, _("Goto function...\tCtrl-Shift-G"));
565 m_SearchMenu->Append(idMenuGotoPrevFunction, _("Goto previous function\tCtrl-PgUp"));
566 m_SearchMenu->Append(idMenuGotoNextFunction, _("Goto next function\tCtrl-PgDn"));
567 m_SearchMenu->Append(idMenuGotoDeclaration, _("Goto declaration\tCtrl-Shift-."));
568 m_SearchMenu->Append(idMenuGotoImplementation, _("Goto implementation\tCtrl-."));
569 m_SearchMenu->Append(idMenuFindReferences, _("Find references\tAlt-."));
570 m_SearchMenu->Append(idMenuOpenIncludeFile, _("Open include file"));
571 }
572 else
573 CCLogger::Get()->DebugLog(_T("Could not find Search menu!"));
574
575 // add the classbrowser window in the "View" menu
576 int idx = menuBar->FindMenu(_("&View"));
577 if (idx != wxNOT_FOUND)
578 {
579 m_ViewMenu = menuBar->GetMenu(idx);
580 wxMenuItemList& items = m_ViewMenu->GetMenuItems();
581 bool inserted = false;
582
583 // find the first separator and insert before it
584 for (size_t i = 0; i < items.GetCount(); ++i)
585 {
586 if (items[i]->IsSeparator())
587 {
588 m_ViewMenu->InsertCheckItem(i, idViewClassBrowser, _("Symbols browser"), _("Toggle displaying the symbols browser"));
589 inserted = true;
590 break;
591 }
592 }
593
594 // not found, just append
595 if (!inserted)
596 m_ViewMenu->AppendCheckItem(idViewClassBrowser, _("Symbols browser"), _("Toggle displaying the symbols browser"));
597 }
598 else
599 CCLogger::Get()->DebugLog(_T("Could not find View menu!"));
600
601 // add Reparse item in the "Project" menu
602 idx = menuBar->FindMenu(_("&Project"));
603 if (idx != wxNOT_FOUND)
604 {
605 m_ProjectMenu = menuBar->GetMenu(idx);
606 wxMenuItemList& items = m_ProjectMenu->GetMenuItems();
607 bool inserted = false;
608
609 // find the first separator and insert before it
610 for (size_t i = items.GetCount() - 1; i > 0; --i)
611 {
612 if (items[i]->IsSeparator())
613 {
614 m_ProjectMenu->InsertSeparator(i);
615 m_ProjectMenu->Insert(i + 1, idCurrentProjectReparse, _("Reparse current project"), _("Reparse of the final switched project"));
616 inserted = true;
617 break;
618 }
619 }
620
621 // not found, just append
622 if (!inserted)
623 {
624 m_ProjectMenu->AppendSeparator();
625 m_ProjectMenu->Append(idCurrentProjectReparse, _("Reparse current project"), _("Reparse of the final switched project"));
626 }
627 }
628 else
629 CCLogger::Get()->DebugLog(_T("Could not find Project menu!"));
630 }
631
BuildModuleMenu(const ModuleType type,wxMenu * menu,const FileTreeData * data)632 void CodeCompletion::BuildModuleMenu(const ModuleType type, wxMenu* menu, const FileTreeData* data)
633 {
634 // if not attached, exit
635 if (!menu || !IsAttached() || !m_InitDone)
636 return;
637
638 if (type == mtEditorManager)
639 {
640 if (cbEditor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor())
641 {
642 if ( !IsProviderFor(ed) )
643 return;
644 }
645
646 wxString NameUnderCursor;
647 bool IsInclude = false;
648 const bool nameUnderCursor = CodeCompletionHelper::EditorHasNameUnderCursor(NameUnderCursor, IsInclude);
649 if (nameUnderCursor)
650 {
651 PluginManager *pluginManager = Manager::Get()->GetPluginManager();
652
653 if (IsInclude)
654 {
655 wxString msg;
656 msg.Printf(_("Open #include file: '%s'"), NameUnderCursor.wx_str());
657 menu->Insert(0, idOpenIncludeFile, msg);
658 menu->Insert(1, wxID_SEPARATOR, wxEmptyString);
659 pluginManager->RegisterFindMenuItems(true, 2);
660 }
661 else
662 {
663 int initialPos = pluginManager->GetFindMenuItemFirst();
664 int pos = initialPos;
665 wxString msg;
666 msg.Printf(_("Find declaration of: '%s'"), NameUnderCursor.wx_str());
667 menu->Insert(pos++, idGotoDeclaration, msg);
668
669 msg.Printf(_("Find implementation of: '%s'"), NameUnderCursor.wx_str());
670 menu->Insert(pos++, idGotoImplementation, msg);
671
672 if (m_NativeParser.GetParser().Done())
673 {
674 msg.Printf(_("Find references of: '%s'"), NameUnderCursor.wx_str());
675 menu->Insert(pos++, idMenuFindReferences, msg);
676 }
677 pluginManager->RegisterFindMenuItems(false, pos - initialPos);
678 }
679 }
680
681 const int insertId = menu->FindItem(_("Insert/Refactor"));
682 if (insertId != wxNOT_FOUND)
683 {
684 if (wxMenuItem* insertMenu = menu->FindItem(insertId, 0))
685 {
686 if (wxMenu* subMenu = insertMenu->GetSubMenu())
687 {
688 subMenu->Append(idClassMethod, _("Class method declaration/implementation..."));
689 subMenu->Append(idUnimplementedClassMethods, _("All class methods without implementation..."));
690
691 subMenu->AppendSeparator();
692
693 const bool enableRename = (m_NativeParser.GetParser().Done() && nameUnderCursor && !IsInclude);
694 subMenu->Append(idMenuRenameSymbols, _("Rename symbols"), _("Rename symbols under cursor"));
695 subMenu->Enable(idMenuRenameSymbols, enableRename);
696 }
697 else
698 CCLogger::Get()->DebugLog(_T("Could not find Insert menu 3!"));
699 }
700 else
701 CCLogger::Get()->DebugLog(_T("Could not find Insert menu 2!"));
702 }
703 else
704 CCLogger::Get()->DebugLog(_T("Could not find Insert menu!"));
705 }
706 else if (type == mtProjectManager)
707 {
708 if (data)
709 {
710 if (data->GetKind() == FileTreeData::ftdkProject)
711 {
712 size_t position = menu->GetMenuItemCount();
713 int id = menu->FindItem(_("Build"));
714 if (id != wxNOT_FOUND)
715 menu->FindChildItem(id, &position);
716 menu->Insert(position, idSelectedProjectReparse, _("Reparse this project"),
717 _("Reparse current actived project"));
718 }
719 else if (data->GetKind() == FileTreeData::ftdkFile)
720 menu->Append(idSelectedFileReparse, _("Reparse this file"), _("Reparse current selected file"));
721 }
722 }
723 }
724
BuildToolBar(wxToolBar * toolBar)725 bool CodeCompletion::BuildToolBar(wxToolBar* toolBar)
726 {
727 // load the toolbar resource
728 Manager::Get()->AddonToolBar(toolBar,_T("codecompletion_toolbar"));
729 // get the wxChoice control pointers
730 m_Function = XRCCTRL(*toolBar, "chcCodeCompletionFunction", wxChoice);
731 m_Scope = XRCCTRL(*toolBar, "chcCodeCompletionScope", wxChoice);
732
733 m_ToolBar = toolBar;
734
735 // set the wxChoice and best toolbar size
736 UpdateToolBar();
737
738 // disable the wxChoices
739 EnableToolbarTools(false);
740
741 return true;
742 }
743
GetProviderStatusFor(cbEditor * ed)744 CodeCompletion::CCProviderStatus CodeCompletion::GetProviderStatusFor(cbEditor* ed)
745 {
746 EditorColourSet *colour_set = ed->GetColourSet();
747 if (colour_set && ed->GetLanguage() == colour_set->GetHighlightLanguage(wxT("C/C++")))
748 return ccpsActive;
749
750 switch (ParserCommon::FileType(ed->GetFilename()))
751 {
752 case ParserCommon::ftHeader:
753 case ParserCommon::ftSource:
754 return ccpsActive;
755
756 case ParserCommon::ftOther:
757 default:
758 break;
759 }
760 return ccpsUniversal;
761 }
762
GetAutocompList(bool isAuto,cbEditor * ed,int & tknStart,int & tknEnd)763 std::vector<CodeCompletion::CCToken> CodeCompletion::GetAutocompList(bool isAuto, cbEditor* ed, int& tknStart, int& tknEnd)
764 {
765 std::vector<CCToken> tokens;
766
767 if (!IsAttached() || !m_InitDone)
768 return tokens;
769
770 cbStyledTextCtrl* stc = ed->GetControl();
771 const int style = stc->GetStyleAt(tknEnd);
772 const wxChar curChar = stc->GetCharAt(tknEnd - 1);
773
774 if (isAuto) // filter illogical cases of auto-launch
775 {
776 // AutocompList can be prompt after user typed "::" or "->"
777 // or if in preprocessor directive, after user typed "<" or "\"" or "/"
778 if ( ( curChar == wxT(':') // scope operator
779 && stc->GetCharAt(tknEnd - 2) != wxT(':') )
780 || ( curChar == wxT('>') // '->'
781 && stc->GetCharAt(tknEnd - 2) != wxT('-') )
782 || ( wxString(wxT("<\"/")).Find(curChar) != wxNOT_FOUND // #include directive
783 && !stc->IsPreprocessor(style) ) )
784 {
785 return tokens;
786 }
787 }
788
789 const int lineIndentPos = stc->GetLineIndentPosition(stc->GetCurrentLine());
790 const wxChar lineFirstChar = stc->GetCharAt(lineIndentPos);
791
792 if (lineFirstChar == wxT('#'))
793 {
794 const int startPos = stc->WordStartPosition(lineIndentPos + 1, true);
795 const int endPos = stc->WordEndPosition(lineIndentPos + 1, true);
796 const wxString str = stc->GetTextRange(startPos, endPos);
797
798 if (str == wxT("include") && tknEnd > endPos)
799 {
800 DoCodeCompleteIncludes(ed, tknStart, tknEnd, tokens);
801 }
802 else if (endPos >= tknEnd && tknEnd > lineIndentPos)
803 DoCodeCompletePreprocessor(tknStart, tknEnd, ed, tokens);
804 else if ( ( str == wxT("define")
805 || str == wxT("if")
806 || str == wxT("ifdef")
807 || str == wxT("ifndef")
808 || str == wxT("elif")
809 || str == wxT("elifdef")
810 || str == wxT("elifndef")
811 || str == wxT("undef") )
812 && tknEnd > endPos )
813 {
814 DoCodeComplete(tknEnd, ed, tokens, true);
815 }
816 return tokens;
817 }
818 else if (curChar == wxT('#'))
819 return tokens;
820 else if (lineFirstChar == wxT(':') && curChar == _T(':'))
821 return tokens;
822
823 if ( stc->IsString(style)
824 || stc->IsComment(style)
825 || stc->IsCharacter(style)
826 || stc->IsPreprocessor(style) )
827 {
828 return tokens;
829 }
830
831 DoCodeComplete(tknEnd, ed, tokens);
832 return tokens;
833 }
834
CalcStcFontSize(cbStyledTextCtrl * stc)835 static int CalcStcFontSize(cbStyledTextCtrl *stc)
836 {
837 wxFont defaultFont = stc->StyleGetFont(wxSCI_STYLE_DEFAULT);
838 defaultFont.SetPointSize(defaultFont.GetPointSize() + stc->GetZoom());
839 int fontSize;
840 stc->GetTextExtent(wxT("A"), nullptr, &fontSize, nullptr, nullptr, &defaultFont);
841 return fontSize;
842 }
843
DoCodeComplete(int caretPos,cbEditor * ed,std::vector<CCToken> & tokens,bool preprocessorOnly)844 void CodeCompletion::DoCodeComplete(int caretPos, cbEditor* ed, std::vector<CCToken>& tokens, bool preprocessorOnly)
845 {
846 const bool caseSens = m_NativeParser.GetParser().Options().caseSensitive;
847 cbStyledTextCtrl* stc = ed->GetControl();
848
849 TokenIdxSet result;
850 if ( m_NativeParser.MarkItemsByAI(result, m_NativeParser.GetParser().Options().useSmartSense, true, caseSens, caretPos)
851 || m_NativeParser.LastAISearchWasGlobal() ) // enter even if no match (code-complete C++ keywords)
852 {
853 if (s_DebugSmartSense)
854 CCLogger::Get()->DebugLog(F(wxT("%lu results"), static_cast<unsigned long>(result.size())));
855 TRACE(F(wxT("%lu results"), static_cast<unsigned long>(result.size())));
856
857 if (result.size() <= m_CCMaxMatches)
858 {
859 if (s_DebugSmartSense)
860 CCLogger::Get()->DebugLog(wxT("Generating tokens list..."));
861
862 const int fontSize = CalcStcFontSize(stc);
863 wxImageList* ilist = m_NativeParser.GetImageList(fontSize);
864 stc->ClearRegisteredImages();
865
866 tokens.reserve(result.size());
867 std::set<int> alreadyRegistered;
868 StringSet uniqueStrings; // ignore keywords with same name as parsed tokens
869
870 TokenTree* tree = m_NativeParser.GetParser().GetTokenTree();
871
872 CC_LOCKER_TRACK_TT_MTX_LOCK(s_TokenTreeMutex)
873
874 for (TokenIdxSet::const_iterator it = result.begin(); it != result.end(); ++it)
875 {
876 const Token* token = tree->at(*it);
877 if (!token || token->m_Name.IsEmpty())
878 continue;
879
880 if (preprocessorOnly && token->m_TokenKind != tkMacroDef)
881 continue;
882
883 int iidx = m_NativeParser.GetTokenKindImage(token);
884 if (alreadyRegistered.find(iidx) == alreadyRegistered.end())
885 {
886 stc->RegisterImage(iidx, ilist->GetBitmap(iidx));
887 alreadyRegistered.insert(iidx);
888 }
889
890 wxString dispStr;
891 if (token->m_TokenKind & tkAnyFunction)
892 {
893 if (m_DocHelper.IsEnabled())
894 dispStr = wxT("(): ") + token->m_FullType;
895 else
896 dispStr = token->GetFormattedArgs() << _T(": ") << token->m_FullType;
897 }
898 else if (token->m_TokenKind == tkVariable)
899 dispStr = wxT(": ") + token->m_FullType;
900 tokens.push_back(CCToken(token->m_Index, token->m_Name + dispStr, token->m_Name, token->m_IsTemp ? 0 : 5, iidx));
901 uniqueStrings.insert(token->m_Name);
902
903 if (token->m_TokenKind == tkNamespace && token->m_Aliases.size())
904 {
905 for (size_t i = 0; i < token->m_Aliases.size(); ++i)
906 {
907 // dispStr will currently be empty, but contain something in the future...
908 tokens.push_back(CCToken(token->m_Index, token->m_Aliases[i] + dispStr, token->m_Aliases[i], 5, iidx));
909 uniqueStrings.insert(token->m_Aliases[i]);
910 }
911 }
912 }
913
914 CC_LOCKER_TRACK_TT_MTX_UNLOCK(s_TokenTreeMutex)
915
916 if (m_NativeParser.LastAISearchWasGlobal() && !preprocessorOnly)
917 {
918 // empty or partial search phrase: add theme keywords in search list
919 if (s_DebugSmartSense)
920 CCLogger::Get()->DebugLog(_T("Last AI search was global: adding theme keywords in list"));
921
922 EditorColourSet* colour_set = ed->GetColourSet();
923 if (colour_set)
924 {
925 wxString lastSearch = m_NativeParser.LastAIGlobalSearch().Lower();
926 int iidx = ilist->GetImageCount();
927 FileType fTp = FileTypeOf(ed->GetShortName());
928 bool isC = (fTp == ftHeader || fTp == ftSource|| fTp == ftTemplateSource);
929 // theme keywords
930 HighlightLanguage lang = ed->GetLanguage();
931 if (lang == HL_NONE)
932 lang = colour_set->GetLanguageForFilename(ed->GetFilename());
933 wxString strLang = colour_set->GetLanguageName(lang);
934 // if its sourcecode/header file and a known fileformat, show the corresponding icon
935 if (isC && strLang == wxT("C/C++"))
936 stc->RegisterImage(iidx, GetImage(ImageId::KeywordCPP, fontSize));
937 else if (isC && strLang == wxT("D"))
938 stc->RegisterImage(iidx, GetImage(ImageId::KeywordD, fontSize));
939 else
940 stc->RegisterImage(iidx, GetImage(ImageId::Unknown, fontSize));
941 // the first two keyword sets are the primary and secondary keywords (for most lexers at least)
942 // but this is now configurable in global settings
943 for (int i = 0; i <= wxSCI_KEYWORDSET_MAX; ++i)
944 {
945 if (!m_LexerKeywordsToInclude[i])
946 continue;
947
948 wxString keywords = colour_set->GetKeywords(lang, i);
949 wxStringTokenizer tkz(keywords, wxT(" \t\r\n"), wxTOKEN_STRTOK);
950 while (tkz.HasMoreTokens())
951 {
952 wxString kw = tkz.GetNextToken();
953 if ( kw.Lower().StartsWith(lastSearch)
954 && uniqueStrings.find(kw) == uniqueStrings.end() )
955 {
956 tokens.push_back(CCToken(wxNOT_FOUND, kw, iidx));
957 }
958 }
959 }
960 }
961 }
962 }
963 else if (!stc->CallTipActive())
964 {
965 wxString msg = _("Too many results.\n"
966 "Please edit results' limit in code-completion options,\n"
967 "or type at least one more character to narrow the scope down.");
968 stc->CallTipShow(stc->GetCurrentPos(), msg);
969 }
970 }
971 else if (!stc->CallTipActive())
972 {
973 if (s_DebugSmartSense)
974 CCLogger::Get()->DebugLog(wxT("0 results"));
975
976 if (!m_NativeParser.GetParser().Done())
977 {
978 wxString msg = _("The Parser is still parsing files.");
979 stc->CallTipShow(stc->GetCurrentPos(), msg);
980 msg += m_NativeParser.GetParser().NotDoneReason();
981 CCLogger::Get()->DebugLog(msg);
982 }
983 }
984 }
985
DoCodeCompletePreprocessor(int tknStart,int tknEnd,cbEditor * ed,std::vector<CCToken> & tokens)986 void CodeCompletion::DoCodeCompletePreprocessor(int tknStart, int tknEnd, cbEditor* ed, std::vector<CCToken>& tokens)
987 {
988 cbStyledTextCtrl* stc = ed->GetControl();
989 if (stc->GetLexer() != wxSCI_LEX_CPP)
990 {
991 const FileType fTp = FileTypeOf(ed->GetShortName());
992 if ( fTp != ftSource
993 && fTp != ftHeader
994 && fTp != ftTemplateSource
995 && fTp != ftResource )
996 {
997 return; // not C/C++
998 }
999 }
1000 const wxString text = stc->GetTextRange(tknStart, tknEnd);
1001
1002 wxStringVec macros;
1003 macros.push_back(wxT("define"));
1004 macros.push_back(wxT("elif"));
1005 macros.push_back(wxT("elifdef"));
1006 macros.push_back(wxT("elifndef"));
1007 macros.push_back(wxT("else"));
1008 macros.push_back(wxT("endif"));
1009 macros.push_back(wxT("error"));
1010 macros.push_back(wxT("if"));
1011 macros.push_back(wxT("ifdef"));
1012 macros.push_back(wxT("ifndef"));
1013 macros.push_back(wxT("include"));
1014 macros.push_back(wxT("line"));
1015 macros.push_back(wxT("pragma"));
1016 macros.push_back(wxT("undef"));
1017 const wxString idxStr = F(wxT("\n%d"), PARSER_IMG_MACRO_DEF);
1018 for (size_t i = 0; i < macros.size(); ++i)
1019 {
1020 if (text.IsEmpty() || macros[i][0] == text[0]) // ignore tokens that start with a different letter
1021 tokens.push_back(CCToken(wxNOT_FOUND, macros[i], PARSER_IMG_MACRO_DEF));
1022 }
1023 stc->ClearRegisteredImages();
1024 const int fontSize = CalcStcFontSize(stc);
1025 stc->RegisterImage(PARSER_IMG_MACRO_DEF,
1026 m_NativeParser.GetImageList(fontSize)->GetBitmap(PARSER_IMG_MACRO_DEF));
1027 }
1028
DoCodeCompleteIncludes(cbEditor * ed,int & tknStart,int tknEnd,std::vector<CCToken> & tokens)1029 void CodeCompletion::DoCodeCompleteIncludes(cbEditor* ed, int& tknStart, int tknEnd, std::vector<CCToken>& tokens)
1030 {
1031 if (!m_CCEnableHeaders)
1032 return;
1033
1034 const wxString curFile(ed->GetFilename());
1035 const wxString curPath(wxFileName(curFile).GetPath());
1036
1037 FileType ft = FileTypeOf(ed->GetShortName());
1038 if ( ft != ftHeader && ft != ftSource && ft != ftTemplateSource) // only parse source/header files
1039 return;
1040
1041 cbStyledTextCtrl* stc = ed->GetControl();
1042 const int lineStartPos = stc->PositionFromLine(stc->GetCurrentLine());
1043 wxString line = stc->GetCurLine();
1044 line.Trim();
1045 if (line.IsEmpty() || !CodeCompletionHelper::TestIncludeLine(line))
1046 return;
1047
1048 int keyPos = line.Find(wxT('"'));
1049 if (keyPos == wxNOT_FOUND || keyPos >= tknEnd - lineStartPos)
1050 keyPos = line.Find(wxT('<'));
1051 if (keyPos == wxNOT_FOUND || keyPos >= tknEnd - lineStartPos)
1052 return;
1053 ++keyPos;
1054
1055 // now, we are after the quote prompt, "filename" is the text we already typed after the
1056 // #include directive, such as #include <abc| , so that we need to prompt all the header files
1057 // which has the prefix "abc"
1058 wxString filename = line.SubString(keyPos, tknEnd - lineStartPos - 1);
1059 filename.Replace(wxT("\\"), wxT("/"), true);
1060 if (!filename.empty() && (filename.Last() == wxT('"') || filename.Last() == wxT('>')))
1061 filename.RemoveLast();
1062
1063 size_t maxFiles = m_CCMaxMatches;
1064 if (filename.IsEmpty() && maxFiles > 3000)
1065 maxFiles = 3000; // do not try to collect too many files if there is no context (prevent hang)
1066
1067 // fill a list of matching files
1068 StringSet files;
1069
1070 // #include < or #include "
1071 cbProject* project = m_NativeParser.GetProjectByEditor(ed);
1072
1073 // since we are going to access the m_SystemHeadersMap, we add a locker here
1074 // here we collect all the header files names which is under "system include search dirs"
1075 #if wxCHECK_VERSION(3, 0, 0)
1076 if (m_SystemHeadersThreadCS.TryEnter())
1077 {
1078 #else
1079 {
1080 m_SystemHeadersThreadCS.Enter();
1081 #endif // wxCHECK_VERSION(3, 0, 0)
1082 // if the project get modified, fetch the dirs again, otherwise, use cached dirs
1083 wxArrayString& incDirs = GetSystemIncludeDirs(project, project ? project->GetModified() : true);
1084 for (size_t i = 0; i < incDirs.GetCount(); ++i)
1085 {
1086 // shm_it means system_header_map_iterator
1087 SystemHeadersMap::const_iterator shm_it = m_SystemHeadersMap.find(incDirs[i]);
1088 if (shm_it != m_SystemHeadersMap.end())
1089 {
1090 const StringSet& headers = shm_it->second;
1091 for (StringSet::const_iterator ss_it = headers.begin(); ss_it != headers.end(); ++ss_it)
1092 {
1093 const wxString& file = *ss_it;
1094 // if find a value matches already typed "filename", add to the result
1095 if (file.StartsWith(filename))
1096 {
1097 files.insert(file);
1098 if (files.size() > maxFiles)
1099 break; // exit inner loop
1100 }
1101 }
1102 if (files.size() > maxFiles)
1103 break; // exit outer loop
1104 }
1105 }
1106 m_SystemHeadersThreadCS.Leave();
1107 }
1108
1109 // #include "
1110 if (project)
1111 {
1112 #if wxCHECK_VERSION(3, 0, 0)
1113 if (m_SystemHeadersThreadCS.TryEnter())
1114 {
1115 #else
1116 {
1117 m_SystemHeadersThreadCS.Enter();
1118 #endif // wxCHECK_VERSION(3, 0, 0)
1119 wxArrayString buildTargets;
1120 ProjectFile* pf = project ? project->GetFileByFilename(curFile, false) : 0;
1121 if (pf)
1122 buildTargets = pf->buildTargets;
1123
1124 const wxArrayString localIncludeDirs = GetLocalIncludeDirs(project, buildTargets);
1125 for (FilesList::const_iterator it = project->GetFilesList().begin();
1126 it != project->GetFilesList().end(); ++it)
1127 {
1128 pf = *it;
1129 if (pf && FileTypeOf(pf->relativeFilename) == ftHeader)
1130 {
1131 wxString file = pf->file.GetFullPath();
1132 wxString header;
1133 for (size_t j = 0; j < localIncludeDirs.GetCount(); ++j)
1134 {
1135 const wxString& dir = localIncludeDirs[j];
1136 if (file.StartsWith(dir))
1137 {
1138 header = file.Mid(dir.Len());
1139 header.Replace(wxT("\\"), wxT("/"));
1140 break;
1141 }
1142 }
1143
1144 if (header.IsEmpty())
1145 {
1146 if (pf->buildTargets != buildTargets)
1147 continue;
1148
1149 wxFileName fn(file);
1150 fn.MakeRelativeTo(curPath);
1151 header = fn.GetFullPath(wxPATH_UNIX);
1152 }
1153
1154 if (header.StartsWith(filename))
1155 {
1156 files.insert(header);
1157 if (files.size() > maxFiles)
1158 break;
1159 }
1160 }
1161 }
1162 m_SystemHeadersThreadCS.Leave();
1163 }
1164 }
1165
1166 if (!files.empty())
1167 {
1168 tknStart = lineStartPos + keyPos;
1169 tokens.reserve(files.size());
1170 for (StringSet::const_iterator ssIt = files.begin(); ssIt != files.end(); ++ssIt)
1171 tokens.push_back(CCToken(wxNOT_FOUND, *ssIt, 0));
1172 stc->ClearRegisteredImages();
1173 const int fontSize = CalcStcFontSize(stc);
1174 stc->RegisterImage(0, GetImage(ImageId::HeaderFile, fontSize));
1175 }
1176 }
1177
1178 std::vector<CodeCompletion::CCCallTip> CodeCompletion::GetCallTips(int pos, int style, cbEditor* ed, int& argsPos)
1179 {
1180 std::vector<CCCallTip> tips;
1181 if (!IsAttached() || !m_InitDone || style == wxSCI_C_WXSMITH || !m_NativeParser.GetParser().Done())
1182 return tips;
1183
1184 int typedCommas = 0;
1185 wxArrayString items;
1186 argsPos = m_NativeParser.GetCallTips(items, typedCommas, ed, pos);
1187 StringSet uniqueTips; // check against this before inserting a new tip in the list
1188 for (size_t i = 0; i < items.GetCount(); ++i)
1189 {
1190 // allow only unique, non-empty items with equal or more commas than what the user has already typed
1191 if ( uniqueTips.find(items[i]) == uniqueTips.end() // unique
1192 && !items[i].IsEmpty() // non-empty
1193 && typedCommas <= m_NativeParser.CountCommas(items[i], 0) ) // commas satisfied
1194 {
1195 uniqueTips.insert(items[i]);
1196 int hlStart = wxSCI_INVALID_POSITION;
1197 int hlEnd = wxSCI_INVALID_POSITION;
1198 m_NativeParser.GetCallTipHighlight(items[i], &hlStart, &hlEnd, typedCommas);
1199 tips.push_back(CCCallTip(items[i], hlStart, hlEnd));
1200 }
1201 }
1202 return tips;
1203 }
1204
1205 wxString CodeCompletion::GetDocumentation(const CCToken& token)
1206 {
1207 return m_DocHelper.GenerateHTML(token.id, m_NativeParser.GetParser().GetTokenTree());
1208 }
1209
1210 std::vector<CodeCompletion::CCToken> CodeCompletion::GetTokenAt(int pos, cbEditor* ed, bool& WXUNUSED(allowCallTip))
1211 {
1212 std::vector<CCToken> tokens;
1213 if (!IsAttached() || !m_InitDone)
1214 return tokens;
1215
1216 // ignore comments, strings, preprocessors, etc
1217 cbStyledTextCtrl* stc = ed->GetControl();
1218 const int style = stc->GetStyleAt(pos);
1219 if ( stc->IsString(style)
1220 || stc->IsComment(style)
1221 || stc->IsCharacter(style)
1222 || stc->IsPreprocessor(style) )
1223 {
1224 return tokens;
1225 }
1226
1227 TokenIdxSet result;
1228 int endOfWord = stc->WordEndPosition(pos, true);
1229 if (m_NativeParser.MarkItemsByAI(result, true, false, true, endOfWord))
1230 {
1231 TokenTree* tree = m_NativeParser.GetParser().GetTokenTree();
1232
1233 CC_LOCKER_TRACK_TT_MTX_LOCK(s_TokenTreeMutex)
1234
1235 for (TokenIdxSet::const_iterator it = result.begin(); it != result.end(); ++it)
1236 {
1237 const Token* token = tree->at(*it);
1238 if (token)
1239 {
1240 tokens.push_back(CCToken(*it, token->DisplayName()));
1241 if (tokens.size() > 32)
1242 break;
1243 }
1244 }
1245
1246 CC_LOCKER_TRACK_TT_MTX_UNLOCK(s_TokenTreeMutex)
1247 }
1248
1249 return tokens;
1250 }
1251
1252 wxString CodeCompletion::OnDocumentationLink(wxHtmlLinkEvent& event, bool& dismissPopup)
1253 {
1254 return m_DocHelper.OnDocumentationLink(event, dismissPopup);
1255 }
1256
1257 void CodeCompletion::DoAutocomplete(const CCToken& token, cbEditor* ed)
1258 {
1259 wxString itemText = CodeCompletionHelper::AutocompGetName(token.displayName);
1260 cbStyledTextCtrl* stc = ed->GetControl();
1261
1262 int curPos = stc->GetCurrentPos();
1263 int startPos = stc->WordStartPosition(curPos, true);
1264 if ( itemText.GetChar(0) == _T('~') // special handle for dtor
1265 && startPos > 0
1266 && stc->GetCharAt(startPos - 1) == _T('~'))
1267 {
1268 --startPos;
1269 }
1270 bool needReparse = false;
1271
1272 if (stc->IsPreprocessor(stc->GetStyleAt(curPos)))
1273 {
1274 curPos = stc->GetLineEndPosition(stc->GetCurrentLine()); // delete rest of line
1275 bool addComment = (itemText == wxT("endif"));
1276 for (int i = stc->GetCurrentPos(); i < curPos; ++i)
1277 {
1278 if (stc->IsComment(stc->GetStyleAt(i)))
1279 {
1280 curPos = i; // preserve line comment
1281 if (wxIsspace(stc->GetCharAt(i - 1)))
1282 --curPos; // preserve a space before the comment
1283 addComment = false;
1284 break;
1285 }
1286 }
1287 if (addComment) // search backwards for the #if*
1288 {
1289 wxRegEx ppIf(wxT("^[ \t]*#[ \t]*if"));
1290 wxRegEx ppEnd(wxT("^[ \t]*#[ \t]*endif"));
1291 int depth = -1;
1292 for (int ppLine = stc->GetCurrentLine() - 1; ppLine >= 0; --ppLine)
1293 {
1294 if (stc->GetLine(ppLine).Find(wxT('#')) != wxNOT_FOUND) // limit testing due to performance cost
1295 {
1296 if (ppIf.Matches(stc->GetLine(ppLine))) // ignore else's, elif's, ...
1297 ++depth;
1298 else if (ppEnd.Matches(stc->GetLine(ppLine)))
1299 --depth;
1300 }
1301 if (depth == 0)
1302 {
1303 wxRegEx pp(wxT("^[ \t]*#[ \t]*[a-z]*([ \t]+([a-zA-Z0-9_]+)|())"));
1304 pp.Matches(stc->GetLine(ppLine));
1305 if (!pp.GetMatch(stc->GetLine(ppLine), 2).IsEmpty())
1306 itemText.Append(wxT(" // ") + pp.GetMatch(stc->GetLine(ppLine), 2));
1307 break;
1308 }
1309 }
1310 }
1311 needReparse = true;
1312
1313 int pos = startPos - 1;
1314 wxChar ch = stc->GetCharAt(pos);
1315 while (ch != _T('<') && ch != _T('"') && ch != _T('#') && (pos>0))
1316 ch = stc->GetCharAt(--pos);
1317 if (ch == _T('<') || ch == _T('"'))
1318 startPos = pos + 1;
1319
1320 if (ch == _T('"'))
1321 itemText << _T('"');
1322 else if (ch == _T('<'))
1323 itemText << _T('>');
1324 }
1325 else
1326 {
1327 const int endPos = stc->WordEndPosition(curPos, true);
1328 const wxString& alreadyText = stc->GetTextRange(curPos, endPos);
1329 if (!alreadyText.IsEmpty() && itemText.EndsWith(alreadyText))
1330 curPos = endPos;
1331 }
1332
1333 int positionModificator = 0;
1334 bool insideParentheses = false;
1335 if (token.id != -1 && m_CCAutoAddParentheses)
1336 {
1337 CC_LOCKER_TRACK_TT_MTX_LOCK(s_TokenTreeMutex)
1338
1339 TokenTree* tree = m_NativeParser.GetParser().GetTokenTree();
1340 const Token* tkn = tree->at(token.id);
1341
1342 if (!tkn)
1343 { CC_LOCKER_TRACK_TT_MTX_UNLOCK(s_TokenTreeMutex) }
1344 else
1345 {
1346 bool addParentheses = tkn->m_TokenKind & tkAnyFunction;
1347 if (!addParentheses && (tkn->m_TokenKind & tkMacroDef))
1348 {
1349 if (tkn->m_Args.size() > 0)
1350 addParentheses = true;
1351 }
1352 // cache args to avoid locking
1353 wxString tokenArgs = tkn->GetStrippedArgs();
1354
1355 CC_LOCKER_TRACK_TT_MTX_UNLOCK(s_TokenTreeMutex)
1356
1357 if (addParentheses)
1358 {
1359 bool insideFunction = true;
1360 if (m_CCDetectImplementation)
1361 {
1362 ccSearchData searchData = { stc, ed->GetFilename() };
1363 int funcToken;
1364 if (m_NativeParser.FindCurrentFunctionStart(&searchData, 0, 0, &funcToken) == -1)
1365 {
1366 // global scope
1367 itemText += tokenArgs;
1368 insideFunction = false;
1369 }
1370 else // Found something, but result may be false positive.
1371 {
1372 CC_LOCKER_TRACK_TT_MTX_LOCK(s_TokenTreeMutex)
1373
1374 const Token* parent = tree->at(funcToken);
1375 // Make sure that parent is not container (class, etc)
1376 if (parent && (parent->m_TokenKind & tkAnyFunction) == 0)
1377 {
1378 // class scope
1379 itemText += tokenArgs;
1380 insideFunction = false;
1381 }
1382
1383 CC_LOCKER_TRACK_TT_MTX_UNLOCK(s_TokenTreeMutex)
1384 }
1385 }
1386
1387 if (insideFunction)
1388 {
1389 // Inside block
1390 // Check if there are brace behind the target
1391 if (stc->GetCharAt(curPos) != _T('('))
1392 {
1393 itemText += _T("()");
1394 if (tokenArgs.size() > 2) // more than '()'
1395 {
1396 positionModificator = -1;
1397 insideParentheses = true;
1398 }
1399 }
1400 else
1401 positionModificator = 1; // Set caret after '('
1402 }
1403 }
1404 } // if tkn
1405 } // if token.id
1406
1407 stc->SetTargetStart(startPos);
1408 stc->SetTargetEnd(curPos);
1409
1410 stc->AutoCompCancel();
1411 if (stc->GetTextRange(startPos, curPos) != itemText)
1412 stc->ReplaceTarget(itemText);
1413 stc->GotoPos(startPos + itemText.Length() + positionModificator);
1414
1415 if (insideParentheses)
1416 {
1417 stc->EnableTabSmartJump();
1418 int tooltipMode = Manager::Get()->GetConfigManager(wxT("ccmanager"))->ReadInt(wxT("/tooltip_mode"), 1);
1419 if (tooltipMode != 3) // keybound only
1420 {
1421 CodeBlocksEvent evt(cbEVT_SHOW_CALL_TIP);
1422 Manager::Get()->ProcessEvent(evt);
1423 }
1424 }
1425
1426 if (needReparse)
1427 {
1428 TRACE(_T("CodeCompletion::EditorEventHook: Starting m_TimerRealtimeParsing."));
1429 m_TimerRealtimeParsing.Start(1, wxTIMER_ONE_SHOT);
1430 }
1431 stc->ChooseCaretX();
1432 }
1433
1434 wxArrayString CodeCompletion::GetLocalIncludeDirs(cbProject* project, const wxArrayString& buildTargets)
1435 {
1436 wxArrayString dirs;
1437 // Do not try to operate include directories if the project is not for this platform
1438 if (m_CCEnablePlatformCheck && !project->SupportsCurrentPlatform())
1439 return dirs;
1440 // add project level compiler include search paths
1441 const wxString prjPath = project->GetCommonTopLevelPath();
1442 GetAbsolutePath(prjPath, project->GetIncludeDirs(), dirs);
1443 // add target level compiler include search paths
1444 for (size_t i = 0; i < buildTargets.GetCount(); ++i)
1445 {
1446 ProjectBuildTarget* tgt = project->GetBuildTarget(buildTargets[i]);
1447 if (!tgt)
1448 continue;
1449 // Do not try to operate include directories if the target is not for this platform
1450 if (!m_CCEnablePlatformCheck || tgt->SupportsCurrentPlatform())
1451 {
1452 GetAbsolutePath(prjPath, tgt->GetIncludeDirs(), dirs);
1453 }
1454 }
1455
1456 // if a path has prefix with the project's path, it is a local include search dir
1457 // other wise, it is a system level include search dir, we try to collect all the system dirs
1458 wxArrayString sysDirs;
1459 for (size_t i = 0; i < dirs.GetCount();)
1460 {
1461 // if the dir with the prefix of project path, then it is a "local dir"
1462 if (dirs[i].StartsWith(prjPath))
1463 ++i;
1464 else // otherwise, it is a "system dir", so add to "sysDirs"
1465 {
1466 if (m_SystemHeadersMap.find(dirs[i]) == m_SystemHeadersMap.end())
1467 sysDirs.Add(dirs[i]);
1468 // remove the system dir in dirs
1469 dirs.RemoveAt(i);
1470 }
1471 }
1472
1473 if (!sysDirs.IsEmpty())
1474 {
1475 cbAssert(m_CCEnableHeaders);
1476 // Create a worker thread associated with "sysDirs". Put it in a queue and run it.
1477 SystemHeadersThread* thread = new SystemHeadersThread(this, &m_SystemHeadersThreadCS,
1478 m_SystemHeadersMap, sysDirs);
1479 m_SystemHeadersThreads.push_back(thread);
1480 thread->Run();
1481 }
1482
1483 dirs.Sort(CodeCompletionHelper::CompareStringLen);
1484 return dirs; // return all the local dirs
1485 }
1486
1487 wxArrayString& CodeCompletion::GetSystemIncludeDirs(cbProject* project, bool force)
1488 {
1489 static cbProject* lastProject = nullptr;
1490 static wxArrayString incDirs;
1491
1492 if (!force && project == lastProject) // force == false means we can use the cached dirs
1493 return incDirs;
1494 else
1495 {
1496 incDirs.Clear();
1497 lastProject = project;
1498 }
1499
1500 wxString prjPath;
1501 if (project)
1502 prjPath = project->GetCommonTopLevelPath();
1503
1504 ParserBase* parser = m_NativeParser.GetParserByProject(project);
1505 if (!parser)
1506 return incDirs;
1507
1508 incDirs = parser->GetIncludeDirs();
1509 // we try to remove the dirs which belong to the project
1510 for (size_t i = 0; i < incDirs.GetCount();)
1511 {
1512 if (incDirs[i].Last() != wxFILE_SEP_PATH)
1513 incDirs[i].Append(wxFILE_SEP_PATH);
1514 // the dirs which have prjPath prefix are local dirs, so they should be removed
1515 if (project && incDirs[i].StartsWith(prjPath))
1516 incDirs.RemoveAt(i);
1517 else
1518 ++i;
1519 }
1520
1521 return incDirs;
1522 }
1523
1524 void CodeCompletion::GetAbsolutePath(const wxString& basePath, const wxArrayString& targets, wxArrayString& dirs)
1525 {
1526 for (size_t i = 0; i < targets.GetCount(); ++i)
1527 {
1528 wxString includePath = targets[i];
1529 Manager::Get()->GetMacrosManager()->ReplaceMacros(includePath);
1530 wxFileName fn(includePath, wxEmptyString);
1531 if (fn.IsRelative())
1532 {
1533 const wxArrayString oldDirs = fn.GetDirs();
1534 fn.SetPath(basePath);
1535 for (size_t j = 0; j < oldDirs.GetCount(); ++j)
1536 fn.AppendDir(oldDirs[j]);
1537 }
1538
1539 // Detect if this directory is for the file system root and skip it. Sometimes macro
1540 // replacements create such paths and we don't want to scan whole disks because of this.
1541 if (fn.IsAbsolute() && fn.GetDirCount() == 0)
1542 continue;
1543
1544 const wxString path = fn.GetFullPath();
1545 if (dirs.Index(path) == wxNOT_FOUND)
1546 dirs.Add(path);
1547 }
1548 }
1549
1550 void CodeCompletion::EditorEventHook(cbEditor* editor, wxScintillaEvent& event)
1551 {
1552 if (!IsAttached() || !m_InitDone)
1553 {
1554 event.Skip();
1555 return;
1556 }
1557
1558 if ( !IsProviderFor(editor) )
1559 {
1560 event.Skip();
1561 return;
1562 }
1563
1564 cbStyledTextCtrl* control = editor->GetControl();
1565
1566 if (event.GetEventType() == wxEVT_SCI_CHARADDED)
1567 { TRACE(_T("wxEVT_SCI_CHARADDED")); }
1568 else if (event.GetEventType() == wxEVT_SCI_CHANGE)
1569 { TRACE(_T("wxEVT_SCI_CHANGE")); }
1570 else if (event.GetEventType() == wxEVT_SCI_MODIFIED)
1571 { TRACE(_T("wxEVT_SCI_MODIFIED")); }
1572 else if (event.GetEventType() == wxEVT_SCI_AUTOCOMP_SELECTION)
1573 { TRACE(_T("wxEVT_SCI_AUTOCOMP_SELECTION")); }
1574 else if (event.GetEventType() == wxEVT_SCI_AUTOCOMP_CANCELLED)
1575 { TRACE(_T("wxEVT_SCI_AUTOCOMP_CANCELLED")); }
1576
1577 // if the user is modifying the editor, then CC should try to reparse the editor's content
1578 // and update the token tree.
1579 if ( m_NativeParser.GetParser().Options().whileTyping
1580 && ( (event.GetModificationType() & wxSCI_MOD_INSERTTEXT)
1581 || (event.GetModificationType() & wxSCI_MOD_DELETETEXT) ) )
1582 {
1583 m_NeedReparse = true;
1584 }
1585
1586 if (control->GetCurrentLine() != m_CurrentLine)
1587 {
1588 // reparsing the editor only happens in the condition that the caret's line number
1589 // is changed.
1590 if (m_NeedReparse)
1591 {
1592 TRACE(_T("CodeCompletion::EditorEventHook: Starting m_TimerRealtimeParsing."));
1593 m_TimerRealtimeParsing.Start(REALTIME_PARSING_DELAY, wxTIMER_ONE_SHOT);
1594 m_CurrentLength = control->GetLength();
1595 m_NeedReparse = false;
1596 }
1597 // wxEVT_SCI_UPDATEUI will be sent on caret's motion, but we are only interested in the
1598 // cases where line number is changed. Then we need to update the CC's toolbar.
1599 if (event.GetEventType() == wxEVT_SCI_UPDATEUI)
1600 {
1601 m_ToolbarNeedRefresh = true;
1602 TRACE(_T("CodeCompletion::EditorEventHook: Starting m_TimerToolbar."));
1603 if (m_TimerEditorActivated.IsRunning())
1604 m_TimerToolbar.Start(EDITOR_ACTIVATED_DELAY + 1, wxTIMER_ONE_SHOT);
1605 else
1606 m_TimerToolbar.Start(TOOLBAR_REFRESH_DELAY, wxTIMER_ONE_SHOT);
1607 }
1608 }
1609
1610 // allow others to handle this event
1611 event.Skip();
1612 }
1613
1614 void CodeCompletion::RereadOptions()
1615 {
1616 // Keep this in sync with CCOptionsDlg::CCOptionsDlg and CCOptionsDlg::OnApply
1617
1618 ConfigManager* cfg = Manager::Get()->GetConfigManager(_T("code_completion"));
1619
1620 m_LexerKeywordsToInclude[0] = cfg->ReadBool(_T("/lexer_keywords_set1"), true);
1621 m_LexerKeywordsToInclude[1] = cfg->ReadBool(_T("/lexer_keywords_set2"), true);
1622 m_LexerKeywordsToInclude[2] = cfg->ReadBool(_T("/lexer_keywords_set3"), false);
1623 m_LexerKeywordsToInclude[3] = cfg->ReadBool(_T("/lexer_keywords_set4"), false);
1624 m_LexerKeywordsToInclude[4] = cfg->ReadBool(_T("/lexer_keywords_set5"), false);
1625 m_LexerKeywordsToInclude[5] = cfg->ReadBool(_T("/lexer_keywords_set6"), false);
1626 m_LexerKeywordsToInclude[6] = cfg->ReadBool(_T("/lexer_keywords_set7"), false);
1627 m_LexerKeywordsToInclude[7] = cfg->ReadBool(_T("/lexer_keywords_set8"), false);
1628 m_LexerKeywordsToInclude[8] = cfg->ReadBool(_T("/lexer_keywords_set9"), false);
1629
1630 // for CC
1631 m_CCMaxMatches = cfg->ReadInt(_T("/max_matches"), 16384);
1632 m_CCAutoAddParentheses = cfg->ReadBool(_T("/auto_add_parentheses"), true);
1633 m_CCDetectImplementation = cfg->ReadBool(_T("/detect_implementation"), false); //depends on auto_add_parentheses
1634 m_CCFillupChars = cfg->Read(_T("/fillup_chars"), wxEmptyString);
1635 m_CCEnableHeaders = cfg->ReadBool(_T("/enable_headers"), true);
1636 m_CCEnablePlatformCheck = cfg->ReadBool(_T("/platform_check"), true);
1637
1638 // update the CC toolbar option, and tick the timer for toolbar
1639 // NOTE (ollydbg#1#12/06/14): why?
1640 if (m_ToolBar)
1641 {
1642 UpdateToolBar();
1643 CodeBlocksLayoutEvent evt(cbEVT_UPDATE_VIEW_LAYOUT);
1644 Manager::Get()->ProcessEvent(evt);
1645 m_ToolbarNeedReparse = true;
1646 TRACE(_T("CodeCompletion::RereadOptions: Starting m_TimerToolbar."));
1647 m_TimerToolbar.Start(TOOLBAR_REFRESH_DELAY, wxTIMER_ONE_SHOT);
1648 }
1649
1650 m_DocHelper.RereadOptions(cfg);
1651 }
1652
1653 void CodeCompletion::UpdateToolBar()
1654 {
1655 ConfigManager* cfg = Manager::Get()->GetConfigManager(_T("code_completion"));
1656 const bool showScope = cfg->ReadBool(_T("/scope_filter"), true);
1657 const int scopeLength = cfg->ReadInt(_T("/toolbar_scope_length"), 280);
1658 const int functionLength = cfg->ReadInt(_T("/toolbar_function_length"), 660);
1659
1660 if (showScope && !m_Scope)
1661 {
1662 // Show the scope choice
1663 m_Scope = new wxChoice(m_ToolBar, XRCID("chcCodeCompletionScope"), wxPoint(0, 0), wxSize(scopeLength, -1), 0, 0);
1664 m_ToolBar->InsertControl(0, m_Scope);
1665 }
1666 else if (!showScope && m_Scope)
1667 {
1668 // Hide the scope choice
1669 m_ToolBar->DeleteTool(m_Scope->GetId());
1670 m_Scope = nullptr;
1671 }
1672 else if (m_Scope)
1673 {
1674 // Just apply new size to scope choice
1675 m_Scope->SetSize(wxSize(scopeLength, -1));
1676 }
1677
1678 m_Function->SetSize(wxSize(functionLength, -1));
1679
1680 m_ToolBar->Realize();
1681 m_ToolBar->SetInitialSize();
1682 }
1683
1684 void CodeCompletion::OnUpdateUI(wxUpdateUIEvent& event)
1685 {
1686 wxString NameUnderCursor;
1687 bool IsInclude = false;
1688 const bool HasNameUnderCursor = CodeCompletionHelper::EditorHasNameUnderCursor(NameUnderCursor, IsInclude);
1689
1690 const bool HasEd = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor() != 0;
1691 if (m_EditMenu)
1692 {
1693 const bool RenameEnable = HasNameUnderCursor && !IsInclude && m_NativeParser.GetParser().Done();
1694 m_EditMenu->Enable(idMenuRenameSymbols, RenameEnable);
1695 }
1696
1697 if (m_SearchMenu)
1698 {
1699 m_SearchMenu->Enable(idMenuGotoFunction, HasEd);
1700 m_SearchMenu->Enable(idMenuGotoPrevFunction, HasEd);
1701 m_SearchMenu->Enable(idMenuGotoNextFunction, HasEd);
1702
1703 const bool GotoEnable = HasNameUnderCursor && !IsInclude;
1704 m_SearchMenu->Enable(idMenuGotoDeclaration, GotoEnable);
1705 m_SearchMenu->Enable(idMenuGotoImplementation, GotoEnable);
1706 const bool FindEnable = HasNameUnderCursor && !IsInclude && m_NativeParser.GetParser().Done();
1707 m_SearchMenu->Enable(idMenuFindReferences, FindEnable);
1708 const bool IncludeEnable = HasNameUnderCursor && IsInclude;
1709 m_SearchMenu->Enable(idMenuOpenIncludeFile, IncludeEnable);
1710 }
1711
1712 if (m_ViewMenu)
1713 {
1714 bool isVis = IsWindowReallyShown((wxWindow*)m_NativeParser.GetClassBrowser());
1715 m_ViewMenu->Check(idViewClassBrowser, isVis);
1716 }
1717
1718 if (m_ProjectMenu)
1719 {
1720 cbProject* project = m_NativeParser.GetCurrentProject();
1721 m_ProjectMenu->Enable(idCurrentProjectReparse, project);
1722 }
1723
1724 // must do...
1725 event.Skip();
1726 }
1727
1728 void CodeCompletion::OnViewClassBrowser(wxCommandEvent& event)
1729 {
1730 #if wxCHECK_VERSION(3, 0, 0)
1731 (void)event;
1732 cbMessageBox(_("The symbols browser is disabled in wx3.x builds.\n"
1733 "We've done this because it causes crashes."), _("Information"), wxICON_INFORMATION);
1734 return;
1735 #else
1736 if (!Manager::Get()->GetConfigManager(_T("code_completion"))->ReadBool(_T("/use_symbols_browser"), true))
1737 {
1738 cbMessageBox(_("The symbols browser is disabled in code-completion options.\n"
1739 "Please enable it there first..."), _("Information"), wxICON_INFORMATION);
1740 return;
1741 }
1742 CodeBlocksDockEvent evt(event.IsChecked() ? cbEVT_SHOW_DOCK_WINDOW : cbEVT_HIDE_DOCK_WINDOW);
1743 evt.pWindow = (wxWindow*)m_NativeParser.GetClassBrowser();
1744 Manager::Get()->ProcessEvent(evt);
1745 #endif // wxCHECK_VERSION
1746 }
1747
1748 void CodeCompletion::OnGotoFunction(cb_unused wxCommandEvent& event)
1749 {
1750 EditorManager* edMan = Manager::Get()->GetEditorManager();
1751 cbEditor* ed = edMan->GetBuiltinActiveEditor();
1752 if (!ed)
1753 return;
1754
1755 TRACE(_T("OnGotoFunction"));
1756
1757 m_NativeParser.GetParser().ParseBufferForFunctions(ed->GetControl()->GetText());
1758
1759
1760 TokenTree* tree = m_NativeParser.GetParser().GetTempTokenTree();
1761
1762 CC_LOCKER_TRACK_TT_MTX_LOCK(s_TokenTreeMutex)
1763
1764 if (tree->empty())
1765 {
1766 cbMessageBox(_("No functions parsed in this file..."));
1767 CC_LOCKER_TRACK_TT_MTX_UNLOCK(s_TokenTreeMutex)
1768 }
1769 else
1770 {
1771 GotoFunctionDlg::Iterator iterator;
1772
1773 for (size_t i = 0; i < tree->size(); i++)
1774 {
1775 Token* token = tree->at(i);
1776 if (token && token->m_TokenKind & tkAnyFunction)
1777 {
1778 GotoFunctionDlg::FunctionToken ft;
1779 // We need to clone the internal data of the strings to make them thread safe.
1780 ft.displayName = wxString(token->DisplayName().c_str());
1781 ft.name = wxString(token->m_Name.c_str());
1782 ft.line = token->m_Line;
1783 ft.implLine = token->m_ImplLine;
1784 if (!token->m_FullType.empty())
1785 ft.paramsAndreturnType = wxString((token->m_Args + wxT(" -> ") + token->m_FullType).c_str());
1786 else
1787 ft.paramsAndreturnType = wxString(token->m_Args.c_str());
1788 ft.funcName = wxString((token->GetNamespace() + token->m_Name).c_str());
1789
1790 iterator.AddToken(ft);
1791 }
1792 }
1793
1794 tree->clear();
1795
1796 CC_LOCKER_TRACK_TT_MTX_UNLOCK(s_TokenTreeMutex)
1797
1798 iterator.Sort();
1799 GotoFunctionDlg dlg(Manager::Get()->GetAppWindow(), &iterator);
1800 PlaceWindow(&dlg);
1801 if (dlg.ShowModal() == wxID_OK)
1802 {
1803 int selection = dlg.GetSelection();
1804 if (selection != wxNOT_FOUND) {
1805 const GotoFunctionDlg::FunctionToken *ft = iterator.GetToken(selection);
1806 if (ed && ft)
1807 {
1808 TRACE(F(_T("OnGotoFunction() : Token '%s' found at line %u."), ft->name.wx_str(), ft->line));
1809 ed->GotoTokenPosition(ft->implLine - 1, ft->name);
1810 }
1811 }
1812 }
1813 }
1814 }
1815
1816 void CodeCompletion::OnGotoPrevFunction(cb_unused wxCommandEvent& event)
1817 {
1818 GotoFunctionPrevNext(); // prev function
1819 }
1820
1821 void CodeCompletion::OnGotoNextFunction(cb_unused wxCommandEvent& event)
1822 {
1823 GotoFunctionPrevNext(true); // next function
1824 }
1825
1826 void CodeCompletion::OnClassMethod(cb_unused wxCommandEvent& event)
1827 {
1828 DoClassMethodDeclImpl();
1829 }
1830
1831 void CodeCompletion::OnUnimplementedClassMethods(cb_unused wxCommandEvent& event)
1832 {
1833 DoAllMethodsImpl();
1834 }
1835
1836 void CodeCompletion::OnGotoDeclaration(wxCommandEvent& event)
1837 {
1838 EditorManager* edMan = Manager::Get()->GetEditorManager();
1839 cbEditor* editor = edMan->GetBuiltinActiveEditor();
1840 if (!editor)
1841 return;
1842
1843 TRACE(_T("OnGotoDeclaration"));
1844
1845 const int pos = editor->GetControl()->GetCurrentPos();
1846 const int startPos = editor->GetControl()->WordStartPosition(pos, true);
1847 const int endPos = editor->GetControl()->WordEndPosition(pos, true);
1848 wxString target;
1849 // if there is a tilde "~", the token can either be a destructor or an Bitwise NOT (One's
1850 // Complement) operator
1851 bool isDestructor = false;
1852 if (CodeCompletionHelper::GetLastNonWhitespaceChar(editor->GetControl(), startPos) == _T('~'))
1853 isDestructor = true;
1854 target << editor->GetControl()->GetTextRange(startPos, endPos);
1855 if (target.IsEmpty())
1856 return;
1857
1858 // prepare a boolean filter for declaration/implementation
1859 bool isDecl = event.GetId() == idGotoDeclaration || event.GetId() == idMenuGotoDeclaration;
1860 bool isImpl = event.GetId() == idGotoImplementation || event.GetId() == idMenuGotoImplementation;
1861
1862 // get the matching set
1863 TokenIdxSet result;
1864 m_NativeParser.MarkItemsByAI(result, true, false, true, endPos);
1865
1866 TokenTree* tree = m_NativeParser.GetParser().GetTokenTree();
1867
1868 CC_LOCKER_TRACK_TT_MTX_LOCK(s_TokenTreeMutex)
1869
1870 // handle destructor function, first we try to see if it is a destructor, we simply do a semantic
1871 // check of the token under cursor, otherwise, it is a variable.
1872 if (isDestructor)
1873 {
1874 TokenIdxSet tmp = result;
1875 result.clear();
1876
1877 for (TokenIdxSet::const_iterator it = tmp.begin(); it != tmp.end(); ++it)
1878 {
1879 const Token* token = tree->at(*it);
1880 if (token && token->m_TokenKind == tkClass)
1881 {
1882 token = tree->at(tree->TokenExists(_T("~") + target, token->m_Index, tkDestructor));
1883 if (token)
1884 result.insert(token->m_Index);
1885 }
1886 }
1887
1888 // no destructor found, this could be a variable.
1889 if (result.empty())
1890 result = tmp;
1891 }
1892 else // handle constructor and functions
1893 {
1894 // AAA * p = new AAA();
1895 // ^^^--------------------------case1:go to class definition kind tokens
1896 // ^^^------------case2:go to function kind tokens such as constructors
1897 // if a token is followed by a '(', it is regarded as a function
1898 const bool isFunction = CodeCompletionHelper::GetNextNonWhitespaceChar(editor->GetControl(), endPos) == _T('(');
1899 // copy the token index set for a fall back case later
1900 TokenIdxSet savedResult = result;
1901 // loop the result, and strip unrelated tokens
1902 for (TokenIdxSet::const_iterator it = result.begin(); it != result.end();)
1903 {
1904 const Token* token = tree->at(*it);
1905 if (isFunction && token && token->m_TokenKind == tkClass)
1906 result.erase(it++); // a class kind token is removed since we need a function
1907 else if (!isFunction && token && token->m_TokenKind == tkConstructor)
1908 result.erase(it++); // a constructor token is removed since we don't need a function
1909 else
1910 ++it;
1911 }
1912 // fall back: restore the saved result in a special case that a class doesn't have a constructor
1913 // defined (implicitly defined by compiler)
1914 // E.g. hover on case2 go to class AAA token since no AAA::AAA(); is defined or declared.
1915 if (!result.size())
1916 result = savedResult;
1917 }
1918
1919 // handle function overloading
1920 if (result.size() > 1)
1921 {
1922 const size_t curLine = editor->GetControl()->GetCurrentLine() + 1;
1923 for (TokenIdxSet::const_iterator it = result.begin(); it != result.end(); ++it)
1924 {
1925 const Token* token = tree->at(*it);
1926 if (token && (token->m_Line == curLine || token->m_ImplLine == curLine) )
1927 {
1928 const int theOnlyOne = *it;
1929 result.clear();
1930 result.insert(theOnlyOne);
1931 break;
1932 }
1933 }
1934 }
1935
1936 // data for the choice dialog
1937 std::deque<CodeCompletionHelper::GotoDeclarationItem> foundItems;
1938 wxArrayString selections;
1939
1940 wxString editorFilename;
1941 unsigned editorLine = -1;
1942 bool tokenFound = false;
1943
1944 // one match
1945 if (result.size() == 1)
1946 {
1947 Token* token = NULL;
1948 Token* sel = tree->at(*(result.begin()));
1949 if ( (isImpl && !sel->GetImplFilename().IsEmpty())
1950 || (isDecl && !sel->GetFilename().IsEmpty()) )
1951 {
1952 token = sel;
1953 }
1954 if (token)
1955 {
1956 // FIXME: implement this correctly, because now it is not showing full results
1957 if ( wxGetKeyState(WXK_CONTROL)
1958 && wxGetKeyState(WXK_SHIFT)
1959 && ( event.GetId() == idGotoDeclaration
1960 || event.GetId() == idGotoImplementation ) )
1961 {
1962 // FIXME: this code can lead to a deadlock (because of double locking from single thread)
1963 CCDebugInfo info(nullptr, &m_NativeParser.GetParser(), token);
1964 info.ShowModal();
1965 }
1966 else if (isImpl)
1967 {
1968 editorFilename = token->GetImplFilename();
1969 editorLine = token->m_ImplLine - 1;
1970 }
1971 else if (isDecl)
1972 {
1973 editorFilename = token->GetFilename();
1974 editorLine = token->m_Line - 1;
1975 }
1976
1977 tokenFound = true;
1978 }
1979 }
1980 // if more than one match, display a selection dialog
1981 else if (result.size() > 1)
1982 {
1983 // TODO: we could parse the line containing the text so
1984 // if namespaces were included, we could limit the results (and be more accurate)
1985 for (TokenIdxSet::const_iterator it = result.begin(); it != result.end(); ++it)
1986 {
1987 const Token* token = tree->at(*it);
1988 if (token)
1989 {
1990 CodeCompletionHelper::GotoDeclarationItem item;
1991
1992 if (isImpl)
1993 {
1994 item.filename = token->GetImplFilename();
1995 item.line = token->m_ImplLine - 1;
1996 }
1997 else if (isDecl)
1998 {
1999 item.filename = token->GetFilename();
2000 item.line = token->m_Line - 1;
2001 }
2002
2003 // only match tokens that have filename info
2004 if (!item.filename.empty())
2005 {
2006 selections.Add(token->DisplayName());
2007 foundItems.push_back(item);
2008 }
2009 }
2010 }
2011 }
2012
2013 CC_LOCKER_TRACK_TT_MTX_UNLOCK(s_TokenTreeMutex)
2014
2015 if (selections.GetCount() > 1)
2016 {
2017 int sel = cbGetSingleChoiceIndex(_("Please make a selection:"), _("Multiple matches"), selections,
2018 Manager::Get()->GetAppWindow(), wxSize(400, 400));
2019 if (sel == -1)
2020 return;
2021
2022 const CodeCompletionHelper::GotoDeclarationItem &item = foundItems[sel];
2023 editorFilename = item.filename;
2024 editorLine = item.line;
2025 tokenFound = true;
2026 }
2027 else if (selections.GetCount() == 1)
2028 {
2029 // number of selections can be < result.size() due to the if tests, so in case we fall
2030 // back on 1 entry no need to show a selection
2031 const CodeCompletionHelper::GotoDeclarationItem &item = foundItems.front();
2032 editorFilename = item.filename;
2033 editorLine = item.line;
2034 tokenFound = true;
2035 }
2036
2037 // do we have a token?
2038 if (tokenFound)
2039 {
2040 cbEditor* targetEditor = edMan->Open(editorFilename);
2041 if (targetEditor)
2042 targetEditor->GotoTokenPosition(editorLine, target);
2043 else
2044 {
2045 if (isImpl)
2046 cbMessageBox(wxString::Format(_("Implementation not found: %s"), target.wx_str()),
2047 _("Warning"), wxICON_WARNING);
2048 else if (isDecl)
2049 cbMessageBox(wxString::Format(_("Declaration not found: %s"), target.wx_str()),
2050 _("Warning"), wxICON_WARNING);
2051 }
2052 }
2053 else
2054 cbMessageBox(wxString::Format(_("Not found: %s"), target.wx_str()), _("Warning"), wxICON_WARNING);
2055 }
2056
2057 void CodeCompletion::OnFindReferences(cb_unused wxCommandEvent& event)
2058 {
2059 m_CodeRefactoring.FindReferences();
2060 }
2061
2062 void CodeCompletion::OnRenameSymbols(cb_unused wxCommandEvent& event)
2063 {
2064 m_CodeRefactoring.RenameSymbols();
2065 }
2066
2067 void CodeCompletion::OnOpenIncludeFile(cb_unused wxCommandEvent& event)
2068 {
2069 wxString lastIncludeFileFrom;
2070 cbEditor* editor = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
2071 if (editor)
2072 lastIncludeFileFrom = editor->GetFilename();
2073
2074 // check one more time because menu entries are enabled only when it makes sense
2075 // but the shortcut accelerator can always be executed
2076 bool MoveOn = false;
2077 wxString NameUnderCursor;
2078 bool IsInclude = false;
2079 if (CodeCompletionHelper::EditorHasNameUnderCursor(NameUnderCursor, IsInclude))
2080 {
2081 if (IsInclude)
2082 MoveOn = true;
2083 }
2084
2085 if (!MoveOn)
2086 return; // nothing under cursor or thing under cursor is not an include
2087
2088 TRACE(_T("OnOpenIncludeFile"));
2089
2090 wxArrayString foundSet = m_NativeParser.GetParser().FindFileInIncludeDirs(NameUnderCursor); // search in all parser's include dirs
2091
2092 // look in the same dir as the source file
2093 wxFileName fname = NameUnderCursor;
2094 wxFileName base = lastIncludeFileFrom;
2095 NormalizePath(fname, base.GetPath());
2096 if (wxFileExists(fname.GetFullPath()) )
2097 foundSet.Add(fname.GetFullPath());
2098
2099 // search for the file in project files
2100 cbProject* project = m_NativeParser.GetProjectByEditor(editor);
2101 if (project)
2102 {
2103 for (FilesList::const_iterator it = project->GetFilesList().begin();
2104 it != project->GetFilesList().end(); ++it)
2105 {
2106 ProjectFile* pf = *it;
2107 if (!pf)
2108 continue;
2109
2110 if ( IsSuffixOfPath(NameUnderCursor, pf->file.GetFullPath()) )
2111 foundSet.Add(pf->file.GetFullPath());
2112 }
2113 }
2114
2115 // Remove duplicates
2116 for (int i = 0; i < (int)foundSet.Count() - 1; i++)
2117 {
2118 for (int j = i + 1; j < (int)foundSet.Count(); )
2119 {
2120 if (foundSet.Item(i) == foundSet.Item(j))
2121 foundSet.RemoveAt(j);
2122 else
2123 j++;
2124 }
2125 }
2126
2127 wxString selectedFile;
2128 if (foundSet.GetCount() > 1)
2129 { // more than 1 hit : let the user choose
2130 SelectIncludeFile Dialog(Manager::Get()->GetAppWindow());
2131 Dialog.AddListEntries(foundSet);
2132 PlaceWindow(&Dialog);
2133 if (Dialog.ShowModal() == wxID_OK)
2134 selectedFile = Dialog.GetIncludeFile();
2135 else
2136 return; // user cancelled the dialog...
2137 }
2138 else if (foundSet.GetCount() == 1)
2139 selectedFile = foundSet[0];
2140
2141 if (!selectedFile.IsEmpty())
2142 {
2143 EditorManager* edMan = Manager::Get()->GetEditorManager();
2144 edMan->Open(selectedFile);
2145 return;
2146 }
2147
2148 cbMessageBox(wxString::Format(_("Not found: %s"), NameUnderCursor.c_str()), _("Warning"), wxICON_WARNING);
2149 }
2150
2151 void CodeCompletion::OnCurrentProjectReparse(wxCommandEvent& event)
2152 {
2153 m_NativeParser.ReparseCurrentProject();
2154 event.Skip();
2155 }
2156
2157 void CodeCompletion::OnSelectedProjectReparse(wxCommandEvent& event)
2158 {
2159 m_NativeParser.ReparseSelectedProject();
2160 event.Skip();
2161 }
2162
2163 void CodeCompletion::OnSelectedFileReparse(wxCommandEvent& event)
2164 {
2165 wxTreeCtrl* tree = Manager::Get()->GetProjectManager()->GetUI().GetTree();
2166 if (!tree)
2167 return;
2168
2169 wxTreeItemId treeItem = Manager::Get()->GetProjectManager()->GetUI().GetTreeSelection();
2170 if (!treeItem.IsOk())
2171 return;
2172
2173 const FileTreeData* data = static_cast<FileTreeData*>(tree->GetItemData(treeItem));
2174 if (!data)
2175 return;
2176
2177 if (data->GetKind() == FileTreeData::ftdkFile)
2178 {
2179 cbProject* project = data->GetProject();
2180 ProjectFile* pf = data->GetProjectFile();
2181 if (pf && m_NativeParser.ReparseFile(project, pf->file.GetFullPath()))
2182 {
2183 CCLogger::Get()->DebugLog(_T("Reparsing the selected file ") +
2184 pf->file.GetFullPath());
2185 }
2186 }
2187
2188 event.Skip();
2189 }
2190
2191 void CodeCompletion::OnAppDoneStartup(CodeBlocksEvent& event)
2192 {
2193 if (!m_InitDone)
2194 DoParseOpenedProjectAndActiveEditor();
2195
2196 event.Skip();
2197 }
2198
2199 void CodeCompletion::OnWorkspaceChanged(CodeBlocksEvent& event)
2200 {
2201 // EVT_WORKSPACE_CHANGED is a powerful event, it's sent after any project
2202 // has finished loading or closing. It's the *LAST* event to be sent when
2203 // the workspace has been changed. So it's the ideal time to parse files
2204 // and update your widgets.
2205 if (IsAttached() && m_InitDone)
2206 {
2207 cbProject* project = Manager::Get()->GetProjectManager()->GetActiveProject();
2208 // if we receive a workspace changed event, but the project is NULL, this means two condition
2209 // could happen.
2210 // (1) the user try to close the application, so we don't need to update the UI here.
2211 // (2) the user just open a new project after cb started up
2212 if (project)
2213 {
2214 if (!m_NativeParser.GetParserByProject(project))
2215 m_NativeParser.CreateParser(project);
2216
2217 // Update the Function toolbar
2218 TRACE(_T("CodeCompletion::OnWorkspaceChanged: Starting m_TimerToolbar."));
2219 m_TimerToolbar.Start(TOOLBAR_REFRESH_DELAY, wxTIMER_ONE_SHOT);
2220
2221 // Update the class browser
2222 if (m_NativeParser.GetParser().ClassBrowserOptions().displayFilter == bdfProject)
2223 m_NativeParser.UpdateClassBrowser();
2224 }
2225 }
2226 event.Skip();
2227 }
2228
2229 void CodeCompletion::OnProjectActivated(CodeBlocksEvent& event)
2230 {
2231 // The Class browser shouldn't be updated if we're in the middle of loading/closing
2232 // a project/workspace, because the class browser would need to be updated again.
2233 // So we need to update it with the EVT_WORKSPACE_CHANGED event, which gets
2234 // triggered after everything's finished loading/closing.
2235 if (!ProjectManager::IsBusy() && IsAttached() && m_InitDone)
2236 {
2237 cbProject* project = event.GetProject();
2238 if (project && !m_NativeParser.GetParserByProject(project) && project->GetFilesCount() > 0)
2239 m_NativeParser.CreateParser(project);
2240
2241 if (m_NativeParser.GetParser().ClassBrowserOptions().displayFilter == bdfProject)
2242 m_NativeParser.UpdateClassBrowser();
2243 }
2244
2245 m_NeedsBatchColour = true;
2246
2247 event.Skip();
2248 }
2249
2250 void CodeCompletion::OnProjectClosed(CodeBlocksEvent& event)
2251 {
2252 // After this, the Class Browser needs to be updated. It will happen
2253 // when we receive the next EVT_PROJECT_ACTIVATED event.
2254 if (IsAttached() && m_InitDone)
2255 {
2256 cbProject* project = event.GetProject();
2257 if (project && m_NativeParser.GetParserByProject(project))
2258 {
2259 // there may be some pending files to be reparsed in m_ReparsingMap
2260 // so just remove them
2261 ReparsingMap::iterator it = m_ReparsingMap.find(project);
2262 if (it != m_ReparsingMap.end())
2263 m_ReparsingMap.erase(it);
2264
2265 // remove the Parser instance associated with the project
2266 m_NativeParser.DeleteParser(project);
2267 }
2268 }
2269 event.Skip();
2270 }
2271
2272 void CodeCompletion::OnProjectSaved(CodeBlocksEvent& event)
2273 {
2274 // reparse project (compiler search dirs might have changed)
2275 m_TimerProjectSaved.SetClientData(event.GetProject());
2276 // we need more time for waiting wxExecute in NativeParser::AddCompilerPredefinedMacros
2277 TRACE(_T("CodeCompletion::OnProjectSaved: Starting m_TimerProjectSaved."));
2278 m_TimerProjectSaved.Start(200, wxTIMER_ONE_SHOT);
2279
2280 event.Skip();
2281 }
2282
2283 void CodeCompletion::OnProjectFileAdded(CodeBlocksEvent& event)
2284 {
2285 if (IsAttached() && m_InitDone)
2286 m_NativeParser.AddFileToParser(event.GetProject(), event.GetString());
2287 event.Skip();
2288 }
2289
2290 void CodeCompletion::OnProjectFileRemoved(CodeBlocksEvent& event)
2291 {
2292 if (IsAttached() && m_InitDone)
2293 m_NativeParser.RemoveFileFromParser(event.GetProject(), event.GetString());
2294 event.Skip();
2295 }
2296
2297 void CodeCompletion::OnProjectFileChanged(CodeBlocksEvent& event)
2298 {
2299 if (IsAttached() && m_InitDone)
2300 {
2301 // TODO (Morten#5#) make sure the event.GetProject() is valid.
2302 cbProject* project = event.GetProject();
2303 wxString filename = event.GetString();
2304 if (!project)
2305 project = m_NativeParser.GetProjectByFilename(filename);
2306 if (project && m_NativeParser.ReparseFile(project, filename))
2307 CCLogger::Get()->DebugLog(_T("Reparsing when file changed: ") + filename);
2308 }
2309 event.Skip();
2310 }
2311
2312 void CodeCompletion::OnEditorSave(CodeBlocksEvent& event)
2313 {
2314 if (!ProjectManager::IsBusy() && IsAttached() && m_InitDone && event.GetEditor())
2315 {
2316 cbProject* project = event.GetProject();
2317
2318 // we know which project the editor belongs to, so put a (project, file) pair to the
2319 // m_ReparsingMap
2320 ReparsingMap::iterator it = m_ReparsingMap.find(project);
2321 if (it == m_ReparsingMap.end())
2322 it = m_ReparsingMap.insert(std::make_pair(project, wxArrayString())).first;
2323
2324 const wxString& filename = event.GetEditor()->GetFilename();
2325 if (it->second.Index(filename) == wxNOT_FOUND)
2326 it->second.Add(filename);
2327
2328 // start the timer, so that it will be handled in timer event handler
2329 TRACE(_T("CodeCompletion::OnEditorSave: Starting m_TimerReparsing."));
2330 m_TimerReparsing.Start(EDITOR_ACTIVATED_DELAY + it->second.GetCount() * 10, wxTIMER_ONE_SHOT);
2331 }
2332
2333 event.Skip();
2334 }
2335
2336 void CodeCompletion::OnEditorOpen(CodeBlocksEvent& event)
2337 {
2338 if (!Manager::IsAppShuttingDown() && IsAttached() && m_InitDone)
2339 {
2340 cbEditor* ed = Manager::Get()->GetEditorManager()->GetBuiltinEditor(event.GetEditor());
2341 if (ed)
2342 {
2343 FunctionsScopePerFile* funcdata = &(m_AllFunctionsScopes[ed->GetFilename()]);
2344 funcdata->parsed = false;
2345 }
2346 }
2347
2348 event.Skip();
2349 }
2350
2351 void CodeCompletion::OnEditorActivated(CodeBlocksEvent& event)
2352 {
2353 TRACE(_T("CodeCompletion::OnEditorActivated(): Enter"));
2354
2355 if (!ProjectManager::IsBusy() && IsAttached() && m_InitDone && event.GetEditor())
2356 {
2357 m_LastEditor = Manager::Get()->GetEditorManager()->GetBuiltinEditor(event.GetEditor());
2358
2359 TRACE(_T("CodeCompletion::OnEditorActivated(): Starting m_TimerEditorActivated."));
2360 m_TimerEditorActivated.Start(EDITOR_ACTIVATED_DELAY, wxTIMER_ONE_SHOT);
2361
2362 if (m_TimerToolbar.IsRunning())
2363 m_TimerToolbar.Stop();
2364 }
2365
2366 event.Skip();
2367 TRACE(_T("CodeCompletion::OnEditorActivated(): Leave"));
2368 }
2369
2370 void CodeCompletion::OnEditorClosed(CodeBlocksEvent& event)
2371 {
2372 EditorManager* edm = Manager::Get()->GetEditorManager();
2373 if (!edm)
2374 {
2375 event.Skip();
2376 return;
2377 }
2378
2379 wxString activeFile;
2380 EditorBase* eb = edm->GetActiveEditor();
2381 if (eb)
2382 activeFile = eb->GetFilename();
2383
2384 TRACE(_T("CodeCompletion::OnEditorClosed(): Closed editor's file is %s"), activeFile.wx_str());
2385
2386 if (m_LastEditor == event.GetEditor())
2387 {
2388 m_LastEditor = nullptr;
2389 if (m_TimerEditorActivated.IsRunning())
2390 m_TimerEditorActivated.Stop();
2391 }
2392
2393 // tell m_NativeParser that a builtin editor was closed
2394 if ( edm->GetBuiltinEditor(event.GetEditor()) )
2395 m_NativeParser.OnEditorClosed(event.GetEditor());
2396
2397 m_LastFile.Clear();
2398
2399 // we need to clear CC toolbar only when we are closing last editor
2400 // in other situations OnEditorActivated does this job
2401 // If no editors were opened, or a non-buildin-editor was active, disable the CC toolbar
2402 if (edm->GetEditorsCount() == 0 || !edm->GetActiveEditor() || !edm->GetActiveEditor()->IsBuiltinEditor())
2403 {
2404 EnableToolbarTools(false);
2405
2406 // clear toolbar when closing last editor
2407 if (m_Scope)
2408 m_Scope->Clear();
2409 if (m_Function)
2410 m_Function->Clear();
2411
2412 cbEditor* ed = edm->GetBuiltinEditor(event.GetEditor());
2413 wxString filename;
2414 if (ed)
2415 filename = ed->GetFilename();
2416
2417 m_AllFunctionsScopes[filename].m_FunctionsScope.clear();
2418 m_AllFunctionsScopes[filename].m_NameSpaces.clear();
2419 m_AllFunctionsScopes[filename].parsed = false;
2420 if (m_NativeParser.GetParser().ClassBrowserOptions().displayFilter == bdfFile)
2421 m_NativeParser.UpdateClassBrowser();
2422 }
2423
2424 event.Skip();
2425 }
2426
2427 void CodeCompletion::OnCCLogger(CodeBlocksThreadEvent& event)
2428 {
2429 if (!Manager::IsAppShuttingDown())
2430 Manager::Get()->GetLogManager()->Log(event.GetString());
2431 }
2432
2433 void CodeCompletion::OnCCDebugLogger(CodeBlocksThreadEvent& event)
2434 {
2435 if (!Manager::IsAppShuttingDown())
2436 Manager::Get()->GetLogManager()->DebugLog(event.GetString());
2437 }
2438
2439 void CodeCompletion::OnParserStart(wxCommandEvent& event)
2440 {
2441 cbProject* project = static_cast<cbProject*>(event.GetClientData());
2442 ParserCommon::ParserState state = static_cast<ParserCommon::ParserState>(event.GetInt());
2443 // Parser::OnBatchTimer will send this Parser Start event
2444 // If it starts a full parsing(ptCreateParser), we should prepare some data for the header
2445 // file crawler
2446 if (state == ParserCommon::ptCreateParser)
2447 {
2448 if (m_CCEnableHeaders)
2449 {
2450 wxArrayString &dirs = GetSystemIncludeDirs(project, true); // true means update the cache
2451 if (!dirs.empty())
2452 {
2453 SystemHeadersThread* thread = new SystemHeadersThread(this,
2454 &m_SystemHeadersThreadCS,
2455 m_SystemHeadersMap, dirs);
2456 m_SystemHeadersThreads.push_back(thread);
2457 thread->Run();
2458 }
2459 }
2460
2461 cbEditor* editor = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
2462 if (m_NativeParser.GetProjectByEditor(editor) == project)
2463 EnableToolbarTools(false);
2464 }
2465 }
2466
2467 void CodeCompletion::OnParserEnd(wxCommandEvent& event)
2468 {
2469 EditorManager* edMan = Manager::Get()->GetEditorManager();
2470 cbEditor* editor = edMan->GetBuiltinActiveEditor();
2471 if (editor)
2472 {
2473 m_ToolbarNeedReparse = true;
2474 TRACE(_T("CodeCompletion::OnParserEnd: Starting m_TimerToolbar."));
2475 m_TimerToolbar.Start(TOOLBAR_REFRESH_DELAY, wxTIMER_ONE_SHOT);
2476 }
2477
2478 if (m_NeedsBatchColour)
2479 {
2480 for (int edIdx = edMan->GetEditorsCount() - 1; edIdx >= 0; --edIdx)
2481 {
2482 editor = edMan->GetBuiltinEditor(edIdx);
2483 if (editor)
2484 UpdateEditorSyntax(editor);
2485 }
2486 m_NeedsBatchColour = false;
2487 }
2488
2489 event.Skip();
2490 }
2491
2492 void CodeCompletion::OnSystemHeadersThreadMessage(CodeBlocksThreadEvent& event)
2493 {
2494 CCLogger::Get()->DebugLog(event.GetString());
2495 }
2496
2497 void CodeCompletion::OnSystemHeadersThreadFinish(CodeBlocksThreadEvent& event)
2498 {
2499 if (m_SystemHeadersThreads.empty())
2500 return;
2501 // Wait for the current thread to finish and remove it from the thread list.
2502 SystemHeadersThread* thread = static_cast<SystemHeadersThread*>(event.GetClientData());
2503
2504 for (std::list<SystemHeadersThread*>::iterator it = m_SystemHeadersThreads.begin();
2505 it != m_SystemHeadersThreads.end();
2506 ++it)
2507 {
2508 if (*it == thread)
2509 {
2510 if (!event.GetString().IsEmpty())
2511 CCLogger::Get()->DebugLog(event.GetString());
2512 thread->Wait();
2513 delete thread;
2514 m_SystemHeadersThreads.erase(it);
2515 break;
2516 }
2517 }
2518 }
2519
2520 int CodeCompletion::DoClassMethodDeclImpl()
2521 {
2522 if (!IsAttached() || !m_InitDone)
2523 return -1;
2524
2525 EditorManager* edMan = Manager::Get()->GetEditorManager();
2526 cbEditor* ed = edMan->GetBuiltinActiveEditor();
2527 if (!ed)
2528 return -3;
2529
2530 FileType ft = FileTypeOf(ed->GetShortName());
2531 if ( ft != ftHeader && ft != ftSource && ft != ftTemplateSource) // only parse source/header files
2532 return -4;
2533
2534 if (!m_NativeParser.GetParser().Done())
2535 {
2536 wxString msg = _("The Parser is still parsing files.");
2537 msg += m_NativeParser.GetParser().NotDoneReason();
2538 CCLogger::Get()->DebugLog(msg);
2539 return -5;
2540 }
2541
2542 int success = -6;
2543
2544 // TokenTree* tree = m_NativeParser.GetParser().GetTokenTree(); // The one used inside InsertClassMethodDlg
2545
2546 CC_LOCKER_TRACK_TT_MTX_LOCK(s_TokenTreeMutex)
2547
2548 // open the insert class dialog
2549 wxString filename = ed->GetFilename();
2550 InsertClassMethodDlg dlg(Manager::Get()->GetAppWindow(), &m_NativeParser.GetParser(), filename);
2551 PlaceWindow(&dlg);
2552 if (dlg.ShowModal() == wxID_OK)
2553 {
2554 cbStyledTextCtrl* control = ed->GetControl();
2555 int pos = control->GetCurrentPos();
2556 int line = control->LineFromPosition(pos);
2557 control->GotoPos(control->PositionFromLine(line));
2558
2559 wxArrayString result = dlg.GetCode();
2560 for (unsigned int i = 0; i < result.GetCount(); ++i)
2561 {
2562 pos = control->GetCurrentPos();
2563 line = control->LineFromPosition(pos);
2564 // get the indent string from previous line
2565 wxString str = ed->GetLineIndentString(line - 1) + result[i];
2566 MatchCodeStyle(str, control->GetEOLMode(), ed->GetLineIndentString(line - 1), control->GetUseTabs(), control->GetTabWidth());
2567 control->SetTargetStart(pos);
2568 control->SetTargetEnd(pos);
2569 control->ReplaceTarget(str);
2570 control->GotoPos(pos + str.Length());// - 3);
2571 }
2572 success = 0;
2573 }
2574
2575 CC_LOCKER_TRACK_TT_MTX_UNLOCK(s_TokenTreeMutex)
2576
2577 return success;
2578 }
2579
2580 int CodeCompletion::DoAllMethodsImpl()
2581 {
2582 if (!IsAttached() || !m_InitDone)
2583 return -1;
2584
2585 EditorManager* edMan = Manager::Get()->GetEditorManager();
2586 cbEditor* ed = edMan->GetBuiltinActiveEditor();
2587 if (!ed)
2588 return -3;
2589
2590 FileType ft = FileTypeOf(ed->GetShortName());
2591 if ( ft != ftHeader && ft != ftSource && ft != ftTemplateSource) // only parse source/header files
2592 return -4;
2593
2594 wxArrayString paths = m_NativeParser.GetAllPathsByFilename(ed->GetFilename());
2595 TokenTree* tree = m_NativeParser.GetParser().GetTokenTree();
2596
2597 CC_LOCKER_TRACK_TT_MTX_LOCK(s_TokenTreeMutex)
2598
2599 // get all filenames' indices matching our mask
2600 TokenFileSet result;
2601 for (size_t i = 0; i < paths.GetCount(); ++i)
2602 {
2603 CCLogger::Get()->DebugLog(_T("CodeCompletion::DoAllMethodsImpl(): Trying to find matches for: ") + paths[i]);
2604 TokenFileSet result_file;
2605 tree->GetFileMatches(paths[i], result_file, true, true);
2606 for (TokenFileSet::const_iterator it = result_file.begin(); it != result_file.end(); ++it)
2607 result.insert(*it);
2608 }
2609
2610 if (result.empty())
2611 {
2612 CC_LOCKER_TRACK_TT_MTX_UNLOCK(s_TokenTreeMutex)
2613
2614 cbMessageBox(_("Could not find any file match in parser's database."), _("Warning"), wxICON_WARNING);
2615 return -5;
2616 }
2617
2618 // loop matching files, loop tokens in file and get list of un-implemented functions
2619 wxArrayString arr; // for selection (keeps strings)
2620 wxArrayInt arrint; // for selection (keeps indices)
2621 typedef std::map<int, std::pair<int, wxString> > ImplMap;
2622 ImplMap im;
2623 for (TokenFileSet::const_iterator itf = result.begin(); itf != result.end(); ++itf)
2624 {
2625 const TokenIdxSet* tokens = tree->GetTokensBelongToFile(*itf);
2626 if (!tokens) continue;
2627
2628 // loop tokens in file
2629 for (TokenIdxSet::const_iterator its = tokens->begin(); its != tokens->end(); ++its)
2630 {
2631 const Token* token = tree->at(*its);
2632 if ( token // valid token
2633 && (token->m_TokenKind & (tkFunction | tkConstructor | tkDestructor)) // is method
2634 && token->m_ImplLine == 0 ) // is un-implemented
2635 {
2636 im[token->m_Line] = std::make_pair(*its, token->DisplayName());
2637 }
2638 }
2639 }
2640
2641 for (ImplMap::const_iterator it = im.begin(); it != im.end(); ++it)
2642 {
2643 arrint.Add(it->second.first);
2644 arr.Add(it->second.second);
2645 }
2646
2647 if (arr.empty())
2648 {
2649 CC_LOCKER_TRACK_TT_MTX_UNLOCK(s_TokenTreeMutex)
2650
2651 cbMessageBox(_("No classes declared or no un-implemented class methods found."), _("Warning"), wxICON_WARNING);
2652 return -5;
2653 }
2654
2655 int success = -5;
2656
2657 // select tokens
2658 MultiSelectDlg dlg(Manager::Get()->GetAppWindow(), arr, true);
2659 if (dlg.ShowModal() == wxID_OK)
2660 {
2661 cbStyledTextCtrl* control = ed->GetControl();
2662 int pos = control->GetCurrentPos();
2663 int line = control->LineFromPosition(pos);
2664 control->GotoPos(control->PositionFromLine(line));
2665
2666 bool addDoxgenComment = Manager::Get()->GetConfigManager(_T("code_completion"))->ReadBool(_T("/add_doxgen_comment"), false);
2667
2668 wxArrayInt indices = dlg.GetSelectedIndices();
2669 for (size_t i = 0; i < indices.GetCount(); ++i)
2670 {
2671 const Token* token = tree->at(arrint[indices[i]]);
2672 if (!token)
2673 continue;
2674
2675 pos = control->GetCurrentPos();
2676 line = control->LineFromPosition(pos);
2677
2678 // actual code generation
2679 wxString str;
2680 if (i > 0)
2681 str << _T("\n");
2682 else
2683 str << ed->GetLineIndentString(line - 1);
2684 if (addDoxgenComment)
2685 str << _T("/** @brief ") << token->m_Name << _T("\n *\n * @todo: document this function\n */\n");
2686 wxString type = token->m_FullType;
2687 if (!type.IsEmpty())
2688 {
2689 // "int *" or "int &" -> "int*" or "int&"
2690 if ( (type.Last() == _T('&') || type.Last() == _T('*'))
2691 && type[type.Len() - 2] == _T(' '))
2692 {
2693 type[type.Len() - 2] = type.Last();
2694 type.RemoveLast();
2695 }
2696 str << type << _T(" ");
2697 }
2698 if (token->m_ParentIndex != -1)
2699 {
2700 const Token* parent = tree->at(token->m_ParentIndex);
2701 if (parent)
2702 str << parent->m_Name << _T("::");
2703 }
2704 str << token->m_Name << token->GetStrippedArgs();
2705 if (token->m_IsConst)
2706 str << _T(" const");
2707 if (token->m_IsNoExcept)
2708 str << _T(" noexcept");
2709 str << _T("\n{\n\t\n}\n");
2710
2711 MatchCodeStyle(str, control->GetEOLMode(), ed->GetLineIndentString(line - 1), control->GetUseTabs(), control->GetTabWidth());
2712
2713 // add code in editor
2714 control->SetTargetStart(pos);
2715 control->SetTargetEnd(pos);
2716 control->ReplaceTarget(str);
2717 control->GotoPos(pos + str.Length());
2718 }
2719 if (!indices.IsEmpty())
2720 {
2721 pos = control->GetCurrentPos();
2722 line = control->LineFromPosition(pos);
2723 control->GotoPos(control->GetLineEndPosition(line - 2));
2724 }
2725 success = 0;
2726 }
2727
2728 CC_LOCKER_TRACK_TT_MTX_UNLOCK(s_TokenTreeMutex)
2729
2730 return success;
2731 }
2732
2733 void CodeCompletion::MatchCodeStyle(wxString& str, int eolStyle, const wxString& indent, bool useTabs, int tabSize)
2734 {
2735 str.Replace(wxT("\n"), GetEOLStr(eolStyle) + indent);
2736 if (!useTabs)
2737 str.Replace(wxT("\t"), wxString(wxT(' '), tabSize));
2738 if (!indent.IsEmpty())
2739 str.RemoveLast(indent.Length());
2740 }
2741
2742 // help method in finding the function position in the vector for the function containing the current line
2743 void CodeCompletion::FunctionPosition(int &scopeItem, int &functionItem) const
2744 {
2745 scopeItem = -1;
2746 functionItem = -1;
2747
2748 for (unsigned int idxSc = 0; idxSc < m_ScopeMarks.size(); ++idxSc)
2749 {
2750 // this is the start and end of a scope
2751 unsigned int start = m_ScopeMarks[idxSc];
2752 unsigned int end = (idxSc + 1 < m_ScopeMarks.size()) ? m_ScopeMarks[idxSc + 1] : m_FunctionsScope.size();
2753
2754 // the scope could have many functions, so loop on the functions
2755 for (int idxFn = 0; start + idxFn < end; ++idxFn)
2756 {
2757 const FunctionScope fs = m_FunctionsScope[start + idxFn];
2758 if (m_CurrentLine >= fs.StartLine && m_CurrentLine <= fs.EndLine)
2759 {
2760 scopeItem = idxSc;
2761 functionItem = idxFn;
2762 }
2763 }
2764 }
2765 }
2766
2767 void CodeCompletion::GotoFunctionPrevNext(bool next /* = false */)
2768 {
2769 EditorManager* edMan = Manager::Get()->GetEditorManager();
2770 cbEditor* ed = edMan->GetBuiltinActiveEditor();
2771 if (!ed)
2772 return;
2773
2774 int current_line = ed->GetControl()->GetCurrentLine();
2775
2776 if (!m_FunctionsScope.size())
2777 return;
2778
2779 // search previous/next function from current line, default: previous
2780 int line = -1;
2781 unsigned int best_func = 0;
2782 bool found_best_func = false;
2783 for (unsigned int idx_func=0; idx_func<m_FunctionsScope.size(); ++idx_func)
2784 {
2785 int best_func_line = m_FunctionsScope[best_func].StartLine;
2786 int func_start_line = m_FunctionsScope[idx_func].StartLine;
2787 if (next)
2788 {
2789 if (best_func_line > current_line) // candidate: is after current line
2790 {
2791 if ( (func_start_line > current_line ) // another candidate
2792 && (func_start_line < best_func_line) ) // decide which is more near
2793 { best_func = idx_func; found_best_func = true; }
2794 }
2795 else if (func_start_line > current_line) // candidate: is after current line
2796 { best_func = idx_func; found_best_func = true; }
2797 }
2798 else // prev
2799 {
2800 if (best_func_line < current_line) // candidate: is before current line
2801 {
2802 if ( (func_start_line < current_line ) // another candidate
2803 && (func_start_line > best_func_line) ) // decide which is closer
2804 { best_func = idx_func; found_best_func = true; }
2805 }
2806 else if (func_start_line < current_line) // candidate: is before current line
2807 { best_func = idx_func; found_best_func = true; }
2808 }
2809 }
2810
2811 if (found_best_func)
2812 { line = m_FunctionsScope[best_func].StartLine; }
2813 else if ( next && m_FunctionsScope[best_func].StartLine>current_line)
2814 { line = m_FunctionsScope[best_func].StartLine; }
2815 else if (!next && m_FunctionsScope[best_func].StartLine<current_line)
2816 { line = m_FunctionsScope[best_func].StartLine; }
2817
2818 if (line != -1)
2819 {
2820 ed->GotoLine(line);
2821 ed->SetFocus();
2822 }
2823 }
2824
2825 // help method in finding the namespace position in the vector for the namespace containing the current line
2826 int CodeCompletion::NameSpacePosition() const
2827 {
2828 int pos = -1;
2829 int startLine = -1;
2830 for (unsigned int idxNs = 0; idxNs < m_NameSpaces.size(); ++idxNs)
2831 {
2832 const NameSpace& ns = m_NameSpaces[idxNs];
2833 if (m_CurrentLine >= ns.StartLine && m_CurrentLine <= ns.EndLine && ns.StartLine > startLine)
2834 {
2835 // got one, maybe there might be a better fitting namespace
2836 // (embedded namespaces) so keep on looking
2837 pos = static_cast<int>(idxNs);
2838 startLine = ns.StartLine;
2839 }
2840 }
2841
2842 return pos;
2843 }
2844
2845 void CodeCompletion::OnScope(wxCommandEvent&)
2846 {
2847 int sel = m_Scope->GetSelection();
2848 if (sel != -1 && sel < static_cast<int>(m_ScopeMarks.size()))
2849 UpdateFunctions(sel);
2850 }
2851
2852 void CodeCompletion::OnFunction(cb_unused wxCommandEvent& event)
2853 {
2854 int selSc = (m_Scope) ? m_Scope->GetSelection() : 0;
2855 if (selSc != -1 && selSc < static_cast<int>(m_ScopeMarks.size()))
2856 {
2857 int idxFn = m_ScopeMarks[selSc] + m_Function->GetSelection();
2858 if (idxFn != -1 && idxFn < static_cast<int>(m_FunctionsScope.size()))
2859 {
2860 cbEditor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
2861 if (ed)
2862 ed->GotoTokenPosition(m_FunctionsScope[idxFn].StartLine,
2863 m_FunctionsScope[idxFn].ShortName);
2864 }
2865 }
2866 }
2867
2868 /** Here is the expansion of how the two wxChoices are constructed.
2869 * for a file have such contents below
2870 * @code{.cpp}
2871 * Line 0 void g_func1(){
2872 * Line 1 }
2873 * Line 2
2874 * Line 3 void ClassA::func1(){
2875 * Line 4 }
2876 * Line 5
2877 * Line 6 void ClassA::func2(){
2878 * Line 7 }
2879 * Line 8
2880 * Line 9 void ClassB::func1(){
2881 * Line 10 }
2882 * Line 11
2883 * Line 12 void ClassB::func2(){
2884 * Line 13 }
2885 * Line 14
2886 * Line 15 namespace NamespaceA{
2887 * Line 16 void func3(){
2888 * Line 17 }
2889 * Line 18
2890 * Line 19 class ClassC {
2891 * Line 20
2892 * Line 21 void func4(){
2893 * Line 22 }
2894 * Line 23 }
2895 * Line 24 }
2896 * Line 25
2897 *
2898 * @endcode
2899 *
2900 * The two key variable will be constructed like below
2901 * @code
2902 * m_FunctionsScope is std::vector of length 9, capacity 9 =
2903 * {
2904 * {StartLine = 0, EndLine = 1, ShortName = L"g_func1", Name = L"g_func1() : void", Scope = L"<global>"},
2905 * {StartLine = 3, EndLine = 4, ShortName = L"func1", Name = L"func1() : void", Scope = L"ClassA::"},
2906 * {StartLine = 6, EndLine = 7, ShortName = L"func2", Name = L"func2() : void", Scope = L"ClassA::"},
2907 * {StartLine = 9, EndLine = 10, ShortName = L"func1", Name = L"func1() : void", Scope = L"ClassB::"},
2908 * {StartLine = 12, EndLine = 13, ShortName = L"func2", Name = L"func2() : void", Scope = L"ClassB::"},
2909 * {StartLine = 14, EndLine = 23, ShortName = L"", Name = L"", Scope = L"NamespaceA::"},
2910 * {StartLine = 16, EndLine = 17, ShortName = L"func3", Name = L"func3() : void", Scope = L"NamespaceA::"},
2911 * {StartLine = 19, EndLine = 23, ShortName = L"", Name = L"", Scope = L"NamespaceA::ClassC::"},
2912 * {StartLine = 21, EndLine = 22, ShortName = L"func4", Name = L"func4() : void", Scope = L"NamespaceA::ClassC::"}
2913 * }
2914 *
2915 * m_NameSpaces is std::vector of length 1, capacity 1 =
2916 * {{Name = L"NamespaceA::", StartLine = 14, EndLine = 23}}
2917 *
2918 * m_ScopeMarks is std::vector of length 5, capacity 8 = {0, 1, 3, 5, 7}
2919 * which is the start of Scope "<global>", Scope "ClassA::" and Scope "ClassB::",
2920 * "NamespaceA::" and "NamespaceA::ClassC::"
2921 * @endcode
2922 *
2923 * Then we have wxChoice Scopes and Functions like below
2924 * @code
2925 * <global> ClassA:: ClassB::
2926 * |- g_func1() |- func1() |- func1()
2927 * |- func2() |- func2()
2928 *
2929 * NamespaceA:: NamespaceA::ClassC::
2930 * |- func3() |- func4()
2931 * @endcode
2932 */
2933 void CodeCompletion::ParseFunctionsAndFillToolbar()
2934 {
2935 TRACE(_T("ParseFunctionsAndFillToolbar() : m_ToolbarNeedReparse=%d, m_ToolbarNeedRefresh=%d, "),
2936 m_ToolbarNeedReparse?1:0, m_ToolbarNeedRefresh?1:0);
2937 EditorManager* edMan = Manager::Get()->GetEditorManager();
2938 if (!edMan) // Closing the app?
2939 return;
2940
2941 cbEditor* ed = edMan->GetBuiltinActiveEditor();
2942 if (!ed || !ed->GetControl())
2943 {
2944 if (m_Function)
2945 m_Function->Clear();
2946 if (m_Scope)
2947 m_Scope->Clear();
2948
2949 EnableToolbarTools(false);
2950 m_LastFile.Clear();
2951 return;
2952 }
2953
2954 const wxString filename = ed->GetFilename();
2955 if (filename.IsEmpty())
2956 return;
2957
2958 bool fileParseFinished = m_NativeParser.GetParser().IsFileParsed(filename);
2959
2960 // FunctionsScopePerFile contains all the function and namespace information for
2961 // a specified file, m_AllFunctionsScopes[filename] will implicitly insert an new element in
2962 // the map if no such key(filename) is found.
2963 FunctionsScopePerFile* funcdata = &(m_AllFunctionsScopes[filename]);
2964
2965 // *** Part 1: Parse the file (if needed) ***
2966 if (m_ToolbarNeedReparse || !funcdata->parsed)
2967 {
2968 if (m_ToolbarNeedReparse)
2969 m_ToolbarNeedReparse = false;
2970
2971 funcdata->m_FunctionsScope.clear();
2972 funcdata->m_NameSpaces.clear();
2973
2974 // collect the function implementation information, just find the specified tokens in the TokenTree
2975 TokenIdxSet result;
2976 m_NativeParser.GetParser().FindTokensInFile(filename, result,
2977 tkAnyFunction | tkEnum | tkClass | tkNamespace);
2978 if (!result.empty())
2979 funcdata->parsed = true; // if the file did have some containers, flag it as parsed
2980 else
2981 fileParseFinished = false; // this indicates the batch parser does not finish parsing for the current file
2982
2983 TokenTree* tree = m_NativeParser.GetParser().GetTokenTree();
2984
2985 CC_LOCKER_TRACK_TT_MTX_LOCK(s_TokenTreeMutex)
2986
2987 for (TokenIdxSet::const_iterator it = result.begin(); it != result.end(); ++it)
2988 {
2989 const Token* token = tree->at(*it);
2990 if (token && token->m_ImplLine != 0)
2991 {
2992 FunctionScope fs;
2993 fs.StartLine = token->m_ImplLine - 1;
2994 fs.EndLine = token->m_ImplLineEnd - 1;
2995 const size_t fileIdx = tree->InsertFileOrGetIndex(filename);
2996 if (token->m_TokenKind & tkAnyFunction && fileIdx == token->m_ImplFileIdx)
2997 {
2998 fs.Scope = token->GetNamespace();
2999 if (fs.Scope.IsEmpty())
3000 fs.Scope = g_GlobalScope;
3001 wxString result_str = token->m_Name;
3002 fs.ShortName = result_str;
3003 result_str << token->GetFormattedArgs();
3004 if (!token->m_BaseType.IsEmpty())
3005 result_str << _T(" : ") << token->m_BaseType;
3006 fs.Name = result_str;
3007 funcdata->m_FunctionsScope.push_back(fs);
3008 }
3009 else if (token->m_TokenKind & (tkEnum | tkClass | tkNamespace))
3010 {
3011 fs.Scope = token->GetNamespace() + token->m_Name + _T("::");
3012 funcdata->m_FunctionsScope.push_back(fs);
3013 }
3014 }
3015 }
3016
3017 CC_LOCKER_TRACK_TT_MTX_UNLOCK(s_TokenTreeMutex)
3018
3019 FunctionsScopeVec& functionsScopes = funcdata->m_FunctionsScope;
3020 NameSpaceVec& nameSpaces = funcdata->m_NameSpaces;
3021
3022 // collect the namespace information in the current file, this is done by running a parserthread
3023 // on the editor's buffer
3024 m_NativeParser.GetParser().ParseBufferForNamespaces(ed->GetControl()->GetText(), nameSpaces);
3025 std::sort(nameSpaces.begin(), nameSpaces.end(), CodeCompletionHelper::LessNameSpace);
3026
3027 // copy the namespace information collected in ParseBufferForNamespaces() to
3028 // the functionsScopes, note that the element type FunctionScope has a constructor
3029 // FunctionScope(const NameSpace& ns), type conversion is done automatically
3030 std::copy(nameSpaces.begin(), nameSpaces.end(), back_inserter(functionsScopes));
3031 std::sort(functionsScopes.begin(), functionsScopes.end(), CodeCompletionHelper::LessFunctionScope);
3032
3033 // remove consecutive duplicates
3034 FunctionsScopeVec::const_iterator it;
3035 it = unique(functionsScopes.begin(), functionsScopes.end(), CodeCompletionHelper::EqualFunctionScope);
3036 functionsScopes.resize(it - functionsScopes.begin());
3037
3038 TRACE(F(_T("Found %lu namespace locations"), static_cast<unsigned long>(nameSpaces.size())));
3039 #if CC_CODECOMPLETION_DEBUG_OUTPUT == 1
3040 for (unsigned int i = 0; i < nameSpaces.size(); ++i)
3041 CCLogger::Get()->DebugLog(F(_T("\t%s (%d:%d)"),
3042 nameSpaces[i].Name.wx_str(), nameSpaces[i].StartLine, nameSpaces[i].EndLine));
3043 #endif
3044
3045 if (!m_ToolbarNeedRefresh)
3046 m_ToolbarNeedRefresh = true;
3047 }
3048
3049 // *** Part 2: Fill the toolbar ***
3050 m_FunctionsScope = funcdata->m_FunctionsScope;
3051 m_NameSpaces = funcdata->m_NameSpaces;
3052
3053 m_ScopeMarks.clear();
3054 unsigned int fsSize = m_FunctionsScope.size();
3055 if (!m_FunctionsScope.empty())
3056 {
3057 m_ScopeMarks.push_back(0);
3058
3059 if (m_Scope) // show scope wxChoice
3060 {
3061 wxString lastScope = m_FunctionsScope[0].Scope;
3062 for (unsigned int idx = 1; idx < fsSize; ++idx)
3063 {
3064 const wxString& currentScope = m_FunctionsScope[idx].Scope;
3065
3066 // if the scope name has changed, push a new index
3067 if (lastScope != currentScope)
3068 {
3069 m_ScopeMarks.push_back(idx);
3070 lastScope = currentScope;
3071 }
3072 }
3073 }
3074 }
3075
3076 TRACE(F(_T("Parsed %lu functionscope items"), static_cast<unsigned long>(m_FunctionsScope.size())));
3077 #if CC_CODECOMPLETION_DEBUG_OUTPUT == 1
3078 for (unsigned int i = 0; i < m_FunctionsScope.size(); ++i)
3079 CCLogger::Get()->DebugLog(F(_T("\t%s%s (%d:%d)"),
3080 m_FunctionsScope[i].Scope.wx_str(), m_FunctionsScope[i].Name.wx_str(),
3081 m_FunctionsScope[i].StartLine, m_FunctionsScope[i].EndLine));
3082 #endif
3083
3084 // Does the toolbar need a refresh?
3085 if (m_ToolbarNeedRefresh || m_LastFile != filename)
3086 {
3087 // Update the last editor and changed flag...
3088 if (m_ToolbarNeedRefresh)
3089 m_ToolbarNeedRefresh = false;
3090 if (m_LastFile != filename)
3091 {
3092 TRACE(_T("ParseFunctionsAndFillToolbar() : Update last file is %s"), filename.wx_str());
3093 m_LastFile = filename;
3094 }
3095
3096 // ...and refresh the toolbars.
3097 m_Function->Clear();
3098
3099 if (m_Scope)
3100 {
3101 m_Scope->Freeze();
3102 m_Scope->Clear();
3103
3104 // add to the choice controls
3105 for (unsigned int idxSc = 0; idxSc < m_ScopeMarks.size(); ++idxSc)
3106 {
3107 int idxFn = m_ScopeMarks[idxSc];
3108 const FunctionScope& fs = m_FunctionsScope[idxFn];
3109 m_Scope->Append(fs.Scope);
3110 }
3111
3112 m_Scope->Thaw();
3113 }
3114 else
3115 {
3116 m_Function->Freeze();
3117
3118 for (unsigned int idxFn = 0; idxFn < m_FunctionsScope.size(); ++idxFn)
3119 {
3120 const FunctionScope& fs = m_FunctionsScope[idxFn];
3121 if (fs.Name != wxEmptyString)
3122 m_Function->Append(fs.Scope + fs.Name);
3123 else if (fs.Scope.EndsWith(wxT("::")))
3124 m_Function->Append(fs.Scope.substr(0, fs.Scope.length()-2));
3125 else
3126 m_Function->Append(fs.Scope);
3127 }
3128
3129 m_Function->Thaw();
3130 }
3131 }
3132
3133 // Find the current function and update
3134 FindFunctionAndUpdate(ed->GetControl()->GetCurrentLine());
3135
3136 // Control the toolbar state, if the batch parser does not finish parsing the file, no need to update CC toolbar.
3137 EnableToolbarTools(fileParseFinished);
3138 }
3139
3140 void CodeCompletion::FindFunctionAndUpdate(int currentLine)
3141 {
3142 if (currentLine == -1)
3143 return;
3144
3145 m_CurrentLine = currentLine;
3146
3147 int selSc, selFn;
3148 FunctionPosition(selSc, selFn);
3149
3150 if (m_Scope)
3151 {
3152 if (selSc != -1 && selSc != m_Scope->GetSelection())
3153 {
3154 m_Scope->SetSelection(selSc);
3155 UpdateFunctions(selSc);
3156 }
3157 else if (selSc == -1)
3158 m_Scope->SetSelection(-1);
3159 }
3160
3161 if (selFn != -1 && selFn != m_Function->GetSelection())
3162 m_Function->SetSelection(selFn);
3163 else if (selFn == -1)
3164 {
3165 m_Function->SetSelection(-1);
3166
3167 wxChoice* choice = (m_Scope) ? m_Scope : m_Function;
3168
3169 int NsSel = NameSpacePosition();
3170 if (NsSel != -1)
3171 choice->SetStringSelection(m_NameSpaces[NsSel].Name);
3172 else if (!m_Scope)
3173 choice->SetSelection(-1);
3174 else
3175 {
3176 choice->SetStringSelection(g_GlobalScope);
3177 wxCommandEvent evt(wxEVT_COMMAND_CHOICE_SELECTED, XRCID("chcCodeCompletionScope"));
3178 wxPostEvent(this, evt);
3179 }
3180 }
3181 }
3182
3183 void CodeCompletion::UpdateFunctions(unsigned int scopeItem)
3184 {
3185 m_Function->Freeze();
3186 m_Function->Clear();
3187
3188 unsigned int idxEnd = (scopeItem + 1 < m_ScopeMarks.size()) ? m_ScopeMarks[scopeItem + 1] : m_FunctionsScope.size();
3189 for (unsigned int idxFn = m_ScopeMarks[scopeItem]; idxFn < idxEnd; ++idxFn)
3190 {
3191 const wxString &name = m_FunctionsScope[idxFn].Name;
3192 m_Function->Append(name);
3193 }
3194
3195 m_Function->Thaw();
3196 }
3197
3198 void CodeCompletion::EnableToolbarTools(bool enable)
3199 {
3200 if (m_Scope)
3201 m_Scope->Enable(enable);
3202 if (m_Function)
3203 m_Function->Enable(enable);
3204 }
3205
3206 void CodeCompletion::DoParseOpenedProjectAndActiveEditor()
3207 {
3208 // Let the app startup before parsing
3209 // This is to prevent the Splash Screen from delaying so much. By adding
3210 // the timer, the splash screen is closed and Code::Blocks doesn't take
3211 // so long in starting.
3212 m_InitDone = true;
3213
3214 // Dreaded DDE-open bug related: do not touch the following lines unless for a good reason
3215
3216 // parse any projects opened through DDE or the command-line
3217 cbProject* curProject = Manager::Get()->GetProjectManager()->GetActiveProject();
3218 if (curProject && !m_NativeParser.GetParserByProject(curProject))
3219 m_NativeParser.CreateParser(curProject);
3220
3221 // parse any files opened through DDE or the command-line
3222 EditorBase* editor = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
3223 if (editor)
3224 m_NativeParser.OnEditorActivated(editor);
3225 }
3226
3227 void CodeCompletion::UpdateEditorSyntax(cbEditor* ed)
3228 {
3229 if (!Manager::Get()->GetConfigManager(wxT("code_completion"))->ReadBool(wxT("/semantic_keywords"), false))
3230 return;
3231 if (!ed)
3232 ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
3233 if (!ed || ed->GetControl()->GetLexer() != wxSCI_LEX_CPP)
3234 return;
3235
3236 TokenIdxSet result;
3237 int flags = tkAnyContainer | tkAnyFunction;
3238 if (ed->GetFilename().EndsWith(wxT(".c")))
3239 flags |= tkVariable;
3240 m_NativeParser.GetParser().FindTokensInFile(ed->GetFilename(), result, flags);
3241 TokenTree* tree = m_NativeParser.GetParser().GetTokenTree();
3242
3243 std::set<wxString> varList;
3244 TokenIdxSet parsedTokens;
3245
3246 CC_LOCKER_TRACK_TT_MTX_LOCK(s_TokenTreeMutex)
3247 for (TokenIdxSet::const_iterator it = result.begin(); it != result.end(); ++it)
3248 {
3249 Token* token = tree->at(*it);
3250 if (!token)
3251 continue;
3252 if (token->m_TokenKind == tkVariable) // global var - only added in C
3253 {
3254 varList.insert(token->m_Name);
3255 continue;
3256 }
3257 else if (token->m_TokenKind & tkAnyFunction) // find parent class
3258 {
3259 if (token->m_ParentIndex == wxNOT_FOUND)
3260 continue;
3261 else
3262 token = tree->at(token->m_ParentIndex);
3263 }
3264 if (!token || parsedTokens.find(token->m_Index) != parsedTokens.end())
3265 continue; // no need to check the same token multiple times
3266 parsedTokens.insert(token->m_Index);
3267 for (TokenIdxSet::const_iterator chIt = token->m_Children.begin();
3268 chIt != token->m_Children.end(); ++chIt)
3269 {
3270 const Token* chToken = tree->at(*chIt);
3271 if (chToken && chToken->m_TokenKind == tkVariable)
3272 {
3273 varList.insert(chToken->m_Name);
3274 }
3275 }
3276 // inherited members
3277 if (token->m_Ancestors.empty())
3278 tree->RecalcInheritanceChain(token);
3279 for (TokenIdxSet::const_iterator ancIt = token->m_Ancestors.begin();
3280 ancIt != token->m_Ancestors.end(); ++ancIt)
3281 {
3282 const Token* ancToken = tree->at(*ancIt);
3283 if (!ancToken || parsedTokens.find(ancToken->m_Index) != parsedTokens.end())
3284 continue;
3285 for (TokenIdxSet::const_iterator chIt = ancToken->m_Children.begin();
3286 chIt != ancToken->m_Children.end(); ++chIt)
3287 {
3288 const Token* chToken = tree->at(*chIt);
3289 if ( chToken && chToken->m_TokenKind == tkVariable
3290 && chToken->m_Scope != tsPrivate) // cannot inherit these...
3291 {
3292 varList.insert(chToken->m_Name);
3293 }
3294 }
3295 }
3296 }
3297 CC_LOCKER_TRACK_TT_MTX_UNLOCK(s_TokenTreeMutex)
3298
3299 EditorColourSet* colour_set = Manager::Get()->GetEditorManager()->GetColourSet();
3300 if (!colour_set)
3301 return;
3302
3303 wxString keywords = colour_set->GetKeywords(ed->GetLanguage(), 3);
3304 for (std::set<wxString>::const_iterator keyIt = varList.begin();
3305 keyIt != varList.end(); ++keyIt)
3306 {
3307 keywords += wxT(" ") + *keyIt;
3308 }
3309 ed->GetControl()->SetKeyWords(3, keywords);
3310 ed->GetControl()->Colourise(0, -1);
3311 }
3312
3313 void CodeCompletion::OnToolbarTimer(cb_unused wxTimerEvent& event)
3314 {
3315 TRACE(_T("CodeCompletion::OnToolbarTimer(): Enter"));
3316
3317 if (!ProjectManager::IsBusy())
3318 ParseFunctionsAndFillToolbar();
3319 else
3320 {
3321 TRACE(_T("CodeCompletion::OnToolbarTimer(): Starting m_TimerToolbar."));
3322 m_TimerToolbar.Start(TOOLBAR_REFRESH_DELAY, wxTIMER_ONE_SHOT);
3323 }
3324
3325 TRACE(_T("CodeCompletion::OnToolbarTimer(): Leave"));
3326 }
3327
3328 void CodeCompletion::OnRealtimeParsingTimer(cb_unused wxTimerEvent& event)
3329 {
3330 cbEditor* editor = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
3331 if (!editor)
3332 return;
3333
3334 TRACE(_T("OnRealtimeParsingTimer"));
3335
3336 // the real time parsing timer event has arrived, but the document size has changed, in this
3337 // case, we should fire another timer event, and do the parsing job later
3338 const int curLen = editor->GetControl()->GetLength();
3339 if (curLen != m_CurrentLength)
3340 {
3341 m_CurrentLength = curLen;
3342 TRACE(_T("CodeCompletion::OnRealtimeParsingTimer: Starting m_TimerRealtimeParsing."));
3343 m_TimerRealtimeParsing.Start(REALTIME_PARSING_DELAY, wxTIMER_ONE_SHOT);
3344 return;
3345 }
3346
3347 cbProject* project = m_NativeParser.GetProjectByEditor(editor);
3348 if (project && !project->GetFileByFilename(m_LastFile, false, true))
3349 return;
3350 if (m_NativeParser.ReparseFile(project, m_LastFile))
3351 CCLogger::Get()->DebugLog(_T("Reparsing when typing for editor ") + m_LastFile);
3352 }
3353
3354 void CodeCompletion::OnProjectSavedTimer(cb_unused wxTimerEvent& event)
3355 {
3356 cbProject* project = static_cast<cbProject*>(m_TimerProjectSaved.GetClientData());
3357 m_TimerProjectSaved.SetClientData(NULL);
3358
3359 ProjectsArray* projs = Manager::Get()->GetProjectManager()->GetProjects();
3360 if (projs->Index(project) == wxNOT_FOUND)
3361 return;
3362
3363 if (IsAttached() && m_InitDone && project)
3364 {
3365 TRACE(_T("OnProjectSavedTimer"));
3366 if (project && m_NativeParser.GetParserByProject(project))
3367 {
3368 ReparsingMap::iterator it = m_ReparsingMap.find(project);
3369 if (it != m_ReparsingMap.end())
3370 m_ReparsingMap.erase(it);
3371 if (m_NativeParser.DeleteParser(project))
3372 {
3373 CCLogger::Get()->DebugLog(_T("Reparsing project."));
3374 m_NativeParser.CreateParser(project);
3375 }
3376 }
3377 }
3378 }
3379
3380 void CodeCompletion::OnReparsingTimer(cb_unused wxTimerEvent& event)
3381 {
3382 if (ProjectManager::IsBusy() || !IsAttached() || !m_InitDone)
3383 {
3384 m_ReparsingMap.clear();
3385 CCLogger::Get()->DebugLog(_T("Reparsing files failed!"));
3386 return;
3387 }
3388
3389 TRACE(_T("OnReparsingTimer"));
3390
3391 ReparsingMap::iterator it = m_ReparsingMap.begin();
3392 if (it != m_ReparsingMap.end() && m_NativeParser.Done())
3393 {
3394 cbProject* project = it->first;
3395 wxArrayString& files = it->second;
3396 if (!project)
3397 project = m_NativeParser.GetProjectByFilename(files[0]);
3398
3399 if (project && Manager::Get()->GetProjectManager()->IsProjectStillOpen(project))
3400 {
3401 wxString curFile;
3402 EditorBase* editor = Manager::Get()->GetEditorManager()->GetActiveEditor();
3403 if (editor)
3404 curFile = editor->GetFilename();
3405
3406 size_t reparseCount = 0;
3407 while (!files.IsEmpty())
3408 {
3409 if (m_NativeParser.ReparseFile(project, files.Last()))
3410 {
3411 ++reparseCount;
3412 TRACE(_T("OnReparsingTimer: Reparsing file : ") + files.Last());
3413 if (files.Last() == curFile)
3414 {
3415 m_ToolbarNeedReparse = true;
3416 TRACE(_T("CodeCompletion::OnReparsingTimer: Starting m_TimerToolbar."));
3417 m_TimerToolbar.Start(TOOLBAR_REFRESH_DELAY, wxTIMER_ONE_SHOT);
3418 }
3419 }
3420
3421 files.RemoveAt(files.GetCount() - 1);
3422 }
3423
3424 if (reparseCount)
3425 CCLogger::Get()->DebugLog(F(_T("Re-parsed %lu files."), static_cast<unsigned long>(reparseCount)));
3426 }
3427
3428 if (files.IsEmpty())
3429 m_ReparsingMap.erase(it);
3430 }
3431
3432 if (!m_ReparsingMap.empty())
3433 {
3434 TRACE(_T("CodeCompletion::OnReparsingTimer: Starting m_TimerReparsing."));
3435 m_TimerReparsing.Start(EDITOR_ACTIVATED_DELAY, wxTIMER_ONE_SHOT);
3436 }
3437 }
3438
3439 void CodeCompletion::OnEditorActivatedTimer(cb_unused wxTimerEvent& event)
3440 {
3441 // the m_LastEditor variable was updated in CodeCompletion::OnEditorActivated, after that,
3442 // the editor-activated-timer was started. So, here in the timer handler, we need to check
3443 // whether the saved editor and the current editor are the same, otherwise, no need to update
3444 // the toolbar, because there must be another editor activated before the timer hits.
3445 // Note: only the builtin editor was considered.
3446 EditorBase* editor = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
3447 if (!editor || editor != m_LastEditor)
3448 {
3449 TRACE(_T("CodeCompletion::OnEditorActivatedTimer(): Not a builtin editor."));
3450 //m_LastEditor = nullptr;
3451 EnableToolbarTools(false);
3452 return;
3453 }
3454
3455 const wxString& curFile = editor->GetFilename();
3456 // if the same file was activated, no need to update the toolbar
3457 if ( !m_LastFile.IsEmpty() && m_LastFile == curFile )
3458 {
3459 TRACE(_T("CodeCompletion::OnEditorActivatedTimer(): Same as the last activated file(%s)."), curFile.wx_str());
3460 return;
3461 }
3462
3463 TRACE(_T("CodeCompletion::OnEditorActivatedTimer(): Need to notify NativeParser and Refresh toolbar."));
3464
3465 m_NativeParser.OnEditorActivated(editor);
3466 TRACE(_T("CodeCompletion::OnEditorActivatedTimer: Starting m_TimerToolbar."));
3467 m_TimerToolbar.Start(TOOLBAR_REFRESH_DELAY, wxTIMER_ONE_SHOT);
3468 TRACE(_T("CodeCompletion::OnEditorActivatedTimer(): Current activated file is %s"), curFile.wx_str());
3469 UpdateEditorSyntax();
3470 }
3471
3472 wxBitmap CodeCompletion::GetImage(ImageId::Id id, int fontSize)
3473 {
3474 const int size = cbFindMinSize16to64(fontSize);
3475 const ImageId key(id, size);
3476 ImagesMap::const_iterator it = m_images.find(key);
3477 if (it == m_images.end())
3478 {
3479 const wxString prefix = ConfigManager::GetDataFolder()
3480 + wxString::Format(_T("/codecompletion.zip#zip:images/%dx%d/"), size,
3481 size);
3482
3483 wxString filename;
3484 switch (id)
3485 {
3486 case ImageId::HeaderFile:
3487 filename = prefix + wxT("header.png");
3488 break;
3489 case ImageId::KeywordCPP:
3490 filename = prefix + wxT("keyword_cpp.png");
3491 break;
3492 case ImageId::KeywordD:
3493 filename = prefix + wxT("keyword_d.png");
3494 break;
3495 case ImageId::Unknown:
3496 filename = prefix + wxT("unknown.png");
3497 break;
3498
3499 case ImageId::Last:
3500 default:
3501 ;
3502 }
3503
3504 if (!filename.empty())
3505 {
3506 wxBitmap bitmap = cbLoadBitmap(filename);
3507 if (!bitmap.IsOk())
3508 {
3509 const wxString msg = wxString::Format(_("Cannot load image: '%s'!"),
3510 filename.wx_str());
3511 Manager::Get()->GetLogManager()->LogError(msg);
3512 }
3513 m_images[key] = bitmap;
3514 return bitmap;
3515 }
3516 else
3517 {
3518 m_images[key] = wxNullBitmap;
3519 return wxNullBitmap;
3520 }
3521 }
3522 else
3523 return it->second;
3524 }
3525