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: 11898 $
6  * $Id: nativeparser.cpp 11898 2019-11-04 19:35:16Z fuscated $
7  * $HeadURL: svn://svn.code.sf.net/p/codeblocks/code/branches/release-20.xx/src/plugins/codecompletion/nativeparser.cpp $
8  */
9 
10 #include <sdk.h>
11 
12 #ifndef CB_PRECOMP
13     #include <cctype>
14 
15     #include <wx/dir.h>
16     #include <wx/log.h> // for wxSafeShowMessage()
17     #include <wx/regex.h>
18     #include <wx/wfstream.h>
19 
20     #include <cbauibook.h>
21     #include <cbeditor.h>
22     #include <cbexception.h>
23     #include <cbproject.h>
24     #include <compilerfactory.h>
25     #include <configmanager.h>
26     #include <editormanager.h>
27     #include <logmanager.h>
28     #include <macrosmanager.h>
29     #include <manager.h>
30     #include <pluginmanager.h>
31     #include <prep.h> // nullptr
32     #include <projectmanager.h>
33 
34     #include <tinyxml/tinyxml.h>
35 #endif
36 
37 #include <wx/tokenzr.h>
38 
39 #include <cbstyledtextctrl.h>
40 #include <compilercommandgenerator.h>
41 
42 #include "nativeparser.h"
43 #include "classbrowser.h"
44 #include "parser/parser.h"
45 #include "parser/profiletimer.h"
46 
47 #define CC_NATIVEPARSER_DEBUG_OUTPUT 0
48 
49 #if defined (CC_GLOBAL_DEBUG_OUTPUT)
50     #if CC_GLOBAL_DEBUG_OUTPUT == 1
51         #undef CC_NATIVEPARSER_DEBUG_OUTPUT
52         #define CC_NATIVEPARSER_DEBUG_OUTPUT 1
53     #elif CC_GLOBAL_DEBUG_OUTPUT == 2
54         #undef CC_NATIVEPARSER_DEBUG_OUTPUT
55         #define CC_NATIVEPARSER_DEBUG_OUTPUT 2
56     #endif
57 #endif
58 
59 #if CC_NATIVEPARSER_DEBUG_OUTPUT == 1
60     #define TRACE(format, args...) \
61         CCLogger::Get()->DebugLog(F(format, ##args))
62     #define TRACE2(format, args...)
63 #elif CC_NATIVEPARSER_DEBUG_OUTPUT == 2
64     #define TRACE(format, args...)                                              \
65         do                                                                      \
66         {                                                                       \
67             if (g_EnableDebugTrace)                                             \
68                 CCLogger::Get()->DebugLog(F(format, ##args));   \
69         }                                                                       \
70         while (false)
71     #define TRACE2(format, args...) \
72         CCLogger::Get()->DebugLog(F(format, ##args))
73 #else
74     #define TRACE(format, args...)
75     #define TRACE2(format, args...)
76 #endif
77 
78 /*
79  * (Recursive) functions that are surrounded by a critical section:
80  * GenerateResultSet() -> AddChildrenOfUnnamed
81  * GetCallTips() -> PrettyPrintToken (recursive function)
82  * FindCurrentFunctionToken() -> ParseFunctionArguments, FindAIMatches (recursive function)
83  * GenerateResultSet (recursive function):
84  *     FindAIMatches(), ResolveActualType(), ResolveExpression(),
85  *     FindCurrentFunctionToken(), ResolveOperator()
86  * FindCurrentFunctionStart() -> GetTokenFromCurrentLine
87  */
88 
89 namespace NativeParserHelper
90 {
91     class ParserDirTraverser : public wxDirTraverser
92     {
93     public:
ParserDirTraverser(const wxString & excludePath,wxArrayString & files)94         ParserDirTraverser(const wxString& excludePath, wxArrayString& files) :
95             m_ExcludeDir(excludePath),
96             m_Files(files)
97         {}
98 
OnFile(const wxString & filename)99         wxDirTraverseResult OnFile(const wxString& filename) override
100         {
101             if (ParserCommon::FileType(filename) != ParserCommon::ftOther)
102                 m_Files.Add(filename);
103             return wxDIR_CONTINUE;
104         }
105 
OnDir(const wxString & dirname)106         wxDirTraverseResult OnDir(const wxString& dirname) override
107         {
108             if (dirname == m_ExcludeDir)
109                 return wxDIR_IGNORE;
110             if (m_Files.GetCount() == 1)
111                 return wxDIR_STOP;
112             m_Files.Clear();
113             return wxDIR_CONTINUE;
114         }
115 
116     private:
117         const wxString& m_ExcludeDir;
118         wxArrayString&  m_Files;
119     };
120 }// namespace NativeParserHelper
121 
122 /** event id for the sequence project parsing timer */
123 int idTimerParsingOneByOne = wxNewId();
124 
125 /** if this option is enabled, there will be many log messages when doing semantic match */
126 bool s_DebugSmartSense = false;
127 
AddToImageList(wxImageList * list,const wxString & path)128 static void AddToImageList(wxImageList *list, const wxString &path)
129 {
130     wxBitmap bmp = cbLoadBitmap(path, wxBITMAP_TYPE_PNG);
131     if (!bmp.IsOk())
132     {
133         printf("failed to load: %s\n", path.utf8_str().data());
134     }
135     list->Add(bmp);
136 }
137 
LoadImageList(int size)138 static wxImageList* LoadImageList(int size)
139 {
140     wxImageList *list = new wxImageList(size, size);
141     wxBitmap bmp;
142     const wxString prefix = ConfigManager::GetDataFolder()
143                           + wxString::Format(_T("/codecompletion.zip#zip:images/%dx%d/"), size,
144                                              size);
145 
146     // Bitmaps must be added by order of PARSER_IMG_* consts.
147     AddToImageList(list, prefix + _T("class_folder.png")); // PARSER_IMG_CLASS_FOLDER
148     AddToImageList(list, prefix + _T("class.png")); // PARSER_IMG_CLASS
149     AddToImageList(list, prefix + _T("class_private.png")); // PARSER_IMG_CLASS_PRIVATE
150     AddToImageList(list, prefix + _T("class_protected.png")); // PARSER_IMG_CLASS_PROTECTED
151     AddToImageList(list, prefix + _T("class_public.png")); // PARSER_IMG_CLASS_PUBLIC
152     AddToImageList(list, prefix + _T("ctor_private.png")); // PARSER_IMG_CTOR_PRIVATE
153     AddToImageList(list, prefix + _T("ctor_protected.png")); // PARSER_IMG_CTOR_PROTECTED
154     AddToImageList(list, prefix + _T("ctor_public.png")); // PARSER_IMG_CTOR_PUBLIC
155     AddToImageList(list, prefix + _T("dtor_private.png")); // PARSER_IMG_DTOR_PRIVATE
156     AddToImageList(list, prefix + _T("dtor_protected.png")); // PARSER_IMG_DTOR_PROTECTED
157     AddToImageList(list, prefix + _T("dtor_public.png")); // PARSER_IMG_DTOR_PUBLIC
158     AddToImageList(list, prefix + _T("method_private.png")); // PARSER_IMG_FUNC_PRIVATE
159     AddToImageList(list, prefix + _T("method_protected.png")); // PARSER_IMG_FUNC_PRIVATE
160     AddToImageList(list, prefix + _T("method_public.png")); // PARSER_IMG_FUNC_PUBLIC
161     AddToImageList(list, prefix + _T("var_private.png")); // PARSER_IMG_VAR_PRIVATE
162     AddToImageList(list, prefix + _T("var_protected.png")); // PARSER_IMG_VAR_PROTECTED
163     AddToImageList(list, prefix + _T("var_public.png")); // PARSER_IMG_VAR_PUBLIC
164     AddToImageList(list, prefix + _T("macro_def.png")); // PARSER_IMG_MACRO_DEF
165     AddToImageList(list, prefix + _T("enum.png")); // PARSER_IMG_ENUM
166     AddToImageList(list, prefix + _T("enum_private.png")); // PARSER_IMG_ENUM_PRIVATE
167     AddToImageList(list, prefix + _T("enum_protected.png")); // PARSER_IMG_ENUM_PROTECTED
168     AddToImageList(list, prefix + _T("enum_public.png")); // PARSER_IMG_ENUM_PUBLIC
169     AddToImageList(list, prefix + _T("enumerator.png")); // PARSER_IMG_ENUMERATOR
170     AddToImageList(list, prefix + _T("namespace.png")); // PARSER_IMG_NAMESPACE
171     AddToImageList(list, prefix + _T("typedef.png")); // PARSER_IMG_TYPEDEF
172     AddToImageList(list, prefix + _T("typedef_private.png")); // PARSER_IMG_TYPEDEF_PRIVATE
173     AddToImageList(list, prefix + _T("typedef_protected.png")); // PARSER_IMG_TYPEDEF_PROTECTED
174     AddToImageList(list, prefix + _T("typedef_public.png")); // PARSER_IMG_TYPEDEF_PUBLIC
175     AddToImageList(list, prefix + _T("symbols_folder.png")); // PARSER_IMG_SYMBOLS_FOLDER
176     AddToImageList(list, prefix + _T("vars_folder.png")); // PARSER_IMG_VARS_FOLDER
177     AddToImageList(list, prefix + _T("funcs_folder.png")); // PARSER_IMG_FUNCS_FOLDER
178     AddToImageList(list, prefix + _T("enums_folder.png")); // PARSER_IMG_ENUMS_FOLDER
179     AddToImageList(list, prefix + _T("macro_def_folder.png")); // PARSER_IMG_MACRO_DEF_FOLDER
180     AddToImageList(list, prefix + _T("others_folder.png")); // PARSER_IMG_OTHERS_FOLDER
181     AddToImageList(list, prefix + _T("typedefs_folder.png")); // PARSER_IMG_TYPEDEF_FOLDER
182     AddToImageList(list, prefix + _T("macro_use.png")); // PARSER_IMG_MACRO_USE
183     AddToImageList(list, prefix + _T("macro_use_private.png")); // PARSER_IMG_MACRO_USE_PRIVATE
184     AddToImageList(list, prefix + _T("macro_use_protected.png")); // PARSER_IMG_MACRO_USE_PROTECTED
185     AddToImageList(list, prefix + _T("macro_use_public.png")); // PARSER_IMG_MACRO_USE_PUBLIC
186     AddToImageList(list, prefix + _T("macro_use_folder.png")); // PARSER_IMG_MACRO_USE_FOLDER
187 
188     return list;
189 }
190 
NativeParser()191 NativeParser::NativeParser() :
192     m_TimerParsingOneByOne(this, idTimerParsingOneByOne),
193     m_ClassBrowser(nullptr),
194     m_ClassBrowserIsFloating(false),
195     m_ParserPerWorkspace(false),
196     m_LastAISearchWasGlobal(false),
197     m_LastControl(nullptr),
198     m_LastFunctionIndex(-1),
199     m_LastFuncTokenIdx(-1),
200     m_LastLine(-1),
201     m_LastResult(-1)
202 {
203     m_TempParser = new Parser(this, nullptr);
204     m_Parser     = m_TempParser;
205 
206     ConfigManager* cfg = Manager::Get()->GetConfigManager(_T("code_completion"));
207     m_ParserPerWorkspace = cfg->ReadBool(_T("/parser_per_workspace"), false);
208 
209     Connect(ParserCommon::idParserStart, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(NativeParser::OnParserStart));
210     Connect(ParserCommon::idParserEnd,   wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(NativeParser::OnParserEnd));
211     Connect(idTimerParsingOneByOne,      wxEVT_TIMER,                 wxTimerEventHandler(NativeParser::OnParsingOneByOneTimer));
212 }
213 
~NativeParser()214 NativeParser::~NativeParser()
215 {
216     Disconnect(ParserCommon::idParserStart, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(NativeParser::OnParserStart));
217     Disconnect(ParserCommon::idParserEnd,   wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(NativeParser::OnParserEnd));
218     Disconnect(idTimerParsingOneByOne,      wxEVT_TIMER,                 wxTimerEventHandler(NativeParser::OnParsingOneByOneTimer));
219     RemoveClassBrowser();
220     ClearParsers();
221     Delete(m_TempParser);
222 }
223 
GetParserByProject(cbProject * project)224 ParserBase* NativeParser::GetParserByProject(cbProject* project)
225 {
226     if (m_ParserPerWorkspace)
227     {
228         std::set<cbProject*>::iterator it = m_ParsedProjects.find(project);
229         if (it != m_ParsedProjects.end())
230             return m_ParserList.begin()->second;
231     }
232     else
233     {
234         for (ParserList::const_iterator it = m_ParserList.begin(); it != m_ParserList.end(); ++it)
235         {
236             if (it->first == project)
237                 return it->second;
238         }
239     }
240 
241     TRACE(_T("NativeParser::GetParserByProject: Returning nullptr."));
242     return nullptr;
243 }
244 
GetParserByFilename(const wxString & filename)245 ParserBase* NativeParser::GetParserByFilename(const wxString& filename)
246 {
247     cbProject* project = GetProjectByFilename(filename);
248     return GetParserByProject(project);
249 }
250 
GetProjectByParser(ParserBase * parser)251 cbProject* NativeParser::GetProjectByParser(ParserBase* parser)
252 {
253     for (ParserList::const_iterator it = m_ParserList.begin(); it != m_ParserList.end(); ++it)
254     {
255         if (it->second == parser)
256             return it->first;
257     }
258 
259     TRACE(_T("NativeParser::GetProjectByParser: Returning NULL."));
260     return NULL;
261 }
262 
GetProjectByFilename(const wxString & filename)263 cbProject* NativeParser::GetProjectByFilename(const wxString& filename)
264 {
265     TRACE(_T("NativeParser::GetProjectByFilename: %s"), filename.wx_str());
266     cbProject* activeProject = Manager::Get()->GetProjectManager()->GetActiveProject();
267     if (activeProject)
268     {
269         ParserBase* parser = GetParserByProject(activeProject);
270         if (   (   parser
271                 && parser->IsFileParsed(filename) )
272             || activeProject->GetFileByFilename(filename, false, true) )
273         {
274             return activeProject;
275         }
276         else
277         {
278             ProjectsArray* projs = Manager::Get()->GetProjectManager()->GetProjects();
279             for (size_t i = 0; i < projs->GetCount(); ++i)
280             {
281                 cbProject* project = projs->Item(i);
282                 if (!project || project == activeProject)
283                     continue;
284 
285                 parser = GetParserByProject(project);
286                 if (   (   parser
287                         && parser->IsFileParsed(filename) )
288                     || project->GetFileByFilename(filename, false, true) )
289                 {
290                     return project;
291                 }
292             }
293         }
294     }
295 
296     return nullptr;
297 }
298 
GetProjectByEditor(cbEditor * editor)299 cbProject* NativeParser::GetProjectByEditor(cbEditor* editor)
300 {
301     if (!editor)
302         return nullptr;
303     ProjectFile* pf = editor->GetProjectFile();
304     if (pf && pf->GetParentProject())
305         return pf->GetParentProject();
306     return GetProjectByFilename(editor->GetFilename());
307 }
308 
GetCurrentProject()309 cbProject* NativeParser::GetCurrentProject()
310 {
311     cbEditor* editor = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
312     cbProject* project = GetProjectByEditor(editor);
313     if (!project)
314         project = Manager::Get()->GetProjectManager()->GetActiveProject();
315     return project;
316 }
317 
Done()318 bool NativeParser::Done()
319 {
320     bool done = true;
321     for (ParserList::const_iterator it = m_ParserList.begin(); it != m_ParserList.end(); ++it)
322     {
323         if (!it->second->Done())
324         {
325             done = false;
326             break;
327         }
328     }
329     TRACE(_T("NativeParser::Done: %s"), done ? _T("true"): _T("false"));
330     return done;
331 }
332 
GetImageList(int maxSize)333 wxImageList* NativeParser::GetImageList(int maxSize)
334 {
335     const int size = cbFindMinSize16to64(maxSize);
336 
337     SizeToImageList::iterator it = m_ImageListMap.find(size);
338     if (it == m_ImageListMap.end())
339     {
340         wxImageList *list = LoadImageList(size);
341         m_ImageListMap.insert(SizeToImageList::value_type(size, std::unique_ptr<wxImageList>(list)));
342         return list;
343     }
344     else
345         return it->second.get();
346 }
347 
GetTokenKindImage(const Token * token)348 int NativeParser::GetTokenKindImage(const Token* token)
349 {
350     if (!token)
351         return PARSER_IMG_NONE;
352 
353     switch (token->m_TokenKind)
354     {
355         case tkMacroDef:          return PARSER_IMG_MACRO_DEF;
356 
357         case tkEnum:
358             switch (token->m_Scope)
359             {
360                 case tsPublic:    return PARSER_IMG_ENUM_PUBLIC;
361                 case tsProtected: return PARSER_IMG_ENUM_PROTECTED;
362                 case tsPrivate:   return PARSER_IMG_ENUM_PRIVATE;
363                 case tsUndefined:
364                 default:          return PARSER_IMG_ENUM;
365             }
366 
367         case tkEnumerator:        return PARSER_IMG_ENUMERATOR;
368 
369         case tkClass:
370             switch (token->m_Scope)
371             {
372                 case tsPublic:    return PARSER_IMG_CLASS_PUBLIC;
373                 case tsProtected: return PARSER_IMG_CLASS_PROTECTED;
374                 case tsPrivate:   return PARSER_IMG_CLASS_PRIVATE;
375                 case tsUndefined:
376                 default:          return PARSER_IMG_CLASS;
377             }
378 
379         case tkNamespace:         return PARSER_IMG_NAMESPACE;
380 
381         case tkTypedef:
382             switch (token->m_Scope)
383             {
384                 case tsPublic:    return PARSER_IMG_TYPEDEF_PUBLIC;
385                 case tsProtected: return PARSER_IMG_TYPEDEF_PROTECTED;
386                 case tsPrivate:   return PARSER_IMG_TYPEDEF_PRIVATE;
387                 case tsUndefined:
388                 default:          return PARSER_IMG_TYPEDEF;
389             }
390 
391         case tkMacroUse:
392             switch (token->m_Scope)
393             {
394                 case tsPublic:    return PARSER_IMG_MACRO_USE_PUBLIC;
395                 case tsProtected: return PARSER_IMG_MACRO_USE_PROTECTED;
396                 case tsPrivate:   return PARSER_IMG_MACRO_USE_PRIVATE;
397                 case tsUndefined:
398                 default:          return PARSER_IMG_MACRO_USE;
399             }
400 
401         case tkConstructor:
402             switch (token->m_Scope)
403             {
404                 case tsProtected: return PARSER_IMG_CTOR_PROTECTED;
405                 case tsPrivate:   return PARSER_IMG_CTOR_PRIVATE;
406                 case tsUndefined:
407                 case tsPublic:
408                 default:          return PARSER_IMG_CTOR_PUBLIC;
409             }
410 
411         case tkDestructor:
412             switch (token->m_Scope)
413             {
414                 case tsProtected: return PARSER_IMG_DTOR_PROTECTED;
415                 case tsPrivate:   return PARSER_IMG_DTOR_PRIVATE;
416                 case tsUndefined:
417                 case tsPublic:
418                 default:          return PARSER_IMG_DTOR_PUBLIC;
419             }
420 
421         case tkFunction:
422             switch (token->m_Scope)
423             {
424                 case tsProtected: return PARSER_IMG_FUNC_PROTECTED;
425                 case tsPrivate:   return PARSER_IMG_FUNC_PRIVATE;
426                 case tsUndefined:
427                 case tsPublic:
428                 default:          return PARSER_IMG_FUNC_PUBLIC;
429             }
430 
431         case tkVariable:
432             switch (token->m_Scope)
433             {
434                 case tsProtected: return PARSER_IMG_VAR_PROTECTED;
435                 case tsPrivate:   return PARSER_IMG_VAR_PRIVATE;
436                 case tsUndefined:
437                 case tsPublic:
438                 default:          return PARSER_IMG_VAR_PUBLIC;
439             }
440 
441         case tkAnyContainer:
442         case tkAnyFunction:
443         case tkUndefined:
444         default:                  return PARSER_IMG_NONE;
445     }
446 }
447 
GetAllPathsByFilename(const wxString & filename)448 wxArrayString NativeParser::GetAllPathsByFilename(const wxString& filename)
449 {
450     TRACE(_T("NativeParser::GetAllPathsByFilename: Enter"));
451 
452     wxArrayString dirs;
453     const wxFileName fn(filename);
454 
455     wxDir dir(fn.GetPath());
456     if (!dir.IsOpened())
457         return wxArrayString();
458 
459     wxArrayString files;
460     NativeParserHelper::ParserDirTraverser traverser(wxEmptyString, files);
461     const wxString filespec = fn.HasExt() ? fn.GetName() + _T(".*") : fn.GetName();
462     CCLogger::Get()->DebugLog(_T("NativeParser::GetAllPathsByFilename: Traversing '") + fn.GetPath() + _T("' for: ") + filespec);
463 
464     // search in the same directory of the input file
465     dir.Traverse(traverser, filespec, wxDIR_FILES);
466 
467     // only find one file in the dir, which is the input file itself, try searching in other places
468     if (files.GetCount() == 1)
469     {
470         cbProject* project = IsParserPerWorkspace() ? GetCurrentProject()
471                                                     : GetProjectByParser(m_Parser);
472         // search in the project
473         if (project)
474         {
475             const wxString prjPath = project->GetCommonTopLevelPath();
476             wxString priorityPath;
477             if (fn.HasExt() && (fn.GetExt().StartsWith(_T("h")) || fn.GetExt().StartsWith(_T("c"))))
478             {
479                 wxFileName priFn(prjPath);
480                 // hard-coded candidate path, the ./sdk or ./include under the project top level folder
481                 priFn.AppendDir(fn.GetExt().StartsWith(_T("h")) ? _T("sdk") : _T("include"));
482                 if (priFn.DirExists())
483                 {
484                     priorityPath = priFn.GetFullPath();
485                     wxDir priorityDir(priorityPath);
486                     if ( priorityDir.IsOpened() )
487                     {
488                         wxArrayString priorityPathSub;
489                         NativeParserHelper::ParserDirTraverser traverser_2(wxEmptyString, priorityPathSub);
490                         CCLogger::Get()->DebugLog(_T("NativeParser::GetAllPathsByFilename: Traversing '") + priorityPath + _T("' for: ") + filespec);
491                         priorityDir.Traverse(traverser_2, filespec, wxDIR_FILES | wxDIR_DIRS);
492                         if (priorityPathSub.GetCount() == 1)
493                             AddPaths(dirs, priorityPathSub[0], fn.HasExt());
494                     }
495                 }
496             }
497 
498             if (dirs.IsEmpty())
499             {
500                 wxDir prjDir(prjPath);
501                 if (prjDir.IsOpened())
502                 {
503                     // try to search the project top level folder
504                     wxArrayString prjDirSub;
505                     NativeParserHelper::ParserDirTraverser traverser_2(priorityPath, prjDirSub);
506                     CCLogger::Get()->DebugLog(_T("NativeParser::GetAllPathsByFilename: Traversing '") + priorityPath + wxT(" - ") + prjPath + _T("' for: ") + filespec);
507                     prjDir.Traverse(traverser_2, filespec, wxDIR_FILES | wxDIR_DIRS);
508                     if (prjDirSub.GetCount() == 1)
509                         AddPaths(dirs, prjDirSub[0], fn.HasExt());
510                 }
511             }
512         }
513     }
514 
515     CCLogger::Get()->DebugLog(F(_T("NativeParser::GetAllPathsByFilename: Found %lu files:"), static_cast<unsigned long>(files.GetCount())));
516     for (size_t i=0; i<files.GetCount(); i++)
517         CCLogger::Get()->DebugLog(F(_T("- %s"), files[i].wx_str()));
518 
519     if (!files.IsEmpty())
520         AddPaths(dirs, files[0], fn.HasExt());
521 
522     TRACE(_T("NativeParser::GetAllPathsByFilename: Leave"));
523     return dirs;
524 }
525 
AddPaths(wxArrayString & dirs,const wxString & path,bool hasExt)526 void NativeParser::AddPaths(wxArrayString& dirs, const wxString& path, bool hasExt)
527 {
528     wxString s;
529     if (hasExt)
530         s = UnixFilename(path.BeforeLast(_T('.'))) + _T(".");
531     else
532         s = UnixFilename(path);
533 
534     if (dirs.Index(s, false) == wxNOT_FOUND)
535         dirs.Add(s);
536 }
537 
CreateParser(cbProject * project)538 ParserBase* NativeParser::CreateParser(cbProject* project)
539 {
540     if ( GetParserByProject(project) )
541     {
542         CCLogger::Get()->DebugLog(_T("NativeParser::CreateParser: Parser for this project already exists!"));
543         return nullptr;
544     }
545 
546     // Easy case for "one parser per workspace" that has already been created:
547     if (m_ParserPerWorkspace && !m_ParsedProjects.empty())
548         return m_ParserList.begin()->second;
549 
550     TRACE(_T("NativeParser::CreateParser: Calling DoFullParsing()"));
551 
552     ParserBase* parser = new Parser(this, project);
553     if ( !DoFullParsing(project, parser) )
554     {
555         CCLogger::Get()->DebugLog(_T("NativeParser::CreateParser: Full parsing failed!"));
556         delete parser;
557         return nullptr;
558     }
559 
560     if (m_Parser == m_TempParser)
561         SetParser(parser); // Also updates class browser
562 
563     if (m_ParserPerWorkspace)
564         m_ParsedProjects.insert(project);
565 
566     m_ParserList.push_back(std::make_pair(project, parser));
567 
568     wxString prj = (project ? project->GetTitle() : _T("*NONE*"));
569     wxString log(F(_("NativeParser::CreateParser: Finish creating a new parser for project '%s'"), prj.wx_str()));
570     CCLogger::Get()->Log(log);
571     CCLogger::Get()->DebugLog(log);
572 
573     RemoveObsoleteParsers();
574 
575     return parser;
576 }
577 
DeleteParser(cbProject * project)578 bool NativeParser::DeleteParser(cbProject* project)
579 {
580     wxString prj = (project ? project->GetTitle() : _T("*NONE*"));
581 
582     ParserList::iterator it = m_ParserList.begin();
583     if (!m_ParserPerWorkspace)
584     {
585         for (; it != m_ParserList.end(); ++it)
586         {
587             if (it->first == project)
588                 break;
589         }
590     }
591 
592     if (it == m_ParserList.end())
593     {
594         CCLogger::Get()->DebugLog(F(_T("NativeParser::DeleteParser: Parser does not exist for delete '%s'!"), prj.wx_str()));
595         return false;
596     }
597 
598     bool removeProjectFromParser = false;
599     if (m_ParserPerWorkspace)
600         removeProjectFromParser = RemoveProjectFromParser(project);
601 
602     if (m_ParsedProjects.empty()) // this indicates we are in one parser per one project mode
603     {
604         wxString log(F(_("NativeParser::DeleteParser: Deleting parser for project '%s'!"), prj.wx_str()));
605         CCLogger::Get()->Log(log);
606         CCLogger::Get()->DebugLog(log);
607 
608         // the logic here is : firstly delete the parser instance, then see whether we need an
609         // active parser switch (call SetParser())
610         delete it->second;
611 
612         // if the active parser is deleted, set the active parser to nullptr
613         if (it->second == m_Parser)
614         {
615             m_Parser = nullptr;
616             SetParser(m_TempParser); // Also updates class browser
617         }
618 
619         m_ParserList.erase(it);
620 
621         return true;
622     }
623 
624     if (removeProjectFromParser)
625         return true;
626 
627     CCLogger::Get()->DebugLog(_T("NativeParser::DeleteParser: Deleting parser failed!"));
628     return false;
629 }
630 
ReparseFile(cbProject * project,const wxString & filename)631 bool NativeParser::ReparseFile(cbProject* project, const wxString& filename)
632 {
633     if (ParserCommon::FileType(filename) == ParserCommon::ftOther)
634         return false;
635 
636     ParserBase* parser = GetParserByProject(project);
637     if (!parser)
638         return false;
639 
640     if (!parser->UpdateParsingProject(project))
641         return false;
642 
643     TRACE(_T("NativeParser::ReparseFile: Calling Parser::Reparse()"));
644 
645     return parser->Reparse(filename);
646 }
647 
AddFileToParser(cbProject * project,const wxString & filename,ParserBase * parser)648 bool NativeParser::AddFileToParser(cbProject* project, const wxString& filename, ParserBase* parser)
649 {
650     if (ParserCommon::FileType(filename) == ParserCommon::ftOther)
651         return false;
652 
653     if (!parser)
654     {
655         parser = GetParserByProject(project);
656         if (!parser)
657             return false;
658     }
659 
660     if (!parser->UpdateParsingProject(project))
661         return false;
662 
663     TRACE(_T("NativeParser::AddFileToParser: Calling Parser::AddFile()"));
664 
665     return parser->AddFile(filename, project);
666 }
667 
RemoveFileFromParser(cbProject * project,const wxString & filename)668 bool NativeParser::RemoveFileFromParser(cbProject* project, const wxString& filename)
669 {
670     ParserBase* parser = GetParserByProject(project);
671     if (!parser)
672         return false;
673 
674     TRACE(_T("NativeParser::RemoveFileFromParser: Calling Parser::RemoveFile()"));
675 
676     return parser->RemoveFile(filename);
677 }
678 
RereadParserOptions()679 void NativeParser::RereadParserOptions()
680 {
681     ConfigManager* cfg = Manager::Get()->GetConfigManager(_T("code_completion"));
682 #if wxCHECK_VERSION(3, 0, 0)
683     bool useSymbolBrowser = false;
684 #else
685     bool useSymbolBrowser = cfg->ReadBool(_T("/use_symbols_browser"), true);
686 #endif // wxCHECK_VERSION
687 
688     if (useSymbolBrowser)
689     {
690         if (!m_ClassBrowser)
691         {
692             CreateClassBrowser();
693             UpdateClassBrowser();
694         }
695         // change class-browser docking settings
696         else if (m_ClassBrowserIsFloating != cfg->ReadBool(_T("/as_floating_window"), false))
697         {
698             RemoveClassBrowser();
699             CreateClassBrowser();
700             // force re-update
701             UpdateClassBrowser();
702         }
703     }
704     else if (!useSymbolBrowser && m_ClassBrowser)
705         RemoveClassBrowser();
706 
707     const bool parserPerWorkspace = cfg->ReadBool(_T("/parser_per_workspace"), false);
708     if (m_Parser == m_TempParser)
709     {
710         m_ParserPerWorkspace = parserPerWorkspace;
711         return;
712     }
713 
714     RemoveObsoleteParsers();
715 
716     // re-parse if settings changed
717     ParserOptions opts = m_Parser->Options();
718     m_Parser->ReadOptions();
719     bool reparse = false;
720     cbProject* project = GetCurrentProject();
721     if (   opts.followLocalIncludes  != m_Parser->Options().followLocalIncludes
722         || opts.followGlobalIncludes != m_Parser->Options().followGlobalIncludes
723         || opts.wantPreprocessor     != m_Parser->Options().wantPreprocessor
724         || opts.parseComplexMacros   != m_Parser->Options().parseComplexMacros
725         || opts.platformCheck        != m_Parser->Options().platformCheck
726         || m_ParserPerWorkspace      != parserPerWorkspace )
727     {
728         // important options changed... flag for reparsing
729         if (cbMessageBox(_("You changed some class parser options. Do you want to "
730                            "reparse your projects now, using the new options?"),
731                          _("Reparse?"), wxYES_NO | wxICON_QUESTION) == wxID_YES)
732         {
733             reparse = true;
734         }
735     }
736 
737     if (reparse)
738         ClearParsers();
739 
740     m_ParserPerWorkspace = parserPerWorkspace;
741 
742     if (reparse)
743         CreateParser(project);
744 }
745 
ReparseCurrentProject()746 void NativeParser::ReparseCurrentProject()
747 {
748     cbProject* project = GetCurrentProject();
749     if (project)
750     {
751         TRACE(_T("NativeParser::ReparseCurrentProject: Calling DeleteParser() and CreateParser()"));
752         DeleteParser(project);
753         CreateParser(project);
754     }
755 }
756 
ReparseSelectedProject()757 void NativeParser::ReparseSelectedProject()
758 {
759     wxTreeCtrl* tree = Manager::Get()->GetProjectManager()->GetUI().GetTree();
760     if (!tree)
761         return;
762 
763     wxTreeItemId treeItem = Manager::Get()->GetProjectManager()->GetUI().GetTreeSelection();
764     if (!treeItem.IsOk())
765         return;
766 
767     const FileTreeData* data = static_cast<FileTreeData*>(tree->GetItemData(treeItem));
768     if (!data)
769         return;
770 
771     if (data->GetKind() == FileTreeData::ftdkProject)
772     {
773         cbProject* project = data->GetProject();
774         if (project)
775         {
776             TRACE(_T("NativeParser::ReparseSelectedProject: Calling DeleteParser() and CreateParser()"));
777             DeleteParser(project);
778             CreateParser(project);
779         }
780     }
781 }
782 
783 // Here, we collect the "using namespace XXXX" directives
784 // Also, we locate the current caret in which function, then, add the function parameters to Token trie
785 // Also, the variables in the function body( local block ) was add to the Token trie
MarkItemsByAI(ccSearchData * searchData,TokenIdxSet & result,bool reallyUseAI,bool isPrefix,bool caseSensitive,int caretPos)786 size_t NativeParser::MarkItemsByAI(ccSearchData* searchData,
787                                    TokenIdxSet&  result,
788                                    bool          reallyUseAI,
789                                    bool          isPrefix,
790                                    bool          caseSensitive,
791                                    int           caretPos)
792 {
793     result.clear();
794 
795     if (!m_Parser->Done())
796     {
797         wxString msg(_("The Parser is still parsing files."));
798         msg += m_Parser->NotDoneReason();
799         CCLogger::Get()->DebugLog(msg);
800         return 0;
801     }
802 
803     TRACE(_T("NativeParser::MarkItemsByAI_2()"));
804 
805     TokenTree* tree = m_Parser->GetTempTokenTree();
806 
807     CC_LOCKER_TRACK_TT_MTX_LOCK(s_TokenTreeMutex)
808 
809     // remove old temporaries
810     tree->Clear();
811 
812     CC_LOCKER_TRACK_TT_MTX_UNLOCK(s_TokenTreeMutex)
813 
814     RemoveLastFunctionChildren(m_Parser->GetTokenTree(), m_LastFuncTokenIdx);
815 
816     // find "using namespace" directives in the file
817     TokenIdxSet search_scope;
818     ParseUsingNamespace(searchData, search_scope, caretPos);
819 
820     // parse function's arguments
821     ParseFunctionArguments(searchData, caretPos);
822 
823     // parse current code block (from the start of function up to the cursor)
824     ParseLocalBlock(searchData, search_scope, caretPos);
825 
826     if (!reallyUseAI)
827     {
828         tree = m_Parser->GetTokenTree();
829 
830         CC_LOCKER_TRACK_TT_MTX_LOCK(s_TokenTreeMutex)
831 
832         // all tokens, no AI whatsoever
833         for (size_t i = 0; i < tree->size(); ++i)
834             result.insert(i);
835 
836         CC_LOCKER_TRACK_TT_MTX_UNLOCK(s_TokenTreeMutex)
837 
838         return result.size();
839     }
840 
841     // we have correctly collected all the tokens, so we will do the artificial intelligence search
842     return AI(result, searchData, wxEmptyString, isPrefix, caseSensitive, &search_scope, caretPos);
843 }
844 
MarkItemsByAI(TokenIdxSet & result,bool reallyUseAI,bool isPrefix,bool caseSensitive,int caretPos)845 size_t NativeParser::MarkItemsByAI(TokenIdxSet& result,
846                                    bool         reallyUseAI,
847                                    bool         isPrefix,
848                                    bool         caseSensitive,
849                                    int          caretPos)
850 {
851     if (s_DebugSmartSense)
852         CCLogger::Get()->DebugLog(F(_T("MarkItemsByAI_1()")));
853 
854     cbEditor* editor = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
855     if (!editor)
856         return 0;
857 
858     ccSearchData searchData = { editor->GetControl(), editor->GetFilename() };
859     if (!searchData.control)
860         return 0;
861 
862     TRACE(_T("NativeParser::MarkItemsByAI_1()"));
863 
864     return MarkItemsByAI(&searchData, result, reallyUseAI, isPrefix, caseSensitive, caretPos);
865 }
866 
GetCallTips(wxArrayString & items,int & typedCommas,cbEditor * ed,int pos)867 int NativeParser::GetCallTips(wxArrayString& items, int& typedCommas, cbEditor* ed, int pos)
868 {
869     items.Clear();
870     typedCommas = 0;
871     int commas = 0;
872 
873     if (!ed || !m_Parser->Done())
874     {
875         items.Add(wxT("Parsing at the moment..."));
876         return wxSCI_INVALID_POSITION;
877     }
878 
879     TRACE(_T("NativeParser::GetCallTips()"));
880 
881     ccSearchData searchData = { ed->GetControl(), ed->GetFilename() };
882     if (pos == wxNOT_FOUND)
883         pos = searchData.control->GetCurrentPos();
884     int nest = 0;
885     while (--pos > 0)
886     {
887         const int style = searchData.control->GetStyleAt(pos);
888         if (   searchData.control->IsString(style)
889             || searchData.control->IsCharacter(style)
890             || searchData.control->IsComment(style) )
891         {
892             continue;
893         }
894 
895         const wxChar ch = searchData.control->GetCharAt(pos);
896         if (ch == _T(';'))
897             return wxSCI_INVALID_POSITION;
898         else if (ch == _T(','))
899         {
900             if (nest == 0)
901                 ++commas;
902         }
903         else if (ch == _T(')'))
904             --nest;
905         else if (ch == _T('('))
906         {
907             ++nest;
908             if (nest > 0)
909                 break;
910         }
911     }// while
912 
913     // strip un-wanted
914     while (--pos > 0)
915     {
916         if (   searchData.control->GetCharAt(pos) <= _T(' ')
917             || searchData.control->IsComment(searchData.control->GetStyleAt(pos)) )
918         {
919             continue;
920         }
921         break;
922     }
923 
924     const int start = searchData.control->WordStartPosition(pos, true);
925     const int end = searchData.control->WordEndPosition(pos, true);
926     const wxString target = searchData.control->GetTextRange(start, end);
927     TRACE(_T("Sending \"%s\" for call-tip"), target.wx_str());
928     if (target.IsEmpty())
929         return wxSCI_INVALID_POSITION;
930 
931     TokenIdxSet result;
932     MarkItemsByAI(result, true, false, true, end);
933 
934     ComputeCallTip(m_Parser->GetTokenTree(), result, items);
935 
936     typedCommas = commas;
937     TRACE(_T("NativeParser::GetCallTips: typedCommas=%d"), typedCommas);
938     items.Sort();
939     return end;
940 }
941 
ParseProjectSearchDirs(const cbProject & project)942 wxArrayString NativeParser::ParseProjectSearchDirs(const cbProject &project)
943 {
944 
945     const TiXmlNode *extensionNode = project.GetExtensionsNode();
946     if (!extensionNode)
947         return wxArrayString();
948     const TiXmlElement* elem = extensionNode->ToElement();
949     if (!elem)
950         return wxArrayString();
951 
952     wxArrayString pdirs;
953     const TiXmlElement* CCConf = elem->FirstChildElement("code_completion");
954     if (CCConf)
955     {
956         const TiXmlElement* pathsElem = CCConf->FirstChildElement("search_path");
957         while (pathsElem)
958         {
959             if (pathsElem->Attribute("add"))
960             {
961                 wxString dir = cbC2U(pathsElem->Attribute("add"));
962                 if (pdirs.Index(dir) == wxNOT_FOUND)
963                     pdirs.Add(dir);
964             }
965 
966             pathsElem = pathsElem->NextSiblingElement("search_path");
967         }
968     }
969     return pdirs;
970 }
971 
SetProjectSearchDirs(cbProject & project,const wxArrayString & dirs)972 void NativeParser::SetProjectSearchDirs(cbProject &project, const wxArrayString &dirs)
973 {
974     TiXmlNode *extensionNode = project.GetExtensionsNode();
975     if (!extensionNode)
976         return;
977     TiXmlElement* elem = extensionNode->ToElement();
978     if (!elem)
979         return;
980 
981     // since rev4332, the project keeps a copy of the <Extensions> element
982     // and re-uses it when saving the project (so to avoid losing entries in it
983     // if plugins that use that element are not loaded atm).
984     // so, instead of blindly inserting the element, we must first check it's
985     // not already there (and if it is, clear its contents)
986     TiXmlElement* node = elem->FirstChildElement("code_completion");
987     if (!node)
988         node = elem->InsertEndChild(TiXmlElement("code_completion"))->ToElement();
989     if (node)
990     {
991         node->Clear();
992         for (size_t i = 0; i < dirs.GetCount(); ++i)
993         {
994             TiXmlElement* path = node->InsertEndChild(TiXmlElement("search_path"))->ToElement();
995             if (path) path->SetAttribute("add", cbU2C(dirs[i]));
996         }
997     }
998 }
999 
CreateClassBrowser()1000 void NativeParser::CreateClassBrowser()
1001 {
1002 #if wxCHECK_VERSION(3, 0, 0)
1003     return;
1004 #endif // wxCHECK_VERSION
1005 
1006     ConfigManager* cfg = Manager::Get()->GetConfigManager(_T("code_completion"));
1007     if (m_ClassBrowser || !cfg->ReadBool(_T("/use_symbols_browser"), true))
1008         return;
1009 
1010     TRACE(_T("NativeParser::CreateClassBrowser: Enter"));
1011 
1012     m_ClassBrowserIsFloating = cfg->ReadBool(_T("/as_floating_window"), false);
1013 
1014     if (m_ClassBrowserIsFloating)
1015     {
1016         m_ClassBrowser = new ClassBrowser(Manager::Get()->GetAppWindow(), this);
1017 
1018         // make this a free floating/docking window
1019         CodeBlocksDockEvent evt(cbEVT_ADD_DOCK_WINDOW);
1020 
1021         evt.name = _T("SymbolsBrowser");
1022         evt.title = _("Symbols browser");
1023         evt.pWindow = m_ClassBrowser;
1024         evt.dockSide = CodeBlocksDockEvent::dsRight;
1025         evt.desiredSize.Set(200, 250);
1026         evt.floatingSize.Set(200, 250);
1027         evt.minimumSize.Set(150, 150);
1028         evt.shown = true;
1029         evt.hideable = true;
1030         Manager::Get()->ProcessEvent(evt);
1031         m_ClassBrowser->UpdateSash();
1032     }
1033     else
1034     {
1035         // make this a tab in projectmanager notebook
1036         m_ClassBrowser = new ClassBrowser(Manager::Get()->GetProjectManager()->GetUI().GetNotebook(), this);
1037         Manager::Get()->GetProjectManager()->GetUI().GetNotebook()->AddPage(m_ClassBrowser, _("Symbols"));
1038         m_ClassBrowser->UpdateSash();
1039     }
1040 
1041     // Dreaded DDE-open bug related: do not touch unless for a good reason
1042     // TODO (Morten): ? what's bug? I test it, it's works well now.
1043     m_ClassBrowser->SetParser(m_Parser); // Also updates class browser
1044 
1045     TRACE(_T("NativeParser::CreateClassBrowser: Leave"));
1046 }
1047 
RemoveClassBrowser(cb_unused bool appShutDown)1048 void NativeParser::RemoveClassBrowser(cb_unused bool appShutDown)
1049 {
1050     if (!m_ClassBrowser)
1051         return;
1052 
1053     TRACE(_T("NativeParser::RemoveClassBrowser()"));
1054 
1055     if (m_ClassBrowserIsFloating)
1056     {
1057         CodeBlocksDockEvent evt(cbEVT_REMOVE_DOCK_WINDOW);
1058         evt.pWindow = m_ClassBrowser;
1059         Manager::Get()->ProcessEvent(evt);
1060     }
1061     else
1062     {
1063         int idx = Manager::Get()->GetProjectManager()->GetUI().GetNotebook()->GetPageIndex(m_ClassBrowser);
1064         if (idx != -1)
1065             Manager::Get()->GetProjectManager()->GetUI().GetNotebook()->RemovePage(idx);
1066     }
1067     m_ClassBrowser->Destroy();
1068     m_ClassBrowser = NULL;
1069 }
1070 
UpdateClassBrowser()1071 void NativeParser::UpdateClassBrowser()
1072 {
1073     if (!m_ClassBrowser)
1074           return;
1075 
1076     TRACE(_T("NativeParser::UpdateClassBrowser()"));
1077 
1078     if (   m_Parser != m_TempParser
1079         && m_Parser->Done()
1080         && !Manager::IsAppShuttingDown() )
1081     {
1082         m_ClassBrowser->UpdateClassBrowserView();
1083     }
1084 }
1085 
DoFullParsing(cbProject * project,ParserBase * parser)1086 bool NativeParser::DoFullParsing(cbProject* project, ParserBase* parser)
1087 {
1088     wxStopWatch timer;
1089     if (!parser)
1090         return false;
1091 
1092     TRACE(_T("NativeParser::DoFullParsing: Enter"));
1093 
1094     if (!AddCompilerDirs(project, parser))
1095         CCLogger::Get()->DebugLog(_T("NativeParser::DoFullParsing: AddCompilerDirs failed!"));
1096 
1097     if (!AddCompilerPredefinedMacros(project, parser))
1098         CCLogger::Get()->DebugLog(_T("NativeParser::DoFullParsing: AddCompilerPredefinedMacros failed!"));
1099 
1100     if (!AddProjectDefinedMacros(project, parser))
1101         CCLogger::Get()->DebugLog(_T("NativeParser::DoFullParsing: AddProjectDefinedMacros failed!"));
1102 
1103     // add per-project dirs
1104     if (project)
1105     {
1106         if (   !parser->Options().platformCheck
1107             || (parser->Options().platformCheck && project->SupportsCurrentPlatform()) )
1108         {
1109             // Note: This parses xml data to get the search directories. It might be expensive if
1110             //       the list of directories is too large.
1111             AddIncludeDirsToParser(ParseProjectSearchDirs(*project),
1112                                    project->GetBasePath(), parser);
1113         }
1114     }
1115 
1116     StringList localSources;
1117 
1118     if (project)
1119     {
1120         for (FilesList::const_iterator fl_it = project->GetFilesList().begin();
1121              fl_it != project->GetFilesList().end(); ++fl_it)
1122         {
1123             ProjectFile* pf = *fl_it;
1124             if (!pf)
1125                 continue;
1126             // check the file types in the project files
1127             ParserCommon::EFileType ft = ParserCommon::FileType(pf->relativeFilename);
1128             if (ft == ParserCommon::ftSource) // parse source files
1129             {
1130                 localSources.push_back(pf->file.GetFullPath());
1131             }
1132         }
1133     }
1134 
1135     CCLogger::Get()->DebugLog(_T("NativeParser::DoFullParsing: Adding cpp/c files to batch-parser"));
1136 
1137     // parse priority files
1138     wxString prj = (project ? project->GetTitle() : _T("*NONE*"));
1139 
1140 
1141     if (!localSources.empty())
1142     {
1143         CCLogger::Get()->DebugLog(F(_T("NativeParser::DoFullParsing: Added %lu source file(s) for project '%s' to batch-parser..."),
1144                                     static_cast<unsigned long>( localSources.size()), prj.wx_str()));
1145 
1146         // local source files added to Parser
1147         parser->AddBatchParse(localSources);
1148     }
1149 
1150     TRACE(_T("NativeParser::DoFullParsing: Leave"));
1151 
1152     long time = timer.Time();
1153     if (time >= 50)
1154         Manager::Get()->GetLogManager()->Log(F(wxT("NativeParser::DoFullParsing took: %.3f seconds."),
1155                                                time / 1000.0f));
1156     return true;
1157 }
1158 
SwitchParser(cbProject * project,ParserBase * parser)1159 bool NativeParser::SwitchParser(cbProject* project, ParserBase* parser)
1160 {
1161     if (!parser || parser == m_Parser || GetParserByProject(project) != parser)
1162     {
1163         TRACE(_T("NativeParser::SwitchParser: No need to / cannot switch."));
1164         return false;
1165     }
1166 
1167     TRACE(_T("NativeParser::SwitchParser()"));
1168 
1169     SetParser(parser); // Also updates class browser
1170 
1171     wxString prj = (project ? project->GetTitle() : _T("*NONE*"));
1172     wxString log(F(_("Switch parser to project '%s'"), prj.wx_str()));
1173     CCLogger::Get()->Log(log);
1174     CCLogger::Get()->DebugLog(log);
1175 
1176     return true;
1177 }
1178 
SetParser(ParserBase * parser)1179 void NativeParser::SetParser(ParserBase* parser)
1180 {
1181     // the active parser is the same as the old active parser, nothing need to be done
1182     if (m_Parser == parser)
1183         return;
1184 
1185     // a new parser is active, so remove the old parser's local variable tokens.
1186     // if m_Parser == nullptr, this means the active parser is already deleted.
1187     if (m_Parser)
1188         RemoveLastFunctionChildren(m_Parser->GetTokenTree(), m_LastFuncTokenIdx);
1189 
1190     // refresh code completion related variables
1191     InitCCSearchVariables();
1192 
1193     // switch the active parser
1194     m_Parser = parser;
1195 
1196     if (m_ClassBrowser)
1197         m_ClassBrowser->SetParser(parser); // Also updates class browser
1198 }
1199 
ClearParsers()1200 void NativeParser::ClearParsers()
1201 {
1202     TRACE(_T("NativeParser::ClearParsers()"));
1203 
1204     if (m_ParserPerWorkspace)
1205     {
1206         while (!m_ParsedProjects.empty() && DeleteParser(*m_ParsedProjects.begin()))
1207             ;
1208     }
1209     else
1210     {
1211         while (!m_ParserList.empty() && DeleteParser(m_ParserList.begin()->first))
1212             ;
1213     }
1214 }
1215 
RemoveObsoleteParsers()1216 void NativeParser::RemoveObsoleteParsers()
1217 {
1218     TRACE(_T("NativeParser::RemoveObsoleteParsers: Enter"));
1219 
1220     ConfigManager* cfg = Manager::Get()->GetConfigManager(_T("code_completion"));
1221     const size_t maxParsers = cfg->ReadInt(_T("/max_parsers"), 5);
1222     wxArrayString removedProjectNames;
1223     std::pair<cbProject*, ParserBase*> info = GetParserInfoByCurrentEditor();
1224 
1225     while (m_ParserList.size() > maxParsers)
1226     {
1227         bool deleted = false;
1228         for (ParserList::const_iterator it = m_ParserList.begin(); it != m_ParserList.end(); ++it)
1229         {
1230             if (it->second == info.second)
1231                 continue;
1232 
1233             wxString prj = (it->first ? it->first->GetTitle() : _T("*NONE*"));
1234             if ( DeleteParser(it->first) )
1235             {
1236                 // Please note that DeleteParser() may erase one element of the m_ParserList, so
1237                 // do NOT use the constant iterator here again, as the element pointed by it may be
1238                 // destroyed in DeleteParser().
1239                 removedProjectNames.Add(prj);
1240                 deleted = true;
1241                 break;
1242             }
1243         }
1244 
1245         if (!deleted)
1246             break;
1247     }
1248 
1249     for (size_t i = 0; i < removedProjectNames.GetCount(); ++i)
1250     {
1251         wxString log(F(_("NativeParser::RemoveObsoleteParsers:Removed obsolete parser of '%s'"), removedProjectNames[i].wx_str()));
1252         CCLogger::Get()->Log(log);
1253         CCLogger::Get()->DebugLog(log);
1254     }
1255 
1256     TRACE(_T("NativeParser::RemoveObsoleteParsers: Leave"));
1257 }
1258 
GetParserInfoByCurrentEditor()1259 std::pair<cbProject*, ParserBase*> NativeParser::GetParserInfoByCurrentEditor()
1260 {
1261     std::pair<cbProject*, ParserBase*> info(nullptr, nullptr);
1262     cbEditor* editor = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
1263 
1264     if ( editor ) //No need to check editor->GetFilename, because a built-in editor always have a filename
1265     {
1266         info.first  = GetProjectByEditor(editor);
1267         info.second = GetParserByProject(info.first);
1268     }
1269 
1270     return info;
1271 }
1272 
SetCBViewMode(const BrowserViewMode & mode)1273 void NativeParser::SetCBViewMode(const BrowserViewMode& mode)
1274 {
1275     m_Parser->ClassBrowserOptions().showInheritance = (mode == bvmInheritance) ? true : false;
1276     UpdateClassBrowser();
1277 }
1278 
1279 // helper funcs
1280 
1281 // Start an Artificial Intelligence (!) sequence to gather all the matching tokens..
1282 // The actual AI is in FindAIMatches() below...
AI(TokenIdxSet & result,ccSearchData * searchData,const wxString & lineText,bool isPrefix,bool caseSensitive,TokenIdxSet * search_scope,int caretPos)1283 size_t NativeParser::AI(TokenIdxSet&    result,
1284                         ccSearchData*   searchData,
1285                         const wxString& lineText,
1286                         bool            isPrefix,
1287                         bool            caseSensitive,
1288                         TokenIdxSet*    search_scope,
1289                         int             caretPos)
1290 {
1291     m_LastAISearchWasGlobal = false;
1292     m_LastAIGlobalSearch.Clear();
1293 
1294     int pos = caretPos == -1 ? searchData->control->GetCurrentPos() : caretPos;
1295     if (pos < 0 || pos > searchData->control->GetLength())
1296         return 0;
1297 
1298     int line = searchData->control->LineFromPosition(pos);
1299 
1300     // Get the actual search text, such as "objA.m_aaa.m_bbb"
1301     wxString actual_search(lineText);
1302     if (actual_search.IsEmpty())
1303     {
1304         // Get the position at the start of current line
1305         const int startPos = searchData->control->PositionFromLine(line);
1306         actual_search = searchData->control->GetTextRange(startPos, pos).Trim();
1307     }
1308 
1309     // Do the whole job here
1310     if (s_DebugSmartSense)
1311     {
1312         CCLogger::Get()->DebugLog(_T("AI() ========================================================="));
1313         CCLogger::Get()->DebugLog(F(_T("AI() Doing AI for '%s':"), actual_search.wx_str()));
1314     }
1315     TRACE(_T("NativeParser::AI()"));
1316 
1317     TokenTree* tree = m_Parser->GetTokenTree();
1318 
1319     // find current function's namespace so we can include local scope's tokens
1320     // we ' ll get the function's token (all matches) and add its parent namespace
1321     TokenIdxSet proc_result;
1322     size_t found_at = FindCurrentFunctionToken(searchData, proc_result, pos);
1323 
1324     TokenIdxSet scope_result;
1325     if (found_at)
1326         FindCurrentFunctionScope(tree, proc_result, scope_result);
1327 
1328     // add additional search scopes???
1329     // for example, we are here:
1330     /*  void ClassA::FunctionB(int paraC){
1331             m_aaa
1332     */
1333     // then, ClassA should be added as a search_scope, the global scope should be added too.
1334 
1335     // if search_scope is already defined, then, add scope_result to search_scope
1336     // otherwise we just set search_scope as scope_result
1337     if (!search_scope)
1338         search_scope = &scope_result;
1339     else
1340     {
1341         // add scopes, "tis" refer to "token index set"
1342         for (TokenIdxSet::const_iterator tis_it = scope_result.begin(); tis_it != scope_result.end(); ++tis_it)
1343             search_scope->insert(*tis_it);
1344     }
1345 
1346     // remove non-namespace/class tokens
1347     CleanupSearchScope(tree, search_scope);
1348 
1349     // find all other matches
1350     std::queue<ParserComponent> components;
1351     BreakUpComponents(actual_search, components);
1352 
1353     m_LastAISearchWasGlobal = components.size() <= 1;
1354     if (!components.empty())
1355         m_LastAIGlobalSearch = components.front().component;
1356 
1357     ResolveExpression(tree, components, *search_scope, result, caseSensitive, isPrefix);
1358 
1359     if (s_DebugSmartSense)
1360         CCLogger::Get()->DebugLog(F(_T("AI() AI leave, returned %lu results"),static_cast<unsigned long>(result.size())));
1361 
1362     return result.size();
1363 }
1364 
1365 // find a function where current caret located.
1366 // We need to find extra class scope, otherwise, we will failed do the cc in a class declaration
FindCurrentFunctionToken(ccSearchData * searchData,TokenIdxSet & result,int caretPos)1367 size_t NativeParser::FindCurrentFunctionToken(ccSearchData* searchData, TokenIdxSet& result, int caretPos)
1368 {
1369     TokenIdxSet scope_result;
1370     wxString procName;
1371     wxString scopeName;
1372     FindCurrentFunctionStart(searchData, &scopeName, &procName, nullptr, caretPos);
1373 
1374     if (procName.IsEmpty())
1375         return 0;
1376 
1377     // add current scope
1378     if (!scopeName.IsEmpty())
1379     {
1380         // _namespace ends with double-colon (::). remove it
1381         scopeName.RemoveLast();
1382         scopeName.RemoveLast();
1383 
1384         // search for namespace
1385         std::queue<ParserComponent> ns;
1386         BreakUpComponents(scopeName, ns);
1387 
1388         CC_LOCKER_TRACK_TT_MTX_LOCK(s_TokenTreeMutex)
1389 
1390         // No critical section needed in this recursive function!
1391         // All functions that call this recursive FindAIMatches function, should already entered a critical section.
1392         FindAIMatches(m_Parser->GetTokenTree(), ns, scope_result, -1,
1393                       true, true, false, tkNamespace | tkClass | tkTypedef);
1394 
1395         CC_LOCKER_TRACK_TT_MTX_UNLOCK(s_TokenTreeMutex)
1396     }
1397 
1398     // if no scope, use global scope
1399     if (scope_result.empty())
1400         scope_result.insert(-1);
1401 
1402     CC_LOCKER_TRACK_TT_MTX_LOCK(s_TokenTreeMutex)
1403 
1404     for (TokenIdxSet::const_iterator tis_it = scope_result.begin(); tis_it != scope_result.end(); ++tis_it)
1405     {
1406         GenerateResultSet(m_Parser->GetTokenTree(), procName, *tis_it, result,
1407                           true, false, tkAnyFunction | tkClass);
1408     }
1409 
1410     CC_LOCKER_TRACK_TT_MTX_UNLOCK(s_TokenTreeMutex)
1411 
1412     return result.size();
1413 }
1414 
1415 // returns current function's position (not line) in the editor
FindCurrentFunctionStart(ccSearchData * searchData,wxString * nameSpace,wxString * procName,int * functionIndex,int caretPos)1416 int NativeParser::FindCurrentFunctionStart(ccSearchData* searchData,
1417                                            wxString*     nameSpace,
1418                                            wxString*     procName,
1419                                            int*          functionIndex,
1420                                            int           caretPos)
1421 {
1422     // cache last result for optimization
1423     int pos = caretPos == -1 ? searchData->control->GetCurrentPos() : caretPos;
1424     if ((pos < 0) || (pos > searchData->control->GetLength()))
1425     {
1426         if (s_DebugSmartSense)
1427             CCLogger::Get()->DebugLog(F(_T("FindCurrentFunctionStart() Cannot determine position. caretPos=%d, control=%d"),
1428                                         caretPos, searchData->control->GetCurrentPos()));
1429         return -1;
1430     }
1431 
1432     TRACE(_T("NativeParser::FindCurrentFunctionStart()"));
1433 
1434     const int curLine = searchData->control->LineFromPosition(pos) + 1;
1435     if (   (curLine == m_LastLine)
1436         && ( (searchData->control == m_LastControl) && (!searchData->control->GetModify()) )
1437         && (searchData->file == m_LastFile) )
1438     {
1439         if (nameSpace)     *nameSpace     = m_LastNamespace;
1440         if (procName)      *procName      = m_LastPROC;
1441         if (functionIndex) *functionIndex = m_LastFunctionIndex;
1442 
1443         if (s_DebugSmartSense)
1444             CCLogger::Get()->DebugLog(F(_T("FindCurrentFunctionStart() Cached namespace='%s', cached proc='%s' (returning %d)"),
1445                                         m_LastNamespace.wx_str(), m_LastPROC.wx_str(), m_LastResult));
1446 
1447         return m_LastResult;
1448     }
1449 
1450     if (s_DebugSmartSense)
1451         CCLogger::Get()->DebugLog(F(_T("FindCurrentFunctionStart() Looking for tokens in '%s'"),
1452                                     searchData->file.wx_str()));
1453     m_LastFile    = searchData->file;
1454     m_LastControl = searchData->control;
1455     m_LastLine    = curLine;
1456 
1457     // we have all the tokens in the current file, then just do a loop on all
1458     // the tokens, see if the line is in the token's imp.
1459     TokenIdxSet result;
1460     size_t num_results = m_Parser->FindTokensInFile(searchData->file, result, tkAnyFunction | tkClass);
1461     if (s_DebugSmartSense)
1462         CCLogger::Get()->DebugLog(F(_T("FindCurrentFunctionStart() Found %lu results"), static_cast<unsigned long>(num_results)));
1463 
1464     TokenTree* tree = m_Parser->GetTokenTree();
1465 
1466     CC_LOCKER_TRACK_TT_MTX_LOCK(s_TokenTreeMutex)
1467 
1468     const int idx = GetTokenFromCurrentLine(tree, result, curLine, searchData->file);
1469     const Token* token = tree->at(idx);
1470     if (token)
1471     {
1472         // got it :)
1473         if (s_DebugSmartSense)
1474             CCLogger::Get()->DebugLog(F(_T("FindCurrentFunctionStart() Current function: '%s' (at line %u)"),
1475                                         token->DisplayName().wx_str(),
1476                                         token->m_ImplLine));
1477 
1478         m_LastNamespace      = token->GetNamespace();
1479         m_LastPROC           = token->m_Name;
1480         m_LastFunctionIndex  = token->m_Index;
1481         m_LastResult         = searchData->control->PositionFromLine(token->m_ImplLine - 1);
1482 
1483         // locate function's opening brace
1484         if (token->m_TokenKind & tkAnyFunction)
1485         {
1486             while (m_LastResult < searchData->control->GetTextLength())
1487             {
1488                 wxChar ch = searchData->control->GetCharAt(m_LastResult);
1489                 if (ch == _T('{'))
1490                     break;
1491                 else if (ch == 0)
1492                 {
1493                     if (s_DebugSmartSense)
1494                         CCLogger::Get()->DebugLog(_T("FindCurrentFunctionStart() Can't determine functions opening brace..."));
1495 
1496                     CC_LOCKER_TRACK_TT_MTX_UNLOCK(s_TokenTreeMutex)
1497                     return -1;
1498                 }
1499 
1500                 ++m_LastResult;
1501             }
1502         }
1503 
1504         if (nameSpace)     *nameSpace     = m_LastNamespace;
1505         if (procName)      *procName      = m_LastPROC;
1506         if (functionIndex) *functionIndex = token->m_Index;
1507 
1508         if (s_DebugSmartSense)
1509             CCLogger::Get()->DebugLog(F(_T("FindCurrentFunctionStart() Namespace='%s', proc='%s' (returning %d)"),
1510                                         m_LastNamespace.wx_str(), m_LastPROC.wx_str(), m_LastResult));
1511 
1512         CC_LOCKER_TRACK_TT_MTX_UNLOCK(s_TokenTreeMutex)
1513         return m_LastResult;
1514     }
1515 
1516     CC_LOCKER_TRACK_TT_MTX_UNLOCK(s_TokenTreeMutex)
1517 
1518     if (s_DebugSmartSense)
1519         CCLogger::Get()->DebugLog(_T("FindCurrentFunctionStart() Can't determine current function..."));
1520 
1521     m_LastResult = -1;
1522     return -1;
1523 }
1524 
ParseUsingNamespace(ccSearchData * searchData,TokenIdxSet & search_scope,int caretPos)1525 bool NativeParser::ParseUsingNamespace(ccSearchData* searchData, TokenIdxSet& search_scope, int caretPos)
1526 {
1527     if (s_DebugSmartSense)
1528         CCLogger::Get()->DebugLog(_T("ParseUsingNamespace() Parse file scope for \"using namespace\""));
1529     TRACE(_T("NativeParser::ParseUsingNamespace()"));
1530 
1531     int pos = caretPos == -1 ? searchData->control->GetCurrentPos() : caretPos;
1532     if (pos < 0 || pos > searchData->control->GetLength())
1533         return false;
1534 
1535     // Get the buffer from begin of the editor to the current caret position
1536     wxString buffer = searchData->control->GetTextRange(0, pos);
1537 
1538     return ParseBufferForUsingNamespace(buffer, search_scope);
1539 }
1540 
ParseBufferForUsingNamespace(const wxString & buffer,TokenIdxSet & search_scope,bool bufferSkipBlocks)1541 bool NativeParser::ParseBufferForUsingNamespace(const wxString& buffer, TokenIdxSet& search_scope, bool bufferSkipBlocks)
1542 {
1543     wxArrayString ns;
1544     m_Parser->ParseBufferForUsingNamespace(buffer, ns, bufferSkipBlocks);
1545 
1546     TokenTree* tree = m_Parser->GetTokenTree();
1547 
1548     CC_LOCKER_TRACK_TT_MTX_LOCK(s_TokenTreeMutex)
1549 
1550     for (size_t i = 0; i < ns.GetCount(); ++i)
1551     {
1552         std::queue<ParserComponent> components;
1553         BreakUpComponents(ns[i], components);
1554 
1555         int parentIdx = -1;
1556         while (!components.empty())
1557         {
1558             ParserComponent pc = components.front();
1559             components.pop();
1560 
1561             int id = tree->TokenExists(pc.component, parentIdx, tkNamespace);
1562             if (id == -1)
1563             {
1564                 parentIdx = -1;
1565                 break;
1566             }
1567             parentIdx = id;
1568         }
1569 
1570         if (s_DebugSmartSense && parentIdx != -1)
1571         {
1572             const Token* token = tree->at(parentIdx);
1573             if (token)
1574                 CCLogger::Get()->DebugLog(F(_T("ParseUsingNamespace() Found %s%s"),
1575                                             token->GetNamespace().wx_str(), token->m_Name.wx_str()));
1576         }
1577         search_scope.insert(parentIdx);
1578     }
1579 
1580     CC_LOCKER_TRACK_TT_MTX_UNLOCK(s_TokenTreeMutex)
1581 
1582     return true;
1583 }
1584 
ParseFunctionArguments(ccSearchData * searchData,int caretPos)1585 bool NativeParser::ParseFunctionArguments(ccSearchData* searchData, int caretPos)
1586 {
1587     if (s_DebugSmartSense)
1588         CCLogger::Get()->DebugLog(_T("ParseFunctionArguments() Parse function arguments"));
1589     TRACE(_T("NativeParser::ParseFunctionArguments()"));
1590 
1591     TokenIdxSet proc_result;
1592 
1593     TokenTree* tree = m_Parser->GetTokenTree(); // the one used inside FindCurrentFunctionToken, FindAIMatches and GenerateResultSet
1594 
1595     size_t found_at = FindCurrentFunctionToken(searchData, proc_result, caretPos);
1596     if (!found_at)
1597     {
1598         if (s_DebugSmartSense)
1599             CCLogger::Get()->DebugLog(_T("ParseFunctionArguments() Could not determine current function's namespace..."));
1600         TRACE(_T("ParseFunctionArguments() Could not determine current function's namespace..."));
1601         return false;
1602     }
1603 
1604     const int pos = caretPos == -1 ? searchData->control->GetCurrentPos() : caretPos;
1605     const unsigned int curLine = searchData->control->LineFromPosition(pos) + 1;
1606 
1607     bool locked = false;
1608     for (TokenIdxSet::const_iterator tis_it = proc_result.begin(); tis_it != proc_result.end(); ++tis_it)
1609     {
1610         wxString buffer;
1611         int initLine = -1;
1612         int tokenIdx = -1;
1613 
1614         if (locked)
1615             CC_LOCKER_TRACK_TT_MTX_UNLOCK(s_TokenTreeMutex)
1616 
1617         CC_LOCKER_TRACK_TT_MTX_LOCK(s_TokenTreeMutex)
1618         locked = true;
1619 
1620         const Token* token = tree->at(*tis_it);
1621 
1622         if (!token)
1623             continue;
1624         if (curLine < token->m_ImplLineStart || curLine > token->m_ImplLineEnd)
1625             continue;
1626 
1627         if (s_DebugSmartSense)
1628             CCLogger::Get()->DebugLog(_T("ParseFunctionArguments() + Function match: ") + token->m_Name);
1629         TRACE(_T("ParseFunctionArguments() + Function match: ") + token->m_Name);
1630 
1631         if (!token->m_Args.IsEmpty() && !token->m_Args.Matches(_T("()")))
1632         {
1633             buffer = token->m_Args;
1634             // Now we have something like "(int my_int, const TheClass* my_class, float f)"
1635             buffer.Remove(0, 1);              // remove (
1636             buffer.RemoveLast();              // remove )
1637             // Now we have                "int my_int, const TheClass* my_class, float f"
1638             buffer.Replace(_T(","), _T(";")); // replace commas with semi-colons
1639             // Now we have                "int my_int; const TheClass* my_class; float f"
1640             buffer << _T(';');                // aid parser ;)
1641             // Finally we have            "int my_int; const TheClass* my_class; float f;"
1642             buffer.Trim();
1643 
1644             if (s_DebugSmartSense)
1645                 CCLogger::Get()->DebugLog(F(_T("ParseFunctionArguments() Parsing arguments: \"%s\""), buffer.wx_str()));
1646 
1647             if (!buffer.IsEmpty())
1648             {
1649                 const int textLength= searchData->control->GetLength();
1650                 if (textLength == -1)
1651                     continue;
1652                 int paraPos = searchData->control->PositionFromLine(token->m_ImplLine - 1);
1653                 if (paraPos == -1)
1654                     continue;
1655                 while (paraPos < textLength && searchData->control->GetCharAt(paraPos++) != _T('('))
1656                     ;
1657                 while (paraPos < textLength && searchData->control->GetCharAt(paraPos++) < _T(' '))
1658                     ;
1659                 initLine = searchData->control->LineFromPosition(paraPos) + 1;
1660                 if (initLine == -1)
1661                     continue;
1662                 tokenIdx = token->m_Index;
1663             }
1664         }
1665 
1666         CC_LOCKER_TRACK_TT_MTX_UNLOCK(s_TokenTreeMutex)
1667         locked = false;
1668 
1669         if (   !buffer.IsEmpty()
1670             && !m_Parser->ParseBuffer(buffer, false, false, true, searchData->file, tokenIdx, initLine)
1671             && s_DebugSmartSense)
1672         {
1673             CCLogger::Get()->DebugLog(_T("ParseFunctionArguments() Error parsing arguments."));
1674         }
1675     }
1676 
1677     if (locked)
1678         CC_LOCKER_TRACK_TT_MTX_UNLOCK(s_TokenTreeMutex)
1679 
1680     return true;
1681 }
1682 
ParseLocalBlock(ccSearchData * searchData,TokenIdxSet & search_scope,int caretPos)1683 bool NativeParser::ParseLocalBlock(ccSearchData* searchData, TokenIdxSet& search_scope, int caretPos)
1684 {
1685     if (s_DebugSmartSense)
1686         CCLogger::Get()->DebugLog(_T("ParseLocalBlock() Parse local block"));
1687     TRACE(_T("NativeParser::ParseLocalBlock()"));
1688 
1689     int parentIdx = -1;
1690     int blockStart = FindCurrentFunctionStart(searchData, nullptr, nullptr, &parentIdx, caretPos);
1691     int initLine = 0;
1692     if (parentIdx != -1)
1693     {
1694         TokenTree* tree = m_Parser->GetTokenTree();
1695 
1696         CC_LOCKER_TRACK_TT_MTX_LOCK(s_TokenTreeMutex)
1697 
1698         const Token* parent = tree->at(parentIdx);
1699         if (parent && (parent->m_TokenKind & tkAnyFunction))
1700         {
1701             m_LastFuncTokenIdx = parent->m_Index;
1702             initLine = parent->m_ImplLineStart;
1703         }
1704 
1705         CC_LOCKER_TRACK_TT_MTX_UNLOCK(s_TokenTreeMutex)
1706 
1707         // only need to parse the function body, other type of Tokens' body such as class declaration
1708         // should not be parsed.
1709         if (!parent || !(parent->m_TokenKind & tkAnyFunction))
1710             return false;
1711     }
1712 
1713     if (blockStart != -1)
1714     {
1715         cbStyledTextCtrl* stc = searchData->control;
1716         // if we are in a function body, then blockStart points to the '{', so we just skip the '{'.
1717         if (stc->GetCharAt(blockStart) == wxT('{'))
1718             ++blockStart;
1719         const int pos         = (caretPos == -1 ? stc->GetCurrentPos() : caretPos);
1720         const int line        = stc->LineFromPosition(pos);
1721         const int blockEnd    = stc->GetLineEndPosition(line);
1722         if (blockEnd < 0 || blockEnd > stc->GetLength())
1723         {
1724             if (s_DebugSmartSense)
1725             {
1726                 CCLogger::Get()->DebugLog(F(_T("ParseLocalBlock() ERROR blockEnd=%d and edLength=%d?!"),
1727                                             blockEnd, stc->GetLength()));
1728             }
1729             return false;
1730         }
1731 
1732         if (blockStart >= blockEnd)
1733             blockStart = blockEnd;
1734 
1735 //        wxString buffer = searchData->control->GetTextRange(blockStart, blockEnd);
1736         wxString buffer;
1737         // condense out-of-scope braces {...}
1738         int scanPos = blockEnd;
1739         for (int curPos = pos; curPos > blockStart; --curPos)
1740         {
1741             if (stc->GetCharAt(curPos) != wxT('}'))
1742                 continue;
1743             const int style = stc->GetStyleAt(curPos);
1744             if (   stc->IsString(style)
1745                 || stc->IsCharacter(style)
1746                 || stc->IsComment(style))
1747             {
1748                 continue;
1749             }
1750             const int scopeStart = stc->BraceMatch(curPos);
1751             if (scopeStart < blockStart)
1752                 break;
1753             buffer.Prepend(stc->GetTextRange(curPos, scanPos));
1754             int startLn = stc->LineFromPosition(scopeStart);
1755             int endLn   = stc->LineFromPosition(curPos);
1756             if (startLn < endLn) // maintain correct line numbers for parsed tokens
1757                 buffer.Prepend( wxString(wxT('\n'), endLn - startLn) );
1758             scanPos = scopeStart + 1;
1759             curPos  = scopeStart;
1760 
1761             // condense out-of-scope for/if/while declarations
1762             int prevCharIdx = scopeStart - 1;
1763             for (; prevCharIdx > blockStart; --prevCharIdx)
1764             {
1765                 if (stc->IsComment(stc->GetStyleAt(prevCharIdx)))
1766                     continue;
1767                 if (!wxIsspace(stc->GetCharAt(prevCharIdx)))
1768                     break;
1769             }
1770             if (stc->GetCharAt(prevCharIdx) != wxT(')'))
1771                 continue;
1772             const int paramStart = stc->BraceMatch(prevCharIdx);
1773             if (paramStart < blockStart)
1774                 continue;
1775             for (prevCharIdx = paramStart - 1; prevCharIdx > blockStart; --prevCharIdx)
1776             {
1777                 if (stc->IsComment(stc->GetStyleAt(prevCharIdx)))
1778                     continue;
1779                 if (!wxIsspace(stc->GetCharAt(prevCharIdx)))
1780                     break;
1781             }
1782             const wxString text = stc->GetTextRange(stc->WordStartPosition(prevCharIdx, true),
1783                                                     stc->WordEndPosition(  prevCharIdx, true));
1784             if (text == wxT("for"))
1785                 buffer.Prepend(wxT("(;;){"));
1786             else if (text == wxT("if") || text == wxT("while") || text == wxT("catch"))
1787                 buffer.Prepend(wxT("(0){"));
1788             else
1789                 continue;
1790             startLn = stc->LineFromPosition(prevCharIdx);
1791             endLn   = stc->LineFromPosition(scopeStart);
1792             if (startLn < endLn)
1793                 buffer.Prepend( wxString(wxT('\n'), endLn - startLn) );
1794             curPos  = stc->WordStartPosition(prevCharIdx, true);
1795             scanPos = stc->WordEndPosition(  prevCharIdx, true);
1796         }
1797         buffer.Prepend(stc->GetTextRange(blockStart, scanPos));
1798 
1799         buffer.Trim();
1800 
1801         ParseBufferForUsingNamespace(buffer, search_scope, false);
1802 
1803         if (   !buffer.IsEmpty()
1804             && !m_Parser->ParseBuffer(buffer, false, false, true, searchData->file, m_LastFuncTokenIdx, initLine) )
1805         {
1806             if (s_DebugSmartSense)
1807                 CCLogger::Get()->DebugLog(_T("ParseLocalBlock() ERROR parsing block:\n") + buffer);
1808         }
1809         else
1810         {
1811             if (s_DebugSmartSense)
1812             {
1813                 CCLogger::Get()->DebugLog(F(_T("ParseLocalBlock() Block:\n%s"), buffer.wx_str()));
1814                 CCLogger::Get()->DebugLog(_T("ParseLocalBlock() Local tokens:"));
1815 
1816                 TokenTree* tree = m_Parser->GetTokenTree();
1817 
1818                 CC_LOCKER_TRACK_TT_MTX_LOCK(s_TokenTreeMutex)
1819 
1820                 for (size_t i = 0; i < tree->size(); ++i)
1821                 {
1822                     const Token* token = tree->at(i);
1823                     if (token && token->m_IsTemp)
1824                     {
1825                         wxString log(wxString::Format(_T(" + %s (%d)"), token->DisplayName().wx_str(), token->m_Index));
1826                         const Token* parent = tree->at(token->m_ParentIndex);
1827                         if (parent)
1828                             log += wxString::Format(_T("; Parent = %s (%d)"), parent->m_Name.wx_str(), token->m_ParentIndex);
1829                         CCLogger::Get()->DebugLog(log);
1830                     }
1831                 }
1832 
1833                 CC_LOCKER_TRACK_TT_MTX_UNLOCK(s_TokenTreeMutex)
1834             }
1835             return true;
1836         }
1837     }
1838     else
1839     {
1840         if (s_DebugSmartSense)
1841             CCLogger::Get()->DebugLog(_T("ParseLocalBlock() Could not determine current block start..."));
1842     }
1843     return false;
1844 }
1845 
AddCompilerDirs(cbProject * project,ParserBase * parser)1846 bool NativeParser::AddCompilerDirs(cbProject* project, ParserBase* parser)
1847 {
1848     if (!parser)
1849         return false;
1850 
1851     TRACE(_T("NativeParser::AddCompilerDirs: Enter"));
1852 
1853     // If there is no project, work on default compiler
1854     if (!project)
1855     {
1856         AddCompilerIncludeDirsToParser(CompilerFactory::GetDefaultCompiler(), parser);
1857         TRACE(_T("NativeParser::AddCompilerDirs: Leave"));
1858         return true;
1859     }
1860 
1861     // Otherwise (if there is a project), work on the project's compiler...
1862     wxString base = project->GetBasePath();
1863     parser->AddIncludeDir(base); // add project's base path
1864     TRACE(_T("NativeParser::AddCompilerDirs: Adding project base dir to parser: ") + base);
1865 
1866     // ...so we can access post-processed project's search dirs
1867     Compiler* compiler = CompilerFactory::GetCompiler(project->GetCompilerID());
1868     cb::shared_ptr<CompilerCommandGenerator> generator(compiler ? compiler->GetCommandGenerator(project) : nullptr);
1869 
1870     // get project include dirs
1871     if (   !parser->Options().platformCheck
1872         || (parser->Options().platformCheck && project->SupportsCurrentPlatform()) )
1873     {
1874         AddIncludeDirsToParser(project->GetIncludeDirs(), base, parser);
1875     }
1876 
1877     // alloc array for project compiler AND "no. of targets" times target compilers
1878     int nCompilers = 1 + project->GetBuildTargetsCount();
1879     Compiler** Compilers = new Compiler* [nCompilers];
1880     memset(Compilers, 0, sizeof(Compiler*) * nCompilers);
1881     nCompilers = 0; // reset , use as insert index in the next for loop
1882 
1883     // get targets include dirs
1884     for (int i = 0; i < project->GetBuildTargetsCount(); ++i)
1885     {
1886         ProjectBuildTarget* target = project->GetBuildTarget(i);
1887         if (!target) continue;
1888 
1889         if (   !parser->Options().platformCheck
1890             || (parser->Options().platformCheck && target->SupportsCurrentPlatform()) )
1891         {
1892             // post-processed search dirs (from build scripts)
1893             if (compiler && generator)
1894                 AddIncludeDirsToParser(generator->GetCompilerSearchDirs(target), base, parser);
1895 
1896             // apply target vars
1897 //            target->GetCustomVars().ApplyVarsToEnvironment();
1898             AddIncludeDirsToParser(target->GetIncludeDirs(), base, parser);
1899 
1900             // get the compiler
1901             wxString CompilerIndex = target->GetCompilerID();
1902             Compiler* tgtCompiler = CompilerFactory::GetCompiler(CompilerIndex);
1903             if (tgtCompiler)
1904             {
1905                 Compilers[nCompilers] = tgtCompiler;
1906                 ++nCompilers;
1907             }
1908         } // if (target)
1909     } // end loop over the targets
1910 
1911     // add the project compiler to the array of compilers
1912     if (compiler)
1913     {   // note it might be possible that this compiler is already in the list
1914         // no need to worry since the compiler list of the parser will filter out duplicate
1915         // entries in the include dir list
1916         Compilers[nCompilers++] = compiler;
1917     }
1918 
1919     // add compiler include dirs
1920     for (int idxCompiler = 0; idxCompiler < nCompilers; ++idxCompiler)
1921         AddCompilerIncludeDirsToParser(Compilers[idxCompiler], parser);
1922 
1923     if (!nCompilers)
1924         CCLogger::Get()->DebugLog(_T("NativeParser::AddCompilerDirs: No compilers found!"));
1925 
1926     delete [] Compilers;
1927     TRACE(_T("NativeParser::AddCompilerDirs: Leave"));
1928     return true;
1929 }
1930 
AddCompilerPredefinedMacros(cbProject * project,ParserBase * parser)1931 bool NativeParser::AddCompilerPredefinedMacros(cbProject* project, ParserBase* parser)
1932 {
1933     if (!parser)
1934         return false;
1935 
1936     if (!parser->Options().wantPreprocessor)
1937         return false;
1938 
1939     TRACE(_T("NativeParser::AddCompilerPredefinedMacros: Enter"));
1940 
1941     // Default compiler is used for for single file parser (non project)
1942     wxString compilerId = project ? project->GetCompilerID() : CompilerFactory::GetDefaultCompilerID();
1943 
1944     wxString defs;
1945     // gcc
1946     if (compilerId.Contains(_T("gcc")))
1947     {
1948         if ( !AddCompilerPredefinedMacrosGCC(compilerId, project, defs, parser) )
1949             return false;
1950     }
1951     // vc
1952     else if (compilerId.StartsWith(_T("msvc")))
1953     {
1954         if ( !AddCompilerPredefinedMacrosVC(compilerId, defs, parser) )
1955           return false;
1956     }
1957 
1958     TRACE(_T("NativeParser::AddCompilerPredefinedMacros: Add compiler predefined preprocessor macros:\n%s"), defs.wx_str());
1959     parser->AddPredefinedMacros(defs);
1960 
1961     TRACE(_T("NativeParser::AddCompilerPredefinedMacros: Leave"));
1962     if ( defs.IsEmpty() )
1963         return false;
1964 
1965     return true;
1966 }
1967 
AddCompilerPredefinedMacrosGCC(const wxString & compilerId,cbProject * project,wxString & defs,ParserBase * parser)1968 bool NativeParser::AddCompilerPredefinedMacrosGCC(const wxString& compilerId, cbProject* project, wxString& defs, ParserBase* parser)
1969 {
1970     Compiler* compiler = CompilerFactory::GetCompiler(compilerId);
1971     if (!compiler)
1972         return false;
1973 
1974     if (parser->Options().platformCheck && !compiler->SupportsCurrentPlatform())
1975     {
1976         TRACE(_T("NativeParser::AddCompilerPredefinedMacrosGCC: Not supported on current platform!"));
1977         return false;
1978     }
1979 
1980     wxString sep = (platform::windows ? _T("\\") : _T("/"));
1981     wxString cpp_compiler = compiler->GetMasterPath() + sep + _T("bin") + sep + compiler->GetPrograms().CPP;
1982     Manager::Get()->GetMacrosManager()->ReplaceMacros(cpp_compiler);
1983 
1984     static std::map<wxString, wxString> gccDefsMap;
1985     if ( gccDefsMap[cpp_compiler].IsEmpty() )
1986     {
1987         // Check if user set language standard version to use
1988         wxString standard = GetCompilerStandardGCC(compiler, project);
1989 
1990         // Different command on Windows and other OSes
1991 #ifdef __WXMSW__
1992         const wxString args(wxString::Format(_T(" -E -dM -x c++ %s nul"), standard.wx_str()) );
1993 #else
1994         const wxString args(wxString::Format(_T(" -E -dM -x c++ %s /dev/null"), standard.wx_str()) );
1995 #endif
1996 
1997         wxArrayString output, error;
1998         if ( !SafeExecute(compiler->GetMasterPath(), compiler->GetPrograms().CPP, args, output, error) )
1999             return false;
2000 
2001         // wxExecute can be a long action and C::B might have been shutdown in the meantime...
2002         if ( Manager::IsAppShuttingDown() )
2003             return false;
2004 
2005         wxString& gccDefs = gccDefsMap[cpp_compiler];
2006         for (size_t i = 0; i < output.Count(); ++i)
2007             gccDefs += output[i] + _T("\n");
2008 
2009         CCLogger::Get()->DebugLog(_T("NativeParser::AddCompilerPredefinedMacrosGCC: Caching predefined macros for compiler '")
2010                                   + cpp_compiler + _T("':\n") + gccDefs);
2011     }
2012 
2013     defs = gccDefsMap[cpp_compiler];
2014 
2015     return true;
2016 }
2017 
GetCompilerStandardGCC(Compiler * compiler,cbProject * project)2018 wxString NativeParser::GetCompilerStandardGCC(Compiler* compiler, cbProject* project)
2019 {
2020     // Check if user set language standard version to use
2021     // 1.) Global compiler settings are first to search in
2022     wxString standard = GetCompilerUsingStandardGCC(compiler->GetCompilerOptions());
2023     if (standard.IsEmpty() && project)
2024     {
2025         // 2.) Project compiler setting are second
2026         standard = GetCompilerUsingStandardGCC(project->GetCompilerOptions());
2027 
2028         // 3.) And targets are third in row to look for standard
2029         // NOTE: If two targets use different standards, only the one we
2030         //       encounter first (eg. c++98) will be used, and any other
2031         //       disregarded (even if it would be c++1y)
2032         if (standard.IsEmpty())
2033         {
2034             for (int i=0; i<project->GetBuildTargetsCount(); ++i)
2035             {
2036                 ProjectBuildTarget* target = project->GetBuildTarget(i);
2037                 standard = GetCompilerUsingStandardGCC(target->GetCompilerOptions());
2038 
2039                 if (!standard.IsEmpty())
2040                     break;
2041             }
2042         }
2043     }
2044     return standard;
2045 }
2046 
GetCompilerUsingStandardGCC(const wxArrayString & compilerOptions)2047 wxString NativeParser::GetCompilerUsingStandardGCC(const wxArrayString& compilerOptions)
2048 {
2049     wxString standard;
2050     for (wxArrayString::size_type i=0; i<compilerOptions.Count(); ++i)
2051     {
2052         if (compilerOptions[i].StartsWith(_T("-std=")))
2053         {
2054             standard = compilerOptions[i];
2055             CCLogger::Get()->DebugLog(wxString::Format(_T("NativeParser::GetCompilerUsingStandardGCC: Using language standard: %s"), standard.wx_str()));
2056             break;
2057         }
2058     }
2059     return standard;
2060 }
2061 
AddCompilerPredefinedMacrosVC(const wxString & compilerId,wxString & defs,ParserBase * parser)2062 bool NativeParser::AddCompilerPredefinedMacrosVC(const wxString& compilerId, wxString& defs, ParserBase* parser)
2063 {
2064     static wxString vcDefs;
2065     static bool     firstExecute = true;
2066 
2067     if (!firstExecute)
2068     {
2069         defs = vcDefs;
2070         return true;
2071     }
2072 
2073     firstExecute = false;
2074     Compiler* compiler = CompilerFactory::GetCompiler(compilerId);
2075     if (!compiler)
2076         return false;
2077 
2078     if (parser->Options().platformCheck && !compiler->SupportsCurrentPlatform())
2079     {
2080         TRACE(_T("NativeParser::AddCompilerPredefinedMacrosVC: Not supported on current platform!"));
2081         return false;
2082     }
2083 
2084     wxArrayString output, error;
2085     if ( !SafeExecute(compiler->GetMasterPath(), compiler->GetPrograms().C, wxEmptyString, output, error) )
2086         return false;
2087 
2088     // wxExecute can be a long action and C::B might have been shutdown in the meantime...
2089     if ( Manager::IsAppShuttingDown() )
2090         return false;
2091 
2092     if (error.IsEmpty())
2093     {
2094         TRACE(_T("NativeParser::AddCompilerPredefinedMacrosVC: Can't get pre-defined macros for MSVC."));
2095         return false;
2096     }
2097 
2098     wxString compilerVersionInfo = error[0];
2099     wxString tmp(_T("Microsoft (R) "));
2100     int pos = compilerVersionInfo.Find(tmp);
2101     if (pos != wxNOT_FOUND)
2102     {
2103         // in earlier versions of MSVC the compiler shows "32 bit" or "64 bit"
2104         // in more recent MSVC version the architecture (x86 or x64) is shown instead
2105         wxString bit = compilerVersionInfo.Mid(pos + tmp.Length(), 2);
2106         if      ( (bit.IsSameAs(_T("32"))) || compilerVersionInfo.Contains(_T("x86")) )
2107             defs += _T("#define _WIN32") _T("\n");
2108         else if ( (bit.IsSameAs(_T("64"))) || compilerVersionInfo.Contains(_T("x64")) )
2109             defs += _T("#define _WIN64") _T("\n");
2110     }
2111 
2112     tmp = _T("Compiler Version ");
2113     pos = compilerVersionInfo.Find(tmp);
2114     if (pos != wxNOT_FOUND)
2115     {
2116         wxString ver = compilerVersionInfo.Mid(pos + tmp.Length(), 4); // is i.e. 12.0
2117         pos = ver.Find(_T('.'));
2118         if (pos != wxNOT_FOUND)
2119         {
2120             // out of "12.0" make "1200" for the #define
2121             ver[pos]     = ver[pos + 1]; // move the mintor version first number to the dot position
2122             ver[pos + 1] = _T('0');      // add another zero at the end
2123             defs += _T("#define _MSC_VER ") + ver;
2124             // Known to now (see https://en.wikipedia.org/wiki/Visual_C%2B%2B):
2125             // MSVC++ 12.0 _MSC_VER = 1800 (Visual Studio 2013)
2126             // MSVC++ 11.0 _MSC_VER = 1700 (Visual Studio 2012)
2127             // MSVC++ 10.0 _MSC_VER = 1600 (Visual Studio 2010)
2128             // MSVC++ 9.0  _MSC_VER = 1500 (Visual Studio 2008)
2129             // MSVC++ 8.0  _MSC_VER = 1400 (Visual Studio 2005)
2130             // MSVC++ 7.1  _MSC_VER = 1310 (Visual Studio 2003)
2131             // MSVC++ 7.0  _MSC_VER = 1300
2132             // MSVC++ 6.0  _MSC_VER = 1200
2133             // MSVC++ 5.0  _MSC_VER = 1100
2134         }
2135     }
2136 
2137     defs = vcDefs;
2138     return true;
2139 }
2140 
AddProjectDefinedMacros(cbProject * project,ParserBase * parser)2141 bool NativeParser::AddProjectDefinedMacros(cbProject* project, ParserBase* parser)
2142 {
2143     if (!parser)
2144         return false;
2145 
2146     if (!project)
2147         return true;
2148 
2149     TRACE(_T("NativeParser::AddProjectDefinedMacros: Enter"));
2150 
2151     wxString compilerId = project->GetCompilerID();
2152     wxString defineCompilerSwitch(wxEmptyString);
2153     if (compilerId.Contains(_T("gcc")))
2154         defineCompilerSwitch = _T("-D");
2155     else if (compilerId.StartsWith(_T("msvc")))
2156         defineCompilerSwitch = _T("/D");
2157 
2158     if (defineCompilerSwitch.IsEmpty())
2159         return false; // no compiler options, return false
2160 
2161     wxString defs;
2162     wxArrayString opts;
2163     if (   !parser->Options().platformCheck
2164         || (parser->Options().platformCheck && project->SupportsCurrentPlatform()) )
2165     {
2166         opts = project->GetCompilerOptions();
2167     }
2168 
2169     ProjectBuildTarget* target = project->GetBuildTarget(project->GetActiveBuildTarget());
2170     if (target != NULL)
2171     {
2172         if (   !parser->Options().platformCheck
2173             || (parser->Options().platformCheck && target->SupportsCurrentPlatform()) )
2174         {
2175             wxArrayString targetOpts = target->GetCompilerOptions();
2176             for (size_t i = 0; i < targetOpts.GetCount(); ++i)
2177                 opts.Add(targetOpts[i]);
2178         }
2179     }
2180     // In case of virtual targets, collect the defines from all child targets.
2181     wxArrayString targets = project->GetExpandedVirtualBuildTargetGroup(project->GetActiveBuildTarget());
2182     for (size_t i = 0; i < targets.GetCount(); ++i)
2183     {
2184         target = project->GetBuildTarget(targets[i]);
2185         if (target != NULL)
2186         {
2187             if (   !parser->Options().platformCheck
2188                 || (parser->Options().platformCheck && target->SupportsCurrentPlatform()) )
2189             {
2190                 wxArrayString targetOpts = target->GetCompilerOptions();
2191                 for (size_t j = 0; j < targetOpts.GetCount(); ++j)
2192                     opts.Add(targetOpts[j]);
2193             }
2194         }
2195     }
2196 
2197     for (size_t i = 0; i < opts.GetCount(); ++i)
2198     {
2199         wxString def = opts[i];
2200         Manager::Get()->GetMacrosManager()->ReplaceMacros(def);
2201         if ( !def.StartsWith(defineCompilerSwitch) )
2202             continue;
2203 
2204         def = def.Right(def.Length() - defineCompilerSwitch.Length());
2205         int pos = def.Find(_T('='));
2206         if (pos != wxNOT_FOUND)
2207             def[pos] = _T(' ');
2208 
2209         defs += _T("#define ") + def + _T("\n");
2210     }
2211 
2212     TRACE(_T("Add project and current build target defined preprocessor macros:\n%s"), defs.wx_str());
2213     parser->AddPredefinedMacros(defs);
2214     TRACE(_T("NativeParser::AddProjectDefinedMacros: Leave"));
2215     if ( defs.IsEmpty() )
2216         return false;
2217 
2218     return true;
2219 }
2220 
AddCompilerIncludeDirsToParser(const Compiler * compiler,ParserBase * parser)2221 void NativeParser::AddCompilerIncludeDirsToParser(const Compiler* compiler, ParserBase* parser)
2222 {
2223     if (!compiler || !parser) return;
2224 
2225     if (   !parser->Options().platformCheck
2226         || (parser->Options().platformCheck && compiler->SupportsCurrentPlatform()) )
2227     {
2228         // these dirs were the user's compiler include search dirs
2229         AddIncludeDirsToParser(compiler->GetIncludeDirs(), wxEmptyString, parser);
2230 
2231         // find out which compiler, if gnu, do the special trick
2232         // to find it's internal include paths
2233         // but do only once per C::B session, thus cache for later calls
2234         if (compiler->GetID().Contains(_T("gcc")))
2235             AddGCCCompilerDirs(compiler->GetMasterPath(), compiler->GetPrograms().CPP, parser);
2236     }
2237 }
2238 
2239 // These dirs are the built-in search dirs of the compiler itself (GCC).
2240 // Such as when you install your MinGW GCC in E:/code/MinGW/bin
2241 // The built-in search dir may contain: E:/code/MinGW/include
GetGCCCompilerDirs(const wxString & cpp_path,const wxString & cpp_executable)2242 const wxArrayString& NativeParser::GetGCCCompilerDirs(const wxString& cpp_path, const wxString& cpp_executable)
2243 {
2244     wxString sep = (platform::windows ? _T("\\") : _T("/"));
2245     wxString cpp_compiler = cpp_path + sep + _T("bin") + sep + cpp_executable;
2246     Manager::Get()->GetMacrosManager()->ReplaceMacros(cpp_compiler);
2247 
2248     // keep the gcc compiler path's once if found across C::B session
2249     // makes opening workspaces a *lot* faster by avoiding endless calls to the compiler
2250     static std::map<wxString, wxArrayString> dirs;
2251     static wxArrayString cached_result; // avoid accessing "dirs" too often (re-entry)
2252     cached_result = dirs[cpp_compiler];
2253     if ( !cached_result.IsEmpty() )
2254         return cached_result;
2255 
2256     TRACE(_T("NativeParser::GetGCCCompilerDirs: Enter"));
2257 
2258     // for starters, only do this for gnu compiler
2259     //CCLogger::Get()->DebugLog(_T("CompilerID ") + CompilerID);
2260     //
2261     //   Windows: mingw32-g++ -v -E -x c++ nul
2262     //   Linux  : g++ -v -E -x c++ /dev/null
2263     // do the trick only for c++, not needed then for C (since this is a subset of C++)
2264 
2265     // Different command on Windows and other OSes
2266 #ifdef __WXMSW__
2267     const wxString args(_T(" -v -E -x c++ nul"));
2268 #else
2269     const wxString args(_T(" -v -E -x c++ /dev/null"));
2270 #endif
2271 
2272     wxArrayString output, error;
2273     if ( !SafeExecute(cpp_path, cpp_executable, args, output, error) )
2274         return cached_result;
2275 
2276     // wxExecute can be a long action and C::B might have been shutdown in the meantime...
2277     if ( Manager::IsAppShuttingDown() )
2278         return cached_result;
2279 
2280     // start from "#include <...>", and the path followed
2281     // let's hope this does not change too quickly, otherwise we need
2282     // to adjust our search code (for several versions ...)
2283     bool start = false;
2284     for (size_t idxCount = 0; idxCount < error.GetCount(); ++idxCount)
2285     {
2286         wxString path = error[idxCount].Trim(true).Trim(false);
2287         if (!start)
2288         {
2289             if (!path.StartsWith(_T("#include <...>")))
2290                 continue; // Next for-loop
2291             path = error[++idxCount].Trim(true).Trim(false);
2292             start = true;
2293         }
2294 
2295         wxFileName fname(path, wxEmptyString);
2296         fname.Normalize();
2297         fname.SetVolume(fname.GetVolume().MakeUpper());
2298         if (!fname.DirExists())
2299             break;
2300 
2301         dirs[cpp_compiler].Add(fname.GetPath());
2302 
2303         CCLogger::Get()->DebugLog(_T("NativeParser::GetGCCCompilerDirs: Caching GCC default include dir: ") + fname.GetPath());
2304     }
2305 
2306     TRACE(_T("NativeParser::GetGCCCompilerDirs: Leave"));
2307     return dirs[cpp_compiler];
2308 }
2309 
AddGCCCompilerDirs(const wxString & masterPath,const wxString & compilerCpp,ParserBase * parser)2310 void NativeParser::AddGCCCompilerDirs(const wxString& masterPath, const wxString& compilerCpp, ParserBase* parser)
2311 {
2312     const wxArrayString& gccDirs = GetGCCCompilerDirs(masterPath, compilerCpp);
2313     TRACE(_T("NativeParser::AddGCCCompilerDirs: Adding %lu cached gcc dirs to parser..."), static_cast<unsigned long>(gccDirs.GetCount()));
2314     for (size_t i=0; i<gccDirs.GetCount(); ++i)
2315     {
2316         parser->AddIncludeDir(gccDirs[i]);
2317         TRACE(_T("NativeParser::AddGCCCompilerDirs: Adding cached compiler dir to parser: ") + gccDirs[i]);
2318     }
2319 }
2320 
AddIncludeDirsToParser(const wxArrayString & dirs,const wxString & base,ParserBase * parser)2321 void NativeParser::AddIncludeDirsToParser(const wxArrayString& dirs, const wxString& base, ParserBase* parser)
2322 {
2323     for (unsigned int i = 0; i < dirs.GetCount(); ++i)
2324     {
2325         wxString dir = dirs[i];
2326         Manager::Get()->GetMacrosManager()->ReplaceMacros(dir);
2327         if ( !base.IsEmpty() )
2328         {
2329             wxFileName fn(dir);
2330             if ( NormalizePath(fn, base) )
2331             {
2332                 parser->AddIncludeDir(fn.GetFullPath());
2333                 TRACE(_T("NativeParser::AddIncludeDirsToParser: Adding directory to parser: ") + fn.GetFullPath());
2334             }
2335             else
2336                 CCLogger::Get()->DebugLog(F(_T("NativeParser::AddIncludeDirsToParser: Error normalizing path: '%s' from '%s'"), dir.wx_str(), base.wx_str()));
2337         }
2338         else
2339             parser->AddIncludeDir(dir); // no base path, nothing to normalise
2340     }
2341 }
2342 
SafeExecute(const wxString & app_path,const wxString & app,const wxString & args,wxArrayString & output,wxArrayString & error)2343 bool NativeParser::SafeExecute(const wxString& app_path, const wxString& app, const wxString& args, wxArrayString& output, wxArrayString& error)
2344 {
2345     wxString sep = (platform::windows ? _T("\\") : _T("/"));
2346     wxString pth = (app_path.IsEmpty() ? _T("") : (app_path + sep + _T("bin") + sep));
2347     Manager::Get()->GetMacrosManager()->ReplaceMacros(pth);
2348     wxString cmd = pth + app;
2349     Manager::Get()->GetMacrosManager()->ReplaceMacros(cmd);
2350 //    CCLogger::Get()->DebugLog(_T("NativeParser::SafeExecute: Application command: ") + cmd + _T(", path (in): ") + app_path + _T(", path (set): ") + pth + _T(", args: ") + args);
2351 
2352     if ( !wxFileExists(cmd) )
2353     {
2354         CCLogger::Get()->DebugLog(_T("NativeParser::SafeExecute: Invalid application command: ") + cmd);
2355         return false;
2356     }
2357 
2358     static bool reentry = false;
2359     if (reentry)
2360     {
2361         CCLogger::Get()->DebugLog(_T("NativeParser::SafeExecute: Re-Entry protection."));
2362         return false;
2363     }
2364     reentry = true;
2365 
2366     // Update PATH environment variable
2367     wxString path_env;
2368     if ( !pth.IsEmpty() && wxGetEnv(_T("PATH"), &path_env) )
2369     {
2370         wxString tmp_path_env = pth + (platform::windows ? _T(";") : _T(":")) + path_env;
2371         if ( !wxSetEnv(_T("PATH"), tmp_path_env) )
2372         {   CCLogger::Get()->DebugLog(_T("NativeParser::SafeExecute: Could not set PATH environment variable: ") + tmp_path_env); }
2373     }
2374 
2375     if ( wxExecute(cmd + args, output, error, wxEXEC_SYNC | wxEXEC_NODISABLE) == -1 )
2376     {
2377         CCLogger::Get()->DebugLog(_T("NativeParser::SafeExecute: Failed application call: ") + cmd + args);
2378         reentry = false;
2379         return false;
2380     }
2381 
2382     if ( !pth.IsEmpty() && !wxSetEnv(_T("PATH"), path_env) )
2383     {   CCLogger::Get()->DebugLog(_T("NativeParser::SafeExecute: Could not restore PATH environment variable: ") + path_env); }
2384 
2385     reentry = false;
2386 
2387     return true;
2388 }
2389 
OnParserStart(wxCommandEvent & event)2390 void NativeParser::OnParserStart(wxCommandEvent& event)
2391 {
2392     TRACE(_T("NativeParser::OnParserStart: Enter"));
2393 
2394     cbProject* project = static_cast<cbProject*>(event.GetClientData());
2395     wxString   prj     = (project ? project->GetTitle() : _T("*NONE*"));
2396     const ParserCommon::ParserState state = static_cast<ParserCommon::ParserState>(event.GetInt());
2397 
2398     switch (state)
2399     {
2400         case ParserCommon::ptCreateParser:
2401             CCLogger::Get()->DebugLog(F(_("NativeParser::OnParserStart: Starting batch parsing for project '%s'..."), prj.wx_str()));
2402             {
2403                 std::pair<cbProject*, ParserBase*> info = GetParserInfoByCurrentEditor();
2404                 if (info.second && m_Parser != info.second)
2405                 {
2406                     CCLogger::Get()->DebugLog(_T("NativeParser::OnParserStart: Start switch from OnParserStart::ptCreateParser"));
2407                     SwitchParser(info.first, info.second); // Calls SetParser() which also calls UpdateClassBrowserView()
2408                 }
2409             }
2410             break;
2411 
2412         case ParserCommon::ptAddFileToParser:
2413             CCLogger::Get()->DebugLog(F(_("NativeParser::OnParserStart: Starting add file parsing for project '%s'..."), prj.wx_str()));
2414             break;
2415 
2416         case ParserCommon::ptReparseFile:
2417             CCLogger::Get()->DebugLog(F(_("NativeParser::OnParserStart: Starting re-parsing for project '%s'..."), prj.wx_str()));
2418             break;
2419 
2420         case ParserCommon::ptUndefined:
2421             if (event.GetString().IsEmpty())
2422                 CCLogger::Get()->DebugLog(F(_("NativeParser::OnParserStart: Batch parsing error in project '%s'"), prj.wx_str()));
2423             else
2424                 CCLogger::Get()->DebugLog(F(_("NativeParser::OnParserStart: %s in project '%s'"), event.GetString().wx_str(), prj.wx_str()));
2425             return;
2426 
2427         default:
2428             break;
2429     }
2430 
2431     event.Skip();
2432 
2433     TRACE(_T("NativeParser::OnParserStart: Leave"));
2434 }
2435 
OnParserEnd(wxCommandEvent & event)2436 void NativeParser::OnParserEnd(wxCommandEvent& event)
2437 {
2438     TRACE(_T("NativeParser::OnParserEnd: Enter"));
2439 
2440     ParserBase* parser = reinterpret_cast<ParserBase*>(event.GetEventObject());
2441     cbProject* project = static_cast<cbProject*>(event.GetClientData());
2442     wxString prj = (project ? project->GetTitle() : _T("*NONE*"));
2443     const ParserCommon::ParserState state = static_cast<ParserCommon::ParserState>(event.GetInt());
2444 
2445     switch (state)
2446     {
2447         case ParserCommon::ptCreateParser:
2448             {
2449                 wxString log(F(_("NativeParser::OnParserEnd: Project '%s' parsing stage done!"), prj.wx_str()));
2450                 CCLogger::Get()->Log(log);
2451                 CCLogger::Get()->DebugLog(log);
2452             }
2453             break;
2454 
2455         case ParserCommon::ptAddFileToParser:
2456             break;
2457 
2458         case ParserCommon::ptReparseFile:
2459             if (parser != m_Parser)
2460             {
2461                 std::pair<cbProject*, ParserBase*> info = GetParserInfoByCurrentEditor();
2462                 if (info.second && info.second != m_Parser)
2463                 {
2464                     CCLogger::Get()->DebugLog(_T("NativeParser::OnParserEnd: Start switch from OnParserEnd::ptReparseFile"));
2465                     SwitchParser(info.first, info.second); // Calls SetParser() which also calls UpdateClassBrowserView()
2466                 }
2467             }
2468             break;
2469 
2470         case ParserCommon::ptUndefined:
2471             CCLogger::Get()->DebugLog(F(_T("NativeParser::OnParserEnd: Parser event handling error of project '%s'"), prj.wx_str()));
2472             return;
2473 
2474         default:
2475             break;
2476     }
2477 
2478     if (!event.GetString().IsEmpty())
2479         CCLogger::Get()->DebugLog(event.GetString());
2480 
2481     UpdateClassBrowser();
2482 
2483     // In this case, the parser will record all the cbprojects' token, so this will start parsing
2484     // the next cbproject.
2485     TRACE(_T("NativeParser::OnParserEnd: Starting m_TimerParsingOneByOne."));
2486     m_TimerParsingOneByOne.Start(500, wxTIMER_ONE_SHOT);
2487 
2488     // both NativeParser and CodeCompletion class need to handle this event
2489     event.Skip();
2490     TRACE(_T("NativeParser::OnParserEnd: Leave"));
2491 }
2492 
OnParsingOneByOneTimer(cb_unused wxTimerEvent & event)2493 void NativeParser::OnParsingOneByOneTimer(cb_unused wxTimerEvent& event)
2494 {
2495     TRACE(_T("NativeParser::OnParsingOneByOneTimer: Enter"));
2496 
2497     std::pair<cbProject*, ParserBase*> info = GetParserInfoByCurrentEditor();
2498     if (m_ParserPerWorkspace)
2499     {
2500         // If there is no parser and an active editor file can be obtained, parse the file according the active project
2501         if (!info.second && Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor())
2502         {
2503             // NOTE (Morten#1#): Shouldn't this actually be a temp parser??? I think this screws things with re-opening files on load of a projects...
2504             AddProjectToParser(info.first);
2505             CCLogger::Get()->DebugLog(_T("NativeParser::OnParsingOneByOneTimer: Add foreign active editor to current active project's parser."));
2506         }
2507         // Otherwise, there is a parser already present
2508         else
2509         {
2510             // First: try to parse the active project (if any)
2511             cbProject* activeProject = Manager::Get()->GetProjectManager()->GetActiveProject();
2512             if (m_ParsedProjects.find(activeProject) == m_ParsedProjects.end())
2513             {
2514                 AddProjectToParser(activeProject);
2515                 CCLogger::Get()->DebugLog(_T("NativeParser::OnParsingOneByOneTimer: Add new (un-parsed) active project to parser."));
2516             }
2517             // Else: add remaining projects one-by-one (if any)
2518             else
2519             {
2520                 ProjectsArray* projs = Manager::Get()->GetProjectManager()->GetProjects();
2521                 // loop on the whole workspace, and only add a new project to the parser
2522                 // here the "new" means a project haven't been parsed. Once it was parsed, it is
2523                 // added to the m_ParsedProjects
2524                 for (size_t i = 0; i < projs->GetCount(); ++i)
2525                 {
2526                     // Only add, if the project is not already parsed
2527                     if (m_ParsedProjects.find(projs->Item(i)) == m_ParsedProjects.end())
2528                     {
2529                         // AddProjectToParser return true means there are something need to parse, otherwise, it is false
2530                         if (!AddProjectToParser(projs->Item(i)))
2531                         {
2532                             CCLogger::Get()->Log(_T("NativeParser::OnParsingOneByOneTimer: nothing need to parse in this project, try next project."));
2533                             continue;
2534                         }
2535 
2536                         CCLogger::Get()->DebugLog(_T("NativeParser::OnParsingOneByOneTimer: Add additional (next) project to parser."));
2537                         break;
2538                     }
2539                 }
2540             }
2541         }
2542     }
2543     else if (info.first && !info.second)
2544     {
2545         info.second = CreateParser(info.first);
2546         if (info.second && info.second != m_Parser)
2547         {
2548             CCLogger::Get()->DebugLog(_T("NativeParser::OnParsingOneByOneTimer: Start switch from OnParsingOneByOneTimer"));
2549             SwitchParser(info.first, info.second); // Calls SetParser() which also calls UpdateClassBrowserView()
2550         }
2551     }
2552     TRACE(_T("NativeParser::OnParsingOneByOneTimer: Leave"));
2553 }
2554 
OnEditorActivated(EditorBase * editor)2555 void NativeParser::OnEditorActivated(EditorBase* editor)
2556 {
2557     cbEditor* curEditor = Manager::Get()->GetEditorManager()->GetBuiltinEditor(editor);
2558     if (!curEditor)
2559         return;
2560 
2561     const wxString& activatedFile = editor->GetFilename();
2562     if ( !wxFile::Exists(activatedFile) )
2563         return;
2564 
2565     cbProject* project = GetProjectByEditor(curEditor);
2566     const int pos = m_StandaloneFiles.Index(activatedFile);
2567     if (project && pos != wxNOT_FOUND)
2568     {
2569         m_StandaloneFiles.RemoveAt(pos);
2570         if (m_StandaloneFiles.IsEmpty())
2571             DeleteParser(NULL);
2572         else
2573             RemoveFileFromParser(NULL, activatedFile);
2574     }
2575 
2576     ParserBase* parser = GetParserByProject(project);
2577     if (!parser)
2578     {
2579         ParserCommon::EFileType ft = ParserCommon::FileType(activatedFile);
2580         if (ft != ParserCommon::ftOther && (parser = CreateParser(project)))
2581         {
2582             if (!project && AddFileToParser(project, activatedFile, parser) )
2583             {
2584                 wxFileName file(activatedFile);
2585                 parser->AddIncludeDir(file.GetPath());
2586                 m_StandaloneFiles.Add(activatedFile);
2587             }
2588         }
2589         else
2590             parser = m_TempParser; // do *not* instead by SetParser(m_TempParser)
2591     }
2592     else if (!project)
2593     {
2594         if (   !parser->IsFileParsed(activatedFile)
2595             && m_StandaloneFiles.Index(activatedFile) == wxNOT_FOUND
2596             && AddFileToParser(project, activatedFile, parser) )
2597         {
2598             wxFileName file(activatedFile);
2599             parser->AddIncludeDir(file.GetPath());
2600             m_StandaloneFiles.Add(activatedFile);
2601         }
2602     }
2603 
2604     if (parser != m_Parser)
2605     {
2606         CCLogger::Get()->DebugLog(_T("Start switch from OnEditorActivatedTimer"));
2607         SwitchParser(project, parser); // Calls SetParser() which also calls UpdateClassBrowserView()
2608     }
2609 
2610     if (m_ClassBrowser)
2611     {
2612         if      (m_Parser->ClassBrowserOptions().displayFilter == bdfFile)
2613             m_ClassBrowser->UpdateClassBrowserView(true); // check header and implementation file swap
2614         else if (   m_ParserPerWorkspace // project view only available in case of one parser per WS
2615                  && m_Parser->ClassBrowserOptions().displayFilter == bdfProject)
2616             m_ClassBrowser->UpdateClassBrowserView();
2617     }
2618 }
2619 
OnEditorClosed(EditorBase * editor)2620 void NativeParser::OnEditorClosed(EditorBase* editor)
2621 {
2622     // the caller of the function should guarantee its a built-in editor
2623     wxString filename = editor->GetFilename();
2624     const int pos = m_StandaloneFiles.Index(filename);
2625     if (pos != wxNOT_FOUND)
2626     {
2627         m_StandaloneFiles.RemoveAt(pos);
2628         if (m_StandaloneFiles.IsEmpty())
2629             DeleteParser(NULL);
2630         else
2631             RemoveFileFromParser(NULL, filename);
2632     }
2633 }
2634 
InitCCSearchVariables()2635 void NativeParser::InitCCSearchVariables()
2636 {
2637     m_LastControl       = nullptr;
2638     m_LastFunctionIndex = -1;
2639     m_LastLine          = -1;
2640     m_LastResult        = -1;
2641     m_LastFile.Clear();
2642     m_LastNamespace.Clear();
2643     m_LastPROC.Clear();
2644 
2645     Reset();
2646 }
2647 
AddProjectToParser(cbProject * project)2648 bool NativeParser::AddProjectToParser(cbProject* project)
2649 {
2650     wxString prj = (project ? project->GetTitle() : _T("*NONE*"));
2651     ParserBase* parser = GetParserByProject(project);
2652     if (parser)
2653         return false;
2654 
2655     if (m_ParsedProjects.empty())
2656         return false;
2657 
2658     m_ParsedProjects.insert(project);
2659     parser = GetParserByProject(project);
2660     if (!parser)
2661         return false;
2662     else if (!parser->UpdateParsingProject(project))
2663     {
2664         m_ParsedProjects.erase(project);
2665         return false;
2666     }
2667 
2668     // TODO (ollydbg#1#) did exactly the same thing as the function NativeParser::DoFullParsing()?
2669     wxString log(F(_("NativeParser::AddProjectToParser: Add project (%s) to parser"), prj.wx_str()));
2670     CCLogger::Get()->Log(log);
2671     CCLogger::Get()->DebugLog(log);
2672 
2673     bool needParseMacros = false;
2674 
2675     if (!AddCompilerDirs(project, parser))
2676         CCLogger::Get()->DebugLog(_T("NativeParser::AddProjectToParser: AddCompilerDirs failed!"));
2677 
2678     if (!AddCompilerPredefinedMacros(project, parser))
2679         CCLogger::Get()->DebugLog(_T("NativeParser::AddProjectToParser: AddCompilerPredefinedMacros failed!"));
2680     else
2681         needParseMacros = true;
2682 
2683     if (!AddProjectDefinedMacros(project, parser))
2684         CCLogger::Get()->DebugLog(_T("NativeParser::AddProjectToParser: AddProjectDefinedMacros failed!"));
2685     else
2686     {
2687         if(!needParseMacros)
2688             needParseMacros = true;
2689     }
2690 
2691     if (project)
2692     {
2693         size_t fileCount = 0;
2694         for (FilesList::const_iterator fl_it = project->GetFilesList().begin(); fl_it != project->GetFilesList().end(); ++fl_it)
2695         {
2696             ProjectFile* pf = *fl_it;
2697             if (pf && FileTypeOf(pf->relativeFilename) == ftHeader)
2698             {
2699                 if ( AddFileToParser(project, pf->file.GetFullPath(), parser) )
2700                     ++fileCount;
2701             }
2702         }
2703         for (FilesList::const_iterator fl_it = project->GetFilesList().begin(); fl_it != project->GetFilesList().end(); ++fl_it)
2704         {
2705             ProjectFile* pf = *fl_it;
2706             if (pf && (FileTypeOf(pf->relativeFilename) == ftSource || FileTypeOf(pf->relativeFilename) == ftTemplateSource) )
2707             {
2708                 if ( AddFileToParser(project, pf->file.GetFullPath(), parser) )
2709                     fileCount++;
2710             }
2711         }
2712 
2713         CCLogger::Get()->DebugLog(F(_("NativeParser::AddProjectToParser: Done adding %lu files of project (%s) to parser."), static_cast<unsigned long>(fileCount), prj.wx_str()));
2714 
2715         // in some cases, all the files were already be parsed, so fileCount is still 0
2716         return ((fileCount>0) || needParseMacros);
2717     }
2718     else
2719     {
2720         EditorBase* editor = Manager::Get()->GetEditorManager()->GetActiveEditor();
2721         if (editor && AddFileToParser(project, editor->GetFilename(), parser))
2722         {
2723             wxFileName file(editor->GetFilename());
2724             parser->AddIncludeDir(file.GetPath());
2725             m_StandaloneFiles.Add(editor->GetFilename());
2726 
2727             CCLogger::Get()->DebugLog(F(_("NativeParser::AddProjectToParser: Done adding stand-alone file (%s) of editor to parser."), editor->GetFilename().wx_str()));
2728             return true;
2729         }
2730     }
2731     return false;
2732 }
2733 
RemoveProjectFromParser(cbProject * project)2734 bool NativeParser::RemoveProjectFromParser(cbProject* project)
2735 {
2736     ParserBase* parser = GetParserByProject(project);
2737     if (!parser)
2738         return false;
2739 
2740     // Remove from the cbProject set
2741     m_ParsedProjects.erase(project);
2742 
2743     if (!project || m_ParsedProjects.empty())
2744         return true;
2745 
2746     wxString prj = (project ? project->GetTitle() : _T("*NONE*"));
2747     wxString log(F(_("Remove project (%s) from parser"), prj.wx_str()));
2748     CCLogger::Get()->Log(log);
2749     CCLogger::Get()->DebugLog(log);
2750 
2751     for (FilesList::const_iterator fl_it = project->GetFilesList().begin(); fl_it != project->GetFilesList().end(); ++fl_it)
2752     {
2753         ProjectFile* pf = *fl_it;
2754         if (pf && ParserCommon::FileType(pf->relativeFilename) != ParserCommon::ftOther)
2755             RemoveFileFromParser(project, pf->file.GetFullPath());
2756     }
2757 
2758     return true;
2759 }
2760